@pactor-app/chat 1.44.0 → 1.46.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.
@@ -0,0 +1,2633 @@
1
+ // src/lib/utils.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // src/components/AgentStatus/index.tsx
9
+ import { Icon } from "@pactor-app/ui/components";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var statusMeta = {
12
+ running: { icon: "loader-circle", className: "text-info", spin: true },
13
+ waiting: { icon: "clock", className: "text-warning" },
14
+ done: { icon: "circle-check", className: "text-success" },
15
+ error: { icon: "circle-x", className: "text-destructive" }
16
+ };
17
+ function AgentStatus({ status = "running", text, elapsed, className, style }) {
18
+ const meta = statusMeta[status];
19
+ return /* @__PURE__ */ jsxs(
20
+ "div",
21
+ {
22
+ "data-slot": "agent-status",
23
+ "data-status": status,
24
+ className: cn("flex items-center gap-2 text-sm", className),
25
+ style,
26
+ children: [
27
+ /* @__PURE__ */ jsx("span", { className: cn(meta.className, meta.spin && "animate-spin motion-reduce:animate-none"), children: /* @__PURE__ */ jsx(Icon, { name: meta.icon, size: "sm" }) }),
28
+ text ? /* @__PURE__ */ jsx("span", { "data-slot": "agent-status-text", children: text }) : null,
29
+ elapsed !== void 0 ? /* @__PURE__ */ jsxs("span", { "data-slot": "agent-status-elapsed", className: "text-xs text-muted-foreground", children: [
30
+ "\u5DF2\u7528\u65F6 ",
31
+ elapsed,
32
+ "s"
33
+ ] }) : null
34
+ ]
35
+ }
36
+ );
37
+ }
38
+
39
+ // src/components/ArtifactPanel/index.tsx
40
+ import {
41
+ useEffect,
42
+ useRef,
43
+ useState
44
+ } from "react";
45
+ import { Button } from "@pactor-app/ui/components";
46
+ import { CodeBlock } from "@pactor-app/ui/components";
47
+ import { DropdownMenu } from "@pactor-app/ui/components";
48
+ import { Icon as Icon2 } from "@pactor-app/ui/components";
49
+ import { Markdown } from "@pactor-app/ui/components";
50
+
51
+ // src/components/ArtifactPanel/artifact.ts
52
+ var ARTIFACT_IMAGE_EXTENSIONS = [
53
+ "png",
54
+ "jpg",
55
+ "jpeg",
56
+ "gif",
57
+ "svg",
58
+ "webp",
59
+ "avif",
60
+ "bmp",
61
+ "ico"
62
+ ];
63
+ function resolveArtifactFileType(artifact) {
64
+ const declared = artifact.fileType?.trim().toLowerCase().replace(/^\./, "");
65
+ if (declared) {
66
+ return declared;
67
+ }
68
+ const match = /\.([A-Za-z0-9]+)$/.exec(artifact.title.trim());
69
+ return match ? match[1].toLowerCase() : null;
70
+ }
71
+ function isImageArtifact(artifact) {
72
+ const ext = resolveArtifactFileType(artifact);
73
+ return ext !== null && ARTIFACT_IMAGE_EXTENSIONS.includes(ext);
74
+ }
75
+ var ARTIFACT_ICON_BY_EXTENSION = {
76
+ // 表格数据
77
+ csv: "table",
78
+ tsv: "table",
79
+ xls: "table",
80
+ xlsx: "table",
81
+ // 数据 / 数据库
82
+ json: "database",
83
+ sql: "database",
84
+ // 脚本
85
+ sh: "terminal",
86
+ bash: "terminal",
87
+ zsh: "terminal",
88
+ // 代码 / 配置
89
+ js: "code",
90
+ mjs: "code",
91
+ cjs: "code",
92
+ jsx: "code",
93
+ ts: "code",
94
+ mts: "code",
95
+ cts: "code",
96
+ tsx: "code",
97
+ py: "code",
98
+ rb: "code",
99
+ go: "code",
100
+ rs: "code",
101
+ java: "code",
102
+ kt: "code",
103
+ c: "code",
104
+ h: "code",
105
+ cc: "code",
106
+ cpp: "code",
107
+ cs: "code",
108
+ swift: "code",
109
+ php: "code",
110
+ css: "code",
111
+ scss: "code",
112
+ less: "code",
113
+ html: "code",
114
+ vue: "code",
115
+ svelte: "code",
116
+ yaml: "code",
117
+ yml: "code",
118
+ toml: "code",
119
+ xml: "code",
120
+ // 文档 / 文本
121
+ md: "file-text",
122
+ markdown: "file-text",
123
+ mdx: "file-text",
124
+ txt: "file-text",
125
+ text: "file-text",
126
+ log: "file-text",
127
+ rst: "file-text",
128
+ pdf: "file-text",
129
+ doc: "file-text",
130
+ docx: "file-text"
131
+ };
132
+ function resolveArtifactIconName(artifact) {
133
+ const ext = resolveArtifactFileType(artifact);
134
+ if (ext !== null) {
135
+ if (ARTIFACT_IMAGE_EXTENSIONS.includes(ext)) {
136
+ return "image";
137
+ }
138
+ const name = ARTIFACT_ICON_BY_EXTENSION[ext];
139
+ if (name !== void 0) {
140
+ return name;
141
+ }
142
+ }
143
+ if (artifact.kind === "code") {
144
+ return "code";
145
+ }
146
+ if (artifact.kind === "dsl") {
147
+ return "layout-grid";
148
+ }
149
+ return ext === null ? "file-text" : "file";
150
+ }
151
+ function buildArtifactTree(artifacts) {
152
+ const root = { name: "", path: "", children: [] };
153
+ for (const artifact of artifacts) {
154
+ const segments = artifact.id.split("/").filter(Boolean);
155
+ let node = root;
156
+ for (let i = 0; i < segments.length - 1; i++) {
157
+ const path = segments.slice(0, i + 1).join("/");
158
+ let child = node.children.find(
159
+ (item) => item.path === path && item.artifact === void 0
160
+ );
161
+ if (!child) {
162
+ child = { name: segments[i], path, children: [] };
163
+ node.children.push(child);
164
+ }
165
+ node = child;
166
+ }
167
+ node.children.push({
168
+ name: artifact.title,
169
+ path: artifact.id,
170
+ artifact,
171
+ children: []
172
+ });
173
+ }
174
+ const sortLevel = (nodes) => {
175
+ nodes.sort((a, b) => {
176
+ const dirDelta = Number(b.artifact === void 0) - Number(a.artifact === void 0);
177
+ return dirDelta !== 0 ? dirDelta : a.name.localeCompare(b.name);
178
+ });
179
+ for (const item of nodes) {
180
+ sortLevel(item.children);
181
+ }
182
+ };
183
+ sortLevel(root.children);
184
+ return root.children;
185
+ }
186
+
187
+ // src/components/ArtifactPanel/download.ts
188
+ var KIND_EXTENSIONS = {
189
+ markdown: "md",
190
+ text: "txt",
191
+ code: "txt",
192
+ dsl: "json"
193
+ };
194
+ function resolveArtifactFilename(artifact) {
195
+ const title = artifact.title.trim() || "artifact";
196
+ if (/\.[A-Za-z0-9]+$/.test(title)) {
197
+ return title;
198
+ }
199
+ const ext = resolveArtifactFileType(artifact) ?? KIND_EXTENSIONS[artifact.kind];
200
+ return `${title}.${ext}`;
201
+ }
202
+ function dataUrlToBlob(dataUrl) {
203
+ const comma = dataUrl.indexOf(",");
204
+ const meta = dataUrl.slice(0, comma);
205
+ const payload = dataUrl.slice(comma + 1);
206
+ const mime = /^data:([^;,]+)/.exec(meta)?.[1] ?? "application/octet-stream";
207
+ if (meta.includes(";base64")) {
208
+ const binary = atob(payload);
209
+ const bytes = new Uint8Array(binary.length);
210
+ for (let i = 0; i < binary.length; i++) {
211
+ bytes[i] = binary.charCodeAt(i);
212
+ }
213
+ return new Blob([bytes], { type: mime });
214
+ }
215
+ return new Blob([decodeURIComponent(payload)], { type: mime });
216
+ }
217
+ function isRemoteUrl(raw) {
218
+ return /^https?:\/\//.test(raw.trim());
219
+ }
220
+ function buildArtifactBlob(artifact) {
221
+ if (artifact.kind === "dsl" && typeof artifact.content === "object") {
222
+ return new Blob([JSON.stringify(artifact.content, null, 2)], {
223
+ type: "application/json"
224
+ });
225
+ }
226
+ const raw = String(artifact.content);
227
+ if (isImageArtifact(artifact)) {
228
+ const trimmed = raw.trim();
229
+ if (trimmed.startsWith("data:")) {
230
+ return dataUrlToBlob(trimmed);
231
+ }
232
+ if (trimmed.startsWith("<svg") || trimmed.startsWith("<?xml")) {
233
+ return new Blob([raw], { type: "image/svg+xml" });
234
+ }
235
+ if (isRemoteUrl(trimmed)) {
236
+ return null;
237
+ }
238
+ }
239
+ const ext = resolveArtifactFileType(artifact);
240
+ const mime = ext === "md" || artifact.kind === "markdown" ? "text/markdown" : "text/plain";
241
+ return new Blob([raw], { type: `${mime};charset=utf-8` });
242
+ }
243
+ function saveBlob(blob, filename) {
244
+ const url = URL.createObjectURL(blob);
245
+ const anchor = document.createElement("a");
246
+ anchor.href = url;
247
+ anchor.download = filename;
248
+ document.body.appendChild(anchor);
249
+ anchor.click();
250
+ anchor.remove();
251
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
252
+ }
253
+ async function downloadArtifact(artifact) {
254
+ const filename = resolveArtifactFilename(artifact);
255
+ const raw = String(artifact.content ?? "").trim();
256
+ if (isImageArtifact(artifact) && isRemoteUrl(raw)) {
257
+ try {
258
+ const response = await fetch(raw);
259
+ if (!response.ok) {
260
+ throw new Error(`HTTP ${response.status}`);
261
+ }
262
+ saveBlob(await response.blob(), filename);
263
+ return;
264
+ } catch {
265
+ window.open(raw, "_blank", "noopener");
266
+ return;
267
+ }
268
+ }
269
+ const blob = buildArtifactBlob(artifact);
270
+ if (blob) {
271
+ saveBlob(blob, filename);
272
+ }
273
+ }
274
+
275
+ // src/components/ArtifactPanel/dsl-preview.tsx
276
+ import { isDslSourceRef, Registry } from "@pactor-app/core";
277
+ import {
278
+ ActionEngine,
279
+ createRuntimeContext,
280
+ DslRenderer,
281
+ registerBuiltinActions,
282
+ useRenderer
283
+ } from "@pactor-app/runtime";
284
+ import {
285
+ Component,
286
+ useMemo
287
+ } from "react";
288
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
289
+ var ARTIFACT_DENIED_TYPE = "ChatDenied";
290
+ function ArtifactDenied({ deniedType }) {
291
+ return /* @__PURE__ */ jsxs2(
292
+ "div",
293
+ {
294
+ "data-slot": "artifact-denied",
295
+ "data-denied-type": deniedType,
296
+ className: "rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground",
297
+ children: [
298
+ "\u8BE5\u7EC4\u4EF6\u4E0D\u5141\u8BB8\u5728\u4EA7\u7269\u9884\u89C8\u4E2D\u4F7F\u7528",
299
+ deniedType ? `\uFF1A${deniedType}` : ""
300
+ ]
301
+ }
302
+ );
303
+ }
304
+ function guardArtifactDsl(node, registry) {
305
+ const type = node.type ?? "(reference)";
306
+ if (type !== ARTIFACT_DENIED_TYPE && !registry.has(type)) {
307
+ return {
308
+ type: ARTIFACT_DENIED_TYPE,
309
+ props: { deniedType: type }
310
+ };
311
+ }
312
+ if (node.children === void 0) {
313
+ return node;
314
+ }
315
+ if (!Array.isArray(node.children)) {
316
+ return {
317
+ type: ARTIFACT_DENIED_TYPE,
318
+ props: { deniedType: `${type}(remote)` }
319
+ };
320
+ }
321
+ if (node.children.length === 0) {
322
+ return node;
323
+ }
324
+ return {
325
+ ...node,
326
+ // 数组中混排的引用元素同样无法静态过白名单:替换为受控占位
327
+ children: node.children.map(
328
+ (child) => isDslSourceRef(child) ? { type: ARTIFACT_DENIED_TYPE, props: { deniedType: "(remote)" } } : guardArtifactDsl(child, registry)
329
+ )
330
+ };
331
+ }
332
+ function useArtifactPreviewRuntime(safeRegistry, user) {
333
+ return useMemo(() => {
334
+ if (!safeRegistry) {
335
+ return void 0;
336
+ }
337
+ const components = new Registry("ArtifactPreviewRegistry");
338
+ components.register(ARTIFACT_DENIED_TYPE, ArtifactDenied);
339
+ for (const name of safeRegistry.list()) {
340
+ components.register(name, safeRegistry.get(name));
341
+ }
342
+ const context = createRuntimeContext({ components, user });
343
+ registerBuiltinActions(context.actions);
344
+ return { context, engine: new ActionEngine(context) };
345
+ }, [safeRegistry, user]);
346
+ }
347
+ var ArtifactDslGuard = class extends Component {
348
+ constructor() {
349
+ super(...arguments);
350
+ this.state = { failed: false };
351
+ }
352
+ static getDerivedStateFromError() {
353
+ return { failed: true };
354
+ }
355
+ componentDidCatch(error) {
356
+ console.warn("[@pactor-app/ui] DSL \u4EA7\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A", error);
357
+ }
358
+ render() {
359
+ return this.state.failed ? /* @__PURE__ */ jsx2(
360
+ "div",
361
+ {
362
+ "data-slot": "artifact-denied",
363
+ className: "rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground",
364
+ children: "DSL \u4EA7\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A"
365
+ }
366
+ ) : this.props.children;
367
+ }
368
+ };
369
+ function ArtifactDslNode({ node }) {
370
+ const { renderChildren } = useRenderer();
371
+ return /* @__PURE__ */ jsx2(Fragment, { children: renderChildren([node]) });
372
+ }
373
+ function ArtifactDslContent({
374
+ content,
375
+ runtime,
376
+ style
377
+ }) {
378
+ const node = typeof content === "object" && content !== null && !Array.isArray(content) && typeof content.type === "string" ? content : void 0;
379
+ if (!node || !runtime) {
380
+ return /* @__PURE__ */ jsx2(
381
+ "div",
382
+ {
383
+ "data-slot": "artifact-denied",
384
+ className: "rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground",
385
+ style,
386
+ children: node ? "DSL \u9884\u89C8\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "DSL \u4EA7\u7269\u5185\u5BB9\u65E0\u6548"
387
+ }
388
+ );
389
+ }
390
+ const guarded = guardArtifactDsl(node, runtime.context.components);
391
+ return /* @__PURE__ */ jsx2(DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx2(ArtifactDslGuard, { children: /* @__PURE__ */ jsx2(ArtifactDslNode, { node: guarded }) }) });
392
+ }
393
+
394
+ // src/components/ArtifactPanel/index.tsx
395
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
396
+ function ArtifactKindIcon({ artifact }) {
397
+ return /* @__PURE__ */ jsx3(Icon2, { name: resolveArtifactIconName(artifact), size: 13 });
398
+ }
399
+ function ArtifactListView({
400
+ nodes,
401
+ activeId,
402
+ onSelect
403
+ }) {
404
+ return /* @__PURE__ */ jsx3("ul", { "data-slot": "artifact-panel-list", className: "flex flex-col gap-0.5", children: nodes.map((node) => /* @__PURE__ */ jsx3(
405
+ ArtifactTreeRow,
406
+ {
407
+ node,
408
+ activeId,
409
+ onSelect
410
+ },
411
+ node.path
412
+ )) });
413
+ }
414
+ function ArtifactTreeRow({
415
+ node,
416
+ activeId,
417
+ onSelect
418
+ }) {
419
+ const [open, setOpen] = useState(true);
420
+ if (node.artifact) {
421
+ return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(
422
+ "button",
423
+ {
424
+ type: "button",
425
+ "data-slot": "artifact-panel-list-item",
426
+ "data-active": node.artifact.id === activeId,
427
+ className: "flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent data-[active=true]:bg-accent",
428
+ onClick: () => onSelect?.(node.artifact.id),
429
+ children: [
430
+ /* @__PURE__ */ jsx3(ArtifactKindIcon, { artifact: node.artifact }),
431
+ /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate", children: node.artifact.title }),
432
+ /* @__PURE__ */ jsx3("span", { className: "shrink-0 text-xs text-muted-foreground", children: node.artifact.kind })
433
+ ]
434
+ }
435
+ ) });
436
+ }
437
+ return /* @__PURE__ */ jsxs3("li", { children: [
438
+ /* @__PURE__ */ jsxs3(
439
+ "button",
440
+ {
441
+ type: "button",
442
+ "data-slot": "artifact-panel-list-folder",
443
+ "aria-expanded": open,
444
+ "data-open": open,
445
+ className: "flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent",
446
+ onClick: () => setOpen((value) => !value),
447
+ children: [
448
+ /* @__PURE__ */ jsx3(
449
+ "span",
450
+ {
451
+ className: "inline-flex transition-transform data-[open=true]:rotate-90",
452
+ "data-open": open,
453
+ children: /* @__PURE__ */ jsx3(Icon2, { name: "chevron-right", size: 12 })
454
+ }
455
+ ),
456
+ /* @__PURE__ */ jsx3(Icon2, { name: "folder", size: 13 }),
457
+ /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate", children: node.name })
458
+ ]
459
+ }
460
+ ),
461
+ open && /* @__PURE__ */ jsx3("div", { className: "pl-4", children: /* @__PURE__ */ jsx3(
462
+ ArtifactListView,
463
+ {
464
+ nodes: node.children,
465
+ activeId,
466
+ onSelect
467
+ }
468
+ ) })
469
+ ] });
470
+ }
471
+ function ArtifactImageContent({ artifact }) {
472
+ const raw = String(artifact.content).trim();
473
+ const src = raw.startsWith("<svg") || raw.startsWith("<?xml") ? `data:image/svg+xml;utf8,${encodeURIComponent(raw)}` : raw;
474
+ return /* @__PURE__ */ jsx3("div", { "data-slot": "artifact-panel-image", className: "flex justify-center", children: /* @__PURE__ */ jsx3("img", { src, alt: artifact.title, className: "max-w-full rounded-md" }) });
475
+ }
476
+ function ArtifactContent({
477
+ artifact,
478
+ runtime,
479
+ viewers
480
+ }) {
481
+ const ext = resolveArtifactFileType(artifact);
482
+ const custom = (ext ? viewers?.[ext] : void 0) ?? viewers?.[artifact.kind];
483
+ if (custom) {
484
+ return /* @__PURE__ */ jsx3(Fragment2, { children: custom(artifact) });
485
+ }
486
+ if (isImageArtifact(artifact)) {
487
+ return /* @__PURE__ */ jsx3(ArtifactImageContent, { artifact });
488
+ }
489
+ switch (artifact.kind) {
490
+ case "markdown":
491
+ return /* @__PURE__ */ jsx3(Markdown, { content: String(artifact.content) });
492
+ case "code":
493
+ return /* @__PURE__ */ jsx3(
494
+ CodeBlock,
495
+ {
496
+ code: String(artifact.content),
497
+ language: artifact.language
498
+ }
499
+ );
500
+ case "text":
501
+ return /* @__PURE__ */ jsx3(
502
+ "pre",
503
+ {
504
+ "data-slot": "artifact-panel-text",
505
+ className: "text-sm break-words whitespace-pre-wrap",
506
+ children: String(artifact.content)
507
+ }
508
+ );
509
+ case "dsl":
510
+ return /* @__PURE__ */ jsx3(ArtifactDslContent, { content: artifact.content, runtime });
511
+ default:
512
+ return null;
513
+ }
514
+ }
515
+ function ArtifactPanel({
516
+ artifact,
517
+ artifacts,
518
+ activeArtifactId,
519
+ onSelectArtifact,
520
+ title = "\u4EA7\u7269",
521
+ emptyText = "\u6682\u65E0\u4EA7\u7269",
522
+ inspector,
523
+ header,
524
+ view: viewProp,
525
+ onViewChange,
526
+ viewRequest,
527
+ onViewRequestConsumed,
528
+ resizable = false,
529
+ onClose,
530
+ onDownload,
531
+ safeRegistry,
532
+ user,
533
+ artifactViewers,
534
+ className,
535
+ style
536
+ }) {
537
+ const runtime = useArtifactPreviewRuntime(safeRegistry, user);
538
+ const [innerView, setInnerView] = useState("content");
539
+ const viewControlled = viewProp !== void 0;
540
+ const view = viewControlled ? viewProp : innerView;
541
+ const switchView = (next) => {
542
+ if (!viewControlled) {
543
+ setInnerView(next);
544
+ }
545
+ onViewChange?.(next);
546
+ };
547
+ const activeId = activeArtifactId ?? artifact?.id;
548
+ useEffect(() => {
549
+ switchView("content");
550
+ }, [activeId]);
551
+ useEffect(() => {
552
+ if (viewRequest != null) {
553
+ switchView("list");
554
+ onViewRequestConsumed?.();
555
+ }
556
+ }, [viewRequest]);
557
+ const hasArtifacts = artifacts !== void 0 && artifacts.length > 0;
558
+ const panelRef = useRef(null);
559
+ const [width, setWidth] = useState(null);
560
+ const handleResizeStart = (event) => {
561
+ event.preventDefault();
562
+ const panel = panelRef.current;
563
+ if (!panel) {
564
+ return;
565
+ }
566
+ const startWidth = panel.getBoundingClientRect().width;
567
+ const startX = event.clientX;
568
+ const prevUserSelect = document.body.style.userSelect;
569
+ document.body.style.userSelect = "none";
570
+ const onMove = (moveEvent) => {
571
+ const hostRect = panel.parentElement?.getBoundingClientRect();
572
+ const hostWidth = hostRect !== void 0 && hostRect.width > 0 ? hostRect.width : window.innerWidth;
573
+ const maxWidth = Math.min(600, hostWidth * 2 / 3);
574
+ const next = startWidth + (startX - moveEvent.clientX);
575
+ setWidth(Math.round(Math.min(Math.max(next, 480), maxWidth)));
576
+ };
577
+ const onUp = () => {
578
+ document.body.style.userSelect = prevUserSelect;
579
+ document.removeEventListener("pointermove", onMove);
580
+ document.removeEventListener("pointerup", onUp);
581
+ };
582
+ document.addEventListener("pointermove", onMove);
583
+ document.addEventListener("pointerup", onUp);
584
+ };
585
+ const headerTitle = view === "list" ? "\u4EA7\u7269\u76EE\u5F55" : artifact?.title ?? title;
586
+ return /* @__PURE__ */ jsxs3(
587
+ "aside",
588
+ {
589
+ ref: panelRef,
590
+ "data-slot": "artifact-panel",
591
+ "data-kind": artifact?.kind,
592
+ className: cn(
593
+ "flex h-full min-w-0 flex-col",
594
+ resizable && "relative shrink-0",
595
+ className
596
+ ),
597
+ style: width !== null ? { ...style, width: `${width}px` } : style,
598
+ children: [
599
+ resizable ? /* @__PURE__ */ jsx3(
600
+ "div",
601
+ {
602
+ "data-slot": "artifact-panel-resize",
603
+ role: "separator",
604
+ "aria-orientation": "vertical",
605
+ "aria-label": "\u62D6\u52A8\u8C03\u6574\u9762\u677F\u5BBD\u5EA6",
606
+ className: "absolute top-0 left-0 z-10 h-full w-1 cursor-col-resize touch-none hover:bg-accent",
607
+ onPointerDown: handleResizeStart
608
+ }
609
+ ) : null,
610
+ header !== void 0 && header !== null ? header : /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-1 border-b border-border px-2 py-2.5", children: [
611
+ hasArtifacts ? /* @__PURE__ */ jsx3(
612
+ Button,
613
+ {
614
+ icon: "list",
615
+ variant: "text",
616
+ tooltip: view === "list" ? "\u8FD4\u56DE\u5185\u5BB9" : "\u4EA7\u7269\u76EE\u5F55",
617
+ onClick: () => switchView(view === "list" ? "content" : "list")
618
+ }
619
+ ) : null,
620
+ hasArtifacts ? /* @__PURE__ */ jsx3(
621
+ DropdownMenu,
622
+ {
623
+ items: artifacts.map((item) => ({
624
+ label: item.title,
625
+ value: item.id,
626
+ icon: resolveArtifactIconName(item)
627
+ })),
628
+ onSelect: (id) => onSelectArtifact?.(id),
629
+ children: /* @__PURE__ */ jsxs3(
630
+ "span",
631
+ {
632
+ "data-slot": "artifact-panel-menu-trigger",
633
+ className: "flex min-w-0 cursor-pointer items-center gap-1 rounded-md px-1.5 py-1 text-sm font-medium hover:bg-accent",
634
+ children: [
635
+ view !== "list" && artifact ? /* @__PURE__ */ jsx3(ArtifactKindIcon, { artifact }) : null,
636
+ /* @__PURE__ */ jsx3("span", { className: "truncate", children: headerTitle }),
637
+ /* @__PURE__ */ jsx3(Icon2, { name: "chevron-down", size: 12 })
638
+ ]
639
+ }
640
+ )
641
+ }
642
+ ) : /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate px-1.5 text-sm font-medium", children: headerTitle }),
643
+ view !== "list" && artifact ? /* @__PURE__ */ jsx3("span", { className: "shrink-0 rounded-md bg-accent px-1.5 py-0.5 text-xs text-muted-foreground", children: artifact.kind }) : null,
644
+ /* @__PURE__ */ jsx3("span", { className: "flex-1" }),
645
+ artifact ? /* @__PURE__ */ jsx3(
646
+ Button,
647
+ {
648
+ icon: "download",
649
+ variant: "text",
650
+ tooltip: "\u4E0B\u8F7D\u4EA7\u7269",
651
+ className: "px-2",
652
+ onClick: () => onDownload ? onDownload(artifact) : void downloadArtifact(artifact)
653
+ }
654
+ ) : null,
655
+ /* @__PURE__ */ jsx3(
656
+ Button,
657
+ {
658
+ icon: "x",
659
+ variant: "text",
660
+ tooltip: "\u6536\u8D77\u4EA7\u7269\u533A",
661
+ className: "px-2",
662
+ onClick: () => onClose?.()
663
+ }
664
+ )
665
+ ] }),
666
+ /* @__PURE__ */ jsx3("div", { className: "scrollbar-subtle min-h-0 flex-1 overflow-auto p-4", children: view === "list" && artifacts !== void 0 ? /* @__PURE__ */ jsx3(
667
+ ArtifactListView,
668
+ {
669
+ nodes: buildArtifactTree(artifacts),
670
+ activeId,
671
+ onSelect: onSelectArtifact
672
+ }
673
+ ) : inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ jsx3(
674
+ ArtifactContent,
675
+ {
676
+ artifact,
677
+ runtime,
678
+ viewers: artifactViewers
679
+ }
680
+ ) : /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: emptyText }) })
681
+ ]
682
+ }
683
+ );
684
+ }
685
+
686
+ // src/components/Attachment/index.tsx
687
+ import { inferKind } from "@pactor-app/ui/components";
688
+ import { Icon as Icon3 } from "@pactor-app/ui/components";
689
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
690
+ var kindIcon = { image: "image", text: "file-text", video: "video", audio: "music", other: "file" };
691
+ function formatSize(bytes) {
692
+ if (bytes < 1024) return `${bytes} B`;
693
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
694
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
695
+ }
696
+ function Attachment({ items = [], onRemove, onRetry, onClick, className, style }) {
697
+ return /* @__PURE__ */ jsx4("div", { "data-slot": "attachment", className: cn("flex flex-wrap gap-2", className), style, children: items.map((item, index) => {
698
+ const key = item.id ?? index;
699
+ const kind = inferKind(item.name, item.fileType);
700
+ const status = item.status ?? "done";
701
+ return /* @__PURE__ */ jsxs4(
702
+ "div",
703
+ {
704
+ "data-slot": "attachment-item",
705
+ "data-status": status,
706
+ className: cn(
707
+ "flex w-56 flex-col rounded-md border border-border px-2 py-1.5",
708
+ onClick && "cursor-pointer"
709
+ ),
710
+ onClick: onClick ? () => onClick(item) : void 0,
711
+ children: [
712
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5", children: [
713
+ /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx4(Icon3, { name: kindIcon[kind], size: "sm" }) }),
714
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-sm", children: item.name }),
715
+ status === "uploading" ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none", children: /* @__PURE__ */ jsx4(Icon3, { name: "loader-circle", size: "sm" }) }) : null,
716
+ status === "error" && onRetry ? /* @__PURE__ */ jsx4(
717
+ "button",
718
+ {
719
+ type: "button",
720
+ "data-slot": "attachment-retry",
721
+ "aria-label": `\u91CD\u8BD5 ${item.name}`,
722
+ className: "shrink-0 text-destructive hover:opacity-80",
723
+ onClick: (event) => {
724
+ event.stopPropagation();
725
+ onRetry(key);
726
+ },
727
+ children: /* @__PURE__ */ jsx4(Icon3, { name: "refresh-ccw", size: "sm" })
728
+ }
729
+ ) : null,
730
+ onRemove ? /* @__PURE__ */ jsx4(
731
+ "button",
732
+ {
733
+ type: "button",
734
+ "data-slot": "attachment-remove",
735
+ "aria-label": `\u79FB\u9664 ${item.name}`,
736
+ className: "shrink-0 text-muted-foreground hover:text-foreground",
737
+ onClick: (event) => {
738
+ event.stopPropagation();
739
+ onRemove(key);
740
+ },
741
+ children: /* @__PURE__ */ jsx4(Icon3, { name: "x", size: "sm" })
742
+ }
743
+ ) : null
744
+ ] }),
745
+ /* @__PURE__ */ jsxs4("div", { className: "mt-0.5 flex items-center gap-1.5 pl-5 text-xs", children: [
746
+ item.fileSize !== void 0 ? /* @__PURE__ */ jsx4("span", { className: "text-muted-foreground", children: formatSize(item.fileSize) }) : null,
747
+ status === "error" ? /* @__PURE__ */ jsx4("span", { className: "text-destructive", children: "\u4E0A\u4F20\u5931\u8D25" }) : null
748
+ ] }),
749
+ status === "uploading" && item.progress !== void 0 ? /* @__PURE__ */ jsx4("div", { "data-slot": "attachment-progress", className: "mt-1 ml-5 h-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx4("div", { className: "h-full bg-primary transition-[width]", style: { width: `${item.progress}%` } }) }) : null
750
+ ]
751
+ },
752
+ key
753
+ );
754
+ }) });
755
+ }
756
+
757
+ // src/components/Bubble/index.tsx
758
+ import { Bubble as ShadcnBubble, BubbleContent } from "@pactor-app/ui/ui";
759
+ import { jsx as jsx5 } from "react/jsx-runtime";
760
+ function Bubble({ align = "start", variant = "muted", className, style, children }) {
761
+ return /* @__PURE__ */ jsx5(ShadcnBubble, { variant, align, className, style, children: /* @__PURE__ */ jsx5(BubbleContent, { children }) });
762
+ }
763
+
764
+ // src/components/ChatMessage/index.tsx
765
+ import { Icon as Icon7 } from "@pactor-app/ui/components";
766
+ import { Markdown as Markdown2 } from "@pactor-app/ui/components";
767
+ import { Tooltip } from "@pactor-app/ui/components";
768
+
769
+ // src/components/Sender/index.tsx
770
+ import { Avatar } from "@pactor-app/ui/components";
771
+ import { Icon as Icon4 } from "@pactor-app/ui/components";
772
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
773
+ function Sender({ name, avatar, avatarText, icon, hideAvatar = false, role, time, align = "start", className, style }) {
774
+ return /* @__PURE__ */ jsxs5(
775
+ "div",
776
+ {
777
+ "data-slot": "sender",
778
+ className: cn("flex items-center gap-2", align === "end" && "flex-row-reverse", className),
779
+ style,
780
+ children: [
781
+ hideAvatar ? null : icon && !avatar ? /* @__PURE__ */ jsx6(
782
+ "span",
783
+ {
784
+ "data-slot": "sender-icon",
785
+ className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground",
786
+ children: /* @__PURE__ */ jsx6(Icon4, { name: icon, size: "sm" })
787
+ }
788
+ ) : /* @__PURE__ */ jsx6(Avatar, { src: avatar, fallback: avatarText ?? name?.slice(0, 1) ?? "?", className: "size-7" }),
789
+ /* @__PURE__ */ jsxs5("div", { className: cn("flex min-w-0 items-baseline gap-1.5", align === "end" && "flex-row-reverse"), children: [
790
+ name ? /* @__PURE__ */ jsx6("span", { "data-slot": "sender-name", className: "truncate text-sm font-medium", children: name }) : null,
791
+ role ? /* @__PURE__ */ jsx6("span", { "data-slot": "sender-role", className: "shrink-0 text-xs text-muted-foreground", children: role }) : null,
792
+ time ? /* @__PURE__ */ jsx6("span", { "data-slot": "sender-time", className: "shrink-0 text-xs text-muted-foreground", children: time }) : null
793
+ ] })
794
+ ]
795
+ }
796
+ );
797
+ }
798
+
799
+ // src/components/StreamingText/index.tsx
800
+ import { useEffect as useEffect2, useState as useState2 } from "react";
801
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
802
+ function StreamingText({ content = "", streaming, typing, speed = 2, className, style }) {
803
+ const [revealed, setRevealed] = useState2(typing ? 0 : content.length);
804
+ useEffect2(() => {
805
+ if (!typing) {
806
+ setRevealed(content.length);
807
+ return;
808
+ }
809
+ setRevealed(0);
810
+ const timer = window.setInterval(() => {
811
+ setRevealed((value) => {
812
+ if (value >= content.length) {
813
+ window.clearInterval(timer);
814
+ return value;
815
+ }
816
+ return Math.min(content.length, value + speed);
817
+ });
818
+ }, 24);
819
+ return () => window.clearInterval(timer);
820
+ }, [content, typing, speed]);
821
+ const typingDone = !typing || revealed >= content.length;
822
+ const showCursor = streaming || typing && !typingDone;
823
+ return /* @__PURE__ */ jsxs6("span", { "data-slot": "streaming-text", className: cn("whitespace-pre-wrap", className), style, children: [
824
+ typing ? content.slice(0, revealed) : content,
825
+ showCursor ? /* @__PURE__ */ jsx7(
826
+ "span",
827
+ {
828
+ "data-slot": "streaming-text-cursor",
829
+ "aria-hidden": "true",
830
+ className: "ml-0.5 inline-block animate-pulse text-primary motion-reduce:animate-none",
831
+ children: "\u258D"
832
+ }
833
+ ) : null
834
+ ] });
835
+ }
836
+
837
+ // src/components/Thinking/index.tsx
838
+ import { useState as useState3 } from "react";
839
+ import { Icon as Icon5 } from "@pactor-app/ui/components";
840
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
841
+ function Thinking({
842
+ content,
843
+ status = "thinking",
844
+ title,
845
+ expanded,
846
+ defaultExpanded,
847
+ onToggle,
848
+ className,
849
+ style
850
+ }) {
851
+ const [innerExpanded, setInnerExpanded] = useState3(defaultExpanded ?? status === "thinking");
852
+ const isExpanded = expanded ?? innerExpanded;
853
+ const resolvedTitle = title ?? (status === "thinking" ? "\u601D\u8003\u4E2D\u2026" : "\u601D\u8003\u5B8C\u6210");
854
+ const toggle = () => {
855
+ if (expanded === void 0) {
856
+ setInnerExpanded(!isExpanded);
857
+ }
858
+ onToggle?.(!isExpanded);
859
+ };
860
+ return /* @__PURE__ */ jsxs7(
861
+ "div",
862
+ {
863
+ "data-slot": "thinking",
864
+ "data-status": status,
865
+ className: cn("w-full", className),
866
+ style,
867
+ children: [
868
+ /* @__PURE__ */ jsxs7(
869
+ "button",
870
+ {
871
+ type: "button",
872
+ "data-slot": "thinking-trigger",
873
+ "aria-expanded": isExpanded,
874
+ className: "flex w-full cursor-pointer items-center gap-2 py-1 text-left text-sm",
875
+ onClick: toggle,
876
+ children: [
877
+ /* @__PURE__ */ jsx8("span", { className: cn("text-muted-foreground", status === "thinking" && "animate-pulse motion-reduce:animate-none"), children: /* @__PURE__ */ jsx8(Icon5, { name: "brain", size: "sm" }) }),
878
+ /* @__PURE__ */ jsx8("span", { "data-slot": "thinking-title", className: "text-muted-foreground", children: resolvedTitle }),
879
+ /* @__PURE__ */ jsx8("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx8(Icon5, { name: isExpanded ? "chevron-down" : "chevron-right", size: "sm" }) })
880
+ ]
881
+ }
882
+ ),
883
+ isExpanded && content ? /* @__PURE__ */ jsx8(
884
+ "div",
885
+ {
886
+ "data-slot": "thinking-content",
887
+ className: "py-1 pl-6 text-sm whitespace-pre-wrap text-muted-foreground",
888
+ children: content
889
+ }
890
+ ) : null
891
+ ]
892
+ }
893
+ );
894
+ }
895
+
896
+ // src/components/ToolCall/index.tsx
897
+ import { useState as useState4 } from "react";
898
+ import { Icon as Icon6 } from "@pactor-app/ui/components";
899
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
900
+ var statusIcon = {
901
+ running: { icon: "loader-circle", className: "text-info", spin: true },
902
+ done: { icon: "circle-check", className: "text-success" },
903
+ error: { icon: "circle-x", className: "text-destructive" }
904
+ };
905
+ function renderValue(value) {
906
+ if (value === void 0 || value === null) return "";
907
+ if (typeof value === "string") return value;
908
+ try {
909
+ return JSON.stringify(value, null, 2);
910
+ } catch {
911
+ return String(value);
912
+ }
913
+ }
914
+ function ToolCall({ name, params, status = "done", result, durationMs, defaultExpanded, className, style }) {
915
+ const [expanded, setExpanded] = useState4(defaultExpanded ?? status === "running");
916
+ const meta = statusIcon[status];
917
+ const paramsText = renderValue(params);
918
+ const hasDetail = Boolean(paramsText) || Boolean(result);
919
+ const headerInner = /* @__PURE__ */ jsxs8(Fragment3, { children: [
920
+ /* @__PURE__ */ jsx9("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx9(Icon6, { name: "terminal", size: "sm" }) }),
921
+ /* @__PURE__ */ jsx9("span", { "data-slot": "tool-call-name", className: "min-w-0 truncate font-medium", children: name ?? "\u5DE5\u5177\u8C03\u7528" }),
922
+ /* @__PURE__ */ jsx9("span", { className: cn("shrink-0", meta.className, meta.spin && "animate-spin motion-reduce:animate-none"), children: /* @__PURE__ */ jsx9(Icon6, { name: meta.icon, size: "sm" }) }),
923
+ durationMs !== void 0 ? /* @__PURE__ */ jsxs8("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
924
+ (durationMs / 1e3).toFixed(1),
925
+ "s"
926
+ ] }) : null,
927
+ hasDetail ? /* @__PURE__ */ jsx9("span", { className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx9(Icon6, { name: expanded ? "chevron-down" : "chevron-right", size: "sm" }) }) : null
928
+ ] });
929
+ return /* @__PURE__ */ jsxs8(
930
+ "div",
931
+ {
932
+ "data-slot": "tool-call",
933
+ "data-status": status,
934
+ className: cn("w-full", className),
935
+ style,
936
+ children: [
937
+ hasDetail ? /* @__PURE__ */ jsx9(
938
+ "button",
939
+ {
940
+ type: "button",
941
+ "data-slot": "tool-call-trigger",
942
+ "aria-expanded": expanded,
943
+ className: "flex w-full cursor-pointer items-center gap-2 py-1 text-left text-sm",
944
+ onClick: () => setExpanded((value) => !value),
945
+ children: headerInner
946
+ }
947
+ ) : /* @__PURE__ */ jsx9(
948
+ "div",
949
+ {
950
+ "data-slot": "tool-call-trigger",
951
+ className: "flex w-full items-center gap-2 py-1 text-left text-sm",
952
+ children: headerInner
953
+ }
954
+ ),
955
+ hasDetail && expanded ? /* @__PURE__ */ jsxs8("div", { "data-slot": "tool-call-body", className: "flex flex-col gap-2 py-1 pl-6", children: [
956
+ paramsText ? /* @__PURE__ */ jsxs8("div", { children: [
957
+ /* @__PURE__ */ jsx9("div", { className: "text-xs font-medium text-muted-foreground", children: "\u53C2\u6570" }),
958
+ /* @__PURE__ */ jsx9("pre", { "data-slot": "tool-call-params", className: "mt-1 overflow-x-auto text-xs whitespace-pre-wrap text-muted-foreground", children: paramsText })
959
+ ] }) : null,
960
+ result ? /* @__PURE__ */ jsxs8("div", { children: [
961
+ /* @__PURE__ */ jsx9("div", { className: "text-xs font-medium text-muted-foreground", children: "\u7ED3\u679C" }),
962
+ /* @__PURE__ */ jsx9("pre", { "data-slot": "tool-call-result", className: "mt-1 overflow-x-auto text-xs whitespace-pre-wrap text-muted-foreground", children: result })
963
+ ] }) : null
964
+ ] }) : null
965
+ ]
966
+ }
967
+ );
968
+ }
969
+
970
+ // src/components/ChatMessage/index.tsx
971
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
972
+ function PartView({
973
+ part,
974
+ role,
975
+ onOpenArtifacts
976
+ }) {
977
+ if (role === "system" && (part.type === "text" || part.type === "markdown")) {
978
+ return /* @__PURE__ */ jsx10(
979
+ "div",
980
+ {
981
+ "data-slot": "chat-message-system-part",
982
+ className: "w-full text-center text-xs text-muted-foreground",
983
+ children: part.type === "markdown" ? /* @__PURE__ */ jsx10(Markdown2, { content: part.content }) : part.content
984
+ }
985
+ );
986
+ }
987
+ switch (part.type) {
988
+ case "text":
989
+ return /* @__PURE__ */ jsx10(Bubble, { align: role === "user" ? "end" : "start", variant: role === "user" ? "default" : "muted", children: /* @__PURE__ */ jsx10("span", { className: "text-sm whitespace-pre-wrap", children: part.content }) });
990
+ case "markdown":
991
+ return /* @__PURE__ */ jsx10(Bubble, { align: role === "user" ? "end" : "start", variant: role === "user" ? "default" : "ghost", children: /* @__PURE__ */ jsx10(Markdown2, { content: part.content }) });
992
+ case "thinking":
993
+ return /* @__PURE__ */ jsx10(Thinking, { content: part.content, status: part.status ?? "done" });
994
+ case "tool-call":
995
+ return /* @__PURE__ */ jsx10(ToolCall, { name: part.name, params: part.params, status: part.status ?? "done", result: part.result });
996
+ case "tool-result":
997
+ return /* @__PURE__ */ jsx10("pre", { "data-slot": "chat-message-tool-result", className: "overflow-x-auto rounded-md bg-muted p-2 text-xs whitespace-pre-wrap", children: part.content });
998
+ case "status":
999
+ return /* @__PURE__ */ jsxs9("div", { "data-slot": "chat-message-status", className: "flex w-fit items-center gap-1.5 text-muted-foreground", children: [
1000
+ /* @__PURE__ */ jsx10(Icon7, { name: "clock", size: "sm" }),
1001
+ part.text
1002
+ ] });
1003
+ case "artifacts": {
1004
+ const artifactsClass = "flex w-fit items-center gap-1.5 text-muted-foreground";
1005
+ const label = `\u5DF2\u751F\u6210 ${part.count} \u4E2A\u4EA7\u7269`;
1006
+ return onOpenArtifacts ? /* @__PURE__ */ jsxs9(
1007
+ "button",
1008
+ {
1009
+ type: "button",
1010
+ "data-slot": "chat-message-artifacts",
1011
+ className: cn(artifactsClass, "cursor-pointer hover:text-foreground"),
1012
+ onClick: onOpenArtifacts,
1013
+ children: [
1014
+ /* @__PURE__ */ jsx10(Icon7, { name: "package", size: "sm" }),
1015
+ label,
1016
+ /* @__PURE__ */ jsx10(Icon7, { name: "chevron-right", size: 12 })
1017
+ ]
1018
+ }
1019
+ ) : /* @__PURE__ */ jsxs9("div", { "data-slot": "chat-message-artifacts", className: artifactsClass, children: [
1020
+ /* @__PURE__ */ jsx10(Icon7, { name: "package", size: "sm" }),
1021
+ label
1022
+ ] });
1023
+ }
1024
+ case "error":
1025
+ return /* @__PURE__ */ jsxs9("div", { "data-slot": "chat-message-error", className: "flex items-center gap-1.5 text-sm text-destructive", children: [
1026
+ /* @__PURE__ */ jsx10(Icon7, { name: "circle-alert", size: "sm" }),
1027
+ part.message
1028
+ ] });
1029
+ default:
1030
+ return null;
1031
+ }
1032
+ }
1033
+ var ACTION_ICON_BY_NAME = {
1034
+ copy: "copy",
1035
+ retry: "rotate-ccw",
1036
+ regenerate: "refresh-ccw",
1037
+ edit: "pencil",
1038
+ like: "thumbs-up",
1039
+ dislike: "thumbs-down",
1040
+ share: "share",
1041
+ delete: "trash"
1042
+ };
1043
+ function ChatMessage({
1044
+ role = "assistant",
1045
+ parts = [],
1046
+ status = "completed",
1047
+ agent,
1048
+ time,
1049
+ showSender = false,
1050
+ actions = [],
1051
+ onAction,
1052
+ onOpenArtifacts,
1053
+ className,
1054
+ style
1055
+ }) {
1056
+ const isUser = role === "user";
1057
+ const isSystem = role === "system";
1058
+ const senderName = isUser ? "\u6211" : agent?.name ?? "\u52A9\u624B";
1059
+ return /* @__PURE__ */ jsxs9(
1060
+ "div",
1061
+ {
1062
+ "data-slot": "chat-message",
1063
+ "data-role": role,
1064
+ "data-status": status,
1065
+ className: cn(
1066
+ "flex w-full flex-col gap-1.5",
1067
+ isUser && "items-end",
1068
+ // system 居中弱提示(参考 @pactor-app/chat MessageBubble)
1069
+ isSystem && "items-center text-muted-foreground",
1070
+ className
1071
+ ),
1072
+ style,
1073
+ children: [
1074
+ showSender ? /* @__PURE__ */ jsx10(
1075
+ Sender,
1076
+ {
1077
+ name: senderName,
1078
+ avatarText: isUser ? void 0 : agent?.icon ? void 0 : senderName.slice(0, 1),
1079
+ time,
1080
+ align: isUser ? "end" : "start"
1081
+ }
1082
+ ) : null,
1083
+ /* @__PURE__ */ jsxs9("div", { className: cn("flex min-w-0 w-full max-w-full flex-col gap-2", isUser && "items-end", isSystem && "items-center"), children: [
1084
+ parts.map((part, index) => /* @__PURE__ */ jsx10(PartView, { part, role, onOpenArtifacts }, index)),
1085
+ status === "streaming" ? /* @__PURE__ */ jsx10(StreamingText, { content: "", streaming: true }) : null
1086
+ ] }),
1087
+ actions.length > 0 && (status === "completed" || status === "error") ? /* @__PURE__ */ jsx10("div", { "data-slot": "chat-message-actions", className: "flex items-center gap-1 mt-2", children: actions.map((action) => {
1088
+ const label = action.label ?? action.name;
1089
+ const iconName = action.icon ?? ACTION_ICON_BY_NAME[action.name];
1090
+ if (iconName) {
1091
+ return /* @__PURE__ */ jsx10(Tooltip, { content: label, side: "top", children: /* @__PURE__ */ jsx10(
1092
+ "button",
1093
+ {
1094
+ type: "button",
1095
+ "data-slot": "chat-message-action",
1096
+ "data-action": action.name,
1097
+ "aria-label": label,
1098
+ className: "flex cursor-pointer items-center rounded-sm p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
1099
+ onClick: () => onAction?.(action.name),
1100
+ children: /* @__PURE__ */ jsx10(Icon7, { name: iconName, size: "sm" })
1101
+ }
1102
+ ) }, action.name);
1103
+ }
1104
+ return /* @__PURE__ */ jsx10(
1105
+ "button",
1106
+ {
1107
+ type: "button",
1108
+ "data-slot": "chat-message-action",
1109
+ "data-action": action.name,
1110
+ className: "flex items-center cursor-pointer gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
1111
+ onClick: () => onAction?.(action.name),
1112
+ children: label
1113
+ },
1114
+ action.name
1115
+ );
1116
+ }) }) : null
1117
+ ]
1118
+ }
1119
+ );
1120
+ }
1121
+
1122
+ // src/components/ChatPage/index.tsx
1123
+ import { useRenderer as useRenderer2 } from "@pactor-app/runtime";
1124
+ import { isValidElement as isValidElement2, useEffect as useEffect5, useState as useState8 } from "react";
1125
+ import {
1126
+ Panel,
1127
+ PanelGroup,
1128
+ ResizableHandle
1129
+ } from "@pactor-app/ui/ui";
1130
+ import { Button as Button2 } from "@pactor-app/ui/components";
1131
+ import { Icon as Icon13 } from "@pactor-app/ui/components";
1132
+ import { Menu } from "@pactor-app/ui/components";
1133
+ import { Text } from "@pactor-app/ui/components";
1134
+ import { Tooltip as Tooltip2 } from "@pactor-app/ui/components";
1135
+
1136
+ // src/components/Composer/index.tsx
1137
+ import {
1138
+ isValidElement,
1139
+ useRef as useRef3,
1140
+ useState as useState6
1141
+ } from "react";
1142
+ import { ChatInput, useOptionalRenderer } from "@pactor-app/ui/components";
1143
+ import { Icon as Icon9 } from "@pactor-app/ui/components";
1144
+
1145
+ // src/components/Composer/mention-input.tsx
1146
+ import {
1147
+ forwardRef,
1148
+ useEffect as useEffect3,
1149
+ useImperativeHandle,
1150
+ useRef as useRef2,
1151
+ useState as useState5
1152
+ } from "react";
1153
+ import { renderToStaticMarkup } from "react-dom/server";
1154
+ import { Icon as Icon8 } from "@pactor-app/ui/components";
1155
+ import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1156
+ var NBSP = "\xA0";
1157
+ var CHIP_ATTR = "data-mention-chip";
1158
+ function escapeRegExp(text) {
1159
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1160
+ }
1161
+ function isChipElement(node) {
1162
+ return node instanceof HTMLElement && node.hasAttribute(CHIP_ATTR);
1163
+ }
1164
+ function serializeNode(node) {
1165
+ let out = "";
1166
+ node.childNodes.forEach((child) => {
1167
+ if (child.nodeType === Node.TEXT_NODE) {
1168
+ out += (child.textContent ?? "").replace(/\u00A0/g, " ");
1169
+ } else if (isChipElement(child)) {
1170
+ out += `${child.dataset["trigger"] ?? ""}${child.dataset["label"] ?? ""}`;
1171
+ } else if (child instanceof HTMLBRElement) {
1172
+ out += "\n";
1173
+ } else if (child instanceof HTMLElement) {
1174
+ const inner = serializeNode(child).replace(/\n$/, "");
1175
+ out += (out === "" || out.endsWith("\n") ? "" : "\n") + inner + "\n";
1176
+ }
1177
+ });
1178
+ return out;
1179
+ }
1180
+ function collectChips(field) {
1181
+ return Array.from(field.querySelectorAll(`[${CHIP_ATTR}]`)).map((el) => ({
1182
+ id: el.dataset["id"] ?? "",
1183
+ label: el.dataset["label"] ?? "",
1184
+ trigger: el.dataset["trigger"] ?? ""
1185
+ }));
1186
+ }
1187
+ function detectActiveMention(field, sources) {
1188
+ const selection = window.getSelection();
1189
+ if (!selection || selection.rangeCount === 0 || !selection.isCollapsed) {
1190
+ return null;
1191
+ }
1192
+ const anchor = selection.anchorNode;
1193
+ if (!anchor || anchor.nodeType !== Node.TEXT_NODE || !field.contains(anchor)) {
1194
+ return null;
1195
+ }
1196
+ const caretOffset = selection.anchorOffset;
1197
+ const beforeCaret = (anchor.textContent ?? "").slice(0, caretOffset);
1198
+ for (const source of sources) {
1199
+ const trigger = source.trigger;
1200
+ const match = new RegExp(
1201
+ `(?:^|\\s)${escapeRegExp(trigger)}([^\\s${escapeRegExp(trigger)}]*)$`
1202
+ ).exec(beforeCaret);
1203
+ if (match) {
1204
+ const query = match[1] ?? "";
1205
+ return {
1206
+ source,
1207
+ query,
1208
+ node: anchor,
1209
+ triggerStart: caretOffset - query.length - trigger.length,
1210
+ caretOffset
1211
+ };
1212
+ }
1213
+ }
1214
+ return null;
1215
+ }
1216
+ function chipIcon(item, source) {
1217
+ return item.icon ?? source.icon ?? (source.trigger === "@" ? "user" : "file-text");
1218
+ }
1219
+ var MentionInput = forwardRef(
1220
+ function MentionInput2({
1221
+ value,
1222
+ placeholder,
1223
+ disabled,
1224
+ sources,
1225
+ chips,
1226
+ onChipsChange,
1227
+ onChange,
1228
+ onSubmit,
1229
+ className,
1230
+ style
1231
+ }, ref) {
1232
+ const fieldRef = useRef2(null);
1233
+ const [menu, setMenu] = useState5(null);
1234
+ const [empty, setEmpty] = useState5(true);
1235
+ const suppressSyncRef = useRef2(false);
1236
+ const composingRef = useRef2(false);
1237
+ const emitChips = () => {
1238
+ if (suppressSyncRef.current || !fieldRef.current) {
1239
+ return;
1240
+ }
1241
+ onChipsChange?.(collectChips(fieldRef.current));
1242
+ };
1243
+ const serialize = () => fieldRef.current ? serializeNode(fieldRef.current).replace(/\n$/, "") : "";
1244
+ const buildChipElement = (item, source) => {
1245
+ const chip = document.createElement("span");
1246
+ chip.setAttribute(CHIP_ATTR, "");
1247
+ chip.dataset["id"] = item.id;
1248
+ chip.dataset["label"] = item.label;
1249
+ chip.dataset["trigger"] = item.trigger ?? source.trigger;
1250
+ chip.contentEditable = "false";
1251
+ chip.className = "mx-0.5 inline-flex items-center gap-1 rounded-md bg-muted px-1.5 py-0.5 align-middle text-xs text-foreground";
1252
+ const iconMarkup = renderToStaticMarkup(
1253
+ /* @__PURE__ */ jsx11(Icon8, { name: chipIcon(item, source), size: 12 })
1254
+ );
1255
+ const label = document.createElement("span");
1256
+ label.textContent = item.label;
1257
+ chip.innerHTML = iconMarkup;
1258
+ chip.appendChild(label);
1259
+ return chip;
1260
+ };
1261
+ const insertChipElement = (item, source, replace) => {
1262
+ const field = fieldRef.current;
1263
+ if (!field) {
1264
+ return;
1265
+ }
1266
+ const chip = buildChipElement(item, source);
1267
+ let caretNode;
1268
+ if (replace) {
1269
+ const { node, start, end } = replace;
1270
+ const text = node.textContent ?? "";
1271
+ node.textContent = text.slice(0, start);
1272
+ caretNode = document.createTextNode(`${NBSP}${text.slice(end)}`);
1273
+ node.after(chip, caretNode);
1274
+ } else {
1275
+ caretNode = document.createTextNode(NBSP);
1276
+ const selection2 = window.getSelection();
1277
+ if (selection2 && selection2.rangeCount > 0 && selection2.anchorNode !== null && field.contains(selection2.anchorNode)) {
1278
+ const range = selection2.getRangeAt(0);
1279
+ range.deleteContents();
1280
+ range.insertNode(caretNode);
1281
+ range.insertNode(chip);
1282
+ } else {
1283
+ field.append(chip, caretNode);
1284
+ }
1285
+ }
1286
+ const selection = window.getSelection();
1287
+ if (selection) {
1288
+ const range = document.createRange();
1289
+ range.setStart(caretNode, 1);
1290
+ range.collapse(true);
1291
+ selection.removeAllRanges();
1292
+ selection.addRange(range);
1293
+ }
1294
+ setEmpty(false);
1295
+ onChange?.(serialize());
1296
+ emitChips();
1297
+ };
1298
+ useImperativeHandle(ref, () => ({
1299
+ insertChip: (item, source) => {
1300
+ fieldRef.current?.focus();
1301
+ insertChipElement(item, source);
1302
+ },
1303
+ focus: () => fieldRef.current?.focus()
1304
+ }));
1305
+ useEffect3(() => {
1306
+ const field = fieldRef.current;
1307
+ if (field && value === "" && serializeNode(field).trim() !== "") {
1308
+ suppressSyncRef.current = true;
1309
+ field.innerHTML = "";
1310
+ setEmpty(true);
1311
+ setMenu(null);
1312
+ suppressSyncRef.current = false;
1313
+ }
1314
+ }, [value]);
1315
+ useEffect3(() => {
1316
+ const field = fieldRef.current;
1317
+ if (!field) {
1318
+ return;
1319
+ }
1320
+ const keep = new Set(chips.map((chip) => `${chip.trigger}:${chip.id}`));
1321
+ const stale = Array.from(field.querySelectorAll(`[${CHIP_ATTR}]`)).filter(
1322
+ (el) => !keep.has(
1323
+ `${el.dataset["trigger"]}:${el.dataset["id"]}`
1324
+ )
1325
+ );
1326
+ if (stale.length > 0) {
1327
+ suppressSyncRef.current = true;
1328
+ stale.forEach((el) => el.remove());
1329
+ suppressSyncRef.current = false;
1330
+ setEmpty(serializeNode(field).trim() === "");
1331
+ }
1332
+ }, [chips]);
1333
+ const closeMenu = () => setMenu(null);
1334
+ const filteredItems = (state) => state.query ? state.source.items.filter((item) => item.label.includes(state.query)) : state.source.items;
1335
+ const refreshMenu = () => {
1336
+ const field = fieldRef.current;
1337
+ if (!field) {
1338
+ return;
1339
+ }
1340
+ const active = detectActiveMention(field, sources);
1341
+ if (!active) {
1342
+ setMenu(null);
1343
+ return;
1344
+ }
1345
+ const range = document.createRange();
1346
+ range.setStart(active.node, active.triggerStart);
1347
+ range.setEnd(active.node, active.caretOffset);
1348
+ const rect = typeof range.getBoundingClientRect === "function" ? range.getBoundingClientRect() : field.getBoundingClientRect();
1349
+ setMenu((prev) => ({
1350
+ source: active.source,
1351
+ query: active.query,
1352
+ left: rect.left,
1353
+ top: rect.top,
1354
+ // 过滤结果变化后候选下标钳制在有效范围内(DSL 每次渲染产出新
1355
+ // props 对象,source 按 trigger 比较而非引用)
1356
+ activeIndex: Math.min(
1357
+ prev?.source.trigger === active.source.trigger ? prev.activeIndex : 0,
1358
+ Math.max(filteredItems({ source: active.source, query: active.query, left: 0, top: 0, activeIndex: 0 }).length - 1, 0)
1359
+ )
1360
+ }));
1361
+ };
1362
+ const selectMenuItem = (item) => {
1363
+ const field = fieldRef.current;
1364
+ if (!field || !menu) {
1365
+ return;
1366
+ }
1367
+ const active = detectActiveMention(field, sources);
1368
+ insertChipElement(
1369
+ { ...item, trigger: item.trigger ?? menu.source.trigger },
1370
+ menu.source,
1371
+ active && active.source.trigger === menu.source.trigger ? { node: active.node, start: active.triggerStart, end: active.caretOffset } : void 0
1372
+ );
1373
+ setMenu(null);
1374
+ };
1375
+ const handleInput = () => {
1376
+ const field = fieldRef.current;
1377
+ if (!field) {
1378
+ return;
1379
+ }
1380
+ if ((field.textContent ?? "") === "" && field.querySelector(`[${CHIP_ATTR}]`) === null) {
1381
+ field.innerHTML = "";
1382
+ }
1383
+ setEmpty(serializeNode(field).trim() === "");
1384
+ onChange?.(serialize());
1385
+ refreshMenu();
1386
+ };
1387
+ const handleKeyDown = (event) => {
1388
+ if (menu) {
1389
+ const items = filteredItems(menu);
1390
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1391
+ event.preventDefault();
1392
+ if (items.length > 0) {
1393
+ const delta = event.key === "ArrowDown" ? 1 : -1;
1394
+ setMenu({
1395
+ ...menu,
1396
+ activeIndex: (menu.activeIndex + delta + items.length) % items.length
1397
+ });
1398
+ }
1399
+ return;
1400
+ }
1401
+ if (event.key === "Enter" && !composingRef.current && !event.nativeEvent.isComposing) {
1402
+ event.preventDefault();
1403
+ const item = items[Math.min(menu.activeIndex, items.length - 1)];
1404
+ if (item) {
1405
+ selectMenuItem(item);
1406
+ } else {
1407
+ closeMenu();
1408
+ }
1409
+ return;
1410
+ }
1411
+ if (event.key === "Escape") {
1412
+ event.preventDefault();
1413
+ closeMenu();
1414
+ return;
1415
+ }
1416
+ }
1417
+ if (event.key === "Enter" && !event.shiftKey && !composingRef.current && !event.nativeEvent.isComposing) {
1418
+ event.preventDefault();
1419
+ const text = serialize();
1420
+ if (text.trim() !== "" && !disabled) {
1421
+ onSubmit?.(text);
1422
+ }
1423
+ return;
1424
+ }
1425
+ if (event.key === "Backspace") {
1426
+ const selection = window.getSelection();
1427
+ const field = fieldRef.current;
1428
+ if (!selection || !selection.isCollapsed || !field) {
1429
+ return;
1430
+ }
1431
+ let chip = null;
1432
+ const anchor = selection.anchorNode;
1433
+ if (anchor === field && selection.anchorOffset > 0) {
1434
+ const before = field.childNodes[selection.anchorOffset - 1];
1435
+ if (isChipElement(before)) {
1436
+ chip = before;
1437
+ }
1438
+ } else if (anchor?.nodeType === Node.TEXT_NODE && selection.anchorOffset === 0) {
1439
+ if (isChipElement(anchor.previousSibling)) {
1440
+ chip = anchor.previousSibling;
1441
+ }
1442
+ }
1443
+ if (chip) {
1444
+ event.preventDefault();
1445
+ chip.remove();
1446
+ onChange?.(serialize());
1447
+ emitChips();
1448
+ setEmpty(serializeNode(field).trim() === "");
1449
+ }
1450
+ }
1451
+ };
1452
+ const menuItems = menu ? filteredItems(menu) : [];
1453
+ const activeIndex = Math.min(menu?.activeIndex ?? 0, Math.max(menuItems.length - 1, 0));
1454
+ return /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1455
+ /* @__PURE__ */ jsx11(
1456
+ "div",
1457
+ {
1458
+ ref: fieldRef,
1459
+ "data-slot": "mention-input",
1460
+ role: "textbox",
1461
+ "aria-multiline": "true",
1462
+ "aria-placeholder": placeholder,
1463
+ contentEditable: !disabled,
1464
+ suppressContentEditableWarning: true,
1465
+ className: cn(
1466
+ "min-h-[60px] w-full px-4 pt-3 pb-1 text-sm whitespace-pre-wrap break-words outline-none",
1467
+ className
1468
+ ),
1469
+ style,
1470
+ onInput: handleInput,
1471
+ onKeyDown: handleKeyDown,
1472
+ onCompositionStart: () => {
1473
+ composingRef.current = true;
1474
+ },
1475
+ onCompositionEnd: () => {
1476
+ composingRef.current = false;
1477
+ },
1478
+ onPaste: (event) => {
1479
+ event.preventDefault();
1480
+ const text = event.clipboardData?.getData("text/plain") ?? "";
1481
+ if (text) {
1482
+ document.execCommand("insertText", false, text);
1483
+ }
1484
+ }
1485
+ }
1486
+ ),
1487
+ empty && placeholder !== void 0 ? /* @__PURE__ */ jsx11("span", { className: "pointer-events-none absolute top-3 left-4 text-sm text-muted-foreground", children: placeholder }) : null,
1488
+ menu ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
1489
+ /* @__PURE__ */ jsx11(
1490
+ "div",
1491
+ {
1492
+ className: "fixed inset-0 z-40",
1493
+ onMouseDown: (event) => {
1494
+ event.preventDefault();
1495
+ closeMenu();
1496
+ }
1497
+ }
1498
+ ),
1499
+ /* @__PURE__ */ jsxs10(
1500
+ "div",
1501
+ {
1502
+ "data-slot": "mention-menu",
1503
+ role: "listbox",
1504
+ className: "scrollbar-subtle fixed z-50 max-h-72 w-64 overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
1505
+ style: {
1506
+ left: menu.left,
1507
+ bottom: typeof window === "undefined" ? void 0 : window.innerHeight - menu.top + 6
1508
+ },
1509
+ children: [
1510
+ /* @__PURE__ */ jsx11("div", { className: "px-2 py-1 text-xs text-muted-foreground", children: menu.source.label }),
1511
+ menuItems.length === 0 ? /* @__PURE__ */ jsx11("div", { className: "px-2 py-2 text-xs text-muted-foreground", children: "\u65E0\u5339\u914D\u9879" }) : menuItems.map((item, index) => /* @__PURE__ */ jsxs10(
1512
+ "button",
1513
+ {
1514
+ type: "button",
1515
+ role: "option",
1516
+ "aria-selected": index === activeIndex,
1517
+ className: cn(
1518
+ "flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm",
1519
+ index === activeIndex ? "bg-accent" : "hover:bg-accent"
1520
+ ),
1521
+ onMouseDown: (event) => {
1522
+ event.preventDefault();
1523
+ selectMenuItem(item);
1524
+ },
1525
+ children: [
1526
+ /* @__PURE__ */ jsx11(Icon8, { name: chipIcon(item, menu.source), size: "sm" }),
1527
+ /* @__PURE__ */ jsx11("span", { className: "min-w-0 flex-1 truncate", children: item.label })
1528
+ ]
1529
+ },
1530
+ item.id
1531
+ ))
1532
+ ]
1533
+ }
1534
+ )
1535
+ ] }) : null
1536
+ ] });
1537
+ }
1538
+ );
1539
+
1540
+ // src/components/Composer/index.tsx
1541
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1542
+ var toolButtonClass = "flex cursor-pointer items-center rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground";
1543
+ var popoverPanelClass = "absolute bottom-full left-0 z-50 mb-2 max-h-72 w-64 overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md";
1544
+ var popoverItemClass = "flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent";
1545
+ function detectMention(text, caret) {
1546
+ const beforeCaret = text.slice(0, caret);
1547
+ const match = /(?:^|\s)@([^\s@]*)$/.exec(beforeCaret);
1548
+ return match ? match[1] : null;
1549
+ }
1550
+ function Composer({
1551
+ value,
1552
+ placeholder,
1553
+ disabled,
1554
+ loading,
1555
+ streaming = false,
1556
+ onStop,
1557
+ rows,
1558
+ attachments = [],
1559
+ onRemoveAttachment,
1560
+ onRetryAttachment,
1561
+ onAttachmentClick,
1562
+ onAttachFiles,
1563
+ onChange,
1564
+ onSubmit,
1565
+ models,
1566
+ model,
1567
+ onModelChange,
1568
+ contexts,
1569
+ contextItems,
1570
+ onContextItemsChange,
1571
+ onMentionTrigger,
1572
+ onPlusMenu,
1573
+ toolbarStart,
1574
+ toolbarEnd,
1575
+ hideToolbar = false,
1576
+ footer,
1577
+ className,
1578
+ style
1579
+ }) {
1580
+ const renderer = useOptionalRenderer();
1581
+ const textControlled = value !== void 0;
1582
+ const [innerText, setInnerText] = useState6("");
1583
+ const text = textControlled ? value : innerText;
1584
+ const [dragOver, setDragOver] = useState6(false);
1585
+ const [plusOpen, setPlusOpen] = useState6(false);
1586
+ const [modelOpen, setModelOpen] = useState6(false);
1587
+ const fileInputRef = useRef3(null);
1588
+ const mentionRef = useRef3(null);
1589
+ const sources = (contexts ?? []).filter(
1590
+ // 调用方接管 '@' 提及时对应源不启用内建行内提及(回调优先)
1591
+ (source) => !(onMentionTrigger !== void 0 && source.trigger === "@")
1592
+ );
1593
+ const rich = sources.length > 0;
1594
+ const plusSources = sources.filter((s) => s.trigger === "+");
1595
+ const plusEnabled = plusSources.length > 0 || onPlusMenu !== void 0;
1596
+ const modelEnabled = models !== void 0 && models.length > 0;
1597
+ const currentModel = models?.find((m) => m.value === model) ?? models?.[0];
1598
+ const [innerChips, setInnerChips] = useState6([]);
1599
+ const chipsControlled = contextItems !== void 0;
1600
+ const chips = chipsControlled ? contextItems : innerChips;
1601
+ const handleChipsChange = (next) => {
1602
+ if (!chipsControlled) {
1603
+ setInnerChips(next);
1604
+ }
1605
+ onContextItemsChange?.(next);
1606
+ };
1607
+ const closePopovers = () => {
1608
+ setPlusOpen(false);
1609
+ setModelOpen(false);
1610
+ };
1611
+ const handleChange = (next) => {
1612
+ if (!textControlled) {
1613
+ setInnerText(next);
1614
+ }
1615
+ onChange?.(next);
1616
+ };
1617
+ const handleSubmit = (submitted) => {
1618
+ onSubmit?.(submitted);
1619
+ if (!textControlled) {
1620
+ setInnerText("");
1621
+ }
1622
+ closePopovers();
1623
+ };
1624
+ const selectContext = (item, source) => {
1625
+ if (rich) {
1626
+ mentionRef.current?.insertChip(item, source);
1627
+ } else {
1628
+ const chip = { ...item, trigger: item.trigger ?? source.trigger };
1629
+ const duplicated = chips.some(
1630
+ (existing) => existing.id === chip.id && existing.trigger === chip.trigger
1631
+ );
1632
+ if (!duplicated) {
1633
+ handleChipsChange([...chips, chip]);
1634
+ }
1635
+ }
1636
+ closePopovers();
1637
+ };
1638
+ const emitFiles = (files) => {
1639
+ if (files.length > 0 && onAttachFiles) {
1640
+ onAttachFiles(files);
1641
+ }
1642
+ };
1643
+ const handleDrop = (event) => {
1644
+ if (!onAttachFiles) {
1645
+ return;
1646
+ }
1647
+ event.preventDefault();
1648
+ setDragOver(false);
1649
+ emitFiles(Array.from(event.dataTransfer?.files ?? []));
1650
+ };
1651
+ const renderSlot = (slot) => isValidElement(slot) || typeof slot !== "object" || slot === null ? slot : renderer?.renderChildren([slot]) ?? null;
1652
+ const builtinToolbarStart = plusEnabled || onAttachFiles ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
1653
+ plusEnabled ? /* @__PURE__ */ jsx12(
1654
+ "button",
1655
+ {
1656
+ type: "button",
1657
+ "aria-label": "\u6DFB\u52A0\u4E0A\u4E0B\u6587",
1658
+ title: "\u6DFB\u52A0\u4E0A\u4E0B\u6587",
1659
+ className: toolButtonClass,
1660
+ onClick: (event) => {
1661
+ if (onPlusMenu) {
1662
+ onPlusMenu(event.currentTarget.getBoundingClientRect());
1663
+ return;
1664
+ }
1665
+ setPlusOpen((prev) => !prev);
1666
+ setModelOpen(false);
1667
+ },
1668
+ children: /* @__PURE__ */ jsx12(Icon9, { name: "plus", size: "sm" })
1669
+ }
1670
+ ) : null,
1671
+ onAttachFiles ? /* @__PURE__ */ jsx12(
1672
+ "button",
1673
+ {
1674
+ type: "button",
1675
+ "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1676
+ title: "\u6DFB\u52A0\u9644\u4EF6",
1677
+ className: toolButtonClass,
1678
+ onClick: () => fileInputRef.current?.click(),
1679
+ children: /* @__PURE__ */ jsx12(Icon9, { name: "paperclip", size: "sm" })
1680
+ }
1681
+ ) : null
1682
+ ] }) : void 0;
1683
+ const builtinToolbarEnd = modelEnabled ? /* @__PURE__ */ jsxs11(
1684
+ "button",
1685
+ {
1686
+ type: "button",
1687
+ "aria-label": "\u9009\u62E9\u6A21\u578B",
1688
+ title: "\u9009\u62E9\u6A21\u578B",
1689
+ className: "flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
1690
+ onClick: () => {
1691
+ setModelOpen((prev) => !prev);
1692
+ setPlusOpen(false);
1693
+ },
1694
+ children: [
1695
+ currentModel?.label,
1696
+ /* @__PURE__ */ jsx12(Icon9, { name: "chevrons-up-down", size: 12 })
1697
+ ]
1698
+ }
1699
+ ) : void 0;
1700
+ const toolbarStartSlot = toolbarStart !== void 0 && toolbarStart !== null ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
1701
+ renderSlot(toolbarStart),
1702
+ builtinToolbarStart
1703
+ ] }) : builtinToolbarStart;
1704
+ const toolbarEndSlot = toolbarEnd !== void 0 && toolbarEnd !== null ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
1705
+ builtinToolbarEnd,
1706
+ renderSlot(toolbarEnd)
1707
+ ] }) : builtinToolbarEnd;
1708
+ const handleBubbleChange = (event) => {
1709
+ const target = event.target;
1710
+ if (!(target instanceof HTMLTextAreaElement)) {
1711
+ return;
1712
+ }
1713
+ const query = detectMention(
1714
+ target.value,
1715
+ target.selectionStart ?? target.value.length
1716
+ );
1717
+ if (query !== null) {
1718
+ onMentionTrigger?.(query, target.getBoundingClientRect());
1719
+ }
1720
+ };
1721
+ const activePopover = plusOpen ? "plus" : modelOpen ? "model" : null;
1722
+ return /* @__PURE__ */ jsxs11(
1723
+ "div",
1724
+ {
1725
+ "data-slot": "composer",
1726
+ className: cn("relative flex w-full flex-col gap-2", className),
1727
+ style,
1728
+ onDragOver: (event) => {
1729
+ if (onAttachFiles) {
1730
+ event.preventDefault();
1731
+ setDragOver(true);
1732
+ }
1733
+ },
1734
+ onDragLeave: () => setDragOver(false),
1735
+ onDrop: handleDrop,
1736
+ onPaste: (event) => {
1737
+ const files = Array.from(event.clipboardData?.files ?? []);
1738
+ if (files.length > 0 && onAttachFiles) {
1739
+ event.preventDefault();
1740
+ emitFiles(files);
1741
+ }
1742
+ },
1743
+ onChange: onMentionTrigger !== void 0 && !rich ? handleBubbleChange : void 0,
1744
+ children: [
1745
+ attachments.length > 0 ? /* @__PURE__ */ jsx12(
1746
+ Attachment,
1747
+ {
1748
+ items: attachments,
1749
+ onRemove: onRemoveAttachment,
1750
+ onRetry: onRetryAttachment,
1751
+ onClick: onAttachmentClick
1752
+ }
1753
+ ) : null,
1754
+ /* @__PURE__ */ jsx12("div", { className: cn(dragOver && "rounded-xl ring-2 ring-ring/50"), children: /* @__PURE__ */ jsx12(
1755
+ ChatInput,
1756
+ {
1757
+ value: text,
1758
+ placeholder,
1759
+ disabled: disabled || loading,
1760
+ streaming,
1761
+ onStop,
1762
+ rows,
1763
+ hideSend: hideToolbar,
1764
+ toolbarStart: hideToolbar ? void 0 : toolbarStartSlot,
1765
+ toolbarEnd: hideToolbar ? void 0 : toolbarEndSlot,
1766
+ footer,
1767
+ field: rich ? /* @__PURE__ */ jsx12(
1768
+ MentionInput,
1769
+ {
1770
+ ref: mentionRef,
1771
+ value: text,
1772
+ placeholder,
1773
+ disabled: disabled || loading,
1774
+ sources,
1775
+ chips,
1776
+ onChipsChange: handleChipsChange,
1777
+ onChange: handleChange,
1778
+ onSubmit: handleSubmit
1779
+ }
1780
+ ) : void 0,
1781
+ onChange: handleChange,
1782
+ onSubmit: handleSubmit
1783
+ }
1784
+ ) }),
1785
+ onAttachFiles ? /* @__PURE__ */ jsx12(
1786
+ "input",
1787
+ {
1788
+ ref: fileInputRef,
1789
+ type: "file",
1790
+ multiple: true,
1791
+ hidden: true,
1792
+ onChange: (event) => {
1793
+ emitFiles(Array.from(event.target.files ?? []));
1794
+ event.target.value = "";
1795
+ }
1796
+ }
1797
+ ) : null,
1798
+ activePopover !== null ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
1799
+ /* @__PURE__ */ jsx12("div", { className: "fixed inset-0 z-40", onClick: closePopovers }),
1800
+ activePopover === "model" ? /* @__PURE__ */ jsx12("div", { "data-slot": "composer-model-popover", className: cn(popoverPanelClass, "right-0 left-auto w-48"), role: "listbox", children: models.map((option) => /* @__PURE__ */ jsxs11(
1801
+ "button",
1802
+ {
1803
+ type: "button",
1804
+ role: "option",
1805
+ "aria-selected": option.value === currentModel?.value,
1806
+ className: popoverItemClass,
1807
+ onClick: () => {
1808
+ onModelChange?.(option.value);
1809
+ closePopovers();
1810
+ },
1811
+ children: [
1812
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate", children: option.label }),
1813
+ option.value === currentModel?.value ? /* @__PURE__ */ jsx12(Icon9, { name: "check", size: 14 }) : null
1814
+ ]
1815
+ },
1816
+ option.value
1817
+ )) }) : /* @__PURE__ */ jsx12("div", { "data-slot": "composer-context-popover", className: popoverPanelClass, role: "listbox", children: plusSources.map(
1818
+ (source) => source.items.length === 0 ? null : /* @__PURE__ */ jsxs11("div", { children: [
1819
+ /* @__PURE__ */ jsx12("div", { className: "px-2 py-1 text-xs text-muted-foreground", children: source.label }),
1820
+ source.items.map((item) => /* @__PURE__ */ jsxs11(
1821
+ "button",
1822
+ {
1823
+ type: "button",
1824
+ className: popoverItemClass,
1825
+ onClick: () => selectContext(item, source),
1826
+ children: [
1827
+ /* @__PURE__ */ jsx12(Icon9, { name: item.icon ?? source.icon ?? "file-text", size: "sm" }),
1828
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate", children: item.label })
1829
+ ]
1830
+ },
1831
+ item.id
1832
+ ))
1833
+ ] }, source.trigger)
1834
+ ) })
1835
+ ] }) : null
1836
+ ]
1837
+ }
1838
+ );
1839
+ }
1840
+
1841
+ // src/components/ConversationList/index.tsx
1842
+ import { useMemo as useMemo2, useState as useState7 } from "react";
1843
+ import { Icon as Icon10 } from "@pactor-app/ui/components";
1844
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1845
+ function ConversationList({ items = [], activeId, searchable, onSelect, onDelete, className, style }) {
1846
+ const [innerActive, setInnerActive] = useState7();
1847
+ const [keyword, setKeyword] = useState7("");
1848
+ const active = activeId ?? innerActive;
1849
+ const filtered = useMemo2(() => {
1850
+ const query = keyword.trim().toLowerCase();
1851
+ if (!query) return items;
1852
+ return items.filter(
1853
+ (item) => item.title.toLowerCase().includes(query) || (item.description ?? "").toLowerCase().includes(query)
1854
+ );
1855
+ }, [items, keyword]);
1856
+ const select = (id) => {
1857
+ if (activeId === void 0) {
1858
+ setInnerActive(id);
1859
+ }
1860
+ onSelect?.(id);
1861
+ };
1862
+ return /* @__PURE__ */ jsxs12("div", { "data-slot": "conversation-list", className: cn("flex w-full flex-col gap-1", className), style, children: [
1863
+ searchable ? /* @__PURE__ */ jsxs12("div", { className: "relative mb-1", children: [
1864
+ /* @__PURE__ */ jsx13("span", { className: "absolute top-1/2 left-2 -translate-y-1/2 text-muted-foreground", children: /* @__PURE__ */ jsx13(Icon10, { name: "search", size: "sm" }) }),
1865
+ /* @__PURE__ */ jsx13(
1866
+ "input",
1867
+ {
1868
+ "data-slot": "conversation-list-search",
1869
+ value: keyword,
1870
+ placeholder: "\u641C\u7D22\u4F1A\u8BDD",
1871
+ className: "h-8 w-full rounded-md border border-border bg-background pr-2 pl-7 text-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
1872
+ onChange: (event) => setKeyword(event.target.value)
1873
+ }
1874
+ )
1875
+ ] }) : null,
1876
+ filtered.map((item) => /* @__PURE__ */ jsxs12(
1877
+ "div",
1878
+ {
1879
+ "data-slot": "conversation-list-item",
1880
+ "data-active": item.id === active || void 0,
1881
+ className: cn(
1882
+ "group flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-accent",
1883
+ item.id === active && "bg-accent"
1884
+ ),
1885
+ onClick: () => select(item.id),
1886
+ children: [
1887
+ /* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
1888
+ /* @__PURE__ */ jsx13("div", { className: cn("truncate text-sm", item.id === active && "font-medium"), children: item.title }),
1889
+ item.description ? /* @__PURE__ */ jsx13("div", { className: "truncate text-xs text-muted-foreground", children: item.description }) : null
1890
+ ] }),
1891
+ item.time ? /* @__PURE__ */ jsx13("span", { className: "shrink-0 text-xs text-muted-foreground", children: item.time }) : null,
1892
+ onDelete ? /* @__PURE__ */ jsx13(
1893
+ "button",
1894
+ {
1895
+ type: "button",
1896
+ "data-slot": "conversation-list-delete",
1897
+ "aria-label": `\u5220\u9664 ${item.title}`,
1898
+ className: "shrink-0 text-muted-foreground opacity-0 group-hover:opacity-100 hover:text-destructive",
1899
+ onClick: (event) => {
1900
+ event.stopPropagation();
1901
+ onDelete(item.id);
1902
+ },
1903
+ children: /* @__PURE__ */ jsx13(Icon10, { name: "trash-2", size: "sm" })
1904
+ }
1905
+ ) : null
1906
+ ]
1907
+ },
1908
+ item.id
1909
+ )),
1910
+ filtered.length === 0 ? /* @__PURE__ */ jsx13("div", { className: "px-2 py-4 text-center text-sm text-muted-foreground", children: "\u65E0\u5339\u914D\u4F1A\u8BDD" }) : null
1911
+ ] });
1912
+ }
1913
+
1914
+ // src/components/MessageList/index.tsx
1915
+ import { useEffect as useEffect4, useRef as useRef4 } from "react";
1916
+ import { jsx as jsx14 } from "react/jsx-runtime";
1917
+ function MessageList({ messages = [], autoScroll = true, onAction, onOpenArtifacts, className, style }) {
1918
+ const listRef = useRef4(null);
1919
+ useEffect4(() => {
1920
+ if (!autoScroll) return;
1921
+ const el = listRef.current;
1922
+ if (el) {
1923
+ el.scrollTop = el.scrollHeight;
1924
+ }
1925
+ }, [messages, autoScroll]);
1926
+ return /* @__PURE__ */ jsx14(
1927
+ "div",
1928
+ {
1929
+ ref: listRef,
1930
+ "data-slot": "message-list",
1931
+ className: cn("flex w-full flex-col gap-4 overflow-y-auto", className),
1932
+ style,
1933
+ children: messages.map((message, index) => /* @__PURE__ */ jsx14(
1934
+ ChatMessage,
1935
+ {
1936
+ ...message,
1937
+ onAction: (action) => onAction?.({ id: message.id, action }),
1938
+ onOpenArtifacts: onOpenArtifacts ? () => onOpenArtifacts({ id: message.id }) : void 0
1939
+ },
1940
+ message.id ?? index
1941
+ ))
1942
+ }
1943
+ );
1944
+ }
1945
+
1946
+ // src/components/Welcome/index.tsx
1947
+ import { Icon as Icon12 } from "@pactor-app/ui/components";
1948
+
1949
+ // src/components/Prompt/index.tsx
1950
+ import { Icon as Icon11 } from "@pactor-app/ui/components";
1951
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1952
+ function Prompt({ title, description, icon, value, disabled, onSelect, className, style }) {
1953
+ return /* @__PURE__ */ jsxs13(
1954
+ "button",
1955
+ {
1956
+ type: "button",
1957
+ "data-slot": "prompt",
1958
+ disabled,
1959
+ className: cn(
1960
+ "flex items-start gap-2.5 rounded-md border border-border p-3 text-left transition-colors hover:border-primary disabled:pointer-events-none disabled:opacity-50",
1961
+ className
1962
+ ),
1963
+ style,
1964
+ onClick: () => onSelect?.(value ?? title ?? ""),
1965
+ children: [
1966
+ icon ? /* @__PURE__ */ jsx15("span", { className: "mt-0.5 shrink-0 text-primary", children: /* @__PURE__ */ jsx15(Icon11, { name: icon, size: "sm" }) }) : null,
1967
+ /* @__PURE__ */ jsxs13("span", { className: "min-w-0", children: [
1968
+ /* @__PURE__ */ jsx15("span", { "data-slot": "prompt-title", className: "block text-sm font-medium", children: title }),
1969
+ description ? /* @__PURE__ */ jsx15("span", { "data-slot": "prompt-description", className: "mt-0.5 block text-xs text-muted-foreground", children: description }) : null
1970
+ ] })
1971
+ ]
1972
+ }
1973
+ );
1974
+ }
1975
+
1976
+ // src/components/Welcome/index.tsx
1977
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1978
+ function Welcome({
1979
+ title = "\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F",
1980
+ description,
1981
+ icon = "chat",
1982
+ prompts = [],
1983
+ onSelect,
1984
+ titleClassName,
1985
+ descriptionClassName,
1986
+ className,
1987
+ style
1988
+ }) {
1989
+ return /* @__PURE__ */ jsxs14(
1990
+ "div",
1991
+ {
1992
+ "data-slot": "welcome",
1993
+ className: cn("flex w-full flex-col items-center gap-4 py-10 text-center", className),
1994
+ style,
1995
+ children: [
1996
+ icon ? /* @__PURE__ */ jsx16("span", { "data-slot": "welcome-icon", className: "text-primary", children: /* @__PURE__ */ jsx16(Icon12, { name: icon, size: "lg" }) }) : null,
1997
+ /* @__PURE__ */ jsxs14("div", { children: [
1998
+ /* @__PURE__ */ jsx16("div", { "data-slot": "welcome-title", className: cn("text-xl font-semibold", titleClassName), children: title }),
1999
+ description ? /* @__PURE__ */ jsx16("div", { "data-slot": "welcome-description", className: cn("mt-1 text-sm text-muted-foreground", descriptionClassName), children: description }) : null
2000
+ ] }),
2001
+ prompts.length > 0 ? /* @__PURE__ */ jsx16("div", { "data-slot": "welcome-prompts", className: "grid w-full max-w-xl grid-cols-1 gap-3 text-left sm:grid-cols-2", children: prompts.map((item, index) => /* @__PURE__ */ jsx16(
2002
+ Prompt,
2003
+ {
2004
+ title: item.title,
2005
+ description: item.description,
2006
+ icon: item.icon,
2007
+ value: item.value,
2008
+ onSelect
2009
+ },
2010
+ `${item.title}-${index}`
2011
+ )) }) : null
2012
+ ]
2013
+ }
2014
+ );
2015
+ }
2016
+
2017
+ // src/components/ChatPage/index.tsx
2018
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2019
+ function ChatPage({
2020
+ brand,
2021
+ menuItems = [],
2022
+ activeMenuKey,
2023
+ railItems = [],
2024
+ railFooterItems = [],
2025
+ menuAction,
2026
+ onMenuSelect,
2027
+ onMenuAction,
2028
+ conversationSectionTitle,
2029
+ conversationSectionCollapsed,
2030
+ defaultConversationSectionCollapsed = false,
2031
+ onConversationSectionToggle,
2032
+ conversations = [],
2033
+ activeConversationId,
2034
+ onSelectConversation,
2035
+ onDeleteConversation,
2036
+ moreLabel,
2037
+ onMore,
2038
+ footerSectionTitle,
2039
+ footerItems = [],
2040
+ footerSectionCollapsed,
2041
+ defaultFooterSectionCollapsed = false,
2042
+ onFooterSectionToggle,
2043
+ conversationTitle,
2044
+ conversationHeaderAction,
2045
+ onConversationHeaderAction,
2046
+ conversationRailItems = [],
2047
+ hideArtifactRailItem = false,
2048
+ activeConversationRailKey,
2049
+ onConversationRailChange,
2050
+ messages = [],
2051
+ sending,
2052
+ composerPlaceholder,
2053
+ welcome,
2054
+ onSend,
2055
+ onMessageAction,
2056
+ models,
2057
+ model,
2058
+ onModelChange,
2059
+ contexts,
2060
+ contextItems,
2061
+ onContextItemsChange,
2062
+ attachments,
2063
+ onAttachFiles,
2064
+ onRemoveAttachment,
2065
+ onRetryAttachment,
2066
+ composerFooter,
2067
+ composerBottom,
2068
+ artifactTitle = "\u4EA7\u7269",
2069
+ artifacts = [],
2070
+ activeArtifactId,
2071
+ onSelectArtifact,
2072
+ artifactEmptyText = "\u6682\u65E0\u4EA7\u7269\uFF0C\u53D1\u9001\u6D88\u606F\u540E\u751F\u6210\u7684\u6587\u4EF6\u5C06\u5C55\u793A\u5728\u8FD9\u91CC",
2073
+ safeRegistry,
2074
+ user,
2075
+ artifactViewers,
2076
+ onDownloadArtifact,
2077
+ artifactOpen,
2078
+ defaultArtifactOpen = false,
2079
+ onArtifactOpenChange,
2080
+ content,
2081
+ className,
2082
+ style
2083
+ }) {
2084
+ const controlled = artifactOpen !== void 0;
2085
+ const [innerOpen, setInnerOpen] = useState8(defaultArtifactOpen);
2086
+ const open = controlled ? artifactOpen === true : innerOpen;
2087
+ const setOpen = (next) => {
2088
+ if (!controlled) setInnerOpen(next);
2089
+ onArtifactOpenChange?.(next);
2090
+ };
2091
+ const railControlled = activeConversationRailKey !== void 0;
2092
+ const [activeRailInner, setActiveRailInner] = useState8(null);
2093
+ const activeRailKey = railControlled ? activeConversationRailKey : activeRailInner;
2094
+ const setActiveRailKey = (next) => {
2095
+ if (!railControlled) setActiveRailInner(next);
2096
+ onConversationRailChange?.(next);
2097
+ };
2098
+ const visibleRailItems = conversationRailItems.filter((item) => item.hidden !== true);
2099
+ const activeRailItem = visibleRailItems.find(
2100
+ (item) => item.key === activeRailKey
2101
+ );
2102
+ const activePanelItem = activeRailItem?.display === "panel" && activeRailItem.content !== void 0 ? activeRailItem : void 0;
2103
+ const activePopoverItem = activeRailItem !== void 0 && activeRailItem.display !== "panel" && activeRailItem.content !== void 0 ? activeRailItem : void 0;
2104
+ const toggleRailItem = (item) => {
2105
+ const next = activeRailKey === item.key ? null : item.key;
2106
+ if (next !== null && item.display === "panel") {
2107
+ setOpen(false);
2108
+ }
2109
+ setActiveRailKey(next);
2110
+ };
2111
+ const toggleArtifact = () => {
2112
+ if (!open && activePanelItem) {
2113
+ setActiveRailKey(null);
2114
+ }
2115
+ setOpen(!open);
2116
+ };
2117
+ useEffect5(() => {
2118
+ if (!activePopoverItem) return;
2119
+ const onKeyDown = (event) => {
2120
+ if (event.key === "Escape") setActiveRailKey(null);
2121
+ };
2122
+ const onPointerDown = (event) => {
2123
+ const target = event.target;
2124
+ if (target.closest("[data-slot=chat-page-rail-popover]") || target.closest("[data-slot=chat-page-conv-rail]")) {
2125
+ return;
2126
+ }
2127
+ setActiveRailKey(null);
2128
+ };
2129
+ document.addEventListener("keydown", onKeyDown);
2130
+ document.addEventListener("mousedown", onPointerDown);
2131
+ return () => {
2132
+ document.removeEventListener("keydown", onKeyDown);
2133
+ document.removeEventListener("mousedown", onPointerDown);
2134
+ };
2135
+ });
2136
+ const { renderChildren } = useRenderer2();
2137
+ const renderSlot = (slot) => isValidElement2(slot) || typeof slot !== "object" || slot === null ? slot : renderChildren([slot]);
2138
+ const convCollControlled = conversationSectionCollapsed !== void 0;
2139
+ const [convCollapsedInner, setConvCollapsedInner] = useState8(
2140
+ defaultConversationSectionCollapsed
2141
+ );
2142
+ const convCollapsed = convCollControlled ? conversationSectionCollapsed === true : convCollapsedInner;
2143
+ const toggleConvSection = () => {
2144
+ const next = !convCollapsed;
2145
+ if (!convCollControlled) setConvCollapsedInner(next);
2146
+ onConversationSectionToggle?.(next);
2147
+ };
2148
+ const footerCollControlled = footerSectionCollapsed !== void 0;
2149
+ const [footerCollapsedInner, setFooterCollapsedInner] = useState8(
2150
+ defaultFooterSectionCollapsed
2151
+ );
2152
+ const footerCollapsed = footerCollControlled ? footerSectionCollapsed === true : footerCollapsedInner;
2153
+ const toggleFooterSection = () => {
2154
+ const next = !footerCollapsed;
2155
+ if (!footerCollControlled) setFooterCollapsedInner(next);
2156
+ onFooterSectionToggle?.(next);
2157
+ };
2158
+ const railVisible = railItems.length > 0 || railFooterItems.length > 0;
2159
+ const railButton = (item) => /* @__PURE__ */ jsx17(Tooltip2, { content: item.label, side: "right", children: /* @__PURE__ */ jsx17(
2160
+ Button2,
2161
+ {
2162
+ icon: item.icon,
2163
+ variant: "text",
2164
+ onClick: () => onMenuSelect?.(item.key)
2165
+ }
2166
+ ) }, item.key);
2167
+ const convRailButton = (item, active, onClick) => {
2168
+ const button = /* @__PURE__ */ jsx17(
2169
+ Button2,
2170
+ {
2171
+ icon: item.icon,
2172
+ variant: "text",
2173
+ className: cn(active ? "bg-accent text-accent-foreground" : void 0, "px-2"),
2174
+ onClick
2175
+ }
2176
+ );
2177
+ return item.label !== void 0 ? /* @__PURE__ */ jsx17(Tooltip2, { content: item.label, side: "left", children: button }, item.key) : /* @__PURE__ */ jsx17("span", { children: button }, item.key);
2178
+ };
2179
+ const convRailVisible = !hideArtifactRailItem || visibleRailItems.length > 0;
2180
+ const convRail = convRailVisible ? /* @__PURE__ */ jsxs15(
2181
+ "div",
2182
+ {
2183
+ "data-slot": "chat-page-conv-rail",
2184
+ className: "relative flex w-(--pactor-chat-rail-width) shrink-0 flex-col items-center gap-1 px-1 py-2",
2185
+ children: [
2186
+ !hideArtifactRailItem ? convRailButton(
2187
+ {
2188
+ key: "artifact",
2189
+ icon: open ? "panel-right-close" : "package",
2190
+ label: open ? "\u6536\u8D77\u4EA7\u7269\u533A" : artifactTitle
2191
+ },
2192
+ open,
2193
+ toggleArtifact
2194
+ ) : null,
2195
+ visibleRailItems.map(
2196
+ (item) => convRailButton(item, activeRailKey === item.key, () => toggleRailItem(item))
2197
+ ),
2198
+ activePopoverItem ? /* @__PURE__ */ jsx17(
2199
+ "div",
2200
+ {
2201
+ "data-slot": "chat-page-rail-popover",
2202
+ className: "absolute right-full top-0 z-10 mr-2 overflow-auto rounded-lg border border-border bg-popover shadow-md",
2203
+ style: {
2204
+ width: "var(--pactor-chat-rail-popover-width)",
2205
+ maxWidth: "calc(100cqw - 2rem)",
2206
+ maxHeight: "calc(100% - 1rem)"
2207
+ },
2208
+ children: renderSlot(activePopoverItem.content)
2209
+ }
2210
+ ) : null
2211
+ ]
2212
+ }
2213
+ ) : null;
2214
+ const chatArea = /* @__PURE__ */ jsxs15("div", { "data-slot": "chat-page-conversation", className: "flex h-full min-w-0 flex-1 flex-col", children: [
2215
+ /* @__PURE__ */ jsxs15(
2216
+ "div",
2217
+ {
2218
+ "data-slot": "chat-page-conversation-header",
2219
+ className: "flex shrink-0 items-center justify-between border-b border-border px-4 py-2.5",
2220
+ children: [
2221
+ /* @__PURE__ */ jsx17(Text, { text: conversationTitle, strong: true }),
2222
+ conversationHeaderAction ? /* @__PURE__ */ jsx17(Tooltip2, { content: conversationHeaderAction.tooltip ?? conversationHeaderAction.label, side: "bottom", children: /* @__PURE__ */ jsx17(
2223
+ Button2,
2224
+ {
2225
+ text: conversationHeaderAction.label,
2226
+ icon: conversationHeaderAction.icon,
2227
+ variant: "text",
2228
+ onClick: () => onConversationHeaderAction?.()
2229
+ }
2230
+ ) }) : null
2231
+ ]
2232
+ }
2233
+ ),
2234
+ /* @__PURE__ */ jsxs15("div", { className: "flex min-h-0 flex-1", children: [
2235
+ /* @__PURE__ */ jsxs15(
2236
+ "div",
2237
+ {
2238
+ "data-slot": "chat-page-conversation-body",
2239
+ className: "mx-auto flex min-h-0 w-[61.8cqw] max-w-full flex-col",
2240
+ children: [
2241
+ messages.length === 0 ? /* @__PURE__ */ jsx17(
2242
+ Welcome,
2243
+ {
2244
+ title: welcome?.title,
2245
+ description: welcome?.description,
2246
+ prompts: welcome?.prompts,
2247
+ style: { flex: 1 },
2248
+ onSelect: (value) => onSend?.(value)
2249
+ }
2250
+ ) : /* @__PURE__ */ jsx17(
2251
+ MessageList,
2252
+ {
2253
+ messages,
2254
+ className: "scrollbar-subtle",
2255
+ style: { flex: 1, minHeight: 0, padding: 16 },
2256
+ onAction: onMessageAction,
2257
+ onOpenArtifacts: () => setOpen(true)
2258
+ }
2259
+ ),
2260
+ /* @__PURE__ */ jsx17(
2261
+ Composer,
2262
+ {
2263
+ placeholder: composerPlaceholder,
2264
+ loading: sending,
2265
+ models,
2266
+ model,
2267
+ contexts,
2268
+ contextItems,
2269
+ attachments,
2270
+ footer: composerFooter,
2271
+ style: { padding: 16, paddingBottom: composerBottom ? 8 : 16, flexShrink: 0 },
2272
+ onModelChange,
2273
+ onContextItemsChange,
2274
+ onAttachFiles,
2275
+ onRemoveAttachment,
2276
+ onRetryAttachment,
2277
+ onSubmit: (text) => onSend?.(text)
2278
+ }
2279
+ ),
2280
+ composerBottom ? /* @__PURE__ */ jsx17("div", { "data-slot": "chat-page-composer-bottom", className: "shrink-0 px-4 pb-2 text-center", children: renderSlot(composerBottom) }) : null
2281
+ ]
2282
+ }
2283
+ ),
2284
+ convRail
2285
+ ] })
2286
+ ] });
2287
+ const activeArtifact = artifacts.find((item) => item.id === activeArtifactId) ?? (activeArtifactId === void 0 || activeArtifactId === "" ? artifacts[artifacts.length - 1] : void 0);
2288
+ const artifactPanel = /* @__PURE__ */ jsx17(
2289
+ "div",
2290
+ {
2291
+ "data-slot": "chat-page-artifact",
2292
+ className: "flex h-full min-w-0 flex-col border-l border-border bg-muted",
2293
+ children: /* @__PURE__ */ jsx17(
2294
+ ArtifactPanel,
2295
+ {
2296
+ artifact: activeArtifact,
2297
+ artifacts,
2298
+ activeArtifactId,
2299
+ title: artifactTitle,
2300
+ emptyText: artifactEmptyText,
2301
+ safeRegistry,
2302
+ user,
2303
+ artifactViewers,
2304
+ onSelectArtifact: (id) => onSelectArtifact?.(id),
2305
+ onDownload: onDownloadArtifact,
2306
+ onClose: () => setOpen(false)
2307
+ }
2308
+ )
2309
+ }
2310
+ );
2311
+ return /* @__PURE__ */ jsxs15(
2312
+ "div",
2313
+ {
2314
+ "data-slot": "chat-page",
2315
+ className: cn("flex w-full overflow-hidden", className),
2316
+ style: { height: "100vh", ...style },
2317
+ children: [
2318
+ railVisible ? /* @__PURE__ */ jsxs15(
2319
+ "div",
2320
+ {
2321
+ "data-slot": "chat-page-rail",
2322
+ className: "flex h-full w-[52px] shrink-0 flex-col items-center gap-1 border-r border-border py-2.5",
2323
+ children: [
2324
+ railItems.map(railButton),
2325
+ /* @__PURE__ */ jsx17("div", { className: "flex-1" }),
2326
+ railFooterItems.map(railButton)
2327
+ ]
2328
+ }
2329
+ ) : null,
2330
+ /* @__PURE__ */ jsxs15(
2331
+ "div",
2332
+ {
2333
+ "data-slot": "chat-page-menu",
2334
+ className: "flex h-full w-[250px] shrink-0 flex-col gap-2 overflow-hidden border-r border-border px-2 py-3",
2335
+ children: [
2336
+ brand ? /* @__PURE__ */ jsxs15("div", { className: "flex shrink-0 items-center justify-between px-2", children: [
2337
+ /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-0.5", children: [
2338
+ /* @__PURE__ */ jsx17(Text, { text: brand.title, strong: true, size: "large" }),
2339
+ brand.version !== void 0 ? /* @__PURE__ */ jsx17(Text, { text: brand.version, type: "secondary", size: "small" }) : null
2340
+ ] }),
2341
+ menuAction ? /* @__PURE__ */ jsx17(
2342
+ Button2,
2343
+ {
2344
+ text: menuAction.label,
2345
+ icon: menuAction.icon,
2346
+ size: "small",
2347
+ onClick: () => onMenuAction?.()
2348
+ }
2349
+ ) : null
2350
+ ] }) : null,
2351
+ /* @__PURE__ */ jsx17("div", { className: "scrollbar-subtle max-h-[40%] min-h-0 shrink-0 overflow-y-auto", children: /* @__PURE__ */ jsx17(
2352
+ Menu,
2353
+ {
2354
+ items: menuItems,
2355
+ selectedKey: activeMenuKey,
2356
+ onSelect: (key) => onMenuSelect?.(key)
2357
+ }
2358
+ ) }),
2359
+ conversationSectionTitle !== void 0 ? /* @__PURE__ */ jsxs15(
2360
+ "button",
2361
+ {
2362
+ type: "button",
2363
+ "data-slot": "chat-page-section-toggle",
2364
+ "aria-expanded": !convCollapsed,
2365
+ className: "flex w-full shrink-0 cursor-pointer items-center justify-between px-2 pt-2",
2366
+ onClick: toggleConvSection,
2367
+ children: [
2368
+ /* @__PURE__ */ jsx17(
2369
+ Text,
2370
+ {
2371
+ text: `${conversationSectionTitle} (${conversations.length})`,
2372
+ type: "secondary",
2373
+ className: "text-sm",
2374
+ strong: true
2375
+ }
2376
+ ),
2377
+ /* @__PURE__ */ jsx17(Icon13, { name: convCollapsed ? "chevron-right" : "chevron-down", size: 14 })
2378
+ ]
2379
+ }
2380
+ ) : null,
2381
+ !convCollapsed ? /* @__PURE__ */ jsxs15("div", { "data-slot": "chat-page-section-body", className: "scrollbar-subtle flex min-h-0 flex-1 flex-col overflow-y-auto", children: [
2382
+ /* @__PURE__ */ jsx17(
2383
+ ConversationList,
2384
+ {
2385
+ items: conversations,
2386
+ activeId: activeConversationId,
2387
+ onSelect: (id) => onSelectConversation?.(id),
2388
+ onDelete: (id) => onDeleteConversation?.(id)
2389
+ }
2390
+ ),
2391
+ moreLabel !== void 0 ? /* @__PURE__ */ jsx17("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx17(Button2, { text: `${moreLabel} >>`, variant: "link", size: "small", onClick: () => onMore?.() }) }) : null
2392
+ ] }) : null,
2393
+ footerSectionTitle !== void 0 ? /* @__PURE__ */ jsxs15(
2394
+ "button",
2395
+ {
2396
+ type: "button",
2397
+ "data-slot": "chat-page-section-toggle",
2398
+ "aria-expanded": !footerCollapsed,
2399
+ className: "flex w-full shrink-0 cursor-pointer items-center justify-between px-2 pt-2",
2400
+ onClick: toggleFooterSection,
2401
+ children: [
2402
+ /* @__PURE__ */ jsx17(Text, { text: footerSectionTitle, type: "secondary", className: "text-sm", strong: true }),
2403
+ /* @__PURE__ */ jsx17(Icon13, { name: footerCollapsed ? "chevron-right" : "chevron-down", size: 14 })
2404
+ ]
2405
+ }
2406
+ ) : null,
2407
+ !footerCollapsed && footerItems.length > 0 ? /* @__PURE__ */ jsx17("div", { "data-slot": "chat-page-section-body", className: "scrollbar-subtle min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx17(
2408
+ Menu,
2409
+ {
2410
+ items: footerItems,
2411
+ onSelect: (key) => onMenuSelect?.(key)
2412
+ }
2413
+ ) }) : null
2414
+ ]
2415
+ }
2416
+ ),
2417
+ /* @__PURE__ */ jsx17("div", { className: "@container h-full min-w-0 flex-1", children: content !== void 0 ? renderSlot(content) : open || activePanelItem ? /* @__PURE__ */ jsxs15(PanelGroup, { orientation: "horizontal", style: { width: "100%", height: "100%" }, children: [
2418
+ /* @__PURE__ */ jsx17(Panel, { defaultSize: "62%", children: chatArea }),
2419
+ /* @__PURE__ */ jsx17(ResizableHandle, { withHandle: true }),
2420
+ /* @__PURE__ */ jsx17(Panel, { defaultSize: "38%", minSize: "380px", maxSize: "760px", children: activePanelItem ? /* @__PURE__ */ jsx17(
2421
+ "div",
2422
+ {
2423
+ "data-slot": "chat-page-rail-panel",
2424
+ className: "flex h-full min-w-0 flex-col border-l border-border bg-muted",
2425
+ children: renderSlot(activePanelItem.content)
2426
+ }
2427
+ ) : artifactPanel })
2428
+ ] }) : chatArea })
2429
+ ]
2430
+ }
2431
+ );
2432
+ }
2433
+
2434
+ // src/components/Citation/index.tsx
2435
+ import {
2436
+ HoverCard,
2437
+ HoverCardContent,
2438
+ HoverCardTrigger
2439
+ } from "@pactor-app/ui/ui";
2440
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2441
+ function Citation({ items = [], start = 1, className, style }) {
2442
+ return /* @__PURE__ */ jsx18("span", { "data-slot": "citation", className: cn("inline-flex gap-1 align-super", className), style, children: items.map((item, index) => /* @__PURE__ */ jsxs16(HoverCard, { children: [
2443
+ /* @__PURE__ */ jsxs16(
2444
+ HoverCardTrigger,
2445
+ {
2446
+ "data-slot": "citation-marker",
2447
+ className: "cursor-pointer rounded-sm border border-border px-1 text-xs leading-4 text-primary hover:bg-accent",
2448
+ children: [
2449
+ "[",
2450
+ start + index,
2451
+ "]"
2452
+ ]
2453
+ }
2454
+ ),
2455
+ /* @__PURE__ */ jsxs16(HoverCardContent, { "data-slot": "citation-card", className: "w-72", children: [
2456
+ /* @__PURE__ */ jsx18("div", { className: "text-sm font-medium", children: item.title }),
2457
+ item.snippet ? /* @__PURE__ */ jsx18("div", { className: "mt-1 line-clamp-3 text-xs text-muted-foreground", children: item.snippet }) : null,
2458
+ item.url ? /* @__PURE__ */ jsx18(
2459
+ "a",
2460
+ {
2461
+ href: item.url,
2462
+ target: "_blank",
2463
+ rel: "noreferrer",
2464
+ className: "mt-1 block truncate text-xs text-primary hover:underline",
2465
+ children: item.url
2466
+ }
2467
+ ) : null
2468
+ ] })
2469
+ ] }, `${item.title}-${index}`)) });
2470
+ }
2471
+
2472
+ // src/components/StepsPanel/index.tsx
2473
+ import { Icon as Icon14 } from "@pactor-app/ui/components";
2474
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2475
+ function StepsPanel({
2476
+ title = "\u6267\u884C\u6B65\u9AA4",
2477
+ steps = [],
2478
+ emptyText = "\u5F53\u524D\u4F1A\u8BDD\u6682\u65E0\u6267\u884C\u6B65\u9AA4",
2479
+ className,
2480
+ style
2481
+ }) {
2482
+ return /* @__PURE__ */ jsxs17(
2483
+ "div",
2484
+ {
2485
+ "data-slot": "steps-panel",
2486
+ className: cn("px-3.5 py-3 text-foreground", className),
2487
+ style,
2488
+ children: [
2489
+ /* @__PURE__ */ jsx19("div", { className: "mb-1 text-[13px] font-semibold", children: title }),
2490
+ steps.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "py-4 text-center text-[13px] text-muted-foreground", children: emptyText }) : /* @__PURE__ */ jsx19("ol", { children: steps.map((step, index) => {
2491
+ const status = step.status ?? "pending";
2492
+ return /* @__PURE__ */ jsxs17(
2493
+ "li",
2494
+ {
2495
+ "data-status": status,
2496
+ className: cn(
2497
+ "relative flex gap-2 py-1.5",
2498
+ // 步骤之间的连接线(对齐 16px 图标中心)
2499
+ 'not-last:before:absolute not-last:before:left-[7.5px] not-last:before:top-7 not-last:before:-bottom-1 not-last:before:w-px not-last:before:bg-border not-last:before:content-[""]'
2500
+ ),
2501
+ children: [
2502
+ /* @__PURE__ */ jsx19(
2503
+ "span",
2504
+ {
2505
+ className: cn(
2506
+ "flex h-5 w-4 shrink-0 items-center justify-center",
2507
+ status !== "pending" && "text-primary"
2508
+ ),
2509
+ children: status === "done" ? /* @__PURE__ */ jsx19(Icon14, { name: "check-circle", size: 16 }) : status === "running" ? /* @__PURE__ */ jsx19(
2510
+ Icon14,
2511
+ {
2512
+ name: "loading",
2513
+ size: 16,
2514
+ className: "animate-spin motion-reduce:animate-none"
2515
+ }
2516
+ ) : /* @__PURE__ */ jsx19("span", { className: "size-1.5 rounded-full bg-muted-foreground/50" })
2517
+ }
2518
+ ),
2519
+ /* @__PURE__ */ jsxs17("div", { className: "min-w-0 flex-1", children: [
2520
+ /* @__PURE__ */ jsx19(
2521
+ "div",
2522
+ {
2523
+ className: cn(
2524
+ "text-[13px] leading-5",
2525
+ status === "pending" && "text-muted-foreground"
2526
+ ),
2527
+ children: step.title
2528
+ }
2529
+ ),
2530
+ step.description ? /* @__PURE__ */ jsx19("div", { className: "mt-0.5 text-xs leading-[18px] text-muted-foreground", children: step.description }) : null
2531
+ ] }),
2532
+ step.duration ? /* @__PURE__ */ jsx19("span", { className: "shrink-0 text-xs leading-5 text-muted-foreground", children: step.duration }) : null
2533
+ ]
2534
+ },
2535
+ `${step.title}-${index}`
2536
+ );
2537
+ }) })
2538
+ ]
2539
+ }
2540
+ );
2541
+ }
2542
+
2543
+ // src/components/Suggestion/index.tsx
2544
+ import { Icon as Icon15 } from "@pactor-app/ui/components";
2545
+ import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2546
+ function normalize(item) {
2547
+ if (typeof item === "string") return { label: item, value: item };
2548
+ return { label: item.label, value: item.value ?? item.label, icon: item.icon };
2549
+ }
2550
+ function Suggestion({ items = [], justify = "start", onSelect, className, style }) {
2551
+ return /* @__PURE__ */ jsx20(
2552
+ "div",
2553
+ {
2554
+ "data-slot": "suggestion",
2555
+ className: cn("flex flex-wrap gap-2", justify === "center" && "justify-center", className),
2556
+ style,
2557
+ children: items.map((item, index) => {
2558
+ const { label, value, icon } = normalize(item);
2559
+ return /* @__PURE__ */ jsxs18(
2560
+ "button",
2561
+ {
2562
+ type: "button",
2563
+ "data-slot": "suggestion-item",
2564
+ "data-value": value,
2565
+ className: "flex items-center gap-1.5 rounded-full border border-border px-3 py-1 text-sm transition-colors hover:bg-accent",
2566
+ onClick: () => onSelect?.(value),
2567
+ children: [
2568
+ icon ? /* @__PURE__ */ jsx20(Icon15, { name: icon, size: "sm" }) : null,
2569
+ label
2570
+ ]
2571
+ },
2572
+ `${value}-${index}`
2573
+ );
2574
+ })
2575
+ }
2576
+ );
2577
+ }
2578
+
2579
+ // src/components/index.ts
2580
+ function registerChatComponents(registry) {
2581
+ const components = {
2582
+ AgentStatus,
2583
+ ArtifactPanel,
2584
+ Attachment,
2585
+ Bubble,
2586
+ ChatMessage,
2587
+ ChatPage,
2588
+ Citation,
2589
+ Composer,
2590
+ ConversationList,
2591
+ MessageList,
2592
+ Prompt,
2593
+ Sender,
2594
+ StepsPanel,
2595
+ StreamingText,
2596
+ Suggestion,
2597
+ Thinking,
2598
+ ToolCall,
2599
+ Welcome
2600
+ };
2601
+ for (const [type, component] of Object.entries(components)) {
2602
+ if (!registry.has(type)) {
2603
+ registry.register(type, component);
2604
+ }
2605
+ }
2606
+ }
2607
+
2608
+ export {
2609
+ buildArtifactTree,
2610
+ resolveArtifactFilename,
2611
+ buildArtifactBlob,
2612
+ saveBlob,
2613
+ downloadArtifact,
2614
+ ArtifactPanel,
2615
+ Attachment,
2616
+ Composer,
2617
+ Suggestion,
2618
+ Prompt,
2619
+ Welcome,
2620
+ Bubble,
2621
+ Sender,
2622
+ AgentStatus,
2623
+ StreamingText,
2624
+ Thinking,
2625
+ ToolCall,
2626
+ ChatMessage,
2627
+ ConversationList,
2628
+ MessageList,
2629
+ ChatPage,
2630
+ Citation,
2631
+ StepsPanel,
2632
+ registerChatComponents
2633
+ };