@pactor-app/chat 1.15.5 → 1.16.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/README.md +7 -3
- package/dist/index.cjs +504 -33
- package/dist/index.d.cts +152 -39
- package/dist/index.d.ts +152 -39
- package/dist/index.js +495 -33
- package/package.json +6 -6
- package/styles.css +193 -6
package/dist/index.js
CHANGED
|
@@ -118,35 +118,113 @@ var ConversationStore = class {
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
// src/artifact.ts
|
|
121
|
+
var ARTIFACT_IMAGE_EXTENSIONS = [
|
|
122
|
+
"png",
|
|
123
|
+
"jpg",
|
|
124
|
+
"jpeg",
|
|
125
|
+
"gif",
|
|
126
|
+
"svg",
|
|
127
|
+
"webp",
|
|
128
|
+
"avif",
|
|
129
|
+
"bmp",
|
|
130
|
+
"ico"
|
|
131
|
+
];
|
|
132
|
+
function resolveArtifactFileType(artifact) {
|
|
133
|
+
const declared = artifact.fileType?.trim().toLowerCase().replace(/^\./, "");
|
|
134
|
+
if (declared) {
|
|
135
|
+
return declared;
|
|
136
|
+
}
|
|
137
|
+
const match = /\.([A-Za-z0-9]+)$/.exec(artifact.title.trim());
|
|
138
|
+
return match ? match[1].toLowerCase() : null;
|
|
139
|
+
}
|
|
140
|
+
function isImageArtifact(artifact) {
|
|
141
|
+
const ext = resolveArtifactFileType(artifact);
|
|
142
|
+
return ext !== null && ARTIFACT_IMAGE_EXTENSIONS.includes(ext);
|
|
143
|
+
}
|
|
144
|
+
function collectArtifacts(messages) {
|
|
145
|
+
const order = [];
|
|
146
|
+
const byId = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const message of messages) {
|
|
148
|
+
for (const part of message.parts) {
|
|
149
|
+
if (part.type !== "artifact" || !part.artifact) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (part.status !== void 0 && part.status !== "ready") {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!byId.has(part.artifact.id)) {
|
|
156
|
+
order.push(part.artifact.id);
|
|
157
|
+
}
|
|
158
|
+
byId.set(part.artifact.id, part.artifact);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return order.map((id) => byId.get(id));
|
|
162
|
+
}
|
|
163
|
+
function mergeArtifacts(stream, opened) {
|
|
164
|
+
const openedById = new Map(opened.map((item) => [item.id, item]));
|
|
165
|
+
const merged = stream.map((item) => openedById.get(item.id) ?? item);
|
|
166
|
+
for (const item of opened) {
|
|
167
|
+
if (!stream.some((s) => s.id === item.id)) {
|
|
168
|
+
merged.push(item);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return merged;
|
|
172
|
+
}
|
|
121
173
|
var ArtifactStore = class {
|
|
122
174
|
constructor() {
|
|
123
|
-
this.
|
|
175
|
+
this.artifacts = [];
|
|
176
|
+
this.activeId = null;
|
|
124
177
|
this.listeners = /* @__PURE__ */ new Set();
|
|
125
178
|
}
|
|
126
|
-
/**
|
|
179
|
+
/** 当前激活产出物(未打开时为 null;引用在不变时保持稳定) */
|
|
127
180
|
get() {
|
|
128
|
-
|
|
181
|
+
if (this.activeId === null) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return this.artifacts.find((item) => item.id === this.activeId) ?? null;
|
|
129
185
|
}
|
|
130
|
-
/**
|
|
186
|
+
/** 全部产出物(插入序;数组不可变,引用稳定) */
|
|
187
|
+
list() {
|
|
188
|
+
return this.artifacts;
|
|
189
|
+
}
|
|
190
|
+
/** 打开(或按 id 替换)产出物并激活 */
|
|
131
191
|
open(artifact) {
|
|
132
|
-
this.
|
|
192
|
+
const index = this.artifacts.findIndex((item) => item.id === artifact.id);
|
|
193
|
+
this.artifacts = index === -1 ? [...this.artifacts, artifact] : this.artifacts.map((item, i) => i === index ? artifact : item);
|
|
194
|
+
this.activeId = artifact.id;
|
|
195
|
+
this.notify();
|
|
196
|
+
}
|
|
197
|
+
/** 切换激活项(未知 id 告警并忽略,不抛错) */
|
|
198
|
+
activate(id) {
|
|
199
|
+
if (this.activeId === id) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (!this.artifacts.some((item) => item.id === id)) {
|
|
203
|
+
console.warn("[@pactor-app/chat] unknown artifact id, activate ignored");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.activeId = id;
|
|
133
207
|
this.notify();
|
|
134
208
|
}
|
|
135
|
-
/**
|
|
209
|
+
/** 部分更新当前激活产出物(id 不可改);未 open 时告警并忽略 */
|
|
136
210
|
update(patch) {
|
|
137
|
-
|
|
211
|
+
const current = this.get();
|
|
212
|
+
if (!current) {
|
|
138
213
|
console.warn("[@pactor-app/chat] artifact not open, update ignored");
|
|
139
214
|
return;
|
|
140
215
|
}
|
|
141
|
-
|
|
216
|
+
const next = { ...current, ...patch, id: current.id };
|
|
217
|
+
this.artifacts = this.artifacts.map(
|
|
218
|
+
(item) => item.id === current.id ? next : item
|
|
219
|
+
);
|
|
142
220
|
this.notify();
|
|
143
221
|
}
|
|
144
|
-
/**
|
|
222
|
+
/** 关闭面板:清空激活项,列表保留(未打开时幂等,不重复通知) */
|
|
145
223
|
close() {
|
|
146
|
-
if (this.
|
|
224
|
+
if (this.activeId === null) {
|
|
147
225
|
return;
|
|
148
226
|
}
|
|
149
|
-
this.
|
|
227
|
+
this.activeId = null;
|
|
150
228
|
this.notify();
|
|
151
229
|
}
|
|
152
230
|
/** 订阅变更;返回退订函数 */
|
|
@@ -163,6 +241,94 @@ var ArtifactStore = class {
|
|
|
163
241
|
}
|
|
164
242
|
};
|
|
165
243
|
|
|
244
|
+
// src/artifact-download.ts
|
|
245
|
+
var KIND_EXTENSIONS = {
|
|
246
|
+
markdown: "md",
|
|
247
|
+
text: "txt",
|
|
248
|
+
code: "txt",
|
|
249
|
+
dsl: "json"
|
|
250
|
+
};
|
|
251
|
+
function resolveArtifactFilename(artifact) {
|
|
252
|
+
const title = artifact.title.trim() || "artifact";
|
|
253
|
+
if (/\.[A-Za-z0-9]+$/.test(title)) {
|
|
254
|
+
return title;
|
|
255
|
+
}
|
|
256
|
+
const ext = resolveArtifactFileType(artifact) ?? KIND_EXTENSIONS[artifact.kind];
|
|
257
|
+
return `${title}.${ext}`;
|
|
258
|
+
}
|
|
259
|
+
function dataUrlToBlob(dataUrl) {
|
|
260
|
+
const comma = dataUrl.indexOf(",");
|
|
261
|
+
const meta = dataUrl.slice(0, comma);
|
|
262
|
+
const payload = dataUrl.slice(comma + 1);
|
|
263
|
+
const mime = /^data:([^;,]+)/.exec(meta)?.[1] ?? "application/octet-stream";
|
|
264
|
+
if (meta.includes(";base64")) {
|
|
265
|
+
const binary = atob(payload);
|
|
266
|
+
const bytes = new Uint8Array(binary.length);
|
|
267
|
+
for (let i = 0; i < binary.length; i++) {
|
|
268
|
+
bytes[i] = binary.charCodeAt(i);
|
|
269
|
+
}
|
|
270
|
+
return new Blob([bytes], { type: mime });
|
|
271
|
+
}
|
|
272
|
+
return new Blob([decodeURIComponent(payload)], { type: mime });
|
|
273
|
+
}
|
|
274
|
+
function isRemoteUrl(raw) {
|
|
275
|
+
return /^https?:\/\//.test(raw.trim());
|
|
276
|
+
}
|
|
277
|
+
function buildArtifactBlob(artifact) {
|
|
278
|
+
if (artifact.kind === "dsl" && typeof artifact.content === "object") {
|
|
279
|
+
return new Blob([JSON.stringify(artifact.content, null, 2)], {
|
|
280
|
+
type: "application/json"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const raw = String(artifact.content);
|
|
284
|
+
if (isImageArtifact(artifact)) {
|
|
285
|
+
const trimmed = raw.trim();
|
|
286
|
+
if (trimmed.startsWith("data:")) {
|
|
287
|
+
return dataUrlToBlob(trimmed);
|
|
288
|
+
}
|
|
289
|
+
if (trimmed.startsWith("<svg") || trimmed.startsWith("<?xml")) {
|
|
290
|
+
return new Blob([raw], { type: "image/svg+xml" });
|
|
291
|
+
}
|
|
292
|
+
if (isRemoteUrl(trimmed)) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const ext = resolveArtifactFileType(artifact);
|
|
297
|
+
const mime = ext === "md" || artifact.kind === "markdown" ? "text/markdown" : "text/plain";
|
|
298
|
+
return new Blob([raw], { type: `${mime};charset=utf-8` });
|
|
299
|
+
}
|
|
300
|
+
function saveBlob(blob, filename) {
|
|
301
|
+
const url = URL.createObjectURL(blob);
|
|
302
|
+
const anchor = document.createElement("a");
|
|
303
|
+
anchor.href = url;
|
|
304
|
+
anchor.download = filename;
|
|
305
|
+
document.body.appendChild(anchor);
|
|
306
|
+
anchor.click();
|
|
307
|
+
anchor.remove();
|
|
308
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
309
|
+
}
|
|
310
|
+
async function downloadArtifact(artifact) {
|
|
311
|
+
const filename = resolveArtifactFilename(artifact);
|
|
312
|
+
const raw = String(artifact.content ?? "").trim();
|
|
313
|
+
if (isImageArtifact(artifact) && isRemoteUrl(raw)) {
|
|
314
|
+
try {
|
|
315
|
+
const response = await fetch(raw);
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
throw new Error(`HTTP ${response.status}`);
|
|
318
|
+
}
|
|
319
|
+
saveBlob(await response.blob(), filename);
|
|
320
|
+
return;
|
|
321
|
+
} catch {
|
|
322
|
+
window.open(raw, "_blank", "noopener");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const blob = buildArtifactBlob(artifact);
|
|
327
|
+
if (blob) {
|
|
328
|
+
saveBlob(blob, filename);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
166
332
|
// src/events.ts
|
|
167
333
|
function applyChatEvent(store, event) {
|
|
168
334
|
switch (event.type) {
|
|
@@ -806,6 +972,7 @@ import { useRenderer as useRenderer4 } from "@pactor-app/runtime";
|
|
|
806
972
|
import {
|
|
807
973
|
Component as Component3,
|
|
808
974
|
isValidElement,
|
|
975
|
+
useMemo as useMemo4,
|
|
809
976
|
useRef as useRef13,
|
|
810
977
|
useState as useState15,
|
|
811
978
|
useSyncExternalStore as useSyncExternalStore2
|
|
@@ -1054,6 +1221,12 @@ function ensureChatChromeActions(actions) {
|
|
|
1054
1221
|
function invokeChatPanelClose() {
|
|
1055
1222
|
handlers.panelClose?.();
|
|
1056
1223
|
}
|
|
1224
|
+
function invokeChatPanelToggleList() {
|
|
1225
|
+
handlers.panelToggleList?.();
|
|
1226
|
+
}
|
|
1227
|
+
function invokeChatPanelDownload() {
|
|
1228
|
+
handlers.panelDownload?.();
|
|
1229
|
+
}
|
|
1057
1230
|
|
|
1058
1231
|
// src/view/chrome/ChromeSlot.tsx
|
|
1059
1232
|
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -1264,10 +1437,47 @@ function ChatPanelCloseButton() {
|
|
|
1264
1437
|
}
|
|
1265
1438
|
);
|
|
1266
1439
|
}
|
|
1267
|
-
var PANEL_HEADER_INTERNAL_COMPONENTS = {
|
|
1440
|
+
var PANEL_HEADER_INTERNAL_COMPONENTS = {
|
|
1441
|
+
ChatPanelCloseButton,
|
|
1442
|
+
ChatPanelDownloadButton,
|
|
1443
|
+
ChatPanelListButton
|
|
1444
|
+
};
|
|
1445
|
+
function ChatPanelListButton() {
|
|
1446
|
+
return /* @__PURE__ */ jsx4(
|
|
1447
|
+
"button",
|
|
1448
|
+
{
|
|
1449
|
+
type: "button",
|
|
1450
|
+
className: "pactor-chat-icon-button pactor-chat-inspector__list-toggle",
|
|
1451
|
+
title: "\u4EA7\u7269\u76EE\u5F55",
|
|
1452
|
+
"aria-label": "\u4EA7\u7269\u76EE\u5F55",
|
|
1453
|
+
onClick: () => invokeChatPanelToggleList(),
|
|
1454
|
+
children: /* @__PURE__ */ jsx4(Icon2, { name: "arrow-left", size: 16 })
|
|
1455
|
+
}
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
function ChatPanelDownloadButton() {
|
|
1459
|
+
return /* @__PURE__ */ jsx4(
|
|
1460
|
+
"button",
|
|
1461
|
+
{
|
|
1462
|
+
type: "button",
|
|
1463
|
+
className: "pactor-chat-icon-button pactor-chat-inspector__download",
|
|
1464
|
+
title: "\u4E0B\u8F7D\u4EA7\u7269",
|
|
1465
|
+
"aria-label": "\u4E0B\u8F7D\u4EA7\u7269",
|
|
1466
|
+
onClick: () => invokeChatPanelDownload(),
|
|
1467
|
+
children: /* @__PURE__ */ jsx4(Icon2, { name: "download", size: 16 })
|
|
1468
|
+
}
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1268
1471
|
function buildPanelHeaderFragment(props) {
|
|
1269
1472
|
return {
|
|
1270
|
-
requires: [
|
|
1473
|
+
requires: [
|
|
1474
|
+
"Flex",
|
|
1475
|
+
"Text",
|
|
1476
|
+
"Badge",
|
|
1477
|
+
"ChatPanelCloseButton",
|
|
1478
|
+
"ChatPanelDownloadButton",
|
|
1479
|
+
"ChatPanelListButton"
|
|
1480
|
+
],
|
|
1271
1481
|
node: {
|
|
1272
1482
|
type: "Flex",
|
|
1273
1483
|
props: {
|
|
@@ -1281,6 +1491,7 @@ function buildPanelHeaderFragment(props) {
|
|
|
1281
1491
|
type: "Flex",
|
|
1282
1492
|
props: { align: "center", gap: "small" },
|
|
1283
1493
|
children: [
|
|
1494
|
+
...props.showListButton ? [{ type: "ChatPanelListButton" }] : [],
|
|
1284
1495
|
{
|
|
1285
1496
|
type: "Text",
|
|
1286
1497
|
props: {
|
|
@@ -1299,7 +1510,14 @@ function buildPanelHeaderFragment(props) {
|
|
|
1299
1510
|
}
|
|
1300
1511
|
]
|
|
1301
1512
|
},
|
|
1302
|
-
{
|
|
1513
|
+
{
|
|
1514
|
+
type: "Flex",
|
|
1515
|
+
props: { align: "center", gap: "small" },
|
|
1516
|
+
children: [
|
|
1517
|
+
...props.showDownload ? [{ type: "ChatPanelDownloadButton" }] : [],
|
|
1518
|
+
{ type: "ChatPanelCloseButton" }
|
|
1519
|
+
]
|
|
1520
|
+
}
|
|
1303
1521
|
]
|
|
1304
1522
|
}
|
|
1305
1523
|
};
|
|
@@ -1350,10 +1568,126 @@ function ArtifactDslContent({
|
|
|
1350
1568
|
const guarded = guardChatDsl(node, runtime.context.components);
|
|
1351
1569
|
return /* @__PURE__ */ jsx5(DslRenderer2, { context: runtime.context, engine: runtime.engine, children: /* @__PURE__ */ jsx5(ArtifactDslGuard, { children: /* @__PURE__ */ jsx5(ArtifactDslNode, { node: guarded }) }) });
|
|
1352
1570
|
}
|
|
1571
|
+
function ArtifactKindIcon({ kind }) {
|
|
1572
|
+
const name = kind === "code" ? "code" : kind === "dsl" ? "layout-grid" : "file-text";
|
|
1573
|
+
return /* @__PURE__ */ jsx5(Icon3, { name, size: 13 });
|
|
1574
|
+
}
|
|
1575
|
+
function buildArtifactTree(artifacts) {
|
|
1576
|
+
const root = { name: "", path: "", children: [] };
|
|
1577
|
+
for (const artifact of artifacts) {
|
|
1578
|
+
const segments = artifact.id.split("/").filter(Boolean);
|
|
1579
|
+
let node = root;
|
|
1580
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
1581
|
+
const path = segments.slice(0, i + 1).join("/");
|
|
1582
|
+
let child = node.children.find(
|
|
1583
|
+
(item) => item.path === path && item.artifact === void 0
|
|
1584
|
+
);
|
|
1585
|
+
if (!child) {
|
|
1586
|
+
child = { name: segments[i], path, children: [] };
|
|
1587
|
+
node.children.push(child);
|
|
1588
|
+
}
|
|
1589
|
+
node = child;
|
|
1590
|
+
}
|
|
1591
|
+
node.children.push({
|
|
1592
|
+
name: artifact.title,
|
|
1593
|
+
path: artifact.id,
|
|
1594
|
+
artifact,
|
|
1595
|
+
children: []
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
const sortLevel = (nodes) => {
|
|
1599
|
+
nodes.sort((a, b) => {
|
|
1600
|
+
const dirDelta = Number(b.artifact === void 0) - Number(a.artifact === void 0);
|
|
1601
|
+
return dirDelta !== 0 ? dirDelta : a.name.localeCompare(b.name);
|
|
1602
|
+
});
|
|
1603
|
+
for (const item of nodes) {
|
|
1604
|
+
sortLevel(item.children);
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
sortLevel(root.children);
|
|
1608
|
+
return root.children;
|
|
1609
|
+
}
|
|
1610
|
+
function ArtifactListView({
|
|
1611
|
+
nodes,
|
|
1612
|
+
activeId,
|
|
1613
|
+
onSelect
|
|
1614
|
+
}) {
|
|
1615
|
+
return /* @__PURE__ */ jsx5("ul", { className: "pactor-chat-inspector__list", children: nodes.map((node) => /* @__PURE__ */ jsx5(
|
|
1616
|
+
ArtifactTreeRow,
|
|
1617
|
+
{
|
|
1618
|
+
node,
|
|
1619
|
+
activeId,
|
|
1620
|
+
onSelect
|
|
1621
|
+
},
|
|
1622
|
+
node.path
|
|
1623
|
+
)) });
|
|
1624
|
+
}
|
|
1625
|
+
function ArtifactTreeRow({
|
|
1626
|
+
node,
|
|
1627
|
+
activeId,
|
|
1628
|
+
onSelect
|
|
1629
|
+
}) {
|
|
1630
|
+
const [open, setOpen] = useState5(true);
|
|
1631
|
+
if (node.artifact) {
|
|
1632
|
+
return /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs2(
|
|
1633
|
+
"button",
|
|
1634
|
+
{
|
|
1635
|
+
type: "button",
|
|
1636
|
+
className: "pactor-chat-inspector__list-item",
|
|
1637
|
+
"data-active": node.artifact.id === activeId,
|
|
1638
|
+
onClick: () => onSelect?.(node.artifact),
|
|
1639
|
+
children: [
|
|
1640
|
+
/* @__PURE__ */ jsx5(ArtifactKindIcon, { kind: node.artifact.kind }),
|
|
1641
|
+
/* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__list-item-title", children: node.artifact.title }),
|
|
1642
|
+
/* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__kind", children: node.artifact.kind })
|
|
1643
|
+
]
|
|
1644
|
+
}
|
|
1645
|
+
) });
|
|
1646
|
+
}
|
|
1647
|
+
return /* @__PURE__ */ jsxs2("li", { children: [
|
|
1648
|
+
/* @__PURE__ */ jsxs2(
|
|
1649
|
+
"button",
|
|
1650
|
+
{
|
|
1651
|
+
type: "button",
|
|
1652
|
+
className: "pactor-chat-inspector__list-folder",
|
|
1653
|
+
"aria-expanded": open,
|
|
1654
|
+
"data-open": open,
|
|
1655
|
+
onClick: () => setOpen((value) => !value),
|
|
1656
|
+
children: [
|
|
1657
|
+
/* @__PURE__ */ jsx5("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx5(Icon3, { name: "chevron-right", size: 12 }) }),
|
|
1658
|
+
/* @__PURE__ */ jsx5(Icon3, { name: "folder", size: 13 }),
|
|
1659
|
+
/* @__PURE__ */ jsx5("span", { children: node.name })
|
|
1660
|
+
]
|
|
1661
|
+
}
|
|
1662
|
+
),
|
|
1663
|
+
open && /* @__PURE__ */ jsx5("div", { className: "pactor-chat-inspector__list-children", children: /* @__PURE__ */ jsx5(
|
|
1664
|
+
ArtifactListView,
|
|
1665
|
+
{
|
|
1666
|
+
nodes: node.children,
|
|
1667
|
+
activeId,
|
|
1668
|
+
onSelect
|
|
1669
|
+
}
|
|
1670
|
+
) })
|
|
1671
|
+
] });
|
|
1672
|
+
}
|
|
1673
|
+
function ArtifactImageContent({ artifact }) {
|
|
1674
|
+
const raw = String(artifact.content).trim();
|
|
1675
|
+
const src = raw.startsWith("<svg") || raw.startsWith("<?xml") ? `data:image/svg+xml;utf8,${encodeURIComponent(raw)}` : raw;
|
|
1676
|
+
return /* @__PURE__ */ jsx5("div", { className: "pactor-chat-inspector__image", children: /* @__PURE__ */ jsx5("img", { src, alt: artifact.title }) });
|
|
1677
|
+
}
|
|
1353
1678
|
function ArtifactContent({
|
|
1354
1679
|
artifact,
|
|
1355
|
-
runtime
|
|
1680
|
+
runtime,
|
|
1681
|
+
viewers
|
|
1356
1682
|
}) {
|
|
1683
|
+
const ext = resolveArtifactFileType(artifact);
|
|
1684
|
+
const custom = (ext ? viewers?.[ext] : void 0) ?? viewers?.[artifact.kind];
|
|
1685
|
+
if (custom) {
|
|
1686
|
+
return /* @__PURE__ */ jsx5(Fragment3, { children: custom(artifact) });
|
|
1687
|
+
}
|
|
1688
|
+
if (isImageArtifact(artifact)) {
|
|
1689
|
+
return /* @__PURE__ */ jsx5(ArtifactImageContent, { artifact });
|
|
1690
|
+
}
|
|
1357
1691
|
switch (artifact.kind) {
|
|
1358
1692
|
case "markdown":
|
|
1359
1693
|
return /* @__PURE__ */ jsx5(MarkdownRenderer2, { content: String(artifact.content) });
|
|
@@ -1372,17 +1706,41 @@ function ArtifactContent({
|
|
|
1372
1706
|
}
|
|
1373
1707
|
function ArtifactPanel({
|
|
1374
1708
|
artifact,
|
|
1709
|
+
artifacts,
|
|
1710
|
+
activeArtifactId,
|
|
1711
|
+
onSelectArtifact,
|
|
1375
1712
|
inspector,
|
|
1376
1713
|
onClose,
|
|
1377
1714
|
safeRegistry,
|
|
1378
1715
|
user,
|
|
1716
|
+
artifactViewers,
|
|
1379
1717
|
className
|
|
1380
1718
|
}) {
|
|
1381
1719
|
const runtime = useArtifactRuntime(safeRegistry, user);
|
|
1720
|
+
const [view, setView] = useState5("content");
|
|
1721
|
+
const activeId = activeArtifactId ?? artifact?.id;
|
|
1722
|
+
useEffect3(() => {
|
|
1723
|
+
setView("content");
|
|
1724
|
+
}, [activeId]);
|
|
1725
|
+
const showListButton = artifacts !== void 0 && artifacts.length > 0;
|
|
1726
|
+
const toggleListRef = useRef4(() => {
|
|
1727
|
+
});
|
|
1728
|
+
toggleListRef.current = () => setView((value) => value === "list" ? "content" : "list");
|
|
1382
1729
|
const closeRef = useRef4(onClose);
|
|
1383
1730
|
closeRef.current = onClose;
|
|
1731
|
+
const artifactRef = useRef4(artifact);
|
|
1732
|
+
artifactRef.current = artifact;
|
|
1384
1733
|
useEffect3(() => {
|
|
1385
|
-
setChatChromeHandlers({
|
|
1734
|
+
setChatChromeHandlers({
|
|
1735
|
+
panelClose: () => closeRef.current?.(),
|
|
1736
|
+
panelToggleList: () => toggleListRef.current(),
|
|
1737
|
+
panelDownload: () => {
|
|
1738
|
+
const current = artifactRef.current;
|
|
1739
|
+
if (current) {
|
|
1740
|
+
void downloadArtifact(current);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1386
1744
|
}, []);
|
|
1387
1745
|
const panelRef = useRef4(null);
|
|
1388
1746
|
const [width, setWidth] = useState5(null);
|
|
@@ -1401,7 +1759,7 @@ function ArtifactPanel({
|
|
|
1401
1759
|
document.body.style.userSelect = "none";
|
|
1402
1760
|
const onMove = (moveEvent) => {
|
|
1403
1761
|
const next = startWidth + (startX - moveEvent.clientX);
|
|
1404
|
-
setWidth(Math.round(Math.min(Math.max(next,
|
|
1762
|
+
setWidth(Math.round(Math.min(Math.max(next, 320), maxWidth)));
|
|
1405
1763
|
};
|
|
1406
1764
|
const onUp = () => {
|
|
1407
1765
|
document.body.style.userSelect = prevUserSelect;
|
|
@@ -1434,12 +1792,36 @@ function ArtifactPanel({
|
|
|
1434
1792
|
{
|
|
1435
1793
|
fragment: buildPanelHeaderFragment({
|
|
1436
1794
|
title: artifact?.title ?? "\u4EA7\u51FA\u7269",
|
|
1437
|
-
kind: artifact?.kind ?? "text"
|
|
1795
|
+
kind: artifact?.kind ?? "text",
|
|
1796
|
+
showListButton,
|
|
1797
|
+
showDownload: artifact != null
|
|
1438
1798
|
}),
|
|
1439
1799
|
components: PANEL_HEADER_INTERNAL_COMPONENTS,
|
|
1440
1800
|
fallback: /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
1801
|
+
showListButton ? /* @__PURE__ */ jsx5(
|
|
1802
|
+
"button",
|
|
1803
|
+
{
|
|
1804
|
+
type: "button",
|
|
1805
|
+
className: "pactor-chat-icon-button pactor-chat-inspector__list-toggle",
|
|
1806
|
+
title: "\u4EA7\u7269\u76EE\u5F55",
|
|
1807
|
+
"aria-label": "\u4EA7\u7269\u76EE\u5F55",
|
|
1808
|
+
onClick: () => toggleListRef.current(),
|
|
1809
|
+
children: /* @__PURE__ */ jsx5(Icon3, { name: "arrow-left", size: 16, strokeWidth: 1.7 })
|
|
1810
|
+
}
|
|
1811
|
+
) : null,
|
|
1441
1812
|
/* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__title", children: artifact?.title ?? "\u4EA7\u51FA\u7269" }),
|
|
1442
1813
|
artifact ? /* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__kind", children: artifact.kind }) : null,
|
|
1814
|
+
artifact ? /* @__PURE__ */ jsx5(
|
|
1815
|
+
"button",
|
|
1816
|
+
{
|
|
1817
|
+
type: "button",
|
|
1818
|
+
className: "pactor-chat-icon-button pactor-chat-inspector__download",
|
|
1819
|
+
title: "\u4E0B\u8F7D\u4EA7\u7269",
|
|
1820
|
+
"aria-label": "\u4E0B\u8F7D\u4EA7\u7269",
|
|
1821
|
+
onClick: () => void downloadArtifact(artifact),
|
|
1822
|
+
children: /* @__PURE__ */ jsx5(Icon3, { name: "download", size: 16, strokeWidth: 1.7 })
|
|
1823
|
+
}
|
|
1824
|
+
) : null,
|
|
1443
1825
|
/* @__PURE__ */ jsx5(
|
|
1444
1826
|
"button",
|
|
1445
1827
|
{
|
|
@@ -1453,7 +1835,38 @@ function ArtifactPanel({
|
|
|
1453
1835
|
] })
|
|
1454
1836
|
}
|
|
1455
1837
|
) }),
|
|
1456
|
-
/* @__PURE__ */ jsx5("div", { className: "pactor-chat-
|
|
1838
|
+
artifacts !== void 0 && artifacts.length > 1 ? /* @__PURE__ */ jsx5("div", { className: "pactor-chat-inspector__tabs", role: "tablist", children: artifacts.map((item) => /* @__PURE__ */ jsxs2(
|
|
1839
|
+
"button",
|
|
1840
|
+
{
|
|
1841
|
+
type: "button",
|
|
1842
|
+
role: "tab",
|
|
1843
|
+
"aria-selected": item.id === activeId,
|
|
1844
|
+
"data-active": item.id === activeId,
|
|
1845
|
+
className: "pactor-chat-inspector__tab",
|
|
1846
|
+
title: item.title,
|
|
1847
|
+
onClick: () => onSelectArtifact?.(item),
|
|
1848
|
+
children: [
|
|
1849
|
+
/* @__PURE__ */ jsx5(ArtifactKindIcon, { kind: item.kind }),
|
|
1850
|
+
/* @__PURE__ */ jsx5("span", { className: "pactor-chat-inspector__tab-title", children: item.title })
|
|
1851
|
+
]
|
|
1852
|
+
},
|
|
1853
|
+
item.id
|
|
1854
|
+
)) }) : null,
|
|
1855
|
+
/* @__PURE__ */ jsx5("div", { className: "pactor-chat-inspector__body", children: view === "list" && artifacts !== void 0 ? /* @__PURE__ */ jsx5(
|
|
1856
|
+
ArtifactListView,
|
|
1857
|
+
{
|
|
1858
|
+
nodes: buildArtifactTree(artifacts),
|
|
1859
|
+
activeId,
|
|
1860
|
+
onSelect: onSelectArtifact
|
|
1861
|
+
}
|
|
1862
|
+
) : inspector !== void 0 && inspector !== null ? inspector : artifact ? /* @__PURE__ */ jsx5(
|
|
1863
|
+
ArtifactContent,
|
|
1864
|
+
{
|
|
1865
|
+
artifact,
|
|
1866
|
+
runtime,
|
|
1867
|
+
viewers: artifactViewers
|
|
1868
|
+
}
|
|
1869
|
+
) : null })
|
|
1457
1870
|
]
|
|
1458
1871
|
}
|
|
1459
1872
|
);
|
|
@@ -2484,7 +2897,11 @@ import { useEffect as useEffect7, useRef as useRef8, useState as useState10 } fr
|
|
|
2484
2897
|
import { Fragment as Fragment8, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2485
2898
|
function usePinnedCollapse(autoOpen) {
|
|
2486
2899
|
const [state, setState] = useState10("auto");
|
|
2487
|
-
const
|
|
2900
|
+
const autoCollapsedRef = useRef8(false);
|
|
2901
|
+
if (state === "auto" && !autoOpen) {
|
|
2902
|
+
autoCollapsedRef.current = true;
|
|
2903
|
+
}
|
|
2904
|
+
const open = state === "auto" ? autoOpen && !autoCollapsedRef.current : state === "pinned-open";
|
|
2488
2905
|
const toggle = () => {
|
|
2489
2906
|
setState(open ? "pinned-closed" : "pinned-open");
|
|
2490
2907
|
};
|
|
@@ -2635,6 +3052,7 @@ function ActivityTrace({ message }) {
|
|
|
2635
3052
|
);
|
|
2636
3053
|
const answerPhase = mode === "responding" || !streaming;
|
|
2637
3054
|
const { open, toggle } = usePinnedCollapse(!answerPhase);
|
|
3055
|
+
const collapsible = entries.length > 0 || thinkingParts.length > 0;
|
|
2638
3056
|
const mountTimeRef = useRef8(0);
|
|
2639
3057
|
if (mountTimeRef.current === 0) {
|
|
2640
3058
|
mountTimeRef.current = Date.now();
|
|
@@ -2656,10 +3074,10 @@ function ActivityTrace({ message }) {
|
|
|
2656
3074
|
elapsed,
|
|
2657
3075
|
"s"
|
|
2658
3076
|
] }),
|
|
2659
|
-
|
|
3077
|
+
collapsible && /* @__PURE__ */ jsx12("span", { className: "pactor-chat-trace__chevron", children: /* @__PURE__ */ jsx12(Icon7, { name: "chevron-right", size: 12 }) })
|
|
2660
3078
|
] });
|
|
2661
3079
|
return /* @__PURE__ */ jsxs8("div", { className: "pactor-chat-trace", "data-open": open, children: [
|
|
2662
|
-
|
|
3080
|
+
collapsible ? /* @__PURE__ */ jsx12(
|
|
2663
3081
|
"button",
|
|
2664
3082
|
{
|
|
2665
3083
|
type: "button",
|
|
@@ -2669,15 +3087,17 @@ function ActivityTrace({ message }) {
|
|
|
2669
3087
|
children: header
|
|
2670
3088
|
}
|
|
2671
3089
|
) : /* @__PURE__ */ jsx12("div", { className: "pactor-chat-trace__header", children: header }),
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
3090
|
+
open && /* @__PURE__ */ jsxs8(Fragment8, { children: [
|
|
3091
|
+
entries.length > 0 && /* @__PURE__ */ jsx12("div", { className: "pactor-chat-trace__entries", children: entries.map((entry) => /* @__PURE__ */ jsx12(TraceRow, { entry }, entry.callIndex)) }),
|
|
3092
|
+
thinkingParts.map((part, index) => /* @__PURE__ */ jsx12(
|
|
3093
|
+
ThinkingCard,
|
|
3094
|
+
{
|
|
3095
|
+
part,
|
|
3096
|
+
answerPhase
|
|
3097
|
+
},
|
|
3098
|
+
part.id ?? index
|
|
3099
|
+
))
|
|
3100
|
+
] })
|
|
2681
3101
|
] });
|
|
2682
3102
|
}
|
|
2683
3103
|
|
|
@@ -3626,6 +4046,8 @@ function ChatComposition({
|
|
|
3626
4046
|
onSend,
|
|
3627
4047
|
artifact,
|
|
3628
4048
|
panelOpen,
|
|
4049
|
+
artifacts: artifactsProp,
|
|
4050
|
+
onSelectArtifact,
|
|
3629
4051
|
onPanelClose,
|
|
3630
4052
|
inspector,
|
|
3631
4053
|
onArtifact,
|
|
@@ -3646,8 +4068,28 @@ function ChatComposition({
|
|
|
3646
4068
|
(listener) => artifactStore.subscribe(listener),
|
|
3647
4069
|
() => artifactStore.get()
|
|
3648
4070
|
);
|
|
4071
|
+
const storeArtifacts = useSyncExternalStore2(
|
|
4072
|
+
(listener) => artifactStore.subscribe(listener),
|
|
4073
|
+
() => artifactStore.list()
|
|
4074
|
+
);
|
|
4075
|
+
const messages = useSyncExternalStore2(
|
|
4076
|
+
(listener) => store.subscribe(listener),
|
|
4077
|
+
() => store.getMessages()
|
|
4078
|
+
);
|
|
4079
|
+
const streamArtifacts = useMemo4(() => collectArtifacts(messages), [messages]);
|
|
4080
|
+
const mergedArtifacts = useMemo4(
|
|
4081
|
+
() => mergeArtifacts(streamArtifacts, storeArtifacts),
|
|
4082
|
+
[streamArtifacts, storeArtifacts]
|
|
4083
|
+
);
|
|
3649
4084
|
const effectiveArtifact = panelControlled ? artifact ?? null : storeArtifact;
|
|
3650
4085
|
const effectivePanelOpen = panelControlled ? panelOpen ?? artifact != null : storeArtifact !== null;
|
|
4086
|
+
const effectiveArtifacts = artifactsProp ?? mergedArtifacts;
|
|
4087
|
+
const handleSelectArtifact = (next) => {
|
|
4088
|
+
onSelectArtifact?.(next);
|
|
4089
|
+
if (!panelControlled) {
|
|
4090
|
+
artifactStore.open(next);
|
|
4091
|
+
}
|
|
4092
|
+
};
|
|
3651
4093
|
const handlePanelClose = () => {
|
|
3652
4094
|
onPanelClose?.();
|
|
3653
4095
|
if (!panelControlled) {
|
|
@@ -3789,10 +4231,14 @@ function ChatComposition({
|
|
|
3789
4231
|
ArtifactPanel,
|
|
3790
4232
|
{
|
|
3791
4233
|
artifact: effectiveArtifact,
|
|
4234
|
+
artifacts: effectiveArtifacts,
|
|
4235
|
+
activeArtifactId: effectiveArtifact?.id,
|
|
4236
|
+
onSelectArtifact: handleSelectArtifact,
|
|
3792
4237
|
inspector,
|
|
3793
4238
|
onClose: handlePanelClose,
|
|
3794
4239
|
safeRegistry: services.safeRegistry,
|
|
3795
|
-
user: services.user
|
|
4240
|
+
user: services.user,
|
|
4241
|
+
artifactViewers: services.artifactViewers
|
|
3796
4242
|
}
|
|
3797
4243
|
)
|
|
3798
4244
|
] }) : null
|
|
@@ -3835,6 +4281,7 @@ function createChatWorkspace(services) {
|
|
|
3835
4281
|
inspector,
|
|
3836
4282
|
onArtifact,
|
|
3837
4283
|
onOpenArtifact,
|
|
4284
|
+
onSelectArtifact,
|
|
3838
4285
|
headerActions,
|
|
3839
4286
|
editableTitle,
|
|
3840
4287
|
onExport,
|
|
@@ -3875,6 +4322,7 @@ function createChatWorkspace(services) {
|
|
|
3875
4322
|
inspector: inspector ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: inspector }) : void 0,
|
|
3876
4323
|
onArtifact,
|
|
3877
4324
|
onOpenArtifact,
|
|
4325
|
+
onSelectArtifact,
|
|
3878
4326
|
headerActions,
|
|
3879
4327
|
editableTitle,
|
|
3880
4328
|
onExport,
|
|
@@ -4084,6 +4532,7 @@ function Chat({
|
|
|
4084
4532
|
inspector,
|
|
4085
4533
|
onArtifact,
|
|
4086
4534
|
onOpenArtifact,
|
|
4535
|
+
onSelectArtifact,
|
|
4087
4536
|
className,
|
|
4088
4537
|
onSend,
|
|
4089
4538
|
onMenuSelect
|
|
@@ -4148,7 +4597,8 @@ function Chat({
|
|
|
4148
4597
|
onPanelClose,
|
|
4149
4598
|
inspector: resolveSlot(inspector),
|
|
4150
4599
|
onArtifact,
|
|
4151
|
-
onOpenArtifact
|
|
4600
|
+
onOpenArtifact,
|
|
4601
|
+
onSelectArtifact
|
|
4152
4602
|
}
|
|
4153
4603
|
);
|
|
4154
4604
|
}
|
|
@@ -4159,6 +4609,7 @@ function ChatDslAdapter({
|
|
|
4159
4609
|
onPanelClose,
|
|
4160
4610
|
onArtifact,
|
|
4161
4611
|
onOpenArtifact,
|
|
4612
|
+
onSelectArtifact,
|
|
4162
4613
|
onModelChange,
|
|
4163
4614
|
onContextsChange,
|
|
4164
4615
|
onMessageAction,
|
|
@@ -4177,6 +4628,7 @@ function ChatDslAdapter({
|
|
|
4177
4628
|
onPanelClose: onPanelClose ? () => onPanelClose({}) : void 0,
|
|
4178
4629
|
onArtifact: onArtifact ? (artifact) => onArtifact({ artifact }) : void 0,
|
|
4179
4630
|
onOpenArtifact: onOpenArtifact ? (artifact) => onOpenArtifact({ artifact }) : void 0,
|
|
4631
|
+
onSelectArtifact: onSelectArtifact ? (artifact) => onSelectArtifact({ artifact }) : void 0,
|
|
4180
4632
|
onModelChange: onModelChange ? (model) => onModelChange({ model }) : void 0,
|
|
4181
4633
|
onContextsChange: onContextsChange ? (contexts) => onContextsChange({ contexts }) : void 0,
|
|
4182
4634
|
onMessageAction: onMessageAction ? (action, message) => onMessageAction({ action, message }) : void 0,
|
|
@@ -4214,6 +4666,7 @@ function registerChatLayout(layoutRegistry) {
|
|
|
4214
4666
|
}
|
|
4215
4667
|
}
|
|
4216
4668
|
export {
|
|
4669
|
+
ARTIFACT_IMAGE_EXTENSIONS,
|
|
4217
4670
|
ActivityTrace,
|
|
4218
4671
|
AppShell,
|
|
4219
4672
|
ArtifactPanel,
|
|
@@ -4237,7 +4690,10 @@ export {
|
|
|
4237
4690
|
MessageBubble,
|
|
4238
4691
|
PartRenderer,
|
|
4239
4692
|
applyChatEvent,
|
|
4693
|
+
buildArtifactBlob,
|
|
4694
|
+
buildArtifactTree,
|
|
4240
4695
|
buildChatScope,
|
|
4696
|
+
collectArtifacts,
|
|
4241
4697
|
collectMessageText,
|
|
4242
4698
|
createChatComponentRegistry,
|
|
4243
4699
|
createChatId,
|
|
@@ -4246,14 +4702,20 @@ export {
|
|
|
4246
4702
|
createManualDriver,
|
|
4247
4703
|
createSafeRegistry,
|
|
4248
4704
|
detectStreamingMode,
|
|
4705
|
+
downloadArtifact,
|
|
4249
4706
|
guardChatDsl,
|
|
4707
|
+
isImageArtifact,
|
|
4708
|
+
mergeArtifacts,
|
|
4250
4709
|
pairToolTraces,
|
|
4251
4710
|
registerCapabilityAction,
|
|
4252
4711
|
registerChatComponent,
|
|
4253
4712
|
registerChatLayout,
|
|
4254
4713
|
registerChatShellComponent,
|
|
4714
|
+
resolveArtifactFileType,
|
|
4715
|
+
resolveArtifactFilename,
|
|
4255
4716
|
runChatTurn,
|
|
4256
4717
|
runRegenerateTurn,
|
|
4718
|
+
saveBlob,
|
|
4257
4719
|
useChatAutoScroll,
|
|
4258
4720
|
useChatServices,
|
|
4259
4721
|
useSidebarDrawer,
|