Opera8 commited on
Commit
67994cc
·
verified ·
1 Parent(s): 4a7c4cc

Update templates/ideogram.html

Browse files
Files changed (1) hide show
  1. templates/ideogram.html +513 -191
templates/ideogram.html CHANGED
@@ -5,249 +5,571 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>تولید تصویر با Ideogram 4</title>
7
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  body {
9
- font-family: Tahoma, Arial, sans-serif;
10
- background-color: #f4f4f9;
11
- color: #333;
12
- display: flex;
13
- justify-content: center;
14
- align-items: center;
15
- min-height: 100vh;
16
- margin: 0;
17
- padding: 20px;
18
- }
19
- .container {
20
- background-color: #fff;
21
- padding: 30px;
22
- border-radius: 12px;
23
- box-shadow: 0 4px 15px rgba(0,0,0,0.1);
24
- max-width: 700px;
25
- width: 100%;
26
- }
27
- h1 {
28
- text-align: center;
29
- font-size: 24px;
30
- margin-bottom: 20px;
31
- color: #2c3e50;
32
- }
33
- .form-group {
34
- margin-bottom: 15px;
35
- }
36
- .flex-group {
37
- display: flex;
38
- gap: 15px;
39
- }
40
- .flex-group .form-group {
41
- flex: 1;
42
- }
43
- label {
44
- display: block;
45
- margin-bottom: 5px;
46
- font-weight: bold;
47
- font-size: 14px;
48
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  input[type="text"], input[type="number"] {
50
- width: 100%;
51
- padding: 10px;
52
- border: 1px solid #ccc;
53
- border-radius: 8px;
54
- font-size: 16px;
55
- box-sizing: border-box;
56
- }
57
- button {
58
- width: 100%;
59
- padding: 12px;
60
- background-color: #3498db;
61
- color: white;
62
- border: none;
63
- border-radius: 8px;
64
- font-size: 16px;
65
- cursor: pointer;
66
- transition: background-color 0.3s;
67
- margin-top: 10px;
68
- }
69
- button:hover {
70
- background-color: #2980b9;
71
- }
72
- button:disabled {
73
- background-color: #95a5a6;
74
- cursor: not-allowed;
75
- }
76
- #status {
77
- text-align: center;
78
- margin-top: 15px;
79
- font-size: 14px;
80
- font-weight: bold;
81
- }
82
- #result {
83
- margin-top: 20px;
84
- text-align: center;
85
- }
86
- #result img {
87
- max-width: 100%;
88
- border-radius: 12px;
89
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
90
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  .prompt-box {
92
- display: none;
93
- background-color: #2c3e50;
94
- color: #ecf0f1;
95
- padding: 15px;
96
- border-radius: 8px;
97
- margin-top: 20px;
98
- font-size: 13px;
99
- text-align: left;
100
- direction: ltr;
101
- overflow-x: auto;
102
- }
103
- .prompt-box h3 {
104
- margin-top: 0;
105
- color: #f1c40f;
106
- font-size: 16px;
107
- text-align: right;
108
- direction: rtl;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
  </style>
111
  </head>
112
  <body>
 
 
 
 
 
 
113
 
114
- <div class="container">
115
- <h1>تولید تصویر با Ideogram 4</h1>
116
-
117
- <div class="form-group">
118
- <label for="prompt">پرامپت (به فارسی یا انگلیسی):</label>
119
- <input type="text" id="prompt" placeholder="مثال: یک گربه زنجبیلی با کلاه جادوگری کوچک..." value="a ginger cat wearing a tiny wizard hat reading a spellbook">
120
- </div>
 
 
 
 
 
 
 
121
 
122
- <div class="flex-group">
123
- <div class="form-group">
124
- <label for="width">عرض (Width):</label>
125
- <input type="number" id="width" value="1024" step="64" min="512" max="1536">
126
- </div>
127
- <div class="form-group">
128
- <label for="height">ارتفاع (Height):</label>
129
- <input type="number" id="height" value="1024" step="64" min="512" max="1536">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  </div>
131
- </div>
 
132
 
133
- <button id="generateBtn">ساخت تصویر</button>
134
- <div id="status"></div>
135
- <div id="result"></div>
136
-
137
- <div id="enhancedPromptBox" class="prompt-box">
138
- <h3>✨ سناریو و پرامپت ارتقا یافته توسط هوش مصنوعی:</h3>
139
- <div id="enhancedPromptText"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  </div>
141
  </div>
142
 
143
  <script type="module">
144
- const btn = document.getElementById('generateBtn');
145
- const statusText = document.getElementById('status');
146
- const resultDiv = document.getElementById('result');
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  const enhancedPromptBox = document.getElementById('enhancedPromptBox');
148
  const enhancedPromptText = document.getElementById('enhancedPromptText');
149
-
 
150
  let pollInterval = null;
 
 
151
 
152
- btn.addEventListener('click', async () => {
153
- const prompt = document.getElementById('prompt').value.trim();
154
- const width = parseInt(document.getElementById('width').value) || 1024;
155
- const height = parseInt(document.getElementById('height').value) || 1024;
156
 
157
- if (!prompt) {
158
- alert("لطفاً پرامپت را وارد کنید.");
159
- return;
 
 
 
 
160
  }
 
 
161
 
162
- btn.disabled = true;
163
- statusText.innerText = "⏳ در حال فرستادن درخواست به صف گیت‌هاب... (لطفاً صبور باشید)";
164
- statusText.style.color = "#e67e22";
165
- resultDiv.innerHTML = "";
166
- enhancedPromptBox.style.display = "none";
167
- enhancedPromptText.innerHTML = "";
168
 
169
- if (pollInterval) clearInterval(pollInterval);
 
 
 
170
 
171
- try {
172
- // ارسال درخواست به سرور خود شما جهت شروع دیسپچ گیت‌هاب
173
- const response = await fetch('/api/generate', {
174
- method: 'POST',
175
- headers: { 'Content-Type': 'application/json' },
176
- body: JSON.stringify({
177
- prompt: prompt,
178
- width: width,
179
- height: height,
180
- action_name: 'generate-ideogram' // هماهنگ با نام فایل پایتون در پوشه yml
181
- })
182
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- if (!response.ok) {
185
- throw new Error("خطا در برقراری ارتباط با سرور صف");
 
 
 
186
  }
 
 
 
 
 
 
 
 
187
 
188
- const data = await response.json();
189
- if (data.status === 'success' && data.run_id) {
190
- statusText.innerText = "⏳ رانر گیت‌هاب فعال شد. در حال طراحی تصویر توسط هوش مصنوعی...";
191
- startPolling(data.run_id);
192
- } else {
193
- throw new Error(data.message || "پاسخ نامشخص از سرور");
 
 
 
 
 
 
 
 
 
 
 
194
  }
 
 
195
 
196
- } catch (error) {
197
- console.error("Error initiating generation:", error);
198
- statusText.innerText = `❌ خطا: ${error.message}`;
199
- statusText.style.color = "#e74c3c";
200
- btn.disabled = false;
201
  }
202
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
  function startPolling(runId) {
 
 
 
205
  pollInterval = setInterval(async () => {
 
 
 
 
206
  try {
207
- const statusRes = await fetch(`/api/status/${runId}`);
208
- if (!statusRes.ok) return;
209
-
210
- const statusData = await statusRes.json();
211
 
212
- if (statusData.status === 'ready') {
 
 
 
 
213
  clearInterval(pollInterval);
214
- btn.disabled = false;
215
- statusText.innerText = "✅ تصویر با موفقیت ساخته شد!";
216
- statusText.style.color = "#27ae60";
217
- resultDiv.innerHTML = `<img src="${statusData.url}" alt="Generated Image" />`;
218
 
219
- // تلاش برای دریافت متادیتای JSON پرامپت ارتقا یافته
220
  try {
221
- const promptRes = await fetch(`/static/images/${runId}_prompt.json`);
222
- if (promptRes.ok) {
223
- const parsedJson = await promptRes.json();
224
  let displayHtml = "";
225
- if (parsedJson.high_level_description) {
226
- displayHtml += `<p><strong>High Level Description:</strong><br>${parsedJson.high_level_description}</p>`;
227
  }
228
- displayHtml += `<pre style="white-space: pre-wrap; font-size: 11px; color: #bdc3c7;">${JSON.stringify(parsedJson, null, 2)}</pre>`;
229
  enhancedPromptText.innerHTML = displayHtml;
230
  enhancedPromptBox.style.display = "block";
231
  }
232
- } catch (jsonErr) {
233
- console.warn("Could not retrieve JSON prompt file:", jsonErr);
234
  }
235
 
236
- } else if (statusData.status === 'failed') {
237
- clearInterval(pollInterval);
238
- btn.disabled = false;
239
- statusText.innerText = `❌ خطا در پردازش تصویر: ${statusData.message || "خطای نامشخص"}`;
240
- statusText.style.color = "#e74c3c";
 
 
 
 
 
 
 
241
  } else {
242
- // وضعیت در حال پردازش (processing)
243
- statusText.innerText = "⏳ سرور گیت‌هاب در حال طراحی و ارتقای تصویر است... لطفا صبور باشید.";
244
  }
245
  } catch (e) {
246
- console.error("Polling error:", e);
247
  }
248
- }, 4000); // بازه ۴ ثانیه‌ای برای پایش وضعیت
249
  }
250
- </script>
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  </body>
253
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>تولید تصویر با Ideogram 4</title>
7
  <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800;900&display=swap');
9
+
10
+ :root {
11
+ --app-font: 'Vazirmatn', sans-serif; --app-bg: #F8F9FC; --panel-bg: #FFFFFF;
12
+ --panel-border: #EAEFF7; --input-bg: #F6F8FB; --input-border: #E1E7EF;
13
+ --text-primary: #1A202C; --text-secondary: #626F86; --text-tertiary: #8A94A6;
14
+ --accent-primary: #4A6CFA; --accent-primary-hover: #3553D6; --accent-primary-glow: rgba(74, 108, 250, 0.25);
15
+ --accent-secondary: #0FD4A8; --accent-secondary-hover: #0DA986; --accent-secondary-glow: rgba(15, 212, 168, 0.2);
16
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
17
+ --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
18
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
19
+ --radius-card: 24px; --radius-btn: 14px; --radius-input: 12px;
20
+ --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
21
+ }
22
+
23
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
24
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
25
+ @keyframes rotate-loader-orbital { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
26
+ @keyframes orbit-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
27
+ @keyframes satellite-pulse-1 { from { transform: scale(0.7) translateX(-50%); opacity: 0.6; } to { transform: scale(1.1) translateX(-50%); opacity: 1; } }
28
+ @keyframes satellite-pulse-2 { from { transform: scale(0.7) translateY(-50%); opacity: 0.6; } to { transform: scale(1.1) translateY(-50%); opacity: 1; } }
29
+ @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } }
30
+ @keyframes slideInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
31
+
32
+ html { scroll-behavior: smooth; }
33
  body {
34
+ font-family: var(--app-font); direction: rtl; background-color: var(--app-bg);
35
+ color: var(--text-primary); font-size: 16px; line-height: 1.8; margin: 0;
36
+ padding: 2.5rem 1rem; min-height: 100vh; display: flex; justify-content: center;
37
+ align-items: flex-start; overflow-x: hidden;
38
+ background-image: radial-gradient(var(--text-tertiary) 0.5px, transparent 0.5px);
39
+ background-size: 20px 20px; background-position: -10px -10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
+ .app-container { max-width: 680px; width: 95%; margin: 0 auto; }
42
+
43
+ .app-header { padding: 0.5rem 0 2.5rem 0; text-align: center; margin-bottom: 1.5rem; animation: fadeIn 0.8s 0.2s ease-out backwards; }
44
+ .app-header h1 { font-size: 2.5em; font-weight: 900; margin: 0 0 0.8rem 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; }
45
+ .app-header p { font-size: 1.05em; color: var(--text-secondary); margin-top: 0; opacity: 0.9; font-weight: 400; line-height: 1.7; }
46
+
47
+ .main-content { position:relative; padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.4s ease-out backwards; }
48
+
49
+ .form-group { margin-bottom: 2.2rem; }
50
+ .flex-group { display: flex; gap: 1.5rem; }
51
+ .flex-group .form-group { flex: 1; }
52
+
53
+ label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.1em; margin-bottom: 1rem; }
54
+ label .label-icon { width: 24px; height: 24px; color: var(--accent-primary); }
55
+
56
  input[type="text"], input[type="number"] {
57
+ width: 100%; padding: 1.1rem 1.2rem; border: 2px solid var(--input-border);
58
+ border-radius: var(--radius-input); background-color: var(--input-bg);
59
+ color: var(--text-primary); font-size: 1.05rem; font-family: inherit;
60
+ box-sizing: border-box; transition: var(--transition-smooth);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
+ input:focus { border-color: var(--accent-primary); background-color: #fff; outline: none; box-shadow: 0 0 12px var(--accent-primary-glow); }
63
+
64
+ .generate-btn { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; padding: 1.1rem 1.5rem; font-size: 1.25em; font-weight: 800; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow); }
65
+ .generate-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow); }
66
+ .generate-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; color: var(--text-secondary); transform: none; }
67
+ .generate-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none;}
68
+
69
+ .output-section { margin-top: 3rem; display: flex; align-items: center; justify-content: center; flex-direction: column; min-height: 220px; position: relative; box-sizing: border-box; padding: 2rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); }
70
+ .output-section.has-content { background-color: var(--panel-bg); border: 1px solid var(--panel-border); box-shadow: var(--shadow-lg); padding: 1rem; min-height: auto; }
71
+ .status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; line-height: 1.9; }
72
+
73
+ .loading-animation-wrapper { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 1.8rem; width: 100%; }
74
+ .orbital-loader { width: 110px; height: 110px; position: relative; animation: rotate-loader-orbital 10s linear infinite; }
75
+ .orbit { position: absolute; top: 50%; left: 50%; border: 2px dashed rgba(74, 108, 250, 0.35); border-radius: 50%; transform-origin: center center; }
76
+ .orbit:nth-child(1) { width: 65px; height: 65px; margin: -32.5px 0 0 -32.5px; animation: orbit-spin 3.8s linear infinite; }
77
+ .orbit:nth-child(2) { width: 95px; height: 95px; margin: -47.5px 0 0 -47.5px; animation: orbit-spin 4.8s linear infinite reverse; }
78
+ .orbit .satellite { position: absolute; width: 10px; height: 10px; border-radius: 50%; background-color: var(--accent-primary); box-shadow: 0 0 8px var(--accent-primary), 0 0 12px var(--accent-secondary); }
79
+ .orbit:nth-child(1) .satellite { top: 50%; left: -5px; background-color: var(--accent-secondary); animation: satellite-pulse-2 1.4s 0.2s ease-in-out infinite alternate; }
80
+ .orbit:nth-child(2) .satellite { bottom: -5px; right: 50%; animation: satellite-pulse-1 1.4s 0.4s ease-in-out infinite alternate;}
81
+ .loading-text { font-size: 1.2em; font-weight: 700; color: var(--text-primary); text-align: center; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
82
+
83
+ #result-content { display: none; width: 100%; text-align: center; animation: fadeIn 0.5s ease-out; }
84
+ #result-content h2 { color: var(--text-primary); border-bottom: 2px solid var(--panel-border); padding-bottom: 1rem; margin-bottom: 1rem; font-size: 1.5em; }
85
+ #result-image { max-width: 100%; border-radius: var(--radius-card); border: 2px solid var(--accent-secondary); padding: 5px; box-shadow: var(--shadow-lg); cursor: pointer; transition: transform 0.2s ease; }
86
+ #result-image:hover { transform: scale(1.02); }
87
+
88
+ #result-content a.download-btn {
89
+ display: none; align-items: center; justify-content: center; gap: 10px;
90
+ margin-top: 1.5rem; padding: 0.9rem 1.8rem; font-size: 1.1em; font-weight: 700;
91
+ background: linear-gradient(95deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
92
+ color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer;
93
+ transition: var(--transition-smooth);
94
+ box-shadow: 0 5px 10px -2px var(--accent-primary-glow), 0 5px 10px -2px var(--accent-secondary-glow);
95
+ text-decoration: none;
96
+ }
97
+ #result-content a.download-btn:hover {
98
+ transform: translateY(-4px) scale(1.03);
99
+ box-shadow: 0 7px 18px -3px var(--accent-primary-glow), 0 7px 18px -3px var(--accent-secondary-glow);
100
+ }
101
+ #result-content a.download-btn svg { width: 20px; height: 20px; }
102
+
103
  .prompt-box {
104
+ display: none; background-color: #1e293b; color: #ecf0f1;
105
+ padding: 1.5rem; border-radius: 16px; margin-top: 2rem;
106
+ font-size: 0.95rem; text-align: left; direction: ltr;
107
+ box-shadow: var(--shadow-md); border: 1px solid rgba(255, 255, 255, 0.1);
108
+ }
109
+ .prompt-box h3 { margin-top: 0; color: #f1c40f; font-size: 1.1rem; text-align: right; direction: rtl; font-family: var(--app-font); }
110
+ .prompt-box div { line-height: 1.7; word-wrap: break-word; }
111
+
112
+ .status-message.error { color: #c53030; font-weight: 600; }
113
+
114
+ .fullscreen-modal {
115
+ display: none; position: fixed; z-index: 1000; top: 0; left: 0;
116
+ width: 100%; height: 100%; background-color: rgba(26, 32, 44, 0.9);
117
+ justify-content: center; align-items: center; animation: fadeIn 0.3s ease;
118
+ }
119
+ .fullscreen-image {
120
+ max-width: 90%; max-height: 90%; object-fit: contain;
121
+ border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.5);
122
+ }
123
+ .close-fullscreen {
124
+ position: absolute; top: 20px; right: 35px; color: #fff; font-size: 40px;
125
+ font-weight: bold; cursor: pointer; transition: color 0.2s, transform 0.2s;
126
+ }
127
+ .close-fullscreen:hover { color: var(--text-tertiary); transform: scale(1.1); }
128
+
129
+ /* نشانگر عضویت و مدال */
130
+ #subscription-status-badge { display: none; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; font-weight: 700; margin-top: 1.25rem; letter-spacing: 0.5px; animation: fadeIn 0.6s 0.5s ease-out backwards; user-select: none; transition: transform 0.2s ease, opacity 0.2s ease; }
131
+ #subscription-status-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3); cursor: pointer; }
132
+ #subscription-status-badge.free-badge:hover { transform: scale(1.05); opacity: 0.95; }
133
+ #subscription-status-badge.paid-badge { background: linear-gradient(45deg, #FFC107, #ffca2c); color: #333; box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3); }
134
+
135
+ .modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(26, 32, 44, 0.5); backdrop-filter: blur(8px); z-index: 1000; animation: modal-fade-in 0.3s ease-out; }
136
+ .modal-overlay.active { display: flex; align-items: center; justify-content: center; }
137
+ .modal-content { background-color: var(--panel-bg); padding: 2.5rem; border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); width: 90%; max-width: 450px; text-align: center; position: relative; animation: modal-zoom-in 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
138
+ .modal-icon { width: 60px; height: 60px; margin: 0 auto 1.5rem; background: rgba(74, 108, 250, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); }
139
+ .modal-icon svg { width: 32px; height: 32px; }
140
+ .modal-title { font-size: 1.3rem; font-weight: 700; color: var(--text-primary); margin: 0 0 1rem 0; }
141
+ .modal-text { font-size: 0.95rem; color: var(--text-secondary); line-height: 1.7; margin: 0 0 2rem 0; }
142
+ .modal-buttons { display: flex; gap: 1rem; width: 100%; }
143
+ .modal-button { flex: 1; padding: 0.8rem 1.5rem; background: var(--accent-primary); color: white; border: none; border-radius: var(--radius-btn); font-family: var(--app-font); font-weight: 600; font-size: 1rem; cursor: pointer; transition: var(--transition-smooth); }
144
+ .modal-button:hover { background: var(--accent-primary-hover); transform: translateY(-2px); }
145
+ .modal-button.secondary { background: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
146
+ .modal-button.secondary:hover { background-color: var(--panel-border); color: var(--text-primary); }
147
+
148
+ @keyframes modal-fade-in { from { opacity: 0; } to { opacity: 1; } }
149
+ @keyframes modal-zoom-in { from { transform: scale(0.9); } to { transform: scale(1); } }
150
+
151
+ @media (max-width: 600px) {
152
+ body { padding: 1.5rem 1rem; }
153
+ .main-content { padding: 1.5rem; }
154
+ .flex-group { flex-direction: column; gap: 0; }
155
+ .app-header h1 { font-size: 2em; }
156
  }
157
  </style>
158
  </head>
159
  <body>
160
+ <div class="app-container">
161
+ <header class="app-header">
162
+ <h1>تولید تصویر با Ideogram 4</h1>
163
+ <p>دستور متنی خود را به هر زبانی بنویسید تا آن را با ساختار عالی و جزییات خیره‌کننده طراحی کند.</p>
164
+ <div id="subscription-status-badge" style="display:none; margin-top: 1.25rem;"></div>
165
+ </header>
166
 
167
+ <main class="main-content" id="main-content">
168
+ <form onsubmit="return false;">
169
+
170
+ <div class="form-group">
171
+ <label for="prompt">
172
+ <span class="label-icon">
173
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
174
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
175
+ </svg>
176
+ </span>
177
+ پرامپت (به فارسی یا انگلیسی):
178
+ </label>
179
+ <input type="text" id="prompt" placeholder="یک گربه بامزه با کلاه جادوگری در حال خواندن کتاب طلسم..." value="یک گربه بامزه با کلاه جادوگری در حال خواندن کتاب طلسم">
180
+ </div>
181
 
182
+ <div class="flex-group">
183
+ <div class="form-group">
184
+ <label for="width">عرض (Width):</label>
185
+ <input type="number" id="width" value="1024" step="64" min="512" max="1536">
186
+ </div>
187
+ <div class="form-group">
188
+ <label for="height">ارتفاع (Height):</label>
189
+ <input type="number" id="height" value="1024" step="64" min="512" max="1536">
190
+ </div>
191
+ </div>
192
+
193
+ <button type="submit" id="submit-btn" class="generate-btn">
194
+ <span class="btn-text">✨ ساخت تصویر</span>
195
+ <div class="spinner"></div>
196
+ </button>
197
+ </form>
198
+
199
+ <div id="output-section" class="output-section">
200
+ <div id="status-message" class="status-message">نتیجه تصویر تولید شده در اینجا نمایش داده خواهد شد.</div>
201
+
202
+ <div id="loading-animation" class="loading-animation-wrapper">
203
+ <div class="orbital-loader">
204
+ <div class="orbit"><div class="satellite"></div></div>
205
+ <div class="orbit"><div class="satellite"></div></div>
206
+ </div>
207
+ <p id="loading-text" class="loading-text"></p>
208
+ </div>
209
+
210
+ <div id="result-content">
211
+ <h2>تصویر تولید شده نهایی:</h2>
212
+ <img id="result-image" src="#" alt="تصویر تولید شده" />
213
+ <a href="#" id="download-btn" class="download-btn">
214
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
215
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
216
+ <polyline points="7 10 12 15 17 10"></polyline>
217
+ <line x1="12" y1="15" x2="12" y2="3"></line>
218
+ </svg>
219
+ <span>دانلود تصویر</span>
220
+ </a>
221
+ </div>
222
+
223
+ <div id="enhancedPromptBox" class="prompt-box">
224
+ <h3>✨ سناریو و پرامپت ارتقا یافته توسط هوش مصنوعی:</h3>
225
+ <div id="enhancedPromptText"></div>
226
+ </div>
227
  </div>
228
+ </main>
229
+ </div>
230
 
231
+ <div id="fullscreen-modal" class="fullscreen-modal">
232
+ <span id="close-fullscreen" class="close-fullscreen">&times;</span>
233
+ <img id="fullscreen-image" class="fullscreen-image" src="#" alt="نمایش تمام صفحه">
234
+ </div>
235
+
236
+ <!-- مودال اتمام اعتبار روزانه برای کاربران رایگان -->
237
+ <div class="modal-overlay" id="limit-modal">
238
+ <div class="modal-content">
239
+ <div class="modal-icon" style="color: #FFC107; background: rgba(255, 193, 7, 0.1);">
240
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
241
+ <circle cx="12" cy="12" r="10"></circle>
242
+ <line x1="12" y1="9" x2="12" y2="13"></line>
243
+ <line x1="12" y1="17" x2="12.01" y2="17"></line>
244
+ </svg>
245
+ </div>
246
+ <h3 class="modal-title">محدودیت استفاده روزانه</h3>
247
+ <p class="modal-text">کاربر گرامی، در نسخه رایگان شما مجاز به انجام روزانه <b>۱ تصویر</b> با کیفیت فوق‌العاده هستید.<br><br>برای استفاده نامحدود، می‌توانید حساب خود را ارتقا دهید.</p>
248
+ <div class="modal-buttons">
249
+ <button class="modal-button secondary" id="close-limit-modal">بستن</button>
250
+ <button class="modal-button" id="upgrade-limit-modal" style="background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);">⭐️ ارتقا به نسخه نامحدود</button>
251
+ </div>
252
  </div>
253
  </div>
254
 
255
  <script type="module">
256
+ const promptInput = document.getElementById('prompt');
257
+ const widthInput = document.getElementById('width');
258
+ const heightInput = document.getElementById('height');
259
+ const submitBtn = document.getElementById('submit-btn');
260
+ const btnText = submitBtn.querySelector('.btn-text');
261
+ const btnSpinner = submitBtn.querySelector('.spinner');
262
+ const outputSection = document.getElementById('output-section');
263
+ const statusMessage = document.getElementById('status-message');
264
+ const loadingAnimation = document.getElementById('loading-animation');
265
+ const loadingText = document.getElementById('loading-text');
266
+ const resultContent = document.getElementById('result-content');
267
+ const resultImage = document.getElementById('result-image');
268
+ const downloadBtn = document.getElementById('download-btn');
269
+ const fullscreenModal = document.getElementById('fullscreen-modal');
270
+ const fullscreenImage = document.getElementById('fullscreen-image');
271
+ const closeFullscreenBtn = document.getElementById('close-fullscreen');
272
  const enhancedPromptBox = document.getElementById('enhancedPromptBox');
273
  const enhancedPromptText = document.getElementById('enhancedPromptText');
274
+
275
+ let currentRunId = '';
276
  let pollInterval = null;
277
+ let activePollRunId = '';
278
+ let timeoutTimer = null;
279
 
280
+ // متغیرهای هویت‌سنجی و وضعیت پرداخت
281
+ let userSubscriptionStatus = 'free';
282
+ let userFingerprint = null;
283
+ const PREMIUM_PAGE_ID = '1149636';
284
 
285
+ async function getBrowserFingerprint() {
286
+ const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset(), "canvas-test"];
287
+ const fingerprintString = components.join('---');
288
+ let hash = 0;
289
+ for (let i = 0; i < fingerprintString.length; i++) {
290
+ hash = ((hash << 5) - hash) + fingerprintString.charCodeAt(i);
291
+ hash |= 0;
292
  }
293
+ return 'fp_ideogram_' + Math.abs(hash).toString(16);
294
+ }
295
 
296
+ function isUserPaid(userObject) {
297
+ return userObject?.isLogin && userObject.accessible_pages?.some(p => p == PREMIUM_PAGE_ID);
298
+ }
 
 
 
299
 
300
+ function updateUIForSubscriptionStatus(status) {
301
+ userSubscriptionStatus = status;
302
+ const subscriptionBadge = document.getElementById('subscription-status-badge');
303
+ if (!subscriptionBadge) return;
304
 
305
+ if (status === 'paid') {
306
+ subscriptionBadge.innerHTML = 'نسخه نامحدود';
307
+ subscriptionBadge.className = 'paid-badge';
308
+ subscriptionBadge.onclick = null;
309
+ subscriptionBadge.style.cursor = 'default';
310
+ } else {
311
+ subscriptionBadge.innerHTML = 'نسخه رایگان (ارتقا)';
312
+ subscriptionBadge.className = 'free-badge';
313
+ subscriptionBadge.style.cursor = 'pointer';
314
+ subscriptionBadge.onclick = (e) => {
315
+ e.preventDefault();
316
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM' }, '*');
317
+ };
318
+ }
319
+ subscriptionBadge.style.display = 'inline-block';
320
+ }
321
+
322
+ function getDailyUsage() {
323
+ if (!userFingerprint) return 0;
324
+ const today = new Date().toDateString();
325
+ const storedDate = localStorage.getItem(`ideogram_date_${userFingerprint}`);
326
+ if (storedDate !== today) {
327
+ return 0;
328
+ }
329
+ return parseInt(localStorage.getItem(`ideogram_count_${userFingerprint}`) || '0', 10);
330
+ }
331
+
332
+ function incrementDailyUsage() {
333
+ if (!userFingerprint) return;
334
+ const today = new Date().toDateString();
335
+ localStorage.setItem(`ideogram_date_${userFingerprint}`, today);
336
+ const currentUsage = getDailyUsage();
337
+ localStorage.setItem(`ideogram_count_${userFingerprint}`, (currentUsage + 1).toString());
338
+ }
339
+
340
+ document.getElementById('close-limit-modal').addEventListener('click', () => {
341
+ document.getElementById('limit-modal').classList.remove('active');
342
+ });
343
+ document.getElementById('upgrade-limit-modal').addEventListener('click', () => {
344
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM' }, '*');
345
+ });
346
 
347
+ window.addEventListener('message', (event) => {
348
+ if (event.data?.type === 'USER_DATA_RESPONSE') {
349
+ if (event.data.error || !event.data.payload) {
350
+ updateUIForSubscriptionStatus('free');
351
+ return;
352
  }
353
+ try {
354
+ const userObject = JSON.parse(event.data.payload);
355
+ updateUIForSubscriptionStatus(isUserPaid(userObject) ? 'paid' : 'free');
356
+ } catch (e) {
357
+ updateUIForSubscriptionStatus('free');
358
+ }
359
+ }
360
+ });
361
 
362
+ const setUIState = (state) => {
363
+ submitBtn.disabled = state === 'loading';
364
+ if (state === 'loading') {
365
+ btnText.style.display = 'none';
366
+ btnSpinner.style.display = 'inline-block';
367
+ outputSection.classList.remove('has-content');
368
+ statusMessage.style.display = 'none';
369
+ statusMessage.classList.remove('error');
370
+ resultContent.style.display = 'none';
371
+ enhancedPromptBox.style.display = 'none';
372
+ loadingAnimation.style.display = 'flex';
373
+ } else {
374
+ btnText.style.display = 'inline-block';
375
+ btnSpinner.style.display = 'none';
376
+ if (state !== 'idle') {
377
+ loadingAnimation.style.display = 'none';
378
+ outputSection.classList.add('has-content');
379
  }
380
+ }
381
+ };
382
 
383
+ const handleTimeout = () => {
384
+ if (pollInterval) {
385
+ clearInterval(pollInterval);
386
+ pollInterval = null;
 
387
  }
388
+ activePollRunId = '';
389
+ setUIState('error');
390
+ statusMessage.style.display = 'block';
391
+ statusMessage.classList.add('error');
392
+ statusMessage.innerHTML = `پردازش بیش از حد طولانی شد ممکن است سرور شلوغ باشد، لطفاً مجدداً تلاش کنید.`;
393
+ };
394
+
395
+ const handleErrorState = (error) => {
396
+ if (timeoutTimer) {
397
+ clearTimeout(timeoutTimer);
398
+ timeoutTimer = null;
399
+ }
400
+ setUIState('error');
401
+ statusMessage.style.display = 'block';
402
+ statusMessage.classList.add('error');
403
+ statusMessage.innerHTML = `خطایی رخ داد: ${error.message || 'مشکل ارتباطی با سرور.'}`;
404
+ };
405
 
406
  function startPolling(runId) {
407
+ if (pollInterval) clearInterval(pollInterval);
408
+ activePollRunId = runId;
409
+
410
  pollInterval = setInterval(async () => {
411
+ if (activePollRunId !== runId) {
412
+ clearInterval(pollInterval);
413
+ return;
414
+ }
415
  try {
416
+ const imgCheck = await fetch(`/static/images/${runId}.png`, { method: 'HEAD' });
417
+ if (activePollRunId !== runId) return;
 
 
418
 
419
+ if (imgCheck.ok) {
420
+ if (timeoutTimer) {
421
+ clearTimeout(timeoutTimer);
422
+ timeoutTimer = null;
423
+ }
424
  clearInterval(pollInterval);
425
+ setUIState('success');
426
+ resultImage.src = `/static/images/${runId}.png`;
427
+ downloadBtn.style.display = 'inline-flex';
428
+ resultContent.style.display = 'block';
429
 
430
+ // بارگذاری پرامپت ارتقا یافته در صورت وجود
431
  try {
432
+ const metaRes = await fetch(`/static/images/${runId}_meta.json`);
433
+ if (metaRes.ok) {
434
+ const metaData = await metaRes.json();
435
  let displayHtml = "";
436
+ if (metaData.high_level_description) {
437
+ displayHtml += `<p><strong>High Level Description:</strong><br>${metaData.high_level_description}</p>`;
438
  }
439
+ displayHtml += `<pre style="white-space: pre-wrap; font-size: 11px; color: #bdc3c7;">${JSON.stringify(metaData, null, 2)}</pre>`;
440
  enhancedPromptText.innerHTML = displayHtml;
441
  enhancedPromptBox.style.display = "block";
442
  }
443
+ } catch (e) {
444
+ console.warn("Failed to fetch upsampled metadata:", e);
445
  }
446
 
447
+ if (userSubscriptionStatus !== 'paid') {
448
+ incrementDailyUsage();
449
+ }
450
+ return;
451
+ }
452
+
453
+ const activeRes = await fetch('/api/actions/active');
454
+ const activeData = await activeRes.json();
455
+ const hasActiveActions = activeData.status === 'success' && activeData.actions.length > 0;
456
+
457
+ if (hasActiveActions) {
458
+ loadingText.textContent = "در حال ارتقای پرامپت و پردازش تصویر با جزئیات بالا...";
459
  } else {
460
+ loadingText.textContent = "در انتظار شروع پردازش در سرور گیت‌هاب...";
 
461
  }
462
  } catch (e) {
463
+ console.error("Error polling status:", e);
464
  }
465
+ }, 4000);
466
  }
 
467
 
468
+ submitBtn.addEventListener('click', async () => {
469
+ const promptVal = promptInput.value.trim();
470
+ const widthVal = parseInt(widthInput.value) || 1024;
471
+ const heightVal = parseInt(heightInput.value) || 1024;
472
+
473
+ if (!promptVal) {
474
+ alert("لطفاً پرامپت را وارد کنید.");
475
+ return;
476
+ }
477
+
478
+ if (userSubscriptionStatus !== 'paid') {
479
+ const usage = getDailyUsage();
480
+ if (usage >= 1) {
481
+ document.getElementById('limit-modal').classList.add('active');
482
+ return;
483
+ }
484
+ }
485
+
486
+ if (pollInterval) clearInterval(pollInterval);
487
+ if (timeoutTimer) clearTimeout(timeoutTimer);
488
+ activePollRunId = '';
489
+
490
+ setUIState('loading');
491
+ currentRunId = 'ideo' + Math.random().toString(36).substring(2, 14);
492
+
493
+ timeoutTimer = setTimeout(() => {
494
+ handleTimeout();
495
+ }, 180000); // زمان انتظار ۳ دقیقه‌ای
496
+
497
+ try {
498
+ loadingText.textContent = "در حال ارسال درخواست به سرور...";
499
+
500
+ const response = await fetch('/api/generate', {
501
+ method: 'POST',
502
+ headers: { 'Content-Type': 'application/json' },
503
+ body: JSON.stringify({
504
+ prompt: promptVal,
505
+ width: widthVal,
506
+ height: heightVal,
507
+ action_name: 'generate-ideogram'
508
+ })
509
+ });
510
+
511
+ const data = await response.json();
512
+
513
+ if (data.status === 'success') {
514
+ startPolling(data.run_id);
515
+ } else {
516
+ throw new Error(data.message || 'خطا در صف‌بندی درخواست.');
517
+ }
518
+
519
+ } catch (error) {
520
+ console.error("An error occurred:", error);
521
+ handleErrorState(error);
522
+ }
523
+ });
524
+
525
+ resultImage.addEventListener('click', () => {
526
+ if (resultImage.src && resultImage.src !== window.location.href + '#') {
527
+ fullscreenImage.src = resultImage.src;
528
+ fullscreenModal.style.display = 'flex';
529
+ }
530
+ });
531
+
532
+ const closeModal = () => { fullscreenModal.style.display = 'none'; };
533
+ closeFullscreenBtn.addEventListener('click', closeModal);
534
+ fullscreenModal.addEventListener('click', (e) => { if (e.target === fullscreenModal) { closeModal(); } });
535
+
536
+ downloadBtn.addEventListener('click', async (event) => {
537
+ event.preventDefault();
538
+ const imageUrl = resultImage.src;
539
+ if (!imageUrl || imageUrl.endsWith('#')) return;
540
+
541
+ const absoluteUrl = new URL(imageUrl, window.location.href).href;
542
+
543
+ if (window.parent && window.parent !== window) {
544
+ window.parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: absoluteUrl }, '*');
545
+ } else {
546
+ try {
547
+ const response = await fetch(absoluteUrl);
548
+ const blob = await response.blob();
549
+ const objectUrl = URL.createObjectURL(blob);
550
+ const a = document.createElement('a');
551
+ a.href = objectUrl;
552
+ a.download = `ideogram-${Date.now()}.png`;
553
+ document.body.appendChild(a);
554
+ a.click();
555
+ document.body.removeChild(a);
556
+ URL.revokeObjectURL(objectUrl);
557
+ } catch (e) {
558
+ console.error("Direct download failed:", e);
559
+ alert("خطا در دانلود مستقیم فایل.");
560
+ }
561
+ }
562
+ });
563
+
564
+ document.addEventListener('DOMContentLoaded', async () => {
565
+ userFingerprint = await getBrowserFingerprint();
566
+ if (parent && parent !== window) {
567
+ parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
568
+ } else {
569
+ updateUIForSubscriptionStatus('free');
570
+ }
571
+ setUIState('idle');
572
+ });
573
+ </script>
574
  </body>
575
  </html>