codebyam commited on
Commit
e78132e
·
verified ·
1 Parent(s): 4c93868

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +416 -201
app.py CHANGED
@@ -94,271 +94,434 @@ async def get_agents_md():
94
 
95
  @app.get("/", response_class=HTMLResponse)
96
  async def read_items():
97
- html_content = """
98
- <!DOCTYPE html>
99
  <html lang="en">
100
  <head>
101
  <meta charset="UTF-8">
102
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
103
  <title>Prompt Optimizer Bot</title>
104
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  body {
106
  margin: 0 auto;
107
- background-color: #e8ebf3;
108
- font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
109
- max-width: 90%;
 
 
 
 
110
  height: 100vh;
111
  display: flex;
112
- border-left: 1px solid #ddd;
113
- border-right: 1px solid #ddd;
114
  flex-direction: column;
 
 
 
115
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  .header {
117
  display: flex;
118
  justify-content: space-between;
119
  align-items: center;
120
- padding: 10px 14px;
121
- background-color: #007bff;
122
- color: white;
123
- font-weight: 600;
124
- border-bottom: 1px solid #0056b3;
125
- font-size: 1.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  }
 
127
  #clearBtn {
128
- background-color: white;
129
- color: #007bff;
130
- border: none;
131
- border-radius: 6px;
132
- padding: 5px 10px;
133
  cursor: pointer;
134
- font-size: 0.65em;
135
- font-weight: 500;
136
- transition: background-color 0.2s, color 0.2s;
 
 
137
  }
138
  #clearBtn:hover {
139
- background-color: #0056b3;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
 
 
 
 
 
142
  .chat-wrapper {
143
  display: flex;
144
  flex-direction: column;
145
  height: 100%;
146
- background-color: #ffffff;
147
- justify-content: space-between;
 
 
148
  }
 
149
  .chat-window {
150
  flex: 1;
151
  overflow-y: auto;
152
- padding: 12px;
153
  display: flex;
154
  flex-direction: column;
155
- gap: 12px;
 
 
 
 
 
 
156
  }
 
157
  .message, .invite {
158
  display: flex;
159
  align-items: flex-start;
160
- gap: 2px;
 
161
  }
162
- .bot-message .bubble {
163
- background-color: #ffffff;
164
- color: #1c1e21;
165
  }
166
- .user-message {
167
- justify-content: flex-end;
 
 
 
 
 
 
 
 
 
 
 
168
  }
169
- .user-message .bubble {
170
- background-color: #007bff;
171
- color: #fff;
 
 
 
 
172
  }
 
 
 
 
 
 
173
  .bubble {
174
- padding: 8px 12px;
175
- border-radius: 16px;
176
- max-width: 75%;
177
- font-size: 1.05em;
178
- line-height: 1.4;
179
  word-wrap: break-word;
180
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
181
  }
182
- .avatar {
183
- font-size: 1.2em;
184
- user-select: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  }
 
 
 
 
 
186
  .input-bar {
187
  display: flex;
188
- border-top: 1px solid #ddd;
189
- padding: 8px;
190
- background-color: #ffffff;
191
- align-items: center;
 
 
 
192
  }
193
  textarea {
194
  flex: 1;
195
- border: 1px solid #ccc;
196
- border-radius: 12px;
197
- padding: 8px 10px;
198
  resize: none;
199
  max-height: 100px;
200
  font-size: 1em;
201
- font-family: inherit;
202
  outline: none;
 
 
 
203
  }
 
204
  textarea:focus {
205
- border-color: #007bff;
 
206
  }
207
  #sendButton {
208
- background-color: #007bff;
209
  color: white;
210
- border: none;
211
  border-radius: 50%;
212
- width: 38px;
213
- height: 38px;
214
- margin-left: 8px;
215
  font-size: 1.1em;
216
  cursor: pointer;
217
- transition: background-color 0.2s;
 
 
218
  }
219
  #sendButton:hover {
220
- background-color: #0056b3;
 
 
221
  }
222
- .copy-btn {
223
- background-color: #4CAF50;
224
- color: white;
225
- border: none;
226
- border-radius: 6px;
227
- padding: 4px 8px;
228
- margin-top: 6px;
229
- font-size: 0.8em;
230
- cursor: pointer;
231
- }
232
- .copy-btn:hover {
233
- background-color: #3e8e41;
234
- }
235
-
236
- .button {
237
- background-color: #007bff;
238
- color: white;
239
- border: none;
240
- padding: 5px 10px;
241
- font-size: 0.65em;
242
- border-radius: 6px;
243
- cursor: pointer;
244
- margin-right: 14px;
245
- font-weight: 500;
246
- transition: background-color 0.2s, color 0.2s;
247
- }
248
-
249
- .button:hover {
250
- background-color: #0056b3;
251
  }
252
 
253
  .modal {
254
  display: none;
255
  position: fixed;
256
  z-index: 1000;
257
- left: 0;
258
- top: 0;
259
- width: 100%;
260
- height: 100%;
261
  overflow: auto;
262
- background-color: rgba(0,0,0,0.6);
263
  }
264
-
265
  .modal-content {
266
- background-color: #fff;
267
- margin: 10% auto;
268
- padding: 20px 30px;
269
- border-radius: 8px;
270
- width: 80%;
271
- max-width: 800px;
 
272
  text-align: left;
273
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
 
 
 
 
 
 
274
  }
275
-
276
  .close {
277
- color: #aaa;
278
  float: right;
279
- font-size: 24px;
280
  font-weight: bold;
281
  cursor: pointer;
 
282
  }
283
-
284
- .close:hover {
285
- color: #000;
286
- }
287
 
288
  h2 {
289
- border-bottom: 2px solid #ddd;
290
- padding-bottom: 8px;
291
- margin-top: 20px;
 
 
292
  }
293
-
294
  pre {
295
- background-color: #f0f0f0;
296
- border-radius: 5px;
297
- padding: 15px;
 
298
  overflow-x: auto;
299
  }
300
-
301
  code {
302
  font-family: Consolas, "Courier New", monospace;
303
- color: #333;
304
  display: block;
 
305
  }
306
- .loading-dots span {
307
- animation: blink 1.4s infinite both;
308
- }
309
- .loading-dots span:nth-child(2) {
310
- animation-delay: 0.2s;
311
- }
312
- .loading-dots span:nth-child(3) {
313
- animation-delay: 0.4s;
314
- }
315
  @keyframes blink {
316
  0%, 80%, 100% { opacity: 0; }
317
  40% { opacity: 1; }
318
  }
319
 
320
  @media (max-width: 600px) {
321
- body {
322
- max-width: 100%;
323
- border-left: none;
324
- border-right: none;
325
- }
326
-
327
- .header {
328
- flex-direction: column;
329
- align-items: flex-start;
330
- padding: 10px;
331
- font-size: 1.2rem;
332
- }
333
- .header > div:last-child {
334
- margin-top: 5px;
335
- }
336
- .bubble {
337
- max-width: 90%
338
- font-size: 1em;
339
- }
340
- .input-bar {
341
- padding: 6px;
342
- }
343
-
344
- #userInput {
345
- max-height: 80px;
346
- font-size: 0.95em;
347
- padding: 6px 8px;
348
- }
349
-
350
- #sendButton {
351
- width: 32px;
352
- height: 32px;
353
- font-size: 1em;
354
- margin-left: 6px;
355
- }
356
-
357
- .modal-content {
358
- margin: 5% auto;
359
- width: 95%;
360
- padding: 15px;
361
- }
362
  }
363
  </style>
364
  <script>
@@ -372,37 +535,61 @@ async def read_items():
372
  const modal = document.getElementById("apiModal");
373
  const btn = document.getElementById("apiKeyBtn");
374
  const close = document.getElementById("closeModal");
375
-
376
  btn.onclick = () => modal.style.display = "block";
377
  close.onclick = () => modal.style.display = "none";
378
  window.onclick = (event) => {
379
- if (event.target == modal) modal.style.display = "none";
380
  };
 
381
  function formatTextWithLineBreaks(text) {
382
  const escaped = text
383
  .replace(/&/g, "&amp;")
384
  .replace(/</g, "&lt;")
385
  .replace(/>/g, "&gt;");
386
- return escaped.replace(/\\n/g, "<br>");
387
  }
 
 
 
 
 
 
 
 
 
 
 
388
  function appendMessage(role, text, options = {}) {
389
  const { withCopy = false, isLoading = false } = options;
390
  const msgDiv = document.createElement("div");
391
  msgDiv.classList.add("message", role === "bot" ? "bot-message" : "user-message");
 
 
 
392
  const avatar = document.createElement("div");
393
  avatar.classList.add("avatar");
394
- avatar.textContent = role === "bot" ? "🤖" : "🧑";
395
- const bubble = document.createElement("div");
396
- bubble.classList.add("bubble");
397
- if (isLoading) {
398
- bubble.innerHTML = `<span class="loading-dots">✨ Optimizing your prompt<span>.</span><span>.</span><span>.</span></span>`;
399
- msgDiv.dataset.loading = "true";
400
  } else {
401
- bubble.innerHTML = formatTextWithLineBreaks(text);
 
402
  }
403
- msgDiv.appendChild(avatar);
404
- msgDiv.appendChild(bubble);
405
- if (withCopy) {
 
 
 
 
 
 
 
 
 
 
 
406
  const copyBtn = document.createElement("button");
407
  copyBtn.classList.add("copy-btn");
408
  copyBtn.textContent = "Copy";
@@ -411,53 +598,80 @@ async def read_items():
411
  copyBtn.textContent = "Copied!";
412
  setTimeout(() => (copyBtn.textContent = "Copy"), 1500);
413
  });
414
- bubble.appendChild(document.createElement("br"));
415
- bubble.appendChild(copyBtn);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  }
 
417
  chatWindow.appendChild(msgDiv);
418
  chatWindow.scrollTop = chatWindow.scrollHeight;
419
  }
 
420
  function removeLoadingMessage() {
421
  const loadingMsg = chatWindow.querySelector('[data-loading="true"]');
422
  if (loadingMsg) chatWindow.removeChild(loadingMsg);
423
  }
 
424
  async function saveHistory() {
425
  const messages = [];
426
  chatWindow.querySelectorAll(".message").forEach((msg) => {
427
  const role = msg.classList.contains("user-message") ? "user" : "bot";
428
- const text = msg.querySelector(".bubble")?.innerText || "";
429
- messages.push({ role, text });
 
 
430
  });
431
  try {
432
- await chrome.storage.local.set({ [STORAGE_KEY]: messages });
433
  } catch (err) {
434
  console.warn("⚠️ Failed to save chat history:", err);
435
  }
436
  }
 
437
  async function loadHistory() {
438
  try {
439
- const result = await chrome.storage.local.get(STORAGE_KEY);
440
  const messages = result[STORAGE_KEY] || [];
441
  messages.forEach((msg) =>
442
- appendMessage(msg.role, msg.text, { withCopy: msg.role === "bot" })
443
  );
444
  } catch (err) {
445
  console.warn("⚠️ Failed to load chat history:", err);
446
  }
447
  }
 
448
  clearBtn.addEventListener("click", async () => {
449
- await chrome.storage.local.remove(STORAGE_KEY);
450
  chatWindow.innerHTML = `
451
- <div class="message bot-message">
452
- <div class="avatar bot-avatar">🤖</div>
453
- <div class="bubble">Hi! I'm your Prompt Optimizer Bot. Paste your prompt below, and I’ll make it better for you.</div>
454
  </div>`;
455
  });
 
456
  async function handleSend() {
457
  const prompt = userInput.value.trim();
458
  if (!prompt) return;
459
  appendMessage("user", prompt);
460
  userInput.value = "";
 
461
  appendMessage("bot", "", { isLoading: true });
462
  await saveHistory();
463
  try {
@@ -473,7 +687,7 @@ async def read_items():
473
  if (optimized) {
474
  appendMessage("bot", optimized, { withCopy: true });
475
  } else {
476
- appendMessage("bot", "⚠️ The API responded successfully but didnt return optimized text.");
477
  }
478
  await saveHistory();
479
  } catch (error) {
@@ -486,6 +700,7 @@ async def read_items():
486
  await saveHistory();
487
  }
488
  }
 
489
  sendButton.addEventListener("click", handleSend);
490
  userInput.addEventListener("input", () => {
491
  userInput.style.height = "auto";
@@ -497,6 +712,7 @@ async def read_items():
497
  handleSend();
498
  }
499
  });
 
500
  loadHistory();
501
  });
502
  </script>
@@ -504,17 +720,17 @@ async def read_items():
504
  <body>
505
  <div class="chat-wrapper">
506
  <div class="header">
507
- <div>Prompt Optimizer</div>
508
- <div style="display: inline-flex;">
509
- <div class="button" id="apiKeyBtn">Get API</div>
510
- <button id="clearBtn">🧹 Clear</button>
511
  </div>
512
  </div>
513
  <div id="chatWindow" class="chat-window">
514
  <div class="invite bot-message">
515
- <div class="avatar bot-avatar">🤖</div>
516
  <div class="bubble">
517
- Hi! I'm your Prompt Optimizer Bot. Paste your prompt below, and I’ll make it better for you.
518
  </div>
519
  </div>
520
  </div>
@@ -550,6 +766,5 @@ const response = await fetch(API_ENDPOINT, {
550
  </div>
551
  </div>
552
  </body>
553
- </html>
554
- """
555
  return html_content
 
94
 
95
  @app.get("/", response_class=HTMLResponse)
96
  async def read_items():
97
+ html_content = """<!DOCTYPE html>
 
98
  <html lang="en">
99
  <head>
100
  <meta charset="UTF-8">
101
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
102
  <title>Prompt Optimizer Bot</title>
103
  <style>
104
+ @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,600;0,700;0,900;1,600&family=Inter:wght@400;500;600;700&family=Caveat:wght@600;700&display=swap');
105
+
106
+ :root {
107
+ --paper: #FAF3E7;
108
+ --paper-2: #F5E9D9;
109
+ --cobalt: #3D5AFE;
110
+ --coral: #FF6B5B;
111
+ --marigold: #FFB627;
112
+ --green: #4CAF7D;
113
+ --ink: #241F1A;
114
+ --ink-muted: #8A7F72;
115
+ --shadow: rgba(36, 31, 26, 0.18);
116
+ }
117
+
118
+ * { box-sizing: border-box; }
119
+
120
  body {
121
  margin: 0 auto;
122
+ background:
123
+ radial-gradient(circle at 15% 20%, rgba(255,182,39,0.08) 0%, transparent 40%),
124
+ radial-gradient(circle at 85% 80%, rgba(61,90,254,0.07) 0%, transparent 40%),
125
+ var(--paper);
126
+ font-family: 'Inter', sans-serif;
127
+ max-width: 720px;
128
+ min-height: 100vh;
129
  height: 100vh;
130
  display: flex;
 
 
131
  flex-direction: column;
132
+ color: var(--ink);
133
+ position: relative;
134
+ box-shadow: 0 0 0 1px rgba(36,31,26,0.06);
135
  }
136
+
137
+ /* paper grain texture overlay */
138
+ body::before {
139
+ content: "";
140
+ position: absolute;
141
+ inset: 0;
142
+ pointer-events: none;
143
+ opacity: 0.05;
144
+ background-image:
145
+ repeating-radial-gradient(circle at 0 0, transparent 0, var(--ink) 1px, transparent 2px);
146
+ background-size: 6px 6px;
147
+ z-index: 0;
148
+ }
149
+
150
  .header {
151
  display: flex;
152
  justify-content: space-between;
153
  align-items: center;
154
+ padding: 18px 22px;
155
+ background: var(--paper);
156
+ border-bottom: 3px solid var(--ink);
157
+ position: relative;
158
+ z-index: 2;
159
+ }
160
+ .header .logo {
161
+ font-family: 'Fraunces', serif;
162
+ font-weight: 900;
163
+ font-size: 1.6rem;
164
+ position: relative;
165
+ letter-spacing: -0.02em;
166
+ }
167
+ .header .logo::after {
168
+ content: "";
169
+ position: absolute;
170
+ left: 0;
171
+ bottom: -4px;
172
+ width: 60%;
173
+ height: 6px;
174
+ background: var(--marigold);
175
+ border-radius: 4px;
176
+ transform: rotate(-1deg);
177
+ z-index: -1;
178
+ }
179
+ .header .tag {
180
+ font-family: 'Caveat', cursive;
181
+ font-weight: 700;
182
+ font-size: 1rem;
183
+ color: var(--coral);
184
+ margin-left: 8px;
185
+ transform: rotate(-3deg);
186
+ display: inline-block;
187
  }
188
+
189
  #clearBtn {
190
+ background-color: var(--marigold);
191
+ color: var(--ink);
192
+ border: 2px solid var(--ink);
193
+ border-radius: 8px;
194
+ padding: 7px 12px;
195
  cursor: pointer;
196
+ font-size: 0.78em;
197
+ font-weight: 700;
198
+ font-family: 'Inter', sans-serif;
199
+ box-shadow: 3px 3px 0 var(--ink);
200
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
201
  }
202
  #clearBtn:hover {
203
+ transform: translate(-1px, -1px);
204
+ box-shadow: 4px 4px 0 var(--ink);
205
+ background-color: #ffc750;
206
+ }
207
+ #clearBtn:active {
208
+ transform: translate(2px, 2px);
209
+ box-shadow: 1px 1px 0 var(--ink);
210
+ }
211
+
212
+ .header-actions {
213
+ display: flex;
214
+ gap: 10px;
215
+ align-items: center;
216
+ }
217
+
218
+ .button {
219
+ background-color: var(--coral);
220
  color: white;
221
+ border: 2px solid var(--ink);
222
+ padding: 7px 12px;
223
+ font-size: 0.78em;
224
+ font-weight: 700;
225
+ border-radius: 8px;
226
+ cursor: pointer;
227
+ font-family: 'Inter', sans-serif;
228
+ box-shadow: 3px 3px 0 var(--ink);
229
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
230
+ }
231
+ .button:hover {
232
+ transform: translate(-1px, -1px);
233
+ box-shadow: 4px 4px 0 var(--ink);
234
+ background-color: #ff7f70;
235
  }
236
+ .button:active {
237
+ transform: translate(2px, 2px);
238
+ box-shadow: 1px 1px 0 var(--ink);
239
+ }
240
+
241
  .chat-wrapper {
242
  display: flex;
243
  flex-direction: column;
244
  height: 100%;
245
+ flex: 1;
246
+ min-height: 0;
247
+ position: relative;
248
+ z-index: 1;
249
  }
250
+
251
  .chat-window {
252
  flex: 1;
253
  overflow-y: auto;
254
+ padding: 20px;
255
  display: flex;
256
  flex-direction: column;
257
+ gap: 18px;
258
+ }
259
+ .chat-window::-webkit-scrollbar { width: 10px; }
260
+ .chat-window::-webkit-scrollbar-thumb {
261
+ background: var(--paper-2);
262
+ border: 2px solid var(--ink);
263
+ border-radius: 6px;
264
  }
265
+
266
  .message, .invite {
267
  display: flex;
268
  align-items: flex-start;
269
+ gap: 10px;
270
+ animation: popIn 0.25s ease;
271
  }
272
+ @keyframes popIn {
273
+ from { opacity: 0; transform: translateY(6px); }
274
+ to { opacity: 1; transform: translateY(0); }
275
  }
276
+
277
+ .avatar {
278
+ user-select: none;
279
+ background: white;
280
+ border: 2px solid var(--ink);
281
+ border-radius: 50%;
282
+ width: 38px;
283
+ height: 38px;
284
+ display: flex;
285
+ align-items: center;
286
+ justify-content: center;
287
+ flex-shrink: 0;
288
+ box-shadow: 2px 2px 0 var(--shadow);
289
  }
290
+ .avatar svg {
291
+ width: 20px;
292
+ height: 20px;
293
+ display: block;
294
+ }
295
+ .avatar.bot-avatar {
296
+ background: var(--cobalt);
297
  }
298
+ .avatar.user-avatar {
299
+ background: var(--marigold);
300
+ }
301
+
302
+ .user-message { justify-content: flex-end; }
303
+
304
  .bubble {
305
+ padding: 12px 16px;
306
+ max-width: 78%;
307
+ font-size: 1em;
308
+ line-height: 1.5;
 
309
  word-wrap: break-word;
310
+ position: relative;
311
  }
312
+
313
+ .bot-message .bubble {
314
+ background: white;
315
+ color: var(--ink);
316
+ border: 2px solid var(--ink);
317
+ border-radius: 18px 18px 18px 4px;
318
+ box-shadow: 3px 3px 0 var(--shadow);
319
+ }
320
+
321
+ .user-message .bubble {
322
+ background: var(--cobalt);
323
+ color: #fff;
324
+ border: 2px solid var(--ink);
325
+ border-radius: 18px 18px 4px 18px;
326
+ box-shadow: 3px 3px 0 var(--shadow);
327
+ }
328
+
329
+ .bot-message .bubble.error-bubble {
330
+ background: #FFF3F1;
331
+ border-color: var(--coral);
332
+ border-style: dashed;
333
+ color: #8a2c22;
334
+ }
335
+ .bot-message .bubble.error-bubble::before {
336
+ content: "";
337
+ }
338
+ .avatar.error-avatar {
339
+ background: var(--coral);
340
+ }
341
+
342
+ /* --- Refined Prompt "deliverable" card --- */
343
+ .output-card {
344
+ background: var(--paper-2);
345
+ border: 2px solid var(--ink);
346
+ border-radius: 14px;
347
+ box-shadow: 5px 5px 0 var(--ink);
348
+ padding: 0;
349
+ max-width: 88%;
350
+ overflow: hidden;
351
+ position: relative;
352
+ }
353
+ .output-card .sticky-tag {
354
+ font-family: 'Caveat', cursive;
355
+ font-weight: 700;
356
+ font-size: 1.25rem;
357
+ background: var(--marigold);
358
+ color: var(--ink);
359
+ display: inline-block;
360
+ padding: 4px 16px 6px 14px;
361
+ border-bottom: 2px solid var(--ink);
362
+ border-right: 2px solid var(--ink);
363
+ border-bottom-right-radius: 10px;
364
+ transform: rotate(-1deg);
365
+ }
366
+ .output-card .card-body {
367
+ padding: 16px 18px 18px 18px;
368
+ font-size: 0.98em;
369
+ line-height: 1.55;
370
+ color: var(--ink);
371
+ }
372
+ .output-card .copy-btn {
373
+ background-color: var(--green);
374
+ color: white;
375
+ border: 2px solid var(--ink);
376
+ border-radius: 8px;
377
+ padding: 6px 14px;
378
+ margin-top: 12px;
379
+ font-size: 0.85em;
380
+ font-weight: 700;
381
+ cursor: pointer;
382
+ box-shadow: 2px 2px 0 var(--ink);
383
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
384
+ }
385
+ .output-card .copy-btn:hover {
386
+ transform: translate(-1px, -1px);
387
+ box-shadow: 3px 3px 0 var(--ink);
388
  }
389
+ .output-card .copy-btn:active {
390
+ transform: translate(1px, 1px);
391
+ box-shadow: 1px 1px 0 var(--ink);
392
+ }
393
+
394
  .input-bar {
395
  display: flex;
396
+ border-top: 3px solid var(--ink);
397
+ padding: 14px;
398
+ background: var(--paper);
399
+ align-items: flex-end;
400
+ gap: 10px;
401
+ position: relative;
402
+ z-index: 2;
403
  }
404
  textarea {
405
  flex: 1;
406
+ border: 2px dashed var(--ink-muted);
407
+ border-radius: 14px;
408
+ padding: 10px 14px;
409
  resize: none;
410
  max-height: 100px;
411
  font-size: 1em;
412
+ font-family: 'Inter', sans-serif;
413
  outline: none;
414
+ background: white;
415
+ color: var(--ink);
416
+ transition: border-color 0.15s ease;
417
  }
418
+ textarea::placeholder { color: var(--ink-muted); }
419
  textarea:focus {
420
+ border-color: var(--cobalt);
421
+ border-style: solid;
422
  }
423
  #sendButton {
424
+ background-color: var(--cobalt);
425
  color: white;
426
+ border: 2px solid var(--ink);
427
  border-radius: 50%;
428
+ width: 44px;
429
+ height: 44px;
 
430
  font-size: 1.1em;
431
  cursor: pointer;
432
+ box-shadow: 3px 3px 0 var(--ink);
433
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
434
+ flex-shrink: 0;
435
  }
436
  #sendButton:hover {
437
+ transform: translate(-1px, -1px);
438
+ box-shadow: 4px 4px 0 var(--ink);
439
+ background-color: #4d68ff;
440
  }
441
+ #sendButton:active {
442
+ transform: translate(2px, 2px);
443
+ box-shadow: 1px 1px 0 var(--ink);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
 
446
  .modal {
447
  display: none;
448
  position: fixed;
449
  z-index: 1000;
450
+ left: 0; top: 0;
451
+ width: 100%; height: 100%;
 
 
452
  overflow: auto;
453
+ background-color: rgba(36,31,26,0.55);
454
  }
 
455
  .modal-content {
456
+ background-color: var(--paper);
457
+ margin: 8% auto;
458
+ padding: 24px 30px;
459
+ border: 3px solid var(--ink);
460
+ border-radius: 14px;
461
+ width: 85%;
462
+ max-width: 640px;
463
  text-align: left;
464
+ box-shadow: 6px 6px 0 var(--ink);
465
+ font-family: 'Inter', sans-serif;
466
+ }
467
+ .modal-content h1 {
468
+ font-family: 'Fraunces', serif;
469
+ font-weight: 900;
470
+ margin-top: 0;
471
  }
 
472
  .close {
473
+ color: var(--ink-muted);
474
  float: right;
475
+ font-size: 26px;
476
  font-weight: bold;
477
  cursor: pointer;
478
+ line-height: 1;
479
  }
480
+ .close:hover { color: var(--coral); }
 
 
 
481
 
482
  h2 {
483
+ font-family: 'Fraunces', serif;
484
+ border-bottom: 2px solid var(--ink);
485
+ padding-bottom: 6px;
486
+ margin-top: 22px;
487
+ font-weight: 700;
488
  }
 
489
  pre {
490
+ background-color: var(--paper-2);
491
+ border: 2px solid var(--ink);
492
+ border-radius: 8px;
493
+ padding: 14px;
494
  overflow-x: auto;
495
  }
 
496
  code {
497
  font-family: Consolas, "Courier New", monospace;
498
+ color: var(--ink);
499
  display: block;
500
+ font-size: 0.85em;
501
  }
502
+
503
+ .loading-dots span { animation: blink 1.4s infinite both; }
504
+ .loading-dots span:nth-child(2) { animation-delay: 0.2s; }
505
+ .loading-dots span:nth-child(3) { animation-delay: 0.4s; }
 
 
 
 
 
506
  @keyframes blink {
507
  0%, 80%, 100% { opacity: 0; }
508
  40% { opacity: 1; }
509
  }
510
 
511
  @media (max-width: 600px) {
512
+ body { max-width: 100%; }
513
+ .header { padding: 14px 16px; }
514
+ .header .logo { font-size: 1.3rem; }
515
+ .chat-window { padding: 8px; }
516
+ .message, .invite { gap: 6px; }
517
+ .avatar { width: 32px; height: 32px; border: 1.5px solid var(--ink); box-shadow: 1.5px 1.5px 0 var(--shadow); }
518
+ .bubble { padding: 6px 10px; }
519
+ .bot-message .bubble { border-radius: 12px 12px 12px 4px; }
520
+ .user-message .bubble { border-radius: 12px 12px 4px 12px; }
521
+ .bubble { max-width: 85%; font-size: 0.95em; }
522
+ .output-card { max-width: 95%; }
523
+ .input-bar { padding: 10px; }
524
+ #sendButton { width: 38px; height: 38px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  }
526
  </style>
527
  <script>
 
535
  const modal = document.getElementById("apiModal");
536
  const btn = document.getElementById("apiKeyBtn");
537
  const close = document.getElementById("closeModal");
538
+
539
  btn.onclick = () => modal.style.display = "block";
540
  close.onclick = () => modal.style.display = "none";
541
  window.onclick = (event) => {
542
+ if (event.target == modal) modal.style.display = "none";
543
  };
544
+
545
  function formatTextWithLineBreaks(text) {
546
  const escaped = text
547
  .replace(/&/g, "&amp;")
548
  .replace(/</g, "&lt;")
549
  .replace(/>/g, "&gt;");
550
+ return escaped.replace(/\n/g, "<br>");
551
  }
552
+
553
+ const BOT_ICON = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3v2.2" stroke="white" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="6.4" r="1.1" fill="white"/><rect x="5" y="8" width="14" height="10" rx="3" stroke="white" stroke-width="1.8"/><circle cx="9" cy="13" r="1.3" fill="white"/><circle cx="15" cy="13" r="1.3" fill="white"/><path d="M9 16.2h6" stroke="white" stroke-width="1.6" stroke-linecap="round"/></svg>`;
554
+
555
+ const USER_ICON = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 20c0-3.6 3.4-6 8-6s8 2.4 8 6" stroke="#241F1A" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="8.5" r="4" stroke="#241F1A" stroke-width="1.8"/></svg>`;
556
+
557
+ const ERROR_ICON = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3.5l8.5 15h-17L12 3.5z" stroke="white" stroke-width="1.8" stroke-linejoin="round"/><path d="M12 10v3.5" stroke="white" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="16" r="0.9" fill="white"/></svg>`;
558
+
559
+ function isErrorText(text) {
560
+ return /^🚨/.test(text.trim());
561
+ }
562
+
563
  function appendMessage(role, text, options = {}) {
564
  const { withCopy = false, isLoading = false } = options;
565
  const msgDiv = document.createElement("div");
566
  msgDiv.classList.add("message", role === "bot" ? "bot-message" : "user-message");
567
+
568
+ const isError = role === "bot" && !isLoading && isErrorText(text);
569
+
570
  const avatar = document.createElement("div");
571
  avatar.classList.add("avatar");
572
+ if (role === "bot") {
573
+ avatar.classList.add(isError ? "error-avatar" : "bot-avatar");
574
+ avatar.innerHTML = isError ? ERROR_ICON : BOT_ICON;
 
 
 
575
  } else {
576
+ avatar.classList.add("user-avatar");
577
+ avatar.innerHTML = USER_ICON;
578
  }
579
+
580
+ if (role === "bot" && withCopy) {
581
+ // Deliverable-style output card
582
+ const card = document.createElement("div");
583
+ card.classList.add("output-card");
584
+
585
+ const tag = document.createElement("div");
586
+ tag.classList.add("sticky-tag");
587
+ tag.textContent = "Refined Prompt ✨";
588
+
589
+ const bodyDiv = document.createElement("div");
590
+ bodyDiv.classList.add("card-body");
591
+ bodyDiv.innerHTML = formatTextWithLineBreaks(text);
592
+
593
  const copyBtn = document.createElement("button");
594
  copyBtn.classList.add("copy-btn");
595
  copyBtn.textContent = "Copy";
 
598
  copyBtn.textContent = "Copied!";
599
  setTimeout(() => (copyBtn.textContent = "Copy"), 1500);
600
  });
601
+
602
+ card.appendChild(tag);
603
+ bodyDiv.appendChild(document.createElement("br"));
604
+ bodyDiv.appendChild(copyBtn);
605
+ card.appendChild(bodyDiv);
606
+
607
+ msgDiv.appendChild(avatar);
608
+ msgDiv.appendChild(card);
609
+ } else {
610
+ const bubble = document.createElement("div");
611
+ bubble.classList.add("bubble");
612
+ if (isLoading) {
613
+ bubble.innerHTML = `<span class="loading-dots">✨ Optimizing your prompt<span>.</span><span>.</span><span>.</span></span>`;
614
+ msgDiv.dataset.loading = "true";
615
+ } else {
616
+ if (isError) bubble.classList.add("error-bubble");
617
+ bubble.innerHTML = formatTextWithLineBreaks(text);
618
+ }
619
+ msgDiv.appendChild(avatar);
620
+ msgDiv.appendChild(bubble);
621
  }
622
+
623
  chatWindow.appendChild(msgDiv);
624
  chatWindow.scrollTop = chatWindow.scrollHeight;
625
  }
626
+
627
  function removeLoadingMessage() {
628
  const loadingMsg = chatWindow.querySelector('[data-loading="true"]');
629
  if (loadingMsg) chatWindow.removeChild(loadingMsg);
630
  }
631
+
632
  async function saveHistory() {
633
  const messages = [];
634
  chatWindow.querySelectorAll(".message").forEach((msg) => {
635
  const role = msg.classList.contains("user-message") ? "user" : "bot";
636
+ const textEl = msg.querySelector(".card-body") || msg.querySelector(".bubble");
637
+ const text = textEl?.innerText || "";
638
+ const withCopy = !!msg.querySelector(".output-card");
639
+ messages.push({ role, text, withCopy });
640
  });
641
  try {
642
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
643
  } catch (err) {
644
  console.warn("⚠️ Failed to save chat history:", err);
645
  }
646
  }
647
+
648
  async function loadHistory() {
649
  try {
650
+ const result = localStorage.getItem(STORAGE_KEY);
651
  const messages = result[STORAGE_KEY] || [];
652
  messages.forEach((msg) =>
653
+ appendMessage(msg.role, msg.text, { withCopy: msg.withCopy ?? (msg.role === "bot") })
654
  );
655
  } catch (err) {
656
  console.warn("⚠️ Failed to load chat history:", err);
657
  }
658
  }
659
+
660
  clearBtn.addEventListener("click", async () => {
661
+ try { localStorage.removeItem(STORAGE_KEY); } catch (e) {}
662
  chatWindow.innerHTML = `
663
+ <div class="invite bot-message">
664
+ <div class="avatar bot-avatar">${BOT_ICON}</div>
665
+ <div class="bubble">Hi! Got a rough prompt? Paste it below let's tighten it up.</div>
666
  </div>`;
667
  });
668
+
669
  async function handleSend() {
670
  const prompt = userInput.value.trim();
671
  if (!prompt) return;
672
  appendMessage("user", prompt);
673
  userInput.value = "";
674
+ userInput.style.height = "auto";
675
  appendMessage("bot", "", { isLoading: true });
676
  await saveHistory();
677
  try {
 
687
  if (optimized) {
688
  appendMessage("bot", optimized, { withCopy: true });
689
  } else {
690
+ appendMessage("bot", "⚠️ The API responded successfully but didn't return optimized text.");
691
  }
692
  await saveHistory();
693
  } catch (error) {
 
700
  await saveHistory();
701
  }
702
  }
703
+
704
  sendButton.addEventListener("click", handleSend);
705
  userInput.addEventListener("input", () => {
706
  userInput.style.height = "auto";
 
712
  handleSend();
713
  }
714
  });
715
+
716
  loadHistory();
717
  });
718
  </script>
 
720
  <body>
721
  <div class="chat-wrapper">
722
  <div class="header">
723
+ <div class="logo">Prompt Optimizer<span class="tag">✎ paper edition</span></div>
724
+ <div class="header-actions">
725
+ <div class="button" id="apiKeyBtn">Get API</div>
726
+ <button id="clearBtn">🧹 Clear</button>
727
  </div>
728
  </div>
729
  <div id="chatWindow" class="chat-window">
730
  <div class="invite bot-message">
731
+ <div class="avatar bot-avatar"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3v2.2" stroke="white" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="6.4" r="1.1" fill="white"/><rect x="5" y="8" width="14" height="10" rx="3" stroke="white" stroke-width="1.8"/><circle cx="9" cy="13" r="1.3" fill="white"/><circle cx="15" cy="13" r="1.3" fill="white"/><path d="M9 16.2h6" stroke="white" stroke-width="1.6" stroke-linecap="round"/></svg></div>
732
  <div class="bubble">
733
+ Hi! Got a rough prompt? Paste it below let's tighten it up.
734
  </div>
735
  </div>
736
  </div>
 
766
  </div>
767
  </div>
768
  </body>
769
+ </html>"""
 
770
  return html_content