@pactor-app/chat 1.15.6 → 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 +490 -22
- package/dist/index.d.cts +147 -37
- package/dist/index.d.ts +147 -37
- package/dist/index.js +481 -22
- package/package.json +6 -6
- package/styles.css +169 -8
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;
|
|
133
195
|
this.notify();
|
|
134
196
|
}
|
|
135
|
-
/**
|
|
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;
|
|
207
|
+
this.notify();
|
|
208
|
+
}
|
|
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
|
};
|
|
@@ -3629,6 +4046,8 @@ function ChatComposition({
|
|
|
3629
4046
|
onSend,
|
|
3630
4047
|
artifact,
|
|
3631
4048
|
panelOpen,
|
|
4049
|
+
artifacts: artifactsProp,
|
|
4050
|
+
onSelectArtifact,
|
|
3632
4051
|
onPanelClose,
|
|
3633
4052
|
inspector,
|
|
3634
4053
|
onArtifact,
|
|
@@ -3649,8 +4068,28 @@ function ChatComposition({
|
|
|
3649
4068
|
(listener) => artifactStore.subscribe(listener),
|
|
3650
4069
|
() => artifactStore.get()
|
|
3651
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
|
+
);
|
|
3652
4084
|
const effectiveArtifact = panelControlled ? artifact ?? null : storeArtifact;
|
|
3653
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
|
+
};
|
|
3654
4093
|
const handlePanelClose = () => {
|
|
3655
4094
|
onPanelClose?.();
|
|
3656
4095
|
if (!panelControlled) {
|
|
@@ -3792,10 +4231,14 @@ function ChatComposition({
|
|
|
3792
4231
|
ArtifactPanel,
|
|
3793
4232
|
{
|
|
3794
4233
|
artifact: effectiveArtifact,
|
|
4234
|
+
artifacts: effectiveArtifacts,
|
|
4235
|
+
activeArtifactId: effectiveArtifact?.id,
|
|
4236
|
+
onSelectArtifact: handleSelectArtifact,
|
|
3795
4237
|
inspector,
|
|
3796
4238
|
onClose: handlePanelClose,
|
|
3797
4239
|
safeRegistry: services.safeRegistry,
|
|
3798
|
-
user: services.user
|
|
4240
|
+
user: services.user,
|
|
4241
|
+
artifactViewers: services.artifactViewers
|
|
3799
4242
|
}
|
|
3800
4243
|
)
|
|
3801
4244
|
] }) : null
|
|
@@ -3838,6 +4281,7 @@ function createChatWorkspace(services) {
|
|
|
3838
4281
|
inspector,
|
|
3839
4282
|
onArtifact,
|
|
3840
4283
|
onOpenArtifact,
|
|
4284
|
+
onSelectArtifact,
|
|
3841
4285
|
headerActions,
|
|
3842
4286
|
editableTitle,
|
|
3843
4287
|
onExport,
|
|
@@ -3878,6 +4322,7 @@ function createChatWorkspace(services) {
|
|
|
3878
4322
|
inspector: inspector ? /* @__PURE__ */ jsx19(DslNodeSlot, { node: inspector }) : void 0,
|
|
3879
4323
|
onArtifact,
|
|
3880
4324
|
onOpenArtifact,
|
|
4325
|
+
onSelectArtifact,
|
|
3881
4326
|
headerActions,
|
|
3882
4327
|
editableTitle,
|
|
3883
4328
|
onExport,
|
|
@@ -4087,6 +4532,7 @@ function Chat({
|
|
|
4087
4532
|
inspector,
|
|
4088
4533
|
onArtifact,
|
|
4089
4534
|
onOpenArtifact,
|
|
4535
|
+
onSelectArtifact,
|
|
4090
4536
|
className,
|
|
4091
4537
|
onSend,
|
|
4092
4538
|
onMenuSelect
|
|
@@ -4151,7 +4597,8 @@ function Chat({
|
|
|
4151
4597
|
onPanelClose,
|
|
4152
4598
|
inspector: resolveSlot(inspector),
|
|
4153
4599
|
onArtifact,
|
|
4154
|
-
onOpenArtifact
|
|
4600
|
+
onOpenArtifact,
|
|
4601
|
+
onSelectArtifact
|
|
4155
4602
|
}
|
|
4156
4603
|
);
|
|
4157
4604
|
}
|
|
@@ -4162,6 +4609,7 @@ function ChatDslAdapter({
|
|
|
4162
4609
|
onPanelClose,
|
|
4163
4610
|
onArtifact,
|
|
4164
4611
|
onOpenArtifact,
|
|
4612
|
+
onSelectArtifact,
|
|
4165
4613
|
onModelChange,
|
|
4166
4614
|
onContextsChange,
|
|
4167
4615
|
onMessageAction,
|
|
@@ -4180,6 +4628,7 @@ function ChatDslAdapter({
|
|
|
4180
4628
|
onPanelClose: onPanelClose ? () => onPanelClose({}) : void 0,
|
|
4181
4629
|
onArtifact: onArtifact ? (artifact) => onArtifact({ artifact }) : void 0,
|
|
4182
4630
|
onOpenArtifact: onOpenArtifact ? (artifact) => onOpenArtifact({ artifact }) : void 0,
|
|
4631
|
+
onSelectArtifact: onSelectArtifact ? (artifact) => onSelectArtifact({ artifact }) : void 0,
|
|
4183
4632
|
onModelChange: onModelChange ? (model) => onModelChange({ model }) : void 0,
|
|
4184
4633
|
onContextsChange: onContextsChange ? (contexts) => onContextsChange({ contexts }) : void 0,
|
|
4185
4634
|
onMessageAction: onMessageAction ? (action, message) => onMessageAction({ action, message }) : void 0,
|
|
@@ -4217,6 +4666,7 @@ function registerChatLayout(layoutRegistry) {
|
|
|
4217
4666
|
}
|
|
4218
4667
|
}
|
|
4219
4668
|
export {
|
|
4669
|
+
ARTIFACT_IMAGE_EXTENSIONS,
|
|
4220
4670
|
ActivityTrace,
|
|
4221
4671
|
AppShell,
|
|
4222
4672
|
ArtifactPanel,
|
|
@@ -4240,7 +4690,10 @@ export {
|
|
|
4240
4690
|
MessageBubble,
|
|
4241
4691
|
PartRenderer,
|
|
4242
4692
|
applyChatEvent,
|
|
4693
|
+
buildArtifactBlob,
|
|
4694
|
+
buildArtifactTree,
|
|
4243
4695
|
buildChatScope,
|
|
4696
|
+
collectArtifacts,
|
|
4244
4697
|
collectMessageText,
|
|
4245
4698
|
createChatComponentRegistry,
|
|
4246
4699
|
createChatId,
|
|
@@ -4249,14 +4702,20 @@ export {
|
|
|
4249
4702
|
createManualDriver,
|
|
4250
4703
|
createSafeRegistry,
|
|
4251
4704
|
detectStreamingMode,
|
|
4705
|
+
downloadArtifact,
|
|
4252
4706
|
guardChatDsl,
|
|
4707
|
+
isImageArtifact,
|
|
4708
|
+
mergeArtifacts,
|
|
4253
4709
|
pairToolTraces,
|
|
4254
4710
|
registerCapabilityAction,
|
|
4255
4711
|
registerChatComponent,
|
|
4256
4712
|
registerChatLayout,
|
|
4257
4713
|
registerChatShellComponent,
|
|
4714
|
+
resolveArtifactFileType,
|
|
4715
|
+
resolveArtifactFilename,
|
|
4258
4716
|
runChatTurn,
|
|
4259
4717
|
runRegenerateTurn,
|
|
4718
|
+
saveBlob,
|
|
4260
4719
|
useChatAutoScroll,
|
|
4261
4720
|
useChatServices,
|
|
4262
4721
|
useSidebarDrawer,
|