@copilotkit/vue 1.83.0 → 2.0.0-rc.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/AGENTS.md +1 -1
- package/dist/index.cjs +76 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +14290 -287
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
- package/dist/v2/components/a2ui/catalog.d.ts +1 -1
- package/dist/v2/components/a2ui/catalog.d.ts.map +1 -1
- package/dist/v2/components/chat/BasicMarkdown.vue.d.ts +9 -0
- package/dist/v2/components/chat/BasicMarkdown.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts +10 -0
- package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/index.d.ts +2 -0
- package/dist/v2/components/chat/index.d.ts.map +1 -1
- package/dist/v2/hooks/index.d.ts +0 -1
- package/dist/v2/hooks/index.d.ts.map +1 -1
- package/dist/v2/hooks/use-pin-to-send.d.ts +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +195 -188
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +10 -0
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/dist/v2/providers/index.d.ts +1 -0
- package/dist/v2/providers/index.d.ts.map +1 -1
- package/dist/v2/providers/markdown-renderer.d.ts +23 -0
- package/dist/v2/providers/markdown-renderer.d.ts.map +1 -0
- package/package.json +8 -9
- package/src/styles/globals.css +0 -97
- package/src/v2/components/A2UIMessageRenderer.ts +3 -2
- package/src/v2/components/a2ui/catalog.ts +4 -2
- package/src/v2/components/chat/BasicMarkdown.vue +158 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +27 -536
- package/src/v2/components/chat/CopilotChatReasoningMessage.vue +7 -2
- package/src/v2/components/chat/StreamingMarkdownDefault.vue +57 -0
- package/src/v2/components/chat/__tests__/BasicMarkdown.test.ts +69 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +53 -16
- package/src/v2/components/chat/__tests__/CopilotChat.licenseWarning.test.ts +5 -7
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.markdown.test.ts +54 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +0 -41
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.ts +18 -4
- package/src/v2/components/chat/__tests__/markdown-renderer-config.e2e.test.ts +59 -0
- package/src/v2/components/chat/index.ts +2 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.ts +7 -3
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +7 -3
- package/src/v2/hooks/index.ts +0 -1
- package/src/v2/hooks/use-pin-to-send.ts +1 -1
- package/src/v2/providers/CopilotKitProvider.types.ts +10 -0
- package/src/v2/providers/CopilotKitProvider.vue +2 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.headersAtSendTime.test.ts +1 -1
- package/src/v2/providers/__tests__/markdown-renderer.test.ts +40 -0
- package/src/v2/providers/index.ts +1 -0
- package/src/v2/providers/markdown-renderer.ts +39 -0
- package/vite.config.ts +1 -3
- package/dist/use-render-activity-message-B7sDGUl4.cjs +0 -70
- package/dist/use-render-activity-message-B7sDGUl4.cjs.map +0 -1
- package/dist/use-render-activity-message-mQvByEYF.js +0 -12544
- package/dist/use-render-activity-message-mQvByEYF.js.map +0 -1
- package/dist/v2/hooks/use-katex-styles.d.ts +0 -22
- package/dist/v2/hooks/use-katex-styles.d.ts.map +0 -1
- package/src/v2/hooks/__tests__/use-katex-styles.test.ts +0 -69
- package/src/v2/hooks/use-katex-styles.ts +0 -44
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
defineComponent,
|
|
5
|
-
h,
|
|
6
|
-
onBeforeUnmount,
|
|
7
|
-
onMounted,
|
|
8
|
-
ref,
|
|
9
|
-
} from "vue";
|
|
2
|
+
import { computed, onBeforeUnmount, ref } from "vue";
|
|
3
|
+
import type { Component } from "vue";
|
|
10
4
|
import type { AssistantMessage, Message } from "@ag-ui/core";
|
|
11
|
-
import { StreamMarkdown } from "streamdown-vue";
|
|
12
5
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
13
6
|
import { CopilotChatDefaultLabels } from "../../providers/types";
|
|
14
7
|
import {
|
|
15
8
|
IconCheck,
|
|
16
9
|
IconCopy,
|
|
17
|
-
IconDownload,
|
|
18
10
|
IconRefreshCw,
|
|
19
11
|
IconThumbsDown,
|
|
20
12
|
IconThumbsUp,
|
|
21
13
|
IconVolume2,
|
|
22
14
|
} from "../icons";
|
|
15
|
+
import StreamingMarkdownDefault from "./StreamingMarkdownDefault.vue";
|
|
16
|
+
import {
|
|
17
|
+
useMarkdownRenderer,
|
|
18
|
+
isVueComponentRenderer,
|
|
19
|
+
} from "../../providers/markdown-renderer";
|
|
23
20
|
import CopilotChatToolCallsView from "./CopilotChatToolCallsView.vue";
|
|
24
21
|
import type {
|
|
25
22
|
CopilotChatAssistantMessageCopyButtonSlotProps,
|
|
@@ -32,10 +29,6 @@ import type {
|
|
|
32
29
|
CopilotChatAssistantMessageToolCallsViewSlotProps,
|
|
33
30
|
CopilotChatAssistantMessageToolbarSlotProps,
|
|
34
31
|
} from "./types";
|
|
35
|
-
import { useKatexStyles } from "../../hooks/use-katex-styles";
|
|
36
|
-
|
|
37
|
-
useKatexStyles();
|
|
38
|
-
|
|
39
32
|
const props = withDefaults(
|
|
40
33
|
defineProps<{
|
|
41
34
|
message: AssistantMessage;
|
|
@@ -101,512 +94,6 @@ const toolbarButtonClass = [
|
|
|
101
94
|
"cpk:dark:hover:text-[rgb(243,243,243)] cpk:disabled:pointer-events-none cpk:disabled:opacity-50",
|
|
102
95
|
].join(" ");
|
|
103
96
|
|
|
104
|
-
function extractFileNameFromUrl(url: string, fallback: string) {
|
|
105
|
-
try {
|
|
106
|
-
const parsed = new URL(url, "https://copilotkit.local");
|
|
107
|
-
const pathname = parsed.pathname.split("/").filter(Boolean).pop();
|
|
108
|
-
if (pathname) {
|
|
109
|
-
return decodeURIComponent(pathname);
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
return fallback;
|
|
113
|
-
}
|
|
114
|
-
return fallback;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function triggerDownload(href: string, fileName: string) {
|
|
118
|
-
if (typeof document === "undefined") {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const anchor = document.createElement("a");
|
|
122
|
-
anchor.href = href;
|
|
123
|
-
anchor.download = fileName;
|
|
124
|
-
anchor.rel = "noopener noreferrer";
|
|
125
|
-
document.body.appendChild(anchor);
|
|
126
|
-
anchor.click();
|
|
127
|
-
document.body.removeChild(anchor);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
type MarkdownTableData = {
|
|
131
|
-
headers: string[];
|
|
132
|
-
rows: string[][];
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
function extractTableData(table: HTMLTableElement): MarkdownTableData {
|
|
136
|
-
const headers = Array.from(table.querySelectorAll("thead th")).map((cell) =>
|
|
137
|
-
(cell.textContent ?? "").trim(),
|
|
138
|
-
);
|
|
139
|
-
const rows = Array.from(table.querySelectorAll("tbody tr")).map((row) =>
|
|
140
|
-
Array.from(row.querySelectorAll("td")).map((cell) =>
|
|
141
|
-
(cell.textContent ?? "").trim(),
|
|
142
|
-
),
|
|
143
|
-
);
|
|
144
|
-
return { headers, rows };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function toDelimitedTable(
|
|
148
|
-
data: MarkdownTableData,
|
|
149
|
-
delimiter: "," | "\t",
|
|
150
|
-
): string {
|
|
151
|
-
const escapeCell = (value: string): string => {
|
|
152
|
-
const needsQuotes =
|
|
153
|
-
value.includes(delimiter) || value.includes('"') || value.includes("\n");
|
|
154
|
-
const escaped = value.replace(/"/g, '""');
|
|
155
|
-
return needsQuotes ? `"${escaped}"` : escaped;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const lines: string[] = [];
|
|
159
|
-
if (data.headers.length > 0) {
|
|
160
|
-
lines.push(data.headers.map(escapeCell).join(delimiter));
|
|
161
|
-
}
|
|
162
|
-
for (const row of data.rows) {
|
|
163
|
-
lines.push(row.map(escapeCell).join(delimiter));
|
|
164
|
-
}
|
|
165
|
-
return lines.join("\n");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function toMarkdownTable(data: MarkdownTableData): string {
|
|
169
|
-
if (data.headers.length === 0) {
|
|
170
|
-
return data.rows.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const separator = `| ${data.headers.map(() => "---").join(" | ")} |`;
|
|
174
|
-
const body = data.rows.map((row) => `| ${row.join(" | ")} |`);
|
|
175
|
-
return [`| ${data.headers.join(" | ")} |`, separator, ...body].join("\n");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function triggerBlobDownload(
|
|
179
|
-
content: string,
|
|
180
|
-
fileName: string,
|
|
181
|
-
mimeType: string,
|
|
182
|
-
) {
|
|
183
|
-
if (typeof document === "undefined") {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const blob = new Blob([content], { type: mimeType });
|
|
187
|
-
const url = URL.createObjectURL(blob);
|
|
188
|
-
triggerDownload(url, fileName);
|
|
189
|
-
URL.revokeObjectURL(url);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const MarkdownImage = defineComponent({
|
|
193
|
-
name: "CopilotMarkdownImage",
|
|
194
|
-
inheritAttrs: false,
|
|
195
|
-
props: {
|
|
196
|
-
src: {
|
|
197
|
-
type: String,
|
|
198
|
-
default: "",
|
|
199
|
-
},
|
|
200
|
-
alt: {
|
|
201
|
-
type: String,
|
|
202
|
-
default: "",
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
setup(imageProps, { attrs }) {
|
|
206
|
-
async function handleDownload() {
|
|
207
|
-
if (!imageProps.src) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const fileName = extractFileNameFromUrl(imageProps.src, "image");
|
|
212
|
-
try {
|
|
213
|
-
const response = await fetch(imageProps.src);
|
|
214
|
-
if (!response.ok) {
|
|
215
|
-
throw new Error("Failed to fetch image");
|
|
216
|
-
}
|
|
217
|
-
const blob = await response.blob();
|
|
218
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
219
|
-
triggerDownload(objectUrl, fileName);
|
|
220
|
-
URL.revokeObjectURL(objectUrl);
|
|
221
|
-
} catch {
|
|
222
|
-
triggerDownload(imageProps.src, fileName);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return () => {
|
|
227
|
-
const imageAttrs = {
|
|
228
|
-
...attrs,
|
|
229
|
-
src: imageProps.src,
|
|
230
|
-
alt: imageProps.alt,
|
|
231
|
-
class: ["cpk:max-w-full cpk:rounded-lg", attrs.class]
|
|
232
|
-
.filter(Boolean)
|
|
233
|
-
.join(" "),
|
|
234
|
-
"data-streamdown": "image",
|
|
235
|
-
} as Record<string, unknown>;
|
|
236
|
-
|
|
237
|
-
delete imageAttrs.className;
|
|
238
|
-
|
|
239
|
-
return h(
|
|
240
|
-
"div",
|
|
241
|
-
{
|
|
242
|
-
class: "cpk:group cpk:relative cpk:my-4 cpk:inline-block",
|
|
243
|
-
"data-streamdown": "image-wrapper",
|
|
244
|
-
},
|
|
245
|
-
[
|
|
246
|
-
h("img", imageAttrs),
|
|
247
|
-
h("div", {
|
|
248
|
-
class:
|
|
249
|
-
"cpk:pointer-events-none cpk:absolute cpk:inset-0 cpk:hidden cpk:rounded-lg cpk:bg-black/10 cpk:group-hover:block",
|
|
250
|
-
}),
|
|
251
|
-
h(
|
|
252
|
-
"button",
|
|
253
|
-
{
|
|
254
|
-
type: "button",
|
|
255
|
-
class:
|
|
256
|
-
"cpk:absolute cpk:right-2 cpk:bottom-2 cpk:flex cpk:h-8 cpk:w-8 cpk:cursor-pointer cpk:items-center cpk:justify-center cpk:rounded-md cpk:border cpk:border-border cpk:bg-background/90 cpk:shadow-sm cpk:backdrop-blur-sm cpk:transition-all cpk:duration-200 cpk:hover:bg-background cpk:opacity-0 cpk:group-hover:opacity-100",
|
|
257
|
-
title: "Download image",
|
|
258
|
-
onClick: handleDownload,
|
|
259
|
-
},
|
|
260
|
-
[h(IconDownload, { class: "cpk:size-[14px]" })],
|
|
261
|
-
),
|
|
262
|
-
],
|
|
263
|
-
);
|
|
264
|
-
};
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const tableIconButtonClass =
|
|
269
|
-
"cpk:cursor-pointer cpk:p-1 cpk:text-muted-foreground cpk:transition-all cpk:hover:text-foreground cpk:disabled:cursor-not-allowed cpk:disabled:opacity-50";
|
|
270
|
-
const tableMenuClass =
|
|
271
|
-
"cpk:absolute cpk:top-full cpk:right-0 cpk:z-10 cpk:mt-1 cpk:min-w-[120px] cpk:overflow-hidden cpk:rounded-md cpk:border cpk:border-border cpk:bg-background cpk:shadow-lg";
|
|
272
|
-
const tableMenuItemClass =
|
|
273
|
-
"cpk:w-full cpk:px-3 cpk:py-2 cpk:text-left cpk:text-sm cpk:transition-colors cpk:hover:bg-muted/40";
|
|
274
|
-
|
|
275
|
-
const MarkdownTable = defineComponent({
|
|
276
|
-
name: "CopilotMarkdownTable",
|
|
277
|
-
inheritAttrs: false,
|
|
278
|
-
setup(_, { attrs, slots }) {
|
|
279
|
-
const wrapperRef = ref<HTMLElement | null>(null);
|
|
280
|
-
const showCopyMenu = ref(false);
|
|
281
|
-
const showDownloadMenu = ref(false);
|
|
282
|
-
const tableCopied = ref(false);
|
|
283
|
-
let tableCopiedResetTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
284
|
-
|
|
285
|
-
const closeMenus = () => {
|
|
286
|
-
showCopyMenu.value = false;
|
|
287
|
-
showDownloadMenu.value = false;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const setTableCopiedStateWithDelay = () => {
|
|
291
|
-
if (tableCopiedResetTimeout) {
|
|
292
|
-
clearTimeout(tableCopiedResetTimeout);
|
|
293
|
-
}
|
|
294
|
-
tableCopied.value = true;
|
|
295
|
-
tableCopiedResetTimeout = setTimeout(() => {
|
|
296
|
-
tableCopied.value = false;
|
|
297
|
-
tableCopiedResetTimeout = null;
|
|
298
|
-
}, 2000);
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
const findTable = (): HTMLTableElement | null => {
|
|
302
|
-
if (!wrapperRef.value) return null;
|
|
303
|
-
return wrapperRef.value.querySelector("table");
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const getTableData = (): MarkdownTableData | null => {
|
|
307
|
-
const table = findTable();
|
|
308
|
-
if (!table) return null;
|
|
309
|
-
return extractTableData(table);
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const copyTableAs = async (format: "csv" | "tsv") => {
|
|
313
|
-
const data = getTableData();
|
|
314
|
-
if (!data) return;
|
|
315
|
-
|
|
316
|
-
const delimiter = format === "csv" ? "," : "\t";
|
|
317
|
-
const text = toDelimitedTable(data, delimiter);
|
|
318
|
-
|
|
319
|
-
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
320
|
-
try {
|
|
321
|
-
await navigator.clipboard.writeText(text);
|
|
322
|
-
} catch {
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
closeMenus();
|
|
328
|
-
setTableCopiedStateWithDelay();
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const downloadTableAs = (format: "csv" | "markdown") => {
|
|
332
|
-
const data = getTableData();
|
|
333
|
-
if (!data) return;
|
|
334
|
-
|
|
335
|
-
if (format === "csv") {
|
|
336
|
-
triggerBlobDownload(
|
|
337
|
-
toDelimitedTable(data, ","),
|
|
338
|
-
"table.csv",
|
|
339
|
-
"text/csv",
|
|
340
|
-
);
|
|
341
|
-
} else {
|
|
342
|
-
triggerBlobDownload(toMarkdownTable(data), "table.md", "text/markdown");
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
closeMenus();
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
349
|
-
if (!wrapperRef.value) return;
|
|
350
|
-
const target = event.target as Node | null;
|
|
351
|
-
if (target && !wrapperRef.value.contains(target)) {
|
|
352
|
-
closeMenus();
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
onMounted(() => {
|
|
357
|
-
if (typeof document !== "undefined") {
|
|
358
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
onBeforeUnmount(() => {
|
|
363
|
-
if (tableCopiedResetTimeout) {
|
|
364
|
-
clearTimeout(tableCopiedResetTimeout);
|
|
365
|
-
}
|
|
366
|
-
if (typeof document !== "undefined") {
|
|
367
|
-
document.removeEventListener("mousedown", handleClickOutside);
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
return () => {
|
|
372
|
-
const tableAttrs = {
|
|
373
|
-
...attrs,
|
|
374
|
-
class: [
|
|
375
|
-
"cpk:w-full cpk:border-collapse cpk:border cpk:border-border",
|
|
376
|
-
attrs.class,
|
|
377
|
-
]
|
|
378
|
-
.filter(Boolean)
|
|
379
|
-
.join(" "),
|
|
380
|
-
"data-streamdown": "table",
|
|
381
|
-
} as Record<string, unknown>;
|
|
382
|
-
|
|
383
|
-
delete tableAttrs.className;
|
|
384
|
-
|
|
385
|
-
return h(
|
|
386
|
-
"div",
|
|
387
|
-
{
|
|
388
|
-
ref: wrapperRef,
|
|
389
|
-
class: "cpk:my-4 cpk:flex cpk:flex-col cpk:space-y-2",
|
|
390
|
-
"data-streamdown": "table-wrapper",
|
|
391
|
-
},
|
|
392
|
-
[
|
|
393
|
-
h(
|
|
394
|
-
"div",
|
|
395
|
-
{ class: "cpk:flex cpk:items-center cpk:justify-end cpk:gap-1" },
|
|
396
|
-
[
|
|
397
|
-
h("div", { class: "cpk:relative" }, [
|
|
398
|
-
h(
|
|
399
|
-
"button",
|
|
400
|
-
{
|
|
401
|
-
type: "button",
|
|
402
|
-
class: tableIconButtonClass,
|
|
403
|
-
title: "Copy table",
|
|
404
|
-
onClick: () => {
|
|
405
|
-
showCopyMenu.value = !showCopyMenu.value;
|
|
406
|
-
showDownloadMenu.value = false;
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
[
|
|
410
|
-
tableCopied.value
|
|
411
|
-
? h(IconCheck, { class: "cpk:size-[14px]" })
|
|
412
|
-
: h(IconCopy, { class: "cpk:size-[14px]" }),
|
|
413
|
-
],
|
|
414
|
-
),
|
|
415
|
-
showCopyMenu.value
|
|
416
|
-
? h("div", { class: tableMenuClass }, [
|
|
417
|
-
h(
|
|
418
|
-
"button",
|
|
419
|
-
{
|
|
420
|
-
type: "button",
|
|
421
|
-
class: tableMenuItemClass,
|
|
422
|
-
title: "Copy table as CSV",
|
|
423
|
-
onClick: () => copyTableAs("csv"),
|
|
424
|
-
},
|
|
425
|
-
"CSV",
|
|
426
|
-
),
|
|
427
|
-
h(
|
|
428
|
-
"button",
|
|
429
|
-
{
|
|
430
|
-
type: "button",
|
|
431
|
-
class: tableMenuItemClass,
|
|
432
|
-
title: "Copy table as TSV",
|
|
433
|
-
onClick: () => copyTableAs("tsv"),
|
|
434
|
-
},
|
|
435
|
-
"TSV",
|
|
436
|
-
),
|
|
437
|
-
])
|
|
438
|
-
: null,
|
|
439
|
-
]),
|
|
440
|
-
h("div", { class: "cpk:relative" }, [
|
|
441
|
-
h(
|
|
442
|
-
"button",
|
|
443
|
-
{
|
|
444
|
-
type: "button",
|
|
445
|
-
class: tableIconButtonClass,
|
|
446
|
-
title: "Download table",
|
|
447
|
-
onClick: () => {
|
|
448
|
-
showDownloadMenu.value = !showDownloadMenu.value;
|
|
449
|
-
showCopyMenu.value = false;
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
[h(IconDownload, { class: "cpk:size-[14px]" })],
|
|
453
|
-
),
|
|
454
|
-
showDownloadMenu.value
|
|
455
|
-
? h("div", { class: tableMenuClass }, [
|
|
456
|
-
h(
|
|
457
|
-
"button",
|
|
458
|
-
{
|
|
459
|
-
type: "button",
|
|
460
|
-
class: tableMenuItemClass,
|
|
461
|
-
title: "Download table as CSV",
|
|
462
|
-
onClick: () => downloadTableAs("csv"),
|
|
463
|
-
},
|
|
464
|
-
"CSV",
|
|
465
|
-
),
|
|
466
|
-
h(
|
|
467
|
-
"button",
|
|
468
|
-
{
|
|
469
|
-
type: "button",
|
|
470
|
-
class: tableMenuItemClass,
|
|
471
|
-
title: "Download table as Markdown",
|
|
472
|
-
onClick: () => downloadTableAs("markdown"),
|
|
473
|
-
},
|
|
474
|
-
"Markdown",
|
|
475
|
-
),
|
|
476
|
-
])
|
|
477
|
-
: null,
|
|
478
|
-
]),
|
|
479
|
-
],
|
|
480
|
-
),
|
|
481
|
-
h("div", { class: "cpk:overflow-x-auto" }, [
|
|
482
|
-
h("table", tableAttrs, slots.default ? slots.default() : []),
|
|
483
|
-
]),
|
|
484
|
-
],
|
|
485
|
-
);
|
|
486
|
-
};
|
|
487
|
-
},
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
const codeActionButtonClass =
|
|
491
|
-
"cpk:cursor-pointer cpk:p-1 cpk:text-muted-foreground cpk:transition-all cpk:hover:text-foreground cpk:disabled:cursor-not-allowed cpk:disabled:opacity-50";
|
|
492
|
-
|
|
493
|
-
const codeLanguageExtensionMap: Record<string, string> = {
|
|
494
|
-
javascript: "js",
|
|
495
|
-
js: "js",
|
|
496
|
-
typescript: "ts",
|
|
497
|
-
ts: "ts",
|
|
498
|
-
json: "json",
|
|
499
|
-
vue: "vue",
|
|
500
|
-
html: "html",
|
|
501
|
-
css: "css",
|
|
502
|
-
md: "md",
|
|
503
|
-
markdown: "md",
|
|
504
|
-
sh: "sh",
|
|
505
|
-
bash: "sh",
|
|
506
|
-
py: "py",
|
|
507
|
-
python: "py",
|
|
508
|
-
go: "go",
|
|
509
|
-
rust: "rs",
|
|
510
|
-
rs: "rs",
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
const CodeBlockCopyAction = defineComponent({
|
|
514
|
-
name: "CopilotCodeBlockCopyAction",
|
|
515
|
-
props: {
|
|
516
|
-
code: {
|
|
517
|
-
type: String,
|
|
518
|
-
default: "",
|
|
519
|
-
},
|
|
520
|
-
},
|
|
521
|
-
setup(actionProps) {
|
|
522
|
-
const isCopied = ref(false);
|
|
523
|
-
let resetTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
524
|
-
|
|
525
|
-
const handleClick = async () => {
|
|
526
|
-
if (!actionProps.code) return;
|
|
527
|
-
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
528
|
-
try {
|
|
529
|
-
await navigator.clipboard.writeText(actionProps.code);
|
|
530
|
-
} catch {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (resetTimeout) {
|
|
536
|
-
clearTimeout(resetTimeout);
|
|
537
|
-
}
|
|
538
|
-
isCopied.value = true;
|
|
539
|
-
resetTimeout = setTimeout(() => {
|
|
540
|
-
isCopied.value = false;
|
|
541
|
-
resetTimeout = null;
|
|
542
|
-
}, 2000);
|
|
543
|
-
};
|
|
544
|
-
|
|
545
|
-
onBeforeUnmount(() => {
|
|
546
|
-
if (resetTimeout) {
|
|
547
|
-
clearTimeout(resetTimeout);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
return () =>
|
|
552
|
-
h(
|
|
553
|
-
"button",
|
|
554
|
-
{
|
|
555
|
-
type: "button",
|
|
556
|
-
class: codeActionButtonClass,
|
|
557
|
-
title: "Copy Code",
|
|
558
|
-
"data-streamdown": "code-block-copy-button",
|
|
559
|
-
onClick: handleClick,
|
|
560
|
-
},
|
|
561
|
-
[
|
|
562
|
-
isCopied.value
|
|
563
|
-
? h(IconCheck, { class: "cpk:size-[14px]" })
|
|
564
|
-
: h(IconCopy, { class: "cpk:size-[14px]" }),
|
|
565
|
-
],
|
|
566
|
-
);
|
|
567
|
-
},
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
const CodeBlockDownloadAction = defineComponent({
|
|
571
|
-
name: "CopilotCodeBlockDownloadAction",
|
|
572
|
-
props: {
|
|
573
|
-
code: {
|
|
574
|
-
type: String,
|
|
575
|
-
default: "",
|
|
576
|
-
},
|
|
577
|
-
language: {
|
|
578
|
-
type: String,
|
|
579
|
-
default: "",
|
|
580
|
-
},
|
|
581
|
-
},
|
|
582
|
-
setup(actionProps) {
|
|
583
|
-
const handleClick = () => {
|
|
584
|
-
if (!actionProps.code) return;
|
|
585
|
-
const extension =
|
|
586
|
-
codeLanguageExtensionMap[actionProps.language.toLowerCase()] ?? "txt";
|
|
587
|
-
triggerBlobDownload(actionProps.code, `file.${extension}`, "text/plain");
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
return () =>
|
|
591
|
-
h(
|
|
592
|
-
"button",
|
|
593
|
-
{
|
|
594
|
-
type: "button",
|
|
595
|
-
class: codeActionButtonClass,
|
|
596
|
-
title: "Download file",
|
|
597
|
-
"data-streamdown": "code-block-download-button",
|
|
598
|
-
onClick: handleClick,
|
|
599
|
-
},
|
|
600
|
-
[h(IconDownload, { class: "cpk:size-[14px]" })],
|
|
601
|
-
);
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
const markdownComponents = {
|
|
606
|
-
img: MarkdownImage,
|
|
607
|
-
table: MarkdownTable,
|
|
608
|
-
};
|
|
609
|
-
|
|
610
97
|
function normalizeContent(content: unknown): string {
|
|
611
98
|
if (!content) {
|
|
612
99
|
return "";
|
|
@@ -642,6 +129,22 @@ const normalizedContent = computed(() =>
|
|
|
642
129
|
normalizeContent(props.message.content),
|
|
643
130
|
);
|
|
644
131
|
const hasContent = computed(() => normalizedContent.value.trim().length > 0);
|
|
132
|
+
|
|
133
|
+
const providerMarkdownRenderer = useMarkdownRenderer();
|
|
134
|
+
const providerIsComponent = computed(() =>
|
|
135
|
+
isVueComponentRenderer(providerMarkdownRenderer),
|
|
136
|
+
);
|
|
137
|
+
const ActiveMarkdownRenderer = computed(() =>
|
|
138
|
+
providerIsComponent.value
|
|
139
|
+
? (providerMarkdownRenderer as Component)
|
|
140
|
+
: StreamingMarkdownDefault,
|
|
141
|
+
);
|
|
142
|
+
const activeMarkdownConfig = computed(() =>
|
|
143
|
+
providerIsComponent.value || !providerMarkdownRenderer
|
|
144
|
+
? {}
|
|
145
|
+
: (providerMarkdownRenderer as Record<string, unknown>),
|
|
146
|
+
);
|
|
147
|
+
|
|
645
148
|
const hasThumbsUp = computed(() => typeof props.onThumbsUp === "function");
|
|
646
149
|
const hasThumbsDown = computed(() => typeof props.onThumbsDown === "function");
|
|
647
150
|
const hasReadAloud = computed(() => typeof props.onReadAloud === "function");
|
|
@@ -743,24 +246,12 @@ onBeforeUnmount(() => {
|
|
|
743
246
|
:message="message"
|
|
744
247
|
:content="normalizedContent"
|
|
745
248
|
>
|
|
746
|
-
<
|
|
249
|
+
<component
|
|
250
|
+
:is="ActiveMarkdownRenderer"
|
|
747
251
|
v-if="hasContent"
|
|
748
|
-
|
|
252
|
+
v-bind="activeMarkdownConfig"
|
|
749
253
|
:content="normalizedContent"
|
|
750
|
-
:
|
|
751
|
-
:code-block-actions="[CodeBlockDownloadAction, CodeBlockCopyAction]"
|
|
752
|
-
:code-block-show-line-numbers="false"
|
|
753
|
-
:code-block-hide-copy="true"
|
|
754
|
-
:code-block-hide-download="true"
|
|
755
|
-
:allowed-link-prefixes="[
|
|
756
|
-
'https://',
|
|
757
|
-
'http://',
|
|
758
|
-
'#',
|
|
759
|
-
'/',
|
|
760
|
-
'./',
|
|
761
|
-
'../',
|
|
762
|
-
]"
|
|
763
|
-
:shiki-theme="{ light: 'github-light', dark: 'github-dark' }"
|
|
254
|
+
:is-streaming="isRunning && isLatestAssistantMessage"
|
|
764
255
|
/>
|
|
765
256
|
</slot>
|
|
766
257
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
|
3
3
|
import type { Message, ReasoningMessage } from "@ag-ui/core";
|
|
4
|
-
import { StreamMarkdown } from "streamdown-vue";
|
|
5
4
|
import { IconChevronRight } from "../icons";
|
|
5
|
+
import StreamingMarkdownDefault from "./StreamingMarkdownDefault.vue";
|
|
6
6
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
7
7
|
import { CopilotChatDefaultLabels } from "../../providers/types";
|
|
8
8
|
|
|
@@ -237,7 +237,12 @@ function toggleOpen() {
|
|
|
237
237
|
>
|
|
238
238
|
<div v-if="hasContent || isStreaming" class="cpk:pb-2 cpk:pt-1">
|
|
239
239
|
<div class="cpk:text-sm cpk:text-muted-foreground">
|
|
240
|
-
|
|
240
|
+
<!-- Reasoning content uses the streaming renderer (same as assistant);
|
|
241
|
+
the pluggable provider renderer applies to assistant messages only. -->
|
|
242
|
+
<StreamingMarkdownDefault
|
|
243
|
+
:content="normalizedContent"
|
|
244
|
+
:is-streaming="isStreaming"
|
|
245
|
+
/>
|
|
241
246
|
<span
|
|
242
247
|
v-if="isStreaming && hasContent"
|
|
243
248
|
class="cpk:inline-flex cpk:items-center cpk:ml-1 cpk:align-middle"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, h, watchEffect } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
StreamingMarkdownRenderer,
|
|
5
|
+
type VueStreamingMarkdownNodeRenderer,
|
|
6
|
+
} from "@copilotkit/markdown-renderer/vue";
|
|
7
|
+
import { warnUnsupportedRichSyntaxOnce } from "@copilotkit/markdown-renderer";
|
|
8
|
+
import type { StreamingMarkdownParserOptions } from "@copilotkit/markdown-renderer";
|
|
9
|
+
import type { DefaultMarkdownRendererProps } from "../../providers/markdown-renderer";
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<
|
|
13
|
+
{ content: string; isStreaming?: boolean } & DefaultMarkdownRendererProps
|
|
14
|
+
>(),
|
|
15
|
+
{ isStreaming: false },
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const resolvedOptions = computed(
|
|
19
|
+
() => props.options as StreamingMarkdownParserOptions | undefined,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const defaultCodeBlockRenderer: VueStreamingMarkdownNodeRenderer = (
|
|
23
|
+
node,
|
|
24
|
+
defaultVNode,
|
|
25
|
+
) => {
|
|
26
|
+
if (node.type !== "code-block") return defaultVNode;
|
|
27
|
+
return h(
|
|
28
|
+
"pre",
|
|
29
|
+
{
|
|
30
|
+
"data-streaming-markdown-node": "code-block",
|
|
31
|
+
"data-node-open": String(!node.closed),
|
|
32
|
+
class: "cpk:overflow-x-auto cpk:rounded-lg cpk:p-3",
|
|
33
|
+
},
|
|
34
|
+
[h("code", { "data-code-info": node.info ?? undefined }, node.text)],
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mergedNodeRenderers = computed(() => ({
|
|
39
|
+
codeBlock: defaultCodeBlockRenderer,
|
|
40
|
+
...props.nodeRenderers,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
// Dev-only: nudge upgraders from the bundled Streamdown default when their
|
|
44
|
+
// content needs math/syntax highlighting the built-in renderer doesn't do.
|
|
45
|
+
watchEffect(() => warnUnsupportedRichSyntaxOnce(props.content));
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<StreamingMarkdownRenderer
|
|
50
|
+
:content="props.content"
|
|
51
|
+
:is-complete="!props.isStreaming"
|
|
52
|
+
:node-renderers="mergedNodeRenderers"
|
|
53
|
+
:caret="props.caret ?? props.isStreaming"
|
|
54
|
+
:options="resolvedOptions"
|
|
55
|
+
:class="props.class"
|
|
56
|
+
/>
|
|
57
|
+
</template>
|