image: Migrate to Node.js.
Browse filesSigned-off-by: Hadad <[email protected]>
- .gitignore +24 -0
- Dockerfile +4 -10
- README.md +5 -2
- app.py +0 -84
- assets/css/styles.css +523 -0
- assets/css/webLoader.css +250 -0
- assets/plugins/imageGenerator.js +22 -0
- assets/plugins/webLoader.js +340 -0
- config.js +33 -0
- config.py +0 -57
- package.json +25 -0
- public/webViewer.ejs +386 -0
- requirements.txt +0 -2
- server.js +45 -0
- src/controllers/imageController.js +140 -0
- src/middleware/viewEngine.js +15 -0
- src/routes/imageRoutes.js +17 -0
- src/services/imageGenerator.js +157 -0
- src/services/storageManager.js +64 -0
- src/utils/idGenerator.js +10 -0
- src/utils/validators.js +39 -0
- src/utils/viewRenderer.js +10 -0
.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
npm-debug.log*
|
| 3 |
+
yarn-debug.log*
|
| 4 |
+
yarn-error.log*
|
| 5 |
+
.DS_Store
|
| 6 |
+
*.log
|
| 7 |
+
logs/
|
| 8 |
+
*.pid
|
| 9 |
+
*.seed
|
| 10 |
+
*.pid.lock
|
| 11 |
+
.npm
|
| 12 |
+
.eslintcache
|
| 13 |
+
.node_repl_history
|
| 14 |
+
.yarn-integrity
|
| 15 |
+
dist/
|
| 16 |
+
build/
|
| 17 |
+
.vscode/
|
| 18 |
+
.idea/
|
| 19 |
+
*.swp
|
| 20 |
+
*.swo
|
| 21 |
+
*~
|
| 22 |
+
.cache/
|
| 23 |
+
tmp/
|
| 24 |
+
temp/
|
Dockerfile
CHANGED
|
@@ -3,20 +3,14 @@
|
|
| 3 |
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
#
|
| 5 |
|
| 6 |
-
|
| 7 |
-
FROM python:latest
|
| 8 |
|
| 9 |
-
# Set the main working directory inside the container
|
| 10 |
WORKDIR /app
|
| 11 |
|
| 12 |
-
# Copy all files into the container
|
| 13 |
COPY . .
|
| 14 |
|
| 15 |
-
|
| 16 |
-
RUN pip install -r requirements.txt
|
| 17 |
|
| 18 |
-
|
| 19 |
-
EXPOSE 7860
|
| 20 |
|
| 21 |
-
|
| 22 |
-
CMD ["python", "app.py"]
|
|
|
|
| 3 |
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
#
|
| 5 |
|
| 6 |
+
FROM node:latest
|
|
|
|
| 7 |
|
|
|
|
| 8 |
WORKDIR /app
|
| 9 |
|
|
|
|
| 10 |
COPY . .
|
| 11 |
|
| 12 |
+
RUN npm install
|
|
|
|
| 13 |
|
| 14 |
+
EXPOSE 3000
|
|
|
|
| 15 |
|
| 16 |
+
CMD ["npm", "start"]
|
|
|
README.md
CHANGED
|
@@ -6,8 +6,8 @@ emoji: ⚡
|
|
| 6 |
colorFrom: indigo
|
| 7 |
colorTo: purple
|
| 8 |
sdk: docker
|
| 9 |
-
app_port:
|
| 10 |
-
pinned:
|
| 11 |
# Used to promote this Hugging Face Space
|
| 12 |
models:
|
| 13 |
- hadadrjt/JARVIS
|
|
@@ -81,6 +81,9 @@ models:
|
|
| 81 |
- facebook/MobileLLM-R1-950M
|
| 82 |
- Alibaba-NLP/Tongyi-DeepResearch-30B-A3B
|
| 83 |
- openbmb/VoxCPM-0.5B
|
|
|
|
|
|
|
|
|
|
| 84 |
# Used to promote this Hugging Face Space
|
| 85 |
datasets:
|
| 86 |
- fka/awesome-chatgpt-prompts
|
|
|
|
| 6 |
colorFrom: indigo
|
| 7 |
colorTo: purple
|
| 8 |
sdk: docker
|
| 9 |
+
app_port: 3000
|
| 10 |
+
pinned: true
|
| 11 |
# Used to promote this Hugging Face Space
|
| 12 |
models:
|
| 13 |
- hadadrjt/JARVIS
|
|
|
|
| 81 |
- facebook/MobileLLM-R1-950M
|
| 82 |
- Alibaba-NLP/Tongyi-DeepResearch-30B-A3B
|
| 83 |
- openbmb/VoxCPM-0.5B
|
| 84 |
+
- Wan-AI/Wan2.2-Animate-14B
|
| 85 |
+
- decart-ai/Lucy-Edit-Dev
|
| 86 |
+
- ibm-granite/granite-docling-258M
|
| 87 |
# Used to promote this Hugging Face Space
|
| 88 |
datasets:
|
| 89 |
- fka/awesome-chatgpt-prompts
|
app.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
| 1 |
-
#
|
| 2 |
-
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
-
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
-
#
|
| 5 |
-
|
| 6 |
-
import os
|
| 7 |
-
from openai import OpenAI, OpenAIError
|
| 8 |
-
import base64
|
| 9 |
-
from io import BytesIO
|
| 10 |
-
from PIL import Image
|
| 11 |
-
from config import MODEL, SIZE, DESCRIPTION, EXAMPLES
|
| 12 |
-
import gradio as gr
|
| 13 |
-
|
| 14 |
-
def playground(model, size, prompt, mode="run"):
|
| 15 |
-
if not model or not size or not prompt or not prompt.strip():
|
| 16 |
-
return gr.update(interactive=False) if mode == "helper" else None
|
| 17 |
-
if mode == "helper":
|
| 18 |
-
return gr.update(interactive=True)
|
| 19 |
-
|
| 20 |
-
try:
|
| 21 |
-
response = OpenAI(
|
| 22 |
-
base_url=os.getenv("OPENAI_API_BASE_URL"),
|
| 23 |
-
api_key=os.getenv("OPENAI_API_KEY")
|
| 24 |
-
).images.generate(
|
| 25 |
-
model=model,
|
| 26 |
-
prompt=prompt,
|
| 27 |
-
size=size,
|
| 28 |
-
n=1,
|
| 29 |
-
response_format="b64_json"
|
| 30 |
-
)
|
| 31 |
-
|
| 32 |
-
except OpenAIError:
|
| 33 |
-
raise gr.Error(
|
| 34 |
-
"The server is currently busy. Please try again later, "
|
| 35 |
-
"or consider switching to another model, "
|
| 36 |
-
"as each model has a different inference speed."
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
return Image.open(
|
| 40 |
-
BytesIO(
|
| 41 |
-
base64.b64decode(
|
| 42 |
-
response.data[0].b64_json
|
| 43 |
-
)
|
| 44 |
-
)
|
| 45 |
-
)
|
| 46 |
-
|
| 47 |
-
with gr.Blocks(theme=gr.themes.Soft()) as app:
|
| 48 |
-
with gr.Sidebar(): gr.HTML(DESCRIPTION)
|
| 49 |
-
image = gr.Image(label="GENERATED IMAGE", type="pil")
|
| 50 |
-
model = gr.Dropdown(MODEL, label="MODEL")
|
| 51 |
-
size = gr.Dropdown(SIZE, label="IMAGE SIZE")
|
| 52 |
-
input = gr.Textbox(
|
| 53 |
-
label="INSTRUCTIONS",
|
| 54 |
-
placeholder="Insert your prompt here...",
|
| 55 |
-
lines=2
|
| 56 |
-
)
|
| 57 |
-
submit = gr.Button("Generate", interactive=False)
|
| 58 |
-
[
|
| 59 |
-
(component.change if hasattr(component, "change") else component)(
|
| 60 |
-
(lambda model, size, prompt:
|
| 61 |
-
playground(model, size, prompt, "helper"))
|
| 62 |
-
if hasattr(component, "change") else playground,
|
| 63 |
-
[model, size, input],
|
| 64 |
-
[submit] if hasattr(component, "change") else [image]
|
| 65 |
-
)
|
| 66 |
-
for component in [
|
| 67 |
-
model,
|
| 68 |
-
size,
|
| 69 |
-
input,
|
| 70 |
-
submit.click,
|
| 71 |
-
input.submit
|
| 72 |
-
]
|
| 73 |
-
]
|
| 74 |
-
gr.Examples(EXAMPLES, inputs=[model, size, input], outputs=[image],
|
| 75 |
-
fn=playground, cache_examples=False, run_on_click=True)
|
| 76 |
-
|
| 77 |
-
app.queue(
|
| 78 |
-
max_size=3,
|
| 79 |
-
default_concurrency_limit=3
|
| 80 |
-
).launch(
|
| 81 |
-
show_api=False,
|
| 82 |
-
server_name="0.0.0.0",
|
| 83 |
-
pwa=True
|
| 84 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/css/styles.css
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
* SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
* {
|
| 7 |
+
margin: 0;
|
| 8 |
+
padding: 0;
|
| 9 |
+
box-sizing: border-box;
|
| 10 |
+
-webkit-font-smoothing: antialiased;
|
| 11 |
+
-moz-osx-font-smoothing: grayscale;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
:root {
|
| 15 |
+
--bg-primary: #0d0d0d;
|
| 16 |
+
--bg-secondary: #151515;
|
| 17 |
+
--bg-tertiary: #1c1c1c;
|
| 18 |
+
--bg-card: #181818;
|
| 19 |
+
--border: rgba(255, 255, 255, 0.05);
|
| 20 |
+
--border-hover: rgba(255, 255, 255, 0.1);
|
| 21 |
+
--text-primary: #fafafa;
|
| 22 |
+
--text-secondary: #9ca3af;
|
| 23 |
+
--text-muted: #6b7280;
|
| 24 |
+
--accent: #7c3aed;
|
| 25 |
+
--accent-hover: #6d28d9;
|
| 26 |
+
--danger: #ef4444;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
body {
|
| 30 |
+
font-family: 'Inter', system-ui, sans-serif;
|
| 31 |
+
background: var(--bg-primary);
|
| 32 |
+
color: var(--text-primary);
|
| 33 |
+
min-height: 100vh;
|
| 34 |
+
line-height: 1.6;
|
| 35 |
+
overflow-x: hidden;
|
| 36 |
+
word-wrap: break-word;
|
| 37 |
+
word-break: break-word;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.gradient-bg {
|
| 41 |
+
background:
|
| 42 |
+
radial-gradient(
|
| 43 |
+
ellipse at top left,
|
| 44 |
+
rgba(124, 58, 237, 0.03) 0%,
|
| 45 |
+
transparent 50%
|
| 46 |
+
),
|
| 47 |
+
radial-gradient(
|
| 48 |
+
ellipse at bottom right,
|
| 49 |
+
rgba(236, 72, 153, 0.03) 0%,
|
| 50 |
+
transparent 50%
|
| 51 |
+
);
|
| 52 |
+
position: fixed;
|
| 53 |
+
inset: 0;
|
| 54 |
+
z-index: -1;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.gradient-mesh {
|
| 58 |
+
background-image:
|
| 59 |
+
radial-gradient(
|
| 60 |
+
at 20% 40%,
|
| 61 |
+
hsla(280, 70%, 40%, 0.05) 0px,
|
| 62 |
+
transparent 50%
|
| 63 |
+
),
|
| 64 |
+
radial-gradient(
|
| 65 |
+
at 80% 20%,
|
| 66 |
+
hsla(320, 70%, 40%, 0.04) 0px,
|
| 67 |
+
transparent 50%
|
| 68 |
+
),
|
| 69 |
+
radial-gradient(
|
| 70 |
+
at 40% 80%,
|
| 71 |
+
hsla(200, 70%, 40%, 0.03) 0px,
|
| 72 |
+
transparent 50%
|
| 73 |
+
);
|
| 74 |
+
position: fixed;
|
| 75 |
+
inset: 0;
|
| 76 |
+
z-index: -1;
|
| 77 |
+
opacity: 0.8;
|
| 78 |
+
animation: meshFloat 100s ease-in-out infinite;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
@keyframes meshFloat {
|
| 82 |
+
0%, 100% {
|
| 83 |
+
transform: translate(0, 0) scale(1) rotate(0deg);
|
| 84 |
+
}
|
| 85 |
+
33% {
|
| 86 |
+
transform: translate(-30px, -40px) scale(1.02) rotate(1deg);
|
| 87 |
+
}
|
| 88 |
+
66% {
|
| 89 |
+
transform: translate(30px, -30px) scale(0.98) rotate(-1deg);
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.card {
|
| 94 |
+
background: rgba(24, 24, 24, 0.4);
|
| 95 |
+
backdrop-filter: blur(60px);
|
| 96 |
+
border: 1px solid var(--border);
|
| 97 |
+
border-radius: clamp(16px, 2vw, 24px);
|
| 98 |
+
transition: all 0.8s cubic-bezier(0.23, 1, 0.320, 1);
|
| 99 |
+
position: relative;
|
| 100 |
+
overflow: hidden;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.card::before {
|
| 104 |
+
content: '';
|
| 105 |
+
position: absolute;
|
| 106 |
+
inset: 0;
|
| 107 |
+
background: radial-gradient(
|
| 108 |
+
800px circle at var(--mouse-x) var(--mouse-y),
|
| 109 |
+
rgba(124, 58, 237, 0.02),
|
| 110 |
+
transparent 40%
|
| 111 |
+
);
|
| 112 |
+
opacity: 0;
|
| 113 |
+
transition: opacity 0.3s;
|
| 114 |
+
pointer-events: none;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.card:hover::before {
|
| 118 |
+
opacity: 1;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.card:hover {
|
| 122 |
+
border-color: var(--border-hover);
|
| 123 |
+
transform: translateY(-2px);
|
| 124 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.btn {
|
| 128 |
+
border-radius: clamp(12px, 1.5vw, 16px);
|
| 129 |
+
font-weight: 600;
|
| 130 |
+
transition: all 0.5s cubic-bezier(0.23, 1, 0.320, 1);
|
| 131 |
+
position: relative;
|
| 132 |
+
overflow: hidden;
|
| 133 |
+
display: inline-flex;
|
| 134 |
+
align-items: center;
|
| 135 |
+
justify-content: center;
|
| 136 |
+
gap: clamp(8px, 1vw, 12px);
|
| 137 |
+
border: none;
|
| 138 |
+
cursor: pointer;
|
| 139 |
+
z-index: 1;
|
| 140 |
+
font-size: clamp(14px, 1.5vw, 16px);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.btn::after {
|
| 144 |
+
content: '';
|
| 145 |
+
position: absolute;
|
| 146 |
+
inset: 0;
|
| 147 |
+
background: linear-gradient(
|
| 148 |
+
45deg,
|
| 149 |
+
transparent 30%,
|
| 150 |
+
rgba(255, 255, 255, 0.05) 50%,
|
| 151 |
+
transparent 70%
|
| 152 |
+
);
|
| 153 |
+
transform: translateX(-100%) skewX(-20deg);
|
| 154 |
+
transition: transform 1.5s;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.btn:hover::after {
|
| 158 |
+
transform: translateX(200%) skewX(-20deg);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.btn-primary {
|
| 162 |
+
background: linear-gradient(
|
| 163 |
+
135deg,
|
| 164 |
+
var(--accent),
|
| 165 |
+
var(--accent-hover)
|
| 166 |
+
);
|
| 167 |
+
color: white;
|
| 168 |
+
padding: clamp(14px, 2vw, 20px) clamp(28px, 4vw, 48px);
|
| 169 |
+
box-shadow: 0 8px 32px rgba(124, 58, 237, 0.2);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.btn-primary:hover:not(:disabled) {
|
| 173 |
+
transform: translateY(-3px) scale(1.02);
|
| 174 |
+
box-shadow: 0 16px 40px rgba(124, 58, 237, 0.3);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.btn-primary:disabled {
|
| 178 |
+
opacity: 0.5;
|
| 179 |
+
cursor: not-allowed;
|
| 180 |
+
transform: none !important;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.btn-danger {
|
| 184 |
+
background: linear-gradient(135deg, var(--danger), #dc2626);
|
| 185 |
+
color: white;
|
| 186 |
+
padding: clamp(14px, 2vw, 20px) clamp(28px, 4vw, 48px);
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.modal-overlay {
|
| 190 |
+
position: fixed;
|
| 191 |
+
inset: 0;
|
| 192 |
+
background: rgba(0, 0, 0, 0.96);
|
| 193 |
+
backdrop-filter: blur(40px);
|
| 194 |
+
display: flex;
|
| 195 |
+
align-items: center;
|
| 196 |
+
justify-content: center;
|
| 197 |
+
z-index: 10000;
|
| 198 |
+
animation: fadeIn 0.4s ease;
|
| 199 |
+
padding: clamp(16px, 2vw, 24px);
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
.modal-content {
|
| 203 |
+
background: linear-gradient(
|
| 204 |
+
135deg,
|
| 205 |
+
rgba(28, 28, 28, 0.98),
|
| 206 |
+
rgba(24, 24, 24, 0.98)
|
| 207 |
+
);
|
| 208 |
+
border-radius: clamp(20px, 2.5vw, 32px);
|
| 209 |
+
padding: clamp(24px, 4vw, 48px);
|
| 210 |
+
max-width: min(90%, 720px);
|
| 211 |
+
width: 100%;
|
| 212 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 213 |
+
box-shadow: 0 40px 100px rgba(0, 0, 0, 0.8);
|
| 214 |
+
animation: slideUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
| 215 |
+
position: relative;
|
| 216 |
+
overflow: hidden;
|
| 217 |
+
word-wrap: break-word;
|
| 218 |
+
word-break: break-word;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
.modal-content::before {
|
| 222 |
+
content: '';
|
| 223 |
+
position: absolute;
|
| 224 |
+
top: -50%;
|
| 225 |
+
left: -50%;
|
| 226 |
+
width: 200%;
|
| 227 |
+
height: 200%;
|
| 228 |
+
background: conic-gradient(
|
| 229 |
+
from 0deg at 50% 50%,
|
| 230 |
+
rgba(124, 58, 237, 0.04),
|
| 231 |
+
rgba(236, 72, 153, 0.04),
|
| 232 |
+
rgba(59, 130, 246, 0.04),
|
| 233 |
+
rgba(124, 58, 237, 0.04)
|
| 234 |
+
);
|
| 235 |
+
animation: rotate 90s linear infinite;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
@keyframes rotate {
|
| 239 |
+
from { transform: rotate(0deg); }
|
| 240 |
+
to { transform: rotate(360deg); }
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
@keyframes fadeIn {
|
| 244 |
+
from { opacity: 0; }
|
| 245 |
+
to { opacity: 1; }
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
@keyframes slideUp {
|
| 249 |
+
from {
|
| 250 |
+
opacity: 0;
|
| 251 |
+
transform: translateY(60px) scale(0.9);
|
| 252 |
+
}
|
| 253 |
+
to {
|
| 254 |
+
opacity: 1;
|
| 255 |
+
transform: translateY(0) scale(1);
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
.progress-bar {
|
| 260 |
+
width: 100%;
|
| 261 |
+
height: 4px;
|
| 262 |
+
background: rgba(124, 58, 237, 0.1);
|
| 263 |
+
border-radius: 2px;
|
| 264 |
+
overflow: hidden;
|
| 265 |
+
margin: 20px 0;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.progress-fill {
|
| 269 |
+
height: 100%;
|
| 270 |
+
background: linear-gradient(90deg, var(--accent), #ec4899);
|
| 271 |
+
transition: width 0.3s ease;
|
| 272 |
+
animation: shimmer 2s infinite;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
@keyframes shimmer {
|
| 276 |
+
0% { opacity: 0.8; }
|
| 277 |
+
50% { opacity: 1; }
|
| 278 |
+
100% { opacity: 0.8; }
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.image-output-section {
|
| 282 |
+
min-height: clamp(120px, 15vh, 200px);
|
| 283 |
+
border: 2px dashed var(--border);
|
| 284 |
+
border-radius: clamp(20px, 2.5vw, 32px);
|
| 285 |
+
padding: clamp(32px, 4vw, 60px);
|
| 286 |
+
margin-bottom: clamp(32px, 4vw, 56px);
|
| 287 |
+
text-align: center;
|
| 288 |
+
transition: all 0.5s ease;
|
| 289 |
+
background: rgba(24, 24, 24, 0.2);
|
| 290 |
+
position: relative;
|
| 291 |
+
overflow: hidden;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.image-output-section.has-images {
|
| 295 |
+
border: none;
|
| 296 |
+
padding: 0;
|
| 297 |
+
background: transparent;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.image-grid {
|
| 301 |
+
display: grid;
|
| 302 |
+
gap: clamp(20px, 2.5vw, 36px);
|
| 303 |
+
grid-template-columns: repeat(
|
| 304 |
+
auto-fit,
|
| 305 |
+
minmax(
|
| 306 |
+
min(100%, clamp(280px, 35vw, 400px)),
|
| 307 |
+
1fr
|
| 308 |
+
)
|
| 309 |
+
);
|
| 310 |
+
justify-content: center;
|
| 311 |
+
justify-items: center;
|
| 312 |
+
align-items: start;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.image-card {
|
| 316 |
+
position: relative;
|
| 317 |
+
border-radius: clamp(16px, 2vw, 24px);
|
| 318 |
+
overflow: hidden;
|
| 319 |
+
background: var(--bg-tertiary);
|
| 320 |
+
border: 1px solid var(--border);
|
| 321 |
+
transition: all 0.8s cubic-bezier(0.23, 1, 0.320, 1);
|
| 322 |
+
animation: scaleIn 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
|
| 323 |
+
width: 100%;
|
| 324 |
+
max-width: fit-content;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.image-card img {
|
| 328 |
+
width: 100%;
|
| 329 |
+
height: auto;
|
| 330 |
+
display: block;
|
| 331 |
+
object-fit: cover;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.image-card:hover {
|
| 335 |
+
transform: translateY(-8px) scale(1.02);
|
| 336 |
+
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
|
| 337 |
+
border-color: rgba(124, 58, 237, 0.2);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
@keyframes scaleIn {
|
| 341 |
+
from {
|
| 342 |
+
opacity: 0;
|
| 343 |
+
transform: scale(0.7) translateY(60px);
|
| 344 |
+
}
|
| 345 |
+
to {
|
| 346 |
+
opacity: 1;
|
| 347 |
+
transform: scale(1) translateY(0);
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.image-actions {
|
| 352 |
+
position: absolute;
|
| 353 |
+
top: clamp(16px, 2vw, 24px);
|
| 354 |
+
right: clamp(16px, 2vw, 24px);
|
| 355 |
+
display: flex;
|
| 356 |
+
gap: clamp(10px, 1.5vw, 14px);
|
| 357 |
+
opacity: 0;
|
| 358 |
+
transform: translateY(-10px);
|
| 359 |
+
transition: all 0.3s ease;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
.image-card:hover .image-actions {
|
| 363 |
+
opacity: 1;
|
| 364 |
+
transform: translateY(0);
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
.action-btn {
|
| 368 |
+
width: clamp(40px, 5vw, 52px);
|
| 369 |
+
height: clamp(40px, 5vw, 52px);
|
| 370 |
+
background: rgba(0, 0, 0, 0.85);
|
| 371 |
+
backdrop-filter: blur(40px);
|
| 372 |
+
border-radius: clamp(12px, 1.5vw, 18px);
|
| 373 |
+
display: flex;
|
| 374 |
+
align-items: center;
|
| 375 |
+
justify-content: center;
|
| 376 |
+
cursor: pointer;
|
| 377 |
+
transition: all 0.3s ease;
|
| 378 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 379 |
+
color: white;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
.action-btn:hover {
|
| 383 |
+
background: rgba(124, 58, 237, 0.2);
|
| 384 |
+
transform: scale(1.15) rotate(10deg);
|
| 385 |
+
border-color: rgba(124, 58, 237, 0.3);
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.input-field {
|
| 389 |
+
background: rgba(28, 28, 28, 0.5);
|
| 390 |
+
border: 1px solid var(--border);
|
| 391 |
+
border-radius: clamp(12px, 1.5vw, 18px);
|
| 392 |
+
padding: clamp(16px, 2vw, 22px) clamp(20px, 2.5vw, 28px);
|
| 393 |
+
color: var(--text-primary);
|
| 394 |
+
width: 100%;
|
| 395 |
+
font-size: clamp(14px, 1.5vw, 16px);
|
| 396 |
+
transition: all 0.3s ease;
|
| 397 |
+
outline: none;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
.input-field:focus {
|
| 401 |
+
border-color: rgba(124, 58, 237, 0.3);
|
| 402 |
+
box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.06);
|
| 403 |
+
background: rgba(28, 28, 28, 0.7);
|
| 404 |
+
transform: translateY(-1px);
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
.select-field {
|
| 408 |
+
appearance: none;
|
| 409 |
+
background-image: url(
|
| 410 |
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='%236b7280' d='M8 11L3 6h10z'/%3E%3C/svg%3E"
|
| 411 |
+
);
|
| 412 |
+
background-repeat: no-repeat;
|
| 413 |
+
background-position: right clamp(20px, 2.5vw, 28px) center;
|
| 414 |
+
padding-right: clamp(48px, 6vw, 64px);
|
| 415 |
+
cursor: pointer;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
.textarea-field {
|
| 419 |
+
resize: vertical;
|
| 420 |
+
min-height: clamp(140px, 20vh, 200px);
|
| 421 |
+
font-family: 'JetBrains Mono', monospace;
|
| 422 |
+
line-height: 1.7;
|
| 423 |
+
word-wrap: break-word;
|
| 424 |
+
word-break: break-word;
|
| 425 |
+
white-space: pre-wrap;
|
| 426 |
+
overflow-wrap: break-word;
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
.example-card {
|
| 430 |
+
background: rgba(28, 28, 28, 0.3);
|
| 431 |
+
border: 1px solid var(--border);
|
| 432 |
+
border-radius: clamp(14px, 1.8vw, 20px);
|
| 433 |
+
padding: clamp(20px, 2.5vw, 32px);
|
| 434 |
+
cursor: pointer;
|
| 435 |
+
transition: all 0.5s cubic-bezier(0.23, 1, 0.320, 1);
|
| 436 |
+
position: relative;
|
| 437 |
+
overflow: hidden;
|
| 438 |
+
word-wrap: break-word;
|
| 439 |
+
word-break: break-word;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.example-card p {
|
| 443 |
+
word-wrap: break-word;
|
| 444 |
+
word-break: break-word;
|
| 445 |
+
overflow-wrap: break-word;
|
| 446 |
+
hyphens: auto;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.example-card::before {
|
| 450 |
+
content: '';
|
| 451 |
+
position: absolute;
|
| 452 |
+
inset: 0;
|
| 453 |
+
background: linear-gradient(
|
| 454 |
+
135deg,
|
| 455 |
+
rgba(124, 58, 237, 0.05),
|
| 456 |
+
rgba(236, 72, 153, 0.05)
|
| 457 |
+
);
|
| 458 |
+
opacity: 0;
|
| 459 |
+
transition: opacity 0.5s;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
.example-card:hover::before {
|
| 463 |
+
opacity: 1;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.example-card:hover {
|
| 467 |
+
transform: translateX(12px) scale(1.02);
|
| 468 |
+
border-color: rgba(124, 58, 237, 0.25);
|
| 469 |
+
box-shadow: 0 16px 60px rgba(0, 0, 0, 0.3);
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
.loading-spinner {
|
| 473 |
+
width: clamp(24px, 3vw, 32px);
|
| 474 |
+
height: clamp(24px, 3vw, 32px);
|
| 475 |
+
border: 3px solid rgba(255, 255, 255, 0.1);
|
| 476 |
+
border-top-color: white;
|
| 477 |
+
border-radius: 50%;
|
| 478 |
+
animation: spin 0.8s linear infinite;
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
@keyframes spin {
|
| 482 |
+
to { transform: rotate(360deg); }
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.header-gradient {
|
| 486 |
+
background: linear-gradient(135deg, #7c3aed, #ec4899, #3b82f6);
|
| 487 |
+
-webkit-background-clip: text;
|
| 488 |
+
-webkit-text-fill-color: transparent;
|
| 489 |
+
background-clip: text;
|
| 490 |
+
font-weight: 900;
|
| 491 |
+
background-size: 200% 200%;
|
| 492 |
+
animation: gradientShift 12s ease infinite;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
@keyframes gradientShift {
|
| 496 |
+
0%, 100% {
|
| 497 |
+
background-position: 0% 50%;
|
| 498 |
+
}
|
| 499 |
+
50% {
|
| 500 |
+
background-position: 100% 50%;
|
| 501 |
+
}
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
@media (max-width: 144px) {
|
| 505 |
+
body {
|
| 506 |
+
font-size: 10px;
|
| 507 |
+
}
|
| 508 |
+
.modal-content {
|
| 509 |
+
padding: 12px;
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
@media (min-width: 3840px) {
|
| 514 |
+
body {
|
| 515 |
+
font-size: 22px;
|
| 516 |
+
}
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
@media (min-width: 7680px) {
|
| 520 |
+
body {
|
| 521 |
+
font-size: 36px;
|
| 522 |
+
}
|
| 523 |
+
}
|
assets/css/webLoader.css
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
* SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
.modal-overlay {
|
| 7 |
+
position: fixed;
|
| 8 |
+
top: 0;
|
| 9 |
+
left: 0;
|
| 10 |
+
right: 0;
|
| 11 |
+
bottom: 0;
|
| 12 |
+
background: rgba(0, 0, 0, 0.8);
|
| 13 |
+
backdrop-filter: blur(10px);
|
| 14 |
+
display: flex;
|
| 15 |
+
align-items: center;
|
| 16 |
+
justify-content: center;
|
| 17 |
+
z-index: 9999;
|
| 18 |
+
padding: 20px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.modal-content {
|
| 22 |
+
background: var(--card-bg);
|
| 23 |
+
border-radius: 24px;
|
| 24 |
+
padding: clamp(32px, 4vw, 48px);
|
| 25 |
+
max-width: min(90%, 600px);
|
| 26 |
+
width: 100%;
|
| 27 |
+
position: relative;
|
| 28 |
+
overflow: hidden;
|
| 29 |
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.modal-content::before {
|
| 33 |
+
content: '';
|
| 34 |
+
position: absolute;
|
| 35 |
+
top: -50%;
|
| 36 |
+
left: -50%;
|
| 37 |
+
width: 200%;
|
| 38 |
+
height: 200%;
|
| 39 |
+
background: radial-gradient(
|
| 40 |
+
circle at center,
|
| 41 |
+
rgba(99, 102, 241, 0.1) 0%,
|
| 42 |
+
transparent 70%
|
| 43 |
+
);
|
| 44 |
+
animation: pulse 4s ease-in-out infinite;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.modal-inner {
|
| 48 |
+
position: relative;
|
| 49 |
+
z-index: 1;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.modal-title {
|
| 53 |
+
font-size: clamp(24px, 5vw, 48px);
|
| 54 |
+
margin-bottom: clamp(20px, 3vw, 40px);
|
| 55 |
+
text-align: center;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.modal-text {
|
| 59 |
+
color: var(--text-secondary);
|
| 60 |
+
line-height: 1.8;
|
| 61 |
+
margin-bottom: clamp(24px, 3vw, 40px);
|
| 62 |
+
font-size: clamp(14px, 1.8vw, 17px);
|
| 63 |
+
word-wrap: break-word;
|
| 64 |
+
word-break: break-word;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.modal-error-title {
|
| 68 |
+
font-size: clamp(20px, 3vw, 28px);
|
| 69 |
+
font-weight: 700;
|
| 70 |
+
color: var(--danger);
|
| 71 |
+
margin-bottom: clamp(16px, 2vw, 24px);
|
| 72 |
+
text-align: center;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.modal-error-text {
|
| 76 |
+
color: var(--text-secondary);
|
| 77 |
+
margin-bottom: clamp(20px, 3vw, 32px);
|
| 78 |
+
text-align: center;
|
| 79 |
+
font-size: clamp(14px, 1.8vw, 17px);
|
| 80 |
+
word-wrap: break-word;
|
| 81 |
+
word-break: break-word;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.header-main {
|
| 85 |
+
border-bottom: 1px solid var(--border);
|
| 86 |
+
padding: clamp(20px, 3vw, 32px) 0;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.header-title {
|
| 90 |
+
font-size: clamp(32px, 6vw, 64px);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.main-content {
|
| 94 |
+
padding: clamp(24px, 4vw, 48px) 16px;
|
| 95 |
+
max-width: min(100%, 1800px);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.loading-container {
|
| 99 |
+
padding: clamp(32px, 4vw, 60px);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
.loading-text {
|
| 103 |
+
color: var(--text-secondary);
|
| 104 |
+
font-size: clamp(16px, 2vw, 20px);
|
| 105 |
+
font-weight: 600;
|
| 106 |
+
margin-bottom: 16px;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.progress-text {
|
| 110 |
+
color: var(--text-muted);
|
| 111 |
+
font-size: clamp(14px, 1.6vw, 16px);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.image-actions form {
|
| 115 |
+
margin: 0;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.image-info {
|
| 119 |
+
padding: clamp(20px, 2.5vw, 28px);
|
| 120 |
+
border-top: 1px solid var(--border);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.image-prompt {
|
| 124 |
+
font-size: clamp(13px, 1.5vw, 15px);
|
| 125 |
+
color: var(--text-muted);
|
| 126 |
+
overflow: hidden;
|
| 127 |
+
text-overflow: ellipsis;
|
| 128 |
+
white-space: nowrap;
|
| 129 |
+
margin-bottom: 8px;
|
| 130 |
+
font-weight: 500;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.image-meta {
|
| 134 |
+
font-size: clamp(12px, 1.4vw, 14px);
|
| 135 |
+
color: var(--text-muted);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.image-model {
|
| 139 |
+
font-weight: 700;
|
| 140 |
+
color: var(--accent);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.placeholder-icon {
|
| 144 |
+
margin: 0 auto clamp(20px, 2.5vw, 28px);
|
| 145 |
+
opacity: 0.15;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.placeholder-text {
|
| 149 |
+
color: var(--text-muted);
|
| 150 |
+
font-size: clamp(16px, 2vw, 20px);
|
| 151 |
+
font-weight: 500;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.form-section {
|
| 155 |
+
padding: clamp(32px, 4vw, 48px);
|
| 156 |
+
margin-bottom: clamp(32px, 4vw, 48px);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.form-grid {
|
| 160 |
+
margin-bottom: clamp(24px, 3vw, 36px);
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.form-label {
|
| 164 |
+
display: block;
|
| 165 |
+
font-size: clamp(13px, 1.5vw, 15px);
|
| 166 |
+
font-weight: 700;
|
| 167 |
+
color: var(--text-secondary);
|
| 168 |
+
margin-bottom: clamp(10px, 1.2vw, 14px);
|
| 169 |
+
text-transform: uppercase;
|
| 170 |
+
letter-spacing: 0.05em;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.form-group {
|
| 174 |
+
margin-bottom: clamp(24px, 3vw, 36px);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.button-icon {
|
| 178 |
+
width: clamp(20px, 2.5vw, 28px);
|
| 179 |
+
height: clamp(20px, 2.5vw, 28px);
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.examples-section {
|
| 183 |
+
padding: clamp(32px, 4vw, 48px);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.examples-title {
|
| 187 |
+
font-size: clamp(24px, 3vw, 32px);
|
| 188 |
+
font-weight: 800;
|
| 189 |
+
color: var(--text-secondary);
|
| 190 |
+
margin-bottom: clamp(24px, 3vw, 36px);
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.example-text {
|
| 194 |
+
margin-bottom: clamp(12px, 1.5vw, 16px);
|
| 195 |
+
font-size: clamp(14px, 1.6vw, 16px);
|
| 196 |
+
line-height: 1.6;
|
| 197 |
+
font-weight: 500;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.example-meta {
|
| 201 |
+
font-size: clamp(12px, 1.4vw, 14px);
|
| 202 |
+
color: var(--text-muted);
|
| 203 |
+
font-weight: 700;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.example-model {
|
| 207 |
+
color: var(--accent);
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.footer-main {
|
| 211 |
+
border-top: 1px solid var(--border);
|
| 212 |
+
padding: clamp(32px, 4vw, 48px) 0;
|
| 213 |
+
margin-top: clamp(80px, 10vw, 140px);
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.footer-copyright {
|
| 217 |
+
font-size: clamp(13px, 1.5vw, 15px);
|
| 218 |
+
color: var(--text-muted);
|
| 219 |
+
font-weight: 500;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.footer-link {
|
| 223 |
+
display: flex;
|
| 224 |
+
align-items: center;
|
| 225 |
+
gap: clamp(10px, 1.5vw, 16px);
|
| 226 |
+
color: var(--text-muted);
|
| 227 |
+
text-decoration: none;
|
| 228 |
+
transition: all 0.3s;
|
| 229 |
+
font-size: clamp(13px, 1.5vw, 15px);
|
| 230 |
+
font-weight: 600;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.footer-link:hover {
|
| 234 |
+
color: var(--accent);
|
| 235 |
+
transform: translateX(-6px);
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.footer-icon {
|
| 239 |
+
width: clamp(20px, 2.5vw, 26px);
|
| 240 |
+
height: clamp(20px, 2.5vw, 26px);
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.modal-error-content {
|
| 244 |
+
max-width: min(90%, 480px);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
.action-icon {
|
| 248 |
+
width: clamp(20px, 2.5vw, 28px);
|
| 249 |
+
height: clamp(20px, 2.5vw, 28px);
|
| 250 |
+
}
|
assets/plugins/imageGenerator.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
(function () {
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
var isGenerating = false;
|
| 10 |
+
var requestId = '';
|
| 11 |
+
|
| 12 |
+
if (document && document.body) {
|
| 13 |
+
isGenerating = document.body.dataset.isGenerating === 'true';
|
| 14 |
+
requestId = document.body.dataset.requestId || '';
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
if (isGenerating) {
|
| 18 |
+
setTimeout(function () {
|
| 19 |
+
window.location.href = '/?rid=' + encodeURIComponent(requestId);
|
| 20 |
+
}, 10000);
|
| 21 |
+
}
|
| 22 |
+
})();
|
assets/plugins/webLoader.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
(function () {
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
var noop = function () {};
|
| 10 |
+
var methods = [
|
| 11 |
+
'log', 'error', 'warn', 'info', 'debug', 'trace',
|
| 12 |
+
'dir', 'table', 'time', 'timeEnd', 'timeLog', 'group',
|
| 13 |
+
'groupEnd', 'assert', 'count', 'countReset', 'profile',
|
| 14 |
+
'profileEnd', 'clear', 'dirxml', 'exception',
|
| 15 |
+
'groupCollapsed'
|
| 16 |
+
];
|
| 17 |
+
|
| 18 |
+
var disableConsole = function () {
|
| 19 |
+
methods.forEach(function (m) {
|
| 20 |
+
try {
|
| 21 |
+
window.console[m] = noop;
|
| 22 |
+
} catch (e) {}
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
try {
|
| 26 |
+
Object.defineProperty(window, 'console', {
|
| 27 |
+
value: Object.freeze(Object.create(null)),
|
| 28 |
+
writable: false,
|
| 29 |
+
configurable: false
|
| 30 |
+
});
|
| 31 |
+
} catch (e) {}
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
disableConsole();
|
| 35 |
+
|
| 36 |
+
var InitWelcomeModal = function () {
|
| 37 |
+
try {
|
| 38 |
+
var hasShown = localStorage.getItem(
|
| 39 |
+
'welcomeModalShown'
|
| 40 |
+
);
|
| 41 |
+
if (!hasShown) {
|
| 42 |
+
var modal = document.getElementById(
|
| 43 |
+
'welcomeModal'
|
| 44 |
+
);
|
| 45 |
+
if (modal) {
|
| 46 |
+
modal.style.display = 'flex';
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
} catch (e) {
|
| 50 |
+
var modal = document.getElementById(
|
| 51 |
+
'welcomeModal'
|
| 52 |
+
);
|
| 53 |
+
if (modal) {
|
| 54 |
+
modal.style.display = 'flex';
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
var closeWelcomeModal = function () {
|
| 60 |
+
var modal = document.getElementById('welcomeModal');
|
| 61 |
+
if (modal) {
|
| 62 |
+
modal.style.display = 'none';
|
| 63 |
+
try {
|
| 64 |
+
localStorage.setItem(
|
| 65 |
+
'welcomeModalShown',
|
| 66 |
+
'true'
|
| 67 |
+
);
|
| 68 |
+
} catch (e) {}
|
| 69 |
+
}
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
var closeErrorModal = function () {
|
| 73 |
+
var modal = document.getElementById('errorModal');
|
| 74 |
+
if (modal) {
|
| 75 |
+
modal.style.animation = 'fadeIn 0.5s ease reverse';
|
| 76 |
+
setTimeout(function () {
|
| 77 |
+
modal.remove();
|
| 78 |
+
}, 500);
|
| 79 |
+
}
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
var validateInputs = function () {
|
| 83 |
+
var model = document.getElementById('modelSelect');
|
| 84 |
+
var size = document.getElementById('sizeSelect');
|
| 85 |
+
var prompt = document.getElementById('promptInput');
|
| 86 |
+
var submitBtn = document.getElementById('submitBtn');
|
| 87 |
+
|
| 88 |
+
if (!model || !size || !prompt || !submitBtn) return;
|
| 89 |
+
|
| 90 |
+
var modelValue = model.value;
|
| 91 |
+
var sizeValue = size.value;
|
| 92 |
+
var promptValue = prompt.value
|
| 93 |
+
.trim()
|
| 94 |
+
.replace(/\s+/g, ' ')
|
| 95 |
+
.replace(/[\t\n\r]/g, '');
|
| 96 |
+
|
| 97 |
+
var isValid = !!modelValue &&
|
| 98 |
+
!!sizeValue &&
|
| 99 |
+
!!promptValue &&
|
| 100 |
+
promptValue.length > 0;
|
| 101 |
+
|
| 102 |
+
submitBtn.disabled = !isValid;
|
| 103 |
+
};
|
| 104 |
+
|
| 105 |
+
var triggerExample = function (prompt, model, size) {
|
| 106 |
+
var modelSelect = document.getElementById(
|
| 107 |
+
'modelSelect'
|
| 108 |
+
);
|
| 109 |
+
var sizeSelect = document.getElementById(
|
| 110 |
+
'sizeSelect'
|
| 111 |
+
);
|
| 112 |
+
var promptInput = document.getElementById(
|
| 113 |
+
'promptInput'
|
| 114 |
+
);
|
| 115 |
+
var form = document.getElementById('generateForm');
|
| 116 |
+
|
| 117 |
+
if (modelSelect) modelSelect.value = model;
|
| 118 |
+
if (sizeSelect) sizeSelect.value = size;
|
| 119 |
+
if (promptInput) promptInput.value = prompt;
|
| 120 |
+
|
| 121 |
+
validateInputs();
|
| 122 |
+
|
| 123 |
+
if (form) {
|
| 124 |
+
setTimeout(function () {
|
| 125 |
+
var fa = document.getElementById('formAction');
|
| 126 |
+
if (fa) fa.value = 'generate';
|
| 127 |
+
form.submit();
|
| 128 |
+
}, 100);
|
| 129 |
+
}
|
| 130 |
+
};
|
| 131 |
+
|
| 132 |
+
var cancelGeneration = function () {
|
| 133 |
+
var form = document.getElementById('generateForm');
|
| 134 |
+
var fa = document.getElementById('formAction');
|
| 135 |
+
var cancelBtn = event && event.target
|
| 136 |
+
? event.target.closest('button')
|
| 137 |
+
: null;
|
| 138 |
+
|
| 139 |
+
if (cancelBtn) {
|
| 140 |
+
cancelBtn.disabled = true;
|
| 141 |
+
cancelBtn.style.opacity = '0.6';
|
| 142 |
+
cancelBtn.style.cursor = 'not-allowed';
|
| 143 |
+
|
| 144 |
+
var btnText = cancelBtn.querySelector('span') ||
|
| 145 |
+
cancelBtn.childNodes[
|
| 146 |
+
cancelBtn.childNodes.length - 1
|
| 147 |
+
];
|
| 148 |
+
if (btnText && btnText.nodeType === 3) {
|
| 149 |
+
btnText.textContent = 'Cancelling...';
|
| 150 |
+
} else if (!btnText) {
|
| 151 |
+
var textNode = Array.from(
|
| 152 |
+
cancelBtn.childNodes
|
| 153 |
+
).find(function(node) {
|
| 154 |
+
return node.nodeType === 3 &&
|
| 155 |
+
node.textContent.trim();
|
| 156 |
+
});
|
| 157 |
+
if (textNode) {
|
| 158 |
+
textNode.textContent = 'Cancelling...';
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
if (fa) {
|
| 164 |
+
fa.value = 'cancel';
|
| 165 |
+
|
| 166 |
+
if (form) {
|
| 167 |
+
var hiddenCancel = document.createElement(
|
| 168 |
+
'input'
|
| 169 |
+
);
|
| 170 |
+
hiddenCancel.type = 'hidden';
|
| 171 |
+
hiddenCancel.name = 'forceCancel';
|
| 172 |
+
hiddenCancel.value = 'true';
|
| 173 |
+
form.appendChild(hiddenCancel);
|
| 174 |
+
|
| 175 |
+
var timestamp = document.createElement('input');
|
| 176 |
+
timestamp.type = 'hidden';
|
| 177 |
+
timestamp.name = 'cancelTime';
|
| 178 |
+
timestamp.value = Date.now();
|
| 179 |
+
form.appendChild(timestamp);
|
| 180 |
+
|
| 181 |
+
try {
|
| 182 |
+
form.submit();
|
| 183 |
+
} catch (e) {
|
| 184 |
+
form.requestSubmit && form.requestSubmit();
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
setTimeout(function() {
|
| 188 |
+
if (form && fa) {
|
| 189 |
+
fa.value = 'cancel';
|
| 190 |
+
try {
|
| 191 |
+
form.submit();
|
| 192 |
+
} catch (e) {
|
| 193 |
+
window.location.reload();
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
}, 500);
|
| 197 |
+
|
| 198 |
+
setTimeout(function() {
|
| 199 |
+
window.location.reload();
|
| 200 |
+
}, 3000);
|
| 201 |
+
}
|
| 202 |
+
} else if (form) {
|
| 203 |
+
var newFA = document.createElement('input');
|
| 204 |
+
newFA.type = 'hidden';
|
| 205 |
+
newFA.id = 'formAction';
|
| 206 |
+
newFA.name = 'action';
|
| 207 |
+
newFA.value = 'cancel';
|
| 208 |
+
form.appendChild(newFA);
|
| 209 |
+
|
| 210 |
+
try {
|
| 211 |
+
form.submit();
|
| 212 |
+
} catch (e) {
|
| 213 |
+
window.location.reload();
|
| 214 |
+
}
|
| 215 |
+
} else {
|
| 216 |
+
var xhr = new XMLHttpRequest();
|
| 217 |
+
xhr.open('POST', window.location.href, true);
|
| 218 |
+
xhr.setRequestHeader('Content-Type',
|
| 219 |
+
'application/x-www-form-urlencoded');
|
| 220 |
+
xhr.onload = function() {
|
| 221 |
+
window.location.reload();
|
| 222 |
+
};
|
| 223 |
+
xhr.onerror = function() {
|
| 224 |
+
window.location.reload();
|
| 225 |
+
};
|
| 226 |
+
xhr.send('action=cancel&forceCancel=true');
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
window.cancelRequested = true;
|
| 230 |
+
|
| 231 |
+
if (window.EventSource) {
|
| 232 |
+
try {
|
| 233 |
+
var sources = window.eventSources || [];
|
| 234 |
+
sources.forEach(function(source) {
|
| 235 |
+
if (source && source.close) {
|
| 236 |
+
source.close();
|
| 237 |
+
}
|
| 238 |
+
});
|
| 239 |
+
} catch (e) {}
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
if (window.AbortController &&
|
| 243 |
+
window.abortController) {
|
| 244 |
+
try {
|
| 245 |
+
window.abortController.abort();
|
| 246 |
+
} catch (e) {}
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
setTimeout(function() {
|
| 250 |
+
if (window.cancelRequested) {
|
| 251 |
+
window.stop && window.stop();
|
| 252 |
+
document.execCommand &&
|
| 253 |
+
document.execCommand('Stop');
|
| 254 |
+
}
|
| 255 |
+
}, 100);
|
| 256 |
+
};
|
| 257 |
+
|
| 258 |
+
var modelSelect = document.getElementById(
|
| 259 |
+
'modelSelect'
|
| 260 |
+
);
|
| 261 |
+
var sizeSelect = document.getElementById(
|
| 262 |
+
'sizeSelect'
|
| 263 |
+
);
|
| 264 |
+
var promptInput = document.getElementById(
|
| 265 |
+
'promptInput'
|
| 266 |
+
);
|
| 267 |
+
|
| 268 |
+
if (modelSelect) {
|
| 269 |
+
modelSelect.addEventListener(
|
| 270 |
+
'change',
|
| 271 |
+
validateInputs
|
| 272 |
+
);
|
| 273 |
+
}
|
| 274 |
+
if (sizeSelect) {
|
| 275 |
+
sizeSelect.addEventListener(
|
| 276 |
+
'change',
|
| 277 |
+
validateInputs
|
| 278 |
+
);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
if (promptInput) {
|
| 282 |
+
promptInput.addEventListener(
|
| 283 |
+
'input',
|
| 284 |
+
validateInputs
|
| 285 |
+
);
|
| 286 |
+
promptInput.addEventListener('paste', function () {
|
| 287 |
+
setTimeout(validateInputs, 10);
|
| 288 |
+
});
|
| 289 |
+
promptInput.addEventListener(
|
| 290 |
+
'keydown',
|
| 291 |
+
function (e) {
|
| 292 |
+
if (e.key === 'Enter' && e.ctrlKey) {
|
| 293 |
+
e.preventDefault();
|
| 294 |
+
var submitBtn = document.getElementById(
|
| 295 |
+
'submitBtn'
|
| 296 |
+
);
|
| 297 |
+
if (submitBtn && !submitBtn.disabled) {
|
| 298 |
+
var form = document.getElementById(
|
| 299 |
+
'generateForm'
|
| 300 |
+
);
|
| 301 |
+
if (form) form.submit();
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
);
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
window.closeWelcomeModal = closeWelcomeModal;
|
| 309 |
+
window.closeErrorModal = closeErrorModal;
|
| 310 |
+
window.triggerExample = triggerExample;
|
| 311 |
+
window.cancelGeneration = cancelGeneration;
|
| 312 |
+
|
| 313 |
+
validateInputs();
|
| 314 |
+
|
| 315 |
+
var cards = document.querySelectorAll('.card');
|
| 316 |
+
Array.prototype.forEach.call(cards, function (card) {
|
| 317 |
+
card.addEventListener('mousemove', function (e) {
|
| 318 |
+
var rect = card.getBoundingClientRect();
|
| 319 |
+
var x = e.clientX - rect.left;
|
| 320 |
+
var y = e.clientY - rect.top;
|
| 321 |
+
card.style.setProperty('--mouse-x', x + 'px');
|
| 322 |
+
card.style.setProperty('--mouse-y', y + 'px');
|
| 323 |
+
});
|
| 324 |
+
});
|
| 325 |
+
|
| 326 |
+
document.addEventListener('DOMContentLoaded',
|
| 327 |
+
function() {
|
| 328 |
+
InitWelcomeModal();
|
| 329 |
+
}
|
| 330 |
+
);
|
| 331 |
+
|
| 332 |
+
if (document.readyState === 'complete' ||
|
| 333 |
+
document.readyState === 'interactive') {
|
| 334 |
+
InitWelcomeModal();
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
setInterval(function () {
|
| 338 |
+
disableConsole();
|
| 339 |
+
}, 1000);
|
| 340 |
+
})();
|
config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
export default {
|
| 7 |
+
server: {
|
| 8 |
+
port: 3000,
|
| 9 |
+
host: '0.0.0.0'
|
| 10 |
+
},
|
| 11 |
+
api: {
|
| 12 |
+
baseUrl: process.env.OPENAI_API_BASE_URL,
|
| 13 |
+
key: process.env.OPENAI_API_KEY,
|
| 14 |
+
timeout: 120000
|
| 15 |
+
},
|
| 16 |
+
storage: {
|
| 17 |
+
maxAge: 600000,
|
| 18 |
+
cleanupInterval: 60000
|
| 19 |
+
},
|
| 20 |
+
limits: {
|
| 21 |
+
bodySize: '2mb',
|
| 22 |
+
maxContentLength: Infinity
|
| 23 |
+
},
|
| 24 |
+
generation: {
|
| 25 |
+
progressInterval: 800,
|
| 26 |
+
startDelay: 100,
|
| 27 |
+
maxProgress: 90
|
| 28 |
+
},
|
| 29 |
+
paths: {
|
| 30 |
+
views: 'public',
|
| 31 |
+
mainView: 'webViewer'
|
| 32 |
+
}
|
| 33 |
+
};
|
config.py
DELETED
|
@@ -1,57 +0,0 @@
|
|
| 1 |
-
#
|
| 2 |
-
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
-
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
-
#
|
| 5 |
-
|
| 6 |
-
MODEL = [
|
| 7 |
-
"flux", "kontext", "turbo"
|
| 8 |
-
]
|
| 9 |
-
|
| 10 |
-
SIZE = [
|
| 11 |
-
"256x256", "512x512", "1024x1024",
|
| 12 |
-
"512x768", "768x1024", "1024x1536",
|
| 13 |
-
"768x512", "1024x768", "1536x1024"
|
| 14 |
-
]
|
| 15 |
-
|
| 16 |
-
EXAMPLES=[
|
| 17 |
-
["flux", "1024x1024", "A woman riding a horse in the forest"],
|
| 18 |
-
["kontext", "1024x1024",
|
| 19 |
-
"A cozy cabin in the snowy mountains, warm lights glowing inside"],
|
| 20 |
-
["turbo", "1024x1024",
|
| 21 |
-
"A cute cat astronaut floating in space, holding a little flag"],
|
| 22 |
-
["kontext", "1024x1024",
|
| 23 |
-
"An ancient library filled with floating books and magical lights"],
|
| 24 |
-
["flux", "1024x1024",
|
| 25 |
-
"Tropical beach with crystal-clear water and palm trees swaying"],
|
| 26 |
-
["flux", "1024x1024",
|
| 27 |
-
"Cyberpunk street musician with neon tattoos"],
|
| 28 |
-
["flux", "1024x1024",
|
| 29 |
-
"Abstract watercolor splash resembling a city skyline"],
|
| 30 |
-
["flux", "1024x1024",
|
| 31 |
-
"Anatomical diagram of the human heart in textbook style"]
|
| 32 |
-
]
|
| 33 |
-
|
| 34 |
-
DESCRIPTION = """
|
| 35 |
-
Welcome to the <b>Image Generation Playground</b>, where imagination transforms
|
| 36 |
-
into visual creations.
|
| 37 |
-
<br><br>
|
| 38 |
-
|
| 39 |
-
For your information, <b>image generation</b> and <b>OpenAI Audio TTS</b>
|
| 40 |
-
is already built-in natively in the primary spaces.
|
| 41 |
-
<br><br>
|
| 42 |
-
|
| 43 |
-
For more advanced capabilities,
|
| 44 |
-
please visit the <b><a href='https://umint-openwebui.hf.space'
|
| 45 |
-
target='_blank'>UltimaX Intelligence</a></b> primary spaces.
|
| 46 |
-
<br><br>
|
| 47 |
-
|
| 48 |
-
Please consider reading the <b><a href=
|
| 49 |
-
'https://huggingface.co/spaces/umint/ai/discussions/37#68b55209c51ca52ed299db4c'
|
| 50 |
-
target='_blank'>Terms of Use and Consequences of Violation</a></b>
|
| 51 |
-
if you wish to proceed to the main Spaces.
|
| 52 |
-
<br><br>
|
| 53 |
-
|
| 54 |
-
<b>Like this project? Feel free to buy me a
|
| 55 |
-
<a href='https://ko-fi.com/hadad' target='_blank'>
|
| 56 |
-
coffee</a></b>.
|
| 57 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Image Generation Playground",
|
| 3 |
+
"version": "0.0.1",
|
| 4 |
+
"description": "Part of the UltimaX Intelligence ecosystem",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"main": "server.js",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"start": "node server.js"
|
| 9 |
+
},
|
| 10 |
+
"keywords": [
|
| 11 |
+
"Image Generation",
|
| 12 |
+
"playground",
|
| 13 |
+
"UltimaX Intelligence",
|
| 14 |
+
"umint",
|
| 15 |
+
"Pollinations.AI"
|
| 16 |
+
],
|
| 17 |
+
"author": "Hadad Darajat",
|
| 18 |
+
"license": "Apache-2.0",
|
| 19 |
+
"dependencies": {
|
| 20 |
+
"axios": "latest",
|
| 21 |
+
"ejs": "latest",
|
| 22 |
+
"express": "latest"
|
| 23 |
+
},
|
| 24 |
+
"homepage": "https://umint-image.hf.space"
|
| 25 |
+
}
|
public/webViewer.ejs
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!--
|
| 2 |
+
SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
-->
|
| 5 |
+
|
| 6 |
+
<!DOCTYPE html>
|
| 7 |
+
<html lang="en">
|
| 8 |
+
<head>
|
| 9 |
+
<meta charset="UTF-8">
|
| 10 |
+
<meta name="viewport"
|
| 11 |
+
content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
|
| 12 |
+
|
| 13 |
+
<meta name="author" content="Hadad Darajat" />
|
| 14 |
+
|
| 15 |
+
<meta name="description"
|
| 16 |
+
content="UltimaX Intelligence is a free AI platform unifying
|
| 17 |
+
multiple premium AI models with an intuitive
|
| 18 |
+
ChatGPT-like interface. Powered by Pollinations
|
| 19 |
+
open-source AI and integrated with OpenWebUI,
|
| 20 |
+
it offers advanced features, no cost, no registration,
|
| 21 |
+
and ensures user privacy with temporary,
|
| 22 |
+
unsaved conversations." />
|
| 23 |
+
|
| 24 |
+
<meta name="keywords"
|
| 25 |
+
content="UltimaX Intelligence, free AI platform,
|
| 26 |
+
premium AI models, Pollinations AI, open-source AI,
|
| 27 |
+
Open-WebUI integration, ChatGPT alternative,
|
| 28 |
+
AI tools free, no registration AI, AI privacy,
|
| 29 |
+
temporary AI conversations, AI platform no login,
|
| 30 |
+
advanced AI features, AI community powered,
|
| 31 |
+
seamless AI experience" />
|
| 32 |
+
|
| 33 |
+
<meta property="og:domain" content="umint-ai.hf.space" />
|
| 34 |
+
<meta property="og:url" content="https://umint-ai.hf.space" />
|
| 35 |
+
<meta property="og:title" content="UltimaX Intelligence" />
|
| 36 |
+
|
| 37 |
+
<meta property="og:description"
|
| 38 |
+
content="UltimaX Intelligence is a free AI platform unifying
|
| 39 |
+
multiple premium AI models with an intuitive
|
| 40 |
+
ChatGPT-like interface. Powered by Pollinations
|
| 41 |
+
open-source AI and integrated with OpenWebUI,
|
| 42 |
+
it offers advanced features, no cost, no registration,
|
| 43 |
+
and ensures user privacy with temporary,
|
| 44 |
+
unsaved conversations." />
|
| 45 |
+
|
| 46 |
+
<meta property="og:image"
|
| 47 |
+
content="https://cdn-uploads.huggingface.co/production/uploads/686e28b405d4ddcdd96adeb2/i9iufR3L-rgj39mk_B9QW.jpeg" />
|
| 48 |
+
|
| 49 |
+
<meta name="twitter:card" content="summary_large_image" />
|
| 50 |
+
<meta name="twitter:domain" content="umint-ai.hf.space" />
|
| 51 |
+
<meta name="twitter:url" content="https://umint-ai.hf.space" />
|
| 52 |
+
<meta name="twitter:title" content="UltimaX Intelligence" />
|
| 53 |
+
|
| 54 |
+
<meta name="twitter:description"
|
| 55 |
+
content="UltimaX Intelligence is a free AI platform unifying
|
| 56 |
+
multiple premium AI models with an intuitive
|
| 57 |
+
ChatGPT-like interface. Powered by Pollinations
|
| 58 |
+
open-source AI and integrated with OpenWebUI,
|
| 59 |
+
it offers advanced features, no cost, no registration,
|
| 60 |
+
and ensures user privacy with temporary,
|
| 61 |
+
unsaved conversations." />
|
| 62 |
+
|
| 63 |
+
<meta name="twitter:image"
|
| 64 |
+
content="https://cdn-uploads.huggingface.co/production/uploads/686e28b405d4ddcdd96adeb2/i9iufR3L-rgj39mk_B9QW.jpeg" />
|
| 65 |
+
|
| 66 |
+
<link rel="icon"
|
| 67 |
+
href="https://umint-ai.hf.space/assets/images/favicon.ico"
|
| 68 |
+
type="image/x-icon" />
|
| 69 |
+
|
| 70 |
+
<title>Image Generation Playground</title>
|
| 71 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 72 |
+
|
| 73 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
| 74 |
+
|
| 75 |
+
<link rel="stylesheet"
|
| 76 |
+
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
| 77 |
+
|
| 78 |
+
<link rel="stylesheet" href="/__public__/assets/css/styles.css" />
|
| 79 |
+
<link rel="stylesheet" href="/__public__/assets/css/webLoader.css" />
|
| 80 |
+
</head>
|
| 81 |
+
<body data-is-generating="<%= !!isGenerating %>"
|
| 82 |
+
data-request-id="<%= requestId || '' %>">
|
| 83 |
+
<div class="gradient-bg"></div>
|
| 84 |
+
<div class="gradient-mesh"></div>
|
| 85 |
+
|
| 86 |
+
<div id="welcomeModal" class="modal-overlay" style="display: none;">
|
| 87 |
+
<div class="modal-content">
|
| 88 |
+
<div class="modal-inner">
|
| 89 |
+
<h2 class="header-gradient animate__animated
|
| 90 |
+
animate__fadeInDown modal-title">
|
| 91 |
+
Hi there!
|
| 92 |
+
</h2>
|
| 93 |
+
<div class="animate__animated animate__fadeInUp modal-text">
|
| 94 |
+
Welcome to the <b>Image Generation Playground</b>,
|
| 95 |
+
where imagination transforms into visual creations.
|
| 96 |
+
<br><br>
|
| 97 |
+
For your information, <b>image generation</b> and
|
| 98 |
+
<b>OpenAI Audio TTS</b> is already built-in natively
|
| 99 |
+
in the primary spaces.
|
| 100 |
+
<br><br>
|
| 101 |
+
For more advanced capabilities, please visit the
|
| 102 |
+
<b><a href='https://umint-openwebui.hf.space'
|
| 103 |
+
target='_blank' style="color: var(--accent);
|
| 104 |
+
text-decoration: underline;">
|
| 105 |
+
UltimaX Intelligence</a></b> primary spaces.
|
| 106 |
+
<br><br>
|
| 107 |
+
Please consider reading the
|
| 108 |
+
<b><a href='https://huggingface.co/spaces/umint/ai/discussions/37'
|
| 109 |
+
target='_blank' style="color: var(--accent);
|
| 110 |
+
text-decoration: underline;">
|
| 111 |
+
Terms of Use and Consequences of Violation</a></b>
|
| 112 |
+
if you wish to proceed to the main Spaces.
|
| 113 |
+
<br><br>
|
| 114 |
+
<b>Like this project? Feel free to buy me a
|
| 115 |
+
<a href='https://ko-fi.com/hadad' target='_blank'
|
| 116 |
+
style="color: var(--accent);
|
| 117 |
+
text-decoration: underline;">coffee</a></b>.
|
| 118 |
+
</div>
|
| 119 |
+
<button onclick="closeWelcomeModal()"
|
| 120 |
+
class="btn btn-primary w-full animate__animated
|
| 121 |
+
animate__fadeInUp animate__delay-1s">
|
| 122 |
+
Get Started
|
| 123 |
+
</button>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<% if (error) { %>
|
| 129 |
+
<div id="errorModal" class="modal-overlay">
|
| 130 |
+
<div class="modal-content modal-error-content">
|
| 131 |
+
<div class="modal-inner">
|
| 132 |
+
<h3 class="modal-error-title">Error</h3>
|
| 133 |
+
<p class="modal-error-text"><%= error %></p>
|
| 134 |
+
<button onclick="closeErrorModal()"
|
| 135 |
+
class="btn btn-primary w-full">OK</button>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
<% } %>
|
| 140 |
+
|
| 141 |
+
<header class="header-main">
|
| 142 |
+
<div class="container mx-auto px-4">
|
| 143 |
+
<h1 class="header-gradient animate__animated
|
| 144 |
+
animate__fadeInDown header-title">
|
| 145 |
+
Image Generation Playground
|
| 146 |
+
</h1>
|
| 147 |
+
</div>
|
| 148 |
+
</header>
|
| 149 |
+
|
| 150 |
+
<main class="container mx-auto px-4 main-content">
|
| 151 |
+
|
| 152 |
+
<div class="image-output-section
|
| 153 |
+
<%= images && images.length > 0 ? 'has-images' : '' %>
|
| 154 |
+
animate__animated animate__fadeIn">
|
| 155 |
+
<% if (isGenerating) { %>
|
| 156 |
+
<div class="loading-container">
|
| 157 |
+
<div class="loading-spinner"
|
| 158 |
+
style="margin: 0 auto 20px;"></div>
|
| 159 |
+
<p class="loading-text">Generating your image...</p>
|
| 160 |
+
<div class="progress-bar">
|
| 161 |
+
<div class="progress-fill"
|
| 162 |
+
style="width: <%= progress %>%;"></div>
|
| 163 |
+
</div>
|
| 164 |
+
<p class="progress-text">
|
| 165 |
+
<%= progress.toFixed(0) %>% Complete
|
| 166 |
+
</p>
|
| 167 |
+
</div>
|
| 168 |
+
<% } else if (images && images.length > 0) { %>
|
| 169 |
+
<div class="image-grid">
|
| 170 |
+
<% images.forEach((image, index) => { %>
|
| 171 |
+
<div class="image-card">
|
| 172 |
+
<img src="data:image/png;base64,<%= image.base64 %>"
|
| 173 |
+
alt="<%= image.prompt %>">
|
| 174 |
+
<div class="image-actions">
|
| 175 |
+
<a href="data:image/png;base64,<%= image.base64 %>"
|
| 176 |
+
download="generated-<%= image.id %>.png"
|
| 177 |
+
class="action-btn">
|
| 178 |
+
<svg class="action-icon" viewBox="0 0 24 24"
|
| 179 |
+
fill="none">
|
| 180 |
+
<path d="M12 16L7 11L8.4 9.55L11 12.15V4H13V12.15L15.6 9.55L17 11L12 16Z"
|
| 181 |
+
fill="currentColor"/>
|
| 182 |
+
<path d="M4 20C3.45 20 2.98 19.8 2.59 19.41C2.2 19.02 2 18.55 2 18V15H4V18H20V15H22V18C22 18.55 21.8 19.02 21.41 19.41C21.02 19.8 20.55 20 20 20H4Z"
|
| 183 |
+
fill="currentColor"/>
|
| 184 |
+
</svg>
|
| 185 |
+
</a>
|
| 186 |
+
<form method="POST" action="/">
|
| 187 |
+
<input type="hidden" name="action" value="delete">
|
| 188 |
+
<input type="hidden" name="imageIndex"
|
| 189 |
+
value="<%= index %>">
|
| 190 |
+
<button type="submit" class="action-btn">
|
| 191 |
+
<svg class="action-icon" viewBox="0 0 24 24"
|
| 192 |
+
fill="none">
|
| 193 |
+
<path d="M18.3 5.71C17.91 5.32 17.28 5.32 16.89 5.71L12 10.59L7.11 5.7C6.72 5.31 6.09 5.31 5.7 5.7C5.31 6.09 5.31 6.72 5.7 7.11L10.59 12L5.7 16.89C5.31 17.28 5.31 17.91 5.7 18.3C6.09 18.69 6.72 18.69 7.11 18.3L12 13.41L16.89 18.3C17.28 18.69 17.91 18.69 18.3 18.3C18.69 17.91 18.69 17.28 18.3 16.89L13.41 12L18.3 7.11C18.68 6.73 18.68 6.09 18.3 5.71Z"
|
| 194 |
+
fill="currentColor"/>
|
| 195 |
+
</svg>
|
| 196 |
+
</button>
|
| 197 |
+
</form>
|
| 198 |
+
</div>
|
| 199 |
+
<div class="image-info">
|
| 200 |
+
<p class="image-prompt"><%= image.prompt %></p>
|
| 201 |
+
<p class="image-meta">
|
| 202 |
+
<span class="image-model">
|
| 203 |
+
<%= image.model.toUpperCase() %>
|
| 204 |
+
</span>
|
| 205 |
+
| <%= image.size %>
|
| 206 |
+
</p>
|
| 207 |
+
</div>
|
| 208 |
+
</div>
|
| 209 |
+
<% }); %>
|
| 210 |
+
</div>
|
| 211 |
+
<% } else { %>
|
| 212 |
+
<svg class="placeholder-icon" width="80" height="80"
|
| 213 |
+
viewBox="0 0 24 24" fill="none">
|
| 214 |
+
<path d="M21 3H3C2 3 1 4 1 5V19C1 20 2 21 3 21H21C22 21 23 20 23 19V5C23 4 22 3 21 3ZM21 19H3V5H21V19Z"
|
| 215 |
+
fill="currentColor"/>
|
| 216 |
+
<path d="M4.5 16.5L9 12L11.5 14.5L16 10L19.5 13.5"
|
| 217 |
+
stroke="currentColor" stroke-width="1.5"
|
| 218 |
+
stroke-linecap="round"/>
|
| 219 |
+
<circle cx="8" cy="8.5" r="1.5" fill="currentColor"/>
|
| 220 |
+
</svg>
|
| 221 |
+
<p class="placeholder-text">
|
| 222 |
+
No images generated yet. Start creating amazing visuals!
|
| 223 |
+
</p>
|
| 224 |
+
<% } %>
|
| 225 |
+
</div>
|
| 226 |
+
|
| 227 |
+
<section class="card animate__animated animate__fadeIn
|
| 228 |
+
animate__delay-1s form-section">
|
| 229 |
+
<form method="POST" action="/" id="generateForm">
|
| 230 |
+
<input type="hidden" name="action" value="generate"
|
| 231 |
+
id="formAction">
|
| 232 |
+
|
| 233 |
+
<div class="grid md:grid-cols-2 gap-6 form-grid">
|
| 234 |
+
<div>
|
| 235 |
+
<label class="form-label">Model</label>
|
| 236 |
+
<select name="model" id="modelSelect" required
|
| 237 |
+
class="input-field select-field"
|
| 238 |
+
<%= isGenerating ? 'disabled' : '' %>>
|
| 239 |
+
<option value="">Select a model</option>
|
| 240 |
+
<option value="flux">Flux</option>
|
| 241 |
+
<option value="kontext">Kontext</option>
|
| 242 |
+
<option value="turbo">Turbo</option>
|
| 243 |
+
<option value="nanobanana">Nano Banana</option>
|
| 244 |
+
</select>
|
| 245 |
+
</div>
|
| 246 |
+
|
| 247 |
+
<div>
|
| 248 |
+
<label class="form-label">Size</label>
|
| 249 |
+
<select name="size" id="sizeSelect" required
|
| 250 |
+
class="input-field select-field"
|
| 251 |
+
<%= isGenerating ? 'disabled' : '' %>>
|
| 252 |
+
<option value="">Select size</option>
|
| 253 |
+
<optgroup label="Square">
|
| 254 |
+
<option value="256x256">256×256</option>
|
| 255 |
+
<option value="512x512">512×512</option>
|
| 256 |
+
<option value="768x768">768×768</option>
|
| 257 |
+
<option value="1024x1024">1024×1024</option>
|
| 258 |
+
<option value="1536x1536">1536×1536</option>
|
| 259 |
+
<option value="2048x2048">2048×2048</option>
|
| 260 |
+
</optgroup>
|
| 261 |
+
<optgroup label="Portrait">
|
| 262 |
+
<option value="256x384">256×384</option>
|
| 263 |
+
<option value="384x512">384×512</option>
|
| 264 |
+
<option value="512x768">512×768</option>
|
| 265 |
+
<option value="768x1024">768×1024</option>
|
| 266 |
+
<option value="1024x1536">1024×1536</option>
|
| 267 |
+
<option value="1536x2048">1536×2048</option>
|
| 268 |
+
</optgroup>
|
| 269 |
+
<optgroup label="Landscape">
|
| 270 |
+
<option value="384x256">384×256</option>
|
| 271 |
+
<option value="512x384">512×384</option>
|
| 272 |
+
<option value="768x512">768×512</option>
|
| 273 |
+
<option value="1024x768">1024×768</option>
|
| 274 |
+
<option value="1536x1024">1536×1024</option>
|
| 275 |
+
<option value="2048x1536">2048×1536</option>
|
| 276 |
+
</optgroup>
|
| 277 |
+
</select>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
<div class="form-group">
|
| 282 |
+
<label class="form-label">Prompt</label>
|
| 283 |
+
<textarea name="prompt" id="promptInput" required
|
| 284 |
+
placeholder="Describe the image you want to generate..."
|
| 285 |
+
class="input-field textarea-field"
|
| 286 |
+
<%= isGenerating ? 'disabled' : '' %>></textarea>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<div class="flex justify-center gap-4">
|
| 290 |
+
<% if (!isGenerating) { %>
|
| 291 |
+
<button type="submit" id="submitBtn" disabled
|
| 292 |
+
class="btn btn-primary">
|
| 293 |
+
<svg class="button-icon" viewBox="0 0 24 24"
|
| 294 |
+
fill="none">
|
| 295 |
+
<path d="M3 20V4L22 12L3 20ZM5 17L16.85 12L5 7V10.5L11 12L5 13.5V17Z"
|
| 296 |
+
fill="currentColor"/>
|
| 297 |
+
</svg>
|
| 298 |
+
Generate Image
|
| 299 |
+
</button>
|
| 300 |
+
<% } else { %>
|
| 301 |
+
<button type="button" onclick="cancelGeneration()"
|
| 302 |
+
class="btn btn-danger">
|
| 303 |
+
<svg class="button-icon" viewBox="0 0 24 24"
|
| 304 |
+
fill="none">
|
| 305 |
+
<rect x="4" y="4" width="16" height="16" rx="3"
|
| 306 |
+
fill="currentColor"/>
|
| 307 |
+
</svg>
|
| 308 |
+
Stop Generation
|
| 309 |
+
</button>
|
| 310 |
+
<% } %>
|
| 311 |
+
</div>
|
| 312 |
+
</form>
|
| 313 |
+
</section>
|
| 314 |
+
|
| 315 |
+
<section class="card animate__animated animate__fadeIn
|
| 316 |
+
animate__delay-1s examples-section">
|
| 317 |
+
<h3 class="examples-title">Example Prompts</h3>
|
| 318 |
+
<div class="grid lg:grid-cols-3 md:grid-cols-2 gap-4">
|
| 319 |
+
<div class="example-card"
|
| 320 |
+
onclick="triggerExample('A majestic mountain landscape at golden hour with dramatic clouds and vibrant colors reflecting on a crystal clear alpine lake', 'flux', '1536x1024')">
|
| 321 |
+
<p class="example-text">
|
| 322 |
+
A majestic mountain landscape at golden hour with
|
| 323 |
+
dramatic clouds and vibrant colors reflecting on a
|
| 324 |
+
crystal clear alpine lake
|
| 325 |
+
</p>
|
| 326 |
+
<p class="example-meta">
|
| 327 |
+
<span class="example-model">FLUX</span> | 1536×1024
|
| 328 |
+
</p>
|
| 329 |
+
</div>
|
| 330 |
+
|
| 331 |
+
<div class="example-card"
|
| 332 |
+
onclick="triggerExample('An enchanted forest with bioluminescent plants and magical floating particles, moonlight streaming through ancient mystical trees', 'kontext', '1024x1024')">
|
| 333 |
+
<p class="example-text">
|
| 334 |
+
An enchanted forest with bioluminescent plants and
|
| 335 |
+
magical floating particles, moonlight streaming
|
| 336 |
+
through ancient mystical trees
|
| 337 |
+
</p>
|
| 338 |
+
<p class="example-meta">
|
| 339 |
+
<span class="example-model">KONTEXT</span> | 1024×1024 | Sometimes the server is busy
|
| 340 |
+
</p>
|
| 341 |
+
</div>
|
| 342 |
+
|
| 343 |
+
<div class="example-card"
|
| 344 |
+
onclick="triggerExample('Underwater coral reef teeming with colorful tropical fish and sea life, sunbeams penetrating crystal clear turquoise water', 'turbo', '1024x768')">
|
| 345 |
+
<p class="example-text">
|
| 346 |
+
Underwater coral reef teeming with colorful tropical
|
| 347 |
+
fish and sea life, sunbeams penetrating crystal
|
| 348 |
+
clear turquoise water
|
| 349 |
+
</p>
|
| 350 |
+
<p class="example-meta">
|
| 351 |
+
<span class="example-model">TURBO</span> | 1024×768 | Sometimes the server is busy
|
| 352 |
+
</p>
|
| 353 |
+
</div>
|
| 354 |
+
|
| 355 |
+
<div class="example-card"
|
| 356 |
+
onclick="triggerExample('A woman riding a horse across an open field, cinematic and realistic style', 'nanobanana', '1024x768')">
|
| 357 |
+
<p class="example-text">
|
| 358 |
+
A woman riding a horse across an open field,
|
| 359 |
+
cinematic and realistic style
|
| 360 |
+
</p>
|
| 361 |
+
<p class="example-meta">
|
| 362 |
+
<span class="example-model">NANO BANANA</span> | 1024×768
|
| 363 |
+
</p>
|
| 364 |
+
</div>
|
| 365 |
+
</div>
|
| 366 |
+
</section>
|
| 367 |
+
</main>
|
| 368 |
+
|
| 369 |
+
<footer class="footer-main">
|
| 370 |
+
<div class="container mx-auto px-4 flex flex-col md:flex-row
|
| 371 |
+
justify-between items-center gap-6">
|
| 372 |
+
<div class="footer-copyright">© 2025 Hadad Darajat</div>
|
| 373 |
+
<a href="https://linkedin.com/in/hadadrjt" target="_blank"
|
| 374 |
+
class="footer-link">
|
| 375 |
+
<svg class="footer-icon" viewBox="0 0 24 24"
|
| 376 |
+
fill="currentColor">
|
| 377 |
+
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
| 378 |
+
</svg>
|
| 379 |
+
Connect with LinkedIn
|
| 380 |
+
</a>
|
| 381 |
+
</div>
|
| 382 |
+
</footer>
|
| 383 |
+
<script defer src="/__public__/assets/plugins/imageGenerator.js"></script>
|
| 384 |
+
<script defer src="/__public__/assets/plugins/webLoader.js"></script>
|
| 385 |
+
</body>
|
| 386 |
+
</html>
|
requirements.txt
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
gradio[oauth,mcp]
|
| 2 |
-
openai
|
|
|
|
|
|
|
|
|
server.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import express from 'express';
|
| 7 |
+
import { fileURLToPath } from 'url';
|
| 8 |
+
import path from 'path';
|
| 9 |
+
import config from './config.js';
|
| 10 |
+
import imageRoutes from
|
| 11 |
+
'./src/routes/imageRoutes.js';
|
| 12 |
+
import { initCleanup } from
|
| 13 |
+
'./src/services/storageManager.js';
|
| 14 |
+
import { setupViewEngine } from
|
| 15 |
+
'./src/middleware/viewEngine.js';
|
| 16 |
+
|
| 17 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 18 |
+
const __dirname = path.dirname(__filename);
|
| 19 |
+
|
| 20 |
+
const app = express();
|
| 21 |
+
|
| 22 |
+
setupViewEngine(app, __dirname);
|
| 23 |
+
|
| 24 |
+
app.use(express.urlencoded({
|
| 25 |
+
extended: true,
|
| 26 |
+
limit: config.limits.bodySize
|
| 27 |
+
}));
|
| 28 |
+
|
| 29 |
+
app.use(
|
| 30 |
+
"/__public__/assets",
|
| 31 |
+
express.static(path.resolve("assets"))
|
| 32 |
+
);
|
| 33 |
+
|
| 34 |
+
app.use(express.static(
|
| 35 |
+
path.join(__dirname, 'public')
|
| 36 |
+
));
|
| 37 |
+
|
| 38 |
+
app.use('/', imageRoutes);
|
| 39 |
+
|
| 40 |
+
initCleanup();
|
| 41 |
+
|
| 42 |
+
app.listen(
|
| 43 |
+
config.server.port,
|
| 44 |
+
config.server.host
|
| 45 |
+
);
|
src/controllers/imageController.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import config from '../../config.js';
|
| 7 |
+
import { generateId } from '../utils/idGenerator.js';
|
| 8 |
+
import {
|
| 9 |
+
validateGenerationParams,
|
| 10 |
+
validateApiConfig,
|
| 11 |
+
validateImageIndex
|
| 12 |
+
} from '../utils/validators.js';
|
| 13 |
+
import {
|
| 14 |
+
getStorage,
|
| 15 |
+
setStorage,
|
| 16 |
+
getDefaultData
|
| 17 |
+
} from '../services/storageManager.js';
|
| 18 |
+
import {
|
| 19 |
+
generateImage,
|
| 20 |
+
cancelGeneration
|
| 21 |
+
} from '../services/imageGenerator.js';
|
| 22 |
+
import { renderView } from '../utils/viewRenderer.js';
|
| 23 |
+
|
| 24 |
+
export const renderHome = (req, res) => {
|
| 25 |
+
const requestId = req.query.rid || generateId();
|
| 26 |
+
const data = getStorage(requestId) || getDefaultData();
|
| 27 |
+
|
| 28 |
+
renderView(res, {
|
| 29 |
+
...data,
|
| 30 |
+
requestId
|
| 31 |
+
});
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
export const handleAction = async (req, res) => {
|
| 35 |
+
const requestId = req.body.requestId || generateId();
|
| 36 |
+
const {
|
| 37 |
+
action,
|
| 38 |
+
prompt,
|
| 39 |
+
model,
|
| 40 |
+
size,
|
| 41 |
+
imageIndex
|
| 42 |
+
} = req.body;
|
| 43 |
+
|
| 44 |
+
let data = getStorage(requestId) || getDefaultData();
|
| 45 |
+
|
| 46 |
+
switch(action) {
|
| 47 |
+
case 'delete':
|
| 48 |
+
return handleDelete(
|
| 49 |
+
requestId,
|
| 50 |
+
imageIndex,
|
| 51 |
+
data,
|
| 52 |
+
res
|
| 53 |
+
);
|
| 54 |
+
|
| 55 |
+
case 'cancel':
|
| 56 |
+
return handleCancel(requestId, res);
|
| 57 |
+
|
| 58 |
+
case 'generate':
|
| 59 |
+
return handleGenerate(
|
| 60 |
+
requestId,
|
| 61 |
+
prompt,
|
| 62 |
+
model,
|
| 63 |
+
size,
|
| 64 |
+
data,
|
| 65 |
+
res
|
| 66 |
+
);
|
| 67 |
+
|
| 68 |
+
default:
|
| 69 |
+
return res.redirect(`/?rid=${requestId}`);
|
| 70 |
+
}
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
const handleDelete = (
|
| 74 |
+
requestId,
|
| 75 |
+
imageIndex,
|
| 76 |
+
data,
|
| 77 |
+
res
|
| 78 |
+
) => {
|
| 79 |
+
if (validateImageIndex(
|
| 80 |
+
imageIndex,
|
| 81 |
+
data.images.length
|
| 82 |
+
)) {
|
| 83 |
+
data.images.splice(parseInt(imageIndex), 1);
|
| 84 |
+
setStorage(requestId, data);
|
| 85 |
+
}
|
| 86 |
+
return res.redirect(`/?rid=${requestId}`);
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
const handleCancel = (requestId, res) => {
|
| 90 |
+
cancelGeneration(requestId);
|
| 91 |
+
return res.redirect(`/?rid=${requestId}`);
|
| 92 |
+
};
|
| 93 |
+
|
| 94 |
+
const handleGenerate = async (
|
| 95 |
+
requestId,
|
| 96 |
+
prompt,
|
| 97 |
+
model,
|
| 98 |
+
size,
|
| 99 |
+
data,
|
| 100 |
+
res
|
| 101 |
+
) => {
|
| 102 |
+
const trimmedPrompt = prompt?.trim() || '';
|
| 103 |
+
|
| 104 |
+
if (!validateGenerationParams(
|
| 105 |
+
trimmedPrompt,
|
| 106 |
+
model,
|
| 107 |
+
size
|
| 108 |
+
)) {
|
| 109 |
+
data.error = 'Please fill in all required fields';
|
| 110 |
+
setStorage(requestId, data);
|
| 111 |
+
return renderView(res, {
|
| 112 |
+
...data,
|
| 113 |
+
requestId
|
| 114 |
+
});
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
if (!validateApiConfig()) {
|
| 118 |
+
data.error = 'The server is currently busy. ' +
|
| 119 |
+
'Please try again later.';
|
| 120 |
+
setStorage(requestId, data);
|
| 121 |
+
return renderView(res, {
|
| 122 |
+
...data,
|
| 123 |
+
requestId
|
| 124 |
+
});
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
data.isGenerating = true;
|
| 128 |
+
data.progress = 50;
|
| 129 |
+
data.error = null;
|
| 130 |
+
setStorage(requestId, data);
|
| 131 |
+
|
| 132 |
+
res.redirect(`/?rid=${requestId}`);
|
| 133 |
+
|
| 134 |
+
await generateImage(
|
| 135 |
+
requestId,
|
| 136 |
+
trimmedPrompt,
|
| 137 |
+
model,
|
| 138 |
+
size
|
| 139 |
+
);
|
| 140 |
+
};
|
src/middleware/viewEngine.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import path from 'path';
|
| 7 |
+
import config from '../../config.js';
|
| 8 |
+
|
| 9 |
+
export const setupViewEngine = (app, rootDir) => {
|
| 10 |
+
app.set('view engine', 'ejs');
|
| 11 |
+
app.set('views', path.join(
|
| 12 |
+
rootDir,
|
| 13 |
+
config.paths.views
|
| 14 |
+
));
|
| 15 |
+
};
|
src/routes/imageRoutes.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import { Router } from 'express';
|
| 7 |
+
import {
|
| 8 |
+
renderHome,
|
| 9 |
+
handleAction
|
| 10 |
+
} from '../controllers/imageController.js';
|
| 11 |
+
|
| 12 |
+
const router = Router();
|
| 13 |
+
|
| 14 |
+
router.get('/', renderHome);
|
| 15 |
+
router.post('/', handleAction);
|
| 16 |
+
|
| 17 |
+
export default router;
|
src/services/imageGenerator.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import axios from 'axios';
|
| 7 |
+
import config from '../../config.js';
|
| 8 |
+
import { generateId } from '../utils/idGenerator.js';
|
| 9 |
+
import {
|
| 10 |
+
getStorage,
|
| 11 |
+
setStorage,
|
| 12 |
+
getActiveGeneration,
|
| 13 |
+
setActiveGeneration,
|
| 14 |
+
deleteActiveGeneration
|
| 15 |
+
} from './storageManager.js';
|
| 16 |
+
|
| 17 |
+
const updateProgress = (requestId, progress) => {
|
| 18 |
+
const data = getStorage(requestId);
|
| 19 |
+
if (data) {
|
| 20 |
+
data.progress = progress;
|
| 21 |
+
setStorage(requestId, data);
|
| 22 |
+
}
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
const createProgressUpdater = (requestId) => {
|
| 26 |
+
return setInterval(() => {
|
| 27 |
+
const data = getStorage(requestId);
|
| 28 |
+
if (data &&
|
| 29 |
+
data.isGenerating &&
|
| 30 |
+
data.progress < config.generation.maxProgress) {
|
| 31 |
+
const increment = Math.random() * 20;
|
| 32 |
+
const newProgress = Math.min(
|
| 33 |
+
config.generation.maxProgress,
|
| 34 |
+
data.progress + increment
|
| 35 |
+
);
|
| 36 |
+
updateProgress(requestId, newProgress);
|
| 37 |
+
}
|
| 38 |
+
}, config.generation.progressInterval);
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
const createImageObject = (
|
| 42 |
+
base64Data,
|
| 43 |
+
prompt,
|
| 44 |
+
model,
|
| 45 |
+
size
|
| 46 |
+
) => ({
|
| 47 |
+
id: generateId(),
|
| 48 |
+
base64: base64Data,
|
| 49 |
+
prompt,
|
| 50 |
+
model,
|
| 51 |
+
size
|
| 52 |
+
});
|
| 53 |
+
|
| 54 |
+
const callImageApi = async (
|
| 55 |
+
prompt,
|
| 56 |
+
model,
|
| 57 |
+
size,
|
| 58 |
+
signal
|
| 59 |
+
) => {
|
| 60 |
+
return await axios.post(
|
| 61 |
+
config.api.baseUrl,
|
| 62 |
+
{
|
| 63 |
+
model,
|
| 64 |
+
prompt,
|
| 65 |
+
size,
|
| 66 |
+
response_format: 'b64_json',
|
| 67 |
+
n: 1
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
headers: {
|
| 71 |
+
'Authorization': `Bearer ${config.api.key}`,
|
| 72 |
+
'Content-Type': 'application/json'
|
| 73 |
+
},
|
| 74 |
+
signal,
|
| 75 |
+
timeout: config.api.timeout,
|
| 76 |
+
maxBodyLength: config.limits.maxContentLength,
|
| 77 |
+
maxContentLength: config.limits.maxContentLength
|
| 78 |
+
}
|
| 79 |
+
);
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
export const generateImage = async (
|
| 83 |
+
requestId,
|
| 84 |
+
prompt,
|
| 85 |
+
model,
|
| 86 |
+
size
|
| 87 |
+
) => {
|
| 88 |
+
const controller = new AbortController();
|
| 89 |
+
setActiveGeneration(requestId, controller);
|
| 90 |
+
|
| 91 |
+
const progressInterval = createProgressUpdater(requestId);
|
| 92 |
+
|
| 93 |
+
setTimeout(async () => {
|
| 94 |
+
try {
|
| 95 |
+
const response = await callImageApi(
|
| 96 |
+
prompt,
|
| 97 |
+
model,
|
| 98 |
+
size,
|
| 99 |
+
controller.signal
|
| 100 |
+
);
|
| 101 |
+
|
| 102 |
+
const data = getStorage(requestId);
|
| 103 |
+
if (!data) return;
|
| 104 |
+
|
| 105 |
+
updateProgress(requestId, 100);
|
| 106 |
+
|
| 107 |
+
if (response.data?.data?.length > 0) {
|
| 108 |
+
const base64 = response.data.data[0].b64_json;
|
| 109 |
+
const newImage = createImageObject(
|
| 110 |
+
base64,
|
| 111 |
+
prompt,
|
| 112 |
+
model,
|
| 113 |
+
size
|
| 114 |
+
);
|
| 115 |
+
|
| 116 |
+
data.images.unshift(newImage);
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
data.isGenerating = false;
|
| 120 |
+
data.progress = 0;
|
| 121 |
+
setStorage(requestId, data);
|
| 122 |
+
|
| 123 |
+
} catch (error) {
|
| 124 |
+
const data = getStorage(requestId);
|
| 125 |
+
if (!data) return;
|
| 126 |
+
|
| 127 |
+
if (error.name !== 'CanceledError' &&
|
| 128 |
+
error.code !== 'ERR_CANCELED') {
|
| 129 |
+
data.error = 'The server is currently busy. ' +
|
| 130 |
+
'Please try again later.';
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
data.isGenerating = false;
|
| 134 |
+
data.progress = 0;
|
| 135 |
+
setStorage(requestId, data);
|
| 136 |
+
|
| 137 |
+
} finally {
|
| 138 |
+
clearInterval(progressInterval);
|
| 139 |
+
deleteActiveGeneration(requestId);
|
| 140 |
+
}
|
| 141 |
+
}, config.generation.startDelay);
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
export const cancelGeneration = (requestId) => {
|
| 145 |
+
const controller = getActiveGeneration(requestId);
|
| 146 |
+
if (controller) {
|
| 147 |
+
controller.abort();
|
| 148 |
+
deleteActiveGeneration(requestId);
|
| 149 |
+
|
| 150 |
+
const data = getStorage(requestId);
|
| 151 |
+
if (data) {
|
| 152 |
+
data.isGenerating = false;
|
| 153 |
+
data.progress = 0;
|
| 154 |
+
setStorage(requestId, data);
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
};
|
src/services/storageManager.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import config from '../../config.js';
|
| 7 |
+
|
| 8 |
+
const tempStorage = new Map();
|
| 9 |
+
const activeGenerations = new Map();
|
| 10 |
+
|
| 11 |
+
export const getStorage = (requestId) => {
|
| 12 |
+
const data = tempStorage.get(requestId);
|
| 13 |
+
if (data) {
|
| 14 |
+
data.lastAccess = Date.now();
|
| 15 |
+
}
|
| 16 |
+
return data;
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
export const setStorage = (requestId, data) => {
|
| 20 |
+
data.lastAccess = Date.now();
|
| 21 |
+
tempStorage.set(requestId, data);
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
export const deleteStorage = (requestId) => {
|
| 25 |
+
tempStorage.delete(requestId);
|
| 26 |
+
activeGenerations.delete(requestId);
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
export const getActiveGeneration = (requestId) => {
|
| 30 |
+
return activeGenerations.get(requestId);
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
export const setActiveGeneration = (
|
| 34 |
+
requestId,
|
| 35 |
+
controller
|
| 36 |
+
) => {
|
| 37 |
+
activeGenerations.set(requestId, controller);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
export const deleteActiveGeneration = (requestId) => {
|
| 41 |
+
activeGenerations.delete(requestId);
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
export const getDefaultData = () => ({
|
| 45 |
+
images: [],
|
| 46 |
+
isGenerating: false,
|
| 47 |
+
progress: 0,
|
| 48 |
+
error: null,
|
| 49 |
+
lastAccess: Date.now()
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
export const initCleanup = () => {
|
| 53 |
+
setInterval(() => {
|
| 54 |
+
const now = Date.now();
|
| 55 |
+
const maxAge = config.storage.maxAge;
|
| 56 |
+
|
| 57 |
+
for (const [key, value] of tempStorage.entries()) {
|
| 58 |
+
if (!value.lastAccess ||
|
| 59 |
+
now - value.lastAccess > maxAge) {
|
| 60 |
+
deleteStorage(key);
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}, config.storage.cleanupInterval);
|
| 64 |
+
};
|
src/utils/idGenerator.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
export const generateId = () => {
|
| 7 |
+
return Math.random()
|
| 8 |
+
.toString(36)
|
| 9 |
+
.substring(2, 15);
|
| 10 |
+
};
|
src/utils/validators.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import config from '../../config.js';
|
| 7 |
+
|
| 8 |
+
export const validatePrompt = (prompt) => {
|
| 9 |
+
if (!prompt) return false;
|
| 10 |
+
|
| 11 |
+
const trimmed = prompt.trim();
|
| 12 |
+
return trimmed &&
|
| 13 |
+
trimmed.replace(/\s+/g, '').length > 0;
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
export const validateGenerationParams = (
|
| 17 |
+
prompt,
|
| 18 |
+
model,
|
| 19 |
+
size
|
| 20 |
+
) => {
|
| 21 |
+
return validatePrompt(prompt) &&
|
| 22 |
+
model &&
|
| 23 |
+
size;
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
export const validateApiConfig = () => {
|
| 27 |
+
return config.api.baseUrl &&
|
| 28 |
+
config.api.key;
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
export const validateImageIndex = (
|
| 32 |
+
index,
|
| 33 |
+
arrayLength
|
| 34 |
+
) => {
|
| 35 |
+
const idx = parseInt(index);
|
| 36 |
+
return !isNaN(idx) &&
|
| 37 |
+
idx >= 0 &&
|
| 38 |
+
idx < arrayLength;
|
| 39 |
+
};
|
src/utils/viewRenderer.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//
|
| 2 |
+
// SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
//
|
| 5 |
+
|
| 6 |
+
import config from '../../config.js';
|
| 7 |
+
|
| 8 |
+
export const renderView = (res, data) => {
|
| 9 |
+
res.render(config.paths.mainView, data);
|
| 10 |
+
};
|