@meistrari/chat-nuxt 3.16.0-rc.0 → 3.16.0-rc.2
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 +4 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -22
- package/dist/runtime/conversations/components/chat-conversation-item.vue +1 -1
- package/dist/runtime/messages/components/chat-message-bubble.vue +15 -20
- package/dist/runtime/messages/components/chat-message-list.d.vue.ts +2 -2
- package/dist/runtime/messages/components/chat-message-list.vue +31 -10
- package/dist/runtime/messages/components/chat-message-list.vue.d.ts +2 -2
- package/dist/runtime/messages/composables/chat.js +3 -8
- package/dist/runtime/messages/composables/use-direct-stream.js +7 -7
- package/dist/runtime/messages/message-reasoning.d.ts +9 -2
- package/dist/runtime/messages/message-reasoning.js +30 -13
- package/dist/runtime/rendering/components/app-markdown-render.d.vue.ts +2 -0
- package/dist/runtime/rendering/components/app-markdown-render.vue +7 -21
- package/dist/runtime/rendering/components/app-markdown-render.vue.d.ts +2 -0
- package/dist/runtime/rendering/markdown/custom-components.d.ts +2 -0
- package/dist/runtime/rendering/markdown/custom-components.js +8 -2
- package/package.json +3 -4
- package/dist/runtime/messages/composables/chat-scroll-stickiness.d.ts +0 -28
- package/dist/runtime/messages/composables/chat-scroll-stickiness.js +0 -181
- package/dist/runtime/rendering/components/renderers/mermaid-diagram.d.vue.ts +0 -7
- package/dist/runtime/rendering/components/renderers/mermaid-diagram.vue +0 -168
- package/dist/runtime/rendering/components/renderers/mermaid-diagram.vue.d.ts +0 -7
- package/dist/runtime/rendering/composables/diagram-export.d.ts +0 -11
- package/dist/runtime/rendering/composables/diagram-export.js +0 -187
- package/dist/runtime/rendering/composables/mermaid-renderer.d.ts +0 -5
- package/dist/runtime/rendering/composables/mermaid-renderer.js +0 -121
- package/dist/runtime/rendering/composables/zoom-pan.d.ts +0 -18
- package/dist/runtime/rendering/composables/zoom-pan.js +0 -130
- package/dist/runtime/rendering/markdown/nodes.d.ts +0 -23
- package/dist/runtime/rendering/markdown/nodes.js +0 -46
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload
|
|
|
65
65
|
| `features` | `Partial<ChatFeatureConfig>` | `{}` | Toggle `showUsageTab`, `showDebugOption`, `showCancelButton`, `showTtft`, and `chatOutboundQueue`; all released features default to `true` |
|
|
66
66
|
| `loadingMessages` | `readonly string[] \| null` | Default chat messages | Override pending-response messages |
|
|
67
67
|
| `loadingMessagesMode` | `'ordered' \| 'random' \| null` | `'ordered'` | Rotate custom messages in order or at random; only applies when `loadingMessages` has a non-empty message |
|
|
68
|
-
| `customComponents` | `Record<string, Component>` | `undefined` | Custom markdown renderers keyed by markdown node or tag name |
|
|
68
|
+
| `customComponents` | `Record<string, Component>` | `undefined` | Custom markdown renderers keyed by markdown node or tag name. Components receive `node` plus the parser lifecycle props `loading` and `autoClosed` |
|
|
69
69
|
| `customHtmlTags` | `readonly string[]` | `undefined` | Extra HTML tags allowed by the markdown renderer |
|
|
70
70
|
| `citations` | `ChatCitationsConfig \| null` | `undefined` | Enables `cite://` citations in assistant messages (see [Citations](docs/guides/citations.md)) |
|
|
71
71
|
| `feedbackConfig` | `MessageFeedbackConfig \| null` | `undefined` | Enables 👍/👎 feedback on assistant messages; providing the prop (even `{}`) shows the controls |
|
|
@@ -111,14 +111,14 @@ Installing `@meistrari/chat-nuxt` installs the runtime packages used by the laye
|
|
|
111
111
|
| `@iconify/vue` + `@iconify-json/ph` | `^5.0.0` / `^1.2.2` | Icon rendering support (Phosphor set) |
|
|
112
112
|
| `@vueuse/core` | `^12.8.0` | Runtime composables used by chat UI |
|
|
113
113
|
| `@vueuse/components` | `^12.8.0` | VueUse component peer expected by the shared runtime |
|
|
114
|
-
| `
|
|
115
|
-
| `dompurify` | `^3.3.2` | HTML sanitization for rendered markdown |
|
|
114
|
+
| `mermaid` | `^11.13.0` | Native markstream Mermaid diagram rendering |
|
|
116
115
|
| `h3` | `^1.15.0` | Server route utilities provided by Nuxt/Nitro |
|
|
117
|
-
| `markstream-vue` | `
|
|
116
|
+
| `markstream-vue` | `2.0.7` | Markdown streaming renderer |
|
|
118
117
|
| `motion` | `^11.13.0` | Animation component runtime |
|
|
119
118
|
| `nitropack` | `^2.10.0` | Runtime config and task utilities provided by Nuxt/Nitro |
|
|
120
119
|
| `pdfjs-dist` | `^5.4.530` | PDF file previews |
|
|
121
120
|
| `posthog-js` | `^1.364.2` | Optional PostHog feature-flag helper |
|
|
121
|
+
| `stream-diffs` | `0.0.2` | Native markstream rich code and diff rendering |
|
|
122
122
|
| `virtua` | `^0.42.0` | Virtualized list runtime expected by shared UI |
|
|
123
123
|
| `vue-i18n` | `^11.0.0` | Internationalization runtime expected by shared UI |
|
|
124
124
|
| `xlsx` | `^0.18.5` | Spreadsheet file previews |
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -145,8 +145,12 @@ function patchStreamMarkdownParserSource(source) {
|
|
|
145
145
|
}
|
|
146
146
|
function resolvePatchedStreamMarkdownParser(from) {
|
|
147
147
|
try {
|
|
148
|
-
const
|
|
149
|
-
const
|
|
148
|
+
const markstreamStyles = createRequire(from).resolve("markstream-vue/index.css");
|
|
149
|
+
const markstreamRoot = dirname(dirname(markstreamStyles));
|
|
150
|
+
const parserRequireEntry = createRequire(join(markstreamRoot, "package.json")).resolve("stream-markdown-parser");
|
|
151
|
+
const parserRoot = dirname(dirname(parserRequireEntry));
|
|
152
|
+
const parserPackage = JSON.parse(readFileSync(join(parserRoot, "package.json"), "utf8"));
|
|
153
|
+
const entry = parserPackage.module ? join(parserRoot, parserPackage.module) : parserRequireEntry;
|
|
150
154
|
const { source, applied } = patchStreamMarkdownParserSource(readFileSync(entry, "utf8"));
|
|
151
155
|
return { entry, source, applied };
|
|
152
156
|
} catch {
|
|
@@ -508,7 +512,6 @@ const module$1 = defineNuxtModule({
|
|
|
508
512
|
if (isServer) {
|
|
509
513
|
return;
|
|
510
514
|
}
|
|
511
|
-
config.plugins = config.plugins || [];
|
|
512
515
|
if (Array.isArray(config.resolve.alias)) {
|
|
513
516
|
config.resolve.alias.push(...Object.entries(runtimeAliases).map(([find, replacement]) => ({ find, replacement })));
|
|
514
517
|
} else {
|
|
@@ -521,25 +524,6 @@ const module$1 = defineNuxtModule({
|
|
|
521
524
|
...config.resolve.dedupe || [],
|
|
522
525
|
...runtimeAliasDedupe
|
|
523
526
|
]));
|
|
524
|
-
config.plugins.push({
|
|
525
|
-
name: "@meistrari/chat-nuxt:elkjs-umd-interop",
|
|
526
|
-
enforce: "pre",
|
|
527
|
-
apply: "serve",
|
|
528
|
-
load(id) {
|
|
529
|
-
const cleanId = id.split("?")[0] ?? id;
|
|
530
|
-
if (!cleanId.endsWith("/elkjs/lib/elk.bundled.js")) {
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
try {
|
|
534
|
-
const source = readFileSync(cleanId, "utf8");
|
|
535
|
-
return `${source}
|
|
536
|
-
export default globalThis.ELK;
|
|
537
|
-
`;
|
|
538
|
-
} catch {
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
527
|
});
|
|
544
528
|
const serverDir = join(runtimeDir, "server");
|
|
545
529
|
const publicAssetsDir = join(runtimeDir, "public/assets");
|
|
@@ -4,7 +4,7 @@ const props = defineProps({
|
|
|
4
4
|
conversation: { type: null, required: true },
|
|
5
5
|
active: { type: Boolean, required: true },
|
|
6
6
|
draggable: { type: Boolean, required: false },
|
|
7
|
-
itemClass: { type: null, required: false },
|
|
7
|
+
itemClass: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
|
|
8
8
|
userImage: { type: String, required: false },
|
|
9
9
|
selectConversation: { type: Function, required: false }
|
|
10
10
|
});
|
|
@@ -56,16 +56,6 @@ function formatTime(date) {
|
|
|
56
56
|
const d = date instanceof Date ? date : new Date(date);
|
|
57
57
|
return d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
|
|
58
58
|
}
|
|
59
|
-
const wasEverPending = ref(false);
|
|
60
|
-
const shouldAnimate = ref(false);
|
|
61
|
-
watch(() => props.message.status, (status, oldStatus) => {
|
|
62
|
-
if (status === "pending") {
|
|
63
|
-
wasEverPending.value = true;
|
|
64
|
-
}
|
|
65
|
-
if (oldStatus === "pending" && status === "completed" && wasEverPending.value) {
|
|
66
|
-
shouldAnimate.value = true;
|
|
67
|
-
}
|
|
68
|
-
}, { immediate: true });
|
|
69
59
|
const displayContent = computed(() => props.message.content ?? "");
|
|
70
60
|
const userContentHtml = computed(() => {
|
|
71
61
|
if (props.message.role !== "user")
|
|
@@ -180,13 +170,12 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
180
170
|
|
|
181
171
|
<template v-else>
|
|
182
172
|
<!--
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
exactly what they cover.
|
|
173
|
+
Mounted for the whole pending turn so activity remains visible
|
|
174
|
+
while the current streamed chunk is empty between assistant
|
|
175
|
+
messages or tool phases.
|
|
187
176
|
-->
|
|
188
177
|
<ChatReasoningThought
|
|
189
|
-
v-if="hasReasoning ||
|
|
178
|
+
v-if="hasReasoning || isPending"
|
|
190
179
|
:reasoning-data="message.reasoningData ?? []"
|
|
191
180
|
:completed="!isPending"
|
|
192
181
|
:preparing-steps="preparingSteps"
|
|
@@ -198,7 +187,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
198
187
|
v-if="message.content && (!isPending || streamActive)"
|
|
199
188
|
max-w-prose
|
|
200
189
|
:class="[
|
|
201
|
-
isFailed ? 'flex gap-12px items-start bg-subtle border border-solid rounded-12px w-full' : 'rounded-12px
|
|
190
|
+
isFailed ? 'flex gap-12px items-start bg-subtle border border-solid rounded-12px w-full' : 'rounded-12px'
|
|
202
191
|
]"
|
|
203
192
|
>
|
|
204
193
|
<template v-if="isFailed">
|
|
@@ -246,8 +235,11 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
246
235
|
:content="segment.content"
|
|
247
236
|
:custom-components="mergedCustomComponents"
|
|
248
237
|
:custom-html-tags="mergedCustomHtmlTags"
|
|
249
|
-
:
|
|
250
|
-
:
|
|
238
|
+
:fade="true"
|
|
239
|
+
:final="!isPending"
|
|
240
|
+
:smooth-streaming="isPending ? 'auto' : false"
|
|
241
|
+
:typewriter="isPending"
|
|
242
|
+
mode="chat"
|
|
251
243
|
/>
|
|
252
244
|
</ClientOnly>
|
|
253
245
|
<ChatFilePreview
|
|
@@ -269,8 +261,11 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
269
261
|
:content="displayContent"
|
|
270
262
|
:custom-components="mergedCustomComponents"
|
|
271
263
|
:custom-html-tags="mergedCustomHtmlTags"
|
|
272
|
-
:
|
|
273
|
-
:
|
|
264
|
+
:fade="true"
|
|
265
|
+
:final="!isPending"
|
|
266
|
+
:smooth-streaming="isPending ? 'auto' : false"
|
|
267
|
+
:typewriter="isPending"
|
|
268
|
+
mode="chat"
|
|
274
269
|
text-black-900
|
|
275
270
|
/>
|
|
276
271
|
</ClientOnly>
|
|
@@ -13,8 +13,8 @@ type __VLS_Props = {
|
|
|
13
13
|
showTtft?: boolean;
|
|
14
14
|
};
|
|
15
15
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
16
|
-
isPinned:
|
|
17
|
-
scrollToBottom:
|
|
16
|
+
isPinned: any;
|
|
17
|
+
scrollToBottom: any;
|
|
18
18
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
19
19
|
retry: (messageId: string) => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { useStickToBottom } from "markstream-vue/utils";
|
|
3
3
|
const props = defineProps({
|
|
4
4
|
messages: { type: Array, required: true },
|
|
5
5
|
loading: { type: Boolean, required: false },
|
|
@@ -10,22 +10,42 @@ const props = defineProps({
|
|
|
10
10
|
});
|
|
11
11
|
const emit = defineEmits(["retry"]);
|
|
12
12
|
const conversationRef = ref(null);
|
|
13
|
-
const
|
|
13
|
+
const conversationElements = computed(() => {
|
|
14
|
+
const root = conversationRef.value?.$el;
|
|
15
|
+
if (!(root instanceof HTMLElement))
|
|
16
|
+
return null;
|
|
17
|
+
const content = root.matches("[data-chat-conversation]") ? root : root.querySelector("[data-chat-conversation]");
|
|
18
|
+
if (!content)
|
|
19
|
+
return null;
|
|
20
|
+
const scroll = root.matches("[data-reka-scroll-area-viewport]") ? root : root.querySelector("[data-reka-scroll-area-viewport]") ?? content;
|
|
21
|
+
return {
|
|
22
|
+
content: content.firstElementChild ?? content,
|
|
23
|
+
scroll
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
const scrollRoot = computed(() => conversationElements.value?.scroll ?? null);
|
|
27
|
+
const contentRoot = computed(() => conversationElements.value?.content ?? null);
|
|
28
|
+
const {
|
|
29
|
+
bottomPinned: isPinned,
|
|
30
|
+
scheduleScrollToBottom,
|
|
31
|
+
scrollToBottom
|
|
32
|
+
} = useStickToBottom(scrollRoot, contentRoot);
|
|
14
33
|
const skeletonMessages = [
|
|
15
34
|
{ id: "user-1", align: "end", width: "58%", height: "44px" },
|
|
16
35
|
{ id: "assistant-1", align: "start", width: "92%", height: "116px" },
|
|
17
36
|
{ id: "user-2", align: "end", width: "46%", height: "44px" },
|
|
18
37
|
{ id: "assistant-2", align: "start", width: "84%", height: "92px" }
|
|
19
38
|
];
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
knownMessageIds = new Set(props.messages.map((message) => message.id));
|
|
26
|
-
if (hasNewUserMessage)
|
|
27
|
-
scrollToBottom();
|
|
39
|
+
const latestOutputSignal = computed(() => {
|
|
40
|
+
const latestMessage = props.messages.at(-1);
|
|
41
|
+
if (!latestMessage)
|
|
42
|
+
return "empty";
|
|
43
|
+
return `${props.messages.length}:${latestMessage.id}:${latestMessage.status}:${latestMessage.content?.length ?? 0}`;
|
|
28
44
|
});
|
|
45
|
+
watch(latestOutputSignal, async () => {
|
|
46
|
+
await nextTick();
|
|
47
|
+
scheduleScrollToBottom();
|
|
48
|
+
}, { flush: "post" });
|
|
29
49
|
defineExpose({
|
|
30
50
|
isPinned,
|
|
31
51
|
scrollToBottom
|
|
@@ -43,6 +63,7 @@ defineExpose({
|
|
|
43
63
|
>
|
|
44
64
|
<TelaChatConversation
|
|
45
65
|
ref="conversationRef"
|
|
66
|
+
:scroll-area="variant === 'widget'"
|
|
46
67
|
:class="{
|
|
47
68
|
'pt-72px!': (variant ?? 'page') === 'page',
|
|
48
69
|
'pt-72px pb-24px px-16px': variant === 'mobile'
|
|
@@ -13,8 +13,8 @@ type __VLS_Props = {
|
|
|
13
13
|
showTtft?: boolean;
|
|
14
14
|
};
|
|
15
15
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
16
|
-
isPinned:
|
|
17
|
-
scrollToBottom:
|
|
16
|
+
isPinned: any;
|
|
17
|
+
scrollToBottom: any;
|
|
18
18
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
19
19
|
retry: (messageId: string) => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
@@ -207,12 +207,10 @@ export function useChat(options = {}) {
|
|
|
207
207
|
);
|
|
208
208
|
const reasoningCache = createReasoningCache();
|
|
209
209
|
const processedMessages = computed(() => {
|
|
210
|
-
const resolved = resolveReasoningDisplayMessages(messages.value, reasoningCache);
|
|
211
210
|
const messageId = streamingMessageId.value;
|
|
212
211
|
const content = directStream.streamingText.value;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return resolved.map((message) => message.id === messageId && (message.status === "pending" || pendingStreamReconciliationIds.value.has(message.id)) ? { ...message, content: resolveStreamingContent(message.content, content) } : message);
|
|
212
|
+
const overlaid = !messageId || content === null ? messages.value : messages.value.map((message) => message.id === messageId && (message.status === "pending" || pendingStreamReconciliationIds.value.has(message.id)) ? { ...message, content } : message);
|
|
213
|
+
return resolveReasoningDisplayMessages(overlaid, reasoningCache);
|
|
216
214
|
});
|
|
217
215
|
const stopCondition = computed(
|
|
218
216
|
() => !hasPendingMessage.value && pendingStreamReconciliationIds.value.size === 0
|
|
@@ -529,9 +527,9 @@ export function useChat(options = {}) {
|
|
|
529
527
|
agentSessionExecutable.value = false;
|
|
530
528
|
agentSessionRewakeable.value = false;
|
|
531
529
|
}
|
|
530
|
+
startPolling();
|
|
532
531
|
};
|
|
533
532
|
let response;
|
|
534
|
-
let acceptedByStream = false;
|
|
535
533
|
try {
|
|
536
534
|
response = await directStream.connectMessage(
|
|
537
535
|
chatApi.path(`/conversations/${currentConversation.value.id}/messages/stream`),
|
|
@@ -542,7 +540,6 @@ export function useChat(options = {}) {
|
|
|
542
540
|
}),
|
|
543
541
|
acceptMessagePair
|
|
544
542
|
);
|
|
545
|
-
acceptedByStream = true;
|
|
546
543
|
} catch (streamError) {
|
|
547
544
|
const statusCode = getChatApiErrorStatus(streamError);
|
|
548
545
|
if (statusCode !== null && statusCode >= 400 && statusCode < 500)
|
|
@@ -565,8 +562,6 @@ export function useChat(options = {}) {
|
|
|
565
562
|
"user"
|
|
566
563
|
);
|
|
567
564
|
}
|
|
568
|
-
if (!acceptedByStream)
|
|
569
|
-
startPolling();
|
|
570
565
|
return response.userMessage;
|
|
571
566
|
} catch (err) {
|
|
572
567
|
const code = getChatApiErrorCode(err);
|
|
@@ -115,7 +115,7 @@ function reviveMessagePair(input) {
|
|
|
115
115
|
}
|
|
116
116
|
export function useDirectStream(options = {}) {
|
|
117
117
|
const activity = ref("idle");
|
|
118
|
-
const streamingText = ref(
|
|
118
|
+
const streamingText = ref(null);
|
|
119
119
|
const streamingThinking = ref("");
|
|
120
120
|
const activeRunId = ref(null);
|
|
121
121
|
let accumulator = createMessagePartsAccumulator();
|
|
@@ -123,7 +123,7 @@ export function useDirectStream(options = {}) {
|
|
|
123
123
|
function resetAccumulator(runId = null) {
|
|
124
124
|
accumulator = createMessagePartsAccumulator(runId);
|
|
125
125
|
activeRunId.value = runId;
|
|
126
|
-
streamingText.value =
|
|
126
|
+
streamingText.value = null;
|
|
127
127
|
streamingThinking.value = "";
|
|
128
128
|
}
|
|
129
129
|
function beginMessage() {
|
|
@@ -199,7 +199,7 @@ export function useDirectStream(options = {}) {
|
|
|
199
199
|
if (status === "completed" || status === "waiting_messages") {
|
|
200
200
|
const result = {
|
|
201
201
|
status,
|
|
202
|
-
text: streamingText.value
|
|
202
|
+
text: streamingText.value ?? ""
|
|
203
203
|
};
|
|
204
204
|
if (!optimisticResultReported) {
|
|
205
205
|
optimisticResultReported = true;
|
|
@@ -287,11 +287,11 @@ export function useDirectStream(options = {}) {
|
|
|
287
287
|
if (event.kind !== "message-parts" || typeof event.runId !== "string" || typeof event.deltaSeq !== "number" || !Number.isInteger(event.deltaSeq) || event.deltaSeq < 0 || !isRecord(event.event)) {
|
|
288
288
|
continue;
|
|
289
289
|
}
|
|
290
|
-
const
|
|
290
|
+
const current = applyMessagePartsEvent(accumulator, event);
|
|
291
291
|
activeRunId.value = accumulator.runId;
|
|
292
|
-
if (
|
|
293
|
-
streamingText.value =
|
|
294
|
-
streamingThinking.value =
|
|
292
|
+
if (current.changed) {
|
|
293
|
+
streamingText.value = current.text;
|
|
294
|
+
streamingThinking.value = current.thinking;
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { Message } from '../types/schemas/chat/conversations.js';
|
|
2
2
|
import type { ReasoningStep } from './reasoning.js';
|
|
3
|
+
type ParsedReasoning = {
|
|
4
|
+
steps: ReasoningStep[];
|
|
5
|
+
thinkingTexts: Set<string>;
|
|
6
|
+
currentAnswerParts: string[];
|
|
7
|
+
currentAnswerStepTexts: Set<string>;
|
|
8
|
+
};
|
|
3
9
|
export type ReasoningCache = {
|
|
4
|
-
bySignature: Map<string,
|
|
10
|
+
bySignature: Map<string, ParsedReasoning>;
|
|
5
11
|
byReference: WeakMap<unknown[], {
|
|
6
12
|
messageId: string;
|
|
7
|
-
|
|
13
|
+
parsed: ParsedReasoning;
|
|
8
14
|
}>;
|
|
9
15
|
};
|
|
10
16
|
export declare function createReasoningCache(): ReasoningCache;
|
|
11
17
|
export declare function resolveReasoningDisplayMessages(messages: Message[], cache?: ReasoningCache): Message[];
|
|
18
|
+
export {};
|
|
@@ -46,27 +46,30 @@ function groupReasoningSteps(parsedSteps) {
|
|
|
46
46
|
function getReasoningCacheKey(messageId, reasoningData) {
|
|
47
47
|
return `${messageId}:${JSON.stringify(reasoningData)}`;
|
|
48
48
|
}
|
|
49
|
-
function
|
|
49
|
+
function getParsedReasoning(cache, messageId, reasoningData) {
|
|
50
50
|
const referenceHit = cache.byReference.get(reasoningData);
|
|
51
51
|
if (referenceHit?.messageId === messageId)
|
|
52
|
-
return referenceHit.
|
|
52
|
+
return referenceHit.parsed;
|
|
53
53
|
const cacheKey = getReasoningCacheKey(messageId, reasoningData);
|
|
54
|
-
let
|
|
55
|
-
if (!
|
|
56
|
-
|
|
54
|
+
let parsed = cache.bySignature.get(cacheKey);
|
|
55
|
+
if (!parsed) {
|
|
56
|
+
parsed = parseReasoningFromPolling(reasoningData);
|
|
57
57
|
for (const staleKey of cache.bySignature.keys()) {
|
|
58
58
|
if (staleKey.startsWith(`${messageId}:`))
|
|
59
59
|
cache.bySignature.delete(staleKey);
|
|
60
60
|
}
|
|
61
|
-
cache.bySignature.set(cacheKey,
|
|
61
|
+
cache.bySignature.set(cacheKey, parsed);
|
|
62
62
|
}
|
|
63
|
-
cache.byReference.set(reasoningData, { messageId,
|
|
64
|
-
return
|
|
63
|
+
cache.byReference.set(reasoningData, { messageId, parsed });
|
|
64
|
+
return parsed;
|
|
65
65
|
}
|
|
66
66
|
function parseReasoningFromPolling(reasoning) {
|
|
67
67
|
const allSteps = [];
|
|
68
68
|
const seenContentIds = /* @__PURE__ */ new Set();
|
|
69
69
|
const seenTexts = /* @__PURE__ */ new Set();
|
|
70
|
+
const thinkingTexts = /* @__PURE__ */ new Set();
|
|
71
|
+
let currentAnswerParts = [];
|
|
72
|
+
let currentAnswerStepTexts = /* @__PURE__ */ new Set();
|
|
70
73
|
for (const item of reasoning) {
|
|
71
74
|
if (!item || typeof item !== "object")
|
|
72
75
|
continue;
|
|
@@ -98,15 +101,23 @@ function parseReasoningFromPolling(reasoning) {
|
|
|
98
101
|
const message = itemObj.message;
|
|
99
102
|
if (!Array.isArray(message.content))
|
|
100
103
|
continue;
|
|
104
|
+
const answerParts = [];
|
|
105
|
+
const answerStepTexts = /* @__PURE__ */ new Set();
|
|
101
106
|
for (const contentPart of message.content) {
|
|
102
107
|
if (!contentPart || typeof contentPart !== "object")
|
|
103
108
|
continue;
|
|
104
109
|
const part = contentPart;
|
|
105
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
110
|
+
if ((part.type === "text" || part.type === "thinking") && typeof part.text === "string") {
|
|
111
|
+
if (part.type === "text")
|
|
112
|
+
answerParts.push(part.text);
|
|
113
|
+
if (part.type === "thinking")
|
|
114
|
+
thinkingTexts.add(part.text);
|
|
106
115
|
if (seenTexts.has(part.text))
|
|
107
116
|
continue;
|
|
108
117
|
seenTexts.add(part.text);
|
|
109
118
|
allSteps.push(part.text);
|
|
119
|
+
if (part.type === "text")
|
|
120
|
+
answerStepTexts.add(part.text);
|
|
110
121
|
} else if (part.type === "tool_use" && part.id && part.name) {
|
|
111
122
|
const toolId = part.id;
|
|
112
123
|
if (seenContentIds.has(`tool_use:${toolId}`))
|
|
@@ -130,6 +141,10 @@ function parseReasoningFromPolling(reasoning) {
|
|
|
130
141
|
});
|
|
131
142
|
}
|
|
132
143
|
}
|
|
144
|
+
if (!itemObj.parent_tool_use_id && answerParts.length > 0) {
|
|
145
|
+
currentAnswerParts = answerParts;
|
|
146
|
+
currentAnswerStepTexts = answerStepTexts;
|
|
147
|
+
}
|
|
133
148
|
} else if (itemObj.type === "text" && typeof itemObj.text === "string") {
|
|
134
149
|
if (seenTexts.has(itemObj.text))
|
|
135
150
|
continue;
|
|
@@ -158,21 +173,23 @@ function parseReasoningFromPolling(reasoning) {
|
|
|
158
173
|
});
|
|
159
174
|
}
|
|
160
175
|
}
|
|
161
|
-
return groupReasoningSteps(allSteps);
|
|
176
|
+
return { steps: groupReasoningSteps(allSteps), thinkingTexts, currentAnswerParts, currentAnswerStepTexts };
|
|
162
177
|
}
|
|
163
178
|
export function resolveReasoningDisplayMessages(messages, cache = createReasoningCache()) {
|
|
164
179
|
const renderedSteps = /* @__PURE__ */ new Set();
|
|
165
180
|
return messages.map((message) => {
|
|
166
181
|
if (message.role !== "assistant" || !message.reasoningData || !Array.isArray(message.reasoningData))
|
|
167
182
|
return message;
|
|
168
|
-
const
|
|
183
|
+
const parsed = getParsedReasoning(cache, message.id, message.reasoningData);
|
|
169
184
|
const displaySteps = [];
|
|
170
|
-
|
|
185
|
+
const currentAnswerMatches = message.content === parsed.currentAnswerParts.join("");
|
|
186
|
+
for (const step of parsed.steps) {
|
|
171
187
|
const key = getReasoningStepKey(step);
|
|
172
188
|
if (renderedSteps.has(key))
|
|
173
189
|
continue;
|
|
174
|
-
if (typeof step === "string" &&
|
|
190
|
+
if (typeof step === "string" && currentAnswerMatches && parsed.currentAnswerStepTexts.has(step) && !parsed.thinkingTexts.has(step)) {
|
|
175
191
|
continue;
|
|
192
|
+
}
|
|
176
193
|
renderedSteps.add(key);
|
|
177
194
|
displaySteps.push(step);
|
|
178
195
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
content?: string | null;
|
|
4
|
+
final?: boolean;
|
|
4
5
|
renderCodeBlocksAsPre?: boolean;
|
|
5
6
|
typewriter?: boolean;
|
|
6
7
|
customComponents?: Record<string, Component>;
|
|
@@ -10,6 +11,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
10
11
|
content: string | null;
|
|
11
12
|
customComponents: Record<string, Component>;
|
|
12
13
|
customHtmlTags: readonly string[];
|
|
14
|
+
final: boolean;
|
|
13
15
|
renderCodeBlocksAsPre: boolean;
|
|
14
16
|
typewriter: boolean;
|
|
15
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import MarkdownRender, {
|
|
3
|
-
import MermaidDiagram from "./renderers/mermaid-diagram.vue";
|
|
4
|
-
import { transformMarkdownNodes } from "../markdown/nodes";
|
|
2
|
+
import MarkdownRender, { removeCustomComponents, setCustomComponents } from "markstream-vue";
|
|
5
3
|
import { wrapCustomMarkdownComponents } from "../markdown/custom-components";
|
|
6
4
|
const props = defineProps({
|
|
7
5
|
content: { type: [String, null], required: false, default: "" },
|
|
6
|
+
final: { type: Boolean, required: false, default: true },
|
|
8
7
|
renderCodeBlocksAsPre: { type: Boolean, required: false, default: false },
|
|
9
8
|
typewriter: { type: Boolean, required: false, default: true },
|
|
10
9
|
customComponents: { type: Object, required: false, default: () => ({}) },
|
|
@@ -13,7 +12,6 @@ const props = defineProps({
|
|
|
13
12
|
const attrs = useAttrs();
|
|
14
13
|
const instance = getCurrentInstance();
|
|
15
14
|
const scopeId = `app-markdown-render-${instance?.uid ?? Math.random().toString(36).slice(2)}`;
|
|
16
|
-
const markdown = computed(() => getMarkdown(scopeId, { customHtmlTags: props.customHtmlTags }));
|
|
17
15
|
function escapeSingleDollarSigns(text) {
|
|
18
16
|
return text.replace(/(```[\s\S]*?```|``+[^`]*``+|`[^`]*`|!?\[(?:[^\[\]]|\[[^\[\]]*\])*\]\([^)]*\))|(?<![$\\])\$(?!\$)/g, (_match, codeSpan) => {
|
|
19
17
|
if (codeSpan)
|
|
@@ -21,24 +19,10 @@ function escapeSingleDollarSigns(text) {
|
|
|
21
19
|
return "\\$";
|
|
22
20
|
});
|
|
23
21
|
}
|
|
24
|
-
const
|
|
25
|
-
const tags = props.customHtmlTags;
|
|
26
|
-
return tags && tags.length > 0 ? { customHtmlTags: [...tags] } : void 0;
|
|
27
|
-
});
|
|
28
|
-
const nodes = computed(() => {
|
|
29
|
-
const parsedNodes = parseMarkdownToStructure(
|
|
30
|
-
escapeSingleDollarSigns(props.content ?? ""),
|
|
31
|
-
markdown.value,
|
|
32
|
-
parseOptions.value
|
|
33
|
-
);
|
|
34
|
-
return transformMarkdownNodes(parsedNodes);
|
|
35
|
-
});
|
|
22
|
+
const escapedContent = computed(() => escapeSingleDollarSigns(props.content ?? ""));
|
|
36
23
|
const customMarkdownComponents = computed(() => wrapCustomMarkdownComponents(props.customComponents));
|
|
37
24
|
watchEffect(() => {
|
|
38
|
-
setCustomComponents(scopeId,
|
|
39
|
-
mermaid: MermaidDiagram,
|
|
40
|
-
...customMarkdownComponents.value
|
|
41
|
-
});
|
|
25
|
+
setCustomComponents(scopeId, customMarkdownComponents.value);
|
|
42
26
|
});
|
|
43
27
|
onBeforeUnmount(() => {
|
|
44
28
|
removeCustomComponents(scopeId);
|
|
@@ -48,8 +32,10 @@ onBeforeUnmount(() => {
|
|
|
48
32
|
<template>
|
|
49
33
|
<MarkdownRender
|
|
50
34
|
v-bind="attrs"
|
|
51
|
-
:
|
|
35
|
+
:content="escapedContent"
|
|
52
36
|
:custom-id="scopeId"
|
|
37
|
+
:custom-html-tags="customHtmlTags"
|
|
38
|
+
:final="final"
|
|
53
39
|
:render-code-blocks-as-pre="renderCodeBlocksAsPre"
|
|
54
40
|
:typewriter="typewriter"
|
|
55
41
|
/>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
content?: string | null;
|
|
4
|
+
final?: boolean;
|
|
4
5
|
renderCodeBlocksAsPre?: boolean;
|
|
5
6
|
typewriter?: boolean;
|
|
6
7
|
customComponents?: Record<string, Component>;
|
|
@@ -10,6 +11,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
10
11
|
content: string | null;
|
|
11
12
|
customComponents: Record<string, Component>;
|
|
12
13
|
customHtmlTags: readonly string[];
|
|
14
|
+
final: boolean;
|
|
13
15
|
renderCodeBlocksAsPre: boolean;
|
|
14
16
|
typewriter: boolean;
|
|
15
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -2,7 +2,9 @@ import { type Component } from 'vue';
|
|
|
2
2
|
import type { BaseNode } from 'markstream-vue';
|
|
3
3
|
export type CustomMarkdownNode = BaseNode & {
|
|
4
4
|
attrs?: [string, unknown][];
|
|
5
|
+
autoClosed?: boolean;
|
|
5
6
|
content?: string;
|
|
7
|
+
loading?: boolean;
|
|
6
8
|
};
|
|
7
9
|
export declare function customMarkdownNodeProps(node: CustomMarkdownNode): Record<string, unknown>;
|
|
8
10
|
export declare function wrapCustomMarkdownComponent(component: Component, tagName?: string): Component;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineComponent, h } from "vue";
|
|
2
|
+
const RESERVED_CUSTOM_MARKDOWN_PROPS = /* @__PURE__ */ new Set(["node", "loading", "autoClosed"]);
|
|
2
3
|
function camelizeAttributeName(name) {
|
|
3
4
|
return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
4
5
|
}
|
|
@@ -9,11 +10,16 @@ export function customMarkdownNodeProps(node) {
|
|
|
9
10
|
if (!name)
|
|
10
11
|
continue;
|
|
11
12
|
const propName = camelizeAttributeName(name);
|
|
12
|
-
if (propName
|
|
13
|
+
if (RESERVED_CUSTOM_MARKDOWN_PROPS.has(propName))
|
|
13
14
|
continue;
|
|
14
15
|
attrProps[propName] = value;
|
|
15
16
|
}
|
|
16
|
-
return {
|
|
17
|
+
return {
|
|
18
|
+
node,
|
|
19
|
+
...typeof node.loading === "boolean" ? { loading: node.loading } : {},
|
|
20
|
+
...typeof node.autoClosed === "boolean" ? { autoClosed: node.autoClosed } : {},
|
|
21
|
+
...attrProps
|
|
22
|
+
};
|
|
17
23
|
}
|
|
18
24
|
function customMarkdownComponentAdapterName(tagName, component) {
|
|
19
25
|
if (typeof component === "function" && component.name)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/chat-nuxt",
|
|
3
|
-
"version": "3.16.0-rc.
|
|
3
|
+
"version": "3.16.0-rc.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -50,15 +50,14 @@
|
|
|
50
50
|
"@sentry/nuxt": "^10.0.0",
|
|
51
51
|
"@vueuse/components": "^12.8.0",
|
|
52
52
|
"@vueuse/core": "^12.8.0",
|
|
53
|
-
"beautiful-mermaid": "^1.1.3",
|
|
54
|
-
"dompurify": "^3.3.2",
|
|
55
53
|
"h3": "^1.15.0",
|
|
56
|
-
"markstream-vue": "
|
|
54
|
+
"markstream-vue": "2.0.7",
|
|
57
55
|
"mermaid": "^11.13.0",
|
|
58
56
|
"motion": "^11.13.0",
|
|
59
57
|
"nitropack": "^2.10.0",
|
|
60
58
|
"pdfjs-dist": "^5.4.530",
|
|
61
59
|
"posthog-js": "^1.364.2",
|
|
60
|
+
"stream-diffs": "0.0.2",
|
|
62
61
|
"virtua": "^0.42.0",
|
|
63
62
|
"vue-i18n": "^11.0.0",
|
|
64
63
|
"xlsx": "^0.18.5",
|