@pactor-app/chat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3368 @@
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/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ActivityTrace: () => ActivityTrace,
24
+ AppShell: () => AppShell,
25
+ ArtifactPanel: () => ArtifactPanel,
26
+ ArtifactStore: () => ArtifactStore,
27
+ CHAT_COMPONENT_TYPE: () => CHAT_COMPONENT_TYPE,
28
+ CHAT_DENIED_TYPE: () => CHAT_DENIED_TYPE,
29
+ CHAT_LAYOUT_TYPE: () => CHAT_LAYOUT_TYPE,
30
+ CHAT_SHELL_COMPONENT_TYPE: () => CHAT_SHELL_COMPONENT_TYPE,
31
+ Chat: () => Chat,
32
+ ChatDenied: () => ChatDenied,
33
+ ChatLayout: () => ChatLayout,
34
+ ChatServicesProvider: () => ChatServicesProvider,
35
+ ChatShell: () => ChatShell,
36
+ ChatSidebarMenu: () => ChatSidebarMenu,
37
+ ChatView: () => ChatView,
38
+ Composer: () => Composer,
39
+ ConversationStore: () => ConversationStore,
40
+ DEFAULT_CHAT_GREETING: () => DEFAULT_CHAT_GREETING,
41
+ EmptyState: () => EmptyState,
42
+ MENU_ICONS: () => MENU_ICONS,
43
+ MESSAGE_ACTION_ICONS: () => MESSAGE_ACTION_ICONS,
44
+ MessageActions: () => MessageActions,
45
+ MessageBubble: () => MessageBubble,
46
+ PartRenderer: () => PartRenderer,
47
+ applyChatEvent: () => applyChatEvent,
48
+ buildChatScope: () => buildChatScope,
49
+ collectMessageText: () => collectMessageText,
50
+ createChatId: () => createChatId,
51
+ createChatRuntime: () => createChatRuntime,
52
+ createChatWorkspace: () => createChatWorkspace,
53
+ createManualDriver: () => createManualDriver,
54
+ createSafeRegistry: () => createSafeRegistry,
55
+ detectStreamingMode: () => detectStreamingMode,
56
+ guardChatDsl: () => guardChatDsl,
57
+ pairToolTraces: () => pairToolTraces,
58
+ registerCapabilityAction: () => registerCapabilityAction,
59
+ registerChatComponent: () => registerChatComponent,
60
+ registerChatLayout: () => registerChatLayout,
61
+ registerChatShellComponent: () => registerChatShellComponent,
62
+ runChatTurn: () => runChatTurn,
63
+ runRegenerateTurn: () => runRegenerateTurn,
64
+ useChatAutoScroll: () => useChatAutoScroll,
65
+ useChatServices: () => useChatServices,
66
+ useSidebarDrawer: () => useSidebarDrawer,
67
+ useSmoothStreamText: () => useSmoothStreamText
68
+ });
69
+ module.exports = __toCommonJS(index_exports);
70
+
71
+ // src/model.ts
72
+ var idCounter = 0;
73
+ function createChatId(prefix = "chat") {
74
+ idCounter += 1;
75
+ return `${prefix}-${Date.now().toString(36)}-${idCounter}`;
76
+ }
77
+
78
+ // src/store.ts
79
+ var ConversationStore = class {
80
+ constructor() {
81
+ this.messages = [];
82
+ this.listeners = /* @__PURE__ */ new Set();
83
+ }
84
+ /** 当前消息数组(只读快照;引用在不变时保持稳定) */
85
+ getMessages() {
86
+ return this.messages;
87
+ }
88
+ getMessage(id) {
89
+ return this.messages.find((message) => message.id === id);
90
+ }
91
+ /** 追加消息(id 重复时告警并忽略) */
92
+ appendMessage(message) {
93
+ if (this.getMessage(message.id)) {
94
+ console.warn(
95
+ `[@pactor-app/chat] duplicate message id "${message.id}", ignored`
96
+ );
97
+ return;
98
+ }
99
+ this.commit([...this.messages, message]);
100
+ }
101
+ /** 更新消息级字段(status / role / parts 等,id 不可改) */
102
+ updateMessage(id, patch) {
103
+ const index = this.messages.findIndex((message) => message.id === id);
104
+ if (index < 0) {
105
+ console.warn(`[@pactor-app/chat] message not found: "${id}"`);
106
+ return;
107
+ }
108
+ const next = [...this.messages];
109
+ next[index] = { ...next[index], ...patch, id };
110
+ this.commit(next);
111
+ }
112
+ /** 追加 part 到目标消息 */
113
+ appendPart(messageId, part) {
114
+ this.mutateMessage(messageId, (message) => ({
115
+ ...message,
116
+ parts: [...message.parts, part]
117
+ }));
118
+ }
119
+ /**
120
+ * 更新目标 part:partRef 为数组下标或 part id。
121
+ * 未命中时告警并忽略(宽容约定同 updateMessage)。
122
+ */
123
+ updatePart(messageId, partRef, patch) {
124
+ this.mutateMessage(messageId, (message) => {
125
+ const index = typeof partRef === "number" ? partRef : message.parts.findIndex((part) => part.id === partRef);
126
+ if (index < 0 || index >= message.parts.length) {
127
+ console.warn(
128
+ `[@pactor-app/chat] part not found: message "${messageId}", ref "${String(partRef)}"`
129
+ );
130
+ return message;
131
+ }
132
+ const parts = [...message.parts];
133
+ parts[index] = { ...parts[index], ...patch };
134
+ return { ...message, parts };
135
+ });
136
+ }
137
+ /** 设置消息状态 */
138
+ setStatus(id, status) {
139
+ this.updateMessage(id, { status });
140
+ }
141
+ /**
142
+ * 不可变地移除目标消息及其后全部消息(编辑重发 / 重新生成的截断基础)。
143
+ * 未知 id 时告警并忽略。
144
+ */
145
+ removeMessagesFrom(messageId) {
146
+ const index = this.messages.findIndex((message) => message.id === messageId);
147
+ if (index < 0) {
148
+ console.warn(`[@pactor-app/chat] message not found: "${messageId}"`);
149
+ return;
150
+ }
151
+ this.commit(this.messages.slice(0, index));
152
+ }
153
+ /** 清空会话 */
154
+ clear() {
155
+ if (this.messages.length === 0) {
156
+ return;
157
+ }
158
+ this.commit([]);
159
+ }
160
+ /** 订阅变更;返回退订函数 */
161
+ subscribe(listener) {
162
+ this.listeners.add(listener);
163
+ return () => {
164
+ this.listeners.delete(listener);
165
+ };
166
+ }
167
+ /** 以 mutator 更新单条消息(找不到时告警并忽略) */
168
+ mutateMessage(id, mutate) {
169
+ const index = this.messages.findIndex((message) => message.id === id);
170
+ if (index < 0) {
171
+ console.warn(`[@pactor-app/chat] message not found: "${id}"`);
172
+ return;
173
+ }
174
+ const nextMessage = mutate(this.messages[index]);
175
+ if (nextMessage === this.messages[index]) {
176
+ return;
177
+ }
178
+ const next = [...this.messages];
179
+ next[index] = nextMessage;
180
+ this.commit(next);
181
+ }
182
+ commit(next) {
183
+ this.messages = next;
184
+ for (const listener of this.listeners) {
185
+ listener();
186
+ }
187
+ }
188
+ };
189
+
190
+ // src/artifact.ts
191
+ var ArtifactStore = class {
192
+ constructor() {
193
+ this.artifact = null;
194
+ this.listeners = /* @__PURE__ */ new Set();
195
+ }
196
+ /** 当前产出物(未打开时为 null;引用在不变时保持稳定) */
197
+ get() {
198
+ return this.artifact;
199
+ }
200
+ /** 打开(或替换)当前产出物 */
201
+ open(artifact) {
202
+ this.artifact = artifact;
203
+ this.notify();
204
+ }
205
+ /** 部分更新当前产出物(id 不可改);未 open 时告警并忽略 */
206
+ update(patch) {
207
+ if (!this.artifact) {
208
+ console.warn("[@pactor-app/chat] artifact not open, update ignored");
209
+ return;
210
+ }
211
+ this.artifact = { ...this.artifact, ...patch, id: this.artifact.id };
212
+ this.notify();
213
+ }
214
+ /** 关闭面板(未打开时幂等,不重复通知) */
215
+ close() {
216
+ if (this.artifact === null) {
217
+ return;
218
+ }
219
+ this.artifact = null;
220
+ this.notify();
221
+ }
222
+ /** 订阅变更;返回退订函数 */
223
+ subscribe(listener) {
224
+ this.listeners.add(listener);
225
+ return () => {
226
+ this.listeners.delete(listener);
227
+ };
228
+ }
229
+ notify() {
230
+ for (const listener of this.listeners) {
231
+ listener();
232
+ }
233
+ }
234
+ };
235
+
236
+ // src/events.ts
237
+ function applyChatEvent(store, event) {
238
+ switch (event.type) {
239
+ case "message.start": {
240
+ const existing = store.getMessage(event.messageId);
241
+ if (existing) {
242
+ store.updateMessage(event.messageId, {
243
+ role: event.role ?? existing.role,
244
+ status: "streaming"
245
+ });
246
+ } else {
247
+ store.appendMessage({
248
+ id: event.messageId,
249
+ role: event.role ?? "assistant",
250
+ status: "streaming",
251
+ parts: [],
252
+ createdAt: Date.now()
253
+ });
254
+ }
255
+ return;
256
+ }
257
+ case "text.delta": {
258
+ ensureMessage(store, event.messageId);
259
+ const message = store.getMessage(event.messageId);
260
+ const part = locatePart(
261
+ message,
262
+ event.partId,
263
+ (p) => event.format === "markdown" ? p.type === "markdown" : p.type === "text" || p.type === "markdown"
264
+ );
265
+ if (part) {
266
+ store.updatePart(event.messageId, part.index, {
267
+ content: part.part.content + event.delta,
268
+ status: "streaming"
269
+ });
270
+ } else {
271
+ store.appendPart(event.messageId, {
272
+ id: event.partId ?? createChatId("part"),
273
+ type: event.format === "markdown" ? "markdown" : "text",
274
+ content: event.delta,
275
+ status: "streaming"
276
+ });
277
+ }
278
+ return;
279
+ }
280
+ case "thinking.delta": {
281
+ ensureMessage(store, event.messageId);
282
+ const message = store.getMessage(event.messageId);
283
+ const part = locatePart(message, event.partId, (p) => p.type === "thinking");
284
+ if (part) {
285
+ store.updatePart(event.messageId, part.index, {
286
+ content: part.part.content + event.delta,
287
+ status: "streaming"
288
+ });
289
+ } else {
290
+ store.appendPart(event.messageId, {
291
+ id: event.partId ?? createChatId("part"),
292
+ type: "thinking",
293
+ content: event.delta,
294
+ status: "streaming"
295
+ });
296
+ }
297
+ return;
298
+ }
299
+ case "tool.start": {
300
+ ensureMessage(store, event.messageId);
301
+ const message = store.getMessage(event.messageId);
302
+ const part = locatePart(
303
+ message,
304
+ event.partId,
305
+ (p) => p.type === "tool-call" && p.name === event.name
306
+ );
307
+ if (part) {
308
+ store.updatePart(event.messageId, part.index, {
309
+ status: "running",
310
+ title: event.title,
311
+ args: event.args
312
+ });
313
+ } else {
314
+ store.appendPart(event.messageId, {
315
+ id: event.partId ?? createChatId("part"),
316
+ type: "tool-call",
317
+ name: event.name,
318
+ title: event.title,
319
+ args: event.args,
320
+ status: "running"
321
+ });
322
+ }
323
+ return;
324
+ }
325
+ case "tool.result": {
326
+ ensureMessage(store, event.messageId);
327
+ const message = store.getMessage(event.messageId);
328
+ const call = locatePart(
329
+ message,
330
+ event.partId,
331
+ (p) => p.type === "tool-call" && p.name === event.name
332
+ );
333
+ if (call) {
334
+ store.updatePart(event.messageId, call.index, {
335
+ status: event.status
336
+ });
337
+ }
338
+ store.appendPart(event.messageId, {
339
+ id: createChatId("part"),
340
+ type: "tool-result",
341
+ name: event.name,
342
+ status: event.status,
343
+ result: event.result,
344
+ error: event.error
345
+ });
346
+ return;
347
+ }
348
+ case "dsl.start": {
349
+ ensureMessage(store, event.messageId);
350
+ store.appendPart(event.messageId, {
351
+ id: event.partId ?? createChatId("part"),
352
+ type: "dsl",
353
+ status: "pending"
354
+ });
355
+ return;
356
+ }
357
+ case "dsl.complete": {
358
+ ensureMessage(store, event.messageId);
359
+ const message = store.getMessage(event.messageId);
360
+ const part = locatePart(message, event.partId, (p) => p.type === "dsl");
361
+ if (part) {
362
+ store.updatePart(event.messageId, part.index, {
363
+ schema: event.schema,
364
+ status: "ready"
365
+ });
366
+ } else {
367
+ store.appendPart(event.messageId, {
368
+ id: event.partId ?? createChatId("part"),
369
+ type: "dsl",
370
+ schema: event.schema,
371
+ status: "ready"
372
+ });
373
+ }
374
+ return;
375
+ }
376
+ case "artifact": {
377
+ ensureMessage(store, event.messageId);
378
+ const message = store.getMessage(event.messageId);
379
+ const part = locatePart(message, event.partId, (p) => p.type === "artifact");
380
+ if (part) {
381
+ store.updatePart(event.messageId, part.index, {
382
+ artifact: event.artifact,
383
+ status: "ready"
384
+ });
385
+ } else {
386
+ store.appendPart(event.messageId, {
387
+ id: event.partId ?? createChatId("part"),
388
+ type: "artifact",
389
+ artifact: event.artifact,
390
+ status: "ready"
391
+ });
392
+ }
393
+ return;
394
+ }
395
+ case "message.end": {
396
+ const message = store.getMessage(event.messageId);
397
+ if (!message) {
398
+ console.warn(
399
+ `[@pactor-app/chat] message.end for unknown message "${event.messageId}", ignored`
400
+ );
401
+ return;
402
+ }
403
+ const parts = message.parts.map(
404
+ (part) => "status" in part && (part.status === "streaming" || part.status === "pending") ? { ...part, status: "ready" } : part
405
+ );
406
+ store.updateMessage(event.messageId, { status: "completed", parts });
407
+ return;
408
+ }
409
+ case "message.error": {
410
+ ensureMessage(store, event.messageId);
411
+ store.appendPart(event.messageId, {
412
+ id: createChatId("part"),
413
+ type: "error",
414
+ message: event.error
415
+ });
416
+ store.setStatus(event.messageId, "error");
417
+ return;
418
+ }
419
+ default: {
420
+ console.warn(
421
+ `[@pactor-app/chat] unknown chat event, ignored:`,
422
+ event.type ?? event
423
+ );
424
+ }
425
+ }
426
+ }
427
+ function ensureMessage(store, messageId) {
428
+ const existing = store.getMessage(messageId);
429
+ if (!existing) {
430
+ store.appendMessage({
431
+ id: messageId,
432
+ role: "assistant",
433
+ status: "streaming",
434
+ parts: [],
435
+ createdAt: Date.now()
436
+ });
437
+ } else if (existing.status === "pending") {
438
+ store.setStatus(messageId, "streaming");
439
+ }
440
+ }
441
+ function locatePart(message, partId, match) {
442
+ if (partId !== void 0) {
443
+ const index = message.parts.findIndex((part) => part.id === partId);
444
+ return index >= 0 ? { index, part: message.parts[index] } : void 0;
445
+ }
446
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
447
+ const part = message.parts[index];
448
+ if (match(part)) {
449
+ return { index, part };
450
+ }
451
+ }
452
+ return void 0;
453
+ }
454
+
455
+ // src/activity.ts
456
+ function detectStreamingMode(message) {
457
+ if (message.status !== "streaming" && message.status !== "pending") {
458
+ return null;
459
+ }
460
+ for (const part of message.parts) {
461
+ if (part.type === "tool-call" && (part.status === "waiting" || part.status === "running")) {
462
+ return "tool";
463
+ }
464
+ }
465
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
466
+ const part = message.parts[index];
467
+ if (part.type === "thinking" && part.status === "streaming") {
468
+ return "reasoning";
469
+ }
470
+ if ((part.type === "text" || part.type === "markdown" || part.type === "dsl") && part.status === "streaming") {
471
+ return "responding";
472
+ }
473
+ }
474
+ return null;
475
+ }
476
+ function pairToolTraces(parts) {
477
+ const entries = [];
478
+ for (let index = 0; index < parts.length; index += 1) {
479
+ const part = parts[index];
480
+ if (part.type === "tool-call") {
481
+ entries.push({
482
+ name: part.name,
483
+ title: part.title,
484
+ args: part.args,
485
+ status: part.status,
486
+ callIndex: index
487
+ });
488
+ } else if (part.type === "tool-result") {
489
+ const entry = entries.find(
490
+ (candidate) => candidate.name === part.name && candidate.resultIndex === void 0
491
+ );
492
+ if (entry) {
493
+ entry.status = part.status;
494
+ entry.result = part.result;
495
+ entry.error = part.error;
496
+ entry.resultIndex = index;
497
+ }
498
+ }
499
+ }
500
+ return entries;
501
+ }
502
+
503
+ // src/transport.ts
504
+ async function runChatTurn(store, transport, input, options = {}) {
505
+ const userMessageId = options.userMessageId ?? createChatId("msg");
506
+ const assistantMessageId = options.assistantMessageId ?? createChatId("msg");
507
+ if (!options.skipUserMessage) {
508
+ store.appendMessage({
509
+ id: userMessageId,
510
+ role: "user",
511
+ status: "completed",
512
+ parts: [
513
+ { id: createChatId("part"), type: "text", content: input.text, status: "ready" }
514
+ ],
515
+ createdAt: Date.now()
516
+ });
517
+ }
518
+ store.appendMessage({
519
+ id: assistantMessageId,
520
+ role: "assistant",
521
+ status: "pending",
522
+ parts: [],
523
+ createdAt: Date.now()
524
+ });
525
+ try {
526
+ for await (const event of transport.send(input)) {
527
+ if (options.signal?.aborted) {
528
+ store.updateMessage(assistantMessageId, { status: "cancelled" });
529
+ return { userMessageId, assistantMessageId };
530
+ }
531
+ applyChatEvent(store, { ...event, messageId: assistantMessageId });
532
+ }
533
+ if (options.signal?.aborted) {
534
+ store.updateMessage(assistantMessageId, { status: "cancelled" });
535
+ return { userMessageId, assistantMessageId };
536
+ }
537
+ const message = store.getMessage(assistantMessageId);
538
+ if (message && (message.status === "pending" || message.status === "streaming")) {
539
+ applyChatEvent(store, { type: "message.end", messageId: assistantMessageId });
540
+ }
541
+ } catch (cause) {
542
+ applyChatEvent(store, {
543
+ type: "message.error",
544
+ messageId: assistantMessageId,
545
+ error: cause instanceof Error ? cause.message : String(cause)
546
+ });
547
+ }
548
+ return { userMessageId, assistantMessageId };
549
+ }
550
+ async function runRegenerateTurn(store, transport, options = {}) {
551
+ const messages = store.getMessages();
552
+ let lastUserIndex = -1;
553
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
554
+ if (messages[index].role === "user") {
555
+ lastUserIndex = index;
556
+ break;
557
+ }
558
+ }
559
+ if (lastUserIndex < 0) {
560
+ console.warn("[@pactor-app/chat] runRegenerateTurn: no user message, ignored");
561
+ return void 0;
562
+ }
563
+ const lastUser = messages[lastUserIndex];
564
+ const next = messages[lastUserIndex + 1];
565
+ if (next) {
566
+ store.removeMessagesFrom(next.id);
567
+ }
568
+ const text = lastUser.parts.filter(
569
+ (part) => part.type === "text" || part.type === "markdown"
570
+ ).map((part) => part.content).join("");
571
+ return runChatTurn(store, transport, { text }, {
572
+ ...options,
573
+ userMessageId: lastUser.id,
574
+ skipUserMessage: true
575
+ });
576
+ }
577
+ function createManualDriver() {
578
+ const queue = [];
579
+ let waiting = null;
580
+ let done = false;
581
+ let failure2;
582
+ let started = false;
583
+ const notify = () => {
584
+ if (waiting) {
585
+ const resolve = waiting;
586
+ waiting = null;
587
+ resolve();
588
+ }
589
+ };
590
+ return {
591
+ transport: {
592
+ async *send() {
593
+ if (started) {
594
+ throw new Error("[@pactor-app/chat] manual driver transport can only be sent once");
595
+ }
596
+ started = true;
597
+ for (; ; ) {
598
+ while (queue.length > 0) {
599
+ yield queue.shift();
600
+ }
601
+ if (failure2 !== void 0) {
602
+ throw failure2;
603
+ }
604
+ if (done) {
605
+ return;
606
+ }
607
+ await new Promise((resolve) => {
608
+ waiting = resolve;
609
+ });
610
+ }
611
+ }
612
+ },
613
+ push(event) {
614
+ queue.push(event);
615
+ notify();
616
+ },
617
+ complete() {
618
+ done = true;
619
+ notify();
620
+ },
621
+ fail(cause) {
622
+ failure2 = cause;
623
+ notify();
624
+ }
625
+ };
626
+ }
627
+
628
+ // src/safe-registry.ts
629
+ var import_core = require("@pactor-app/core");
630
+ var SafeComponentRegistry = class extends import_core.Registry {
631
+ constructor(source, allowed) {
632
+ super("SafeComponentRegistry");
633
+ this.source = source;
634
+ this.allowed = allowed;
635
+ }
636
+ get(name) {
637
+ if (!this.allowed.has(name)) {
638
+ return void 0;
639
+ }
640
+ return this.source.get(name);
641
+ }
642
+ has(name) {
643
+ return this.allowed.has(name) && this.source.has(name);
644
+ }
645
+ list() {
646
+ return this.source.list().filter((name) => this.allowed.has(name));
647
+ }
648
+ };
649
+ function createSafeRegistry(source, allowed) {
650
+ return new SafeComponentRegistry(source, new Set(allowed));
651
+ }
652
+
653
+ // src/capability-action.ts
654
+ function failure(message) {
655
+ return { ok: false, error: new Error(message) };
656
+ }
657
+ function registerCapabilityAction(actions, deps) {
658
+ if (actions.has("capability")) {
659
+ return;
660
+ }
661
+ actions.register("capability", async (action, ctx) => {
662
+ const name = action.params?.["name"];
663
+ if (typeof name !== "string" || !name) {
664
+ return failure("[@pactor-app/chat] capability \u52A8\u4F5C\u7F3A\u5C11 name \u53C2\u6570");
665
+ }
666
+ const capability = deps.capabilities.get(name);
667
+ if (!capability) {
668
+ return failure(`[@pactor-app/chat] capability not registered: "${name}"`);
669
+ }
670
+ if (capability.permission !== void 0 && deps.permission !== void 0 && !deps.permission.check(capability.permission)) {
671
+ return failure(
672
+ `[@pactor-app/chat] permission denied for capability "${name}"`
673
+ );
674
+ }
675
+ const params = action.params?.["params"] ?? {};
676
+ if (capability.risk === "high") {
677
+ if (!deps.confirm) {
678
+ return failure(
679
+ `[@pactor-app/chat] capability "${name}" \u4E3A\u9AD8\u98CE\u9669\u64CD\u4F5C\uFF0C\u4F46\u672A\u6CE8\u5165 confirm`
680
+ );
681
+ }
682
+ const confirmed = await deps.confirm({ name, params });
683
+ if (!confirmed) {
684
+ return { ok: true, skipped: true };
685
+ }
686
+ }
687
+ const data = await capability.execute(params, ctx);
688
+ return { ok: true, data };
689
+ });
690
+ }
691
+
692
+ // src/scope.ts
693
+ var import_permission = require("@pactor-app/permission");
694
+ var import_runtime = require("@pactor-app/runtime");
695
+ function buildChatScope(scope) {
696
+ return {
697
+ conversation: scope.conversation,
698
+ message: scope.message,
699
+ params: scope.params ?? {}
700
+ };
701
+ }
702
+ function createChatRuntime(options = {}) {
703
+ const permission = options.permission ?? (options.permissionStore ? (0, import_permission.createPermissionAdapter)(options.permissionStore) : void 0);
704
+ const context = (0, import_runtime.createRuntimeContext)({
705
+ components: options.safeRegistry,
706
+ user: options.user,
707
+ permission
708
+ });
709
+ (0, import_runtime.registerBuiltinActions)(context.actions);
710
+ if (options.capabilities) {
711
+ const deps = {
712
+ capabilities: options.capabilities,
713
+ permission,
714
+ confirm: options.confirm
715
+ };
716
+ registerCapabilityAction(context.actions, deps);
717
+ }
718
+ return { context, engine: new import_runtime.ActionEngine(context) };
719
+ }
720
+
721
+ // src/dsl-guard.ts
722
+ var CHAT_DENIED_TYPE = "ChatDenied";
723
+ function guardChatDsl(node, registry) {
724
+ if (node.type !== CHAT_DENIED_TYPE && !registry.has(node.type)) {
725
+ return {
726
+ type: CHAT_DENIED_TYPE,
727
+ props: { deniedType: node.type }
728
+ };
729
+ }
730
+ if (!node.children || node.children.length === 0) {
731
+ return node;
732
+ }
733
+ return {
734
+ ...node,
735
+ children: node.children.map((child) => guardChatDsl(child, registry))
736
+ };
737
+ }
738
+
739
+ // src/hooks/use-smooth-stream-text.ts
740
+ var import_react = require("react");
741
+ function useSmoothStreamText(content, isStreaming, options = {}) {
742
+ const {
743
+ maxCharsPerFrame = 120,
744
+ minCharsPerFrame = 2,
745
+ catchUpDivisor = 5,
746
+ enabled = true
747
+ } = options;
748
+ const [shown, setShown] = (0, import_react.useState)(content);
749
+ const shownLengthRef = (0, import_react.useRef)(content.length);
750
+ const rafRef = (0, import_react.useRef)(0);
751
+ (0, import_react.useEffect)(() => {
752
+ if (!enabled || !isStreaming || shownLengthRef.current > content.length) {
753
+ if (rafRef.current) {
754
+ cancelAnimationFrame(rafRef.current);
755
+ rafRef.current = 0;
756
+ }
757
+ if (shownLengthRef.current !== content.length) {
758
+ shownLengthRef.current = content.length;
759
+ setShown(content);
760
+ }
761
+ return;
762
+ }
763
+ if (shownLengthRef.current >= content.length) {
764
+ return;
765
+ }
766
+ const step = () => {
767
+ rafRef.current = 0;
768
+ const target = content.length;
769
+ const current = shownLengthRef.current;
770
+ if (current >= target) {
771
+ return;
772
+ }
773
+ const backlog = target - current;
774
+ const advance = Math.min(
775
+ maxCharsPerFrame,
776
+ Math.max(minCharsPerFrame, Math.ceil(backlog / catchUpDivisor))
777
+ );
778
+ const next = Math.min(target, current + advance);
779
+ shownLengthRef.current = next;
780
+ setShown(content.slice(0, next));
781
+ if (next < target) {
782
+ rafRef.current = requestAnimationFrame(step);
783
+ }
784
+ };
785
+ if (!rafRef.current) {
786
+ rafRef.current = requestAnimationFrame(step);
787
+ }
788
+ return () => {
789
+ if (rafRef.current) {
790
+ cancelAnimationFrame(rafRef.current);
791
+ rafRef.current = 0;
792
+ }
793
+ };
794
+ }, [content, isStreaming, enabled, maxCharsPerFrame, minCharsPerFrame, catchUpDivisor]);
795
+ return shown;
796
+ }
797
+
798
+ // src/hooks/use-chat-auto-scroll.ts
799
+ var import_react2 = require("react");
800
+ var RELEASE_THRESHOLD = 80;
801
+ var REENGAGE_THRESHOLD = 40;
802
+ function useChatAutoScroll({
803
+ messageCount,
804
+ lastContentLength: lastContentLength2,
805
+ isStreaming
806
+ }) {
807
+ const containerRef = (0, import_react2.useRef)(null);
808
+ const followRef = (0, import_react2.useRef)(true);
809
+ const [isFollowing, setIsFollowing] = (0, import_react2.useState)(true);
810
+ const setFollow = (follow) => {
811
+ followRef.current = follow;
812
+ setIsFollowing(follow);
813
+ };
814
+ (0, import_react2.useLayoutEffect)(() => {
815
+ const container = containerRef.current;
816
+ if (container && followRef.current) {
817
+ container.scrollTop = container.scrollHeight;
818
+ }
819
+ }, [messageCount, lastContentLength2, isStreaming]);
820
+ (0, import_react2.useLayoutEffect)(() => {
821
+ const container = containerRef.current;
822
+ if (!container) {
823
+ return;
824
+ }
825
+ const onScroll = () => {
826
+ const distance = container.scrollHeight - container.scrollTop - container.clientHeight;
827
+ if (followRef.current && distance > RELEASE_THRESHOLD) {
828
+ setFollow(false);
829
+ } else if (!followRef.current && distance < REENGAGE_THRESHOLD) {
830
+ setFollow(true);
831
+ }
832
+ };
833
+ container.addEventListener("scroll", onScroll, { passive: true });
834
+ return () => container.removeEventListener("scroll", onScroll);
835
+ }, []);
836
+ const scrollToBottom = (0, import_react2.useCallback)(() => {
837
+ const container = containerRef.current;
838
+ if (container) {
839
+ container.scrollTop = container.scrollHeight;
840
+ }
841
+ setFollow(true);
842
+ }, []);
843
+ return { containerRef, isFollowing, scrollToBottom };
844
+ }
845
+
846
+ // src/chat-composition.tsx
847
+ var import_runtime4 = require("@pactor-app/runtime");
848
+ var import_react15 = require("react");
849
+
850
+ // src/hooks/use-chat-model.ts
851
+ var import_react3 = require("react");
852
+ function useChatModel({
853
+ models,
854
+ model,
855
+ onModelChange
856
+ }) {
857
+ const modelsEnabled = models !== void 0 && models.length > 0;
858
+ const modelControlled = model !== void 0 && onModelChange !== void 0;
859
+ const [innerModel, setInnerModel] = (0, import_react3.useState)(
860
+ () => models?.[0]?.value
861
+ );
862
+ const currentModel = modelsEnabled ? modelControlled ? model : innerModel ?? models[0].value : void 0;
863
+ const handleModelChange = (value) => {
864
+ if (!modelControlled) {
865
+ setInnerModel(value);
866
+ }
867
+ onModelChange?.(value);
868
+ };
869
+ return { modelsEnabled, currentModel, handleModelChange };
870
+ }
871
+
872
+ // src/view/AppShell.tsx
873
+ var import_lucide_react = require("lucide-react");
874
+ var import_react4 = require("react");
875
+ var import_jsx_runtime = require("react/jsx-runtime");
876
+ var SidebarDrawerContext = (0, import_react4.createContext)(null);
877
+ function useSidebarDrawer() {
878
+ return (0, import_react4.useContext)(SidebarDrawerContext);
879
+ }
880
+ var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
881
+ function useIsMobileViewport() {
882
+ const [isMobile, setIsMobile] = (0, import_react4.useState)(
883
+ () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_MEDIA_QUERY).matches
884
+ );
885
+ (0, import_react4.useEffect)(() => {
886
+ if (typeof window.matchMedia !== "function") {
887
+ return;
888
+ }
889
+ const media = window.matchMedia(MOBILE_MEDIA_QUERY);
890
+ const onChange = (event) => {
891
+ setIsMobile(event.matches);
892
+ };
893
+ setIsMobile(media.matches);
894
+ media.addEventListener("change", onChange);
895
+ return () => media.removeEventListener("change", onChange);
896
+ }, []);
897
+ return isMobile;
898
+ }
899
+ function AppShell({ sidebar, brand, locationKey, sidebarFooter, collapsible = false, children, className }) {
900
+ const [drawerOpen, setDrawerOpen] = (0, import_react4.useState)(false);
901
+ const isMobile = useIsMobileViewport();
902
+ const close = (0, import_react4.useCallback)(() => setDrawerOpen(false), []);
903
+ const [collapsed, setCollapsed] = (0, import_react4.useState)(false);
904
+ const collapsedEffective = collapsible && collapsed && !isMobile;
905
+ const framed = brand !== void 0 || sidebarFooter !== void 0;
906
+ const [trackedLocationKey, setTrackedLocationKey] = (0, import_react4.useState)(locationKey);
907
+ if (trackedLocationKey !== locationKey) {
908
+ setTrackedLocationKey(locationKey);
909
+ setDrawerOpen(false);
910
+ }
911
+ (0, import_react4.useEffect)(() => {
912
+ if (!drawerOpen) {
913
+ return;
914
+ }
915
+ const onKeyDown = (event) => {
916
+ if (event.key === "Escape") {
917
+ setDrawerOpen(false);
918
+ }
919
+ };
920
+ window.addEventListener("keydown", onKeyDown);
921
+ return () => window.removeEventListener("keydown", onKeyDown);
922
+ }, [drawerOpen]);
923
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarDrawerContext.Provider, { value: { close, collapsed: collapsedEffective }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `pactor-chat-shell${className ? ` ${className}` : ""}`, children: [
924
+ drawerOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__scrim", "aria-hidden": true, onClick: close }) : null,
925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
926
+ "div",
927
+ {
928
+ className: `pactor-chat-shell__sidebar${framed ? " pactor-chat-shell__sidebar--framed" : ""}${collapsedEffective ? " pactor-chat-shell__sidebar--collapsed" : ""}`,
929
+ "data-open": drawerOpen ? "true" : "false",
930
+ inert: isMobile && !drawerOpen ? true : void 0,
931
+ children: framed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
932
+ brand !== void 0 && // 桌面品牌行(决策 D3):brand 左、折叠钮右;移动端由
933
+ // CSS 隐藏(品牌维持顶栏现状)
934
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pactor-chat-shell__sidebar-header", children: [
935
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__brand", children: brand }),
936
+ collapsible && !isMobile && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
937
+ "button",
938
+ {
939
+ type: "button",
940
+ className: "pactor-chat-icon-button pactor-chat-shell__collapse",
941
+ "aria-label": collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
942
+ "aria-expanded": !collapsedEffective,
943
+ title: collapsedEffective ? "\u5C55\u5F00\u4FA7\u680F" : "\u6298\u53E0\u4FA7\u680F",
944
+ onClick: () => setCollapsed((value) => !value),
945
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.PanelLeft, { size: 16, strokeWidth: 1.7 })
946
+ }
947
+ )
948
+ ] }),
949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__sidebar-body", children: sidebar }),
950
+ sidebarFooter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__sidebar-footer", children: sidebarFooter })
951
+ ] }) : sidebar
952
+ }
953
+ ),
954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: "pactor-chat-shell__main", children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pactor-chat-shell__topbar", children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
957
+ "button",
958
+ {
959
+ type: "button",
960
+ className: "pactor-chat-icon-button pactor-chat-shell__menu",
961
+ "aria-label": "\u6253\u5F00\u5BFC\u822A",
962
+ "aria-expanded": drawerOpen,
963
+ onClick: () => setDrawerOpen(true),
964
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Menu, { size: 18, strokeWidth: 1.7 })
965
+ }
966
+ ),
967
+ brand
968
+ ] }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pactor-chat-shell__content", children })
970
+ ] })
971
+ ] }) });
972
+ }
973
+
974
+ // src/view/ArtifactPanel.tsx
975
+ var import_lucide_react3 = require("lucide-react");
976
+ var import_core2 = require("@pactor-app/core");
977
+ var import_markdown2 = require("@pactor-app/markdown");
978
+ var import_runtime3 = require("@pactor-app/runtime");
979
+ var import_react5 = require("react");
980
+
981
+ // src/view/PartRenderer.tsx
982
+ var import_lucide_react2 = require("lucide-react");
983
+ var import_markdown = require("@pactor-app/markdown");
984
+ var import_runtime2 = require("@pactor-app/runtime");
985
+ var import_jsx_runtime2 = require("react/jsx-runtime");
986
+ function ChatDenied({ deniedType }) {
987
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "pactor-chat-denied", "data-pactor-chat-denied": deniedType, children: [
988
+ "\u8BE5\u7EC4\u4EF6\u4E0D\u5141\u8BB8\u5728 Chat \u4E2D\u4F7F\u7528",
989
+ deniedType ? `\uFF1A${deniedType}` : ""
990
+ ] });
991
+ }
992
+ function ChatDslNode({
993
+ node,
994
+ scope
995
+ }) {
996
+ const renderer = (0, import_runtime2.useRenderer)();
997
+ const extraScope = scope ? buildChatScope(scope) : void 0;
998
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderer.renderChildren([node], extraScope) });
999
+ }
1000
+ function SmoothText({ content, streaming }) {
1001
+ const shown = useSmoothStreamText(content, streaming);
1002
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-text", children: shown });
1003
+ }
1004
+ function SmoothMarkdown({ content, streaming }) {
1005
+ const shown = useSmoothStreamText(content, streaming);
1006
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_markdown.MarkdownRenderer, { content: shown });
1007
+ }
1008
+ function PartRenderer({ part, runtime, scope, streaming = false, onOpenArtifact }) {
1009
+ switch (part.type) {
1010
+ case "text":
1011
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmoothText, { content: part.content, streaming });
1012
+ case "markdown":
1013
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmoothMarkdown, { content: part.content, streaming });
1014
+ case "thinking":
1015
+ case "tool-call":
1016
+ case "tool-result":
1017
+ return null;
1018
+ case "dsl": {
1019
+ if (!part.schema || !runtime) {
1020
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-dsl-pending", children: part.schema ? "DSL \u6E32\u67D3\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "\u5185\u5BB9\u751F\u6210\u4E2D\u2026" });
1021
+ }
1022
+ const guarded = guardChatDsl(part.schema, runtime.context.components);
1023
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-dsl", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_runtime2.DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatDslNode, { node: guarded, scope }) }) });
1024
+ }
1025
+ case "error":
1026
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "pactor-chat-error", role: "alert", children: [
1027
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.CircleAlert, { size: 14 }),
1028
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: part.message })
1029
+ ] });
1030
+ case "status":
1031
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pactor-chat-status", children: part.text });
1032
+ case "artifact":
1033
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1034
+ "div",
1035
+ {
1036
+ className: "pactor-chat-artifact-card",
1037
+ "data-artifact-kind": part.artifact.kind,
1038
+ children: [
1039
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "pactor-chat-artifact-card__title", children: part.artifact.title }),
1040
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "pactor-chat-artifact-card__kind", children: part.artifact.kind }),
1041
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1042
+ "button",
1043
+ {
1044
+ type: "button",
1045
+ className: "pactor-chat-artifact-card__open",
1046
+ onClick: () => onOpenArtifact?.(part.artifact),
1047
+ children: [
1048
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.PanelRightOpen, { size: 13, strokeWidth: 1.7 }),
1049
+ "\u5728\u9762\u677F\u4E2D\u6253\u5F00"
1050
+ ]
1051
+ }
1052
+ )
1053
+ ]
1054
+ }
1055
+ );
1056
+ default:
1057
+ return null;
1058
+ }
1059
+ }
1060
+
1061
+ // src/view/ArtifactPanel.tsx
1062
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1063
+ function useArtifactRuntime(safeRegistry, user) {
1064
+ return (0, import_react5.useMemo)(() => {
1065
+ if (!safeRegistry) {
1066
+ return void 0;
1067
+ }
1068
+ const components = new import_core2.Registry("ChatArtifactRegistry");
1069
+ components.register(CHAT_DENIED_TYPE, ChatDenied);
1070
+ for (const name of safeRegistry.list()) {
1071
+ components.register(name, safeRegistry.get(name));
1072
+ }
1073
+ return createChatRuntime({ safeRegistry: components, user });
1074
+ }, [safeRegistry, user]);
1075
+ }
1076
+ var ArtifactDslGuard = class extends import_react5.Component {
1077
+ constructor() {
1078
+ super(...arguments);
1079
+ this.state = { failed: false };
1080
+ }
1081
+ static getDerivedStateFromError() {
1082
+ return { failed: true };
1083
+ }
1084
+ componentDidCatch(error) {
1085
+ console.warn("[@pactor-app/chat] DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A", error);
1086
+ }
1087
+ render() {
1088
+ return this.state.failed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-denied", children: "DSL \u4EA7\u51FA\u7269\u6E32\u67D3\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u5C55\u793A" }) : this.props.children;
1089
+ }
1090
+ };
1091
+ function ArtifactDslNode({ node }) {
1092
+ const { renderChildren } = (0, import_runtime3.useRenderer)();
1093
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderChildren([node]) });
1094
+ }
1095
+ function ArtifactDslContent({
1096
+ content,
1097
+ runtime
1098
+ }) {
1099
+ const node = typeof content === "object" && content !== null && !Array.isArray(content) && typeof content.type === "string" ? content : void 0;
1100
+ if (!node || !runtime) {
1101
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-denied", children: node ? "DSL \u9884\u89C8\u4E0D\u53EF\u7528\uFF08\u672A\u6CE8\u5165 Safe Registry\uFF09" : "DSL \u4EA7\u51FA\u7269\u5185\u5BB9\u65E0\u6548" });
1102
+ }
1103
+ const guarded = guardChatDsl(node, runtime.context.components);
1104
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_runtime3.DslRenderer, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactDslGuard, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactDslNode, { node: guarded }) }) });
1105
+ }
1106
+ function ArtifactContent({
1107
+ artifact,
1108
+ runtime
1109
+ }) {
1110
+ switch (artifact.kind) {
1111
+ case "markdown":
1112
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_markdown2.MarkdownRenderer, { content: String(artifact.content) });
1113
+ case "code":
1114
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pactor-chat-inspector__code", children: [
1115
+ artifact.language ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-inspector__code-lang", children: artifact.language }) : null,
1116
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: String(artifact.content) }) })
1117
+ ] });
1118
+ case "text":
1119
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { className: "pactor-chat-inspector__text", children: String(artifact.content) });
1120
+ case "dsl":
1121
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactDslContent, { content: artifact.content, runtime });
1122
+ default:
1123
+ return null;
1124
+ }
1125
+ }
1126
+ function ArtifactPanel({
1127
+ artifact,
1128
+ inspector,
1129
+ onClose,
1130
+ safeRegistry,
1131
+ user,
1132
+ className
1133
+ }) {
1134
+ const runtime = useArtifactRuntime(safeRegistry, user);
1135
+ const panelRef = (0, import_react5.useRef)(null);
1136
+ const [width, setWidth] = (0, import_react5.useState)(null);
1137
+ const handleResizeStart = (event) => {
1138
+ event.preventDefault();
1139
+ const panel = panelRef.current;
1140
+ if (!panel) {
1141
+ return;
1142
+ }
1143
+ const hostRect = panel.parentElement?.getBoundingClientRect();
1144
+ const hostWidth = hostRect !== void 0 && hostRect.width > 0 ? hostRect.width : window.innerWidth;
1145
+ const maxWidth = hostWidth * 0.7;
1146
+ const startWidth = panel.getBoundingClientRect().width;
1147
+ const startX = event.clientX;
1148
+ const prevUserSelect = document.body.style.userSelect;
1149
+ document.body.style.userSelect = "none";
1150
+ const onMove = (moveEvent) => {
1151
+ const next = startWidth + (startX - moveEvent.clientX);
1152
+ setWidth(Math.round(Math.min(Math.max(next, 240), maxWidth)));
1153
+ };
1154
+ const onUp = () => {
1155
+ document.body.style.userSelect = prevUserSelect;
1156
+ document.removeEventListener("pointermove", onMove);
1157
+ document.removeEventListener("pointerup", onUp);
1158
+ };
1159
+ document.addEventListener("pointermove", onMove);
1160
+ document.addEventListener("pointerup", onUp);
1161
+ };
1162
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1163
+ "aside",
1164
+ {
1165
+ ref: panelRef,
1166
+ className: `pactor-chat-inspector${className ? ` ${className}` : ""}`,
1167
+ "data-kind": artifact?.kind,
1168
+ style: width !== null ? { width: `${width}px` } : void 0,
1169
+ children: [
1170
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1171
+ "div",
1172
+ {
1173
+ className: "pactor-chat-inspector__resize",
1174
+ role: "separator",
1175
+ "aria-orientation": "vertical",
1176
+ "aria-label": "\u62D6\u52A8\u8C03\u6574\u9762\u677F\u5BBD\u5EA6",
1177
+ onPointerDown: handleResizeStart
1178
+ }
1179
+ ),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pactor-chat-inspector__header", children: [
1181
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
1182
+ artifact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
1183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1184
+ "button",
1185
+ {
1186
+ type: "button",
1187
+ className: "pactor-chat-icon-button pactor-chat-inspector__close",
1188
+ "aria-label": "\u5173\u95ED\u9762\u677F",
1189
+ onClick: onClose,
1190
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.X, { size: 16, strokeWidth: 1.7 })
1191
+ }
1192
+ )
1193
+ ] }),
1194
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pactor-chat-inspector__body", children: inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArtifactContent, { artifact, runtime }) : null })
1195
+ ]
1196
+ }
1197
+ );
1198
+ }
1199
+
1200
+ // src/view/ChatView.tsx
1201
+ var import_lucide_react8 = require("lucide-react");
1202
+ var import_core3 = require("@pactor-app/core");
1203
+ var import_react13 = require("react");
1204
+
1205
+ // src/view/Composer.tsx
1206
+ var import_lucide_react5 = require("lucide-react");
1207
+ var import_react9 = require("react");
1208
+
1209
+ // src/view/AnchoredPopover.tsx
1210
+ var import_react6 = require("react");
1211
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1212
+ function AnchoredPopover({
1213
+ anchorRect,
1214
+ onClose,
1215
+ children,
1216
+ className,
1217
+ role = "dialog",
1218
+ sideOffset = 8
1219
+ }) {
1220
+ const ref = (0, import_react6.useRef)(null);
1221
+ const [position, setPosition] = (0, import_react6.useState)(null);
1222
+ (0, import_react6.useLayoutEffect)(() => {
1223
+ const element = ref.current;
1224
+ if (!element) return;
1225
+ const height = element.offsetHeight;
1226
+ const width = element.offsetWidth;
1227
+ const below = height + sideOffset > anchorRect.top;
1228
+ const left = Math.max(
1229
+ 0,
1230
+ Math.min(anchorRect.left, window.innerWidth - width - sideOffset)
1231
+ );
1232
+ setPosition(
1233
+ below ? { below, left, top: anchorRect.bottom + sideOffset } : {
1234
+ below,
1235
+ left,
1236
+ bottom: window.innerHeight - anchorRect.top + sideOffset
1237
+ }
1238
+ );
1239
+ }, [anchorRect, sideOffset]);
1240
+ (0, import_react6.useEffect)(() => {
1241
+ const onKeyDown = (event) => {
1242
+ if (event.key !== "Escape") return;
1243
+ const element = ref.current;
1244
+ const active = document.activeElement;
1245
+ if (element && active instanceof HTMLElement && element.contains(active)) {
1246
+ active.blur();
1247
+ }
1248
+ onClose();
1249
+ };
1250
+ const onPointerDown = (event) => {
1251
+ const element = ref.current;
1252
+ if (element && event.target instanceof Node && !element.contains(event.target)) {
1253
+ onClose();
1254
+ }
1255
+ };
1256
+ document.addEventListener("keydown", onKeyDown);
1257
+ document.addEventListener("pointerdown", onPointerDown, true);
1258
+ return () => {
1259
+ document.removeEventListener("keydown", onKeyDown);
1260
+ document.removeEventListener("pointerdown", onPointerDown, true);
1261
+ };
1262
+ }, [onClose]);
1263
+ const classNames = [
1264
+ "pactor-chat-popover",
1265
+ position?.below ? "pactor-chat-popover--below" : "",
1266
+ className ?? ""
1267
+ ].filter(Boolean).join(" ");
1268
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1269
+ "div",
1270
+ {
1271
+ ref,
1272
+ className: classNames,
1273
+ role,
1274
+ tabIndex: -1,
1275
+ style: {
1276
+ left: position?.left ?? anchorRect.left,
1277
+ top: position?.top,
1278
+ bottom: position?.bottom,
1279
+ visibility: position ? "visible" : "hidden"
1280
+ },
1281
+ children
1282
+ }
1283
+ );
1284
+ }
1285
+
1286
+ // src/view/ModelChip.tsx
1287
+ var import_lucide_react4 = require("lucide-react");
1288
+ var import_react7 = require("react");
1289
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1290
+ function ModelChip({
1291
+ models,
1292
+ model,
1293
+ onModelChange
1294
+ }) {
1295
+ const chipRef = (0, import_react7.useRef)(null);
1296
+ const [anchor, setAnchor] = (0, import_react7.useState)(null);
1297
+ const currentOption = models.find((option) => option.value === model) ?? models[0];
1298
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1299
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1300
+ "button",
1301
+ {
1302
+ ref: chipRef,
1303
+ type: "button",
1304
+ className: "pactor-chat-model-chip",
1305
+ title: "\u9009\u62E9\u6A21\u578B",
1306
+ "aria-label": "\u9009\u62E9\u6A21\u578B",
1307
+ onClick: () => setAnchor(chipRef.current.getBoundingClientRect()),
1308
+ children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-model-chip__label", children: currentOption.label }),
1310
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.ChevronsUpDown, { size: 12 })
1311
+ ]
1312
+ }
1313
+ ),
1314
+ anchor && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1315
+ AnchoredPopover,
1316
+ {
1317
+ anchorRect: anchor,
1318
+ onClose: () => setAnchor(null),
1319
+ role: "listbox",
1320
+ children: models.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1321
+ "button",
1322
+ {
1323
+ type: "button",
1324
+ className: "pactor-chat-popover__item",
1325
+ role: "option",
1326
+ "aria-selected": option.value === currentOption.value,
1327
+ onClick: () => {
1328
+ onModelChange?.(option.value);
1329
+ setAnchor(null);
1330
+ },
1331
+ children: [
1332
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pactor-chat-popover__item-label", children: option.label }),
1333
+ option.value === currentOption.value && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.Check, { size: 13, className: "pactor-chat-popover__item-check" })
1334
+ ]
1335
+ },
1336
+ option.value
1337
+ ))
1338
+ }
1339
+ )
1340
+ ] });
1341
+ }
1342
+
1343
+ // src/view/context-sources.tsx
1344
+ var import_react8 = require("react");
1345
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1346
+ function sourceCacheKey(source) {
1347
+ return `${source.service ?? ""}:${JSON.stringify(source.params ?? {})}`;
1348
+ }
1349
+ function resolveSource(source, callService, cache) {
1350
+ if (source.items) {
1351
+ return Promise.resolve(source.items);
1352
+ }
1353
+ if (!source.service) {
1354
+ return Promise.resolve([]);
1355
+ }
1356
+ const key = sourceCacheKey(source);
1357
+ let cached = cache.get(key);
1358
+ if (!cached) {
1359
+ cached = callService ? callService(source.service, source.params).then(
1360
+ (value) => Array.isArray(value) ? value : []
1361
+ ) : Promise.reject(
1362
+ new Error(
1363
+ `\u672A\u6CE8\u5165 callService\uFF0C\u65E0\u6CD5\u89E3\u6790\u670D\u52A1 "${source.service}"`
1364
+ )
1365
+ );
1366
+ cache.set(key, cached);
1367
+ }
1368
+ return cached;
1369
+ }
1370
+ function matchesQuery(item, query) {
1371
+ return item.label.toLowerCase().includes(query.toLowerCase());
1372
+ }
1373
+ function ContextSourceList({
1374
+ sources,
1375
+ query,
1376
+ callService,
1377
+ cache,
1378
+ onSelect
1379
+ }) {
1380
+ const [resolutions, setResolutions] = (0, import_react8.useState)([]);
1381
+ (0, import_react8.useEffect)(() => {
1382
+ let cancelled = false;
1383
+ const initial = sources.map(
1384
+ (source) => source.items ? { status: "ready", items: source.items } : { status: "loading" }
1385
+ );
1386
+ setResolutions(initial);
1387
+ sources.forEach((source, index) => {
1388
+ if (source.items) {
1389
+ return;
1390
+ }
1391
+ resolveSource(source, callService, cache).then(
1392
+ (items) => {
1393
+ if (!cancelled) {
1394
+ setResolutions((prev) => {
1395
+ const next = [...prev];
1396
+ next[index] = { status: "ready", items };
1397
+ return next;
1398
+ });
1399
+ }
1400
+ },
1401
+ (cause) => {
1402
+ if (!cancelled) {
1403
+ setResolutions((prev) => {
1404
+ const next = [...prev];
1405
+ next[index] = {
1406
+ status: "error",
1407
+ message: cause instanceof Error ? cause.message : String(cause)
1408
+ };
1409
+ return next;
1410
+ });
1411
+ }
1412
+ }
1413
+ );
1414
+ });
1415
+ return () => {
1416
+ cancelled = true;
1417
+ };
1418
+ }, [sources, callService, cache]);
1419
+ let totalMatched = 0;
1420
+ const groups = sources.map((source, index) => {
1421
+ const resolution = resolutions[index] ?? { status: "loading" };
1422
+ const items = resolution.status === "ready" ? resolution.items.filter((item) => matchesQuery(item, query)) : [];
1423
+ totalMatched += items.length;
1424
+ return { source, resolution, items };
1425
+ });
1426
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "pactor-chat-source-list", children: [
1427
+ groups.map(({ source, resolution, items }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "pactor-chat-source-list__group", children: [
1428
+ sources.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__group-label", children: source.label }),
1429
+ resolution.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__hint", children: "\u52A0\u8F7D\u4E2D\u2026" }),
1430
+ resolution.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__error", role: "alert", children: resolution.message }),
1431
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1432
+ "button",
1433
+ {
1434
+ type: "button",
1435
+ className: "pactor-chat-popover__item",
1436
+ onClick: () => onSelect(item, source),
1437
+ children: item.label
1438
+ },
1439
+ item.id
1440
+ ))
1441
+ ] }, `${source.trigger}:${source.label}`)),
1442
+ totalMatched === 0 && groups.every(({ resolution }) => resolution.status === "ready") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pactor-chat-source-list__hint", children: "\u65E0\u5339\u914D\u9879" })
1443
+ ] });
1444
+ }
1445
+
1446
+ // src/view/Composer.tsx
1447
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1448
+ var MAX_INPUT_HEIGHT = 200;
1449
+ function formatSize(bytes) {
1450
+ if (bytes < 1024) {
1451
+ return `${bytes} B`;
1452
+ }
1453
+ if (bytes < 1024 * 1024) {
1454
+ return `${(bytes / 1024).toFixed(1)} KB`;
1455
+ }
1456
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1457
+ }
1458
+ function detectMention(text, caret) {
1459
+ const beforeCaret = text.slice(0, caret);
1460
+ const match = /(?:^|\s)@([^\s@]*)$/.exec(beforeCaret);
1461
+ return match ? match[1] : null;
1462
+ }
1463
+ function collectDropFiles(event) {
1464
+ return event.dataTransfer ? Array.from(event.dataTransfer.files) : [];
1465
+ }
1466
+ function Composer({
1467
+ onSend,
1468
+ streaming = false,
1469
+ onStop,
1470
+ placeholder = "\u8F93\u5165\u6D88\u606F\uFF0CEnter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C",
1471
+ disabled = false,
1472
+ attachments,
1473
+ onAttachmentsChange,
1474
+ onAttachFiles,
1475
+ onAttachmentClick,
1476
+ toolbarStart,
1477
+ toolbarEnd,
1478
+ onMentionTrigger,
1479
+ onPlusMenu,
1480
+ models,
1481
+ model,
1482
+ onModelChange,
1483
+ contextSources,
1484
+ contextChips,
1485
+ onContextChipsChange,
1486
+ callService
1487
+ }) {
1488
+ const [text, setText] = (0, import_react9.useState)("");
1489
+ const [dragOver, setDragOver] = (0, import_react9.useState)(false);
1490
+ const inputRef = (0, import_react9.useRef)(null);
1491
+ const fileInputRef = (0, import_react9.useRef)(null);
1492
+ const plusButtonRef = (0, import_react9.useRef)(null);
1493
+ const composingRef = (0, import_react9.useRef)(false);
1494
+ const modelChipEnabled = models !== void 0 && models.length > 0;
1495
+ const mentionSources = (0, import_react9.useMemo)(
1496
+ () => contextSources?.filter((source) => source.trigger === "@") ?? [],
1497
+ [contextSources]
1498
+ );
1499
+ const plusSources = (0, import_react9.useMemo)(
1500
+ () => contextSources?.filter((source) => source.trigger === "+") ?? [],
1501
+ [contextSources]
1502
+ );
1503
+ const builtinMentionEnabled = onMentionTrigger === void 0 && mentionSources.length > 0;
1504
+ const builtinPlusEnabled = onPlusMenu === void 0 && plusSources.length > 0;
1505
+ const [mentionState, setMentionState] = (0, import_react9.useState)(null);
1506
+ const [plusAnchor, setPlusAnchor] = (0, import_react9.useState)(null);
1507
+ const sourceCacheRef = (0, import_react9.useRef)(null);
1508
+ if (sourceCacheRef.current === null) {
1509
+ sourceCacheRef.current = /* @__PURE__ */ new Map();
1510
+ }
1511
+ const handleSelectContext = (item, source) => {
1512
+ const chip = {
1513
+ ...item,
1514
+ trigger: item.trigger ?? source.trigger
1515
+ };
1516
+ const chips = contextChips ?? [];
1517
+ const duplicated = chips.some(
1518
+ (existing) => existing.id === chip.id && existing.trigger === chip.trigger
1519
+ );
1520
+ if (!duplicated) {
1521
+ onContextChipsChange?.([...chips, chip]);
1522
+ }
1523
+ setMentionState(null);
1524
+ setPlusAnchor(null);
1525
+ };
1526
+ (0, import_react9.useLayoutEffect)(() => {
1527
+ const input = inputRef.current;
1528
+ if (input) {
1529
+ input.style.height = "auto";
1530
+ input.style.height = `${Math.min(input.scrollHeight, MAX_INPUT_HEIGHT)}px`;
1531
+ }
1532
+ }, [text]);
1533
+ const send = () => {
1534
+ const value = text.trim();
1535
+ if (!value || disabled) {
1536
+ return;
1537
+ }
1538
+ onSend?.(value);
1539
+ setText("");
1540
+ };
1541
+ const handleChange = (event) => {
1542
+ const value = event.target.value;
1543
+ setText(value);
1544
+ if (onMentionTrigger || builtinMentionEnabled) {
1545
+ const caret = event.target.selectionStart ?? value.length;
1546
+ const query = detectMention(value, caret);
1547
+ if (onMentionTrigger) {
1548
+ if (query !== null) {
1549
+ onMentionTrigger(query, event.target.getBoundingClientRect());
1550
+ }
1551
+ } else {
1552
+ setMentionState(
1553
+ query !== null ? { query, anchorRect: event.target.getBoundingClientRect() } : null
1554
+ );
1555
+ }
1556
+ }
1557
+ };
1558
+ const showToolbar = toolbarStart !== void 0 || toolbarEnd !== void 0 || onPlusMenu !== void 0 || builtinPlusEnabled || onAttachFiles !== void 0 || modelChipEnabled;
1559
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1560
+ "div",
1561
+ {
1562
+ className: `pactor-chat-composer${dragOver ? " pactor-chat-composer--dragover" : ""}`,
1563
+ onDragOver: (event) => {
1564
+ event.preventDefault();
1565
+ setDragOver(true);
1566
+ },
1567
+ onDragLeave: () => setDragOver(false),
1568
+ onDrop: (event) => {
1569
+ event.preventDefault();
1570
+ setDragOver(false);
1571
+ const files = collectDropFiles(event);
1572
+ if (files.length > 0 && onAttachFiles) {
1573
+ onAttachFiles(files);
1574
+ }
1575
+ },
1576
+ children: [
1577
+ attachments !== void 0 && attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pactor-chat-composer__attachments", children: attachments.map((attachment) => {
1578
+ const isImage = attachment.mimeType.startsWith("image/");
1579
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1580
+ "div",
1581
+ {
1582
+ className: "pactor-chat-composer__attachment",
1583
+ "data-attachment-id": attachment.id,
1584
+ onClick: () => onAttachmentClick?.(attachment),
1585
+ children: [
1586
+ isImage && attachment.url ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__attachment-thumb", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: attachment.url, alt: attachment.name }) }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "pactor-chat-composer__attachment-info", children: [
1587
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.FileText, { size: 14 }),
1588
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__attachment-name", children: attachment.name }),
1589
+ attachment.size !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__attachment-size", children: formatSize(attachment.size) })
1590
+ ] }),
1591
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1592
+ "button",
1593
+ {
1594
+ type: "button",
1595
+ className: "pactor-chat-icon-button pactor-chat-composer__attachment-remove",
1596
+ title: "\u79FB\u9664\u9644\u4EF6",
1597
+ "aria-label": "\u79FB\u9664\u9644\u4EF6",
1598
+ onClick: (event) => {
1599
+ event.stopPropagation();
1600
+ onAttachmentsChange?.(
1601
+ attachments.filter((item) => item.id !== attachment.id)
1602
+ );
1603
+ },
1604
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.X, { size: 12 })
1605
+ }
1606
+ )
1607
+ ]
1608
+ },
1609
+ attachment.id
1610
+ );
1611
+ }) }),
1612
+ contextChips !== void 0 && contextChips.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pactor-chat-composer__contexts", children: contextChips.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1613
+ "span",
1614
+ {
1615
+ className: "pactor-chat-context-chip",
1616
+ "data-context-id": chip.id,
1617
+ children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-context-chip__label", children: chip.label }),
1619
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1620
+ "button",
1621
+ {
1622
+ type: "button",
1623
+ className: "pactor-chat-icon-button pactor-chat-context-chip__remove",
1624
+ title: `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1625
+ "aria-label": `\u79FB\u9664\u4E0A\u4E0B\u6587 ${chip.label}`,
1626
+ onClick: () => onContextChipsChange?.(
1627
+ contextChips.filter(
1628
+ (existing) => !(existing.id === chip.id && existing.trigger === chip.trigger)
1629
+ )
1630
+ ),
1631
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.X, { size: 12 })
1632
+ }
1633
+ )
1634
+ ]
1635
+ },
1636
+ `${chip.trigger ?? ""}:${chip.id}`
1637
+ )) }),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pactor-chat-composer__row", children: [
1639
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1640
+ "textarea",
1641
+ {
1642
+ ref: inputRef,
1643
+ className: "pactor-chat-composer__input",
1644
+ rows: 1,
1645
+ value: text,
1646
+ placeholder,
1647
+ disabled,
1648
+ onChange: handleChange,
1649
+ onCompositionStart: () => {
1650
+ composingRef.current = true;
1651
+ },
1652
+ onCompositionEnd: () => {
1653
+ composingRef.current = false;
1654
+ },
1655
+ onPaste: (event) => {
1656
+ const files = Array.from(event.clipboardData.files);
1657
+ if (files.length > 0 && onAttachFiles) {
1658
+ event.preventDefault();
1659
+ onAttachFiles(files);
1660
+ }
1661
+ },
1662
+ onKeyDown: (event) => {
1663
+ if (event.nativeEvent.isComposing || composingRef.current) {
1664
+ return;
1665
+ }
1666
+ if (event.key === "Enter" && !event.shiftKey) {
1667
+ event.preventDefault();
1668
+ send();
1669
+ }
1670
+ }
1671
+ }
1672
+ ),
1673
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1674
+ "button",
1675
+ {
1676
+ type: "button",
1677
+ className: `pactor-chat-composer__button${streaming ? " pactor-chat-composer__button--stop" : ""}`,
1678
+ title: streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1679
+ "aria-label": streaming ? "\u505C\u6B62\u751F\u6210" : "\u53D1\u9001",
1680
+ disabled: streaming ? false : disabled || !text.trim(),
1681
+ onClick: streaming ? onStop : send,
1682
+ children: streaming ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Square, { size: 14, fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.ArrowUp, { size: 16 })
1683
+ }
1684
+ )
1685
+ ] }),
1686
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pactor-chat-composer__toolbar", children: [
1687
+ toolbarStart,
1688
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pactor-chat-composer__toolbar-spacer" }),
1689
+ (onPlusMenu || builtinPlusEnabled) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1690
+ "button",
1691
+ {
1692
+ ref: plusButtonRef,
1693
+ type: "button",
1694
+ className: "pactor-chat-icon-button",
1695
+ title: "\u66F4\u591A\u529F\u80FD",
1696
+ "aria-label": "\u66F4\u591A\u529F\u80FD",
1697
+ onClick: () => {
1698
+ const rect = plusButtonRef.current.getBoundingClientRect();
1699
+ if (onPlusMenu) {
1700
+ onPlusMenu(rect);
1701
+ } else {
1702
+ setPlusAnchor(rect);
1703
+ }
1704
+ },
1705
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Plus, { size: 14 })
1706
+ }
1707
+ ),
1708
+ onAttachFiles && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1709
+ "button",
1710
+ {
1711
+ type: "button",
1712
+ className: "pactor-chat-icon-button",
1713
+ title: "\u6DFB\u52A0\u9644\u4EF6",
1714
+ "aria-label": "\u6DFB\u52A0\u9644\u4EF6",
1715
+ onClick: () => fileInputRef.current?.click(),
1716
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Paperclip, { size: 14 })
1717
+ }
1718
+ ),
1719
+ modelChipEnabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1720
+ ModelChip,
1721
+ {
1722
+ models,
1723
+ model,
1724
+ onModelChange
1725
+ }
1726
+ ),
1727
+ toolbarEnd
1728
+ ] }),
1729
+ onAttachFiles && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1730
+ "input",
1731
+ {
1732
+ ref: fileInputRef,
1733
+ type: "file",
1734
+ multiple: true,
1735
+ hidden: true,
1736
+ onChange: (event) => {
1737
+ const files = event.target.files ? Array.from(event.target.files) : [];
1738
+ if (files.length > 0) {
1739
+ onAttachFiles(files);
1740
+ }
1741
+ event.target.value = "";
1742
+ }
1743
+ }
1744
+ ),
1745
+ builtinMentionEnabled && mentionState && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1746
+ AnchoredPopover,
1747
+ {
1748
+ anchorRect: mentionState.anchorRect,
1749
+ onClose: () => setMentionState(null),
1750
+ role: "listbox",
1751
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1752
+ ContextSourceList,
1753
+ {
1754
+ sources: mentionSources,
1755
+ query: mentionState.query,
1756
+ callService,
1757
+ cache: sourceCacheRef.current,
1758
+ onSelect: handleSelectContext
1759
+ }
1760
+ )
1761
+ }
1762
+ ),
1763
+ builtinPlusEnabled && plusAnchor && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1764
+ AnchoredPopover,
1765
+ {
1766
+ anchorRect: plusAnchor,
1767
+ onClose: () => setPlusAnchor(null),
1768
+ role: "listbox",
1769
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1770
+ ContextSourceList,
1771
+ {
1772
+ sources: plusSources,
1773
+ query: "",
1774
+ callService,
1775
+ cache: sourceCacheRef.current,
1776
+ onSelect: handleSelectContext
1777
+ }
1778
+ )
1779
+ }
1780
+ )
1781
+ ]
1782
+ }
1783
+ );
1784
+ }
1785
+
1786
+ // src/view/EmptyState.tsx
1787
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1788
+ var DEFAULT_CHAT_GREETING = "\u4F60\u597D\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u4F60\uFF1F";
1789
+ function suggestionLabel(suggestion) {
1790
+ return typeof suggestion === "string" ? suggestion : suggestion.label;
1791
+ }
1792
+ function suggestionPrompt(suggestion) {
1793
+ return typeof suggestion === "string" ? suggestion : suggestion.prompt ?? suggestion.label;
1794
+ }
1795
+ function EmptyState({
1796
+ greeting = DEFAULT_CHAT_GREETING,
1797
+ subtitle,
1798
+ suggestions,
1799
+ onSuggestionClick
1800
+ }) {
1801
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "pactor-chat-empty", children: [
1802
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-empty__greeting", children: greeting }),
1803
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-empty__subtitle", children: subtitle }),
1804
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "pactor-chat-empty__suggestions", children: suggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1805
+ "button",
1806
+ {
1807
+ type: "button",
1808
+ className: "pactor-chat-empty__suggestion",
1809
+ onClick: () => onSuggestionClick?.(suggestionPrompt(suggestion)),
1810
+ children: suggestionLabel(suggestion)
1811
+ },
1812
+ suggestionLabel(suggestion)
1813
+ )) })
1814
+ ] });
1815
+ }
1816
+
1817
+ // src/view/MessageBubble.tsx
1818
+ var import_react12 = require("react");
1819
+
1820
+ // src/view/ActivityTrace.tsx
1821
+ var import_lucide_react6 = require("lucide-react");
1822
+ var import_markdown3 = require("@pactor-app/markdown");
1823
+ var import_react10 = require("react");
1824
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1825
+ function usePinnedCollapse(autoOpen) {
1826
+ const [state, setState] = (0, import_react10.useState)("auto");
1827
+ const open = state === "auto" ? autoOpen : state === "pinned-open";
1828
+ const toggle = () => {
1829
+ setState(open ? "pinned-closed" : "pinned-open");
1830
+ };
1831
+ return { open, toggle };
1832
+ }
1833
+ var STREAMING_STATUS_TEXT = {
1834
+ reasoning: "\u6B63\u5728\u601D\u8003\u2026",
1835
+ tool: "\u6B63\u5728\u8C03\u7528\u5DE5\u5177\u2026",
1836
+ responding: "\u6B63\u5728\u751F\u6210\u56DE\u590D\u2026"
1837
+ };
1838
+ function statusText(message, mode, toolCount) {
1839
+ if (message.status === "pending" || message.status === "streaming") {
1840
+ return mode ? STREAMING_STATUS_TEXT[mode] : "\u6B63\u5728\u54CD\u5E94\u2026";
1841
+ }
1842
+ if (message.status === "error") {
1843
+ return "\u51FA\u9519\u4E86";
1844
+ }
1845
+ if (message.status === "cancelled") {
1846
+ return "\u5DF2\u505C\u6B62";
1847
+ }
1848
+ return toolCount > 0 ? `\u5DF2\u5B8C\u6210 \xB7 ${toolCount} \u4E2A\u5DE5\u5177\u8C03\u7528` : "\u5DF2\u5B8C\u6210";
1849
+ }
1850
+ function useElapsedSeconds(startedAt, active) {
1851
+ const compute = () => Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1852
+ const [elapsed, setElapsed] = (0, import_react10.useState)(compute);
1853
+ (0, import_react10.useEffect)(() => {
1854
+ if (!active) {
1855
+ setElapsed(compute());
1856
+ return;
1857
+ }
1858
+ const tick = () => setElapsed(compute());
1859
+ tick();
1860
+ const id = setInterval(tick, 1e3);
1861
+ return () => clearInterval(id);
1862
+ }, [active, startedAt]);
1863
+ return elapsed;
1864
+ }
1865
+ function TraceStatusIcon({ status }) {
1866
+ if (status === "running") {
1867
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Loader2, { size: 13, className: "pactor-chat-spin" });
1868
+ }
1869
+ if (status === "success") {
1870
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.CheckCircle2, { size: 13 });
1871
+ }
1872
+ if (status === "failed") {
1873
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.XCircle, { size: 13 });
1874
+ }
1875
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Clock, { size: 13 });
1876
+ }
1877
+ function summarizeArgs(args) {
1878
+ if (args === void 0) {
1879
+ return "";
1880
+ }
1881
+ if (typeof args === "string") {
1882
+ return args;
1883
+ }
1884
+ try {
1885
+ return JSON.stringify(args) ?? "";
1886
+ } catch {
1887
+ return String(args);
1888
+ }
1889
+ }
1890
+ function formatDetail(value) {
1891
+ if (value === void 0) {
1892
+ return "\uFF08\u65E0\u8FD4\u56DE\uFF09";
1893
+ }
1894
+ if (typeof value === "string") {
1895
+ return value;
1896
+ }
1897
+ try {
1898
+ return JSON.stringify(value, null, 2);
1899
+ } catch {
1900
+ return String(value);
1901
+ }
1902
+ }
1903
+ function TraceRow({ entry }) {
1904
+ const [open, setOpen] = (0, import_react10.useState)(false);
1905
+ const summary = summarizeArgs(entry.args);
1906
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1907
+ "div",
1908
+ {
1909
+ className: "pactor-chat-trace__entry",
1910
+ "data-status": entry.status,
1911
+ "data-open": open,
1912
+ children: [
1913
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1914
+ "button",
1915
+ {
1916
+ type: "button",
1917
+ className: "pactor-chat-trace__entry-header",
1918
+ "aria-expanded": open,
1919
+ onClick: () => setOpen((value) => !value),
1920
+ children: [
1921
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.ChevronRight, { size: 12 }) }),
1922
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TraceStatusIcon, { status: entry.status }) }),
1923
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__entry-title", children: entry.title ?? entry.name }),
1924
+ summary && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__entry-args", children: summary })
1925
+ ]
1926
+ }
1927
+ ),
1928
+ open && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pactor-chat-trace__entry-body", children: [
1929
+ entry.args !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { className: "pactor-chat-trace__entry-detail", children: formatDetail(entry.args) }),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { className: "pactor-chat-trace__entry-detail", children: entry.status === "failed" ? entry.error ?? "\u6267\u884C\u5931\u8D25" : formatDetail(entry.result) })
1931
+ ] })
1932
+ ]
1933
+ }
1934
+ );
1935
+ }
1936
+ function ThinkingCard({
1937
+ part,
1938
+ answerPhase
1939
+ }) {
1940
+ const partStreaming = part.status === "streaming";
1941
+ const { open, toggle } = usePinnedCollapse(partStreaming && !answerPhase);
1942
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1943
+ "div",
1944
+ {
1945
+ className: "pactor-chat-thinking",
1946
+ "data-open": open,
1947
+ "data-streaming": partStreaming,
1948
+ children: [
1949
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1950
+ "button",
1951
+ {
1952
+ type: "button",
1953
+ className: "pactor-chat-thinking__header",
1954
+ "aria-expanded": open,
1955
+ onClick: toggle,
1956
+ children: [
1957
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.ChevronRight, { size: 12 }) }),
1958
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-thinking__icon", children: partStreaming ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Brain, { size: 13 }) }),
1959
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "\u601D\u8003\u8FC7\u7A0B" })
1960
+ ]
1961
+ }
1962
+ ),
1963
+ open && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-thinking__body", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_markdown3.MarkdownRenderer, { content: part.content }) })
1964
+ ]
1965
+ }
1966
+ );
1967
+ }
1968
+ function ActivityTrace({ message }) {
1969
+ const mode = detectStreamingMode(message);
1970
+ const streaming = message.status === "pending" || message.status === "streaming";
1971
+ const entries = pairToolTraces(message.parts);
1972
+ const thinkingParts = message.parts.filter(
1973
+ (part) => part.type === "thinking"
1974
+ );
1975
+ const answerPhase = mode === "responding" || !streaming;
1976
+ const { open, toggle } = usePinnedCollapse(!answerPhase);
1977
+ const mountTimeRef = (0, import_react10.useRef)(0);
1978
+ if (mountTimeRef.current === 0) {
1979
+ mountTimeRef.current = Date.now();
1980
+ }
1981
+ const elapsed = useElapsedSeconds(
1982
+ message.createdAt ?? mountTimeRef.current,
1983
+ streaming
1984
+ );
1985
+ const header = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1986
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__status-icon", children: streaming ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.Loader2, { size: 13, className: "pactor-chat-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.CheckCircle2, { size: 13 }) }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1988
+ "span",
1989
+ {
1990
+ className: `pactor-chat-trace__status-text${streaming ? " pactor-chat-trace__status-text--breathing" : ""}`,
1991
+ children: statusText(message, mode, entries.length)
1992
+ }
1993
+ ),
1994
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "pactor-chat-trace__elapsed", children: [
1995
+ elapsed,
1996
+ "s"
1997
+ ] }),
1998
+ entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react6.ChevronRight, { size: 12 }) })
1999
+ ] });
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pactor-chat-trace", "data-open": open, children: [
2001
+ entries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2002
+ "button",
2003
+ {
2004
+ type: "button",
2005
+ className: "pactor-chat-trace__header",
2006
+ "aria-expanded": open,
2007
+ onClick: toggle,
2008
+ children: header
2009
+ }
2010
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-trace__header", children: header }),
2011
+ thinkingParts.map((part, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2012
+ ThinkingCard,
2013
+ {
2014
+ part,
2015
+ answerPhase
2016
+ },
2017
+ part.id ?? index
2018
+ )),
2019
+ open && entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pactor-chat-trace__entries", children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TraceRow, { entry }, entry.callIndex)) })
2020
+ ] });
2021
+ }
2022
+
2023
+ // src/view/MessageActions.tsx
2024
+ var import_lucide_react7 = require("lucide-react");
2025
+ var import_react11 = require("react");
2026
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2027
+ var MESSAGE_ACTION_ICONS = {
2028
+ copy: import_lucide_react7.Copy,
2029
+ check: import_lucide_react7.Check,
2030
+ "rotate-ccw": import_lucide_react7.RotateCcw,
2031
+ pencil: import_lucide_react7.Pencil,
2032
+ quote: import_lucide_react7.Quote,
2033
+ "thumbs-up": import_lucide_react7.ThumbsUp,
2034
+ "thumbs-down": import_lucide_react7.ThumbsDown,
2035
+ share: import_lucide_react7.Share2,
2036
+ bookmark: import_lucide_react7.Bookmark
2037
+ };
2038
+ var BUILTIN_ACTION_DEFAULTS = {
2039
+ copy: {
2040
+ label: "\u590D\u5236",
2041
+ icon: "copy",
2042
+ roles: ["assistant"],
2043
+ visibleWhen: "completed"
2044
+ },
2045
+ regenerate: {
2046
+ label: "\u91CD\u65B0\u751F\u6210",
2047
+ icon: "rotate-ccw",
2048
+ roles: ["assistant"],
2049
+ visibleWhen: "completed"
2050
+ },
2051
+ retry: {
2052
+ label: "\u91CD\u8BD5",
2053
+ icon: "rotate-ccw",
2054
+ roles: ["assistant"],
2055
+ visibleWhen: "error"
2056
+ },
2057
+ edit: {
2058
+ label: "\u7F16\u8F91",
2059
+ icon: "pencil",
2060
+ roles: ["user"],
2061
+ visibleWhen: "completed"
2062
+ }
2063
+ };
2064
+ var DEFAULT_ACTIONS = [
2065
+ "copy",
2066
+ "regenerate",
2067
+ "retry",
2068
+ "edit"
2069
+ ];
2070
+ function resolveMessageActions(actions) {
2071
+ return (actions ?? DEFAULT_ACTIONS).map((item) => {
2072
+ const config = typeof item === "string" ? { id: item, builtin: item } : item;
2073
+ const defaults = config.builtin ? BUILTIN_ACTION_DEFAULTS[config.builtin] : void 0;
2074
+ return {
2075
+ id: config.id,
2076
+ builtin: config.builtin,
2077
+ label: config.label ?? defaults?.label ?? config.id,
2078
+ icon: config.icon ?? defaults?.icon,
2079
+ roles: config.roles ?? defaults?.roles ?? ["assistant"],
2080
+ visibleWhen: config.visibleWhen ?? defaults?.visibleWhen ?? "completed",
2081
+ service: config.service
2082
+ };
2083
+ });
2084
+ }
2085
+ function matchesStatus(visibleWhen, status) {
2086
+ if (visibleWhen === "always") {
2087
+ return true;
2088
+ }
2089
+ if (visibleWhen === "error") {
2090
+ return status === "error";
2091
+ }
2092
+ return status === "completed" || status === "cancelled";
2093
+ }
2094
+ function collectMessageText(message) {
2095
+ return message.parts.filter(
2096
+ (part) => part.type === "text" || part.type === "markdown"
2097
+ ).map((part) => part.content).join("\n");
2098
+ }
2099
+ function MessageActions({
2100
+ message,
2101
+ actions,
2102
+ showRegenerate = false,
2103
+ onRegenerate,
2104
+ onEdit,
2105
+ onRetry,
2106
+ forceVisible = false,
2107
+ callService,
2108
+ onMessageAction
2109
+ }) {
2110
+ const [copied, setCopied] = (0, import_react11.useState)(false);
2111
+ const copy = async () => {
2112
+ try {
2113
+ await navigator.clipboard.writeText(collectMessageText(message));
2114
+ setCopied(true);
2115
+ setTimeout(() => setCopied(false), 1500);
2116
+ } catch {
2117
+ }
2118
+ };
2119
+ const visibleActions = resolveMessageActions(actions).filter((action) => {
2120
+ if (!action.roles.includes(message.role)) {
2121
+ return false;
2122
+ }
2123
+ if (!matchesStatus(action.visibleWhen, message.status)) {
2124
+ return false;
2125
+ }
2126
+ switch (action.builtin) {
2127
+ case "regenerate":
2128
+ return showRegenerate && onRegenerate !== void 0;
2129
+ case "retry":
2130
+ return onRetry !== void 0;
2131
+ case "edit":
2132
+ return onEdit !== void 0;
2133
+ default:
2134
+ return true;
2135
+ }
2136
+ });
2137
+ if (visibleActions.length === 0) {
2138
+ return null;
2139
+ }
2140
+ const dispatch = (action) => {
2141
+ switch (action.builtin) {
2142
+ case "copy":
2143
+ void copy();
2144
+ break;
2145
+ case "regenerate":
2146
+ onRegenerate?.();
2147
+ break;
2148
+ case "retry":
2149
+ onRetry?.();
2150
+ break;
2151
+ case "edit":
2152
+ onEdit?.();
2153
+ break;
2154
+ default:
2155
+ if (action.service) {
2156
+ if (callService) {
2157
+ void callService(action.service, {
2158
+ actionId: action.id,
2159
+ message
2160
+ });
2161
+ } else {
2162
+ console.warn(
2163
+ `[@pactor-app/chat] \u6D88\u606F\u52A8\u4F5C\u300C${action.id}\u300D\u914D\u7F6E\u4E86 service\u300C${action.service}\u300D\u4F46\u672A\u6CE8\u5165 callService\uFF0C\u5DF2\u8DF3\u8FC7\u6D3E\u53D1`
2164
+ );
2165
+ }
2166
+ }
2167
+ }
2168
+ onMessageAction?.(action.id, message);
2169
+ };
2170
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2171
+ "div",
2172
+ {
2173
+ className: `pactor-chat-message__actions${forceVisible ? " pactor-chat-message__actions--visible" : ""}`,
2174
+ children: visibleActions.map((action) => {
2175
+ const copiedFeedback = action.builtin === "copy" && copied;
2176
+ const label = copiedFeedback ? "\u5DF2\u590D\u5236" : action.label;
2177
+ const iconName = copiedFeedback ? "check" : action.icon;
2178
+ const Icon = iconName !== void 0 ? MESSAGE_ACTION_ICONS[iconName] : void 0;
2179
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2180
+ "button",
2181
+ {
2182
+ type: "button",
2183
+ className: "pactor-chat-icon-button",
2184
+ title: label,
2185
+ "aria-label": label,
2186
+ "data-message-action": action.id,
2187
+ onClick: () => dispatch(action),
2188
+ children: Icon ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react7.HelpCircle, { size: 14, "data-icon-placeholder": "true" })
2189
+ },
2190
+ action.id
2191
+ );
2192
+ })
2193
+ }
2194
+ );
2195
+ }
2196
+
2197
+ // src/view/MessageBubble.tsx
2198
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2199
+ function MessageEditForm({
2200
+ initialText,
2201
+ onConfirm,
2202
+ onCancel
2203
+ }) {
2204
+ const [text, setText] = (0, import_react12.useState)(initialText);
2205
+ const confirm = () => {
2206
+ const value = text.trim();
2207
+ if (!value) {
2208
+ return;
2209
+ }
2210
+ onConfirm(value);
2211
+ };
2212
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "pactor-chat-message__edit", children: [
2213
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2214
+ "textarea",
2215
+ {
2216
+ className: "pactor-chat-message__edit-input",
2217
+ "aria-label": "\u7F16\u8F91\u6D88\u606F",
2218
+ rows: 3,
2219
+ value: text,
2220
+ onChange: (event) => setText(event.target.value),
2221
+ onKeyDown: (event) => {
2222
+ if (event.key === "Enter" && !event.shiftKey) {
2223
+ event.preventDefault();
2224
+ confirm();
2225
+ } else if (event.key === "Escape") {
2226
+ onCancel();
2227
+ }
2228
+ }
2229
+ }
2230
+ ),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "pactor-chat-message__edit-actions", children: [
2232
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2233
+ "button",
2234
+ {
2235
+ type: "button",
2236
+ className: "pactor-chat-message__edit-button pactor-chat-message__edit-button--confirm",
2237
+ "aria-label": "\u786E\u8BA4\u53D1\u9001",
2238
+ onClick: confirm,
2239
+ children: "\u53D1\u9001"
2240
+ }
2241
+ ),
2242
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2243
+ "button",
2244
+ {
2245
+ type: "button",
2246
+ className: "pactor-chat-message__edit-button",
2247
+ "aria-label": "\u53D6\u6D88",
2248
+ onClick: onCancel,
2249
+ children: "\u53D6\u6D88"
2250
+ }
2251
+ )
2252
+ ] })
2253
+ ] });
2254
+ }
2255
+ function MessageBubble({
2256
+ message,
2257
+ runtime,
2258
+ scope,
2259
+ showRegenerate = false,
2260
+ onRegenerate,
2261
+ onRetry,
2262
+ onEditResend,
2263
+ messageActions,
2264
+ callService,
2265
+ onMessageAction,
2266
+ onOpenArtifact
2267
+ }) {
2268
+ const [editing, setEditing] = (0, import_react12.useState)(false);
2269
+ const bubbleScope = {
2270
+ conversation: scope?.conversation ?? {},
2271
+ message: { id: message.id },
2272
+ params: scope?.params
2273
+ };
2274
+ const streaming = message.status === "streaming";
2275
+ const live = message.status === "pending" || message.status === "streaming";
2276
+ const wasLiveRef = (0, import_react12.useRef)(live);
2277
+ if (live) {
2278
+ wasLiveRef.current = true;
2279
+ }
2280
+ const showTrace = message.role === "assistant" && (wasLiveRef.current || message.parts.some(
2281
+ (part) => part.type === "thinking" || part.type === "tool-call" || part.type === "tool-result"
2282
+ ));
2283
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2284
+ "div",
2285
+ {
2286
+ className: `pactor-chat-message pactor-chat-message--${message.role}`,
2287
+ "data-message-id": message.id,
2288
+ "data-message-status": message.status,
2289
+ children: [
2290
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2291
+ MessageEditForm,
2292
+ {
2293
+ initialText: collectMessageText(message),
2294
+ onConfirm: (text) => {
2295
+ setEditing(false);
2296
+ onEditResend?.(text);
2297
+ },
2298
+ onCancel: () => setEditing(false)
2299
+ }
2300
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "pactor-chat-message__body", children: [
2301
+ showTrace && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ActivityTrace, { message }),
2302
+ message.parts.length === 0 && message.status === "pending" && message.role !== "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pactor-chat-message__pending", children: "\u2026" }) : message.parts.map((part, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2303
+ PartRenderer,
2304
+ {
2305
+ part,
2306
+ runtime,
2307
+ scope: bubbleScope,
2308
+ streaming,
2309
+ onOpenArtifact
2310
+ },
2311
+ part.id ?? index
2312
+ ))
2313
+ ] }),
2314
+ !editing && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2315
+ MessageActions,
2316
+ {
2317
+ message,
2318
+ actions: messageActions,
2319
+ showRegenerate,
2320
+ onRegenerate,
2321
+ onRetry,
2322
+ onEdit: onEditResend ? () => setEditing(true) : void 0,
2323
+ forceVisible: message.status === "error",
2324
+ callService,
2325
+ onMessageAction
2326
+ }
2327
+ )
2328
+ ]
2329
+ }
2330
+ );
2331
+ }
2332
+
2333
+ // src/view/ChatView.tsx
2334
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2335
+ function lastContentLength(messages) {
2336
+ const last = messages[messages.length - 1];
2337
+ if (!last) {
2338
+ return 0;
2339
+ }
2340
+ return last.parts.reduce(
2341
+ (total, part) => total + ("content" in part ? String(part.content).length : 0),
2342
+ 0
2343
+ );
2344
+ }
2345
+ function ChatView({
2346
+ store,
2347
+ transport,
2348
+ onSend,
2349
+ safeRegistry,
2350
+ capabilities,
2351
+ permission,
2352
+ confirm,
2353
+ conversationId,
2354
+ user,
2355
+ params,
2356
+ greeting,
2357
+ subtitle,
2358
+ suggestions,
2359
+ className,
2360
+ onArtifact,
2361
+ onOpenArtifact,
2362
+ attachments,
2363
+ onAttachmentsChange,
2364
+ onAttachFiles,
2365
+ onAttachmentClick,
2366
+ toolbarStart,
2367
+ toolbarEnd,
2368
+ onMentionTrigger,
2369
+ onPlusMenu,
2370
+ models,
2371
+ model,
2372
+ onModelChange,
2373
+ modelPlacement = "composer",
2374
+ contexts,
2375
+ onContextsChange,
2376
+ callService,
2377
+ messageActions,
2378
+ onMessageAction
2379
+ }) {
2380
+ const messages = (0, import_react13.useSyncExternalStore)(
2381
+ (listener) => store.subscribe(listener),
2382
+ () => store.getMessages()
2383
+ );
2384
+ const isStreaming = messages.some(
2385
+ (message) => message.status === "pending" || message.status === "streaming"
2386
+ );
2387
+ const runtime = (0, import_react13.useMemo)(() => {
2388
+ if (!safeRegistry) {
2389
+ return void 0;
2390
+ }
2391
+ const components = new import_core3.Registry("ChatComponentRegistry");
2392
+ components.register(CHAT_DENIED_TYPE, ChatDenied);
2393
+ for (const name of safeRegistry.list()) {
2394
+ components.register(name, safeRegistry.get(name));
2395
+ }
2396
+ return createChatRuntime({
2397
+ safeRegistry: components,
2398
+ capabilities,
2399
+ permission,
2400
+ confirm,
2401
+ user
2402
+ });
2403
+ }, [safeRegistry, capabilities, permission, confirm, user]);
2404
+ const scope = (0, import_react13.useMemo)(
2405
+ () => ({ conversation: { id: conversationId }, params }),
2406
+ [conversationId, params]
2407
+ );
2408
+ const notifiedArtifactsRef = (0, import_react13.useRef)(/* @__PURE__ */ new Set());
2409
+ (0, import_react13.useEffect)(() => {
2410
+ if (!onArtifact) {
2411
+ return;
2412
+ }
2413
+ for (const message of messages) {
2414
+ if (message.role !== "assistant") {
2415
+ continue;
2416
+ }
2417
+ message.parts.forEach((part, index) => {
2418
+ if (part.type === "artifact" && (part.status ?? "ready") === "ready") {
2419
+ const key = `${message.id}:${part.id ?? index}`;
2420
+ if (!notifiedArtifactsRef.current.has(key)) {
2421
+ notifiedArtifactsRef.current.add(key);
2422
+ onArtifact(part.artifact);
2423
+ }
2424
+ }
2425
+ });
2426
+ }
2427
+ }, [messages, onArtifact]);
2428
+ const abortRef = (0, import_react13.useRef)(null);
2429
+ const { containerRef, isFollowing, scrollToBottom } = useChatAutoScroll({
2430
+ messageCount: messages.length,
2431
+ lastContentLength: lastContentLength(messages),
2432
+ isStreaming
2433
+ });
2434
+ const { currentModel, handleModelChange } = useChatModel({
2435
+ models,
2436
+ model,
2437
+ onModelChange
2438
+ });
2439
+ const [contextChips, setContextChips] = (0, import_react13.useState)([]);
2440
+ const handleContextChipsChange = (chips) => {
2441
+ setContextChips(chips);
2442
+ onContextsChange?.(chips);
2443
+ };
2444
+ const handleSend = (text) => {
2445
+ onSend?.(text);
2446
+ if (transport) {
2447
+ const controller = new AbortController();
2448
+ abortRef.current = controller;
2449
+ void runChatTurn(
2450
+ store,
2451
+ transport,
2452
+ {
2453
+ text,
2454
+ conversationId,
2455
+ model: currentModel,
2456
+ contexts: contextChips.length > 0 ? contextChips : void 0
2457
+ },
2458
+ {
2459
+ signal: controller.signal
2460
+ }
2461
+ );
2462
+ }
2463
+ if (contextChips.length > 0) {
2464
+ handleContextChipsChange([]);
2465
+ }
2466
+ };
2467
+ const handleStop = () => {
2468
+ abortRef.current?.abort();
2469
+ abortRef.current = null;
2470
+ };
2471
+ const handleRegenerate = () => {
2472
+ if (!transport) {
2473
+ return;
2474
+ }
2475
+ handleStop();
2476
+ const controller = new AbortController();
2477
+ abortRef.current = controller;
2478
+ void runRegenerateTurn(store, transport, { signal: controller.signal });
2479
+ };
2480
+ const handleEditResend = (messageId, text) => {
2481
+ handleStop();
2482
+ store.removeMessagesFrom(messageId);
2483
+ handleSend(text);
2484
+ };
2485
+ const lastMessage = messages[messages.length - 1];
2486
+ const regenerableMessageId = lastMessage?.role === "assistant" && lastMessage.status === "completed" ? lastMessage.id : void 0;
2487
+ const isEmpty = messages.length === 0;
2488
+ const composerNode = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2489
+ Composer,
2490
+ {
2491
+ onSend: handleSend,
2492
+ streaming: isStreaming,
2493
+ onStop: handleStop,
2494
+ attachments,
2495
+ onAttachmentsChange,
2496
+ onAttachFiles,
2497
+ onAttachmentClick,
2498
+ toolbarStart,
2499
+ toolbarEnd,
2500
+ onMentionTrigger,
2501
+ onPlusMenu,
2502
+ models: modelPlacement === "header" ? void 0 : models,
2503
+ model: currentModel,
2504
+ onModelChange: handleModelChange,
2505
+ contextSources: contexts,
2506
+ contextChips,
2507
+ onContextChipsChange: handleContextChipsChange,
2508
+ callService
2509
+ }
2510
+ );
2511
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2512
+ "div",
2513
+ {
2514
+ className: `pactor-chat-view${isEmpty ? " pactor-chat-view--empty" : ""}${className ? ` ${className}` : ""}`,
2515
+ children: [
2516
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "pactor-chat-messages", ref: containerRef, children: [
2517
+ isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "pactor-chat-view__empty-stack", children: [
2518
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2519
+ EmptyState,
2520
+ {
2521
+ greeting,
2522
+ subtitle,
2523
+ suggestions,
2524
+ onSuggestionClick: handleSend
2525
+ }
2526
+ ),
2527
+ composerNode
2528
+ ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2529
+ MessageBubble,
2530
+ {
2531
+ message,
2532
+ runtime,
2533
+ scope,
2534
+ showRegenerate: message.id === regenerableMessageId,
2535
+ onRegenerate: transport ? handleRegenerate : void 0,
2536
+ onRetry: transport ? handleRegenerate : void 0,
2537
+ onEditResend: (text) => handleEditResend(message.id, text),
2538
+ messageActions,
2539
+ callService,
2540
+ onMessageAction,
2541
+ onOpenArtifact
2542
+ },
2543
+ message.id
2544
+ )),
2545
+ !isFollowing && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2546
+ "button",
2547
+ {
2548
+ type: "button",
2549
+ className: "pactor-chat-scroll-bottom",
2550
+ onClick: scrollToBottom,
2551
+ children: [
2552
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react8.ArrowDown, { size: 13 }),
2553
+ "\u56DE\u5230\u5E95\u90E8"
2554
+ ]
2555
+ }
2556
+ )
2557
+ ] }),
2558
+ !isEmpty && composerNode
2559
+ ]
2560
+ }
2561
+ );
2562
+ }
2563
+
2564
+ // src/view/WorkspaceHeader.tsx
2565
+ var import_lucide_react9 = require("lucide-react");
2566
+ var import_react14 = require("react");
2567
+
2568
+ // src/export-markdown.ts
2569
+ var ROLE_LABELS = {
2570
+ user: "\u7528\u6237",
2571
+ assistant: "\u52A9\u624B",
2572
+ system: "\u7CFB\u7EDF"
2573
+ };
2574
+ function serializeMessages(title, messages) {
2575
+ const lines = [`# ${title}`, ""];
2576
+ for (const message of messages) {
2577
+ const content = message.parts.filter(
2578
+ (part) => (part.type === "text" || part.type === "markdown") && "content" in part
2579
+ ).map((part) => part.content).join("\n\n").trim();
2580
+ if (!content) {
2581
+ continue;
2582
+ }
2583
+ lines.push(`## ${ROLE_LABELS[message.role]}`, "", content, "");
2584
+ }
2585
+ return lines.join("\n");
2586
+ }
2587
+ function downloadMessagesAsMarkdown(title, messages) {
2588
+ const markdown = serializeMessages(title, messages);
2589
+ const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" });
2590
+ const url = URL.createObjectURL(blob);
2591
+ const anchor = document.createElement("a");
2592
+ anchor.href = url;
2593
+ anchor.download = `${title || "chat"}.md`;
2594
+ anchor.click();
2595
+ URL.revokeObjectURL(url);
2596
+ }
2597
+
2598
+ // src/view/WorkspaceHeader.tsx
2599
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2600
+ function readCurrentTheme() {
2601
+ if (typeof document === "undefined") {
2602
+ return "light";
2603
+ }
2604
+ return document.documentElement.getAttribute("data-pactor-theme") === "dark" ? "dark" : "light";
2605
+ }
2606
+ function WorkspaceHeader({
2607
+ title,
2608
+ headerExtra,
2609
+ headerActions,
2610
+ editableTitle = false,
2611
+ headerStart,
2612
+ store,
2613
+ onExport,
2614
+ onThemeChange,
2615
+ onClear,
2616
+ onTitleChange
2617
+ }) {
2618
+ const hasActions = headerActions !== void 0 && headerActions.length > 0;
2619
+ const [overrideTitle, setOverrideTitle] = (0, import_react14.useState)(null);
2620
+ const [editing, setEditing] = (0, import_react14.useState)(false);
2621
+ const [draft, setDraft] = (0, import_react14.useState)("");
2622
+ const prevTitleRef = (0, import_react14.useRef)(title);
2623
+ if (prevTitleRef.current !== title) {
2624
+ prevTitleRef.current = title;
2625
+ if (overrideTitle !== null) {
2626
+ setOverrideTitle(null);
2627
+ }
2628
+ }
2629
+ const displayTitle = overrideTitle ?? title;
2630
+ const [theme, setTheme] = (0, import_react14.useState)(readCurrentTheme);
2631
+ const commitTitle = () => {
2632
+ setEditing(false);
2633
+ const next = draft.trim();
2634
+ if (next && next !== displayTitle) {
2635
+ setOverrideTitle(next);
2636
+ onTitleChange?.(next);
2637
+ }
2638
+ };
2639
+ const handleExport = () => {
2640
+ downloadMessagesAsMarkdown(displayTitle ?? "chat", store.getMessages());
2641
+ onExport?.();
2642
+ };
2643
+ const handleTheme = () => {
2644
+ const next = readCurrentTheme() === "dark" ? "light" : "dark";
2645
+ if (next === "dark") {
2646
+ document.documentElement.setAttribute("data-pactor-theme", "dark");
2647
+ } else {
2648
+ document.documentElement.removeAttribute("data-pactor-theme");
2649
+ }
2650
+ setTheme(next);
2651
+ onThemeChange?.(next);
2652
+ };
2653
+ const handleClear = () => {
2654
+ store.clear();
2655
+ onClear?.();
2656
+ };
2657
+ const actionButtons = {
2658
+ export: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2659
+ "button",
2660
+ {
2661
+ type: "button",
2662
+ className: "pactor-chat-icon-button",
2663
+ title: "\u5BFC\u51FA Markdown",
2664
+ "aria-label": "\u5BFC\u51FA Markdown",
2665
+ onClick: handleExport,
2666
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Download, { size: 15 })
2667
+ },
2668
+ "export"
2669
+ ),
2670
+ theme: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2671
+ "button",
2672
+ {
2673
+ type: "button",
2674
+ className: "pactor-chat-icon-button",
2675
+ title: theme === "dark" ? "\u5207\u6362\u4E3A\u6D45\u8272" : "\u5207\u6362\u4E3A\u6DF1\u8272",
2676
+ "aria-label": "\u5207\u6362\u660E\u6697\u4E3B\u9898",
2677
+ onClick: handleTheme,
2678
+ children: theme === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Sun, { size: 15 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Moon, { size: 15 })
2679
+ },
2680
+ "theme"
2681
+ ),
2682
+ clear: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2683
+ "button",
2684
+ {
2685
+ type: "button",
2686
+ className: "pactor-chat-icon-button",
2687
+ title: "\u6E05\u7A7A\u4F1A\u8BDD",
2688
+ "aria-label": "\u6E05\u7A7A\u4F1A\u8BDD",
2689
+ onClick: handleClear,
2690
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react9.Trash2, { size: 15 })
2691
+ },
2692
+ "clear"
2693
+ )
2694
+ };
2695
+ const titleNode = editableTitle ? editing ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2696
+ "input",
2697
+ {
2698
+ className: "pactor-chat-workspace__title-input",
2699
+ value: draft,
2700
+ autoFocus: true,
2701
+ "aria-label": "\u7F16\u8F91\u6807\u9898",
2702
+ onChange: (event) => setDraft(event.target.value),
2703
+ onBlur: commitTitle,
2704
+ onKeyDown: (event) => {
2705
+ if (event.key === "Enter") {
2706
+ commitTitle();
2707
+ } else if (event.key === "Escape") {
2708
+ setEditing(false);
2709
+ }
2710
+ }
2711
+ }
2712
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2713
+ "button",
2714
+ {
2715
+ type: "button",
2716
+ className: "pactor-chat-workspace__title pactor-chat-workspace__title--editable",
2717
+ title: "\u70B9\u51FB\u7F16\u8F91\u6807\u9898",
2718
+ onClick: () => {
2719
+ setDraft(displayTitle ?? "");
2720
+ setEditing(true);
2721
+ },
2722
+ children: displayTitle
2723
+ }
2724
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__title", children: title });
2725
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("header", { className: "pactor-chat-workspace__header", children: [
2726
+ headerStart !== void 0 ? (
2727
+ // header 位模型 chip(决策 D2):与 title 组成左端组,chip 在前
2728
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "pactor-chat-workspace__header-left", children: [
2729
+ headerStart,
2730
+ titleNode
2731
+ ] })
2732
+ ) : titleNode,
2733
+ hasActions ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "pactor-chat-workspace__header-right", children: [
2734
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__header-actions", children: headerActions.map((action) => actionButtons[action]) }),
2735
+ headerExtra !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2736
+ ] }) : headerExtra !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "pactor-chat-workspace__header-extra", children: headerExtra }) : null
2737
+ ] });
2738
+ }
2739
+
2740
+ // src/chat-composition.tsx
2741
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2742
+ function DslNodeSlotInner({ node }) {
2743
+ const { renderChildren } = (0, import_runtime4.useRenderer)();
2744
+ return renderChildren([node]);
2745
+ }
2746
+ var DslSlotGuard = class extends import_react15.Component {
2747
+ constructor() {
2748
+ super(...arguments);
2749
+ this.state = { failed: false };
2750
+ }
2751
+ static getDerivedStateFromError() {
2752
+ return { failed: true };
2753
+ }
2754
+ componentDidCatch() {
2755
+ console.warn(
2756
+ "[@pactor-app/chat] DslNode \u69FD\u4EC5\u5728\u9875\u9762\u8FD0\u884C\u65F6\uFF08DslRenderer\uFF09\u4E0A\u4E0B\u6587\u5185\u53EF\u6E32\u67D3\uFF0C\u5DF2\u8DF3\u8FC7"
2757
+ );
2758
+ }
2759
+ render() {
2760
+ return this.state.failed ? null : this.props.children;
2761
+ }
2762
+ };
2763
+ function DslNodeSlot({ node }) {
2764
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DslSlotGuard, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DslNodeSlotInner, { node }) });
2765
+ }
2766
+ function isDslNodeValue(value) {
2767
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(0, import_react15.isValidElement)(value) && typeof value.type === "string";
2768
+ }
2769
+ function resolveSlot(slot) {
2770
+ if (slot === void 0 || slot === null) {
2771
+ return void 0;
2772
+ }
2773
+ if (isDslNodeValue(slot)) {
2774
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DslNodeSlot, { node: slot });
2775
+ }
2776
+ return slot;
2777
+ }
2778
+ function ChatComposition({
2779
+ services,
2780
+ title,
2781
+ greeting,
2782
+ subtitle,
2783
+ suggestions,
2784
+ conversationId,
2785
+ params,
2786
+ showHeader,
2787
+ header,
2788
+ headerExtra,
2789
+ headerActions,
2790
+ editableTitle,
2791
+ onExport,
2792
+ onThemeChange,
2793
+ onClear,
2794
+ onTitleChange,
2795
+ models,
2796
+ model,
2797
+ onModelChange,
2798
+ modelPlacement = "composer",
2799
+ contexts,
2800
+ onContextsChange,
2801
+ messageActions,
2802
+ onMessageAction,
2803
+ sidebar,
2804
+ toolbarEnd,
2805
+ className,
2806
+ onSend,
2807
+ artifact,
2808
+ panelOpen,
2809
+ onPanelClose,
2810
+ inspector,
2811
+ onArtifact,
2812
+ onOpenArtifact
2813
+ }) {
2814
+ const storeRef = (0, import_react15.useRef)(null);
2815
+ if (storeRef.current === null) {
2816
+ storeRef.current = services.store ?? services.storeFactory?.() ?? new ConversationStore();
2817
+ }
2818
+ const store = storeRef.current;
2819
+ const artifactStoreRef = (0, import_react15.useRef)(null);
2820
+ if (artifactStoreRef.current === null) {
2821
+ artifactStoreRef.current = services.artifactStore ?? new ArtifactStore();
2822
+ }
2823
+ const artifactStore = artifactStoreRef.current;
2824
+ const panelControlled = panelOpen !== void 0 || artifact !== void 0;
2825
+ const storeArtifact = (0, import_react15.useSyncExternalStore)(
2826
+ (listener) => artifactStore.subscribe(listener),
2827
+ () => artifactStore.get()
2828
+ );
2829
+ const effectiveArtifact = panelControlled ? artifact ?? null : storeArtifact;
2830
+ const effectivePanelOpen = panelControlled ? panelOpen ?? artifact != null : storeArtifact !== null;
2831
+ const handlePanelClose = () => {
2832
+ onPanelClose?.();
2833
+ if (!panelControlled) {
2834
+ artifactStore.close();
2835
+ }
2836
+ };
2837
+ const handleOpenArtifact = (next) => {
2838
+ onOpenArtifact?.(next);
2839
+ if (!panelControlled) {
2840
+ artifactStore.open(next);
2841
+ }
2842
+ };
2843
+ const [attachments, setAttachments] = (0, import_react15.useState)([]);
2844
+ const handleAttachFiles = services.onAttachFiles ? (files) => {
2845
+ void Promise.resolve(services.onAttachFiles(files)).then(
2846
+ (created) => {
2847
+ if (created && created.length > 0) {
2848
+ setAttachments((prev) => [...prev, ...created]);
2849
+ }
2850
+ }
2851
+ );
2852
+ } : void 0;
2853
+ const handleSend = (text) => {
2854
+ services.onSend?.(text);
2855
+ onSend?.(text);
2856
+ setAttachments([]);
2857
+ };
2858
+ const { modelsEnabled, currentModel, handleModelChange } = useChatModel({
2859
+ models,
2860
+ model,
2861
+ onModelChange
2862
+ });
2863
+ const headerModelChip = modelPlacement === "header" && modelsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2864
+ ModelChip,
2865
+ {
2866
+ models,
2867
+ model: currentModel,
2868
+ onModelChange: handleModelChange
2869
+ }
2870
+ ) : void 0;
2871
+ const mergedToolbarEnd = services.toolbarEnd !== void 0 || toolbarEnd !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2872
+ services.toolbarEnd,
2873
+ toolbarEnd
2874
+ ] }) : void 0;
2875
+ const workspace = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2876
+ "div",
2877
+ {
2878
+ className: `pactor-chat-workspace${className ? ` ${className}` : ""}`,
2879
+ children: [
2880
+ showHeader ? header ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2881
+ WorkspaceHeader,
2882
+ {
2883
+ title,
2884
+ headerExtra,
2885
+ headerActions,
2886
+ editableTitle,
2887
+ headerStart: headerModelChip,
2888
+ store,
2889
+ onExport,
2890
+ onThemeChange,
2891
+ onClear,
2892
+ onTitleChange
2893
+ }
2894
+ ) : null,
2895
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2896
+ ChatView,
2897
+ {
2898
+ store,
2899
+ transport: services.transport,
2900
+ onSend: handleSend,
2901
+ safeRegistry: services.safeRegistry,
2902
+ capabilities: services.capabilities,
2903
+ permission: services.permission,
2904
+ confirm: services.confirm,
2905
+ conversationId,
2906
+ user: services.user,
2907
+ params,
2908
+ greeting,
2909
+ subtitle,
2910
+ suggestions,
2911
+ attachments,
2912
+ onAttachmentsChange: setAttachments,
2913
+ onAttachFiles: handleAttachFiles,
2914
+ onAttachmentClick: services.onAttachmentClick,
2915
+ toolbarStart: services.toolbarStart,
2916
+ toolbarEnd: mergedToolbarEnd,
2917
+ onMentionTrigger: services.onMentionTrigger,
2918
+ onPlusMenu: services.onPlusMenu,
2919
+ onArtifact,
2920
+ onOpenArtifact: handleOpenArtifact,
2921
+ models,
2922
+ model: currentModel,
2923
+ onModelChange: handleModelChange,
2924
+ modelPlacement,
2925
+ contexts,
2926
+ onContextsChange,
2927
+ callService: services.callService,
2928
+ messageActions,
2929
+ onMessageAction
2930
+ }
2931
+ )
2932
+ ]
2933
+ }
2934
+ );
2935
+ const main = sidebar ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AppShell, { sidebar, children: workspace }) : workspace;
2936
+ const panelFeatureUsed = artifact !== void 0 || panelOpen !== void 0 || inspector !== void 0 || onPanelClose !== void 0 || onArtifact !== void 0 || onOpenArtifact !== void 0 || services.artifactStore !== void 0;
2937
+ if (!panelFeatureUsed && !effectivePanelOpen) {
2938
+ return main;
2939
+ }
2940
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "pactor-chat-inspector-host", children: [
2941
+ main,
2942
+ effectivePanelOpen ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2943
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2944
+ "div",
2945
+ {
2946
+ className: "pactor-chat-inspector__scrim",
2947
+ "aria-hidden": true,
2948
+ onClick: handlePanelClose
2949
+ }
2950
+ ),
2951
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2952
+ ArtifactPanel,
2953
+ {
2954
+ artifact: effectiveArtifact,
2955
+ inspector,
2956
+ onClose: handlePanelClose,
2957
+ safeRegistry: services.safeRegistry,
2958
+ user: services.user
2959
+ }
2960
+ )
2961
+ ] }) : null
2962
+ ] });
2963
+ }
2964
+
2965
+ // src/chat-services.tsx
2966
+ var import_react16 = require("react");
2967
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2968
+ var ChatServicesContext = (0, import_react16.createContext)(null);
2969
+ function ChatServicesProvider({
2970
+ value,
2971
+ children
2972
+ }) {
2973
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatServicesContext.Provider, { value, children });
2974
+ }
2975
+ function useChatServices() {
2976
+ return (0, import_react16.useContext)(ChatServicesContext);
2977
+ }
2978
+
2979
+ // src/chat-workspace.tsx
2980
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2981
+ function createChatWorkspace(services) {
2982
+ function ChatWorkspace({
2983
+ title,
2984
+ greeting,
2985
+ subtitle,
2986
+ suggestions,
2987
+ conversationId,
2988
+ params,
2989
+ sidebar,
2990
+ toolbarEnd,
2991
+ artifact,
2992
+ panelOpen,
2993
+ onPanelClose,
2994
+ inspector,
2995
+ onArtifact,
2996
+ onOpenArtifact,
2997
+ headerActions,
2998
+ editableTitle,
2999
+ onExport,
3000
+ onThemeChange,
3001
+ onClear,
3002
+ onTitleChange,
3003
+ models,
3004
+ model,
3005
+ onModelChange,
3006
+ modelPlacement,
3007
+ contexts,
3008
+ onContextsChange,
3009
+ messageActions,
3010
+ onMessageAction,
3011
+ className
3012
+ }) {
3013
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatServicesProvider, { value: services, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3014
+ ChatComposition,
3015
+ {
3016
+ services,
3017
+ title,
3018
+ greeting,
3019
+ subtitle,
3020
+ suggestions,
3021
+ conversationId,
3022
+ params,
3023
+ className,
3024
+ showHeader: title !== void 0 || services.headerExtra !== void 0 || // header 位模型 chip(决策 D2):仅配置 models 也渲染极简头部
3025
+ modelPlacement === "header" && models !== void 0 && models.length > 0,
3026
+ headerExtra: services.headerExtra,
3027
+ sidebar: sidebar ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DslNodeSlot, { node: sidebar }) : void 0,
3028
+ toolbarEnd: toolbarEnd ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DslNodeSlot, { node: toolbarEnd }) : void 0,
3029
+ artifact,
3030
+ panelOpen,
3031
+ onPanelClose,
3032
+ inspector: inspector ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DslNodeSlot, { node: inspector }) : void 0,
3033
+ onArtifact,
3034
+ onOpenArtifact,
3035
+ headerActions,
3036
+ editableTitle,
3037
+ onExport,
3038
+ onThemeChange,
3039
+ onClear,
3040
+ onTitleChange,
3041
+ models,
3042
+ model,
3043
+ onModelChange,
3044
+ modelPlacement,
3045
+ contexts,
3046
+ onContextsChange,
3047
+ messageActions,
3048
+ onMessageAction
3049
+ }
3050
+ ) });
3051
+ }
3052
+ return ChatWorkspace;
3053
+ }
3054
+
3055
+ // src/view/Chat.tsx
3056
+ var import_lucide_react10 = require("lucide-react");
3057
+ var import_react17 = require("react");
3058
+
3059
+ // src/view/ChatShell.tsx
3060
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3061
+ function ChatShell({
3062
+ menu,
3063
+ activeMenuKey,
3064
+ brand,
3065
+ collapsible,
3066
+ sidebar,
3067
+ sidebarFooter,
3068
+ content,
3069
+ className,
3070
+ onMenuSelect
3071
+ }) {
3072
+ const sidebarContent = resolveSlot(sidebar) ?? (menu && menu.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3073
+ ChatSidebarMenu,
3074
+ {
3075
+ menu,
3076
+ activeMenuKey,
3077
+ onMenuSelect
3078
+ }
3079
+ ) : null);
3080
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3081
+ AppShell,
3082
+ {
3083
+ sidebar: sidebarContent,
3084
+ brand,
3085
+ collapsible,
3086
+ sidebarFooter: resolveSlot(sidebarFooter),
3087
+ className,
3088
+ children: resolveSlot(content)
3089
+ }
3090
+ );
3091
+ }
3092
+ var CHAT_SHELL_COMPONENT_TYPE = "ChatShell";
3093
+ function ChatShellDslAdapter({
3094
+ onMenuSelect,
3095
+ ...rest
3096
+ }) {
3097
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3098
+ ChatShell,
3099
+ {
3100
+ ...rest,
3101
+ onMenuSelect: onMenuSelect ? (key, item) => onMenuSelect({ key, item }) : void 0
3102
+ }
3103
+ );
3104
+ }
3105
+ function registerChatShellComponent(registry) {
3106
+ if (!registry.has(CHAT_SHELL_COMPONENT_TYPE)) {
3107
+ registry.register(CHAT_SHELL_COMPONENT_TYPE, ChatShellDslAdapter);
3108
+ }
3109
+ }
3110
+
3111
+ // src/view/Chat.tsx
3112
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3113
+ var MENU_ICONS = {
3114
+ plus: import_lucide_react10.Plus,
3115
+ chat: import_lucide_react10.MessageSquare,
3116
+ history: import_lucide_react10.History,
3117
+ settings: import_lucide_react10.Settings,
3118
+ star: import_lucide_react10.Star,
3119
+ search: import_lucide_react10.Search,
3120
+ user: import_lucide_react10.User,
3121
+ folder: import_lucide_react10.Folder
3122
+ };
3123
+ function ChatSidebarMenu({
3124
+ menu,
3125
+ activeMenuKey,
3126
+ onMenuSelect
3127
+ }) {
3128
+ const drawer = useSidebarDrawer();
3129
+ let lastGroup;
3130
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("nav", { className: "pactor-chat-menu", "aria-label": "\u4F1A\u8BDD\u5BFC\u822A", children: menu.map((item) => {
3131
+ const groupLabel = item.group !== void 0 && item.group !== lastGroup ? item.group : void 0;
3132
+ lastGroup = item.group;
3133
+ const Icon = item.icon !== void 0 ? MENU_ICONS[item.icon] : void 0;
3134
+ const active = item.key === activeMenuKey;
3135
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react17.Fragment, { children: [
3136
+ groupLabel !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "pactor-chat-menu__group", children: groupLabel }) : null,
3137
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3138
+ "button",
3139
+ {
3140
+ type: "button",
3141
+ className: `pactor-chat-menu__item${active ? " pactor-chat-menu__item--active" : ""}`,
3142
+ disabled: item.disabled === true,
3143
+ "aria-current": active ? "true" : void 0,
3144
+ title: drawer?.collapsed ? item.label : void 0,
3145
+ onClick: () => {
3146
+ if (item.disabled) {
3147
+ return;
3148
+ }
3149
+ onMenuSelect?.(item.key, item);
3150
+ drawer?.close();
3151
+ },
3152
+ children: [
3153
+ item.icon !== void 0 ? Icon ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__icon", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Icon, { size: 15, strokeWidth: 1.7 }) }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3154
+ "span",
3155
+ {
3156
+ className: "pactor-chat-menu__icon-placeholder",
3157
+ "aria-hidden": true
3158
+ }
3159
+ ) : null,
3160
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__label", children: item.label }),
3161
+ item.dot === true ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__dot", "aria-hidden": true }) : null,
3162
+ item.badge !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "pactor-chat-menu__badge", children: item.badge }) : null
3163
+ ]
3164
+ }
3165
+ )
3166
+ ] }, item.key);
3167
+ }) });
3168
+ }
3169
+ function Chat({
3170
+ title,
3171
+ greeting,
3172
+ subtitle,
3173
+ suggestions,
3174
+ conversationId,
3175
+ params,
3176
+ showHeader,
3177
+ menu,
3178
+ activeMenuKey,
3179
+ sidebar,
3180
+ headerExtra,
3181
+ header,
3182
+ headerActions,
3183
+ editableTitle,
3184
+ onExport,
3185
+ onThemeChange,
3186
+ onClear,
3187
+ onTitleChange,
3188
+ models,
3189
+ model,
3190
+ onModelChange,
3191
+ modelPlacement,
3192
+ contexts,
3193
+ onContextsChange,
3194
+ messageActions,
3195
+ onMessageAction,
3196
+ artifact,
3197
+ panelOpen,
3198
+ onPanelClose,
3199
+ inspector,
3200
+ onArtifact,
3201
+ onOpenArtifact,
3202
+ className,
3203
+ onSend,
3204
+ onMenuSelect
3205
+ }) {
3206
+ const services = useChatServices();
3207
+ (0, import_react17.useEffect)(() => {
3208
+ if (!services) {
3209
+ console.warn(
3210
+ "[@pactor-app/chat] Chat \u672A\u68C0\u6D4B\u5230 ChatServicesProvider\uFF1A\u8BF7\u5728\u5E94\u7528\u6839\u90E8\u4EE5 <ChatServicesProvider> \u6CE8\u5165 transport \u7B49\u670D\u52A1"
3211
+ );
3212
+ }
3213
+ }, [services]);
3214
+ if (!services) {
3215
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "pactor-chat-notice", role: "note", children: "\u672A\u914D\u7F6E\u804A\u5929\u670D\u52A1\uFF1A\u8BF7\u5728 Chat \u5916\u5C42\u4F7F\u7528 ChatServicesProvider \u6CE8\u5165 transport \u7B49\u670D\u52A1\u540E\u91CD\u8BD5\u3002" });
3216
+ }
3217
+ const sidebarSlot = resolveSlot(sidebar);
3218
+ const sidebarContent = sidebarSlot ?? (menu && menu.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3219
+ ChatSidebarMenu,
3220
+ {
3221
+ menu,
3222
+ activeMenuKey,
3223
+ onMenuSelect
3224
+ }
3225
+ ) : void 0);
3226
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3227
+ ChatComposition,
3228
+ {
3229
+ services,
3230
+ title,
3231
+ greeting,
3232
+ subtitle,
3233
+ suggestions,
3234
+ conversationId,
3235
+ params,
3236
+ className,
3237
+ showHeader: showHeader !== false,
3238
+ header: resolveSlot(header),
3239
+ headerExtra: resolveSlot(headerExtra) ?? services.headerExtra,
3240
+ headerActions,
3241
+ editableTitle,
3242
+ onExport,
3243
+ onThemeChange,
3244
+ onClear,
3245
+ onTitleChange,
3246
+ models,
3247
+ model,
3248
+ onModelChange,
3249
+ modelPlacement,
3250
+ contexts,
3251
+ onContextsChange,
3252
+ messageActions,
3253
+ onMessageAction,
3254
+ sidebar: sidebarContent,
3255
+ onSend,
3256
+ artifact,
3257
+ panelOpen,
3258
+ onPanelClose,
3259
+ inspector: resolveSlot(inspector),
3260
+ onArtifact,
3261
+ onOpenArtifact
3262
+ }
3263
+ );
3264
+ }
3265
+ var CHAT_COMPONENT_TYPE = "Chat";
3266
+ function ChatDslAdapter({
3267
+ onSend,
3268
+ onMenuSelect,
3269
+ onPanelClose,
3270
+ onArtifact,
3271
+ onOpenArtifact,
3272
+ onModelChange,
3273
+ onContextsChange,
3274
+ onMessageAction,
3275
+ onExport,
3276
+ onThemeChange,
3277
+ onClear,
3278
+ onTitleChange,
3279
+ ...rest
3280
+ }) {
3281
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3282
+ Chat,
3283
+ {
3284
+ ...rest,
3285
+ onSend: onSend ? (text) => onSend({ text }) : void 0,
3286
+ onMenuSelect: onMenuSelect ? (key, item) => onMenuSelect({ key, item }) : void 0,
3287
+ onPanelClose: onPanelClose ? () => onPanelClose({}) : void 0,
3288
+ onArtifact: onArtifact ? (artifact) => onArtifact({ artifact }) : void 0,
3289
+ onOpenArtifact: onOpenArtifact ? (artifact) => onOpenArtifact({ artifact }) : void 0,
3290
+ onModelChange: onModelChange ? (model) => onModelChange({ model }) : void 0,
3291
+ onContextsChange: onContextsChange ? (contexts) => onContextsChange({ contexts }) : void 0,
3292
+ onMessageAction: onMessageAction ? (action, message) => onMessageAction({ action, message }) : void 0,
3293
+ onExport: onExport ? () => onExport({}) : void 0,
3294
+ onThemeChange: onThemeChange ? (theme) => onThemeChange({ theme }) : void 0,
3295
+ onClear: onClear ? () => onClear({}) : void 0,
3296
+ onTitleChange: onTitleChange ? (title) => onTitleChange({ title }) : void 0
3297
+ }
3298
+ );
3299
+ }
3300
+ function registerChatComponent(registry) {
3301
+ if (!registry.has(CHAT_COMPONENT_TYPE)) {
3302
+ registry.register(CHAT_COMPONENT_TYPE, ChatDslAdapter);
3303
+ }
3304
+ registerChatShellComponent(registry);
3305
+ }
3306
+
3307
+ // src/view/ChatLayout.tsx
3308
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3309
+ function ChatLayout({ title, children }) {
3310
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "pactor-chat-layout", children: [
3311
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("header", { className: "pactor-chat-layout__header", children: title ?? "Chat" }),
3312
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("main", { className: "pactor-chat-layout__main", children })
3313
+ ] });
3314
+ }
3315
+ var CHAT_LAYOUT_TYPE = "chat";
3316
+ function registerChatLayout(layoutRegistry) {
3317
+ if (!layoutRegistry.has(CHAT_LAYOUT_TYPE)) {
3318
+ layoutRegistry.register(CHAT_LAYOUT_TYPE, ChatLayout);
3319
+ }
3320
+ }
3321
+ // Annotate the CommonJS export names for ESM import in node:
3322
+ 0 && (module.exports = {
3323
+ ActivityTrace,
3324
+ AppShell,
3325
+ ArtifactPanel,
3326
+ ArtifactStore,
3327
+ CHAT_COMPONENT_TYPE,
3328
+ CHAT_DENIED_TYPE,
3329
+ CHAT_LAYOUT_TYPE,
3330
+ CHAT_SHELL_COMPONENT_TYPE,
3331
+ Chat,
3332
+ ChatDenied,
3333
+ ChatLayout,
3334
+ ChatServicesProvider,
3335
+ ChatShell,
3336
+ ChatSidebarMenu,
3337
+ ChatView,
3338
+ Composer,
3339
+ ConversationStore,
3340
+ DEFAULT_CHAT_GREETING,
3341
+ EmptyState,
3342
+ MENU_ICONS,
3343
+ MESSAGE_ACTION_ICONS,
3344
+ MessageActions,
3345
+ MessageBubble,
3346
+ PartRenderer,
3347
+ applyChatEvent,
3348
+ buildChatScope,
3349
+ collectMessageText,
3350
+ createChatId,
3351
+ createChatRuntime,
3352
+ createChatWorkspace,
3353
+ createManualDriver,
3354
+ createSafeRegistry,
3355
+ detectStreamingMode,
3356
+ guardChatDsl,
3357
+ pairToolTraces,
3358
+ registerCapabilityAction,
3359
+ registerChatComponent,
3360
+ registerChatLayout,
3361
+ registerChatShellComponent,
3362
+ runChatTurn,
3363
+ runRegenerateTurn,
3364
+ useChatAutoScroll,
3365
+ useChatServices,
3366
+ useSidebarDrawer,
3367
+ useSmoothStreamText
3368
+ });