alemonjs-aichat 1.0.39 → 1.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2200 @@
1
+ import React from 'react';
2
+ import { panelGroupSwitchDefinitions } from '../../panel/groupConfig.js';
3
+
4
+ const configValue = `app:
5
+ - alemonjs-aichat
6
+
7
+ master_id:
8
+ - "3501869534"
9
+
10
+ ai:
11
+ current: OpenAI
12
+ capiContextLimit: 256k
13
+
14
+ features:
15
+ chat: true
16
+ tools: true
17
+ onlyAt: false
18
+ toolCallContent: false
19
+ affection: true
20
+ tts: false
21
+ `;
22
+ const countEnabledGroupSwitches = (group) => panelGroupSwitchDefinitions.filter((item) => group.switches[item.key]).length;
23
+ const createRedisPreview = (items, currentAI, redisError) => {
24
+ if (redisError) {
25
+ return `# AI列表来自 Redis, 但当前读取失败
26
+ # ${redisError}
27
+ keys ai:list:*
28
+
29
+ ai:currentAI:{guid} = 当前会话选中的 AI 名称
30
+ ai:config:{guid} = 当前会话实际使用的 AIConfig
31
+ `;
32
+ }
33
+ const listPreview = items.length
34
+ ? items
35
+ .map((item) => `ai:list:${item.name} = {
36
+ "host": "${item.host}",
37
+ "key": "${item.key}",
38
+ "model": "${item.model}",
39
+ "temperature": 0.7,
40
+ "max_tokens": 4096,
41
+ "systemPrompt": "",
42
+ "rapi": ${item.rapi}
43
+ }`)
44
+ .join("\n\n")
45
+ : "# 当前 Redis 中没有 ai:list:*";
46
+ return `# AI列表来自 Redis, 不是配置文件
47
+ keys ai:list:*
48
+
49
+ ${listPreview}
50
+
51
+ ai:currentAI:{guid} = "${currentAI || "按会话记录"}"
52
+ ai:config:{guid} = 当前会话实际使用的 AIConfig
53
+ `;
54
+ };
55
+ const renderNav = (page) => (React.createElement("nav", { className: "nav", "aria-label": "\u9762\u677F\u5BFC\u822A" },
56
+ React.createElement("a", { className: `nav-link ${page === "config" ? "active" : ""}`, href: "/panel/config" }, "\u914D\u7F6E"),
57
+ React.createElement("a", { className: `nav-link ${page === "chat" ? "active" : ""}`, href: "/panel/chat" }, "\u804A\u5929")));
58
+ const ChatConfigPanel = ({ aiConfigs, currentAI, chatGuid, chatUserId, chatNickname, chatMode, chatModel, groupGuidOptions, workspaceNames, promptOptions, currentPrompt, currentGroupConfig, chatHref, idPrefix, className, defaultOpen = false, }) => {
59
+ const currentAILabel = currentAI || "未选择";
60
+ const currentNicknameLabel = chatNickname || chatUserId;
61
+ const groupOptions = [...new Set([...groupGuidOptions, chatGuid])];
62
+ return (React.createElement("details", { className: className, open: defaultOpen },
63
+ React.createElement("summary", { className: "chat-config-summary" },
64
+ React.createElement("span", { className: "chat-menu-icon", "aria-hidden": "true" },
65
+ React.createElement("span", null),
66
+ React.createElement("span", null)),
67
+ React.createElement("span", { className: "chat-config-summary-main" },
68
+ React.createElement("span", { className: "chat-config-summary-title" }, "\u804A\u5929\u914D\u7F6E"),
69
+ React.createElement("span", { className: "chat-config-summary-desc" },
70
+ currentAILabel,
71
+ " / ",
72
+ chatMode.toUpperCase())),
73
+ React.createElement("span", { className: "chat-config-summary-toggle", "aria-hidden": "true" }, "\u6536\u8D77")),
74
+ React.createElement("div", { className: "chat-config-body" },
75
+ React.createElement("nav", { className: "chat-side-nav", "aria-label": "\u804A\u5929\u9762\u677F\u5BFC\u822A" },
76
+ React.createElement("a", { className: "active", href: chatHref }, "\u804A\u5929"),
77
+ React.createElement("a", { href: "/panel/config" }, "\u914D\u7F6E")),
78
+ React.createElement("section", { className: "chat-config-block" },
79
+ React.createElement("div", { className: "chat-config-block-title" }, "\u5F53\u524D\u4F1A\u8BDD"),
80
+ React.createElement("a", { className: "conversation active", href: chatHref },
81
+ React.createElement("div", { className: "conversation-name" },
82
+ "\u7FA4\u53F7 ",
83
+ chatGuid),
84
+ React.createElement("div", { className: "conversation-last" },
85
+ "\u8D26\u53F7 ",
86
+ chatUserId,
87
+ " / \u6635\u79F0 ",
88
+ currentNicknameLabel)),
89
+ React.createElement("form", { action: "/panel/chat", className: "chat-sidebar-form", method: "get" },
90
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
91
+ React.createElement("div", { className: "chat-info-grid" },
92
+ React.createElement("label", { className: "chat-info-item" },
93
+ React.createElement("span", null, "\u8BBE\u7F6E\u8D26\u53F7"),
94
+ React.createElement("input", { name: "userId", defaultValue: chatUserId, autoComplete: "off" })),
95
+ React.createElement("label", { className: "chat-info-item" },
96
+ React.createElement("span", null, "\u8BBE\u7F6E\u6635\u79F0"),
97
+ React.createElement("input", { name: "nickname", defaultValue: currentNicknameLabel, autoComplete: "off" }))),
98
+ React.createElement("button", { className: "subtle chat-inline-action", type: "submit" }, "\u5E94\u7528"))),
99
+ React.createElement("section", { className: "chat-config-block" },
100
+ React.createElement("div", { className: "chat-config-block-title" }, "\u5F53\u524D\u7FA4\u914D\u7F6E"),
101
+ currentGroupConfig ? (React.createElement(React.Fragment, null,
102
+ React.createElement("div", { className: "chat-info-grid" },
103
+ React.createElement("form", { action: "/panel/chat", className: "chat-info-form", "data-auto-submit": "change", method: "get" },
104
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
105
+ React.createElement("input", { type: "hidden", name: "nickname", value: currentNicknameLabel }),
106
+ React.createElement("label", { className: "chat-info-item" },
107
+ React.createElement("span", null, "\u7FA4\u53F7"),
108
+ React.createElement("select", { name: "guid", defaultValue: currentGroupConfig.guid }, groupOptions.map((item) => (React.createElement("option", { key: `${idPrefix}-group-guid-${item}`, value: item }, item)))))),
109
+ React.createElement("form", { action: "/panel/action/chat/use-ai", className: "chat-info-form", "data-auto-submit": "change", method: "post" },
110
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
111
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
112
+ React.createElement("input", { type: "hidden", name: "nickname", value: currentNicknameLabel }),
113
+ React.createElement("label", { className: "chat-info-item" },
114
+ React.createElement("span", null, "\u5F53\u524DAI"),
115
+ React.createElement("select", { name: "name", defaultValue: currentGroupConfig.currentAI }, aiConfigs.length ? (aiConfigs.map((item) => (React.createElement("option", { key: `${idPrefix}-group-ai-${item.name}`, value: item.name }, item.name)))) : (React.createElement("option", { value: "" }, "\u6682\u65E0AI\u914D\u7F6E"))))),
116
+ React.createElement("form", { action: "/panel/action/chat/switch-model", className: "chat-info-form", "data-auto-submit": "blur", method: "post" },
117
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
118
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
119
+ React.createElement("input", { type: "hidden", name: "nickname", value: currentNicknameLabel }),
120
+ React.createElement("label", { className: "chat-info-item full" },
121
+ React.createElement("span", null, "\u5F53\u524D\u6A21\u578B"),
122
+ React.createElement("input", { name: "model", defaultValue: currentGroupConfig.model || chatModel, autoComplete: "off" }))),
123
+ React.createElement("form", { action: "/panel/action/chat/switch-context-limit", className: "chat-info-form", "data-auto-submit": "blur", method: "post" },
124
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
125
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
126
+ React.createElement("input", { type: "hidden", name: "nickname", value: currentNicknameLabel }),
127
+ React.createElement("label", { className: "chat-info-item full" },
128
+ React.createElement("span", null, "CAPI\u4E0A\u4E0B\u6587\u957F\u5EA6"),
129
+ React.createElement("input", { name: "capiContextLimit", type: "number", inputMode: "numeric", defaultValue: String(currentGroupConfig.capiContextLimit) }))),
130
+ React.createElement("form", { action: "/panel/action/chat/switch-prompt", className: "chat-info-form", "data-auto-submit": "change", method: "post" },
131
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
132
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
133
+ React.createElement("input", { type: "hidden", name: "nickname", value: currentNicknameLabel }),
134
+ React.createElement("label", { className: "chat-info-item full" },
135
+ React.createElement("span", null, "\u63D0\u793A\u8BCD"),
136
+ React.createElement("select", { name: "prompt", defaultValue: currentPrompt },
137
+ React.createElement("option", { value: "" }, "\u9ED8\u8BA4\u63D0\u793A\u8BCD"),
138
+ promptOptions.map((item) => (React.createElement("option", { key: `${idPrefix}-group-prompt-${item.name}`, value: item.name }, item.name))))))),
139
+ React.createElement("div", { className: "chat-switch-status-list" }, panelGroupSwitchDefinitions.map((item) => {
140
+ const enabled = currentGroupConfig.switches[item.key];
141
+ return (React.createElement("form", { action: "/panel/action/update-group-config", key: `${idPrefix}-group-switch-${item.key}`, method: "post" },
142
+ React.createElement("input", { type: "hidden", name: "source", value: "chat" }),
143
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
144
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
145
+ React.createElement("input", { type: "hidden", name: "nickname", value: currentNicknameLabel }),
146
+ React.createElement("input", { type: "hidden", name: "currentAI", value: currentGroupConfig.currentAI }),
147
+ React.createElement("input", { type: "hidden", name: "capiContextLimit", value: String(currentGroupConfig.capiContextLimit) }),
148
+ panelGroupSwitchDefinitions.map((stateItem) => {
149
+ const nextEnabled = stateItem.key === item.key
150
+ ? !enabled
151
+ : currentGroupConfig.switches[stateItem.key];
152
+ return nextEnabled ? (React.createElement("input", { key: `${idPrefix}-${item.key}-${stateItem.key}`, type: "hidden", name: stateItem.key, value: "1" })) : null;
153
+ }),
154
+ React.createElement("button", { className: `chat-switch-status ${enabled ? "enabled" : ""}`, type: "submit" },
155
+ React.createElement("span", null, item.label),
156
+ React.createElement("strong", null, enabled ? "开" : "关"))));
157
+ })))) : (React.createElement("div", { className: "empty-state" }, "\u5F53\u524D guid \u6682\u65E0\u7FA4\u914D\u7F6E\u3002"))),
158
+ React.createElement("section", { className: "chat-config-block" },
159
+ React.createElement("div", { className: "chat-config-block-title" }, "\u5DE5\u4F5C\u533A\u76EE\u5F55"),
160
+ workspaceNames.length ? (React.createElement("div", { className: "chat-directory-list" }, workspaceNames.map((item) => (React.createElement("div", { className: "chat-directory-item", key: `${idPrefix}-workspace-${item}` },
161
+ React.createElement("div", { className: "conversation-name" }, item),
162
+ React.createElement("div", { className: "conversation-last" },
163
+ "public/",
164
+ item)))))) : (React.createElement("div", { className: "empty-state" }, "\u5F53\u524D\u8FD8\u6CA1\u6709\u521B\u5EFA\u8FC7\u5DE5\u4F5C\u533A\u76EE\u5F55\u3002"))))));
165
+ };
166
+ const AppStyle = () => (React.createElement("style", null, `
167
+ * {
168
+ box-sizing: border-box;
169
+ }
170
+
171
+ :root {
172
+ color-scheme: light;
173
+ --bg: #eef3f8;
174
+ --surface: #ffffff;
175
+ --surface-soft: #f8fafc;
176
+ --border: #dbe3eb;
177
+ --text: #17202a;
178
+ --muted: #667485;
179
+ --strong: #0f172a;
180
+ --blue: #2563eb;
181
+ --green: #059669;
182
+ --amber: #b45309;
183
+ --violet: #6d28d9;
184
+ --shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
185
+ }
186
+
187
+ body {
188
+ margin: 0;
189
+ min-height: 100vh;
190
+ font-family: Inter, "Microsoft YaHei", Arial, sans-serif;
191
+ color: var(--text);
192
+ background: linear-gradient(180deg, #f6f9fc 0%, var(--bg) 100%);
193
+ }
194
+
195
+ button,
196
+ input,
197
+ textarea,
198
+ select {
199
+ font: inherit;
200
+ }
201
+
202
+ button {
203
+ height: 34px;
204
+ border: 1px solid var(--border);
205
+ border-radius: 8px;
206
+ padding: 0 12px;
207
+ color: var(--strong);
208
+ background: var(--surface);
209
+ font-weight: 650;
210
+ }
211
+
212
+ button.primary {
213
+ border-color: var(--blue);
214
+ color: #ffffff;
215
+ background: var(--blue);
216
+ }
217
+
218
+ button.subtle {
219
+ background: var(--surface-soft);
220
+ }
221
+
222
+ button.danger {
223
+ color: #b91c1c;
224
+ background: #fff5f5;
225
+ border-color: #fecaca;
226
+ }
227
+
228
+ .notice {
229
+ margin: 0 18px 14px;
230
+ border: 1px solid #bbf7d0;
231
+ border-radius: 8px;
232
+ padding: 10px 12px;
233
+ color: #166534;
234
+ background: #f0fdf4;
235
+ font-size: 13px;
236
+ font-weight: 700;
237
+ transition:
238
+ opacity 0.18s ease,
239
+ transform 0.18s ease;
240
+ }
241
+
242
+ .notice.is-hiding {
243
+ opacity: 0;
244
+ pointer-events: none;
245
+ }
246
+
247
+ .notice.error {
248
+ border-color: #fecaca;
249
+ color: #b91c1c;
250
+ background: #fff5f5;
251
+ }
252
+
253
+ .shell {
254
+ width: min(1180px, calc(100vw - 28px));
255
+ margin: 0 auto;
256
+ padding: 22px 0 28px;
257
+ }
258
+
259
+ .header {
260
+ display: flex;
261
+ align-items: center;
262
+ justify-content: space-between;
263
+ gap: 16px;
264
+ margin-bottom: 16px;
265
+ }
266
+
267
+ .brand {
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 12px;
271
+ min-width: 0;
272
+ }
273
+
274
+ .logo {
275
+ width: 38px;
276
+ height: 38px;
277
+ border-radius: 8px;
278
+ display: grid;
279
+ place-items: center;
280
+ color: #ffffff;
281
+ background: #111827;
282
+ font-weight: 800;
283
+ }
284
+
285
+ h1 {
286
+ margin: 0;
287
+ font-size: 20px;
288
+ line-height: 1.2;
289
+ letter-spacing: 0;
290
+ }
291
+
292
+ .meta {
293
+ margin-top: 3px;
294
+ color: var(--muted);
295
+ font-size: 13px;
296
+ }
297
+
298
+ .nav {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: 6px;
302
+ border: 1px solid var(--border);
303
+ border-radius: 8px;
304
+ padding: 4px;
305
+ background: rgba(255, 255, 255, 0.72);
306
+ }
307
+
308
+ .nav-link {
309
+ min-width: 78px;
310
+ border-radius: 6px;
311
+ padding: 8px 14px;
312
+ color: var(--muted);
313
+ text-align: center;
314
+ text-decoration: none;
315
+ font-size: 14px;
316
+ font-weight: 700;
317
+ }
318
+
319
+ .nav-link.active {
320
+ color: #ffffff;
321
+ background: #111827;
322
+ }
323
+
324
+ .stats {
325
+ display: grid;
326
+ grid-template-columns: repeat(4, minmax(0, 1fr));
327
+ gap: 10px;
328
+ margin-bottom: 12px;
329
+ }
330
+
331
+ .stat {
332
+ border: 1px solid var(--border);
333
+ border-radius: 8px;
334
+ padding: 12px;
335
+ background: var(--surface);
336
+ box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04);
337
+ }
338
+
339
+ .stat-label {
340
+ color: var(--muted);
341
+ font-size: 12px;
342
+ }
343
+
344
+ .stat-value {
345
+ margin-top: 5px;
346
+ font-size: 16px;
347
+ font-weight: 800;
348
+ }
349
+
350
+ .tone-green {
351
+ color: var(--green);
352
+ }
353
+
354
+ .tone-blue {
355
+ color: var(--blue);
356
+ }
357
+
358
+ .tone-amber {
359
+ color: var(--amber);
360
+ }
361
+
362
+ .tone-violet {
363
+ color: var(--violet);
364
+ }
365
+
366
+ .panel {
367
+ border: 1px solid var(--border);
368
+ border-radius: 8px;
369
+ background: var(--surface);
370
+ box-shadow: var(--shadow);
371
+ overflow: hidden;
372
+ }
373
+
374
+ .panel-head {
375
+ display: flex;
376
+ align-items: center;
377
+ justify-content: space-between;
378
+ gap: 12px;
379
+ min-height: 52px;
380
+ border-bottom: 1px solid var(--border);
381
+ padding: 0 16px;
382
+ background: var(--surface-soft);
383
+ }
384
+
385
+ .panel-title {
386
+ font-size: 15px;
387
+ font-weight: 800;
388
+ white-space: nowrap;
389
+ }
390
+
391
+ .toolbar {
392
+ display: flex;
393
+ align-items: center;
394
+ flex-wrap: wrap;
395
+ gap: 8px;
396
+ }
397
+
398
+ .config-layout {
399
+ display: grid;
400
+ grid-template-columns: minmax(320px, 0.9fr) minmax(0, 1.1fr);
401
+ gap: 0;
402
+ }
403
+
404
+ .section {
405
+ padding: 16px;
406
+ }
407
+
408
+ .section + .section {
409
+ border-left: 1px solid var(--border);
410
+ }
411
+
412
+ .section-title {
413
+ margin: 0 0 10px;
414
+ color: var(--strong);
415
+ font-size: 14px;
416
+ font-weight: 800;
417
+ }
418
+
419
+ .switch-list {
420
+ display: grid;
421
+ gap: 8px;
422
+ }
423
+
424
+ .group-list {
425
+ display: grid;
426
+ gap: 10px;
427
+ }
428
+
429
+ .group-add-card {
430
+ display: grid;
431
+ gap: 12px;
432
+ margin: 12px 0;
433
+ border: 1px solid var(--border);
434
+ border-radius: 8px;
435
+ padding: 12px;
436
+ background: var(--surface-soft);
437
+ }
438
+
439
+ .group-add-actions {
440
+ display: flex;
441
+ align-items: center;
442
+ justify-content: space-between;
443
+ gap: 12px;
444
+ flex-wrap: wrap;
445
+ }
446
+
447
+ .group-add-toggle {
448
+ display: inline-flex;
449
+ align-items: center;
450
+ gap: 10px;
451
+ color: var(--muted);
452
+ font-size: 12px;
453
+ font-weight: 700;
454
+ }
455
+
456
+ .group-row {
457
+ border: 1px solid var(--border);
458
+ border-radius: 8px;
459
+ background: var(--surface);
460
+ overflow: hidden;
461
+ }
462
+
463
+ .group-summary {
464
+ display: flex;
465
+ align-items: center;
466
+ justify-content: space-between;
467
+ gap: 10px;
468
+ padding: 12px;
469
+ cursor: pointer;
470
+ list-style: none;
471
+ background: var(--surface-soft);
472
+ }
473
+
474
+ .group-summary::-webkit-details-marker {
475
+ display: none;
476
+ }
477
+
478
+ .group-body {
479
+ display: grid;
480
+ gap: 12px;
481
+ padding: 0 12px 12px;
482
+ }
483
+
484
+ details.group-row:not([open]) > .group-body {
485
+ display: none;
486
+ }
487
+
488
+ .group-fields {
489
+ display: grid;
490
+ grid-template-columns: repeat(3, minmax(0, 1fr));
491
+ gap: 10px;
492
+ }
493
+
494
+ .group-form-actions {
495
+ display: flex;
496
+ justify-content: flex-end;
497
+ gap: 8px;
498
+ }
499
+
500
+ .switch-row {
501
+ display: grid;
502
+ grid-template-columns: 1fr auto;
503
+ align-items: center;
504
+ gap: 12px;
505
+ border: 1px solid var(--border);
506
+ border-radius: 8px;
507
+ padding: 12px;
508
+ background: var(--surface);
509
+ }
510
+
511
+ .switch-label {
512
+ font-weight: 750;
513
+ }
514
+
515
+ .switch-desc {
516
+ margin-top: 3px;
517
+ color: var(--muted);
518
+ font-size: 12px;
519
+ }
520
+
521
+ .switch {
522
+ position: relative;
523
+ width: 42px;
524
+ height: 24px;
525
+ flex: 0 0 auto;
526
+ }
527
+
528
+ .switch input {
529
+ position: absolute;
530
+ inset: 0;
531
+ opacity: 0;
532
+ cursor: pointer;
533
+ }
534
+
535
+ .track {
536
+ position: absolute;
537
+ inset: 0;
538
+ border-radius: 999px;
539
+ background: #cbd5e1;
540
+ transition: background 0.18s ease;
541
+ }
542
+
543
+ .track::after {
544
+ content: "";
545
+ position: absolute;
546
+ top: 3px;
547
+ left: 3px;
548
+ width: 18px;
549
+ height: 18px;
550
+ border-radius: 999px;
551
+ background: #ffffff;
552
+ box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18);
553
+ transition: transform 0.18s ease;
554
+ }
555
+
556
+ .switch input:checked + .track {
557
+ background: var(--blue);
558
+ }
559
+
560
+ .switch input:checked + .track::after {
561
+ transform: translateX(18px);
562
+ }
563
+
564
+ .field-grid {
565
+ display: grid;
566
+ grid-template-columns: 1fr 1fr;
567
+ gap: 10px;
568
+ margin-bottom: 12px;
569
+ }
570
+
571
+ .field {
572
+ display: grid;
573
+ gap: 5px;
574
+ }
575
+
576
+ .field label {
577
+ color: var(--muted);
578
+ font-size: 12px;
579
+ font-weight: 700;
580
+ }
581
+
582
+ .field input,
583
+ .field select {
584
+ width: 100%;
585
+ height: 34px;
586
+ border: 1px solid var(--border);
587
+ border-radius: 8px;
588
+ padding: 0 10px;
589
+ color: var(--strong);
590
+ background: #ffffff;
591
+ }
592
+
593
+ .group-manager,
594
+ .ai-manager {
595
+ display: grid;
596
+ gap: 10px;
597
+ margin-bottom: 14px;
598
+ }
599
+
600
+ .group-manager-summary,
601
+ .ai-manager-summary,
602
+ .ai-row-summary {
603
+ display: flex;
604
+ align-items: center;
605
+ justify-content: space-between;
606
+ gap: 10px;
607
+ cursor: pointer;
608
+ list-style: none;
609
+ }
610
+
611
+ .group-manager-summary::-webkit-details-marker,
612
+ .ai-manager-summary::-webkit-details-marker,
613
+ .ai-row-summary::-webkit-details-marker {
614
+ display: none;
615
+ }
616
+
617
+ .group-manager-summary,
618
+ .ai-manager-summary {
619
+ border: 1px solid var(--border);
620
+ border-radius: 8px;
621
+ padding: 12px;
622
+ background: var(--surface-soft);
623
+ }
624
+
625
+ details.group-manager:not([open]) > .source-note,
626
+ details.group-manager:not([open]) > .group-add-card,
627
+ details.group-manager:not([open]) > .group-list,
628
+ details.ai-manager:not([open]) > .source-note,
629
+ details.ai-manager:not([open]) > .error-note,
630
+ details.ai-manager:not([open]) > .ai-manager-actions,
631
+ details.ai-manager:not([open]) > .ai-list {
632
+ display: none;
633
+ }
634
+
635
+ .summary-title {
636
+ display: grid;
637
+ gap: 3px;
638
+ min-width: 0;
639
+ }
640
+
641
+ .summary-main {
642
+ color: var(--strong);
643
+ font-weight: 850;
644
+ }
645
+
646
+ .summary-meta {
647
+ color: var(--muted);
648
+ font-size: 12px;
649
+ overflow: hidden;
650
+ text-overflow: ellipsis;
651
+ white-space: nowrap;
652
+ }
653
+
654
+ .summary-caret {
655
+ display: grid;
656
+ place-items: center;
657
+ width: 28px;
658
+ height: 28px;
659
+ flex: 0 0 28px;
660
+ border: 1px solid var(--border);
661
+ border-radius: 8px;
662
+ color: var(--muted);
663
+ background: #ffffff;
664
+ transition: transform 0.16s ease;
665
+ }
666
+
667
+ details[open] > .group-manager-summary .summary-caret,
668
+ details[open] > .ai-manager-summary .summary-caret,
669
+ details[open] > .ai-row-summary .summary-caret {
670
+ transform: rotate(90deg);
671
+ }
672
+
673
+ .source-note {
674
+ border: 1px solid #bfdbfe;
675
+ border-radius: 8px;
676
+ padding: 10px 12px;
677
+ color: #1e40af;
678
+ background: #eff6ff;
679
+ font-size: 13px;
680
+ line-height: 1.6;
681
+ }
682
+
683
+ .error-note,
684
+ .empty-state {
685
+ border: 1px solid #fed7aa;
686
+ border-radius: 8px;
687
+ padding: 12px;
688
+ color: #9a3412;
689
+ background: #fff7ed;
690
+ font-size: 13px;
691
+ line-height: 1.6;
692
+ }
693
+
694
+ .ai-list {
695
+ display: grid;
696
+ gap: 8px;
697
+ }
698
+
699
+ .ai-row {
700
+ border: 1px solid var(--border);
701
+ border-radius: 8px;
702
+ background: var(--surface-soft);
703
+ overflow: hidden;
704
+ }
705
+
706
+ .ai-row-summary {
707
+ padding: 12px;
708
+ background: #ffffff;
709
+ }
710
+
711
+ .ai-row-body {
712
+ display: grid;
713
+ gap: 10px;
714
+ padding: 0 12px 12px;
715
+ }
716
+
717
+ details.ai-row:not([open]) > .ai-row-body {
718
+ display: none;
719
+ }
720
+
721
+ .ai-row-title {
722
+ font-weight: 850;
723
+ }
724
+
725
+ .ai-row-actions {
726
+ display: flex;
727
+ align-items: center;
728
+ gap: 8px;
729
+ flex-wrap: wrap;
730
+ }
731
+
732
+ .ai-row-actions form {
733
+ margin: 0;
734
+ }
735
+
736
+ .ai-fields {
737
+ display: grid;
738
+ grid-template-columns: minmax(120px, 0.8fr) minmax(180px, 1.4fr) minmax(140px, 1fr) minmax(150px, 1fr) minmax(92px, 0.5fr);
739
+ gap: 10px;
740
+ }
741
+
742
+ .editor {
743
+ width: 100%;
744
+ min-height: 220px;
745
+ border: 1px solid var(--border);
746
+ border-radius: 8px;
747
+ padding: 12px;
748
+ color: #dbeafe;
749
+ background: #0f172a;
750
+ line-height: 1.55;
751
+ resize: vertical;
752
+ font-family: "Cascadia Code", Consolas, monospace;
753
+ font-size: 13px;
754
+ }
755
+
756
+ .chat-shell {
757
+ width: 100%;
758
+ min-height: 100vh;
759
+ padding: 0;
760
+ }
761
+
762
+ .chat-page-header {
763
+ display: none;
764
+ }
765
+
766
+ .chat-panel {
767
+ min-height: 100vh;
768
+ border: 0;
769
+ border-radius: 0;
770
+ background: #ffffff;
771
+ box-shadow: none;
772
+ overflow: hidden;
773
+ }
774
+
775
+ .chat-panel .notice {
776
+ position: fixed;
777
+ top: 14px;
778
+ left: 50%;
779
+ z-index: 50;
780
+ width: min(520px, calc(100vw - 28px));
781
+ margin: 0;
782
+ transform: translateX(-50%);
783
+ box-shadow: 0 14px 34px rgba(15, 23, 42, 0.12);
784
+ }
785
+
786
+ .chat-panel .notice.is-hiding {
787
+ transform: translate(-50%, -8px);
788
+ }
789
+
790
+ .chat-layout {
791
+ display: grid;
792
+ grid-template-columns: auto minmax(0, 1fr);
793
+ height: 100vh;
794
+ min-height: 0;
795
+ background: #ffffff;
796
+ }
797
+
798
+ .chat-config-panel {
799
+ width: 336px;
800
+ min-height: 0;
801
+ height: 100vh;
802
+ border-right: 1px solid #ececf1;
803
+ background: #f7f7f8;
804
+ overflow: hidden;
805
+ transition: width 0.18s ease;
806
+ }
807
+
808
+ .chat-config-panel:not([open]) {
809
+ width: 64px;
810
+ }
811
+
812
+ .chat-config-panel-mobile {
813
+ display: none;
814
+ }
815
+
816
+ .chat-config-summary {
817
+ display: flex;
818
+ align-items: center;
819
+ gap: 12px;
820
+ min-height: 64px;
821
+ padding: 10px 14px;
822
+ cursor: pointer;
823
+ list-style: none;
824
+ color: #202123;
825
+ background: #f7f7f8;
826
+ user-select: none;
827
+ }
828
+
829
+ .chat-config-summary::-webkit-details-marker {
830
+ display: none;
831
+ }
832
+
833
+ .chat-menu-icon {
834
+ display: inline-grid;
835
+ align-content: center;
836
+ justify-items: center;
837
+ gap: 5px;
838
+ flex: 0 0 auto;
839
+ width: 38px;
840
+ height: 38px;
841
+ border-radius: 999px;
842
+ background: #ffffff;
843
+ box-shadow: 0 1px 8px rgba(15, 23, 42, 0.05);
844
+ }
845
+
846
+ .chat-menu-icon span {
847
+ display: block;
848
+ width: 16px;
849
+ height: 2px;
850
+ border-radius: 999px;
851
+ background: #202123;
852
+ }
853
+
854
+ .chat-menu-icon span + span {
855
+ margin-top: 0;
856
+ }
857
+
858
+ .chat-config-summary-main {
859
+ display: grid;
860
+ min-width: 0;
861
+ gap: 3px;
862
+ }
863
+
864
+ .chat-config-summary-title {
865
+ font-weight: 850;
866
+ }
867
+
868
+ .chat-config-summary-desc {
869
+ overflow: hidden;
870
+ color: var(--muted);
871
+ font-size: 12px;
872
+ text-overflow: ellipsis;
873
+ white-space: nowrap;
874
+ }
875
+
876
+ .chat-config-summary-toggle {
877
+ flex: 0 0 auto;
878
+ margin-left: auto;
879
+ border: 1px solid #e5e5e5;
880
+ border-radius: 999px;
881
+ padding: 4px 8px;
882
+ color: var(--muted);
883
+ background: #ffffff;
884
+ font-size: 12px;
885
+ font-weight: 800;
886
+ }
887
+
888
+ .chat-config-panel:not([open]) .chat-config-summary {
889
+ justify-content: center;
890
+ padding: 13px 0;
891
+ }
892
+
893
+ .chat-config-panel:not([open]) .chat-config-summary-main {
894
+ display: none;
895
+ }
896
+
897
+ .chat-config-panel:not([open]) .chat-config-summary-toggle,
898
+ .chat-config-panel:not([open]) .chat-config-body {
899
+ display: none;
900
+ }
901
+
902
+ .chat-config-body {
903
+ display: grid;
904
+ gap: 10px;
905
+ max-height: calc(100vh - 64px);
906
+ overflow-y: auto;
907
+ padding: 10px 12px 16px;
908
+ }
909
+
910
+ .chat-config-block {
911
+ display: grid;
912
+ gap: 10px;
913
+ border: 1px solid #ececf1;
914
+ border-radius: 12px;
915
+ padding: 12px;
916
+ background: #ffffff;
917
+ }
918
+
919
+ .chat-config-block-title {
920
+ color: var(--strong);
921
+ font-size: 13px;
922
+ font-weight: 850;
923
+ }
924
+
925
+ .chat-side-nav {
926
+ display: grid;
927
+ grid-template-columns: repeat(2, minmax(0, 1fr));
928
+ gap: 6px;
929
+ }
930
+
931
+ .chat-side-nav a {
932
+ border-radius: 10px;
933
+ padding: 9px 10px;
934
+ color: #5f6368;
935
+ background: #ffffff;
936
+ text-align: center;
937
+ text-decoration: none;
938
+ font-size: 13px;
939
+ font-weight: 780;
940
+ }
941
+
942
+ .chat-side-nav a.active {
943
+ color: #202123;
944
+ background: #ededed;
945
+ }
946
+
947
+ .chat-info-grid {
948
+ display: grid;
949
+ grid-template-columns: repeat(2, minmax(0, 1fr));
950
+ gap: 8px;
951
+ }
952
+
953
+ .chat-info-form {
954
+ display: block;
955
+ margin: 0;
956
+ min-width: 0;
957
+ }
958
+
959
+ .chat-info-item {
960
+ display: grid;
961
+ gap: 3px;
962
+ min-width: 0;
963
+ border: 1px solid #ececf1;
964
+ border-radius: 10px;
965
+ padding: 8px;
966
+ background: #f9f9f9;
967
+ }
968
+
969
+ .chat-info-item.full {
970
+ grid-column: 1 / -1;
971
+ }
972
+
973
+ .chat-info-item span {
974
+ color: var(--muted);
975
+ font-size: 11px;
976
+ font-weight: 750;
977
+ }
978
+
979
+ .chat-info-item strong {
980
+ overflow: hidden;
981
+ color: var(--strong);
982
+ font-size: 13px;
983
+ text-overflow: ellipsis;
984
+ white-space: nowrap;
985
+ }
986
+
987
+ .chat-info-item input,
988
+ .chat-info-item select {
989
+ width: 100%;
990
+ min-width: 0;
991
+ border: 0;
992
+ padding: 0;
993
+ color: var(--strong);
994
+ background: transparent;
995
+ font-size: 13px;
996
+ font-weight: 800;
997
+ outline: none;
998
+ appearance: none;
999
+ }
1000
+
1001
+ .chat-info-item select {
1002
+ cursor: pointer;
1003
+ }
1004
+
1005
+ .chat-inline-action {
1006
+ width: 100%;
1007
+ }
1008
+
1009
+ .chat-switch-status-list {
1010
+ display: grid;
1011
+ gap: 7px;
1012
+ }
1013
+
1014
+ .chat-switch-status-list form {
1015
+ margin: 0;
1016
+ }
1017
+
1018
+ .chat-switch-status {
1019
+ display: flex;
1020
+ align-items: center;
1021
+ justify-content: space-between;
1022
+ gap: 10px;
1023
+ width: 100%;
1024
+ height: auto;
1025
+ border: 1px solid #ececf1;
1026
+ border-radius: 10px;
1027
+ padding: 8px 10px;
1028
+ color: #6b7280;
1029
+ background: #f9f9f9;
1030
+ font-size: 12px;
1031
+ font-weight: 780;
1032
+ text-align: left;
1033
+ cursor: pointer;
1034
+ }
1035
+
1036
+ .chat-switch-status strong {
1037
+ flex: 0 0 auto;
1038
+ border-radius: 999px;
1039
+ padding: 2px 7px;
1040
+ color: #8e8ea0;
1041
+ background: #ffffff;
1042
+ font-size: 12px;
1043
+ }
1044
+
1045
+ .chat-switch-status.enabled {
1046
+ color: #202123;
1047
+ border-color: #d7f2e8;
1048
+ background: #f0fbf7;
1049
+ }
1050
+
1051
+ .chat-switch-status.enabled strong {
1052
+ color: #08785f;
1053
+ background: #dff7ee;
1054
+ }
1055
+
1056
+ .chat-sidebar-form {
1057
+ display: grid;
1058
+ gap: 9px;
1059
+ margin: 0;
1060
+ }
1061
+
1062
+ .conversation {
1063
+ display: block;
1064
+ border: 1px solid #ececf1;
1065
+ border-radius: 12px;
1066
+ padding: 13px 14px;
1067
+ color: inherit;
1068
+ text-decoration: none;
1069
+ }
1070
+
1071
+ .conversation.active {
1072
+ background: #f2f2f2;
1073
+ box-shadow: none;
1074
+ }
1075
+
1076
+ .conversation-name {
1077
+ font-weight: 800;
1078
+ }
1079
+
1080
+ .conversation-last {
1081
+ margin-top: 5px;
1082
+ color: var(--muted);
1083
+ font-size: 12px;
1084
+ overflow: hidden;
1085
+ text-overflow: ellipsis;
1086
+ white-space: nowrap;
1087
+ }
1088
+
1089
+ .chat-directory-list {
1090
+ display: grid;
1091
+ gap: 8px;
1092
+ }
1093
+
1094
+ .chat-directory-item {
1095
+ border: 1px solid #ececf1;
1096
+ border-radius: 12px;
1097
+ padding: 10px 12px;
1098
+ background: #f9f9f9;
1099
+ }
1100
+
1101
+ .chat-main {
1102
+ display: grid;
1103
+ grid-template-rows: auto 1fr auto;
1104
+ min-width: 0;
1105
+ height: 100vh;
1106
+ background: #ffffff;
1107
+ }
1108
+
1109
+ .chat-head {
1110
+ display: flex;
1111
+ align-items: center;
1112
+ justify-content: space-between;
1113
+ gap: 12px;
1114
+ min-height: 64px;
1115
+ padding: 10px 20px;
1116
+ }
1117
+
1118
+ .chat-model-pill {
1119
+ display: inline-flex;
1120
+ align-items: center;
1121
+ gap: 8px;
1122
+ min-width: 0;
1123
+ max-width: min(420px, 48vw);
1124
+ border: 1px solid #ececf1;
1125
+ border-radius: 999px;
1126
+ padding: 9px 14px;
1127
+ color: #202123;
1128
+ background: #ffffff;
1129
+ box-shadow: 0 4px 18px rgba(15, 23, 42, 0.06);
1130
+ font-size: 16px;
1131
+ font-weight: 760;
1132
+ }
1133
+
1134
+ .chat-model-pill span:first-child {
1135
+ overflow: hidden;
1136
+ text-overflow: ellipsis;
1137
+ white-space: nowrap;
1138
+ }
1139
+
1140
+ .chat-mode-dot {
1141
+ flex: 0 0 auto;
1142
+ border-radius: 999px;
1143
+ padding: 3px 7px;
1144
+ color: #10a37f;
1145
+ background: #e7f8f2;
1146
+ font-size: 12px;
1147
+ font-weight: 850;
1148
+ }
1149
+
1150
+ .clear-chat-form {
1151
+ margin: 0;
1152
+ }
1153
+
1154
+ .chat-top-actions {
1155
+ display: flex;
1156
+ align-items: center;
1157
+ justify-content: flex-end;
1158
+ gap: 8px;
1159
+ }
1160
+
1161
+ .chat-icon-button {
1162
+ display: inline-flex;
1163
+ align-items: center;
1164
+ justify-content: center;
1165
+ min-width: 44px;
1166
+ height: 42px;
1167
+ border: 1px solid #ececf1;
1168
+ border-radius: 999px;
1169
+ padding: 0 13px;
1170
+ color: #202123;
1171
+ background: #ffffff;
1172
+ text-decoration: none;
1173
+ font-size: 13px;
1174
+ font-weight: 760;
1175
+ box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
1176
+ }
1177
+
1178
+ .messages {
1179
+ display: flex;
1180
+ flex-direction: column;
1181
+ align-items: center;
1182
+ gap: 20px;
1183
+ min-height: 0;
1184
+ overflow-y: auto;
1185
+ padding: 28px max(24px, calc((100% - 820px) / 2)) 18px;
1186
+ background: #ffffff;
1187
+ }
1188
+
1189
+ .messages.empty {
1190
+ justify-content: center;
1191
+ padding-bottom: 120px;
1192
+ }
1193
+
1194
+ .chat-empty-state {
1195
+ display: grid;
1196
+ justify-items: center;
1197
+ gap: 10px;
1198
+ color: #202123;
1199
+ text-align: center;
1200
+ }
1201
+
1202
+ .chat-empty-state h2 {
1203
+ margin: 0;
1204
+ font-size: clamp(30px, 4vw, 46px);
1205
+ font-weight: 780;
1206
+ letter-spacing: 0;
1207
+ line-height: 1.16;
1208
+ }
1209
+
1210
+ .chat-empty-meta {
1211
+ color: #8e8ea0;
1212
+ font-size: 13px;
1213
+ font-weight: 680;
1214
+ }
1215
+
1216
+ .message {
1217
+ align-self: center;
1218
+ width: min(100%, 820px);
1219
+ border: 0;
1220
+ border-radius: 0;
1221
+ padding: 0;
1222
+ color: #202123;
1223
+ background: transparent;
1224
+ box-shadow: none;
1225
+ }
1226
+
1227
+ .message.user {
1228
+ align-self: flex-end;
1229
+ width: auto;
1230
+ max-width: min(76%, 720px);
1231
+ border-radius: 24px;
1232
+ padding: 12px 16px;
1233
+ color: #202123;
1234
+ background: #f4f4f4;
1235
+ }
1236
+
1237
+ .message.system {
1238
+ align-self: center;
1239
+ width: min(92%, 720px);
1240
+ border-radius: 18px;
1241
+ padding: 11px 14px;
1242
+ color: #6b7280;
1243
+ background: #f7f7f8;
1244
+ box-shadow: none;
1245
+ }
1246
+
1247
+ .message-meta {
1248
+ display: flex;
1249
+ justify-content: space-between;
1250
+ gap: 12px;
1251
+ margin-bottom: 5px;
1252
+ font-size: 12px;
1253
+ font-weight: 800;
1254
+ color: #8e8ea0;
1255
+ }
1256
+
1257
+ .message-text {
1258
+ line-height: 1.65;
1259
+ font-size: 15px;
1260
+ white-space: pre-wrap;
1261
+ overflow-wrap: anywhere;
1262
+ }
1263
+
1264
+ .message.user .message-meta {
1265
+ display: none;
1266
+ }
1267
+
1268
+ .message-images {
1269
+ display: flex;
1270
+ flex-wrap: wrap;
1271
+ gap: 8px;
1272
+ margin-top: 8px;
1273
+ }
1274
+
1275
+ .message-images img {
1276
+ width: min(180px, 100%);
1277
+ max-height: 180px;
1278
+ border: 1px solid #ececf1;
1279
+ border-radius: 12px;
1280
+ object-fit: cover;
1281
+ }
1282
+
1283
+ .composer {
1284
+ border: 0;
1285
+ padding: 12px max(20px, calc((100% - 820px) / 2)) 22px;
1286
+ background: #ffffff;
1287
+ }
1288
+
1289
+ .composer-row {
1290
+ display: grid;
1291
+ grid-template-columns: 1fr auto;
1292
+ gap: 8px;
1293
+ align-items: center;
1294
+ border: 1px solid #e5e5e5;
1295
+ border-radius: 28px;
1296
+ padding: 8px 8px 8px 16px;
1297
+ background: #ffffff;
1298
+ box-shadow: 0 12px 36px rgba(15, 23, 42, 0.1);
1299
+ }
1300
+
1301
+ .composer textarea {
1302
+ width: 100%;
1303
+ min-height: 24px;
1304
+ max-height: 150px;
1305
+ border: 0;
1306
+ border-radius: 0;
1307
+ padding: 9px 0;
1308
+ resize: none;
1309
+ color: var(--strong);
1310
+ background: transparent;
1311
+ line-height: 1.55;
1312
+ outline: none;
1313
+ }
1314
+
1315
+ .send-stack {
1316
+ display: grid;
1317
+ gap: 8px;
1318
+ }
1319
+
1320
+ .chat-form-actions {
1321
+ display: grid;
1322
+ gap: 8px;
1323
+ }
1324
+
1325
+ .send-button {
1326
+ display: grid;
1327
+ place-items: center;
1328
+ width: 42px;
1329
+ height: 42px;
1330
+ border: 0;
1331
+ border-radius: 999px;
1332
+ padding: 0;
1333
+ color: #ffffff;
1334
+ background: #000000;
1335
+ font-size: 25px;
1336
+ font-weight: 800;
1337
+ line-height: 1;
1338
+ }
1339
+
1340
+ @media (max-width: 860px) {
1341
+ .header {
1342
+ align-items: flex-start;
1343
+ flex-direction: column;
1344
+ }
1345
+
1346
+ .nav {
1347
+ width: 100%;
1348
+ }
1349
+
1350
+ .nav-link {
1351
+ flex: 1;
1352
+ }
1353
+
1354
+ .stats,
1355
+ .config-layout,
1356
+ .ai-fields {
1357
+ grid-template-columns: 1fr;
1358
+ }
1359
+
1360
+ .section + .section {
1361
+ border-left: 0;
1362
+ border-right: 0;
1363
+ border-top: 1px solid var(--border);
1364
+ }
1365
+
1366
+ .chat-layout {
1367
+ grid-template-columns: 1fr;
1368
+ height: 100vh;
1369
+ min-height: 0;
1370
+ }
1371
+
1372
+ .chat-config-panel-desktop {
1373
+ display: none;
1374
+ }
1375
+
1376
+ .chat-config-panel-mobile {
1377
+ display: block;
1378
+ width: auto;
1379
+ height: auto;
1380
+ min-height: 0;
1381
+ border: 0;
1382
+ background: transparent;
1383
+ overflow: visible;
1384
+ }
1385
+
1386
+ .chat-config-panel-mobile:not([open]) {
1387
+ width: auto;
1388
+ }
1389
+
1390
+ .chat-config-panel-mobile .chat-config-summary,
1391
+ .chat-config-panel-mobile:not([open]) .chat-config-summary {
1392
+ justify-content: center;
1393
+ width: 48px;
1394
+ min-height: 48px;
1395
+ height: 48px;
1396
+ border: 0;
1397
+ border-radius: 999px;
1398
+ padding: 0;
1399
+ background: #f7f7f7;
1400
+ box-shadow: none;
1401
+ }
1402
+
1403
+ .chat-config-panel-mobile .chat-config-summary-main,
1404
+ .chat-config-panel-mobile .chat-config-summary-toggle,
1405
+ .chat-config-panel-mobile:not([open]) .chat-config-summary-main,
1406
+ .chat-config-panel-mobile:not([open]) .chat-config-summary-toggle {
1407
+ display: none;
1408
+ }
1409
+
1410
+ .chat-config-panel-mobile .chat-menu-icon {
1411
+ width: 48px;
1412
+ height: 48px;
1413
+ background: transparent;
1414
+ box-shadow: none;
1415
+ }
1416
+
1417
+ .chat-config-panel-mobile[open]::after {
1418
+ content: "";
1419
+ position: fixed;
1420
+ inset: 0;
1421
+ z-index: 20;
1422
+ background: rgba(15, 23, 42, 0.34);
1423
+ }
1424
+
1425
+ .chat-config-panel-mobile[open] .chat-config-summary {
1426
+ position: fixed;
1427
+ top: 12px;
1428
+ left: 12px;
1429
+ z-index: 32;
1430
+ width: 48px;
1431
+ border: 0;
1432
+ border-radius: 999px;
1433
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.14);
1434
+ }
1435
+
1436
+ .chat-config-panel-mobile[open] .chat-config-body {
1437
+ position: fixed;
1438
+ top: 0;
1439
+ bottom: 0;
1440
+ left: 0;
1441
+ z-index: 31;
1442
+ width: min(86vw, 360px);
1443
+ max-height: none;
1444
+ padding: 78px 12px 16px;
1445
+ border-right: 1px solid #ececf1;
1446
+ background: #f7f7f8;
1447
+ box-shadow: 18px 0 40px rgba(15, 23, 42, 0.2);
1448
+ }
1449
+
1450
+ .chat-main {
1451
+ height: 100vh;
1452
+ min-height: 0;
1453
+ }
1454
+
1455
+ .chat-head {
1456
+ min-height: 70px;
1457
+ padding: 12px;
1458
+ }
1459
+
1460
+ .chat-model-pill {
1461
+ max-width: calc(100vw - 176px);
1462
+ padding: 9px 14px;
1463
+ font-size: 16px;
1464
+ }
1465
+
1466
+ .chat-top-actions {
1467
+ gap: 6px;
1468
+ }
1469
+
1470
+ .chat-top-actions .clear-chat-form {
1471
+ display: none;
1472
+ }
1473
+
1474
+ .chat-icon-button {
1475
+ width: 48px;
1476
+ min-width: 48px;
1477
+ height: 48px;
1478
+ padding: 0;
1479
+ font-size: 12px;
1480
+ box-shadow: none;
1481
+ }
1482
+
1483
+ .messages {
1484
+ padding: 18px 16px 12px;
1485
+ }
1486
+
1487
+ .messages.empty {
1488
+ padding-bottom: 92px;
1489
+ }
1490
+
1491
+ .chat-empty-state h2 {
1492
+ font-size: 30px;
1493
+ }
1494
+
1495
+ .message {
1496
+ width: 100%;
1497
+ }
1498
+
1499
+ .message.user {
1500
+ max-width: 86%;
1501
+ }
1502
+
1503
+ .composer {
1504
+ padding: 10px 12px 14px;
1505
+ }
1506
+ }
1507
+
1508
+ @media (max-width: 620px) {
1509
+ .shell {
1510
+ width: min(100vw - 20px, 1180px);
1511
+ padding-top: 14px;
1512
+ }
1513
+
1514
+ .chat-shell {
1515
+ width: 100%;
1516
+ padding: 0;
1517
+ }
1518
+
1519
+ .stats,
1520
+ .field-grid,
1521
+ .group-fields,
1522
+ .ai-fields {
1523
+ grid-template-columns: 1fr;
1524
+ }
1525
+
1526
+ .message-meta {
1527
+ display: grid;
1528
+ gap: 3px;
1529
+ }
1530
+
1531
+ .message.user .message-meta {
1532
+ display: none;
1533
+ }
1534
+
1535
+ .toolbar {
1536
+ width: 100%;
1537
+ }
1538
+
1539
+ .toolbar button {
1540
+ flex: 1;
1541
+ }
1542
+ }
1543
+ `));
1544
+ const ConfigPage = ({ aiConfigs = [], groupConfigs = [], currentAI = "", currentGuid = "global", redisError = "", panelStatus = "", panelMessage = "", restoreGroupListOpen = true, restoreOpenGroups = [], restoreAiListOpen = false, restoreOpenRows = [], }) => {
1545
+ const selectedAI = currentAI || aiConfigs[0]?.name || "";
1546
+ const selectedModel = aiConfigs.find((item) => item.name === selectedAI)?.model ||
1547
+ aiConfigs[0]?.model ||
1548
+ "";
1549
+ const runtimeStats = [
1550
+ { label: "服务状态", value: "在线", tone: "green" },
1551
+ {
1552
+ label: "群配置",
1553
+ value: `${groupConfigs.length} 个guid`,
1554
+ tone: "blue",
1555
+ },
1556
+ { label: "AI列表", value: `${aiConfigs.length} 个`, tone: "amber" },
1557
+ {
1558
+ label: "当前预览",
1559
+ value: currentGuid || "global",
1560
+ tone: "violet",
1561
+ },
1562
+ ];
1563
+ return (React.createElement(React.Fragment, null,
1564
+ React.createElement("div", { className: "stats" }, runtimeStats.map((item) => (React.createElement("div", { className: "stat", key: item.label },
1565
+ React.createElement("div", { className: "stat-label" }, item.label),
1566
+ React.createElement("div", { className: `stat-value tone-${item.tone}` }, item.value))))),
1567
+ React.createElement("section", { className: "panel" },
1568
+ React.createElement("div", { className: "panel-head" },
1569
+ React.createElement("div", { className: "panel-title" }, "\u914D\u7F6E\u7BA1\u7406"),
1570
+ React.createElement("div", { className: "toolbar" },
1571
+ React.createElement("button", { className: "subtle" }, "\u91CD\u8F7D"),
1572
+ React.createElement("button", { className: "subtle" }, "\u6821\u9A8C"),
1573
+ React.createElement("button", { className: "primary" }, "\u4FDD\u5B58"))),
1574
+ panelMessage ? (React.createElement("div", { className: `notice ${panelStatus === "error" ? "error" : "success"}` }, panelMessage)) : null,
1575
+ React.createElement("div", { className: "config-layout" },
1576
+ React.createElement("div", { className: "section" },
1577
+ React.createElement("details", { className: "group-manager", open: restoreGroupListOpen || groupConfigs.length <= 3 },
1578
+ React.createElement("summary", { className: "group-manager-summary" },
1579
+ React.createElement("div", { className: "summary-title" },
1580
+ React.createElement("span", { className: "summary-main" }, "\u7FA4\u914D\u7F6E"),
1581
+ React.createElement("span", { className: "summary-meta" },
1582
+ "Redis \u7FA4\u914D\u7F6E\u89C6\u56FE\uFF0C\u5171 ",
1583
+ groupConfigs.length,
1584
+ " \u4E2A guid")),
1585
+ React.createElement("span", { className: "summary-caret", "aria-hidden": "true" }, "\u203A")),
1586
+ React.createElement("div", { className: "source-note" },
1587
+ "\u5148\u6309 ",
1588
+ React.createElement("strong", null, "guid"),
1589
+ " ",
1590
+ "\u67E5\u770B\u7FA4\u914D\u7F6E\u3002\u5C55\u5F00\u540E\u4FEE\u6539\u5F53\u524DAI\u3001\u4E0A\u4E0B\u6587\u957F\u5EA6\u548C\u5404\u4E2A\u5F00\u5173\uFF0C \u4FDD\u5B58\u65F6\u5199\u56DE\u8FD9\u4E2A guid \u5BF9\u5E94\u7684 Redis \u914D\u7F6E\u3002"),
1591
+ React.createElement("form", { action: "/panel/action/add-group-config", className: "panel-action-form group-add-card", method: "post" },
1592
+ React.createElement("div", { className: "group-fields" },
1593
+ React.createElement("div", { className: "field" },
1594
+ React.createElement("label", { htmlFor: "new-group-guid" }, "\u65B0\u589E\u7FA4\u53F7 guid"),
1595
+ React.createElement("input", { id: "new-group-guid", name: "guid", placeholder: "\u4F8B\u5982 123456789" })),
1596
+ React.createElement("div", { className: "field" },
1597
+ React.createElement("label", { htmlFor: "new-group-ai" }, "\u9ED8\u8BA4AI"),
1598
+ React.createElement("select", { id: "new-group-ai", name: "currentAI", defaultValue: "" },
1599
+ React.createElement("option", { value: "" }, "\u5148\u4E0D\u6307\u5B9A"),
1600
+ aiConfigs.map((item) => (React.createElement("option", { key: `new-group-${item.name}`, value: item.name }, item.name))))),
1601
+ React.createElement("div", { className: "field" },
1602
+ React.createElement("label", { htmlFor: "new-group-chat-switch" }, "\u804A\u5929\u603B\u5F00\u5173"),
1603
+ React.createElement("select", { id: "new-group-chat-switch", name: "chatSwitch", defaultValue: "0" },
1604
+ React.createElement("option", { value: "0" }, "\u5148\u5173\u95ED"),
1605
+ React.createElement("option", { value: "1" }, "\u521B\u5EFA\u65F6\u5F00\u542F")))),
1606
+ React.createElement("div", { className: "group-add-actions" },
1607
+ React.createElement("div", { className: "group-add-toggle" }, "\u521B\u5EFA\u540E\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u4E0B\u9762\u7684\u7FA4\u914D\u7F6E\u5217\u8868\u91CC\u3002"),
1608
+ React.createElement("button", { className: "primary", type: "submit" }, "\u6DFB\u52A0\u7FA4"))),
1609
+ React.createElement("div", { className: "group-list" }, groupConfigs.length ? (groupConfigs.map((group) => (React.createElement("details", { className: "group-row", "data-guid": group.guid, key: group.guid, open: restoreOpenGroups.includes(group.guid) },
1610
+ React.createElement("summary", { className: "group-summary" },
1611
+ React.createElement("div", { className: "summary-title" },
1612
+ React.createElement("span", { className: "summary-main" },
1613
+ "\u7FA4\u53F7 ",
1614
+ group.guid),
1615
+ React.createElement("span", { className: "summary-meta" },
1616
+ group.currentAI || "未切换AI",
1617
+ " /",
1618
+ " ",
1619
+ group.model || "未设置模型",
1620
+ " / \u5DF2\u5F00",
1621
+ countEnabledGroupSwitches(group),
1622
+ " \u9879")),
1623
+ React.createElement("span", { className: "summary-caret", "aria-hidden": "true" }, "\u203A")),
1624
+ React.createElement("div", { className: "group-body" },
1625
+ React.createElement("form", { action: "/panel/action/update-group-config", className: "panel-action-form", method: "post" },
1626
+ React.createElement("input", { type: "hidden", name: "guid", value: group.guid }),
1627
+ React.createElement("div", { className: "group-fields" },
1628
+ React.createElement("div", { className: "field" },
1629
+ React.createElement("label", { htmlFor: `group-ai-${group.guid}` }, "\u5F53\u524DAI"),
1630
+ React.createElement("select", { id: `group-ai-${group.guid}`, name: "currentAI", defaultValue: group.currentAI },
1631
+ React.createElement("option", { value: "" }, "\u4FDD\u6301\u5F53\u524D"),
1632
+ aiConfigs.map((item) => (React.createElement("option", { key: `${group.guid}-${item.name}`, value: item.name }, item.name))))),
1633
+ React.createElement("div", { className: "field" },
1634
+ React.createElement("label", { htmlFor: `group-model-${group.guid}` }, "\u5F53\u524D\u6A21\u578B"),
1635
+ React.createElement("input", { id: `group-model-${group.guid}`, value: group.model || "未设置", readOnly: true })),
1636
+ React.createElement("div", { className: "field" },
1637
+ React.createElement("label", { htmlFor: `group-context-${group.guid}` }, "CAPI\u4E0A\u4E0B\u6587\u957F\u5EA6"),
1638
+ React.createElement("input", { id: `group-context-${group.guid}`, name: "capiContextLimit", defaultValue: String(group.capiContextLimit) }))),
1639
+ React.createElement("div", { className: "switch-list" }, panelGroupSwitchDefinitions.map((item) => (React.createElement("div", { className: "switch-row", key: `${group.guid}-${item.key}` },
1640
+ React.createElement("div", null,
1641
+ React.createElement("div", { className: "switch-label" }, item.label),
1642
+ React.createElement("div", { className: "switch-desc" }, item.desc)),
1643
+ React.createElement("label", { className: "switch", "aria-label": `${group.guid}-${item.label}` },
1644
+ React.createElement("input", { defaultChecked: group.switches[item.key], name: item.key, type: "checkbox" }),
1645
+ React.createElement("span", { className: "track" })))))),
1646
+ React.createElement("div", { className: "group-form-actions" },
1647
+ React.createElement("button", { className: "primary", type: "submit" }, "\u4FDD\u5B58\u672C\u7FA4\u914D\u7F6E")))))))) : (React.createElement("div", { className: "empty-state" }, "\u8FD8\u6CA1\u6709\u53D1\u73B0\u4EFB\u4F55 guid \u914D\u7F6E\u3002\u7B49\u7FA4\u804A\u91CC\u4EA7\u751F AI \u914D\u7F6E\u6216\u5F00\u5173\u6570\u636E\u540E\uFF0C\u8FD9\u91CC\u5C31\u4F1A\u5217\u51FA\u6765\u3002"))))),
1648
+ React.createElement("div", { className: "section" },
1649
+ React.createElement("h2", { className: "section-title" }, "\u5F53\u524D\u9884\u89C8"),
1650
+ React.createElement("div", { className: "field-grid" },
1651
+ React.createElement("div", { className: "field" },
1652
+ React.createElement("label", { htmlFor: "current-ai" }, "\u5F53\u524DAI"),
1653
+ React.createElement("select", { id: "current-ai", defaultValue: selectedAI }, aiConfigs.length ? (aiConfigs.map((item) => (React.createElement("option", { key: item.name, value: item.name }, item.name)))) : (React.createElement("option", { value: "" }, "\u6682\u65E0Redis AI\u914D\u7F6E")))),
1654
+ React.createElement("div", { className: "field" },
1655
+ React.createElement("label", { htmlFor: "model" }, "\u6A21\u578B"),
1656
+ React.createElement("input", { id: "model", defaultValue: selectedModel })),
1657
+ React.createElement("div", { className: "field" },
1658
+ React.createElement("label", { htmlFor: "context" }, "\u4E0A\u4E0B\u6587\u957F\u5EA6"),
1659
+ React.createElement("input", { id: "context", defaultValue: "256k" })),
1660
+ React.createElement("div", { className: "field" },
1661
+ React.createElement("label", { htmlFor: "master" }, "\u7BA1\u7406\u5458"),
1662
+ React.createElement("input", { id: "master", defaultValue: "3501869534" }))),
1663
+ React.createElement("details", { className: "ai-manager", open: restoreAiListOpen || aiConfigs.length <= 3 },
1664
+ React.createElement("summary", { className: "ai-manager-summary" },
1665
+ React.createElement("div", { className: "summary-title" },
1666
+ React.createElement("span", { className: "summary-main" }, "AI\u5217\u8868"),
1667
+ React.createElement("span", { className: "summary-meta" },
1668
+ "Redis ai:list:*\uFF0C\u5171 ",
1669
+ aiConfigs.length,
1670
+ " \u4E2A\u914D\u7F6E")),
1671
+ React.createElement("span", { className: "summary-caret", "aria-hidden": "true" }, "\u203A")),
1672
+ React.createElement("div", { className: "source-note" },
1673
+ "\u6570\u636E\u6765\u6E90\uFF1ARedis \u7684 ",
1674
+ React.createElement("strong", null, "ai:list:*"),
1675
+ "\u3002\u6DFB\u52A0/\u7F16\u8F91\u540E\u5E94\u5199\u5165",
1676
+ React.createElement("strong", null, " ai:list:<\u540D\u79F0>"),
1677
+ "\uFF0C\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u65F6\u5199\u5165",
1678
+ React.createElement("strong", null, " ai:currentAI:<guid>"),
1679
+ " \u5E76\u540C\u6B65",
1680
+ React.createElement("strong", null, " ai:config:<guid>"),
1681
+ "\u3002\u5F53\u524D\u64CD\u4F5C\u76EE\u6807\uFF1A",
1682
+ React.createElement("strong", null,
1683
+ " ",
1684
+ currentGuid),
1685
+ "\u3002"),
1686
+ redisError ? (React.createElement("div", { className: "error-note" },
1687
+ "Redis\u8BFB\u53D6\u5931\u8D25\uFF1A",
1688
+ redisError)) : null,
1689
+ React.createElement("div", { className: "ai-manager-actions" },
1690
+ React.createElement("button", { className: "subtle" }, "\u6DFB\u52A0AI")),
1691
+ React.createElement("div", { className: "ai-list" }, aiConfigs.length ? (aiConfigs.map((item) => (React.createElement("details", { className: "ai-row", "data-ai-name": item.name, key: item.name, open: restoreOpenRows.includes(item.name) },
1692
+ React.createElement("summary", { className: "ai-row-summary" },
1693
+ React.createElement("div", { className: "summary-title" },
1694
+ React.createElement("span", { className: "summary-main" }, item.name),
1695
+ React.createElement("span", { className: "summary-meta" },
1696
+ item.model || "未设置模型",
1697
+ " /",
1698
+ " ",
1699
+ item.host || "未设置host")),
1700
+ React.createElement("span", { className: "summary-caret", "aria-hidden": "true" }, "\u203A")),
1701
+ React.createElement("div", { className: "ai-row-body" },
1702
+ React.createElement("div", { className: "ai-row-actions" },
1703
+ React.createElement("form", { action: "/panel/action/use-ai", className: "panel-action-form", method: "post" },
1704
+ React.createElement("input", { type: "hidden", name: "name", value: item.name }),
1705
+ React.createElement("input", { type: "hidden", name: "guid", value: currentGuid }),
1706
+ React.createElement("button", { className: "primary", type: "submit" }, "\u4F7F\u7528\u8FD9\u4E2AAI")),
1707
+ React.createElement("form", { action: "/panel/action/delete-ai", className: "panel-action-form", method: "post" },
1708
+ React.createElement("input", { type: "hidden", name: "name", value: item.name }),
1709
+ React.createElement("button", { className: "subtle danger", type: "submit" }, "\u5220\u9664AI"))),
1710
+ React.createElement("div", { className: "ai-fields" },
1711
+ React.createElement("div", { className: "field" },
1712
+ React.createElement("label", { htmlFor: `ai-name-${item.name}` }, "AI\u540D\u79F0"),
1713
+ React.createElement("input", { id: `ai-name-${item.name}`, defaultValue: item.name })),
1714
+ React.createElement("div", { className: "field" },
1715
+ React.createElement("label", { htmlFor: `ai-host-${item.name}` }, "\u4E3B\u673A\u5730\u5740 host"),
1716
+ React.createElement("input", { id: `ai-host-${item.name}`, defaultValue: item.host })),
1717
+ React.createElement("div", { className: "field" },
1718
+ React.createElement("label", { htmlFor: `ai-key-${item.name}` }, "api_key"),
1719
+ React.createElement("input", { id: `ai-key-${item.name}`, type: "password", defaultValue: item.key })),
1720
+ React.createElement("div", { className: "field" },
1721
+ React.createElement("label", { htmlFor: `ai-model-${item.name}` }, "\u6A21\u578B"),
1722
+ React.createElement("input", { id: `ai-model-${item.name}`, defaultValue: item.model })),
1723
+ React.createElement("div", { className: "field" },
1724
+ React.createElement("label", { htmlFor: `ai-rapi-${item.name}` }, "RAPI"),
1725
+ React.createElement("select", { id: `ai-rapi-${item.name}`, defaultValue: item.rapi ? "1" : "0" },
1726
+ React.createElement("option", { value: "0" }, "\u5173\u95ED"),
1727
+ React.createElement("option", { value: "1" }, "\u5F00\u542F"))))))))) : (React.createElement("div", { className: "empty-state" }, "Redis \u4E2D\u8FD8\u6CA1\u6709 ai:list:*\uFF0C\u53EF\u901A\u8FC7 /\u6DFB\u52A0ai \u6216\u540E\u7EED\u9762\u677F\u8868\u5355\u521B\u5EFA\u3002")))),
1728
+ React.createElement("h2", { className: "section-title" }, "Redis\u6570\u636E\u9884\u89C8"),
1729
+ React.createElement("textarea", { className: "editor", spellCheck: false, defaultValue: createRedisPreview(aiConfigs, currentAI, redisError) }),
1730
+ React.createElement("h2", { className: "section-title" }, "\u914D\u7F6E\u6587\u4EF6"),
1731
+ React.createElement("textarea", { className: "editor", spellCheck: false, defaultValue: configValue }))))));
1732
+ };
1733
+ const ChatPage = ({ aiConfigs = [], currentAI = "", chatGuid = "global", chatUserId = "webuser", chatNickname = chatUserId, chatMode = "capi", chatModel = "", groupGuidOptions = [], workspaceNames = [], promptOptions = [], currentPrompt = "", chatMessages = [], currentGroupConfig = null, panelStatus = "", panelMessage = "", chatConfigOpen = false, }) => {
1734
+ const chatHref = `/panel/chat?guid=${encodeURIComponent(chatGuid)}&userId=${encodeURIComponent(chatUserId)}&nickname=${encodeURIComponent(chatNickname)}`;
1735
+ const modelLabel = chatModel || currentAI || "AI Chat";
1736
+ const hasMessages = chatMessages.length > 0;
1737
+ return (React.createElement("section", { className: "panel chat-panel" },
1738
+ panelMessage ? (React.createElement("div", { className: `notice ${panelStatus === "error" ? "error" : "success"}` }, panelMessage)) : null,
1739
+ React.createElement("div", { className: "chat-layout" },
1740
+ React.createElement(ChatConfigPanel, { aiConfigs: aiConfigs, currentAI: currentAI, chatGuid: chatGuid, chatUserId: chatUserId, chatNickname: chatNickname, chatMode: chatMode, chatModel: chatModel, groupGuidOptions: groupGuidOptions, workspaceNames: workspaceNames, promptOptions: promptOptions, currentPrompt: currentPrompt, currentGroupConfig: currentGroupConfig, chatHref: chatHref, idPrefix: "desktop", className: "chat-config-panel chat-config-panel-desktop", defaultOpen: true }),
1741
+ React.createElement("div", { className: "chat-main" },
1742
+ React.createElement("div", { className: "chat-head" },
1743
+ React.createElement(ChatConfigPanel, { aiConfigs: aiConfigs, currentAI: currentAI, chatGuid: chatGuid, chatUserId: chatUserId, chatNickname: chatNickname, chatMode: chatMode, chatModel: chatModel, groupGuidOptions: groupGuidOptions, workspaceNames: workspaceNames, promptOptions: promptOptions, currentPrompt: currentPrompt, currentGroupConfig: currentGroupConfig, chatHref: chatHref, idPrefix: "mobile", className: "chat-config-panel chat-config-panel-mobile", defaultOpen: chatConfigOpen }),
1744
+ React.createElement("div", { className: "chat-model-pill" },
1745
+ React.createElement("span", null, modelLabel),
1746
+ React.createElement("span", { className: "chat-mode-dot" }, chatMode.toUpperCase())),
1747
+ React.createElement("div", { className: "chat-top-actions" },
1748
+ React.createElement("a", { className: "chat-icon-button", href: chatHref, "aria-label": "\u5237\u65B0" }, "\u5237\u65B0"),
1749
+ React.createElement("form", { action: "/panel/action/clear-chat", className: "clear-chat-form", method: "post" },
1750
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
1751
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
1752
+ React.createElement("input", { type: "hidden", name: "nickname", value: chatNickname }),
1753
+ React.createElement("button", { className: "chat-icon-button", type: "submit", "aria-label": "\u6E05\u7A7A\u804A\u5929" }, "\u6E05\u7A7A")))),
1754
+ React.createElement("div", { className: `messages ${hasMessages ? "" : "empty"}` }, hasMessages ? (chatMessages.map((item, index) => (React.createElement("div", { className: `message ${item.from}`, key: `${item.from}-${index}-${item.time}` },
1755
+ React.createElement("div", { className: "message-meta" },
1756
+ React.createElement("span", null, item.name),
1757
+ React.createElement("span", null, item.time)),
1758
+ item.text ? (React.createElement("div", { className: "message-text" }, item.text)) : null,
1759
+ item.images?.length ? (React.createElement("div", { className: "message-images" }, item.images.map((image, imageIndex) => (React.createElement("img", { alt: "AI\u56DE\u590D\u56FE\u7247", key: `${image}-${imageIndex}`, src: image }))))) : null)))) : (React.createElement("div", { className: "chat-empty-state" },
1760
+ React.createElement("h2", null, "\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u5FD9\u7684\uFF1F"),
1761
+ React.createElement("div", { className: "chat-empty-meta" },
1762
+ "\u7FA4\u53F7 ",
1763
+ chatGuid,
1764
+ " / ",
1765
+ chatNickname,
1766
+ "(",
1767
+ chatUserId,
1768
+ ")")))),
1769
+ React.createElement("div", { className: "composer" },
1770
+ React.createElement("form", { action: "/panel/action/chat", className: "chat-composer-form", method: "post" },
1771
+ React.createElement("input", { type: "hidden", name: "guid", value: chatGuid }),
1772
+ React.createElement("input", { type: "hidden", name: "userId", value: chatUserId }),
1773
+ React.createElement("input", { type: "hidden", name: "nickname", value: chatNickname }),
1774
+ React.createElement("div", { className: "composer-row" },
1775
+ React.createElement("textarea", { "aria-label": "\u804A\u5929\u8F93\u5165", name: "message", placeholder: "\u8F93\u5165\u6D88\u606F...", required: true, defaultValue: "" }),
1776
+ React.createElement("div", { className: "chat-form-actions" },
1777
+ React.createElement("button", { className: "send-button", type: "submit", "aria-label": "\u53D1\u9001" }, "\u2191")))))))));
1778
+ };
1779
+ const PanelStateScript = ({ restoreScrollY }) => (React.createElement("script", { dangerouslySetInnerHTML: {
1780
+ __html: `
1781
+ (function () {
1782
+ var restoreScrollY = ${Number.isFinite(restoreScrollY) ? restoreScrollY : "null"};
1783
+
1784
+ function upsertHidden(form, name, value) {
1785
+ var input = form.querySelector('input[name="' + name + '"]');
1786
+ if (!input) {
1787
+ input = document.createElement("input");
1788
+ input.type = "hidden";
1789
+ input.name = name;
1790
+ form.appendChild(input);
1791
+ }
1792
+ input.value = value;
1793
+ }
1794
+
1795
+ function capturePanelState(event) {
1796
+ var form = event.target;
1797
+ if (!form || !form.matches) {
1798
+ return;
1799
+ }
1800
+
1801
+ var mobileChatPanel = document.querySelector(".chat-config-panel-mobile");
1802
+ if (mobileChatPanel && form.closest && form.closest(".chat-panel")) {
1803
+ upsertHidden(
1804
+ form,
1805
+ "chatConfigOpen",
1806
+ mobileChatPanel.open ? "1" : "0",
1807
+ );
1808
+ }
1809
+
1810
+ if (!form.matches("form.panel-action-form")) {
1811
+ return;
1812
+ }
1813
+
1814
+ var groupManager = document.querySelector("details.group-manager");
1815
+ var manager = document.querySelector("details.ai-manager");
1816
+ var openGroups = Array.prototype.slice
1817
+ .call(document.querySelectorAll("details.group-row[open]"))
1818
+ .map(function (row) {
1819
+ return row.getAttribute("data-guid") || "";
1820
+ })
1821
+ .filter(Boolean);
1822
+ var openRows = Array.prototype.slice
1823
+ .call(document.querySelectorAll("details.ai-row[open]"))
1824
+ .map(function (row) {
1825
+ return row.getAttribute("data-ai-name") || "";
1826
+ })
1827
+ .filter(Boolean);
1828
+
1829
+ upsertHidden(form, "panelOpenGroups", JSON.stringify(openGroups));
1830
+ upsertHidden(
1831
+ form,
1832
+ "panelGroupListOpen",
1833
+ groupManager && groupManager.open ? "1" : "0",
1834
+ );
1835
+ upsertHidden(form, "panelAiListOpen", manager && manager.open ? "1" : "0");
1836
+ upsertHidden(form, "panelOpenRows", JSON.stringify(openRows));
1837
+ upsertHidden(
1838
+ form,
1839
+ "panelScrollY",
1840
+ String(
1841
+ window.scrollY ||
1842
+ document.documentElement.scrollTop ||
1843
+ document.body.scrollTop ||
1844
+ 0,
1845
+ ),
1846
+ );
1847
+ }
1848
+
1849
+ function restorePanelScroll() {
1850
+ if (!Number.isFinite(restoreScrollY)) return;
1851
+ requestAnimationFrame(function () {
1852
+ setTimeout(function () {
1853
+ window.scrollTo(0, restoreScrollY);
1854
+ }, 0);
1855
+ });
1856
+ }
1857
+
1858
+ document.addEventListener("change", function (event) {
1859
+ var target = event.target;
1860
+ if (!target || !target.form) return;
1861
+ if (target.form.getAttribute("data-auto-submit") !== "change") return;
1862
+ if (typeof target.form.requestSubmit === "function") {
1863
+ target.form.requestSubmit();
1864
+ } else {
1865
+ target.form.submit();
1866
+ }
1867
+ });
1868
+
1869
+ document.addEventListener(
1870
+ "focusout",
1871
+ function (event) {
1872
+ var target = event.target;
1873
+ if (!target || !target.form) return;
1874
+ if (target.form.getAttribute("data-auto-submit") !== "blur") return;
1875
+ if (
1876
+ target.tagName !== "INPUT" ||
1877
+ target.type === "hidden" ||
1878
+ target.value === target.defaultValue
1879
+ ) {
1880
+ return;
1881
+ }
1882
+ if (typeof target.form.requestSubmit === "function") {
1883
+ target.form.requestSubmit();
1884
+ } else {
1885
+ target.form.submit();
1886
+ }
1887
+ },
1888
+ true,
1889
+ );
1890
+
1891
+ document.addEventListener("keydown", function (event) {
1892
+ var target = event.target;
1893
+ if (!target || !target.form) return;
1894
+ if (target.form.getAttribute("data-auto-submit") !== "blur") return;
1895
+ if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
1896
+ if (target.tagName !== "INPUT") return;
1897
+ event.preventDefault();
1898
+ if (typeof target.form.requestSubmit === "function") {
1899
+ target.form.requestSubmit();
1900
+ } else {
1901
+ target.form.submit();
1902
+ }
1903
+ });
1904
+
1905
+ document.addEventListener("submit", capturePanelState, true);
1906
+
1907
+ if (document.readyState === "loading") {
1908
+ document.addEventListener("DOMContentLoaded", restorePanelScroll);
1909
+ } else {
1910
+ restorePanelScroll();
1911
+ }
1912
+ })();
1913
+ `,
1914
+ } }));
1915
+ const PanelNoticeScript = () => (React.createElement("script", { dangerouslySetInnerHTML: {
1916
+ __html: `
1917
+ (function () {
1918
+ var notices = Array.prototype.slice.call(document.querySelectorAll(".notice"));
1919
+ if (!notices.length) return;
1920
+
1921
+ function cleanNoticeParams() {
1922
+ try {
1923
+ var url = new URL(window.location.href);
1924
+ var changed = false;
1925
+ ["panelStatus", "panelMessage"].forEach(function (key) {
1926
+ if (url.searchParams.has(key)) {
1927
+ url.searchParams.delete(key);
1928
+ changed = true;
1929
+ }
1930
+ });
1931
+ if (!changed || !window.history || !window.history.replaceState) return;
1932
+ var query = url.searchParams.toString();
1933
+ window.history.replaceState(null, "", url.pathname + (query ? "?" + query : "") + url.hash);
1934
+ } catch {}
1935
+ }
1936
+
1937
+ setTimeout(function () {
1938
+ notices.forEach(function (notice) {
1939
+ notice.classList.add("is-hiding");
1940
+ setTimeout(function () {
1941
+ if (notice.parentNode) notice.parentNode.removeChild(notice);
1942
+ }, 220);
1943
+ });
1944
+ cleanNoticeParams();
1945
+ }, 2600);
1946
+ })();
1947
+ `,
1948
+ } }));
1949
+ const PanelChatScript = () => (React.createElement("script", { dangerouslySetInnerHTML: {
1950
+ __html: `
1951
+ (function () {
1952
+ var form = document.querySelector(".chat-composer-form");
1953
+ var messages = document.querySelector(".messages");
1954
+ if (!form || !messages || !window.fetch || !window.ReadableStream) return;
1955
+
1956
+ var textarea = form.querySelector('textarea[name="message"]');
1957
+ var sendButton = form.querySelector(".send-button");
1958
+ var streamMessage = null;
1959
+
1960
+ function getTime() {
1961
+ var now = new Date();
1962
+ return String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0");
1963
+ }
1964
+
1965
+ function activateMessages() {
1966
+ messages.classList.remove("empty");
1967
+ var empty = messages.querySelector(".chat-empty-state");
1968
+ if (empty) empty.remove();
1969
+ }
1970
+
1971
+ function scrollMessages() {
1972
+ messages.scrollTop = messages.scrollHeight;
1973
+ }
1974
+
1975
+ function appendText(parent, className, text) {
1976
+ var node = document.createElement("div");
1977
+ node.className = className;
1978
+ node.textContent = text || "";
1979
+ parent.appendChild(node);
1980
+ return node;
1981
+ }
1982
+
1983
+ function renderImages(parent, images) {
1984
+ if (!images || !images.length) return;
1985
+ var wrap = document.createElement("div");
1986
+ wrap.className = "message-images";
1987
+ images.forEach(function (src) {
1988
+ if (!src) return;
1989
+ var img = document.createElement("img");
1990
+ img.alt = "AI回复图片";
1991
+ img.src = src;
1992
+ wrap.appendChild(img);
1993
+ });
1994
+ if (wrap.childNodes.length) parent.appendChild(wrap);
1995
+ }
1996
+
1997
+ function createMessage(message) {
1998
+ activateMessages();
1999
+ var item = document.createElement("div");
2000
+ item.className = "message " + (message.from || "system");
2001
+ var meta = document.createElement("div");
2002
+ meta.className = "message-meta";
2003
+ appendText(meta, "", message.name || "");
2004
+ appendText(meta, "", message.time || getTime());
2005
+ item.appendChild(meta);
2006
+ appendText(item, "message-text", message.text || "");
2007
+ renderImages(item, message.images || []);
2008
+ messages.appendChild(item);
2009
+ scrollMessages();
2010
+ return item;
2011
+ }
2012
+
2013
+ function ensureStreamMessage() {
2014
+ if (streamMessage && streamMessage.isConnected) return streamMessage;
2015
+ streamMessage = createMessage({
2016
+ from: "bot",
2017
+ name: "aichatbot",
2018
+ text: "",
2019
+ time: getTime(),
2020
+ });
2021
+ streamMessage.setAttribute("data-streaming", "1");
2022
+ return streamMessage;
2023
+ }
2024
+
2025
+ function appendDelta(text) {
2026
+ if (!text) return;
2027
+ var item = ensureStreamMessage();
2028
+ var textNode = item.querySelector(".message-text");
2029
+ if (!textNode) textNode = appendText(item, "message-text", "");
2030
+ textNode.textContent += text;
2031
+ scrollMessages();
2032
+ }
2033
+
2034
+ function replaceStreamMessage(message) {
2035
+ if (!streamMessage || !streamMessage.isConnected) {
2036
+ createMessage(message);
2037
+ return;
2038
+ }
2039
+ var next = createMessage(message);
2040
+ messages.insertBefore(next, streamMessage);
2041
+ streamMessage.remove();
2042
+ streamMessage = null;
2043
+ scrollMessages();
2044
+ }
2045
+
2046
+ function clearStreamMessage() {
2047
+ if (streamMessage && streamMessage.isConnected) {
2048
+ streamMessage.remove();
2049
+ }
2050
+ streamMessage = null;
2051
+ scrollMessages();
2052
+ }
2053
+
2054
+ function handleStreamEvent(event) {
2055
+ if (!event || !event.type) return;
2056
+ if (event.type === "delta") {
2057
+ appendDelta(event.text || "");
2058
+ return;
2059
+ }
2060
+ if (event.type === "replace") {
2061
+ replaceStreamMessage(event.message || {});
2062
+ return;
2063
+ }
2064
+ if (event.type === "clear") {
2065
+ clearStreamMessage();
2066
+ return;
2067
+ }
2068
+ if (event.type === "message") {
2069
+ createMessage(event.message || {});
2070
+ return;
2071
+ }
2072
+ if (event.type === "error") {
2073
+ createMessage({
2074
+ from: "system",
2075
+ name: "系统",
2076
+ text: event.message || "发送失败",
2077
+ time: getTime(),
2078
+ });
2079
+ }
2080
+ }
2081
+
2082
+ async function readEventStream(response) {
2083
+ if (!response.body) return;
2084
+ var reader = response.body.getReader();
2085
+ var decoder = new TextDecoder();
2086
+ var buffer = "";
2087
+
2088
+ while (true) {
2089
+ var result = await reader.read();
2090
+ if (result.done) break;
2091
+ buffer += decoder.decode(result.value, { stream: true });
2092
+ var lines = buffer.split("\\n");
2093
+ buffer = lines.pop() || "";
2094
+ lines.forEach(function (line) {
2095
+ if (!line.trim()) return;
2096
+ try {
2097
+ handleStreamEvent(JSON.parse(line));
2098
+ } catch {}
2099
+ });
2100
+ }
2101
+
2102
+ buffer += decoder.decode();
2103
+ if (buffer.trim()) {
2104
+ try {
2105
+ handleStreamEvent(JSON.parse(buffer));
2106
+ } catch {}
2107
+ }
2108
+ }
2109
+
2110
+ function resizeTextarea() {
2111
+ if (!textarea) return;
2112
+ textarea.style.height = "auto";
2113
+ textarea.style.height = Math.min(textarea.scrollHeight, 150) + "px";
2114
+ }
2115
+
2116
+ if (textarea) {
2117
+ textarea.addEventListener("keydown", function (event) {
2118
+ if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
2119
+ event.preventDefault();
2120
+ if (typeof form.requestSubmit === "function") {
2121
+ form.requestSubmit();
2122
+ } else {
2123
+ form.dispatchEvent(new Event("submit", { cancelable: true }));
2124
+ }
2125
+ });
2126
+ textarea.addEventListener("input", resizeTextarea);
2127
+ resizeTextarea();
2128
+ }
2129
+
2130
+ form.addEventListener("submit", async function (event) {
2131
+ event.preventDefault();
2132
+ if (!textarea) return;
2133
+ var text = textarea.value.trim();
2134
+ if (!text) return;
2135
+
2136
+ var formData = new FormData(form);
2137
+ var body = new URLSearchParams();
2138
+ body.set("guid", String(formData.get("guid") || ""));
2139
+ body.set("userId", String(formData.get("userId") || ""));
2140
+ body.set("nickname", String(formData.get("nickname") || ""));
2141
+ body.set("message", text);
2142
+ createMessage({
2143
+ from: "user",
2144
+ name: String(formData.get("nickname") || formData.get("userId") || "webuser"),
2145
+ text: text,
2146
+ time: getTime(),
2147
+ });
2148
+ textarea.value = "";
2149
+ resizeTextarea();
2150
+ if (sendButton) sendButton.disabled = true;
2151
+
2152
+ try {
2153
+ var response = await fetch("/panel/action/chat/stream", {
2154
+ method: "POST",
2155
+ body: body,
2156
+ headers: {
2157
+ Accept: "application/x-ndjson",
2158
+ },
2159
+ });
2160
+ if (!response.ok) {
2161
+ throw new Error("HTTP " + response.status);
2162
+ }
2163
+ await readEventStream(response);
2164
+ } catch (error) {
2165
+ createMessage({
2166
+ from: "system",
2167
+ name: "系统",
2168
+ text: "发送失败:" + (error && error.message ? error.message : String(error)),
2169
+ time: getTime(),
2170
+ });
2171
+ } finally {
2172
+ if (sendButton) sendButton.disabled = false;
2173
+ if (textarea) textarea.focus();
2174
+ }
2175
+ });
2176
+ })();
2177
+ `,
2178
+ } }));
2179
+ function PanelPreview({ page = "config", aiConfigs = [], groupConfigs = [], currentAI = "", currentGuid = "global", redisError = "", panelStatus = "", panelMessage = "", restoreGroupListOpen = true, restoreOpenGroups = [], restoreAiListOpen = false, restoreOpenRows = [], restoreScrollY, chatGuid = currentGuid, chatUserId = "webuser", chatNickname = chatUserId, chatMode = "capi", chatModel = "", groupGuidOptions = [], workspaceNames = [], promptOptions = [], currentPrompt = "", chatMessages = [], currentGroupConfig = null, chatConfigOpen = false, }) {
2180
+ return (React.createElement("html", null,
2181
+ React.createElement("head", null,
2182
+ React.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
2183
+ React.createElement("title", null, "AlemonJS AI \u9762\u677F"),
2184
+ React.createElement(AppStyle, null)),
2185
+ React.createElement("body", null,
2186
+ React.createElement("main", { className: `shell ${page === "chat" ? "chat-shell" : ""}` },
2187
+ React.createElement("header", { className: `header ${page === "chat" ? "chat-page-header" : ""}` },
2188
+ React.createElement("div", { className: "brand" },
2189
+ React.createElement("div", { className: "logo" }, "AI"),
2190
+ React.createElement("div", null,
2191
+ React.createElement("h1", null, "AlemonJS Aichat"),
2192
+ React.createElement("div", { className: "meta" }, "\u7F51\u9875\u63A7\u5236\u53F0"))),
2193
+ renderNav(page)),
2194
+ page === "chat" ? (React.createElement(ChatPage, { aiConfigs: aiConfigs, currentAI: currentAI, chatGuid: chatGuid, chatUserId: chatUserId, chatNickname: chatNickname, chatMode: chatMode, chatModel: chatModel, groupGuidOptions: groupGuidOptions, workspaceNames: workspaceNames, promptOptions: promptOptions, currentPrompt: currentPrompt, chatMessages: chatMessages, currentGroupConfig: currentGroupConfig, panelStatus: panelStatus, panelMessage: panelMessage, chatConfigOpen: chatConfigOpen })) : (React.createElement(ConfigPage, { aiConfigs: aiConfigs, groupConfigs: groupConfigs, currentAI: currentAI, currentGuid: currentGuid, redisError: redisError, panelStatus: panelStatus, panelMessage: panelMessage, restoreGroupListOpen: restoreGroupListOpen, restoreOpenGroups: restoreOpenGroups, restoreAiListOpen: restoreAiListOpen, restoreOpenRows: restoreOpenRows }))),
2195
+ React.createElement(PanelStateScript, { restoreScrollY: restoreScrollY }),
2196
+ React.createElement(PanelNoticeScript, null),
2197
+ page === "chat" ? React.createElement(PanelChatScript, null) : null)));
2198
+ }
2199
+
2200
+ export { PanelPreview as default };