AiCoderv2 commited on
Commit
70a8ce7
·
verified ·
1 Parent(s): cc180c9

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +359 -286
index.html CHANGED
@@ -1,5 +1,6 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
@@ -8,49 +9,55 @@
8
  :root {
9
  --bg: #0b1020;
10
  --bg2: #0f1427;
11
- --panel: rgba(255,255,255,0.06);
12
- --panel-strong: rgba(255,255,255,0.12);
13
  --text: #e6e8f0;
14
  --muted: #a9b1c6;
15
  --accent: #7c5cff;
16
  --accent-2: #00d4ff;
17
  --danger: #ff5c7c;
18
  --success: #2bd67b;
19
- --shadow: 0 10px 30px rgba(0,0,0,0.35);
20
  --radius: 16px;
21
  --radius-sm: 10px;
22
  --radius-lg: 20px;
23
  --blur: 12px;
24
- --glass: rgba(255,255,255,0.06);
25
- --glass-strong: rgba(255,255,255,0.12);
26
  }
 
27
  .theme-light {
28
  --bg: #f7f8fc;
29
  --bg2: #eef1fb;
30
- --panel: rgba(255,255,255,0.7);
31
- --panel-strong: rgba(255,255,255,0.9);
32
  --text: #14151a;
33
  --muted: #4b4f5c;
34
  --accent: #6b5cff;
35
  --accent-2: #00a6ff;
36
  --danger: #e11d48;
37
  --success: #16a34a;
38
- --glass: rgba(255,255,255,0.7);
39
- --glass-strong: rgba(255,255,255,0.9);
40
  --shadow: 0 10px 30px rgba(10, 20, 50, 0.15);
41
  }
42
 
43
- * { box-sizing: border-box; }
44
- html, body {
 
 
 
 
45
  height: 100%;
46
  }
 
47
  body {
48
  margin: 0;
49
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
50
  color: var(--text);
51
- background: radial-gradient(1200px 700px at 10% 10%, rgba(124,92,255,0.15), transparent 60%),
52
- radial-gradient(900px 600px at 90% 20%, rgba(0,212,255,0.12), transparent 60%),
53
- linear-gradient(180deg, var(--bg), var(--bg2));
54
  background-attachment: fixed;
55
  overflow: hidden;
56
  }
@@ -71,9 +78,10 @@
71
  position: sticky;
72
  top: 0;
73
  z-index: 5;
74
- background: linear-gradient(180deg, rgba(0,0,0,0.25), rgba(0,0,0,0)) ;
75
  backdrop-filter: blur(8px);
76
  }
 
77
  .brand {
78
  display: flex;
79
  align-items: center;
@@ -85,6 +93,7 @@
85
  box-shadow: var(--shadow);
86
  backdrop-filter: blur(var(--blur));
87
  }
 
88
  .logo {
89
  width: 36px;
90
  height: 36px;
@@ -95,13 +104,15 @@
95
  color: white;
96
  font-weight: 900;
97
  letter-spacing: 0.5px;
98
- box-shadow: 0 8px 20px rgba(124,92,255,0.35), inset 0 0 20px rgba(255,255,255,0.2);
99
  }
 
100
  .brand h1 {
101
  margin: 0;
102
  font-size: 18px;
103
  letter-spacing: 0.3px;
104
  }
 
105
  .brand small {
106
  display: block;
107
  color: var(--muted);
@@ -131,23 +142,45 @@
131
  box-shadow: var(--shadow);
132
  user-select: none;
133
  }
134
- .btn:hover { transform: translateY(-1px); background: var(--glass-strong); }
135
- .btn:active { transform: translateY(0); }
 
 
 
 
 
 
 
 
136
  .btn.primary {
137
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
138
  border-color: transparent;
139
  color: white;
140
- box-shadow: 0 10px 25px rgba(124,92,255,0.35);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
- .btn.ghost { background: transparent; border-color: var(--panel-strong); }
143
- .btn.small { padding: 8px 10px; border-radius: 10px; font-size: 13px; }
144
- .btn .icon { width: 18px; height: 18px; }
145
 
146
  /* Chat area */
147
  .chat {
148
  position: relative;
149
  overflow: hidden;
150
  }
 
151
  .messages {
152
  position: absolute;
153
  inset: 0;
@@ -155,11 +188,16 @@
155
  padding: 16px clamp(10px, 2vw, 24px) 24px;
156
  scroll-behavior: smooth;
157
  }
158
- .messages::-webkit-scrollbar { width: 10px; }
 
 
 
 
159
  .messages::-webkit-scrollbar-thumb {
160
  background: linear-gradient(180deg, var(--panel-strong), transparent);
161
  border-radius: 8px;
162
  }
 
163
  .empty {
164
  height: 100%;
165
  display: grid;
@@ -167,6 +205,7 @@
167
  pointer-events: none;
168
  color: var(--muted);
169
  }
 
170
  .empty-inner {
171
  text-align: center;
172
  max-width: 680px;
@@ -177,6 +216,7 @@
177
  backdrop-filter: blur(var(--blur));
178
  box-shadow: var(--shadow);
179
  }
 
180
  .chips {
181
  display: flex;
182
  flex-wrap: wrap;
@@ -184,6 +224,7 @@
184
  margin-top: 14px;
185
  justify-content: center;
186
  }
 
187
  .chip {
188
  padding: 8px 12px;
189
  border-radius: 999px;
@@ -193,7 +234,11 @@
193
  transition: all 0.2s ease;
194
  font-size: 13px;
195
  }
196
- .chip:hover { transform: translateY(-1px); background: var(--glass-strong); }
 
 
 
 
197
 
198
  .msg {
199
  display: grid;
@@ -203,7 +248,11 @@
203
  max-width: min(900px, 92vw);
204
  align-items: flex-start;
205
  }
206
- .msg.user { grid-template-columns: 1fr 36px; }
 
 
 
 
207
  .avatar {
208
  width: 36px;
209
  height: 36px;
@@ -214,10 +263,12 @@
214
  place-items: center;
215
  backdrop-filter: blur(var(--blur));
216
  }
 
217
  .avatar.ai {
218
- background: linear-gradient(135deg, rgba(124,92,255,0.3), rgba(0,212,255,0.3));
219
  border-color: transparent;
220
  }
 
221
  .bubble {
222
  padding: 12px 14px;
223
  border-radius: 14px;
@@ -229,30 +280,36 @@
229
  white-space: pre-wrap;
230
  word-wrap: break-word;
231
  }
 
232
  .msg.user .bubble {
233
- background: linear-gradient(135deg, rgba(124,92,255,0.15), rgba(0,212,255,0.15));
234
  border-color: transparent;
235
  }
 
236
  .bubble .meta {
237
  font-size: 12px;
238
  color: var(--muted);
239
  margin-bottom: 6px;
240
  }
241
- .bubble .content { min-height: 20px; }
 
 
 
242
 
243
  .bubble pre {
244
- background: rgba(0,0,0,0.35);
245
  padding: 10px 12px;
246
  border-radius: 10px;
247
  overflow-x: auto;
248
- border: 1px solid rgba(255,255,255,0.12);
249
  }
 
250
  .bubble code {
251
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
252
- background: rgba(255,255,255,0.08);
253
  padding: 2px 6px;
254
  border-radius: 6px;
255
- border: 1px solid rgba(255,255,255,0.1);
256
  }
257
 
258
  .typing {
@@ -260,6 +317,7 @@
260
  gap: 4px;
261
  align-items: center;
262
  }
 
263
  .dot {
264
  width: 6px;
265
  height: 6px;
@@ -268,11 +326,28 @@
268
  opacity: 0.7;
269
  animation: blink 1.2s infinite ease-in-out;
270
  }
271
- .dot:nth-child(2) { animation-delay: 0.15s; }
272
- .dot:nth-child(3) { animation-delay: 0.3s; }
 
 
 
 
 
 
 
273
  @keyframes blink {
274
- 0%, 80%, 100% { transform: translateY(0); opacity: 0.7; }
275
- 40% { transform: translateY(-3px); opacity: 1; }
 
 
 
 
 
 
 
 
 
 
276
  }
277
 
278
  /* Composer */
@@ -280,9 +355,10 @@
280
  position: sticky;
281
  bottom: 0;
282
  padding: 12px clamp(10px, 2vw, 24px) 16px;
283
- background: linear-gradient(0deg, rgba(0,0,0,0.25), rgba(0,0,0,0));
284
  backdrop-filter: blur(8px);
285
  }
 
286
  .composer-inner {
287
  margin: 0 auto;
288
  max-width: min(980px, 95vw);
@@ -296,6 +372,7 @@
296
  gap: 8px;
297
  backdrop-filter: blur(var(--blur));
298
  }
 
299
  .input-wrap {
300
  display: flex;
301
  align-items: flex-end;
@@ -305,6 +382,7 @@
305
  border: 1px solid var(--panel-strong);
306
  border-radius: 12px;
307
  }
 
308
  textarea {
309
  width: 100%;
310
  resize: none;
@@ -317,11 +395,13 @@
317
  max-height: 180px;
318
  padding: 8px;
319
  }
 
320
  .send {
321
  display: flex;
322
  align-items: center;
323
  gap: 8px;
324
  }
 
325
  .hint {
326
  margin-top: 6px;
327
  color: var(--muted);
@@ -343,20 +423,54 @@
343
  color: var(--muted);
344
  }
345
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  /* Utility */
347
- .row { display: flex; align-items: center; gap: 10px; }
348
- .spacer { flex: 1; }
 
 
 
 
 
 
 
349
 
350
  /* Responsive tweaks */
351
  @media (max-width: 700px) {
352
- .brand h1 { font-size: 16px; }
353
- .brand small { display: none; }
354
- .msg { max-width: 96vw; }
355
- .composer-inner { grid-template-columns: 1fr; }
356
- .send { justify-content: flex-end; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  }
358
  </style>
359
  </head>
 
360
  <body>
361
  <div class="app" id="app">
362
  <header class="header">
@@ -364,7 +478,7 @@
364
  <div class="logo">AI</div>
365
  <div>
366
  <h1>AnyCoder Chat</h1>
367
- <small>Your friendly AI assistant</small>
368
  </div>
369
  </div>
370
  <div class="header-actions">
@@ -380,8 +494,12 @@
380
  <svg class="icon" id="themeIcon" viewBox="0 0 24 24" fill="none"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
381
  Theme
382
  </button>
383
- <a class="btn small" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener noreferrer" title="Visit AnyCoder on Hugging Face">
384
- <svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M14 3h7v7M10 14L21 3M21 14v7H3V3h7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
 
 
 
 
385
  Built with anycoder
386
  </a>
387
  </div>
@@ -391,8 +509,9 @@
391
  <div class="messages" id="messages"></div>
392
  <div class="empty" id="empty">
393
  <div class="empty-inner">
394
- <h2 style="margin:0 0 8px 0">Hi! I’m AnyCoder Chat 👋</h2>
395
- <p style="margin:0;color:var(--muted)">Ask me to explain code, brainstorm ideas, draft emails, or just chat. I can format responses with Markdown, code blocks, and links.</p>
 
396
  <div class="chips" id="chips">
397
  <div class="chip">Explain closures in JavaScript</div>
398
  <div class="chip">Help me write a Python script</div>
@@ -411,10 +530,6 @@
411
  </div>
412
  <div class="send">
413
  <div class="row">
414
- <label for="speed" style="font-size:12px;color:var(--muted);display:flex;align-items:center;gap:6px">
415
- Speed
416
- <input id="speed" type="range" min="0" max="50" value="10" />
417
- </label>
418
  <button class="btn ghost small" id="stopBtn" title="Stop generating" style="display:none">
419
  <svg class="icon" viewBox="0 0 24 24" fill="none"><rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/></svg>
420
  Stop
@@ -428,15 +543,20 @@
428
  </div>
429
  <div class="hint">
430
  <div class="row" style="gap:6px">
431
- <span class="kbd">Enter</span> to send • <span class="kbd">Shift + Enter</span> for newline • Ask for code, summaries, or explanations
432
  </div>
433
  <div class="spacer"></div>
434
- <div>Local demo chatbot no external API required</div>
435
  </div>
436
  </footer>
437
  </div>
438
 
439
  <script>
 
 
 
 
 
440
  // Utility: local storage wrapper
441
  const store = {
442
  get(key, fallback) { try { return JSON.parse(localStorage.getItem(key)) ?? fallback; } catch { return fallback; } },
@@ -504,7 +624,6 @@
504
  exportBtn: document.getElementById('exportBtn'),
505
  themeBtn: document.getElementById('themeBtn'),
506
  themeIcon: document.getElementById('themeIcon'),
507
- speed: document.getElementById('speed'),
508
  chat: document.getElementById('chat')
509
  };
510
 
@@ -549,7 +668,7 @@
549
 
550
  const meta = document.createElement('div');
551
  meta.className = 'meta';
552
- meta.textContent = isUser ? 'You' : 'AnyCoder';
553
 
554
  const content = document.createElement('div');
555
  content.className = 'content';
@@ -600,224 +719,108 @@
600
  scrollToBottom();
601
  }
602
 
603
- // AI Engine (local, no external APIs)
604
- const AI = {
605
- abort: null,
606
- async respond(prompt, opts = {}) {
607
- // Streaming: return a controller with write/close
608
- const controller = {
609
- write: (chunk) => {},
610
- close: () => {}
611
- };
612
- const text = buildResponse(prompt);
613
- const speed = Number(els.speed.value) || 10; // 0..50 (higher is faster)
614
- const minDelay = 5; // min ms per chunk
615
- const maxDelay = 60; // max ms per chunk
616
- // Map speed: 0 slow -> maxDelay, 50 fast -> minDelay
617
- const delay = speed === 0 ? 30 : Math.max(minDelay, maxDelay - speed);
618
-
619
- const chunkSize = Math.max(2, Math.floor(60 - speed)); // chars per chunk
620
- let i = 0;
621
-
622
- const tempMsgId = uid();
623
- const tempMsg = { id: tempMsgId, role: 'assistant', content: '' };
624
- ChatState.data.push(tempMsg);
625
- ChatState.save();
626
- const el = messageElement(tempMsg, { streaming: true });
627
- els.messages.appendChild(el);
628
- refreshEmpty();
629
- scrollToBottom();
630
-
631
- const contentEl = el.querySelector('.content');
632
-
633
- function writeNext() {
634
- const end = Math.min(text.length, i + chunkSize);
635
- const chunk = text.slice(i, end);
636
- i = end;
637
- // append chunk as plain text, then re-render markdown for preview? To keep it simple, we'll append plain safe HTML.
638
- // But we want markdown; accumulate into tempMsg.content and re-render.
639
- tempMsg.content = text.slice(0, i);
640
- contentEl.innerHTML = renderMarkdown(tempMsg.content);
641
- scrollToBottom();
642
 
643
- if (i < text.length) {
644
- AI.abort = setTimeout(writeNext, delay);
645
- } else {
646
- controller.close?.();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  }
 
648
  }
649
-
650
- AI.abort = setTimeout(writeNext, delay);
651
-
652
- controller.close = () => {
653
- if (AI.abort) clearTimeout(AI.abort);
654
- // Ensure final render
655
- tempMsg.content = text;
656
- contentEl.innerHTML = renderMarkdown(tempMsg.content);
657
- // Remove typing indicator
658
- const t = el.querySelector('.meta:last-child');
659
- if (t && t.querySelector('.typing')) t.remove();
660
- ChatState.save();
661
- AI.abort = null;
662
- };
663
-
664
- return controller;
665
  },
666
 
667
  stop() {
668
- if (AI.abort) clearTimeout(AI.abort);
669
- AI.abort = null;
670
- }
671
- };
672
-
673
- function buildResponse(prompt) {
674
- const p = prompt.trim();
675
- const lower = p.toLowerCase();
676
-
677
- // Commands
678
- if (/^(\/help|\/h)$/.test(lower)) {
679
- return [
680
- "Here are some things you can try:",
681
- "",
682
- "- Ask for explanations: “Explain closures in JavaScript”",
683
- "- Get code help: “Write a Python script to download files”",
684
- "- Brainstorm: “Ideas for a habit tracker app”",
685
- "- Draft text: “Write a polite email to reschedule a meeting”",
686
- "- Summaries: “Summarize the key points of [topic]”",
687
- "",
688
- "Tips:",
689
- "- Shift+Enter for a newline",
690
- "- Use backticks for inline code and triple backticks for code blocks",
691
- "- Use **bold** and *italic*",
692
- ""
693
- ].join("\n");
694
- }
695
- if (/^(\/clear|\/c)$/.test(lower)) {
696
- ChatState.data = [];
697
- ChatState.save();
698
- renderAll();
699
- return "Cleared the chat.";
700
- }
701
- if (/^(\/time|\/date)$/.test(lower)) {
702
- const now = new Date();
703
- return `Current date and time: ${now.toLocaleString()}`;
704
- }
705
- if (/^(\/joke)$/.test(lower)) {
706
- return joke();
707
- }
708
-
709
- // Heuristic responses
710
- if (lower.includes('weather')) {
711
- return "I don't have live weather data in this demo, but here's what I'd do: " +
712
- "I'd call a weather API (like OpenWeatherMap) with your location, parse the JSON, and display conditions. " +
713
- "You can also show hourly and daily forecasts with charts.";
714
- }
715
- if (lower.includes('remind')) {
716
- return "Reminders aren't persisted in this offline demo, but you could store them in localStorage or IndexedDB. " +
717
- "For scheduling notifications, use the Notification API and setTimeout or a service worker.";
718
- }
719
- if (lower.includes('password') || lower.includes('random')) {
720
- return generatePassword();
721
- }
722
- if (lower.includes('explain') && lower.includes('closure')) {
723
- return "A closure is a function paired with the lexical environment in which it was declared. " +
724
- "It lets the inner function remember variables from the outer scope even after the outer function has finished executing.\n\n" +
725
- "Example:\n```js\nfunction counter() {\n let n = 0;\n return () => ++n;\n}\nconst inc = counter();\ninc(); // 1\ninc(); // 2\n```\nHere, the returned function closes over `n`, keeping it alive between calls.";
726
- }
727
- if (lower.includes('write') && (lower.includes('python') || lower.includes('script'))) {
728
- return `Here's a tiny Python script to download a file:\n\`\`\`python\nimport requests\n\nurl = "https://example.com/file.pdf"\nresp = requests.get(url)\nwith open("file.pdf", "wb") as f:\n f.write(resp.content)\nprint("Saved to file.pdf")\n\`\`\`\nTip: Use tqdm for progress bars and pathlib for paths.`;
729
- }
730
- if (lower.includes('brainstorm') || lower.includes('ideas')) {
731
- return "Let’s brainstorm features for a habit tracker app:\n" +
732
- "- Daily streaks with visual badges\n" +
733
- "- Habit templates and categories\n" +
734
- "- Reminders with the Notification API\n" +
735
- "- Calendar heatmap view\n" +
736
- "- Sync via a backend (or locally with IndexedDB)\n" +
737
- "- Smart suggestions based on your schedule\n" +
738
- "- Gamification: points, levels, challenges";
739
- }
740
- if (lower.includes('email') || lower.includes('draft')) {
741
- return "Draft email example:\n\n" +
742
- "Subject: Rescheduling our meeting\n\n" +
743
- "Hi [Name],\n\n" +
744
- "I hope you’re doing well. Due to an unexpected conflict, I need to reschedule our meeting originally planned for [Day, Time]. " +
745
- "Would [Alternative Day, Time] work for you? I’m happy to adjust.\n\n" +
746
- "Thanks for your understanding!\n\n" +
747
- "Best,\n[Your Name]";
748
- }
749
- if (lower.includes('summarize') || lower.includes('summary')) {
750
- return "To summarize effectively:\n" +
751
- "1) Identify the thesis or main claim\n" +
752
- "2) Extract key arguments and evidence\n" +
753
- "3) Note counterpoints if relevant\n" +
754
- "4) Conclude with implications or takeaways\n\n" +
755
- "For long texts, chunk by sections and summarize each, then combine.";
756
- }
757
-
758
- // Fallback helpful response
759
- const tips = [
760
- "Here are some tips to get better results:",
761
- "- Be specific about what you want (goal, constraints, examples)",
762
- "- Mention your audience or tone (e.g., beginner-friendly)",
763
- "- Provide sample input/output formats",
764
- "- For code, specify language and error messages (if any)"
765
- ];
766
- const examples = [
767
- "",
768
- "Examples you can try:",
769
- "- Explain closures in JavaScript with a small example",
770
- "- Write a Python script to download files with a progress bar",
771
- "- Brainstorm app features for a habit tracker",
772
- "- Draft a polite email to reschedule a meeting",
773
- ""
774
- ].join("\n");
775
-
776
- const fallback = [
777
- `You said: “${prompt}”`,
778
- "",
779
- "I can help with coding, explanations, brainstorming, drafting, and more. I support Markdown, code blocks, and links.",
780
- tips.join("\n"),
781
- examples
782
- ].join("\n");
783
-
784
- return fallback;
785
- }
786
-
787
- function joke() {
788
- const jokes = [
789
- "Why do programmers prefer dark mode? Because light attracts bugs.",
790
- "There are 10 kinds of people: those who understand binary and those who don’t.",
791
- "A SQL query walks into a bar, walks up to two tables and asks: 'Can I join you?'",
792
- "To understand recursion, you must first understand recursion."
793
- ];
794
- return jokes[Math.floor(Math.random() * jokes.length)];
795
- }
796
-
797
- function generatePassword() {
798
- const len = 16;
799
- const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*";
800
- let out = "";
801
- const cryptoObj = window.crypto || window.msCrypto;
802
- if (cryptoObj?.getRandomValues) {
803
- const arr = new Uint32Array(len);
804
- cryptoObj.getRandomValues(arr);
805
- for (let i = 0; i < len; i++) {
806
- out += chars[arr[i] % chars.length];
807
- }
808
- } else {
809
- for (let i = 0; i < len; i++) {
810
- out += chars[Math.floor(Math.random() * chars.length)];
811
  }
812
  }
813
- return `Generated password (length ${len}):\n\`${out}\`\nTip: Use a password manager and enable 2FA.`;
814
- }
815
 
816
  // Actions
817
  async function sendMessage() {
818
  const text = els.input.value.trim();
819
  if (!text) return;
820
 
 
 
 
 
 
821
  // Add user message
822
  const userMsg = { id: uid(), role: 'user', content: text, ts: Date.now() };
823
  ChatState.data.push(userMsg);
@@ -834,26 +837,74 @@
834
  // Generate AI response
835
  els.sendBtn.disabled = true;
836
  els.stopBtn.style.display = 'inline-flex';
837
- const controller = await AI.respond(text);
838
- els.stopBtn.onclick = () => {
839
- AI.stop();
840
- // finalize message
841
- controller.close?.();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
842
  els.stopBtn.style.display = 'none';
843
  els.sendBtn.disabled = false;
844
- };
845
-
846
- // When streaming ends
847
- const waitEnd = () => new Promise(resolve => {
848
- const check = () => {
849
- if (!AI.abort) resolve();
850
- else setTimeout(check, 80);
851
- };
852
- check();
853
- });
854
- await waitEnd();
855
- els.stopBtn.style.display = 'none';
856
- els.sendBtn.disabled = false;
857
  }
858
 
859
  function newChat() {
@@ -891,6 +942,13 @@
891
  els.newChatBtn.addEventListener('click', newChat);
892
  els.exportBtn.addEventListener('click', exportChat);
893
 
 
 
 
 
 
 
 
894
  // Chips
895
  els.chips.addEventListener('click', (e) => {
896
  const chip = e.target.closest('.chip');
@@ -900,9 +958,13 @@
900
  els.input.focus();
901
  });
902
 
903
- // Stop generation when switching tabs (saves CPU)
904
  document.addEventListener('visibilitychange', () => {
905
- if (document.hidden) AI.stop();
 
 
 
 
906
  });
907
 
908
  // Initialize
@@ -911,15 +973,26 @@
911
 
912
  // Preload a welcome message if empty
913
  if (ChatState.isEmpty) {
914
- const welcome = { id: uid(), role: 'assistant', content:
915
- `Welcome! I’m your built‑in demo assistant.
916
- - Ask me to explain code, brainstorm ideas, draft text, or summarize topics.
917
- - Use Markdown: **bold**, *italic*, \`inline code\`, and triple backticks for code blocks.
918
- - Try: /help, /time, /joke, /clear`};
 
 
 
 
 
 
 
 
 
 
919
  ChatState.data.push(welcome);
920
  ChatState.save();
921
  renderAll();
922
  }
923
  </script>
924
  </body>
 
925
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width,initial-scale=1" />
 
9
  :root {
10
  --bg: #0b1020;
11
  --bg2: #0f1427;
12
+ --panel: rgba(255, 255, 255, 0.06);
13
+ --panel-strong: rgba(255, 255, 255, 0.12);
14
  --text: #e6e8f0;
15
  --muted: #a9b1c6;
16
  --accent: #7c5cff;
17
  --accent-2: #00d4ff;
18
  --danger: #ff5c7c;
19
  --success: #2bd67b;
20
+ --shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
21
  --radius: 16px;
22
  --radius-sm: 10px;
23
  --radius-lg: 20px;
24
  --blur: 12px;
25
+ --glass: rgba(255, 255, 255, 0.06);
26
+ --glass-strong: rgba(255, 255, 255, 0.12);
27
  }
28
+
29
  .theme-light {
30
  --bg: #f7f8fc;
31
  --bg2: #eef1fb;
32
+ --panel: rgba(255, 255, 255, 0.7);
33
+ --panel-strong: rgba(255, 255, 255, 0.9);
34
  --text: #14151a;
35
  --muted: #4b4f5c;
36
  --accent: #6b5cff;
37
  --accent-2: #00a6ff;
38
  --danger: #e11d48;
39
  --success: #16a34a;
40
+ --glass: rgba(255, 255, 255, 0.7);
41
+ --glass-strong: rgba(255, 255, 255, 0.9);
42
  --shadow: 0 10px 30px rgba(10, 20, 50, 0.15);
43
  }
44
 
45
+ * {
46
+ box-sizing: border-box;
47
+ }
48
+
49
+ html,
50
+ body {
51
  height: 100%;
52
  }
53
+
54
  body {
55
  margin: 0;
56
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
57
  color: var(--text);
58
+ background: radial-gradient(1200px 700px at 10% 10%, rgba(124, 92, 255, 0.15), transparent 60%),
59
+ radial-gradient(900px 600px at 90% 20%, rgba(0, 212, 255, 0.12), transparent 60%),
60
+ linear-gradient(180deg, var(--bg), var(--bg2));
61
  background-attachment: fixed;
62
  overflow: hidden;
63
  }
 
78
  position: sticky;
79
  top: 0;
80
  z-index: 5;
81
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0));
82
  backdrop-filter: blur(8px);
83
  }
84
+
85
  .brand {
86
  display: flex;
87
  align-items: center;
 
93
  box-shadow: var(--shadow);
94
  backdrop-filter: blur(var(--blur));
95
  }
96
+
97
  .logo {
98
  width: 36px;
99
  height: 36px;
 
104
  color: white;
105
  font-weight: 900;
106
  letter-spacing: 0.5px;
107
+ box-shadow: 0 8px 20px rgba(124, 92, 255, 0.35), inset 0 0 20px rgba(255, 255, 255, 0.2);
108
  }
109
+
110
  .brand h1 {
111
  margin: 0;
112
  font-size: 18px;
113
  letter-spacing: 0.3px;
114
  }
115
+
116
  .brand small {
117
  display: block;
118
  color: var(--muted);
 
142
  box-shadow: var(--shadow);
143
  user-select: none;
144
  }
145
+
146
+ .btn:hover {
147
+ transform: translateY(-1px);
148
+ background: var(--glass-strong);
149
+ }
150
+
151
+ .btn:active {
152
+ transform: translateY(0);
153
+ }
154
+
155
  .btn.primary {
156
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
157
  border-color: transparent;
158
  color: white;
159
+ box-shadow: 0 10px 25px rgba(124, 92, 255, 0.35);
160
+ }
161
+
162
+ .btn.ghost {
163
+ background: transparent;
164
+ border-color: var(--panel-strong);
165
+ }
166
+
167
+ .btn.small {
168
+ padding: 8px 10px;
169
+ border-radius: 10px;
170
+ font-size: 13px;
171
+ }
172
+
173
+ .btn .icon {
174
+ width: 18px;
175
+ height: 18px;
176
  }
 
 
 
177
 
178
  /* Chat area */
179
  .chat {
180
  position: relative;
181
  overflow: hidden;
182
  }
183
+
184
  .messages {
185
  position: absolute;
186
  inset: 0;
 
188
  padding: 16px clamp(10px, 2vw, 24px) 24px;
189
  scroll-behavior: smooth;
190
  }
191
+
192
+ .messages::-webkit-scrollbar {
193
+ width: 10px;
194
+ }
195
+
196
  .messages::-webkit-scrollbar-thumb {
197
  background: linear-gradient(180deg, var(--panel-strong), transparent);
198
  border-radius: 8px;
199
  }
200
+
201
  .empty {
202
  height: 100%;
203
  display: grid;
 
205
  pointer-events: none;
206
  color: var(--muted);
207
  }
208
+
209
  .empty-inner {
210
  text-align: center;
211
  max-width: 680px;
 
216
  backdrop-filter: blur(var(--blur));
217
  box-shadow: var(--shadow);
218
  }
219
+
220
  .chips {
221
  display: flex;
222
  flex-wrap: wrap;
 
224
  margin-top: 14px;
225
  justify-content: center;
226
  }
227
+
228
  .chip {
229
  padding: 8px 12px;
230
  border-radius: 999px;
 
234
  transition: all 0.2s ease;
235
  font-size: 13px;
236
  }
237
+
238
+ .chip:hover {
239
+ transform: translateY(-1px);
240
+ background: var(--glass-strong);
241
+ }
242
 
243
  .msg {
244
  display: grid;
 
248
  max-width: min(900px, 92vw);
249
  align-items: flex-start;
250
  }
251
+
252
+ .msg.user {
253
+ grid-template-columns: 1fr 36px;
254
+ }
255
+
256
  .avatar {
257
  width: 36px;
258
  height: 36px;
 
263
  place-items: center;
264
  backdrop-filter: blur(var(--blur));
265
  }
266
+
267
  .avatar.ai {
268
+ background: linear-gradient(135deg, rgba(124, 92, 255, 0.3), rgba(0, 212, 255, 0.3));
269
  border-color: transparent;
270
  }
271
+
272
  .bubble {
273
  padding: 12px 14px;
274
  border-radius: 14px;
 
280
  white-space: pre-wrap;
281
  word-wrap: break-word;
282
  }
283
+
284
  .msg.user .bubble {
285
+ background: linear-gradient(135deg, rgba(124, 92, 255, 0.15), rgba(0, 212, 255, 0.15));
286
  border-color: transparent;
287
  }
288
+
289
  .bubble .meta {
290
  font-size: 12px;
291
  color: var(--muted);
292
  margin-bottom: 6px;
293
  }
294
+
295
+ .bubble .content {
296
+ min-height: 20px;
297
+ }
298
 
299
  .bubble pre {
300
+ background: rgba(0, 0, 0, 0.35);
301
  padding: 10px 12px;
302
  border-radius: 10px;
303
  overflow-x: auto;
304
+ border: 1px solid rgba(255, 255, 255, 0.12);
305
  }
306
+
307
  .bubble code {
308
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
309
+ background: rgba(255, 255, 255, 0.08);
310
  padding: 2px 6px;
311
  border-radius: 6px;
312
+ border: 1px solid rgba(255, 255, 255, 0.1);
313
  }
314
 
315
  .typing {
 
317
  gap: 4px;
318
  align-items: center;
319
  }
320
+
321
  .dot {
322
  width: 6px;
323
  height: 6px;
 
326
  opacity: 0.7;
327
  animation: blink 1.2s infinite ease-in-out;
328
  }
329
+
330
+ .dot:nth-child(2) {
331
+ animation-delay: 0.15s;
332
+ }
333
+
334
+ .dot:nth-child(3) {
335
+ animation-delay: 0.3s;
336
+ }
337
+
338
  @keyframes blink {
339
+
340
+ 0%,
341
+ 80%,
342
+ 100% {
343
+ transform: translateY(0);
344
+ opacity: 0.7;
345
+ }
346
+
347
+ 40% {
348
+ transform: translateY(-3px);
349
+ opacity: 1;
350
+ }
351
  }
352
 
353
  /* Composer */
 
355
  position: sticky;
356
  bottom: 0;
357
  padding: 12px clamp(10px, 2vw, 24px) 16px;
358
+ background: linear-gradient(0deg, rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0));
359
  backdrop-filter: blur(8px);
360
  }
361
+
362
  .composer-inner {
363
  margin: 0 auto;
364
  max-width: min(980px, 95vw);
 
372
  gap: 8px;
373
  backdrop-filter: blur(var(--blur));
374
  }
375
+
376
  .input-wrap {
377
  display: flex;
378
  align-items: flex-end;
 
382
  border: 1px solid var(--panel-strong);
383
  border-radius: 12px;
384
  }
385
+
386
  textarea {
387
  width: 100%;
388
  resize: none;
 
395
  max-height: 180px;
396
  padding: 8px;
397
  }
398
+
399
  .send {
400
  display: flex;
401
  align-items: center;
402
  gap: 8px;
403
  }
404
+
405
  .hint {
406
  margin-top: 6px;
407
  color: var(--muted);
 
423
  color: var(--muted);
424
  }
425
 
426
+ /* Error styling */
427
+ .error {
428
+ border: 1px solid var(--danger) !important;
429
+ background: rgba(255, 92, 124, 0.1) !important;
430
+ }
431
+
432
+ .error-message {
433
+ color: var(--danger);
434
+ font-size: 12px;
435
+ margin-top: 4px;
436
+ }
437
+
438
  /* Utility */
439
+ .row {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 10px;
443
+ }
444
+
445
+ .spacer {
446
+ flex: 1;
447
+ }
448
 
449
  /* Responsive tweaks */
450
  @media (max-width: 700px) {
451
+ .brand h1 {
452
+ font-size: 16px;
453
+ }
454
+
455
+ .brand small {
456
+ display: none;
457
+ }
458
+
459
+ .msg {
460
+ max-width: 96vw;
461
+ }
462
+
463
+ .composer-inner {
464
+ grid-template-columns: 1fr;
465
+ }
466
+
467
+ .send {
468
+ justify-content: flex-end;
469
+ }
470
  }
471
  </style>
472
  </head>
473
+
474
  <body>
475
  <div class="app" id="app">
476
  <header class="header">
 
478
  <div class="logo">AI</div>
479
  <div>
480
  <h1>AnyCoder Chat</h1>
481
+ <small>Powered by Poe.com API</small>
482
  </div>
483
  </div>
484
  <div class="header-actions">
 
494
  <svg class="icon" id="themeIcon" viewBox="0 0 24 24" fill="none"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
495
  Theme
496
  </button>
497
+ <a class="btn small" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"
498
+ rel="noopener noreferrer" title="Visit AnyCoder on Hugging Face">
499
+ <svg class="icon" viewBox="0 0 24 24" fill="none">
500
+ <path d="M14 3h7v7M10 14L21 3M21 14v7H3V3h7" stroke="currentColor" stroke-width="2" stroke-linecap="round"
501
+ stroke-linejoin="round" />
502
+ </svg>
503
  Built with anycoder
504
  </a>
505
  </div>
 
509
  <div class="messages" id="messages"></div>
510
  <div class="empty" id="empty">
511
  <div class="empty-inner">
512
+ <h2 style="margin:0 0 8px 0">Hi! I’m AnyCoder Chat powered by Claude on Poe.com 👋</h2>
513
+ <p style="margin:0;color:var(--muted)">Ask me to explain code, brainstorm ideas, draft emails, or just chat. I
514
+ can format responses with Markdown, code blocks, and links. Connected to the claude-haiku-cheap model.</p>
515
  <div class="chips" id="chips">
516
  <div class="chip">Explain closures in JavaScript</div>
517
  <div class="chip">Help me write a Python script</div>
 
530
  </div>
531
  <div class="send">
532
  <div class="row">
 
 
 
 
533
  <button class="btn ghost small" id="stopBtn" title="Stop generating" style="display:none">
534
  <svg class="icon" viewBox="0 0 24 24" fill="none"><rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/></svg>
535
  Stop
 
543
  </div>
544
  <div class="hint">
545
  <div class="row" style="gap:6px">
546
+ <span class="kbd">Enter</span> to send • <span class="kbd">Shift + Enter</span> for newline • Connected to Poe.com API
547
  </div>
548
  <div class="spacer"></div>
549
+ <div>Using claude-haiku-cheap model via Poe.com</div>
550
  </div>
551
  </footer>
552
  </div>
553
 
554
  <script>
555
+ // Configuration
556
+ const POE_API_KEY = '5AvcWZHjdjrxOuRodUjyTZ2TE_D0tdrN-XantWcBY4E';
557
+ const POE_BASE_URL = 'https://api.poe.com/v1';
558
+ const MODEL_NAME = 'claude-haiku-cheap';
559
+
560
  // Utility: local storage wrapper
561
  const store = {
562
  get(key, fallback) { try { return JSON.parse(localStorage.getItem(key)) ?? fallback; } catch { return fallback; } },
 
624
  exportBtn: document.getElementById('exportBtn'),
625
  themeBtn: document.getElementById('themeBtn'),
626
  themeIcon: document.getElementById('themeIcon'),
 
627
  chat: document.getElementById('chat')
628
  };
629
 
 
668
 
669
  const meta = document.createElement('div');
670
  meta.className = 'meta';
671
+ meta.textContent = isUser ? 'You' : 'Claude (via Poe.com)';
672
 
673
  const content = document.createElement('div');
674
  content.className = 'content';
 
719
  scrollToBottom();
720
  }
721
 
722
+ // Poe.com API integration
723
+ const PoeAPI = {
724
+ abortController: null,
725
+
726
+ async chatCompletion(messages) {
727
+ this.abortController = new AbortController();
728
+
729
+ try {
730
+ const response = await fetch(`${POE_BASE_URL}/chat/completions`, {
731
+ method: 'POST',
732
+ headers: {
733
+ 'Content-Type': 'application/json',
734
+ 'Authorization': `Bearer ${POE_API_KEY}`
735
+ },
736
+ body: JSON.stringify({
737
+ model: MODEL_NAME,
738
+ messages: messages,
739
+ stream: true,
740
+ temperature: 0.7,
741
+ max_tokens: 2000
742
+ }),
743
+ signal: this.abortController.signal
744
+ });
745
+
746
+ if (!response.ok) {
747
+ const errorData = await response.json().catch(() => ({}));
748
+ throw new Error(errorData.error?.message || `API request failed with status ${response.status}`);
749
+ }
 
 
 
 
 
 
 
 
 
 
 
750
 
751
+ const reader = response.body.getReader();
752
+ const decoder = new TextDecoder();
753
+ let buffer = '';
754
+ let fullContent = '';
755
+
756
+ const processChunk = async () => {
757
+ while (true) {
758
+ const { done, value } = await reader.read();
759
+ if (done) break;
760
+
761
+ buffer += decoder.decode(value, { stream: true });
762
+ const lines = buffer.split('\n');
763
+ buffer = lines.pop() || '';
764
+
765
+ for (const line of lines) {
766
+ const trimmed = line.trim();
767
+ if (!trimmed) continue;
768
+
769
+ if (trimmed.startsWith('data: ')) {
770
+ const data = trimmed.slice(6);
771
+ if (data === '[DONE]') {
772
+ return { content: fullContent, done: true };
773
+ }
774
+
775
+ try {
776
+ const parsed = JSON.parse(data);
777
+ if (parsed.choices?.[0]?.delta?.content) {
778
+ fullContent += parsed.choices[0].delta.content;
779
+ yield { content: fullContent, done: false };
780
+ }
781
+ } catch (e) {
782
+ // Ignore JSON parse errors for incomplete chunks
783
+ }
784
+ }
785
+ }
786
+ }
787
+ return { content: fullContent, done: true };
788
+ };
789
+
790
+ return {
791
+ [Symbol.asyncIterator]: async function* () {
792
+ for await (const chunk of processChunk()) {
793
+ yield chunk;
794
+ }
795
+ }
796
+ };
797
+
798
+ } catch (error) {
799
+ if (error.name === 'AbortError') {
800
+ throw new Error('Request was cancelled');
801
  }
802
+ throw error;
803
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
  },
805
 
806
  stop() {
807
+ if (this.abortController) {
808
+ this.abortController.abort();
809
+ this.abortController = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
  }
811
  }
812
+ };
 
813
 
814
  // Actions
815
  async function sendMessage() {
816
  const text = els.input.value.trim();
817
  if (!text) return;
818
 
819
+ // Clear any previous errors
820
+ els.input.classList.remove('error');
821
+ const existingError = els.input.parentNode.querySelector('.error-message');
822
+ if (existingError) existingError.remove();
823
+
824
  // Add user message
825
  const userMsg = { id: uid(), role: 'user', content: text, ts: Date.now() };
826
  ChatState.data.push(userMsg);
 
837
  // Generate AI response
838
  els.sendBtn.disabled = true;
839
  els.stopBtn.style.display = 'inline-flex';
840
+
841
+ // Create AI message placeholder
842
+ const tempMsgId = uid();
843
+ const tempMsg = { id: tempMsgId, role: 'assistant', content: '' };
844
+ ChatState.data.push(tempMsg);
845
+ ChatState.save();
846
+
847
+ const el = messageElement(tempMsg, { streaming: true });
848
+ els.messages.appendChild(el);
849
+ refreshEmpty();
850
+ scrollToBottom();
851
+
852
+ const contentEl = el.querySelector('.content');
853
+
854
+ try {
855
+ // Prepare messages for API (last 10 messages to avoid token limits)
856
+ const apiMessages = ChatState.data.slice(-10).map(msg => ({
857
+ role: msg.role,
858
+ content: msg.content
859
+ }));
860
+
861
+ // Get streaming response
862
+ const stream = await PoeAPI.chatCompletion(apiMessages);
863
+
864
+ let finalContent = '';
865
+ for await (const chunk of stream) {
866
+ finalContent = chunk.content;
867
+ contentEl.innerHTML = renderMarkdown(finalContent);
868
+ scrollToBottom();
869
+ }
870
+
871
+ // Final update
872
+ tempMsg.content = finalContent;
873
+ contentEl.innerHTML = renderMarkdown(finalContent);
874
+
875
+ // Remove typing indicator
876
+ const typingIndicator = el.querySelector('.meta:last-child .typing');
877
+ if (typingIndicator) {
878
+ typingIndicator.parentNode.remove();
879
+ }
880
+
881
+ ChatState.save();
882
+
883
+ } catch (error) {
884
+ console.error('API Error:', error);
885
+
886
+ // Show error message
887
+ tempMsg.content = `❌ **Error**: ${error.message}\n\nPlease check your internet connection and try again.`;
888
+ contentEl.innerHTML = renderMarkdown(tempMsg.content);
889
+
890
+ // Remove typing indicator
891
+ const typingIndicator = el.querySelector('.meta:last-child .typing');
892
+ if (typingIndicator) {
893
+ typingIndicator.parentNode.remove();
894
+ }
895
+
896
+ // Show visual error on input
897
+ els.input.classList.add('error');
898
+ const errorMsg = document.createElement('div');
899
+ errorMsg.className = 'error-message';
900
+ errorMsg.textContent = `API Error: ${error.message}`;
901
+ els.input.parentNode.appendChild(errorMsg);
902
+
903
+ ChatState.save();
904
+ } finally {
905
  els.stopBtn.style.display = 'none';
906
  els.sendBtn.disabled = false;
907
+ }
 
 
 
 
 
 
 
 
 
 
 
 
908
  }
909
 
910
  function newChat() {
 
942
  els.newChatBtn.addEventListener('click', newChat);
943
  els.exportBtn.addEventListener('click', exportChat);
944
 
945
+ // Stop button
946
+ els.stopBtn.addEventListener('click', () => {
947
+ PoeAPI.stop();
948
+ els.stopBtn.style.display = 'none';
949
+ els.sendBtn.disabled = false;
950
+ });
951
+
952
  // Chips
953
  els.chips.addEventListener('click', (e) => {
954
  const chip = e.target.closest('.chip');
 
958
  els.input.focus();
959
  });
960
 
961
+ // Stop generation when switching tabs (saves API calls)
962
  document.addEventListener('visibilitychange', () => {
963
+ if (document.hidden) {
964
+ PoeAPI.stop();
965
+ els.stopBtn.style.display = 'none';
966
+ els.sendBtn.disabled = false;
967
+ }
968
  });
969
 
970
  // Initialize
 
973
 
974
  // Preload a welcome message if empty
975
  if (ChatState.isEmpty) {
976
+ const welcome = {
977
+ id: uid(),
978
+ role: 'assistant',
979
+ content:
980
+ `Hello! I'm connected to the Poe.com API using the claude-haiku-cheap model.
981
+
982
+ I can help you with:
983
+ - Code explanations and programming help
984
+ - Writing and editing text
985
+ - Brainstorming ideas
986
+ - Answering questions
987
+ - And much more!
988
+
989
+ Just type your message and I'll respond in real-time. Use Markdown for formatting.`
990
+ };
991
  ChatState.data.push(welcome);
992
  ChatState.save();
993
  renderAll();
994
  }
995
  </script>
996
  </body>
997
+
998
  </html>