AiCoderv2 commited on
Commit
45eb4b4
·
verified ·
1 Parent(s): aaff728

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +925 -19
index.html CHANGED
@@ -1,19 +1,925 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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" />
6
+ <title>AnyCoder Chat — AI Chatbot</title>
7
+ <style>
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
+ }
57
+
58
+ .app {
59
+ display: grid;
60
+ grid-template-rows: auto 1fr auto;
61
+ height: 100dvh;
62
+ max-height: 100dvh;
63
+ }
64
+
65
+ /* Header */
66
+ .header {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 14px;
70
+ padding: 14px clamp(12px, 2vw, 24px);
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;
80
+ gap: 12px;
81
+ padding: 10px 14px;
82
+ background: var(--panel);
83
+ border: 1px solid var(--panel-strong);
84
+ border-radius: var(--radius);
85
+ box-shadow: var(--shadow);
86
+ backdrop-filter: blur(var(--blur));
87
+ }
88
+ .logo {
89
+ width: 36px;
90
+ height: 36px;
91
+ border-radius: 10px;
92
+ background: linear-gradient(135deg, var(--accent), var(--accent-2));
93
+ display: grid;
94
+ place-items: center;
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);
108
+ font-size: 12px;
109
+ margin-top: 2px;
110
+ }
111
+
112
+ .header-actions {
113
+ margin-left: auto;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 8px;
117
+ }
118
+
119
+ .btn {
120
+ border: 1px solid var(--panel-strong);
121
+ background: var(--panel);
122
+ color: var(--text);
123
+ padding: 10px 14px;
124
+ border-radius: 12px;
125
+ cursor: pointer;
126
+ display: inline-flex;
127
+ align-items: center;
128
+ gap: 8px;
129
+ transition: all 0.2s ease;
130
+ backdrop-filter: blur(var(--blur));
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;
154
+ overflow-y: auto;
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;
166
+ place-items: center;
167
+ pointer-events: none;
168
+ color: var(--muted);
169
+ }
170
+ .empty-inner {
171
+ text-align: center;
172
+ max-width: 680px;
173
+ padding: 30px;
174
+ border-radius: var(--radius);
175
+ background: var(--panel);
176
+ border: 1px solid var(--panel-strong);
177
+ backdrop-filter: blur(var(--blur));
178
+ box-shadow: var(--shadow);
179
+ }
180
+ .chips {
181
+ display: flex;
182
+ flex-wrap: wrap;
183
+ gap: 8px;
184
+ margin-top: 14px;
185
+ justify-content: center;
186
+ }
187
+ .chip {
188
+ padding: 8px 12px;
189
+ border-radius: 999px;
190
+ background: var(--glass);
191
+ border: 1px solid var(--panel-strong);
192
+ cursor: pointer;
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;
200
+ grid-template-columns: 36px 1fr;
201
+ gap: 10px;
202
+ margin: 10px auto;
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;
210
+ border-radius: 10px;
211
+ background: var(--panel);
212
+ border: 1px solid var(--panel-strong);
213
+ display: grid;
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;
224
+ background: var(--panel);
225
+ border: 1px solid var(--panel-strong);
226
+ backdrop-filter: blur(var(--blur));
227
+ box-shadow: var(--shadow);
228
+ line-height: 1.5;
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 {
259
+ display: inline-flex;
260
+ gap: 4px;
261
+ align-items: center;
262
+ }
263
+ .dot {
264
+ width: 6px;
265
+ height: 6px;
266
+ background: var(--muted);
267
+ border-radius: 50%;
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 */
279
+ .composer {
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);
289
+ background: var(--panel);
290
+ border: 1px solid var(--panel-strong);
291
+ border-radius: var(--radius-lg);
292
+ box-shadow: var(--shadow);
293
+ padding: 10px;
294
+ display: grid;
295
+ grid-template-columns: 1fr auto;
296
+ gap: 8px;
297
+ backdrop-filter: blur(var(--blur));
298
+ }
299
+ .input-wrap {
300
+ display: flex;
301
+ align-items: flex-end;
302
+ gap: 8px;
303
+ padding: 6px;
304
+ background: var(--glass);
305
+ border: 1px solid var(--panel-strong);
306
+ border-radius: 12px;
307
+ }
308
+ textarea {
309
+ width: 100%;
310
+ resize: none;
311
+ border: none;
312
+ outline: none;
313
+ background: transparent;
314
+ color: var(--text);
315
+ font: inherit;
316
+ line-height: 1.4;
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);
328
+ font-size: 12px;
329
+ display: flex;
330
+ align-items: center;
331
+ gap: 10px;
332
+ justify-content: space-between;
333
+ padding: 0 4px;
334
+ flex-wrap: wrap;
335
+ }
336
+
337
+ .kbd {
338
+ border: 1px solid var(--panel-strong);
339
+ background: var(--panel);
340
+ padding: 2px 6px;
341
+ border-radius: 6px;
342
+ font-size: 12px;
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">
363
+ <div class="brand" title="AnyCoder Chat">
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">
371
+ <button class="btn small ghost" id="newChatBtn" title="Start a new chat">
372
+ <svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
373
+ New
374
+ </button>
375
+ <button class="btn small ghost" id="exportBtn" title="Export chat as JSON">
376
+ <svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M12 16V4m0 0l-4 4m4-4l4 4M4 20h16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
377
+ Export
378
+ </button>
379
+ <button class="btn small ghost" id="themeBtn" title="Toggle theme">
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>
388
+ </header>
389
+
390
+ <main class="chat" id="chat">
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>
399
+ <div class="chip">Summarize: "Effective Remote Work"</div>
400
+ <div class="chip">Brainstorm app features</div>
401
+ <div class="chip">Draft a polite email</div>
402
+ </div>
403
+ </div>
404
+ </div>
405
+ </main>
406
+
407
+ <footer class="composer">
408
+ <div class="composer-inner">
409
+ <div class="input-wrap">
410
+ <textarea id="input" rows="1" placeholder="Message AnyCoder..."></textarea>
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
421
+ </button>
422
+ </div>
423
+ <button class="btn primary" id="sendBtn" title="Send message">
424
+ <svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M22 2L11 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 2l-7 20-4-9-9-4 20-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
425
+ Send
426
+ </button>
427
+ </div>
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; } },
443
+ set(key, value) { localStorage.setItem(key, JSON.stringify(value)); },
444
+ remove(key) { localStorage.removeItem(key); }
445
+ };
446
+
447
+ // Utility: escape HTML
448
+ const escapeHtml = (str) => str
449
+ .replace(/&/g, "&amp;")
450
+ .replace(/</g, "&lt;")
451
+ .replace(/>/g, "&gt;");
452
+
453
+ // Utility: minimal markdown renderer (safe-ish)
454
+ function renderMarkdown(md) {
455
+ if (!md) return "";
456
+ // Extract code blocks first to avoid double-processing inside them
457
+ const codeBlocks = [];
458
+ md = md.replace(/```([\s\S]*?)```/g, (_, code) => {
459
+ const placeholder = `@@CODEBLOCK_${codeBlocks.length}@@`;
460
+ codeBlocks.push(code);
461
+ return placeholder;
462
+ });
463
+
464
+ // Escape HTML
465
+ md = escapeHtml(md);
466
+
467
+ // Restore code blocks with <pre><code>
468
+ codeBlocks.forEach((code, i) => {
469
+ const safe = escapeHtml(code);
470
+ md = md.replace(`@@CODEBLOCK_${i}@@`, `<pre><code>${safe}</code></pre>`);
471
+ });
472
+
473
+ // Inline code: `code`
474
+ md = md.replace(/`([^`]+)`/g, `<code>$1</code>`);
475
+
476
+ // Bold: **text**
477
+ md = md.replace(/\*\*([^*]+)\*\*/g, `<strong>$1</strong>`);
478
+
479
+ // Italic: *text* (avoid overlapping with bold)
480
+ md = md.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
481
+
482
+ // Links: [text](url)
483
+ md = md.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, `<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>`);
484
+
485
+ // Line breaks -> paragraphs
486
+ md = md.split(/\n{2,}/).map(chunk => `<p>${chunk.replace(/\n/g, "<br>")}</p>`).join("");
487
+
488
+ return md;
489
+ }
490
+
491
+ // Utility: simple UUID
492
+ const uid = () => (crypto?.randomUUID?.() || `id_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`);
493
+
494
+ // Elements
495
+ const els = {
496
+ app: document.getElementById('app'),
497
+ messages: document.getElementById('messages'),
498
+ empty: document.getElementById('empty'),
499
+ chips: document.getElementById('chips'),
500
+ input: document.getElementById('input'),
501
+ sendBtn: document.getElementById('sendBtn'),
502
+ stopBtn: document.getElementById('stopBtn'),
503
+ newChatBtn: document.getElementById('newChatBtn'),
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
+
511
+ // Theme
512
+ function applyTheme(theme) {
513
+ if (theme === 'light') document.body.classList.add('theme-light');
514
+ else document.body.classList.remove('theme-light');
515
+ els.themeIcon.innerHTML = theme === 'light'
516
+ ? '<path d="M12 3v2m0 14v2m9-9h-2M5 12H3m14.95 6.95l-1.414-1.414M7.464 7.464L6.05 6.05m11.314 0l-1.414 1.414M7.464 16.536l-1.414 1.414" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="4" stroke="currentColor" stroke-width="2"/>'
517
+ : '<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"/>';
518
+ }
519
+ const savedTheme = store.get('anycoder_theme', 'dark');
520
+ applyTheme(savedTheme);
521
+ els.themeBtn.addEventListener('click', () => {
522
+ const next = document.body.classList.contains('theme-light') ? 'dark' : 'light';
523
+ store.set('anycoder_theme', next);
524
+ applyTheme(next);
525
+ });
526
+
527
+ // Chat state
528
+ const ChatState = {
529
+ data: store.get('anycoder_chat', []),
530
+ get isEmpty() { return this.data.length === 0; },
531
+ save() { store.set('anycoder_chat', this.data); }
532
+ };
533
+
534
+ // Renderers
535
+ function messageElement(msg, { streaming = false } = {}) {
536
+ const isUser = msg.role === 'user';
537
+ const el = document.createElement('div');
538
+ el.className = `msg ${isUser ? 'user' : 'ai'}`;
539
+ el.dataset.id = msg.id;
540
+
541
+ const avatar = document.createElement('div');
542
+ avatar.className = `avatar ${isUser ? '' : 'ai'}`;
543
+ avatar.innerHTML = isUser
544
+ ? '<svg class="icon" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2"/><path d="M4 20c0-4 4-6 8-6s8 2 8 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'
545
+ : '<svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M12 3l8 4v5c0 5-3.5 9-8 9S4 17 4 12V7l8-4z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
546
+
547
+ const bubble = document.createElement('div');
548
+ bubble.className = 'bubble';
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';
556
+ if (isUser) {
557
+ content.textContent = msg.content;
558
+ } else {
559
+ content.innerHTML = renderMarkdown(msg.content);
560
+ }
561
+
562
+ bubble.appendChild(meta);
563
+ bubble.appendChild(content);
564
+
565
+ if (isUser) {
566
+ // user: [avatar right]
567
+ el.appendChild(bubble);
568
+ el.appendChild(avatar);
569
+ } else {
570
+ el.appendChild(avatar);
571
+ el.appendChild(bubble);
572
+ }
573
+
574
+ // Typing indicator if streaming
575
+ if (!isUser && streaming) {
576
+ const typing = document.createElement('div');
577
+ typing.className = 'meta';
578
+ typing.style.marginTop = '6px';
579
+ typing.innerHTML = '<span class="typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span>';
580
+ bubble.appendChild(typing);
581
+ }
582
+
583
+ return el;
584
+ }
585
+
586
+ function scrollToBottom() {
587
+ els.messages.scrollTop = els.messages.scrollHeight;
588
+ }
589
+
590
+ function refreshEmpty() {
591
+ els.empty.style.display = ChatState.isEmpty ? 'grid' : 'none';
592
+ }
593
+
594
+ function renderAll() {
595
+ els.messages.innerHTML = '';
596
+ ChatState.data.forEach(msg => {
597
+ els.messages.appendChild(messageElement(msg));
598
+ });
599
+ refreshEmpty();
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);
824
+ ChatState.save();
825
+ els.messages.appendChild(messageElement(userMsg));
826
+ refreshEmpty();
827
+ scrollToBottom();
828
+
829
+ // Clear input
830
+ els.input.value = "";
831
+ autoResizeTextarea();
832
+ els.input.focus();
833
+
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() {
860
+ ChatState.data = [];
861
+ ChatState.save();
862
+ renderAll();
863
+ }
864
+
865
+ function exportChat() {
866
+ const blob = new Blob([JSON.stringify(ChatState.data, null, 2)], { type: 'application/json' });
867
+ const url = URL.createObjectURL(blob);
868
+ const a = document.createElement('a');
869
+ a.href = url;
870
+ a.download = `anycoder_chat_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
871
+ document.body.appendChild(a);
872
+ a.click();
873
+ a.remove();
874
+ URL.revokeObjectURL(url);
875
+ }
876
+
877
+ // Input behaviors
878
+ function autoResizeTextarea() {
879
+ const ta = els.input;
880
+ ta.style.height = 'auto';
881
+ ta.style.height = Math.min(180, ta.scrollHeight) + 'px';
882
+ }
883
+ els.input.addEventListener('input', autoResizeTextarea);
884
+ els.input.addEventListener('keydown', (e) => {
885
+ if (e.key === 'Enter' && !e.shiftKey) {
886
+ e.preventDefault();
887
+ sendMessage();
888
+ }
889
+ });
890
+ els.sendBtn.addEventListener('click', sendMessage);
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');
897
+ if (!chip) return;
898
+ els.input.value = chip.textContent.trim();
899
+ autoResizeTextarea();
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
909
+ renderAll();
910
+ autoResizeTextarea();
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>