@beanx/cathygo-web-core 0.1.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,2409 @@
1
+ // src/features/chat/ChatHomeView.tsx
2
+ import { useRef as useRef2, useState as useState2 } from "react";
3
+
4
+ // src/chat-ui/agentIdentity.ts
5
+ function agentDisplayName(status) {
6
+ return status?.display_name?.trim() || status?.agent_id?.trim() || "CathyGO Agent";
7
+ }
8
+ function agentShortId(status) {
9
+ const value = status?.agent_short_id?.trim();
10
+ return value || void 0;
11
+ }
12
+
13
+ // src/chat-ui/icons.tsx
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ function IconNewChat({ className }) {
16
+ return /* @__PURE__ */ jsx(
17
+ "svg",
18
+ {
19
+ "aria-hidden": true,
20
+ className,
21
+ fill: "none",
22
+ height: "18",
23
+ viewBox: "0 0 24 24",
24
+ width: "18",
25
+ xmlns: "http://www.w3.org/2000/svg",
26
+ children: /* @__PURE__ */ jsx(
27
+ "path",
28
+ {
29
+ d: "M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z",
30
+ stroke: "currentColor",
31
+ strokeLinecap: "round",
32
+ strokeLinejoin: "round",
33
+ strokeWidth: "1.8"
34
+ }
35
+ )
36
+ }
37
+ );
38
+ }
39
+ function IconPencil({ className }) {
40
+ return /* @__PURE__ */ jsx(
41
+ "svg",
42
+ {
43
+ "aria-hidden": true,
44
+ className,
45
+ fill: "none",
46
+ height: "18",
47
+ viewBox: "0 0 24 24",
48
+ width: "18",
49
+ xmlns: "http://www.w3.org/2000/svg",
50
+ children: /* @__PURE__ */ jsx(
51
+ "path",
52
+ {
53
+ d: "M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z",
54
+ stroke: "currentColor",
55
+ strokeLinecap: "round",
56
+ strokeLinejoin: "round",
57
+ strokeWidth: "1.8"
58
+ }
59
+ )
60
+ }
61
+ );
62
+ }
63
+ function IconCalendar({ className }) {
64
+ return /* @__PURE__ */ jsxs(
65
+ "svg",
66
+ {
67
+ "aria-hidden": true,
68
+ className,
69
+ fill: "none",
70
+ height: "18",
71
+ viewBox: "0 0 24 24",
72
+ width: "18",
73
+ xmlns: "http://www.w3.org/2000/svg",
74
+ children: [
75
+ /* @__PURE__ */ jsx("rect", { height: "16", rx: "2", stroke: "currentColor", strokeWidth: "1.8", width: "18", x: "3", y: "5" }),
76
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18M8 3v4M16 3v4", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.8" })
77
+ ]
78
+ }
79
+ );
80
+ }
81
+ function IconMessage({ className }) {
82
+ return /* @__PURE__ */ jsx(
83
+ "svg",
84
+ {
85
+ "aria-hidden": true,
86
+ className,
87
+ fill: "none",
88
+ height: "18",
89
+ viewBox: "0 0 24 24",
90
+ width: "18",
91
+ xmlns: "http://www.w3.org/2000/svg",
92
+ children: /* @__PURE__ */ jsx(
93
+ "path",
94
+ {
95
+ d: "M7 9h10M7 13h6M5 19l2.5-4H19a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2z",
96
+ stroke: "currentColor",
97
+ strokeLinecap: "round",
98
+ strokeLinejoin: "round",
99
+ strokeWidth: "1.8"
100
+ }
101
+ )
102
+ }
103
+ );
104
+ }
105
+ function IconDocument({ className }) {
106
+ return /* @__PURE__ */ jsxs(
107
+ "svg",
108
+ {
109
+ "aria-hidden": true,
110
+ className,
111
+ fill: "none",
112
+ height: "18",
113
+ viewBox: "0 0 24 24",
114
+ width: "18",
115
+ xmlns: "http://www.w3.org/2000/svg",
116
+ children: [
117
+ /* @__PURE__ */ jsx(
118
+ "path",
119
+ {
120
+ d: "M8 4h7l3 3v13H8V4z",
121
+ stroke: "currentColor",
122
+ strokeLinejoin: "round",
123
+ strokeWidth: "1.8"
124
+ }
125
+ ),
126
+ /* @__PURE__ */ jsx("path", { d: "M15 4v3h3", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.8" })
127
+ ]
128
+ }
129
+ );
130
+ }
131
+ function IconSearch({ className }) {
132
+ return /* @__PURE__ */ jsxs(
133
+ "svg",
134
+ {
135
+ "aria-hidden": true,
136
+ className,
137
+ fill: "none",
138
+ height: "18",
139
+ viewBox: "0 0 24 24",
140
+ width: "18",
141
+ xmlns: "http://www.w3.org/2000/svg",
142
+ children: [
143
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7", stroke: "currentColor", strokeWidth: "1.8" }),
144
+ /* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.8" })
145
+ ]
146
+ }
147
+ );
148
+ }
149
+ function IconSettings({ className }) {
150
+ return /* @__PURE__ */ jsxs(
151
+ "svg",
152
+ {
153
+ "aria-hidden": true,
154
+ className,
155
+ fill: "none",
156
+ height: "18",
157
+ viewBox: "0 0 24 24",
158
+ width: "18",
159
+ xmlns: "http://www.w3.org/2000/svg",
160
+ children: [
161
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3", stroke: "currentColor", strokeWidth: "1.8" }),
162
+ /* @__PURE__ */ jsx(
163
+ "path",
164
+ {
165
+ d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41",
166
+ stroke: "currentColor",
167
+ strokeLinecap: "round",
168
+ strokeWidth: "1.8"
169
+ }
170
+ )
171
+ ]
172
+ }
173
+ );
174
+ }
175
+ function IconImageUpload({ className }) {
176
+ return /* @__PURE__ */ jsxs(
177
+ "svg",
178
+ {
179
+ "aria-hidden": true,
180
+ className,
181
+ fill: "none",
182
+ height: "20",
183
+ viewBox: "0 0 24 24",
184
+ width: "20",
185
+ xmlns: "http://www.w3.org/2000/svg",
186
+ children: [
187
+ /* @__PURE__ */ jsx(
188
+ "rect",
189
+ {
190
+ height: "16",
191
+ rx: "3",
192
+ ry: "3",
193
+ stroke: "currentColor",
194
+ strokeWidth: "1.8",
195
+ width: "18",
196
+ x: "3",
197
+ y: "5"
198
+ }
199
+ ),
200
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "10", fill: "currentColor", r: "1.4" }),
201
+ /* @__PURE__ */ jsx(
202
+ "path",
203
+ {
204
+ d: "M3 17l5.2-5.2a1.2 1.2 0 0 1 1.7 0L15 17M14 12l2.3-2.3a1.2 1.2 0 0 1 1.7 0L21 14",
205
+ stroke: "currentColor",
206
+ strokeLinecap: "round",
207
+ strokeLinejoin: "round",
208
+ strokeWidth: "1.8"
209
+ }
210
+ )
211
+ ]
212
+ }
213
+ );
214
+ }
215
+ function IconChevronRight({ className }) {
216
+ return /* @__PURE__ */ jsx(
217
+ "svg",
218
+ {
219
+ "aria-hidden": true,
220
+ className,
221
+ fill: "none",
222
+ height: "18",
223
+ viewBox: "0 0 24 24",
224
+ width: "18",
225
+ xmlns: "http://www.w3.org/2000/svg",
226
+ children: /* @__PURE__ */ jsx(
227
+ "path",
228
+ {
229
+ d: "m9 6 6 6-6 6",
230
+ stroke: "currentColor",
231
+ strokeLinecap: "round",
232
+ strokeLinejoin: "round",
233
+ strokeWidth: "1.8"
234
+ }
235
+ )
236
+ }
237
+ );
238
+ }
239
+ function IconMore({ className }) {
240
+ return /* @__PURE__ */ jsxs(
241
+ "svg",
242
+ {
243
+ "aria-hidden": true,
244
+ className,
245
+ fill: "none",
246
+ height: "18",
247
+ viewBox: "0 0 24 24",
248
+ width: "18",
249
+ xmlns: "http://www.w3.org/2000/svg",
250
+ children: [
251
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "12", fill: "currentColor", r: "1.4" }),
252
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", fill: "currentColor", r: "1.4" }),
253
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "12", fill: "currentColor", r: "1.4" })
254
+ ]
255
+ }
256
+ );
257
+ }
258
+ function IconExternalLink({ className }) {
259
+ return /* @__PURE__ */ jsx(
260
+ "svg",
261
+ {
262
+ "aria-hidden": true,
263
+ className,
264
+ fill: "none",
265
+ height: "14",
266
+ viewBox: "0 0 24 24",
267
+ width: "14",
268
+ xmlns: "http://www.w3.org/2000/svg",
269
+ children: /* @__PURE__ */ jsx(
270
+ "path",
271
+ {
272
+ d: "M14 4h6v6M10 14 20 4M15 4h5v5",
273
+ stroke: "currentColor",
274
+ strokeLinecap: "round",
275
+ strokeLinejoin: "round",
276
+ strokeWidth: "1.8"
277
+ }
278
+ )
279
+ }
280
+ );
281
+ }
282
+ function IconHome({ className }) {
283
+ return /* @__PURE__ */ jsx(
284
+ "svg",
285
+ {
286
+ "aria-hidden": true,
287
+ className,
288
+ fill: "none",
289
+ height: "18",
290
+ viewBox: "0 0 24 24",
291
+ width: "18",
292
+ xmlns: "http://www.w3.org/2000/svg",
293
+ children: /* @__PURE__ */ jsx(
294
+ "path",
295
+ {
296
+ d: "M4 10.5 12 4l8 6.5V20a1 1 0 0 1-1 1h-5v-6H10v6H5a1 1 0 0 1-1-1v-9.5Z",
297
+ stroke: "currentColor",
298
+ strokeLinejoin: "round",
299
+ strokeWidth: "1.7"
300
+ }
301
+ )
302
+ }
303
+ );
304
+ }
305
+ function IconGithub({ className }) {
306
+ return /* @__PURE__ */ jsx(
307
+ "svg",
308
+ {
309
+ "aria-hidden": true,
310
+ className,
311
+ fill: "none",
312
+ height: "18",
313
+ viewBox: "0 0 24 24",
314
+ width: "18",
315
+ xmlns: "http://www.w3.org/2000/svg",
316
+ children: /* @__PURE__ */ jsx(
317
+ "path",
318
+ {
319
+ d: "M9 19c-4.3 1.4-4.3-2.1-6-2.4m6 2.4v-3.5c0-.9-.3-1.5-1.2-1.8.9-.1 1.8-.4 2.5-1 1.1-.9 1.6-2.2 1.6-3.9 0-3-1.8-4.5-4.4-4.5-1.2 0-2.2.4-3 .9l-.6-.6c-.3-.3-.8-.1-.8.4v1.8c-1.2 1.1-1.8 2.6-1.8 4.3 0 1.7.5 3 1.6 3.9.7.6 1.6.9 2.5 1-.9.3-1.2.9-1.2 1.8V19",
320
+ stroke: "currentColor",
321
+ strokeLinecap: "round",
322
+ strokeLinejoin: "round",
323
+ strokeWidth: "1.7"
324
+ }
325
+ )
326
+ }
327
+ );
328
+ }
329
+ function IconBack({ className }) {
330
+ return /* @__PURE__ */ jsx(
331
+ "svg",
332
+ {
333
+ "aria-hidden": true,
334
+ className,
335
+ fill: "none",
336
+ height: "18",
337
+ viewBox: "0 0 24 24",
338
+ width: "18",
339
+ xmlns: "http://www.w3.org/2000/svg",
340
+ children: /* @__PURE__ */ jsx(
341
+ "path",
342
+ {
343
+ d: "m15 6-6 6 6 6",
344
+ stroke: "currentColor",
345
+ strokeLinecap: "round",
346
+ strokeLinejoin: "round",
347
+ strokeWidth: "1.8"
348
+ }
349
+ )
350
+ }
351
+ );
352
+ }
353
+ function IconClose({ className }) {
354
+ return /* @__PURE__ */ jsx(
355
+ "svg",
356
+ {
357
+ "aria-hidden": true,
358
+ className,
359
+ fill: "none",
360
+ height: "18",
361
+ viewBox: "0 0 24 24",
362
+ width: "18",
363
+ xmlns: "http://www.w3.org/2000/svg",
364
+ children: /* @__PURE__ */ jsx(
365
+ "path",
366
+ {
367
+ d: "M6 6l12 12M18 6 6 18",
368
+ stroke: "currentColor",
369
+ strokeLinecap: "round",
370
+ strokeWidth: "1.8"
371
+ }
372
+ )
373
+ }
374
+ );
375
+ }
376
+ function IconPlug({ className }) {
377
+ return /* @__PURE__ */ jsx(
378
+ "svg",
379
+ {
380
+ "aria-hidden": true,
381
+ className,
382
+ fill: "none",
383
+ height: "18",
384
+ viewBox: "0 0 24 24",
385
+ width: "18",
386
+ xmlns: "http://www.w3.org/2000/svg",
387
+ children: /* @__PURE__ */ jsx(
388
+ "path",
389
+ {
390
+ d: "M12 22v-5M9 8V2M15 8V2M7 12h10a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2z",
391
+ stroke: "currentColor",
392
+ strokeLinecap: "round",
393
+ strokeLinejoin: "round",
394
+ strokeWidth: "1.8"
395
+ }
396
+ )
397
+ }
398
+ );
399
+ }
400
+ function IconBrain({ className }) {
401
+ return /* @__PURE__ */ jsx(
402
+ "svg",
403
+ {
404
+ "aria-hidden": true,
405
+ className,
406
+ fill: "none",
407
+ height: "18",
408
+ viewBox: "0 0 24 24",
409
+ width: "18",
410
+ xmlns: "http://www.w3.org/2000/svg",
411
+ children: /* @__PURE__ */ jsx(
412
+ "path",
413
+ {
414
+ d: "M9.5 2A5.5 5.5 0 0 0 4 7.5c0 .9.2 1.7.6 2.5A5.5 5.5 0 0 0 9.5 22M14.5 2A5.5 5.5 0 0 1 20 7.5a5.5 5.5 0 0 1-5.5 14.5M12 6v12",
415
+ stroke: "currentColor",
416
+ strokeLinecap: "round",
417
+ strokeLinejoin: "round",
418
+ strokeWidth: "1.8"
419
+ }
420
+ )
421
+ }
422
+ );
423
+ }
424
+ function IconDevice({ className }) {
425
+ return /* @__PURE__ */ jsxs(
426
+ "svg",
427
+ {
428
+ "aria-hidden": true,
429
+ className,
430
+ fill: "none",
431
+ height: "18",
432
+ viewBox: "0 0 24 24",
433
+ width: "18",
434
+ xmlns: "http://www.w3.org/2000/svg",
435
+ children: [
436
+ /* @__PURE__ */ jsx("rect", { height: "14", rx: "2", stroke: "currentColor", strokeWidth: "1.8", width: "18", x: "3", y: "5" }),
437
+ /* @__PURE__ */ jsx("path", { d: "M8 19h8", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.8" })
438
+ ]
439
+ }
440
+ );
441
+ }
442
+ function IconDevices({ className }) {
443
+ return /* @__PURE__ */ jsxs(
444
+ "svg",
445
+ {
446
+ "aria-hidden": true,
447
+ className,
448
+ fill: "none",
449
+ height: "18",
450
+ viewBox: "0 0 24 24",
451
+ width: "18",
452
+ xmlns: "http://www.w3.org/2000/svg",
453
+ children: [
454
+ /* @__PURE__ */ jsx("rect", { height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.7", width: "16", x: "2", y: "4" }),
455
+ /* @__PURE__ */ jsx("path", { d: "M6 15h8", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.7" }),
456
+ /* @__PURE__ */ jsx("rect", { height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.7", width: "7", x: "13", y: "9" }),
457
+ /* @__PURE__ */ jsx("path", { d: "M15.5 17.5h2", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.7" })
458
+ ]
459
+ }
460
+ );
461
+ }
462
+ function IconSmartphone({ className }) {
463
+ return /* @__PURE__ */ jsxs(
464
+ "svg",
465
+ {
466
+ "aria-hidden": true,
467
+ className,
468
+ fill: "none",
469
+ height: "18",
470
+ viewBox: "0 0 24 24",
471
+ width: "18",
472
+ xmlns: "http://www.w3.org/2000/svg",
473
+ children: [
474
+ /* @__PURE__ */ jsx("rect", { height: "16", rx: "2.5", stroke: "currentColor", strokeWidth: "1.8", width: "10", x: "7", y: "4" }),
475
+ /* @__PURE__ */ jsx("path", { d: "M11 17h2", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.8" })
476
+ ]
477
+ }
478
+ );
479
+ }
480
+ function IconShield({ className }) {
481
+ return /* @__PURE__ */ jsx(
482
+ "svg",
483
+ {
484
+ "aria-hidden": true,
485
+ className,
486
+ fill: "none",
487
+ height: "18",
488
+ viewBox: "0 0 24 24",
489
+ width: "18",
490
+ xmlns: "http://www.w3.org/2000/svg",
491
+ children: /* @__PURE__ */ jsx(
492
+ "path",
493
+ {
494
+ d: "M12 3 4 7v6c0 5 3.4 8.7 8 9 4.6-.3 8-4 8-9V7l-8-4z",
495
+ stroke: "currentColor",
496
+ strokeLinecap: "round",
497
+ strokeLinejoin: "round",
498
+ strokeWidth: "1.8"
499
+ }
500
+ )
501
+ }
502
+ );
503
+ }
504
+ function IconCode({ className }) {
505
+ return /* @__PURE__ */ jsx(
506
+ "svg",
507
+ {
508
+ "aria-hidden": true,
509
+ className,
510
+ fill: "none",
511
+ height: "18",
512
+ viewBox: "0 0 24 24",
513
+ width: "18",
514
+ xmlns: "http://www.w3.org/2000/svg",
515
+ children: /* @__PURE__ */ jsx(
516
+ "path",
517
+ {
518
+ d: "m8 9-4 3 4 3M16 9l4 3-4 3M13 5 11 19",
519
+ stroke: "currentColor",
520
+ strokeLinecap: "round",
521
+ strokeLinejoin: "round",
522
+ strokeWidth: "1.8"
523
+ }
524
+ )
525
+ }
526
+ );
527
+ }
528
+ function IconInfo({ className }) {
529
+ return /* @__PURE__ */ jsxs(
530
+ "svg",
531
+ {
532
+ "aria-hidden": true,
533
+ className,
534
+ fill: "none",
535
+ height: "18",
536
+ viewBox: "0 0 24 24",
537
+ width: "18",
538
+ xmlns: "http://www.w3.org/2000/svg",
539
+ children: [
540
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "1.8" }),
541
+ /* @__PURE__ */ jsx("path", { d: "M12 10v6M12 8h.01", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.8" })
542
+ ]
543
+ }
544
+ );
545
+ }
546
+ function IconCloud({ className }) {
547
+ return /* @__PURE__ */ jsx(
548
+ "svg",
549
+ {
550
+ "aria-hidden": true,
551
+ className,
552
+ fill: "none",
553
+ height: "18",
554
+ viewBox: "0 0 24 24",
555
+ width: "18",
556
+ xmlns: "http://www.w3.org/2000/svg",
557
+ children: /* @__PURE__ */ jsx(
558
+ "path",
559
+ {
560
+ d: "M7.5 18.5h9.2a3.3 3.3 0 0 0 .2-6.6A4.4 4.4 0 0 0 6.2 9.4 3.6 3.6 0 0 0 7.5 18.5Z",
561
+ stroke: "currentColor",
562
+ strokeLinejoin: "round",
563
+ strokeWidth: "1.7"
564
+ }
565
+ )
566
+ }
567
+ );
568
+ }
569
+ function IconLocalComputer({ className }) {
570
+ return /* @__PURE__ */ jsxs(
571
+ "svg",
572
+ {
573
+ "aria-hidden": true,
574
+ className,
575
+ fill: "none",
576
+ height: "18",
577
+ viewBox: "0 0 24 24",
578
+ width: "18",
579
+ xmlns: "http://www.w3.org/2000/svg",
580
+ children: [
581
+ /* @__PURE__ */ jsx(
582
+ "path",
583
+ {
584
+ d: "M5 6.8A2.8 2.8 0 0 1 7.8 4h8.4A2.8 2.8 0 0 1 19 6.8V14a2.8 2.8 0 0 1-2.8 2.8H7.8A2.8 2.8 0 0 1 5 14V6.8Z",
585
+ stroke: "currentColor",
586
+ strokeLinejoin: "round",
587
+ strokeWidth: "1.7"
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsx("path", { d: "M9.8 20h4.4", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.7" })
591
+ ]
592
+ }
593
+ );
594
+ }
595
+ function IconChevronDown({ className }) {
596
+ return /* @__PURE__ */ jsx(
597
+ "svg",
598
+ {
599
+ "aria-hidden": true,
600
+ className,
601
+ fill: "none",
602
+ height: "18",
603
+ viewBox: "0 0 24 24",
604
+ width: "18",
605
+ xmlns: "http://www.w3.org/2000/svg",
606
+ children: /* @__PURE__ */ jsx(
607
+ "path",
608
+ {
609
+ d: "m6 9 6 6 6-6",
610
+ stroke: "currentColor",
611
+ strokeLinecap: "round",
612
+ strokeLinejoin: "round",
613
+ strokeWidth: "1.8"
614
+ }
615
+ )
616
+ }
617
+ );
618
+ }
619
+ function IconGatewayOnline({ className }) {
620
+ return /* @__PURE__ */ jsxs(
621
+ "svg",
622
+ {
623
+ "aria-hidden": true,
624
+ className,
625
+ fill: "none",
626
+ height: "18",
627
+ viewBox: "0 0 24 24",
628
+ width: "18",
629
+ xmlns: "http://www.w3.org/2000/svg",
630
+ children: [
631
+ /* @__PURE__ */ jsx(
632
+ "path",
633
+ {
634
+ d: "M5 6.8A2.8 2.8 0 0 1 7.8 4h8.4A2.8 2.8 0 0 1 19 6.8V14a2.8 2.8 0 0 1-2.8 2.8H7.8A2.8 2.8 0 0 1 5 14V6.8Z",
635
+ stroke: "currentColor",
636
+ strokeLinejoin: "round",
637
+ strokeWidth: "1.7"
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsx(
641
+ "path",
642
+ {
643
+ d: "M9.8 20h4.4",
644
+ stroke: "currentColor",
645
+ strokeLinecap: "round",
646
+ strokeWidth: "1.7"
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx(
650
+ "path",
651
+ {
652
+ d: "m9.2 11.1 1.7 1.7 3.9-4",
653
+ stroke: "currentColor",
654
+ strokeLinecap: "round",
655
+ strokeLinejoin: "round",
656
+ strokeWidth: "1.8"
657
+ }
658
+ )
659
+ ]
660
+ }
661
+ );
662
+ }
663
+ function IconGatewayConnecting({ className }) {
664
+ return /* @__PURE__ */ jsxs(
665
+ "svg",
666
+ {
667
+ "aria-hidden": true,
668
+ className,
669
+ fill: "none",
670
+ height: "18",
671
+ viewBox: "0 0 24 24",
672
+ width: "18",
673
+ xmlns: "http://www.w3.org/2000/svg",
674
+ children: [
675
+ /* @__PURE__ */ jsx(
676
+ "path",
677
+ {
678
+ d: "M12 4.5v2.8M12 16.7V19.5M5.1 7.4l2 2M16.9 16.6l2 2M4.5 12h2.8M16.7 12H19.5M5.1 16.6l2-2M16.9 7.4l2-2",
679
+ stroke: "currentColor",
680
+ strokeLinecap: "round",
681
+ strokeWidth: "1.7"
682
+ }
683
+ ),
684
+ /* @__PURE__ */ jsx(
685
+ "path",
686
+ {
687
+ d: "M15.2 9.8a4.2 4.2 0 1 0 1.8 5.4",
688
+ stroke: "currentColor",
689
+ strokeLinecap: "round",
690
+ strokeWidth: "1.7"
691
+ }
692
+ )
693
+ ]
694
+ }
695
+ );
696
+ }
697
+ function IconGatewayOffline({ className }) {
698
+ return /* @__PURE__ */ jsxs(
699
+ "svg",
700
+ {
701
+ "aria-hidden": true,
702
+ className,
703
+ fill: "none",
704
+ height: "18",
705
+ viewBox: "0 0 24 24",
706
+ width: "18",
707
+ xmlns: "http://www.w3.org/2000/svg",
708
+ children: [
709
+ /* @__PURE__ */ jsx(
710
+ "path",
711
+ {
712
+ d: "M6.6 6.2A2.6 2.6 0 0 1 9.2 3.6h5.6a2.6 2.6 0 0 1 2.6 2.6v8.3",
713
+ stroke: "currentColor",
714
+ strokeLinecap: "round",
715
+ strokeLinejoin: "round",
716
+ strokeWidth: "1.7"
717
+ }
718
+ ),
719
+ /* @__PURE__ */ jsx(
720
+ "path",
721
+ {
722
+ d: "M6.6 10.8V15a2.6 2.6 0 0 0 2.6 2.6h5.6a2.6 2.6 0 0 0 2.6-2.6v-1.2",
723
+ stroke: "currentColor",
724
+ strokeLinecap: "round",
725
+ strokeLinejoin: "round",
726
+ strokeWidth: "1.7"
727
+ }
728
+ ),
729
+ /* @__PURE__ */ jsx(
730
+ "path",
731
+ {
732
+ d: "M8.9 20.4h6.2",
733
+ stroke: "currentColor",
734
+ strokeLinecap: "round",
735
+ strokeWidth: "1.7"
736
+ }
737
+ ),
738
+ /* @__PURE__ */ jsx(
739
+ "path",
740
+ {
741
+ d: "M4.2 4.2 19.8 19.8",
742
+ stroke: "currentColor",
743
+ strokeLinecap: "round",
744
+ strokeWidth: "1.8"
745
+ }
746
+ )
747
+ ]
748
+ }
749
+ );
750
+ }
751
+ function IconSend({ className }) {
752
+ return /* @__PURE__ */ jsx(
753
+ "svg",
754
+ {
755
+ "aria-hidden": true,
756
+ className,
757
+ fill: "none",
758
+ height: "18",
759
+ viewBox: "0 0 24 24",
760
+ width: "18",
761
+ xmlns: "http://www.w3.org/2000/svg",
762
+ children: /* @__PURE__ */ jsx(
763
+ "path",
764
+ {
765
+ d: "M12 19V5M5 12l7-7 7 7",
766
+ stroke: "currentColor",
767
+ strokeLinecap: "round",
768
+ strokeLinejoin: "round",
769
+ strokeWidth: "2"
770
+ }
771
+ )
772
+ }
773
+ );
774
+ }
775
+
776
+ // src/chat-ui/components/ChatAgentIdentity.tsx
777
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
778
+ function ChatAgentIdentity({ agentStatus }) {
779
+ const label = agentDisplayName(agentStatus);
780
+ return /* @__PURE__ */ jsxs2(
781
+ "button",
782
+ {
783
+ className: "chat-agent-identity",
784
+ title: "\u5207\u6362 Agent\uFF08\u5373\u5C06\u652F\u6301\uFF09",
785
+ type: "button",
786
+ onClick: (event) => event.preventDefault(),
787
+ children: [
788
+ /* @__PURE__ */ jsx2(IconLocalComputer, { className: "chat-agent-identity-icon" }),
789
+ /* @__PURE__ */ jsx2("span", { className: "chat-agent-identity-label", children: label }),
790
+ /* @__PURE__ */ jsx2(IconChevronDown, { "aria-hidden": true, className: "chat-agent-identity-chevron" })
791
+ ]
792
+ }
793
+ );
794
+ }
795
+
796
+ // src/chat-ui/components/ChatTopBar.tsx
797
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
798
+ function modelShortLabel(model) {
799
+ if (!model) return "\u6A21\u578B";
800
+ if (model.provider === "unavailable") return "\u672A\u914D\u7F6E";
801
+ return model.model || model.display_name || model.provider;
802
+ }
803
+ function connectionTone(status, gatewayLinked = false) {
804
+ if (gatewayLinked) return "ok";
805
+ const lower = status.trim().toLowerCase();
806
+ if (lower === "connected") return "ok";
807
+ if (lower === "connecting" || lower === "socket open") return "idle";
808
+ if (lower === "disconnected") return "idle";
809
+ if (lower.includes("error") || lower.includes("fail")) return "warn";
810
+ return "idle";
811
+ }
812
+ function ChatTopBar({
813
+ agentStatus,
814
+ connectionStatus,
815
+ gatewayLinked = false,
816
+ model,
817
+ debugUI,
818
+ canAbort,
819
+ onAbort
820
+ }) {
821
+ const tone = connectionTone(connectionStatus, gatewayLinked);
822
+ const statusLabel = gatewayLinked ? "Connected" : connectionStatus;
823
+ return /* @__PURE__ */ jsxs3("header", { className: "chat-topbar", children: [
824
+ /* @__PURE__ */ jsx3("div", { className: "chat-topbar-start", children: /* @__PURE__ */ jsx3(ChatAgentIdentity, { agentStatus }) }),
825
+ /* @__PURE__ */ jsxs3("div", { className: "chat-topbar-end", children: [
826
+ /* @__PURE__ */ jsx3("span", { className: "chat-topbar-model", title: model?.base_url ?? void 0, children: modelShortLabel(model) }),
827
+ /* @__PURE__ */ jsx3(
828
+ "span",
829
+ {
830
+ className: `chat-topbar-status-dot ${tone}`,
831
+ title: statusLabel,
832
+ "aria-label": statusLabel
833
+ }
834
+ ),
835
+ debugUI ? /* @__PURE__ */ jsx3("span", { className: "chat-topbar-debug", children: "Debug" }) : null,
836
+ canAbort ? /* @__PURE__ */ jsx3("button", { className: "chat-topbar-abort", onClick: onAbort, type: "button", children: "\u505C\u6B62" }) : null
837
+ ] })
838
+ ] });
839
+ }
840
+
841
+ // src/chat-ui/components/MessageComposer.tsx
842
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
843
+
844
+ // src/chat-ui/attachmentValidation.ts
845
+ var DEFAULT_COMPOSER_ATTACHMENT_POLICY = {
846
+ acceptMimeTypes: ["image/png", "image/jpeg", "image/webp"],
847
+ maxFileSizeBytes: 10 * 1024 * 1024,
848
+ maxFiles: 4
849
+ };
850
+ var MIME_EXTENSIONS = {
851
+ "image/jpeg": [".jpg", ".jpeg"],
852
+ "image/png": [".png"],
853
+ "image/webp": [".webp"]
854
+ };
855
+ function normalizeComposerAttachmentPolicy(policy = {}) {
856
+ return {
857
+ acceptMimeTypes: policy.acceptMimeTypes?.length ? policy.acceptMimeTypes.map((type) => type.toLowerCase()) : DEFAULT_COMPOSER_ATTACHMENT_POLICY.acceptMimeTypes,
858
+ maxFileSizeBytes: policy.maxFileSizeBytes && policy.maxFileSizeBytes > 0 ? policy.maxFileSizeBytes : DEFAULT_COMPOSER_ATTACHMENT_POLICY.maxFileSizeBytes,
859
+ maxFiles: policy.maxFiles && policy.maxFiles > 0 ? policy.maxFiles : DEFAULT_COMPOSER_ATTACHMENT_POLICY.maxFiles
860
+ };
861
+ }
862
+ function composerAcceptAttribute(policy = {}) {
863
+ const normalized = normalizeComposerAttachmentPolicy(policy);
864
+ return normalized.acceptMimeTypes.join(",");
865
+ }
866
+ function validateComposerFiles(files, existingCount = 0, policy = {}) {
867
+ const normalized = normalizeComposerAttachmentPolicy(policy);
868
+ const accepted = [];
869
+ const rejected = [];
870
+ for (const file of files) {
871
+ if (existingCount + accepted.length >= normalized.maxFiles) {
872
+ rejected.push({
873
+ fileName: file.name,
874
+ reason: "count",
875
+ message: `\u6700\u591A\u53EF\u4E0A\u4F20 ${normalized.maxFiles} \u5F20\u56FE\u7247\u3002`
876
+ });
877
+ continue;
878
+ }
879
+ if (!matchesAcceptedMimeType(file, normalized.acceptMimeTypes)) {
880
+ rejected.push({
881
+ fileName: file.name,
882
+ reason: "type",
883
+ message: `\u4EC5\u652F\u6301 ${formatAcceptedMimeTypes(normalized.acceptMimeTypes)} \u56FE\u7247\u3002`
884
+ });
885
+ continue;
886
+ }
887
+ if (file.size > normalized.maxFileSizeBytes) {
888
+ rejected.push({
889
+ fileName: file.name,
890
+ reason: "size",
891
+ message: `\u56FE\u7247\u9700\u5C0F\u4E8E ${formatBytes(normalized.maxFileSizeBytes)}\u3002`
892
+ });
893
+ continue;
894
+ }
895
+ accepted.push(file);
896
+ }
897
+ return {
898
+ accepted,
899
+ rejected,
900
+ error: firstComposerFileError(rejected)
901
+ };
902
+ }
903
+ function formatBytes(bytes) {
904
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
905
+ if (bytes < 1024) return `${bytes} B`;
906
+ const units = ["KB", "MB", "GB"];
907
+ let value = bytes / 1024;
908
+ let unitIndex = 0;
909
+ while (value >= 1024 && unitIndex < units.length - 1) {
910
+ value /= 1024;
911
+ unitIndex += 1;
912
+ }
913
+ const rounded = value >= 10 ? Math.round(value) : Math.round(value * 10) / 10;
914
+ return `${rounded} ${units[unitIndex]}`;
915
+ }
916
+ function firstComposerFileError(rejected) {
917
+ const first = rejected[0];
918
+ if (!first) return void 0;
919
+ return rejected.length === 1 ? `${first.fileName}: ${first.message}` : `${first.fileName}: ${first.message} \u53E6\u6709 ${rejected.length - 1} \u4E2A\u6587\u4EF6\u672A\u6DFB\u52A0\u3002`;
920
+ }
921
+ function matchesAcceptedMimeType(file, acceptMimeTypes) {
922
+ const fileType = file.type.toLowerCase();
923
+ if (fileType && acceptMimeTypes.includes(fileType)) return true;
924
+ const name = file.name.toLowerCase();
925
+ return acceptMimeTypes.some(
926
+ (type) => (MIME_EXTENSIONS[type] ?? []).some((extension) => name.endsWith(extension))
927
+ );
928
+ }
929
+ function formatAcceptedMimeTypes(acceptMimeTypes) {
930
+ const labels = acceptMimeTypes.map((type) => {
931
+ if (type === "image/jpeg") return "JPG";
932
+ if (type === "image/png") return "PNG";
933
+ if (type === "image/webp") return "WebP";
934
+ return type;
935
+ });
936
+ if (labels.length <= 1) return labels[0] ?? "\u56FE\u7247";
937
+ return `${labels.slice(0, -1).join("\u3001")} \u6216 ${labels[labels.length - 1]}`;
938
+ }
939
+
940
+ // src/chat-ui/components/MessageComposer.tsx
941
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
942
+ var MessageComposer = forwardRef(
943
+ function MessageComposer2({
944
+ value,
945
+ attachments = [],
946
+ error,
947
+ attachmentPolicy,
948
+ disabled,
949
+ layout = "default",
950
+ placeholder = "\u6709\u95EE\u9898\uFF0C\u5C3D\u7BA1\u95EE",
951
+ showDisclaimer = true,
952
+ onChange,
953
+ onFilesSelected,
954
+ onRemoveAttachment,
955
+ onSubmit
956
+ }, ref) {
957
+ const [isComposing, setIsComposing] = useState(false);
958
+ const [localError, setLocalError] = useState();
959
+ const inputRef = useRef(null);
960
+ const fileRef = useRef(null);
961
+ const heroLayout = layout === "hero";
962
+ const acceptAttribute = composerAcceptAttribute(attachmentPolicy);
963
+ const displayError = error ?? localError;
964
+ useImperativeHandle(ref, () => ({
965
+ openFilePicker: () => fileRef.current?.click()
966
+ }));
967
+ useEffect(() => {
968
+ if (!disabled) {
969
+ inputRef.current?.focus();
970
+ }
971
+ }, [disabled]);
972
+ function submitIfReady() {
973
+ if (disabled || !value.trim() && attachments.length === 0) return;
974
+ setLocalError(void 0);
975
+ onSubmit();
976
+ }
977
+ function addFiles(files) {
978
+ const validation = validateComposerFiles(files, attachments.length, attachmentPolicy);
979
+ setLocalError(validation.error);
980
+ if (validation.accepted.length) {
981
+ onFilesSelected?.(validation.accepted);
982
+ }
983
+ }
984
+ return /* @__PURE__ */ jsx4("div", { className: heroLayout ? "chat-composer-wrap chat-composer-wrap-hero" : "chat-composer-wrap", children: /* @__PURE__ */ jsxs4("div", { className: heroLayout ? "chat-column chat-column-hero" : "chat-column", children: [
985
+ /* @__PURE__ */ jsxs4(
986
+ "form",
987
+ {
988
+ className: heroLayout ? "message-composer message-composer-hero" : "message-composer",
989
+ onSubmit: (event) => {
990
+ event.preventDefault();
991
+ submitIfReady();
992
+ },
993
+ children: [
994
+ onFilesSelected ? /* @__PURE__ */ jsxs4(Fragment, { children: [
995
+ /* @__PURE__ */ jsx4(
996
+ "input",
997
+ {
998
+ ref: fileRef,
999
+ accept: acceptAttribute,
1000
+ className: "file-input",
1001
+ disabled,
1002
+ multiple: true,
1003
+ onChange: (event) => {
1004
+ const files = Array.from(event.target.files ?? []);
1005
+ if (files.length) addFiles(files);
1006
+ event.target.value = "";
1007
+ },
1008
+ type: "file"
1009
+ }
1010
+ ),
1011
+ /* @__PURE__ */ jsx4(
1012
+ "button",
1013
+ {
1014
+ "aria-label": "\u4E0A\u4F20\u7167\u7247",
1015
+ className: "composer-icon-btn composer-photo-btn",
1016
+ disabled,
1017
+ onClick: () => fileRef.current?.click(),
1018
+ type: "button",
1019
+ children: /* @__PURE__ */ jsx4(IconImageUpload, {})
1020
+ }
1021
+ )
1022
+ ] }) : null,
1023
+ /* @__PURE__ */ jsx4(
1024
+ "textarea",
1025
+ {
1026
+ ref: inputRef,
1027
+ value,
1028
+ disabled,
1029
+ onChange: (event) => onChange(event.target.value),
1030
+ onCompositionEnd: () => setIsComposing(false),
1031
+ onCompositionStart: () => setIsComposing(true),
1032
+ onKeyDown: (event) => {
1033
+ if (event.key !== "Enter" || event.shiftKey) return;
1034
+ if (event.nativeEvent.isComposing || isComposing) return;
1035
+ event.preventDefault();
1036
+ submitIfReady();
1037
+ },
1038
+ placeholder,
1039
+ rows: heroLayout ? 3 : 1
1040
+ }
1041
+ ),
1042
+ /* @__PURE__ */ jsx4(
1043
+ "button",
1044
+ {
1045
+ "aria-label": "\u53D1\u9001\u6D88\u606F",
1046
+ className: "composer-send-btn",
1047
+ disabled: disabled || !value.trim() && attachments.length === 0,
1048
+ type: "submit",
1049
+ children: /* @__PURE__ */ jsx4(IconSend, {})
1050
+ }
1051
+ )
1052
+ ]
1053
+ }
1054
+ ),
1055
+ attachments.length ? /* @__PURE__ */ jsx4("div", { className: "composer-attachments", children: attachments.map((attachment) => /* @__PURE__ */ jsxs4("figure", { children: [
1056
+ /* @__PURE__ */ jsx4("img", { alt: attachment.name, src: attachment.previewUrl }),
1057
+ /* @__PURE__ */ jsx4("figcaption", { children: attachment.name }),
1058
+ /* @__PURE__ */ jsx4(
1059
+ "button",
1060
+ {
1061
+ "aria-label": `\u79FB\u9664 ${attachment.name}`,
1062
+ onClick: () => onRemoveAttachment?.(attachment.id),
1063
+ type: "button",
1064
+ children: "\u79FB\u9664"
1065
+ }
1066
+ )
1067
+ ] }, attachment.id)) }) : null,
1068
+ displayError ? /* @__PURE__ */ jsx4("p", { className: "composer-error", children: displayError }) : null,
1069
+ showDisclaimer ? /* @__PURE__ */ jsx4("p", { className: "composer-disclaimer", children: "CathyGO \u4E5F\u53EF\u80FD\u4F1A\u72AF\u9519\uFF0C\u8BF7\u6838\u67E5\u91CD\u8981\u4FE1\u606F\u3002" }) : null
1070
+ ] }) });
1071
+ }
1072
+ );
1073
+
1074
+ // src/features/chat/chatHomeModes.ts
1075
+ var CHAT_HOME_MODES = [
1076
+ {
1077
+ id: "photo_question",
1078
+ label: "\u62CD\u9898\u8BB2\u89E3",
1079
+ placeholder: "\u4E0A\u4F20\u9898\u76EE\u7167\u7247\uFF0C\u6216\u63CF\u8FF0\u4F60\u60F3\u5F04\u61C2\u7684\u5730\u65B9",
1080
+ emphasizeAttachment: true
1081
+ },
1082
+ {
1083
+ id: "paper_analysis",
1084
+ label: "\u8BD5\u5377\u5206\u6790",
1085
+ placeholder: "\u4E0A\u4F20\u8BD5\u5377\u3001\u9519\u9898\uFF0C\u6216\u63CF\u8FF0\u4F60\u60F3\u5206\u6790\u7684\u5185\u5BB9"
1086
+ },
1087
+ {
1088
+ id: "knowledge_learning",
1089
+ label: "\u77E5\u8BC6\u70B9\u5B66\u4E60",
1090
+ placeholder: "\u8F93\u5165\u4E00\u4E2A\u77E5\u8BC6\u70B9\uFF0C\u6216\u63CF\u8FF0\u4F60\u60F3\u7406\u89E3\u7684\u6982\u5FF5"
1091
+ }
1092
+ ];
1093
+ var DEFAULT_CHAT_HOME_MODE_ID = "photo_question";
1094
+ function findChatHomeMode(id) {
1095
+ return CHAT_HOME_MODES.find((mode) => mode.id === id) ?? CHAT_HOME_MODES[0];
1096
+ }
1097
+
1098
+ // src/features/chat/ChatHomeView.tsx
1099
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1100
+ function ChatHomeView({
1101
+ draft,
1102
+ attachments,
1103
+ attachmentPolicy,
1104
+ error,
1105
+ busy,
1106
+ connectionStatus,
1107
+ gatewayLinked,
1108
+ model,
1109
+ agentStatus,
1110
+ avatarSrc,
1111
+ onDraftChange,
1112
+ onFilesSelected,
1113
+ onRemoveAttachment,
1114
+ onSend
1115
+ }) {
1116
+ const composerRef = useRef2(null);
1117
+ const [activeModeId, setActiveModeId] = useState2(DEFAULT_CHAT_HOME_MODE_ID);
1118
+ const activeMode = findChatHomeMode(activeModeId);
1119
+ function selectMode(modeId) {
1120
+ setActiveModeId(modeId);
1121
+ const mode = findChatHomeMode(modeId);
1122
+ if (mode.emphasizeAttachment) {
1123
+ composerRef.current?.openFilePicker();
1124
+ }
1125
+ }
1126
+ return /* @__PURE__ */ jsxs5("section", { className: "cathygo-chat-panel chat-home", children: [
1127
+ /* @__PURE__ */ jsx5(
1128
+ ChatTopBar,
1129
+ {
1130
+ connectionStatus,
1131
+ gatewayLinked,
1132
+ model,
1133
+ agentStatus
1134
+ }
1135
+ ),
1136
+ /* @__PURE__ */ jsxs5("div", { className: "chat-home-center", children: [
1137
+ avatarSrc ? /* @__PURE__ */ jsx5(
1138
+ "img",
1139
+ {
1140
+ alt: "",
1141
+ className: "chat-home-avatar",
1142
+ height: 88,
1143
+ src: avatarSrc,
1144
+ width: 88
1145
+ }
1146
+ ) : null,
1147
+ /* @__PURE__ */ jsx5("h1", { className: "chat-home-title", children: "\u4ECA\u5929\u60F3\u5B66\u4F1A\u4EC0\u4E48\uFF1F" }),
1148
+ /* @__PURE__ */ jsx5("div", { className: "chat-home-mode-tabs", role: "tablist", children: CHAT_HOME_MODES.map((mode) => /* @__PURE__ */ jsx5(
1149
+ "button",
1150
+ {
1151
+ "aria-selected": mode.id === activeModeId,
1152
+ className: mode.id === activeModeId ? "chat-home-mode-tab active" : "chat-home-mode-tab",
1153
+ disabled: busy,
1154
+ role: "tab",
1155
+ type: "button",
1156
+ onClick: () => selectMode(mode.id),
1157
+ children: mode.label
1158
+ },
1159
+ mode.id
1160
+ )) }),
1161
+ /* @__PURE__ */ jsx5("div", { className: "chat-home-composer", children: /* @__PURE__ */ jsx5(
1162
+ MessageComposer,
1163
+ {
1164
+ ref: composerRef,
1165
+ attachments,
1166
+ attachmentPolicy,
1167
+ disabled: busy,
1168
+ error,
1169
+ layout: "hero",
1170
+ placeholder: activeMode.placeholder,
1171
+ value: draft,
1172
+ onChange: onDraftChange,
1173
+ onFilesSelected,
1174
+ onRemoveAttachment,
1175
+ onSubmit: () => onSend(activeModeId)
1176
+ }
1177
+ ) })
1178
+ ] })
1179
+ ] });
1180
+ }
1181
+
1182
+ // src/chat-ui/previewText.ts
1183
+ function stripMathForPreview(text, maxLen = 80) {
1184
+ const plain = text.replace(/\$\$[\s\S]*?\$\$/g, " ").replace(/\$[^$\n]+?\$/g, " ").replace(/\\\[[\s\S]*?\\\]/g, " ").replace(/\\\([\s\S]*?\\\)/g, " ").replace(/\s+/g, " ").trim();
1185
+ if (plain.length <= maxLen) return plain;
1186
+ return `${plain.slice(0, maxLen - 1)}\u2026`;
1187
+ }
1188
+
1189
+ // src/features/chat/ChatListView.tsx
1190
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1191
+ function ChatListView({ chats, onOpen, onNewChat }) {
1192
+ return /* @__PURE__ */ jsxs6("section", { className: "cathygo-chat-panel list-workspace", children: [
1193
+ /* @__PURE__ */ jsxs6("header", { className: "page-topbar", children: [
1194
+ /* @__PURE__ */ jsx6("h1", { className: "page-topbar-title", children: "\u5BF9\u8BDD\u5386\u53F2" }),
1195
+ /* @__PURE__ */ jsx6("button", { className: "primary-action", onClick: onNewChat, type: "button", children: "\u65B0\u5BF9\u8BDD" })
1196
+ ] }),
1197
+ /* @__PURE__ */ jsx6("div", { className: "chat-column list-column", children: chats.length ? /* @__PURE__ */ jsx6("div", { className: "history-list", children: chats.map((chat) => /* @__PURE__ */ jsxs6(
1198
+ "button",
1199
+ {
1200
+ className: "history-row",
1201
+ onClick: () => onOpen(chat.session_id),
1202
+ type: "button",
1203
+ children: [
1204
+ /* @__PURE__ */ jsx6("strong", { children: chat.title }),
1205
+ /* @__PURE__ */ jsx6("span", { children: chat.preview ? stripMathForPreview(chat.preview, 120) : "\u6682\u65E0\u9884\u89C8" }),
1206
+ /* @__PURE__ */ jsxs6("small", { children: [
1207
+ chat.message_count,
1208
+ " \u6761\u6D88\u606F"
1209
+ ] })
1210
+ ]
1211
+ },
1212
+ chat.session_id
1213
+ )) }) : /* @__PURE__ */ jsx6("p", { className: "empty", children: "\u6682\u65E0\u5BF9\u8BDD\u3002\u521B\u5EFA\u65B0\u5BF9\u8BDD\u540E\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002" }) })
1214
+ ] });
1215
+ }
1216
+
1217
+ // src/chat-ui/components/ChatTranscript.tsx
1218
+ import { useEffect as useEffect4, useRef as useRef3 } from "react";
1219
+
1220
+ // src/chat-ui/components/AgentActivityTimeline.tsx
1221
+ import { useEffect as useEffect2, useState as useState3 } from "react";
1222
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1223
+ function AgentActivityTimeline({ activities, progress }) {
1224
+ const tick = useTimelineTick(progress, activities);
1225
+ if (!activities.length && !progress) return null;
1226
+ return /* @__PURE__ */ jsxs7("div", { "aria-label": "Agent \u8FD0\u884C\u8F68\u8FF9", className: "agent-activity-timeline", children: [
1227
+ progress ? /* @__PURE__ */ jsx7(AgentProgressRow, { progress, now: tick }) : null,
1228
+ progress?.thinking ? /* @__PURE__ */ jsx7(ReasoningPanel, { thinking: progress.thinking }) : null,
1229
+ activities.length ? /* @__PURE__ */ jsx7("div", { className: "agent-activity-list", children: activities.map((activity) => /* @__PURE__ */ jsx7(AgentActivityRow, { activity, now: tick }, activity.activity_id)) }) : null
1230
+ ] });
1231
+ }
1232
+ function ReasoningPanel({ thinking }) {
1233
+ const [expanded, setExpanded] = useState3(thinking.expanded);
1234
+ const count = thinking.lines.length;
1235
+ if (!count && !thinking.hiddenCount) return null;
1236
+ const hiddenSummary = hiddenReasoningSummary(thinking);
1237
+ return /* @__PURE__ */ jsxs7("section", { className: `agent-reasoning-panel ${expanded ? "expanded" : "collapsed"}`, children: [
1238
+ /* @__PURE__ */ jsxs7(
1239
+ "button",
1240
+ {
1241
+ "aria-expanded": expanded,
1242
+ className: "agent-reasoning-toggle",
1243
+ onClick: () => setExpanded((value) => !value),
1244
+ type: "button",
1245
+ children: [
1246
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "agent-reasoning-chevron", children: expanded ? "\u2304" : "\u203A" }),
1247
+ /* @__PURE__ */ jsx7("strong", { children: "\u601D\u8003\u4E2D" }),
1248
+ /* @__PURE__ */ jsx7("span", { children: count ? `${count} \u6761` : hiddenSummary }),
1249
+ count && thinking.hiddenCount ? /* @__PURE__ */ jsx7("span", { children: `\u63A8\u7406\u7247\u6BB5 ${thinking.hiddenCount}` }) : null
1250
+ ]
1251
+ }
1252
+ ),
1253
+ expanded ? /* @__PURE__ */ jsx7("div", { className: "agent-reasoning-lines", children: [...thinking.lines, ...hiddenSummary ? [hiddenSummary] : []].map((line, index) => /* @__PURE__ */ jsxs7("div", { className: "agent-reasoning-line", children: [
1254
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "agent-activity-rail" }),
1255
+ /* @__PURE__ */ jsx7("span", { children: line })
1256
+ ] }, `${index}:${line}`)) }) : null
1257
+ ] });
1258
+ }
1259
+ function hiddenReasoningSummary(thinking) {
1260
+ if (!thinking.hiddenCount) return void 0;
1261
+ const elapsedMs = thinking.hiddenStartedAt ? Date.now() - Date.parse(thinking.hiddenStartedAt) : 0;
1262
+ if (elapsedMs >= 4e4 || thinking.hiddenCount >= 80) {
1263
+ return "\u6A21\u578B\u6B63\u5728\u6DF1\u5EA6\u63A8\u7406\uFF0C\u5C1A\u672A\u5F00\u59CB\u751F\u6210\u6700\u7EC8\u56DE\u7B54";
1264
+ }
1265
+ if (elapsedMs >= 2e4 || thinking.hiddenCount >= 32) {
1266
+ return "\u8FD9\u4E00\u6B65\u8017\u65F6\u8F83\u957F\uFF0C\u4ECD\u5728\u5904\u7406";
1267
+ }
1268
+ return "\u6B63\u5728\u63A8\u7406\u590D\u6742\u6B65\u9AA4";
1269
+ }
1270
+ function AgentProgressRow({ progress, now }) {
1271
+ const seconds = elapsedSeconds(progress.startedAt, void 0, now);
1272
+ const duration = seconds === void 0 ? void 0 : formatDuration(seconds);
1273
+ const detail = progress.detail ?? progressWaitDetail(progress, seconds);
1274
+ return /* @__PURE__ */ jsxs7("div", { className: `agent-progress-row ${progress.phase} ${progress.status}`, children: [
1275
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "agent-progress-spinner" }),
1276
+ /* @__PURE__ */ jsxs7("span", { className: "agent-progress-copy", children: [
1277
+ /* @__PURE__ */ jsx7("strong", { children: progress.summary }),
1278
+ detail ? /* @__PURE__ */ jsx7("small", { children: detail }) : null
1279
+ ] }),
1280
+ duration ? /* @__PURE__ */ jsx7("span", { className: "agent-activity-duration", children: duration }) : null
1281
+ ] });
1282
+ }
1283
+ function AgentActivityRow({ activity, now }) {
1284
+ const duration = durationLabel(activity.started_at, activity.completed_at, now);
1285
+ const summary = activity.summary || activity.title;
1286
+ return /* @__PURE__ */ jsxs7("div", { className: `agent-activity ${activity.kind} ${activity.status}`, children: [
1287
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "agent-activity-rail" }),
1288
+ /* @__PURE__ */ jsx7("span", { className: "agent-activity-kind", children: kindLabel(activity.kind) }),
1289
+ /* @__PURE__ */ jsxs7("span", { className: "agent-activity-copy", children: [
1290
+ /* @__PURE__ */ jsx7("strong", { children: summary }),
1291
+ activity.detail ? /* @__PURE__ */ jsx7("small", { children: activity.detail }) : null
1292
+ ] }),
1293
+ duration ? /* @__PURE__ */ jsx7("span", { className: "agent-activity-duration", children: duration }) : null,
1294
+ activity.status === "failed" && activity.error?.message ? /* @__PURE__ */ jsx7("small", { className: "agent-activity-error", children: activity.error.message }) : null
1295
+ ] });
1296
+ }
1297
+ function useTimelineTick(progress, activities) {
1298
+ const [now, setNow] = useState3(() => Date.now());
1299
+ const hasRunning = Boolean(progress?.status === "running") || activities.some((activity) => activity.status === "running");
1300
+ useEffect2(() => {
1301
+ if (!hasRunning) return void 0;
1302
+ const id = window.setInterval(() => setNow(Date.now()), 1e3);
1303
+ return () => window.clearInterval(id);
1304
+ }, [hasRunning]);
1305
+ return now;
1306
+ }
1307
+ function kindLabel(kind) {
1308
+ if (kind === "model") return "model";
1309
+ if (kind === "skill") return "skill";
1310
+ if (kind === "vision") return "vision";
1311
+ if (kind === "memory") return "memory";
1312
+ if (kind === "tool") return "tool";
1313
+ return "system";
1314
+ }
1315
+ function durationLabel(start, end, now) {
1316
+ const seconds = elapsedSeconds(start, end, now);
1317
+ return seconds === void 0 ? void 0 : formatDuration(seconds);
1318
+ }
1319
+ function elapsedSeconds(start, end, now) {
1320
+ const startMs = parseTime(start);
1321
+ if (startMs === void 0) return void 0;
1322
+ const endMs = parseTime(end) ?? now;
1323
+ return Math.max(0, (endMs - startMs) / 1e3);
1324
+ }
1325
+ function formatDuration(seconds) {
1326
+ return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
1327
+ }
1328
+ function progressWaitDetail(progress, seconds) {
1329
+ if (seconds === void 0) return void 0;
1330
+ if (progress.phase === "queued" && seconds >= 3) {
1331
+ return "\u6B63\u5728\u8FDE\u63A5 CathyGO \u8FD0\u884C\u73AF\u5883";
1332
+ }
1333
+ if (progress.phase === "model" && seconds >= 8) {
1334
+ return progress.summary.includes("\u770B\u56FE") ? "\u56FE\u7247\u9898\u5206\u6790\u901A\u5E38\u9700\u8981\u5341\u51E0\u79D2\uFF0C\u8BF7\u7A0D\u7B49" : "\u6B63\u5728\u7B49\u5F85\u6A21\u578B\u8FD4\u56DE\uFF0C\u590D\u6742\u95EE\u9898\u4F1A\u591A\u7B49\u51E0\u79D2";
1335
+ }
1336
+ if (progress.phase === "generating" && seconds >= 8) {
1337
+ return "\u6B63\u5728\u6574\u7406\u6B65\u9AA4\uFF0C\u7A0D\u7B49\u7247\u523B";
1338
+ }
1339
+ return void 0;
1340
+ }
1341
+ function parseTime(value) {
1342
+ if (!value) return void 0;
1343
+ const parsed = Date.parse(value);
1344
+ return Number.isFinite(parsed) ? parsed : void 0;
1345
+ }
1346
+
1347
+ // src/chat-ui/components/ChatMessage.tsx
1348
+ import { useState as useState4 } from "react";
1349
+
1350
+ // src/chat-ui/components/MathMarkdown.tsx
1351
+ import { memo } from "react";
1352
+ import { createMathPlugin } from "@streamdown/math";
1353
+ import { Streamdown } from "streamdown";
1354
+
1355
+ // src/chat-ui/mathMarkdown.ts
1356
+ var CODE_SEGMENT_PATTERN = /(```[\s\S]*?```|`[^`\n]*`)/g;
1357
+ var LATEX_COMMAND_FIXES = [
1358
+ ["$frac{", "$\\frac{"],
1359
+ ["$\\$frac{", "$\\frac{"],
1360
+ ["$$frac{", "$$\\frac{"],
1361
+ ["\\\\frac{", "\\frac{"],
1362
+ ["\\\\neq", "\\neq"],
1363
+ ["\\\\times", "\\times"],
1364
+ ["\\\\cdot", "\\cdot"]
1365
+ ];
1366
+ function fixCommonLatexMistakes(source) {
1367
+ if (!source) return source;
1368
+ let out = source;
1369
+ for (const [from, to] of LATEX_COMMAND_FIXES) {
1370
+ out = out.split(from).join(to);
1371
+ }
1372
+ return out;
1373
+ }
1374
+ function normalizeMathDelimiters(source) {
1375
+ if (!source) return source;
1376
+ return source.split(CODE_SEGMENT_PATTERN).map((segment) => {
1377
+ if (segment.startsWith("`")) return segment;
1378
+ return normalizeTextSegment(segment);
1379
+ }).join("");
1380
+ }
1381
+ function normalizeTextSegment(segment) {
1382
+ return segment.replace(/\\{1,2}\[([\s\S]*?)\\{1,2}\]/g, (_match, body) => {
1383
+ return `
1384
+
1385
+ $$
1386
+ ${cleanMathBody(body)}
1387
+ $$
1388
+
1389
+ `;
1390
+ }).replace(/\\{1,2}\(([\s\S]*?)\\{1,2}\)/g, (_match, body) => {
1391
+ return `$${cleanMathBody(body)}$`;
1392
+ });
1393
+ }
1394
+ function cleanMathBody(body) {
1395
+ return body.trim().replace(/\\\\(?=[A-Za-z])/g, "\\");
1396
+ }
1397
+ function prepareMathMarkdown(source) {
1398
+ return normalizeMathDelimiters(fixCommonLatexMistakes(source));
1399
+ }
1400
+
1401
+ // src/chat-ui/components/MathMarkdown.tsx
1402
+ import { jsx as jsx8 } from "react/jsx-runtime";
1403
+ var math = createMathPlugin({
1404
+ errorColor: "#dc2626",
1405
+ // Align with iOS/Gateway protocol: model emits inline `$...$` formulas.
1406
+ singleDollarTextMath: true
1407
+ });
1408
+ var MathMarkdown = memo(function MathMarkdown2({ content }) {
1409
+ return /* @__PURE__ */ jsx8(Streamdown, { className: "message-markdown", plugins: { math }, children: prepareMathMarkdown(content) });
1410
+ });
1411
+
1412
+ // src/chat-ui/components/ChatMessage.tsx
1413
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1414
+ function ChatMessage({ message }) {
1415
+ const [copied, setCopied] = useState4(false);
1416
+ const isUser = message.role === "user";
1417
+ const isAssistant = message.role === "assistant";
1418
+ async function copyContent() {
1419
+ if (!message.content) return;
1420
+ try {
1421
+ await navigator.clipboard.writeText(message.content);
1422
+ setCopied(true);
1423
+ window.setTimeout(() => setCopied(false), 1500);
1424
+ } catch {
1425
+ }
1426
+ }
1427
+ return /* @__PURE__ */ jsxs8(
1428
+ "article",
1429
+ {
1430
+ "aria-label": isUser ? "\u4F60\u7684\u6D88\u606F" : "CathyGO \u56DE\u590D",
1431
+ className: `chat-message ${message.role} ${message.status ?? "done"}`,
1432
+ children: [
1433
+ /* @__PURE__ */ jsxs8("div", { className: "chat-message-body", children: [
1434
+ message.content ? isAssistant ? /* @__PURE__ */ jsx9(MathMarkdown, { content: message.content }) : /* @__PURE__ */ jsx9("p", { children: message.content }) : null,
1435
+ message.parts?.some((part) => part.type === "image") ? /* @__PURE__ */ jsx9("div", { className: "message-images", children: message.parts.map(
1436
+ (part) => part.type === "image" ? /* @__PURE__ */ jsx9(
1437
+ "img",
1438
+ {
1439
+ alt: part.attachment.original_name ?? "Attached image",
1440
+ src: part.attachment.uri
1441
+ },
1442
+ part.attachment_id
1443
+ ) : null
1444
+ ) }) : null,
1445
+ message.status === "streaming" ? /* @__PURE__ */ jsx9("small", { className: "chat-message-meta", children: "\u6B63\u5728\u751F\u6210\u2026" }) : null,
1446
+ message.status === "error" ? /* @__PURE__ */ jsx9("small", { className: "chat-message-meta error", children: "\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" }) : null
1447
+ ] }),
1448
+ isAssistant && message.content ? /* @__PURE__ */ jsx9("div", { className: "message-actions", children: /* @__PURE__ */ jsx9(
1449
+ "button",
1450
+ {
1451
+ "aria-label": "\u590D\u5236\u56DE\u590D",
1452
+ className: "message-action-btn",
1453
+ onClick: () => void copyContent(),
1454
+ type: "button",
1455
+ children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236"
1456
+ }
1457
+ ) }) : null
1458
+ ]
1459
+ }
1460
+ );
1461
+ }
1462
+
1463
+ // src/chat-ui/components/ScrollToBottomButton.tsx
1464
+ import { useEffect as useEffect3, useState as useState5 } from "react";
1465
+ import { jsx as jsx10 } from "react/jsx-runtime";
1466
+ function ScrollToBottomButton({
1467
+ containerRef,
1468
+ endRef,
1469
+ watchKey
1470
+ }) {
1471
+ const [visible, setVisible] = useState5(false);
1472
+ useEffect3(() => {
1473
+ const el = containerRef.current;
1474
+ if (!el) return;
1475
+ function update() {
1476
+ const node = containerRef.current;
1477
+ if (!node) return;
1478
+ const distance = node.scrollHeight - node.scrollTop - node.clientHeight;
1479
+ setVisible(distance > 200);
1480
+ }
1481
+ update();
1482
+ el.addEventListener("scroll", update, { passive: true });
1483
+ return () => el.removeEventListener("scroll", update);
1484
+ }, [containerRef, watchKey]);
1485
+ if (!visible) return null;
1486
+ return /* @__PURE__ */ jsx10(
1487
+ "button",
1488
+ {
1489
+ "aria-label": "\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F",
1490
+ className: "scroll-to-bottom",
1491
+ onClick: () => endRef.current?.scrollIntoView({ behavior: "smooth", block: "end" }),
1492
+ type: "button",
1493
+ children: "\u2193"
1494
+ }
1495
+ );
1496
+ }
1497
+
1498
+ // src/chat-ui/components/ChatTranscript.tsx
1499
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1500
+ var SUGGESTIONS = [
1501
+ "1/2 + 1/3 \u4E3A\u4EC0\u4E48\u4E0D\u80FD\u7B49\u4E8E 2/5\uFF1F",
1502
+ "\u5E2E\u6211\u8BB2\u89E3\u4E00\u9053\u5206\u5F0F\u5316\u7B80\u9898",
1503
+ "\u5BC6\u5EA6\u5355\u4F4D\u600E\u4E48\u6362\u7B97\uFF1F"
1504
+ ];
1505
+ function ChatTranscript({
1506
+ messages,
1507
+ activitiesByTurnId = {},
1508
+ progressByTurnId = {},
1509
+ activeTurnId,
1510
+ onSuggest
1511
+ }) {
1512
+ const containerRef = useRef3(null);
1513
+ const endRef = useRef3(null);
1514
+ const activeTurnHasAssistant = Boolean(
1515
+ activeTurnId && messages.some((message) => message.role === "assistant" && message.turnId === activeTurnId)
1516
+ );
1517
+ const activityWatchKey = Object.values(activitiesByTurnId).map((activities) => activities.map((activity) => `${activity.activity_id}:${activity.status}`).join("|")).join(";");
1518
+ const progressWatchKey = Object.values(progressByTurnId).map((progress) => `${progress.turnId}:${progress.phase}:${progress.summary}`).join(";");
1519
+ useEffect4(() => {
1520
+ endRef.current?.scrollIntoView({ block: "end" });
1521
+ }, [messages, activityWatchKey, progressWatchKey]);
1522
+ return /* @__PURE__ */ jsxs9("div", { className: "chat-transcript-wrap", children: [
1523
+ /* @__PURE__ */ jsx11("section", { className: "chat-transcript", ref: containerRef, children: /* @__PURE__ */ jsxs9("div", { className: "chat-column", children: [
1524
+ messages.length === 0 ? /* @__PURE__ */ jsxs9("div", { className: "empty-chat", children: [
1525
+ /* @__PURE__ */ jsx11("h2", { children: "\u5F00\u59CB\u4F60\u7684\u5B66\u4E60\u5BF9\u8BDD" }),
1526
+ /* @__PURE__ */ jsx11("p", { children: "\u5411 CathyGO \u63D0\u95EE\uFF0C\u83B7\u53D6\u5206\u6B65\u8BB2\u89E3\u4E0E\u6E05\u6670\u63A8\u5BFC\u3002" }),
1527
+ onSuggest ? /* @__PURE__ */ jsx11("div", { className: "suggestion-chips", children: SUGGESTIONS.map((text) => /* @__PURE__ */ jsx11(
1528
+ "button",
1529
+ {
1530
+ className: "suggestion-chip",
1531
+ onClick: () => onSuggest(text),
1532
+ type: "button",
1533
+ children: text
1534
+ },
1535
+ text
1536
+ )) }) : null
1537
+ ] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
1538
+ messages.map((message) => /* @__PURE__ */ jsxs9("div", { className: "chat-transcript-item", children: [
1539
+ message.role === "assistant" && message.turnId ? /* @__PURE__ */ jsx11(
1540
+ AgentActivityTimeline,
1541
+ {
1542
+ activities: activitiesByTurnId[message.turnId] ?? [],
1543
+ progress: message.turnId === activeTurnId ? progressByTurnId[message.turnId] : void 0
1544
+ }
1545
+ ) : null,
1546
+ /* @__PURE__ */ jsx11(ChatMessage, { message })
1547
+ ] }, message.id)),
1548
+ activeTurnId && !activeTurnHasAssistant ? /* @__PURE__ */ jsx11(
1549
+ AgentActivityTimeline,
1550
+ {
1551
+ activities: activitiesByTurnId[activeTurnId] ?? [],
1552
+ progress: progressByTurnId[activeTurnId]
1553
+ }
1554
+ ) : null
1555
+ ] }),
1556
+ /* @__PURE__ */ jsx11("div", { ref: endRef })
1557
+ ] }) }),
1558
+ /* @__PURE__ */ jsx11(
1559
+ ScrollToBottomButton,
1560
+ {
1561
+ containerRef,
1562
+ endRef,
1563
+ watchKey: `${messages.length}:${activityWatchKey}:${progressWatchKey}`
1564
+ }
1565
+ )
1566
+ ] });
1567
+ }
1568
+
1569
+ // src/chat-ui/components/RunStatus.tsx
1570
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1571
+ function RunStatus({
1572
+ status,
1573
+ sessionId,
1574
+ activeTurnId,
1575
+ contextMessageCount,
1576
+ error,
1577
+ errorCode
1578
+ }) {
1579
+ return /* @__PURE__ */ jsxs10("section", { className: "run-status", children: [
1580
+ /* @__PURE__ */ jsxs10("div", { children: [
1581
+ /* @__PURE__ */ jsx12("span", { children: "Status" }),
1582
+ /* @__PURE__ */ jsx12("strong", { children: status })
1583
+ ] }),
1584
+ /* @__PURE__ */ jsxs10("div", { children: [
1585
+ /* @__PURE__ */ jsx12("span", { children: "Session" }),
1586
+ /* @__PURE__ */ jsx12("strong", { children: sessionId ?? "-" })
1587
+ ] }),
1588
+ /* @__PURE__ */ jsxs10("div", { children: [
1589
+ /* @__PURE__ */ jsx12("span", { children: "Turn" }),
1590
+ /* @__PURE__ */ jsx12("strong", { children: activeTurnId ?? "-" })
1591
+ ] }),
1592
+ /* @__PURE__ */ jsxs10("div", { children: [
1593
+ /* @__PURE__ */ jsx12("span", { children: "Context" }),
1594
+ /* @__PURE__ */ jsxs10("strong", { children: [
1595
+ contextMessageCount ?? 0,
1596
+ " messages"
1597
+ ] })
1598
+ ] }),
1599
+ error ? /* @__PURE__ */ jsxs10("p", { children: [
1600
+ errorCode ? `${errorCode}: ` : null,
1601
+ error
1602
+ ] }) : null
1603
+ ] });
1604
+ }
1605
+
1606
+ // src/features/chat/chatModelStatus.ts
1607
+ function modelStatusText(model) {
1608
+ if (model.provider === "unavailable") {
1609
+ return model.error ?? "Gateway \u672A\u63D0\u4F9B\u6A21\u578B\u72B6\u6001\u3002";
1610
+ }
1611
+ const capabilities = model.capabilities?.length ? ` \u80FD\u529B\uFF1A${model.capabilities.join(", ")}.` : "";
1612
+ if (model.provider === "mock") {
1613
+ return `\u5F53\u524D\u4F7F\u7528\u672C\u5730 mock\u3002\u9009\u62E9 OpenRouter \u6216 Ollama \u540E\u542F\u7528\u771F\u5B9E AI Chat\u3002${capabilities}`;
1614
+ }
1615
+ if (!model.configured) {
1616
+ const missing = model.missing_config_keys.length ? ` Missing: ${model.missing_config_keys.join(", ")}.` : "";
1617
+ return `\u6A21\u578B provider \u5C1A\u672A\u914D\u7F6E\u5B8C\u6574.${capabilities}${missing}`;
1618
+ }
1619
+ return `${model.provider} \u5DF2\u914D\u7F6E${model.base_url ? `\uFF1A${model.base_url}` : ""}.${capabilities}`;
1620
+ }
1621
+
1622
+ // src/features/chat/ChatView.tsx
1623
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1624
+ function isDebugUIEnabled() {
1625
+ if (typeof window === "undefined") return false;
1626
+ return new URLSearchParams(window.location.search).get("debug") === "1";
1627
+ }
1628
+ function ChatView({
1629
+ chat,
1630
+ draft,
1631
+ attachments,
1632
+ attachmentPolicy,
1633
+ composerError,
1634
+ connectionStatus,
1635
+ gatewayLinked,
1636
+ model,
1637
+ agentStatus,
1638
+ onDraftChange,
1639
+ onFilesSelected,
1640
+ onRemoveAttachment,
1641
+ onSend,
1642
+ onAbort,
1643
+ onSuggest
1644
+ }) {
1645
+ const busy = Boolean(chat.activeTurnId);
1646
+ const ready = Boolean(chat.sessionId);
1647
+ const usingMock = model?.provider === "mock";
1648
+ const modelLabel = model ? model.model || model.display_name || model.provider : "loading model status";
1649
+ const debugUI = isDebugUIEnabled();
1650
+ return /* @__PURE__ */ jsxs11("section", { className: "cathygo-chat-panel chat-workspace", children: [
1651
+ /* @__PURE__ */ jsx13(
1652
+ ChatTopBar,
1653
+ {
1654
+ connectionStatus,
1655
+ gatewayLinked,
1656
+ model,
1657
+ agentStatus,
1658
+ debugUI,
1659
+ canAbort: Boolean(chat.activeTurnId),
1660
+ onAbort
1661
+ }
1662
+ ),
1663
+ debugUI ? /* @__PURE__ */ jsxs11("div", { className: "chat-debug-panels", children: [
1664
+ /* @__PURE__ */ jsxs11("section", { className: `model-banner ${model?.configured === false || usingMock ? "attention" : ""}`, children: [
1665
+ /* @__PURE__ */ jsxs11("div", { children: [
1666
+ /* @__PURE__ */ jsx13("span", { children: "Model" }),
1667
+ /* @__PURE__ */ jsx13("strong", { children: modelLabel })
1668
+ ] }),
1669
+ /* @__PURE__ */ jsx13("p", { children: model ? modelStatusText(model) : "\u6B63\u5728\u8BFB\u53D6 Gateway \u6A21\u578B\u72B6\u6001..." })
1670
+ ] }),
1671
+ /* @__PURE__ */ jsx13(
1672
+ RunStatus,
1673
+ {
1674
+ status: chat.status,
1675
+ sessionId: chat.sessionId,
1676
+ activeTurnId: chat.activeTurnId,
1677
+ contextMessageCount: chat.contextMessageCount,
1678
+ error: chat.error,
1679
+ errorCode: chat.errorCode
1680
+ }
1681
+ )
1682
+ ] }) : null,
1683
+ /* @__PURE__ */ jsxs11("div", { className: "chat-main", children: [
1684
+ /* @__PURE__ */ jsx13(
1685
+ ChatTranscript,
1686
+ {
1687
+ activeTurnId: chat.activeTurnId,
1688
+ activitiesByTurnId: chat.activitiesByTurnId,
1689
+ progressByTurnId: chat.progressByTurnId,
1690
+ messages: chat.messages,
1691
+ onSuggest: ready && !busy ? onSuggest : void 0
1692
+ }
1693
+ ),
1694
+ /* @__PURE__ */ jsx13(
1695
+ MessageComposer,
1696
+ {
1697
+ value: draft,
1698
+ attachments,
1699
+ attachmentPolicy,
1700
+ error: composerError,
1701
+ disabled: !ready || busy,
1702
+ onChange: onDraftChange,
1703
+ onFilesSelected,
1704
+ onRemoveAttachment,
1705
+ onSubmit: onSend
1706
+ }
1707
+ )
1708
+ ] })
1709
+ ] });
1710
+ }
1711
+
1712
+ // src/CathyGOChatApp.tsx
1713
+ import { jsx as jsx14 } from "react/jsx-runtime";
1714
+ function CathyGOChatApp({
1715
+ screen,
1716
+ chat,
1717
+ chats = [],
1718
+ draft,
1719
+ attachments,
1720
+ attachmentPolicy,
1721
+ composerError,
1722
+ busy,
1723
+ connectionStatus,
1724
+ gatewayLinked,
1725
+ model,
1726
+ agentStatus,
1727
+ homeAvatarSrc,
1728
+ onDraftChange,
1729
+ onFilesSelected,
1730
+ onRemoveAttachment,
1731
+ onSendMessage,
1732
+ onSendHomeMessage,
1733
+ onAbort,
1734
+ onSuggest,
1735
+ onOpenChat,
1736
+ onNewChat
1737
+ }) {
1738
+ if (screen === "history") {
1739
+ return /* @__PURE__ */ jsx14(ChatListView, { chats, onOpen: onOpenChat, onNewChat });
1740
+ }
1741
+ if (screen === "home") {
1742
+ return /* @__PURE__ */ jsx14(
1743
+ ChatHomeView,
1744
+ {
1745
+ attachments,
1746
+ attachmentPolicy,
1747
+ avatarSrc: homeAvatarSrc,
1748
+ busy,
1749
+ connectionStatus,
1750
+ draft,
1751
+ error: composerError,
1752
+ gatewayLinked,
1753
+ model,
1754
+ agentStatus,
1755
+ onDraftChange,
1756
+ onFilesSelected,
1757
+ onRemoveAttachment,
1758
+ onSend: onSendHomeMessage
1759
+ }
1760
+ );
1761
+ }
1762
+ return /* @__PURE__ */ jsx14(
1763
+ ChatView,
1764
+ {
1765
+ chat,
1766
+ draft,
1767
+ attachments,
1768
+ attachmentPolicy,
1769
+ composerError,
1770
+ connectionStatus,
1771
+ gatewayLinked,
1772
+ model,
1773
+ agentStatus,
1774
+ onDraftChange,
1775
+ onFilesSelected,
1776
+ onRemoveAttachment,
1777
+ onSend: onSendMessage,
1778
+ onAbort,
1779
+ onSuggest
1780
+ }
1781
+ );
1782
+ }
1783
+
1784
+ // src/useCathyGOChat.ts
1785
+ import { useCallback, useReducer } from "react";
1786
+
1787
+ // src/chat-ui/reducer.ts
1788
+ var initialChatState = {
1789
+ messages: [],
1790
+ activitiesByTurnId: {},
1791
+ progressByTurnId: {},
1792
+ status: "Disconnected",
1793
+ eventCount: 0
1794
+ };
1795
+ function reduceChat(state, action) {
1796
+ if (action.type === "reset") {
1797
+ return initialChatState;
1798
+ }
1799
+ if (action.type === "session.created") {
1800
+ return {
1801
+ ...state,
1802
+ sessionId: action.sessionId,
1803
+ status: "Ready",
1804
+ error: void 0,
1805
+ errorCode: void 0
1806
+ };
1807
+ }
1808
+ if (action.type === "session.loaded") {
1809
+ return {
1810
+ ...state,
1811
+ sessionId: action.sessionId,
1812
+ activeTurnId: void 0,
1813
+ messages: action.messages,
1814
+ activitiesByTurnId: groupActivitiesByTurn(action.activities ?? []),
1815
+ progressByTurnId: {},
1816
+ status: "Ready",
1817
+ error: void 0,
1818
+ errorCode: void 0
1819
+ };
1820
+ }
1821
+ if (action.type === "user.sent") {
1822
+ return {
1823
+ ...state,
1824
+ messages: [
1825
+ ...state.messages,
1826
+ {
1827
+ id: action.id,
1828
+ role: "user",
1829
+ content: action.content,
1830
+ parts: action.parts,
1831
+ status: "done"
1832
+ }
1833
+ ],
1834
+ status: "Sending",
1835
+ error: void 0,
1836
+ errorCode: void 0
1837
+ };
1838
+ }
1839
+ if (action.type === "send.failed") {
1840
+ return {
1841
+ ...state,
1842
+ messages: state.messages.map(
1843
+ (message) => message.id === action.id ? { ...message, status: "error" } : message
1844
+ ),
1845
+ activeTurnId: void 0,
1846
+ progressByTurnId: {},
1847
+ status: "Error",
1848
+ error: action.error,
1849
+ errorCode: action.code
1850
+ };
1851
+ }
1852
+ if (action.type === "turn.accepted") {
1853
+ return {
1854
+ ...state,
1855
+ sessionId: action.sessionId,
1856
+ activeTurnId: action.turnId,
1857
+ progressByTurnId: setProgress(state.progressByTurnId, {
1858
+ turnId: action.turnId,
1859
+ phase: "queued",
1860
+ status: "running",
1861
+ summary: "\u6B63\u5728\u63D0\u4EA4\u95EE\u9898",
1862
+ detail: "\u7B49\u5F85 CathyGO \u5F00\u59CB\u5904\u7406",
1863
+ startedAt: nowIso()
1864
+ }),
1865
+ status: "Thinking",
1866
+ error: void 0,
1867
+ errorCode: void 0
1868
+ };
1869
+ }
1870
+ const event = action.event;
1871
+ const payload = event.payload;
1872
+ const next = {
1873
+ ...state,
1874
+ eventCount: state.eventCount + 1,
1875
+ sessionId: payload.session_id ?? state.sessionId,
1876
+ activeTurnId: payload.turn_id ?? state.activeTurnId
1877
+ };
1878
+ if (event.event === "agent.progress.delta") {
1879
+ return {
1880
+ ...next,
1881
+ progressByTurnId: mergeProgressDelta(next.progressByTurnId, event)
1882
+ };
1883
+ }
1884
+ if (isAgentActivityEvent(event)) {
1885
+ const activity = activityFromEvent(event);
1886
+ if (!activity) return next;
1887
+ const activitiesByTurnId = mergeActivity(next.activitiesByTurnId, activity);
1888
+ return {
1889
+ ...next,
1890
+ status: activity.status === "running" ? activity.summary ?? activity.title : next.status,
1891
+ activitiesByTurnId,
1892
+ progressByTurnId: progressFromActivity(
1893
+ next.progressByTurnId,
1894
+ activity,
1895
+ activitiesByTurnId[activity.turn_id] ?? [],
1896
+ next.messages
1897
+ )
1898
+ };
1899
+ }
1900
+ if (event.event === "turn.started") {
1901
+ const inputMode = typeof payload.input_mode === "string" ? payload.input_mode : "";
1902
+ return {
1903
+ ...next,
1904
+ activeTurnId: payload.turn_id ?? next.activeTurnId,
1905
+ progressByTurnId: payload.turn_id ? updateProgress(next.progressByTurnId, payload.turn_id, {
1906
+ phase: "model",
1907
+ status: "running",
1908
+ summary: inputMode === "image" || inputMode === "text_image" ? "\u6B63\u5728\u770B\u56FE\u5E76\u5224\u65AD\u89E3\u9898\u6D41\u7A0B" : "\u6B63\u5728\u7406\u89E3\u95EE\u9898\u5E76\u89C4\u5212\u4E0B\u4E00\u6B65",
1909
+ detail: void 0
1910
+ }) : next.progressByTurnId,
1911
+ status: "Thinking"
1912
+ };
1913
+ }
1914
+ if (event.event === "assistant.delta") {
1915
+ const messageId = String(payload.message_id ?? `msg_${payload.turn_id ?? "active"}`);
1916
+ const turnId = optionalString(payload.turn_id);
1917
+ return {
1918
+ ...next,
1919
+ status: "Streaming",
1920
+ progressByTurnId: turnId ? updateProgress(next.progressByTurnId, turnId, {
1921
+ phase: "generating",
1922
+ status: "running",
1923
+ summary: "\u6B63\u5728\u751F\u6210\u56DE\u7B54",
1924
+ detail: void 0
1925
+ }) : next.progressByTurnId,
1926
+ messages: appendAssistantDelta(
1927
+ next.messages,
1928
+ messageId,
1929
+ String(payload.delta ?? ""),
1930
+ payload.turn_id
1931
+ )
1932
+ };
1933
+ }
1934
+ if (event.event === "assistant.completed") {
1935
+ const finalMessage = typeof payload.message === "string" ? payload.message : "";
1936
+ return {
1937
+ ...next,
1938
+ status: "Streaming",
1939
+ messages: finalizeAssistantMessage(
1940
+ next.messages,
1941
+ payload.message_id,
1942
+ finalMessage,
1943
+ payload.turn_id
1944
+ )
1945
+ };
1946
+ }
1947
+ if (event.event === "turn.completed") {
1948
+ return {
1949
+ ...next,
1950
+ activeTurnId: void 0,
1951
+ progressByTurnId: removeProgress(next.progressByTurnId, payload.turn_id),
1952
+ status: "Ready"
1953
+ };
1954
+ }
1955
+ if (event.event === "turn.cancelled") {
1956
+ return {
1957
+ ...next,
1958
+ activeTurnId: void 0,
1959
+ progressByTurnId: removeProgress(next.progressByTurnId, payload.turn_id),
1960
+ status: "Ready"
1961
+ };
1962
+ }
1963
+ if (event.event === "turn.failed") {
1964
+ const error = payload.error;
1965
+ return {
1966
+ ...next,
1967
+ activeTurnId: void 0,
1968
+ progressByTurnId: removeProgress(next.progressByTurnId, payload.turn_id),
1969
+ status: "Error",
1970
+ error: error?.message ?? "CathyGO turn failed",
1971
+ errorCode: error?.code
1972
+ };
1973
+ }
1974
+ return next;
1975
+ }
1976
+ function appendAssistantDelta(messages, messageId, delta, turnId) {
1977
+ const resolvedTurnId = optionalString(turnId);
1978
+ const index = messages.findIndex((message) => message.id === messageId);
1979
+ if (index === -1) {
1980
+ return [
1981
+ ...messages,
1982
+ {
1983
+ id: messageId,
1984
+ role: "assistant",
1985
+ content: delta,
1986
+ turnId: resolvedTurnId,
1987
+ status: "streaming"
1988
+ }
1989
+ ];
1990
+ }
1991
+ return messages.map(
1992
+ (message, itemIndex) => itemIndex === index ? {
1993
+ ...message,
1994
+ content: `${message.content}${delta}`,
1995
+ turnId: message.turnId ?? resolvedTurnId,
1996
+ status: "streaming"
1997
+ } : message
1998
+ );
1999
+ }
2000
+ function finalizeAssistantMessage(messages, messageId, finalMessage, turnId) {
2001
+ const id = String(messageId || "");
2002
+ const resolvedTurnId = optionalString(turnId);
2003
+ if (!id && !finalMessage) {
2004
+ return messages.map(
2005
+ (message) => message.role === "assistant" && message.status === "streaming" ? { ...message, turnId: message.turnId ?? resolvedTurnId, status: "done" } : message
2006
+ );
2007
+ }
2008
+ const index = messages.findIndex((message) => message.id === id);
2009
+ if (index === -1 && finalMessage) {
2010
+ return [
2011
+ ...messages,
2012
+ {
2013
+ id: id || `msg_done_${messages.length}`,
2014
+ role: "assistant",
2015
+ content: finalMessage,
2016
+ turnId: resolvedTurnId,
2017
+ status: "done"
2018
+ }
2019
+ ];
2020
+ }
2021
+ return messages.map(
2022
+ (message, itemIndex) => itemIndex === index || message.role === "assistant" && message.status === "streaming" ? {
2023
+ ...message,
2024
+ content: message.content || finalMessage,
2025
+ turnId: message.turnId ?? resolvedTurnId,
2026
+ status: "done"
2027
+ } : message
2028
+ );
2029
+ }
2030
+ function isAgentActivityEvent(event) {
2031
+ return event.event.startsWith("agent.activity.");
2032
+ }
2033
+ function activityFromEvent(event) {
2034
+ const payload = event.payload;
2035
+ const activityId = optionalString(payload.activity_id);
2036
+ const sessionId = optionalString(payload.session_id);
2037
+ const turnId = optionalString(payload.turn_id);
2038
+ const title = optionalString(payload.title);
2039
+ if (!activityId || !sessionId || !turnId || !title) return void 0;
2040
+ return {
2041
+ activity_id: activityId,
2042
+ session_id: sessionId,
2043
+ turn_id: turnId,
2044
+ kind: activityKind(payload.kind),
2045
+ status: activityStatus(payload.status, event.event),
2046
+ title,
2047
+ summary: optionalString(payload.summary),
2048
+ detail: optionalString(payload.detail),
2049
+ started_at: optionalString(payload.started_at),
2050
+ completed_at: optionalString(payload.completed_at),
2051
+ error: activityError(payload.error)
2052
+ };
2053
+ }
2054
+ function activityKind(value) {
2055
+ if (value === "model" || value === "tool" || value === "skill" || value === "vision" || value === "memory" || value === "system") {
2056
+ return value;
2057
+ }
2058
+ return "system";
2059
+ }
2060
+ function activityStatus(value, eventName) {
2061
+ if (value === "running" || value === "completed" || value === "failed" || value === "cancelled") {
2062
+ return value;
2063
+ }
2064
+ if (eventName === "agent.activity.started") return "running";
2065
+ return "completed";
2066
+ }
2067
+ function activityError(value) {
2068
+ if (!value || typeof value !== "object") return void 0;
2069
+ const raw = value;
2070
+ const message = optionalString(raw.message);
2071
+ if (!message) return void 0;
2072
+ const code = optionalString(raw.code);
2073
+ return code ? { code, message } : { message };
2074
+ }
2075
+ function groupActivitiesByTurn(activities) {
2076
+ return activities.reduce(
2077
+ (items, activity) => mergeActivity(items, activity),
2078
+ {}
2079
+ );
2080
+ }
2081
+ function mergeActivity(activitiesByTurnId, activity) {
2082
+ const turnActivities = activitiesByTurnId[activity.turn_id] ?? [];
2083
+ const index = turnActivities.findIndex((item) => item.activity_id === activity.activity_id);
2084
+ const nextTurnActivities = index === -1 ? [...turnActivities, activity] : turnActivities.map(
2085
+ (item, itemIndex) => itemIndex === index ? mergeDefinedActivity(item, activity) : item
2086
+ );
2087
+ return {
2088
+ ...activitiesByTurnId,
2089
+ [activity.turn_id]: nextTurnActivities
2090
+ };
2091
+ }
2092
+ function mergeDefinedActivity(base, update) {
2093
+ const next = { ...base };
2094
+ Object.entries(update).forEach(([key, value]) => {
2095
+ if (value !== void 0) {
2096
+ next[key] = value;
2097
+ }
2098
+ });
2099
+ return next;
2100
+ }
2101
+ function progressFromActivity(progressByTurnId, activity, turnActivities, messages) {
2102
+ if (activity.status === "failed" || activity.status === "cancelled") {
2103
+ return updateProgress(progressByTurnId, activity.turn_id, {
2104
+ phase: "tool",
2105
+ status: activity.status,
2106
+ summary: activity.error?.message ?? activity.summary ?? activity.title,
2107
+ detail: activity.detail
2108
+ });
2109
+ }
2110
+ if (activity.status === "running") {
2111
+ return updateProgress(progressByTurnId, activity.turn_id, {
2112
+ phase: activity.kind === "model" ? "model" : "tool",
2113
+ status: "running",
2114
+ summary: activity.summary ?? activity.title,
2115
+ detail: activity.detail
2116
+ });
2117
+ }
2118
+ if (activity.kind === "model") {
2119
+ return updateProgress(progressByTurnId, activity.turn_id, {
2120
+ phase: "generating",
2121
+ status: "completed",
2122
+ summary: activity.summary ?? "\u5DF2\u751F\u6210\u56DE\u7B54",
2123
+ detail: activity.detail
2124
+ });
2125
+ }
2126
+ const hasRunningActivity = turnActivities.some((item) => item.status === "running");
2127
+ const hasAssistantForTurn = messages.some(
2128
+ (message) => message.role === "assistant" && message.turnId === activity.turn_id
2129
+ );
2130
+ if (!hasRunningActivity && !hasAssistantForTurn) {
2131
+ return updateProgress(progressByTurnId, activity.turn_id, {
2132
+ phase: "generating",
2133
+ status: "running",
2134
+ summary: "\u6B63\u5728\u7EC4\u7EC7\u89E3\u7B54",
2135
+ detail: "\u5DF2\u5B8C\u6210\u524D\u7F6E\u5206\u6790\uFF0C\u7B49\u5F85\u6A21\u578B\u751F\u6210\u56DE\u7B54"
2136
+ });
2137
+ }
2138
+ return progressByTurnId;
2139
+ }
2140
+ function setProgress(progressByTurnId, progress) {
2141
+ return {
2142
+ ...progressByTurnId,
2143
+ [progress.turnId]: progress
2144
+ };
2145
+ }
2146
+ function updateProgress(progressByTurnId, turnIdValue, patch) {
2147
+ const turnId = optionalString(turnIdValue);
2148
+ if (!turnId) return progressByTurnId;
2149
+ const existing = progressByTurnId[turnId];
2150
+ return setProgress(progressByTurnId, {
2151
+ turnId,
2152
+ phase: patch.phase ?? existing?.phase ?? "model",
2153
+ status: patch.status ?? existing?.status ?? "running",
2154
+ summary: patch.summary ?? existing?.summary ?? "\u6B63\u5728\u5904\u7406",
2155
+ detail: patch.detail,
2156
+ thinking: patch.thinking ?? existing?.thinking,
2157
+ startedAt: existing?.startedAt ?? nowIso(),
2158
+ updatedAt: nowIso()
2159
+ });
2160
+ }
2161
+ function mergeProgressDelta(progressByTurnId, event) {
2162
+ const payload = event.payload;
2163
+ const turnId = optionalString(payload.turn_id);
2164
+ if (!turnId) return progressByTurnId;
2165
+ const existing = progressByTurnId[turnId];
2166
+ const thinking = appendThinkingLine(
2167
+ existing?.thinking,
2168
+ progressLineFromPayload(payload),
2169
+ optionalString(payload.source)
2170
+ );
2171
+ if (!thinking) return progressByTurnId;
2172
+ return updateProgress(progressByTurnId, turnId, {
2173
+ phase: progressPhase(payload.phase) ?? existing?.phase ?? "reasoning",
2174
+ status: "running",
2175
+ summary: existing?.summary ?? "\u601D\u8003\u4E2D",
2176
+ detail: existing?.detail,
2177
+ thinking
2178
+ });
2179
+ }
2180
+ function appendThinkingLine(current, line, source) {
2181
+ const now = nowIso();
2182
+ const existing = current ?? { expanded: false, lines: [], hiddenCount: 0, updatedAt: now };
2183
+ if (source === "hidden_reasoning") {
2184
+ return {
2185
+ ...existing,
2186
+ hiddenCount: existing.hiddenCount + 1,
2187
+ hiddenStartedAt: existing.hiddenStartedAt ?? now,
2188
+ updatedAt: now
2189
+ };
2190
+ }
2191
+ if (!line) return existing;
2192
+ return {
2193
+ ...existing,
2194
+ lines: boundedLines([...existing.lines, line]),
2195
+ updatedAt: now
2196
+ };
2197
+ }
2198
+ function progressLineFromPayload(payload) {
2199
+ const text = optionalString(payload.text);
2200
+ if (text) return text;
2201
+ return optionalString(payload.text_delta);
2202
+ }
2203
+ function progressPhase(value) {
2204
+ if (value === "queued" || value === "model" || value === "reasoning" || value === "tool" || value === "generating") {
2205
+ return value;
2206
+ }
2207
+ return void 0;
2208
+ }
2209
+ function boundedLines(lines) {
2210
+ return lines.slice(-24);
2211
+ }
2212
+ function removeProgress(progressByTurnId, turnIdValue) {
2213
+ const turnId = optionalString(turnIdValue);
2214
+ if (!turnId || !progressByTurnId[turnId]) return progressByTurnId;
2215
+ const next = { ...progressByTurnId };
2216
+ delete next[turnId];
2217
+ return next;
2218
+ }
2219
+ function optionalString(value) {
2220
+ const text = typeof value === "string" ? value.trim() : "";
2221
+ return text || void 0;
2222
+ }
2223
+ function nowIso() {
2224
+ return (/* @__PURE__ */ new Date()).toISOString();
2225
+ }
2226
+
2227
+ // src/useCathyGOChat.ts
2228
+ function useCathyGOChat(initialState = initialChatState) {
2229
+ const [chat, dispatchChat] = useReducer(reduceChat, initialState);
2230
+ const resetChat = useCallback(() => {
2231
+ dispatchChat({ type: "reset" });
2232
+ }, []);
2233
+ const createSession = useCallback((sessionId) => {
2234
+ dispatchChat({ type: "session.created", sessionId });
2235
+ }, []);
2236
+ const loadSession = useCallback(
2237
+ (sessionId, messages, activities) => {
2238
+ dispatchChat({ type: "session.loaded", sessionId, messages, activities });
2239
+ },
2240
+ []
2241
+ );
2242
+ const applyTurnEvent = useCallback((event) => {
2243
+ dispatchChat({ type: "event.received", event });
2244
+ }, []);
2245
+ return {
2246
+ chat,
2247
+ dispatchChat,
2248
+ resetChat,
2249
+ createSession,
2250
+ loadSession,
2251
+ applyTurnEvent
2252
+ };
2253
+ }
2254
+
2255
+ // src/chat-ui/components/ChatRuntimeSelector.tsx
2256
+ import { useEffect as useEffect5, useId, useMemo, useRef as useRef4, useState as useState6 } from "react";
2257
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2258
+ function buildRuntimeOptions(agentStatus) {
2259
+ return [
2260
+ {
2261
+ id: "lite",
2262
+ label: "CathyGOLite",
2263
+ icon: "cloud"
2264
+ },
2265
+ {
2266
+ id: "local",
2267
+ label: agentDisplayName(agentStatus),
2268
+ icon: "local"
2269
+ }
2270
+ ];
2271
+ }
2272
+ function ChatRuntimeSelector({
2273
+ agentStatus,
2274
+ value,
2275
+ onChange
2276
+ }) {
2277
+ const [internalValue, setInternalValue] = useState6("lite");
2278
+ const [open, setOpen] = useState6(false);
2279
+ const rootRef = useRef4(null);
2280
+ const listboxId = useId();
2281
+ const selected = value ?? internalValue;
2282
+ const options = useMemo(() => buildRuntimeOptions(agentStatus), [agentStatus]);
2283
+ const active = options.find((item) => item.id === selected) ?? options[0];
2284
+ function selectOption(id) {
2285
+ if (value === void 0) {
2286
+ setInternalValue(id);
2287
+ }
2288
+ onChange?.(id);
2289
+ setOpen(false);
2290
+ }
2291
+ useEffect5(() => {
2292
+ if (!open) return void 0;
2293
+ function handlePointerDown(event) {
2294
+ if (!rootRef.current?.contains(event.target)) {
2295
+ setOpen(false);
2296
+ }
2297
+ }
2298
+ function handleKeyDown(event) {
2299
+ if (event.key === "Escape") setOpen(false);
2300
+ }
2301
+ window.addEventListener("mousedown", handlePointerDown);
2302
+ window.addEventListener("keydown", handleKeyDown);
2303
+ return () => {
2304
+ window.removeEventListener("mousedown", handlePointerDown);
2305
+ window.removeEventListener("keydown", handleKeyDown);
2306
+ };
2307
+ }, [open]);
2308
+ return /* @__PURE__ */ jsxs12("div", { className: "chat-runtime-selector", ref: rootRef, children: [
2309
+ /* @__PURE__ */ jsxs12(
2310
+ "button",
2311
+ {
2312
+ "aria-controls": listboxId,
2313
+ "aria-expanded": open,
2314
+ "aria-haspopup": "listbox",
2315
+ className: "chat-runtime-selector-trigger",
2316
+ onClick: () => setOpen((current) => !current),
2317
+ type: "button",
2318
+ children: [
2319
+ /* @__PURE__ */ jsx15(RuntimeIcon, { kind: active.icon }),
2320
+ /* @__PURE__ */ jsx15("span", { className: "chat-runtime-selector-label", children: active.label }),
2321
+ /* @__PURE__ */ jsx15(IconChevronDown, { className: "chat-runtime-selector-chevron" })
2322
+ ]
2323
+ }
2324
+ ),
2325
+ open ? /* @__PURE__ */ jsx15("ul", { className: "chat-runtime-selector-menu", id: listboxId, role: "listbox", children: options.map((option) => /* @__PURE__ */ jsx15("li", { role: "presentation", children: /* @__PURE__ */ jsxs12(
2326
+ "button",
2327
+ {
2328
+ "aria-selected": option.id === selected,
2329
+ className: option.id === selected ? "chat-runtime-option active" : "chat-runtime-option",
2330
+ onClick: () => selectOption(option.id),
2331
+ role: "option",
2332
+ type: "button",
2333
+ children: [
2334
+ /* @__PURE__ */ jsx15(RuntimeIcon, { kind: option.icon }),
2335
+ /* @__PURE__ */ jsx15("span", { className: "chat-runtime-option-label", children: option.label })
2336
+ ]
2337
+ }
2338
+ ) }, option.id)) }) : null
2339
+ ] });
2340
+ }
2341
+ function RuntimeIcon({ kind }) {
2342
+ const className = "chat-runtime-icon";
2343
+ return kind === "cloud" ? /* @__PURE__ */ jsx15(IconCloud, { className }) : /* @__PURE__ */ jsx15(IconLocalComputer, { className });
2344
+ }
2345
+ export {
2346
+ AgentActivityTimeline,
2347
+ CHAT_HOME_MODES,
2348
+ CathyGOChatApp,
2349
+ ChatAgentIdentity,
2350
+ ChatHomeView,
2351
+ ChatListView,
2352
+ ChatMessage as ChatMessageView,
2353
+ ChatRuntimeSelector,
2354
+ ChatTopBar,
2355
+ ChatTranscript,
2356
+ ChatView,
2357
+ DEFAULT_CHAT_HOME_MODE_ID,
2358
+ DEFAULT_COMPOSER_ATTACHMENT_POLICY,
2359
+ IconBack,
2360
+ IconBrain,
2361
+ IconCalendar,
2362
+ IconChevronDown,
2363
+ IconChevronRight,
2364
+ IconClose,
2365
+ IconCloud,
2366
+ IconCode,
2367
+ IconDevice,
2368
+ IconDevices,
2369
+ IconDocument,
2370
+ IconExternalLink,
2371
+ IconGatewayConnecting,
2372
+ IconGatewayOffline,
2373
+ IconGatewayOnline,
2374
+ IconGithub,
2375
+ IconHome,
2376
+ IconImageUpload,
2377
+ IconInfo,
2378
+ IconLocalComputer,
2379
+ IconMessage,
2380
+ IconMore,
2381
+ IconNewChat,
2382
+ IconPencil,
2383
+ IconPlug,
2384
+ IconSearch,
2385
+ IconSend,
2386
+ IconSettings,
2387
+ IconShield,
2388
+ IconSmartphone,
2389
+ MathMarkdown,
2390
+ MessageComposer,
2391
+ RunStatus,
2392
+ ScrollToBottomButton,
2393
+ agentDisplayName,
2394
+ agentShortId,
2395
+ buildRuntimeOptions,
2396
+ composerAcceptAttribute,
2397
+ findChatHomeMode,
2398
+ fixCommonLatexMistakes,
2399
+ formatBytes,
2400
+ initialChatState,
2401
+ modelStatusText,
2402
+ normalizeComposerAttachmentPolicy,
2403
+ normalizeMathDelimiters,
2404
+ prepareMathMarkdown,
2405
+ reduceChat,
2406
+ stripMathForPreview,
2407
+ useCathyGOChat,
2408
+ validateComposerFiles
2409
+ };