Sanyam400 commited on
Commit
766bc9d
Β·
verified Β·
1 Parent(s): 9f3e11e

Create static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +1000 -0
static/index.html ADDED
@@ -0,0 +1,1000 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>PraisonChat</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" />
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
10
+ <style>
11
+ :root {
12
+ --bg-primary: #0f0f17;
13
+ --bg-secondary: #17172a;
14
+ --bg-tertiary: #1e1e35;
15
+ --bg-hover: #252540;
16
+ --border: #2a2a4a;
17
+ --text-primary: #e8e8f0;
18
+ --text-secondary:#9898b8;
19
+ --text-muted: #5a5a7a;
20
+ --accent: #7c6af7;
21
+ --accent-hover: #9580ff;
22
+ --accent-glow: rgba(124,106,247,0.25);
23
+ --green: #4caf88;
24
+ --orange: #f0a055;
25
+ --red: #f05555;
26
+ --user-bg: #1e1e35;
27
+ --assistant-bg: #17172a;
28
+ --input-bg: #1e1e35;
29
+ --sidebar-w: 280px;
30
+ --radius: 12px;
31
+ --radius-sm: 8px;
32
+ --shadow: 0 4px 24px rgba(0,0,0,0.4);
33
+ }
34
+ [data-theme="light"] {
35
+ --bg-primary: #f5f5fa;
36
+ --bg-secondary: #ffffff;
37
+ --bg-tertiary: #ececf6;
38
+ --bg-hover: #e0e0f0;
39
+ --border: #d0d0e8;
40
+ --text-primary: #1a1a2e;
41
+ --text-secondary:#555570;
42
+ --text-muted: #9898b8;
43
+ --user-bg: #ececf6;
44
+ --assistant-bg: #ffffff;
45
+ --input-bg: #ffffff;
46
+ }
47
+ * { box-sizing: border-box; margin: 0; padding: 0; }
48
+ html, body { height: 100%; font-family: 'Inter', system-ui, sans-serif; background: var(--bg-primary); color: var(--text-primary); overflow: hidden; }
49
+
50
+ /* ── Layout ── */
51
+ #app { display: flex; height: 100vh; }
52
+
53
+ /* ── Sidebar ── */
54
+ #sidebar {
55
+ width: var(--sidebar-w); min-width: var(--sidebar-w);
56
+ background: var(--bg-secondary);
57
+ border-right: 1px solid var(--border);
58
+ display: flex; flex-direction: column;
59
+ transition: transform 0.3s ease;
60
+ z-index: 100;
61
+ }
62
+ #sidebar-header {
63
+ padding: 20px 16px 12px;
64
+ border-bottom: 1px solid var(--border);
65
+ }
66
+ .logo { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
67
+ .logo-icon {
68
+ width: 36px; height: 36px; border-radius: 10px;
69
+ background: linear-gradient(135deg, var(--accent), #a855f7);
70
+ display: flex; align-items: center; justify-content: center;
71
+ font-size: 18px; box-shadow: 0 2px 12px var(--accent-glow);
72
+ }
73
+ .logo-text { font-size: 18px; font-weight: 700; letter-spacing: -0.3px; }
74
+ .logo-text span { color: var(--accent); }
75
+
76
+ #new-chat-btn {
77
+ width: 100%; padding: 9px 14px;
78
+ background: var(--accent); color: #fff; border: none;
79
+ border-radius: var(--radius-sm); cursor: pointer;
80
+ font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px;
81
+ transition: background 0.2s, transform 0.1s;
82
+ }
83
+ #new-chat-btn:hover { background: var(--accent-hover); }
84
+ #new-chat-btn:active { transform: scale(0.98); }
85
+
86
+ #conversations {
87
+ flex: 1; overflow-y: auto; padding: 8px;
88
+ scrollbar-width: thin; scrollbar-color: var(--border) transparent;
89
+ }
90
+ .conv-group-label {
91
+ font-size: 11px; font-weight: 600; color: var(--text-muted);
92
+ text-transform: uppercase; letter-spacing: 0.8px;
93
+ padding: 8px 8px 4px;
94
+ }
95
+ .conv-item {
96
+ padding: 9px 12px; border-radius: var(--radius-sm);
97
+ cursor: pointer; display: flex; align-items: center; gap: 8px;
98
+ font-size: 13.5px; color: var(--text-secondary);
99
+ transition: background 0.15s;
100
+ position: relative;
101
+ }
102
+ .conv-item:hover { background: var(--bg-hover); color: var(--text-primary); }
103
+ .conv-item.active { background: var(--accent-glow); color: var(--text-primary); }
104
+ .conv-item .conv-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
105
+ .conv-item .conv-del {
106
+ opacity: 0; font-size: 12px; color: var(--text-muted);
107
+ background: none; border: none; cursor: pointer; padding: 2px 4px;
108
+ transition: opacity 0.2s; border-radius: 4px;
109
+ }
110
+ .conv-item:hover .conv-del { opacity: 1; }
111
+ .conv-item .conv-del:hover { color: var(--red); background: rgba(240,85,85,0.12); }
112
+
113
+ #sidebar-footer {
114
+ padding: 12px 8px;
115
+ border-top: 1px solid var(--border);
116
+ display: flex; flex-direction: column; gap: 4px;
117
+ }
118
+ .sidebar-btn {
119
+ padding: 9px 12px; border-radius: var(--radius-sm);
120
+ cursor: pointer; display: flex; align-items: center; gap: 8px;
121
+ font-size: 13.5px; color: var(--text-secondary);
122
+ background: none; border: none; width: 100%; text-align: left;
123
+ transition: background 0.15s;
124
+ }
125
+ .sidebar-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
126
+
127
+ /* ── Main area ── */
128
+ #main { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
129
+
130
+ #topbar {
131
+ padding: 12px 20px; border-bottom: 1px solid var(--border);
132
+ background: var(--bg-secondary);
133
+ display: flex; align-items: center; gap: 12px;
134
+ }
135
+ #menu-toggle {
136
+ display: none; background: none; border: none;
137
+ color: var(--text-secondary); cursor: pointer; font-size: 20px; padding: 4px;
138
+ }
139
+ #model-badge {
140
+ display: flex; align-items: center; gap: 8px;
141
+ background: var(--bg-tertiary); border: 1px solid var(--border);
142
+ border-radius: 20px; padding: 5px 12px; cursor: pointer;
143
+ font-size: 13px; font-weight: 500; transition: background 0.15s;
144
+ position: relative;
145
+ }
146
+ #model-badge:hover { background: var(--bg-hover); }
147
+ .model-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; }
148
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
149
+ #topbar-title { flex: 1; font-size: 15px; font-weight: 600; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
150
+ .icon-btn {
151
+ background: none; border: none; cursor: pointer;
152
+ color: var(--text-secondary); padding: 7px; border-radius: var(--radius-sm);
153
+ font-size: 17px; transition: background 0.15s, color 0.15s; line-height: 1;
154
+ }
155
+ .icon-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
156
+
157
+ /* ── Messages ── */
158
+ #messages-wrap { flex: 1; overflow-y: auto; padding: 24px 0; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
159
+ #messages { max-width: 800px; margin: 0 auto; padding: 0 20px; display: flex; flex-direction: column; gap: 0; }
160
+
161
+ .msg-row { display: flex; gap: 14px; padding: 14px 0; animation: fadeIn 0.2s ease; }
162
+ @keyframes fadeIn { from { opacity:0; transform: translateY(6px); } to { opacity:1; transform: none; } }
163
+ .msg-row.user { flex-direction: row-reverse; }
164
+
165
+ .avatar {
166
+ width: 36px; height: 36px; min-width: 36px; border-radius: 10px;
167
+ display: flex; align-items: center; justify-content: center;
168
+ font-size: 16px; font-weight: 700;
169
+ }
170
+ .avatar.user-av { background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; }
171
+ .avatar.ai-av { background: linear-gradient(135deg, var(--accent), #a855f7); color: #fff; }
172
+
173
+ .msg-content { flex: 1; min-width: 0; }
174
+ .msg-row.user .msg-content { display: flex; flex-direction: column; align-items: flex-end; }
175
+
176
+ .msg-bubble {
177
+ max-width: 85%; padding: 12px 16px; border-radius: var(--radius);
178
+ font-size: 14.5px; line-height: 1.65;
179
+ }
180
+ .msg-row.user .msg-bubble {
181
+ background: var(--accent); color: #fff;
182
+ border-radius: var(--radius) var(--radius) 4px var(--radius);
183
+ }
184
+ .msg-row.assistant .msg-bubble {
185
+ background: var(--bg-tertiary); color: var(--text-primary);
186
+ border-radius: var(--radius) var(--radius) var(--radius) 4px;
187
+ border: 1px solid var(--border); width: 100%; max-width: 100%;
188
+ }
189
+
190
+ /* Markdown in messages */
191
+ .msg-bubble h1,.msg-bubble h2,.msg-bubble h3 { margin: 14px 0 6px; line-height: 1.3; }
192
+ .msg-bubble h1 { font-size: 20px; } .msg-bubble h2 { font-size: 17px; } .msg-bubble h3 { font-size: 15px; }
193
+ .msg-bubble p { margin: 0 0 10px; } .msg-bubble p:last-child { margin-bottom: 0; }
194
+ .msg-bubble ul,.msg-bubble ol { margin: 8px 0 8px 20px; }
195
+ .msg-bubble li { margin-bottom: 4px; }
196
+ .msg-bubble strong { font-weight: 700; }
197
+ .msg-bubble em { font-style: italic; }
198
+ .msg-bubble a { color: var(--accent); text-decoration: underline; }
199
+ .msg-bubble hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; }
200
+ .msg-bubble blockquote { border-left: 3px solid var(--accent); padding-left: 12px; margin: 8px 0; color: var(--text-secondary); }
201
+ .msg-bubble table { border-collapse: collapse; width: 100%; margin: 10px 0; font-size: 13px; }
202
+ .msg-bubble th,.msg-bubble td { border: 1px solid var(--border); padding: 7px 10px; }
203
+ .msg-bubble th { background: var(--bg-hover); font-weight: 600; }
204
+
205
+ .code-block-wrapper { position: relative; margin: 10px 0; border-radius: var(--radius-sm); overflow: hidden; }
206
+ .code-header { display: flex; justify-content: space-between; align-items: center; padding: 6px 12px; background: #0d1117; border-bottom: 1px solid #30363d; font-size: 12px; color: #8b949e; }
207
+ .copy-btn { background: none; border: 1px solid #30363d; color: #8b949e; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: all 0.15s; }
208
+ .copy-btn:hover { background: #21262d; color: #c9d1d9; }
209
+ .msg-bubble pre { margin: 0; }
210
+ .msg-bubble pre code { display: block; padding: 14px; overflow-x: auto; font-size: 13px; line-height: 1.5; }
211
+ .msg-bubble code:not(pre code) { background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'JetBrains Mono', 'Fira Code', monospace; }
212
+
213
+ /* ── Agent Steps ── */
214
+ .agent-steps {
215
+ margin: 6px 0 10px; border: 1px solid var(--border);
216
+ border-radius: var(--radius-sm); overflow: hidden;
217
+ }
218
+ .steps-toggle {
219
+ width: 100%; padding: 9px 14px; background: var(--bg-hover);
220
+ border: none; cursor: pointer; color: var(--text-secondary);
221
+ display: flex; align-items: center; gap: 8px; font-size: 12.5px;
222
+ text-align: left; transition: background 0.15s;
223
+ }
224
+ .steps-toggle:hover { background: var(--bg-tertiary); color: var(--text-primary); }
225
+ .steps-toggle .chevron { margin-left: auto; transition: transform 0.2s; font-size: 10px; }
226
+ .steps-toggle.open .chevron { transform: rotate(180deg); }
227
+ .steps-body { display: none; padding: 8px; background: var(--bg-primary); }
228
+ .steps-body.open { display: block; }
229
+ .step-item { display: flex; align-items: flex-start; gap: 8px; padding: 5px 6px; font-size: 12.5px; color: var(--text-secondary); border-radius: 6px; }
230
+ .step-item:hover { background: var(--bg-hover); }
231
+ .agent-card { background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; margin: 4px 0; font-size: 12.5px; }
232
+ .agent-card-name { font-weight: 700; color: var(--accent); margin-bottom: 3px; font-size: 13px; }
233
+ .agent-card-role { color: var(--text-secondary); margin-bottom: 4px; }
234
+ .agent-card-tools { display: flex; flex-wrap: wrap; gap: 4px; }
235
+ .tool-tag { background: var(--bg-hover); border: 1px solid var(--border); border-radius: 20px; padding: 1px 8px; font-size: 11px; color: var(--text-muted); font-family: monospace; }
236
+ .agent-result { background: var(--bg-tertiary); border-left: 3px solid var(--green); border-radius: 6px; padding: 8px 12px; margin: 4px 0; font-size: 12px; color: var(--text-secondary); }
237
+ .agent-result-name { font-weight: 700; color: var(--green); margin-bottom: 3px; }
238
+
239
+ .msg-meta { font-size: 11px; color: var(--text-muted); margin-top: 5px; padding: 0 4px; display: flex; gap: 10px; align-items: center; }
240
+ .msg-copy-btn { background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 11px; padding: 2px 6px; border-radius: 4px; transition: all 0.15s; }
241
+ .msg-copy-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
242
+
243
+ /* ── Typing indicator ── */
244
+ .typing-dots { display: flex; gap: 5px; padding: 14px 18px; }
245
+ .typing-dots span { width: 7px; height: 7px; background: var(--text-muted); border-radius: 50%; animation: bounce 1.2s infinite; }
246
+ .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
247
+ .typing-dots span:nth-child(3) { animation-delay: 0.4s; }
248
+ @keyframes bounce { 0%,60%,100%{transform:translateY(0)} 30%{transform:translateY(-8px)} }
249
+
250
+ /* ── Welcome screen ── */
251
+ #welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; padding: 40px 20px; text-align: center; }
252
+ #welcome h1 { font-size: 32px; font-weight: 800; margin-bottom: 10px; background: linear-gradient(135deg, var(--accent), #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
253
+ #welcome p { color: var(--text-secondary); font-size: 16px; max-width: 480px; line-height: 1.6; margin-bottom: 32px; }
254
+ .suggestion-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; max-width: 580px; width: 100%; }
255
+ .suggestion-card {
256
+ padding: 14px 16px; background: var(--bg-tertiary);
257
+ border: 1px solid var(--border); border-radius: var(--radius);
258
+ cursor: pointer; text-align: left; transition: all 0.2s;
259
+ font-size: 13.5px; color: var(--text-secondary); line-height: 1.4;
260
+ }
261
+ .suggestion-card:hover { background: var(--bg-hover); border-color: var(--accent); color: var(--text-primary); transform: translateY(-1px); box-shadow: 0 4px 16px var(--accent-glow); }
262
+ .suggestion-card strong { display: block; color: var(--text-primary); margin-bottom: 3px; font-size: 14px; }
263
+
264
+ /* ── Input area ── */
265
+ #input-area {
266
+ padding: 16px 20px 20px;
267
+ background: var(--bg-secondary);
268
+ border-top: 1px solid var(--border);
269
+ }
270
+ #input-container { max-width: 800px; margin: 0 auto; }
271
+ #input-box {
272
+ display: flex; align-items: flex-end; gap: 10px;
273
+ background: var(--input-bg); border: 1.5px solid var(--border);
274
+ border-radius: var(--radius); padding: 10px 12px;
275
+ transition: border-color 0.2s, box-shadow 0.2s;
276
+ }
277
+ #input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
278
+ #message-input {
279
+ flex: 1; background: none; border: none; color: var(--text-primary);
280
+ font-size: 14.5px; resize: none; outline: none; line-height: 1.5;
281
+ max-height: 180px; min-height: 24px; font-family: inherit;
282
+ }
283
+ #message-input::placeholder { color: var(--text-muted); }
284
+ #send-btn {
285
+ width: 36px; height: 36px; min-width: 36px;
286
+ background: var(--accent); border: none; border-radius: 8px;
287
+ cursor: pointer; color: #fff; font-size: 16px;
288
+ display: flex; align-items: center; justify-content: center;
289
+ transition: background 0.2s, transform 0.1s;
290
+ }
291
+ #send-btn:hover:not(:disabled) { background: var(--accent-hover); }
292
+ #send-btn:active { transform: scale(0.95); }
293
+ #send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
294
+ #stop-btn { display: none; width: 36px; height: 36px; min-width: 36px; background: var(--red); border: none; border-radius: 8px; cursor: pointer; color: #fff; font-size: 14px; align-items: center; justify-content: center; transition: opacity 0.2s; }
295
+ #stop-btn:hover { opacity: 0.85; }
296
+ .input-hint { font-size: 11.5px; color: var(--text-muted); margin-top: 7px; text-align: center; }
297
+
298
+ /* ── Settings Modal ── */
299
+ .modal-overlay {
300
+ display: none; position: fixed; inset: 0;
301
+ background: rgba(0,0,0,0.6); backdrop-filter: blur(6px);
302
+ z-index: 999; align-items: center; justify-content: center;
303
+ }
304
+ .modal-overlay.open { display: flex; }
305
+ .modal {
306
+ background: var(--bg-secondary); border: 1px solid var(--border);
307
+ border-radius: 16px; padding: 28px; width: 440px; max-width: 95vw;
308
+ box-shadow: var(--shadow); animation: modalIn 0.2s ease;
309
+ }
310
+ @keyframes modalIn { from{opacity:0;transform:scale(0.95)} to{opacity:1;transform:scale(1)} }
311
+ .modal h2 { font-size: 18px; font-weight: 700; margin-bottom: 20px; }
312
+ .form-group { margin-bottom: 16px; }
313
+ .form-group label { display: block; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; }
314
+ .form-group input, .form-group select {
315
+ width: 100%; padding: 9px 12px;
316
+ background: var(--bg-tertiary); border: 1.5px solid var(--border);
317
+ border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px;
318
+ outline: none; transition: border-color 0.2s;
319
+ }
320
+ .form-group input:focus, .form-group select:focus { border-color: var(--accent); }
321
+ .form-group .hint { font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
322
+ .modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 22px; }
323
+ .btn-primary { padding: 9px 20px; background: var(--accent); color: #fff; border: none; border-radius: var(--radius-sm); cursor: pointer; font-size: 14px; font-weight: 600; transition: background 0.2s; }
324
+ .btn-primary:hover { background: var(--accent-hover); }
325
+ .btn-ghost { padding: 9px 20px; background: none; color: var(--text-secondary); border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; font-size: 14px; transition: background 0.2s; }
326
+ .btn-ghost:hover { background: var(--bg-hover); }
327
+
328
+ /* ── Model dropdown ── */
329
+ #model-dropdown { display: none; position: absolute; top: calc(100% + 8px); left: 0; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); min-width: 260px; box-shadow: var(--shadow); z-index: 200; overflow: hidden; }
330
+ #model-dropdown.open { display: block; }
331
+ .model-option { padding: 10px 14px; cursor: pointer; transition: background 0.15s; display: flex; align-items: center; gap: 10px; }
332
+ .model-option:hover { background: var(--bg-hover); }
333
+ .model-option.active { background: var(--accent-glow); }
334
+ .model-name { font-size: 13.5px; font-weight: 600; }
335
+ .model-meta { font-size: 11px; color: var(--text-muted); }
336
+
337
+ /* ── Mobile ── */
338
+ @media (max-width: 700px) {
339
+ #sidebar { position: fixed; left: 0; top: 0; bottom: 0; transform: translateX(-100%); }
340
+ #sidebar.open { transform: translateX(0); box-shadow: var(--shadow); }
341
+ #menu-toggle { display: flex; }
342
+ .suggestion-grid { grid-template-columns: 1fr; }
343
+ #welcome h1 { font-size: 24px; }
344
+ }
345
+
346
+ /* ── Scrollbar ── */
347
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
348
+ ::-webkit-scrollbar-track { background: transparent; }
349
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
350
+
351
+ /* ── Notification toast ── */
352
+ #toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(80px); background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 8px; padding: 10px 18px; font-size: 13px; transition: transform 0.3s ease; z-index: 9999; white-space: nowrap; box-shadow: var(--shadow); }
353
+ #toast.show { transform: translateX(-50%) translateY(0); }
354
+ </style>
355
+ </head>
356
+ <body>
357
+ <div id="app">
358
+
359
+ <!-- ── Sidebar ─────────────────────────────────────────── -->
360
+ <aside id="sidebar">
361
+ <div id="sidebar-header">
362
+ <div class="logo">
363
+ <div class="logo-icon">🐱</div>
364
+ <div class="logo-text">Praison<span>Chat</span></div>
365
+ </div>
366
+ <button id="new-chat-btn" onclick="newChat()">
367
+ <span>οΌ‹</span> New Chat
368
+ </button>
369
+ </div>
370
+ <div id="conversations"></div>
371
+ <div id="sidebar-footer">
372
+ <button class="sidebar-btn" onclick="openSettings()">βš™οΈ Settings</button>
373
+ <button class="sidebar-btn" onclick="toggleTheme()">πŸŒ“ Toggle Theme</button>
374
+ <button class="sidebar-btn" onclick="clearAllChats()">πŸ—‘οΈ Clear All Chats</button>
375
+ </div>
376
+ </aside>
377
+
378
+ <!-- ── Main ────────────────────────────────────────────── -->
379
+ <div id="main">
380
+ <div id="topbar">
381
+ <button id="menu-toggle" class="icon-btn" onclick="toggleSidebar()">☰</button>
382
+ <div style="position:relative">
383
+ <div id="model-badge" onclick="toggleModelDropdown()">
384
+ <div class="model-dot"></div>
385
+ <span id="current-model-name">LongCat Flash Lite</span>
386
+ <span style="font-size:10px;color:var(--text-muted)">β–Ό</span>
387
+ </div>
388
+ <div id="model-dropdown">
389
+ <div class="model-option active" onclick="selectModel('LongCat-Flash-Lite','LongCat Flash Lite')">
390
+ <div><div class="model-name">LongCat Flash Lite</div><div class="model-meta">320K context Β· Fastest Β· 50M free tokens/day</div></div>
391
+ </div>
392
+ <div class="model-option" onclick="selectModel('LongCat-Flash-Chat','LongCat Flash Chat')">
393
+ <div><div class="model-name">LongCat Flash Chat</div><div class="model-meta">256K context Β· Fast</div></div>
394
+ </div>
395
+ <div class="model-option" onclick="selectModel('LongCat-Flash-Thinking-2601','LongCat Flash Thinking')">
396
+ <div><div class="model-name">LongCat Flash Thinking</div><div class="model-meta">256K context Β· Deep reasoning</div></div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+ <div id="topbar-title">New Conversation</div>
401
+ <button class="icon-btn" title="Clear chat" onclick="clearCurrentChat()">πŸ—‘οΈ</button>
402
+ <button class="icon-btn" title="Settings" onclick="openSettings()">βš™οΈ</button>
403
+ </div>
404
+
405
+ <div id="messages-wrap">
406
+ <div id="messages">
407
+ <!-- Welcome shown when no messages -->
408
+ <div id="welcome">
409
+ <h1>PraisonChat</h1>
410
+ <p>Multi-agent AI powered by LongCat Flash Lite. The main agent dynamically creates sub-agents with custom tools to solve complex tasks.</p>
411
+ <div class="suggestion-grid">
412
+ <div class="suggestion-card" onclick="sendSuggestion(this)">
413
+ <strong>πŸ”¬ Research & Report</strong>
414
+ Research quantum computing advances in 2025 and write a detailed report
415
+ </div>
416
+ <div class="suggestion-card" onclick="sendSuggestion(this)">
417
+ <strong>πŸ’» Code & Debug</strong>
418
+ Write a Python web scraper that extracts product prices from a website
419
+ </div>
420
+ <div class="suggestion-card" onclick="sendSuggestion(this)">
421
+ <strong>πŸ“Š Data Analysis</strong>
422
+ Analyze the pros and cons of different database architectures for a startup
423
+ </div>
424
+ <div class="suggestion-card" onclick="sendSuggestion(this)">
425
+ <strong>πŸ€– AI Architecture</strong>
426
+ Design a multi-agent system for automated customer support with escalation
427
+ </div>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ </div>
432
+
433
+ <div id="input-area">
434
+ <div id="input-container">
435
+ <div id="input-box">
436
+ <textarea
437
+ id="message-input"
438
+ placeholder="Message PraisonChat..."
439
+ rows="1"
440
+ onkeydown="handleKey(event)"
441
+ oninput="autoResize(this)"
442
+ ></textarea>
443
+ <button id="send-btn" onclick="sendMessage()" title="Send">➀</button>
444
+ <button id="stop-btn" onclick="stopGeneration()" title="Stop" style="display:none">⏹</button>
445
+ </div>
446
+ <div class="input-hint">Enter to send Β· Shift+Enter for new line</div>
447
+ </div>
448
+ </div>
449
+ </div>
450
+ </div>
451
+
452
+ <!-- ── Settings Modal ──────────────────────────────────── -->
453
+ <div class="modal-overlay" id="settings-modal" onclick="closeSettingsOutside(event)">
454
+ <div class="modal">
455
+ <h2>βš™οΈ Settings</h2>
456
+ <div class="form-group">
457
+ <label>LongCat API Key</label>
458
+ <input type="password" id="api-key-input" placeholder="Enter your LongCat API key..." />
459
+ <div class="hint">Get your free key at <a href="https://longcat.chat/platform" target="_blank" style="color:var(--accent)">longcat.chat/platform</a> β€” 50M free tokens/day for Flash Lite</div>
460
+ </div>
461
+ <div class="form-group">
462
+ <label>Temperature</label>
463
+ <input type="range" id="temperature-input" min="0" max="1" step="0.05" value="0.7"
464
+ oninput="document.getElementById('temp-val').textContent=this.value" style="padding:0" />
465
+ <div class="hint">Current: <span id="temp-val">0.7</span></div>
466
+ </div>
467
+ <div class="form-group">
468
+ <label>System Prompt (optional)</label>
469
+ <input type="text" id="system-prompt-input" placeholder="You are a helpful AI assistant..." />
470
+ </div>
471
+ <div class="modal-actions">
472
+ <button class="btn-ghost" onclick="closeSettings()">Cancel</button>
473
+ <button class="btn-primary" onclick="saveSettings()">Save Settings</button>
474
+ </div>
475
+ </div>
476
+ </div>
477
+
478
+ <div id="toast"></div>
479
+
480
+ <script>
481
+ // ── State ──────────────────────────────────────────────────────
482
+ let state = {
483
+ conversations: JSON.parse(localStorage.getItem('praison_convs') || '[]'),
484
+ currentId: null,
485
+ messages: [],
486
+ settings: JSON.parse(localStorage.getItem('praison_settings') || '{"apiKey":"","temperature":0.7,"systemPrompt":"","model":"LongCat-Flash-Lite"}'),
487
+ isGenerating: false,
488
+ abortController: null,
489
+ };
490
+
491
+ // ── Init ───────────────────────────────────────────────────────
492
+ function init() {
493
+ renderConversations();
494
+ loadSettings();
495
+ if (state.conversations.length > 0) {
496
+ loadConversation(state.conversations[0].id);
497
+ }
498
+ }
499
+
500
+ // ── Conversation management ───────────────────────────��────────
501
+ function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2); }
502
+
503
+ function newChat() {
504
+ const id = genId();
505
+ const conv = { id, title: 'New Chat', messages: [], created: Date.now() };
506
+ state.conversations.unshift(conv);
507
+ saveConversations();
508
+ loadConversation(id);
509
+ document.getElementById('welcome').style.display = '';
510
+ closeSidebar();
511
+ }
512
+
513
+ function loadConversation(id) {
514
+ const conv = state.conversations.find(c => c.id === id);
515
+ if (!conv) return;
516
+ state.currentId = id;
517
+ state.messages = [...conv.messages];
518
+ renderMessages();
519
+ renderConversations();
520
+ document.getElementById('topbar-title').textContent = conv.title;
521
+ }
522
+
523
+ function saveConversations() {
524
+ localStorage.setItem('praison_convs', JSON.stringify(state.conversations));
525
+ }
526
+
527
+ function updateCurrentConv() {
528
+ const idx = state.conversations.findIndex(c => c.id === state.currentId);
529
+ if (idx >= 0) {
530
+ state.conversations[idx].messages = [...state.messages];
531
+ if (state.messages.length > 0) {
532
+ const first = state.messages[0]?.content || 'New Chat';
533
+ state.conversations[idx].title = first.slice(0, 40) + (first.length > 40 ? '...' : '');
534
+ document.getElementById('topbar-title').textContent = state.conversations[idx].title;
535
+ }
536
+ saveConversations();
537
+ renderConversations();
538
+ }
539
+ }
540
+
541
+ function deleteConversation(id, e) {
542
+ e.stopPropagation();
543
+ state.conversations = state.conversations.filter(c => c.id !== id);
544
+ saveConversations();
545
+ if (state.currentId === id) {
546
+ state.currentId = null;
547
+ state.messages = [];
548
+ clearMessages();
549
+ }
550
+ renderConversations();
551
+ }
552
+
553
+ function clearCurrentChat() {
554
+ state.messages = [];
555
+ clearMessages();
556
+ updateCurrentConv();
557
+ }
558
+
559
+ function clearAllChats() {
560
+ if (!confirm('Clear all conversations?')) return;
561
+ state.conversations = [];
562
+ state.currentId = null;
563
+ state.messages = [];
564
+ saveConversations();
565
+ clearMessages();
566
+ renderConversations();
567
+ }
568
+
569
+ function renderConversations() {
570
+ const el = document.getElementById('conversations');
571
+ if (state.conversations.length === 0) {
572
+ el.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-muted);font-size:13px">No conversations yet</div>';
573
+ return;
574
+ }
575
+ el.innerHTML = '<div class="conv-group-label">Recent</div>' +
576
+ state.conversations.map(c => `
577
+ <div class="conv-item ${c.id === state.currentId ? 'active' : ''}" onclick="loadConversation('${c.id}')">
578
+ <span style="font-size:14px">πŸ’¬</span>
579
+ <span class="conv-title">${escHtml(c.title)}</span>
580
+ <button class="conv-del" onclick="deleteConversation('${c.id}',event)" title="Delete">βœ•</button>
581
+ </div>
582
+ `).join('');
583
+ }
584
+
585
+ // ── Rendering messages ─────────────────────────────────────────
586
+ function clearMessages() {
587
+ const el = document.getElementById('messages');
588
+ el.innerHTML = '<div id="welcome">' + document.getElementById('welcome')?.innerHTML + '</div>';
589
+ showWelcome(true);
590
+ }
591
+
592
+ function showWelcome(show) {
593
+ const w = document.getElementById('welcome');
594
+ if (w) w.style.display = show ? '' : 'none';
595
+ }
596
+
597
+ function renderMessages() {
598
+ const el = document.getElementById('messages');
599
+ if (state.messages.length === 0) {
600
+ el.innerHTML = `<div id="welcome">
601
+ <h1>PraisonChat</h1>
602
+ <p>Multi-agent AI powered by LongCat Flash Lite. The main agent dynamically creates sub-agents with custom tools to solve complex tasks.</p>
603
+ <div class="suggestion-grid">
604
+ <div class="suggestion-card" onclick="sendSuggestion(this)"><strong>πŸ”¬ Research & Report</strong>Research quantum computing advances in 2025 and write a detailed report</div>
605
+ <div class="suggestion-card" onclick="sendSuggestion(this)"><strong>πŸ’» Code & Debug</strong>Write a Python web scraper that extracts product prices from a website</div>
606
+ <div class="suggestion-card" onclick="sendSuggestion(this)"><strong>πŸ“Š Data Analysis</strong>Analyze the pros and cons of different database architectures for a startup</div>
607
+ <div class="suggestion-card" onclick="sendSuggestion(this)"><strong>πŸ€– AI Architecture</strong>Design a multi-agent system for automated customer support with escalation</div>
608
+ </div>
609
+ </div>`;
610
+ return;
611
+ }
612
+ el.innerHTML = '';
613
+ for (const msg of state.messages) {
614
+ appendMessageToDOM(msg);
615
+ }
616
+ scrollToBottom();
617
+ }
618
+
619
+ function appendMessageToDOM(msg) {
620
+ const w = document.getElementById('messages');
621
+ const welcome = document.getElementById('welcome');
622
+ if (welcome) welcome.style.display = 'none';
623
+
624
+ const row = document.createElement('div');
625
+ row.className = `msg-row ${msg.role}`;
626
+ row.dataset.msgId = msg.id || '';
627
+
628
+ const isUser = msg.role === 'user';
629
+ const avatarChar = isUser ? 'πŸ‘€' : 'πŸ€–';
630
+ const avatarClass = isUser ? 'user-av' : 'ai-av';
631
+
632
+ let stepsHTML = '';
633
+ if (msg.steps && msg.steps.length > 0) {
634
+ stepsHTML = buildStepsHTML(msg.steps, msg.id);
635
+ }
636
+
637
+ let bubbleContent = '';
638
+ if (isUser) {
639
+ bubbleContent = escHtml(msg.content).replace(/\n/g, '<br>');
640
+ } else {
641
+ bubbleContent = renderMarkdown(msg.content || '');
642
+ }
643
+
644
+ row.innerHTML = `
645
+ <div class="avatar ${avatarClass}">${avatarChar}</div>
646
+ <div class="msg-content">
647
+ ${stepsHTML}
648
+ <div class="msg-bubble">${bubbleContent}</div>
649
+ <div class="msg-meta">
650
+ <span>${formatTime(msg.ts)}</span>
651
+ <button class="msg-copy-btn" onclick="copyMessage(this,'${escAttr(msg.content)}')">πŸ“‹ Copy</button>
652
+ </div>
653
+ </div>
654
+ `;
655
+
656
+ w.appendChild(row);
657
+ addCodeCopyButtons(row);
658
+ hljs.highlightAll();
659
+ scrollToBottom();
660
+ return row;
661
+ }
662
+
663
+ function buildStepsHTML(steps, msgId) {
664
+ const uid = msgId || genId();
665
+ const stepItems = steps.map(s => {
666
+ if (s.type === 'step') {
667
+ return `<div class="step-item"><span>${escHtml(s.text)}</span></div>`;
668
+ } else if (s.type === 'agent_created') {
669
+ return `<div class="agent-card">
670
+ <div class="agent-card-name">πŸ€– ${escHtml(s.name)}</div>
671
+ <div class="agent-card-role">${escHtml(s.role)}</div>
672
+ ${s.tools && s.tools.length ? `<div class="agent-card-tools">${s.tools.map(t=>`<span class="tool-tag">${escHtml(t)}</span>`).join('')}</div>` : ''}
673
+ </div>`;
674
+ } else if (s.type === 'agent_result') {
675
+ return `<div class="agent-result">
676
+ <div class="agent-result-name">βœ… ${escHtml(s.name)} completed</div>
677
+ <div>${escHtml(s.preview || '')}</div>
678
+ </div>`;
679
+ }
680
+ return '';
681
+ }).join('');
682
+
683
+ return `<div class="agent-steps">
684
+ <button class="steps-toggle" onclick="toggleSteps(this,'steps-${uid}')">
685
+ πŸ”§ Agent Steps (${steps.filter(s=>s.type==='step').length} steps)
686
+ <span class="chevron">β–Ό</span>
687
+ </button>
688
+ <div class="steps-body" id="steps-${uid}">${stepItems}</div>
689
+ </div>`;
690
+ }
691
+
692
+ function toggleSteps(btn, bodyId) {
693
+ const body = document.getElementById(bodyId);
694
+ if (!body) return;
695
+ const isOpen = body.classList.contains('open');
696
+ body.classList.toggle('open', !isOpen);
697
+ btn.classList.toggle('open', !isOpen);
698
+ }
699
+
700
+ // ── Streaming chat ─────────────────────────────────────────────
701
+ async function sendMessage(overrideText) {
702
+ const input = document.getElementById('message-input');
703
+ const text = overrideText || input.value.trim();
704
+ if (!text || state.isGenerating) return;
705
+
706
+ const apiKey = state.settings.apiKey;
707
+ if (!apiKey) {
708
+ openSettings();
709
+ showToast('⚠️ Please enter your LongCat API key first');
710
+ return;
711
+ }
712
+
713
+ if (!state.currentId) newChat();
714
+ showWelcome(false);
715
+
716
+ // Add user message
717
+ const userMsg = { id: genId(), role: 'user', content: text, ts: Date.now() };
718
+ state.messages.push(userMsg);
719
+ appendMessageToDOM(userMsg);
720
+ input.value = '';
721
+ autoResize(input);
722
+ updateCurrentConv();
723
+
724
+ // Add assistant placeholder
725
+ const assistantMsg = { id: genId(), role: 'assistant', content: '', steps: [], ts: Date.now() };
726
+ state.messages.push(assistantMsg);
727
+
728
+ // Show typing indicator
729
+ const typingRow = addTypingIndicator();
730
+
731
+ state.isGenerating = true;
732
+ state.abortController = new AbortController();
733
+ document.getElementById('send-btn').disabled = true;
734
+ document.getElementById('stop-btn').style.display = 'flex';
735
+
736
+ try {
737
+ const body = {
738
+ messages: state.messages.slice(0, -1).map(m => ({ role: m.role, content: m.content }))
739
+ .concat([{ role: 'user', content: text }]),
740
+ api_key: apiKey,
741
+ model: state.settings.model || 'LongCat-Flash-Lite',
742
+ temperature: state.settings.temperature || 0.7,
743
+ };
744
+
745
+ const resp = await fetch('/api/chat', {
746
+ method: 'POST',
747
+ headers: { 'Content-Type': 'application/json' },
748
+ body: JSON.stringify(body),
749
+ signal: state.abortController.signal,
750
+ });
751
+
752
+ if (!resp.ok) {
753
+ const err = await resp.json().catch(() => ({ detail: resp.statusText }));
754
+ throw new Error(err.detail || 'Request failed');
755
+ }
756
+
757
+ const reader = resp.body.getReader();
758
+ const decoder = new TextDecoder();
759
+ let buffer = '';
760
+ let responseStarted = false;
761
+ let msgEl = null;
762
+
763
+ while (true) {
764
+ const { value, done } = await reader.read();
765
+ if (done) break;
766
+ buffer += decoder.decode(value, { stream: true });
767
+ const lines = buffer.split('\n');
768
+ buffer = lines.pop();
769
+
770
+ for (const line of lines) {
771
+ if (!line.startsWith('data: ')) continue;
772
+ const raw = line.slice(6).trim();
773
+ if (!raw) continue;
774
+ let event;
775
+ try { event = JSON.parse(raw); } catch { continue; }
776
+
777
+ if (event.type === 'step') {
778
+ assistantMsg.steps.push({ type: 'step', text: event.text });
779
+ } else if (event.type === 'agent_created') {
780
+ assistantMsg.steps.push({ type: 'agent_created', name: event.name, role: event.role, tools: event.tools || [] });
781
+ } else if (event.type === 'agent_result') {
782
+ assistantMsg.steps.push({ type: 'agent_result', name: event.name, preview: event.preview });
783
+ } else if (event.type === 'response_start') {
784
+ typingRow?.remove();
785
+ responseStarted = true;
786
+ msgEl = appendMessageToDOM({ ...assistantMsg, content: '' });
787
+ } else if (event.type === 'token' && responseStarted) {
788
+ assistantMsg.content += event.content;
789
+ updateAssistantBubble(msgEl, assistantMsg.content);
790
+ } else if (event.type === 'done') {
791
+ if (!responseStarted) {
792
+ typingRow?.remove();
793
+ msgEl = appendMessageToDOM(assistantMsg);
794
+ }
795
+ // Rebuild with final steps
796
+ if (msgEl && assistantMsg.steps.length > 0) {
797
+ rebuildMessageWithSteps(msgEl, assistantMsg);
798
+ }
799
+ } else if (event.type === 'error') {
800
+ typingRow?.remove();
801
+ assistantMsg.content = `❌ Error: ${event.message}`;
802
+ if (!responseStarted) appendMessageToDOM(assistantMsg);
803
+ else updateAssistantBubble(msgEl, assistantMsg.content);
804
+ }
805
+ }
806
+ }
807
+ } catch (err) {
808
+ typingRow?.remove();
809
+ if (err.name !== 'AbortError') {
810
+ assistantMsg.content = `❌ ${err.message}`;
811
+ appendMessageToDOM(assistantMsg);
812
+ }
813
+ } finally {
814
+ state.isGenerating = false;
815
+ state.abortController = null;
816
+ document.getElementById('send-btn').disabled = false;
817
+ document.getElementById('stop-btn').style.display = 'none';
818
+ updateCurrentConv();
819
+ scrollToBottom();
820
+ }
821
+ }
822
+
823
+ function updateAssistantBubble(msgRow, content) {
824
+ if (!msgRow) return;
825
+ const bubble = msgRow.querySelector('.msg-bubble');
826
+ if (!bubble) return;
827
+ bubble.innerHTML = renderMarkdown(content);
828
+ addCodeCopyButtons(msgRow);
829
+ hljs.highlightAll();
830
+ scrollToBottom();
831
+ }
832
+
833
+ function rebuildMessageWithSteps(msgRow, msg) {
834
+ if (!msgRow || !msg.steps.length) return;
835
+ const content = msgRow.querySelector('.msg-content');
836
+ if (!content) return;
837
+ const stepsDiv = content.querySelector('.agent-steps');
838
+ if (stepsDiv) stepsDiv.remove();
839
+ const newSteps = document.createElement('div');
840
+ newSteps.innerHTML = buildStepsHTML(msg.steps, msg.id);
841
+ content.insertBefore(newSteps.firstChild, content.firstChild);
842
+ }
843
+
844
+ function addTypingIndicator() {
845
+ const w = document.getElementById('messages');
846
+ const row = document.createElement('div');
847
+ row.className = 'msg-row assistant';
848
+ row.id = 'typing-indicator';
849
+ row.innerHTML = `<div class="avatar ai-av">πŸ€–</div><div class="msg-content"><div class="msg-bubble"><div class="typing-dots"><span></span><span></span><span></span></div></div></div>`;
850
+ w.appendChild(row);
851
+ scrollToBottom();
852
+ return row;
853
+ }
854
+
855
+ function stopGeneration() {
856
+ if (state.abortController) { state.abortController.abort(); }
857
+ }
858
+
859
+ // ── Helpers ───────────────────────────────────────────────────
860
+ function renderMarkdown(text) {
861
+ if (!text) return '';
862
+ marked.setOptions({ breaks: true, gfm: true });
863
+ let html = marked.parse(text);
864
+ // Wrap code blocks with copy button
865
+ html = html.replace(/<pre><code(.*?)>([\s\S]*?)<\/code><\/pre>/g, (match, attrs, code) => {
866
+ const lang = (attrs.match(/class="language-(\w+)"/) || [])[1] || '';
867
+ return `<div class="code-block-wrapper"><div class="code-header"><span>${lang || 'code'}</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><pre><code${attrs}>${code}</code></pre></div>`;
868
+ });
869
+ return html;
870
+ }
871
+
872
+ function addCodeCopyButtons(container) {
873
+ container.querySelectorAll('pre code').forEach(block => {
874
+ if (!block.closest('.code-block-wrapper')) {
875
+ const wrapper = document.createElement('div');
876
+ wrapper.className = 'code-block-wrapper';
877
+ const header = document.createElement('div');
878
+ header.className = 'code-header';
879
+ header.innerHTML = '<span>code</span><button class="copy-btn" onclick="copyCode(this)">Copy</button>';
880
+ const pre = block.parentElement;
881
+ pre.parentNode.insertBefore(wrapper, pre);
882
+ wrapper.appendChild(header);
883
+ wrapper.appendChild(pre);
884
+ }
885
+ });
886
+ }
887
+
888
+ function copyCode(btn) {
889
+ const code = btn.closest('.code-block-wrapper').querySelector('code').innerText;
890
+ navigator.clipboard.writeText(code).then(() => { btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = 'Copy', 2000); });
891
+ }
892
+
893
+ function copyMessage(btn, text) {
894
+ navigator.clipboard.writeText(text).then(() => { btn.textContent = 'βœ… Copied'; setTimeout(() => btn.textContent = 'πŸ“‹ Copy', 2000); });
895
+ }
896
+
897
+ function escHtml(str) { return String(str || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
898
+ function escAttr(str) { return String(str || '').replace(/'/g,'&#39;').replace(/"/g,'&quot;'); }
899
+ function formatTime(ts) { return ts ? new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''; }
900
+ function scrollToBottom() { const w = document.getElementById('messages-wrap'); w.scrollTop = w.scrollHeight; }
901
+
902
+ function handleKey(e) {
903
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
904
+ }
905
+
906
+ function autoResize(el) {
907
+ el.style.height = 'auto';
908
+ el.style.height = Math.min(el.scrollHeight, 180) + 'px';
909
+ }
910
+
911
+ function sendSuggestion(card) {
912
+ const text = card.querySelector('strong').nextSibling?.textContent?.trim() || card.textContent.trim();
913
+ sendMessage(text);
914
+ }
915
+
916
+ // ── Model ─────────────────────────────────────────────────────
917
+ function toggleModelDropdown() {
918
+ document.getElementById('model-dropdown').classList.toggle('open');
919
+ }
920
+
921
+ function selectModel(id, name) {
922
+ state.settings.model = id;
923
+ document.getElementById('current-model-name').textContent = name;
924
+ document.getElementById('model-dropdown').classList.remove('open');
925
+ document.querySelectorAll('.model-option').forEach(o => o.classList.remove('active'));
926
+ event.currentTarget.classList.add('active');
927
+ saveSettingsToStorage();
928
+ showToast(`Switched to ${name}`);
929
+ }
930
+
931
+ document.addEventListener('click', (e) => {
932
+ if (!e.target.closest('#model-badge') && !e.target.closest('#model-dropdown')) {
933
+ document.getElementById('model-dropdown')?.classList.remove('open');
934
+ }
935
+ });
936
+
937
+ // ── Settings ─────────────────────────────────────────────────
938
+ function openSettings() {
939
+ document.getElementById('api-key-input').value = state.settings.apiKey || '';
940
+ document.getElementById('temperature-input').value = state.settings.temperature || 0.7;
941
+ document.getElementById('temp-val').textContent = state.settings.temperature || 0.7;
942
+ document.getElementById('system-prompt-input').value = state.settings.systemPrompt || '';
943
+ document.getElementById('settings-modal').classList.add('open');
944
+ }
945
+
946
+ function closeSettings() { document.getElementById('settings-modal').classList.remove('open'); }
947
+ function closeSettingsOutside(e) { if (e.target === document.getElementById('settings-modal')) closeSettings(); }
948
+
949
+ function saveSettings() {
950
+ state.settings.apiKey = document.getElementById('api-key-input').value.trim();
951
+ state.settings.temperature = parseFloat(document.getElementById('temperature-input').value);
952
+ state.settings.systemPrompt = document.getElementById('system-prompt-input').value.trim();
953
+ saveSettingsToStorage();
954
+ closeSettings();
955
+ showToast('βœ… Settings saved');
956
+ }
957
+
958
+ function saveSettingsToStorage() {
959
+ localStorage.setItem('praison_settings', JSON.stringify(state.settings));
960
+ }
961
+
962
+ function loadSettings() {
963
+ const s = state.settings;
964
+ if (s.model) {
965
+ const modelNames = { 'LongCat-Flash-Lite': 'LongCat Flash Lite', 'LongCat-Flash-Chat': 'LongCat Flash Chat', 'LongCat-Flash-Thinking-2601': 'LongCat Flash Thinking' };
966
+ document.getElementById('current-model-name').textContent = modelNames[s.model] || s.model;
967
+ }
968
+ }
969
+
970
+ // ── Theme ─────────────────────────────────────────────────────
971
+ function toggleTheme() {
972
+ const curr = document.documentElement.getAttribute('data-theme');
973
+ document.documentElement.setAttribute('data-theme', curr === 'dark' ? 'light' : 'dark');
974
+ localStorage.setItem('praison_theme', curr === 'dark' ? 'light' : 'dark');
975
+ }
976
+
977
+ // ── Sidebar ───────────────────────────────────────────────────
978
+ function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); }
979
+ function closeSidebar() { document.getElementById('sidebar').classList.remove('open'); }
980
+
981
+ // ── Toast ────────────────────────────────────────────────────
982
+ function showToast(msg) {
983
+ const t = document.getElementById('toast');
984
+ t.textContent = msg;
985
+ t.classList.add('show');
986
+ setTimeout(() => t.classList.remove('show'), 2500);
987
+ }
988
+
989
+ // ── Bootstrap ────────────────────────────────────────────────
990
+ const savedTheme = localStorage.getItem('praison_theme');
991
+ if (savedTheme) document.documentElement.setAttribute('data-theme', savedTheme);
992
+
993
+ init();
994
+
995
+ if (!state.settings.apiKey) {
996
+ setTimeout(() => { openSettings(); }, 600);
997
+ }
998
+ </script>
999
+ </body>
1000
+ </html>