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