@meistrari/chat-nuxt 4.1.0-rc.1 → 4.1.0-rc.3
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 +2 -0
- package/dist/module.json +1 -1
- package/dist/runtime/embed/components/chat-embed-inner.vue +8 -2
- package/dist/runtime/embed/composables/conversation-browser-state.d.ts +2 -0
- package/dist/runtime/embed/composables/conversation-browser-state.js +18 -0
- package/dist/runtime/embed/conversation-share-url.d.ts +1 -0
- package/dist/runtime/embed/conversation-share-url.js +6 -1
- package/dist/runtime/messages/components/chat-message-bubble.vue +1 -1
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.d.vue.ts +1 -1
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +7 -43
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue.d.ts +1 -1
- package/dist/runtime/messages/composables/chat.js +6 -1
- package/dist/runtime/messages/composables/reasoning-timer.d.ts +9 -0
- package/dist/runtime/messages/composables/reasoning-timer.js +22 -0
- package/dist/runtime/messages/debug/chat-debug-scenarios.js +1 -0
- package/dist/runtime/messages/debug/chat-debug-switch-scenario.js +1 -0
- package/dist/runtime/messages/message-state.js +3 -1
- package/dist/runtime/types/schemas/chat/conversation-runtime.d.ts +2 -0
- package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -0
- package/dist/runtime/types/schemas/chat/conversations.js +1 -0
- package/dist/runtime/types/schemas/chat/messages.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ bunx --bun typedoc --entryPoints typedoc.ts --out /tmp/chat-nuxt-api --skipError
|
|
|
45
45
|
|
|
46
46
|
The table below is a quick index for reading in-repo. **When you change a prop, update its JSDoc and this row together.**
|
|
47
47
|
|
|
48
|
+
The embed automatically sets the browser tab title to the active conversation title and synchronizes its ID to `?conversationId=` using router replacement, preserving other query parameters and the URL fragment. Starting a new conversation removes the conversation parameter. Opening or reloading that URL restores the conversation; existing `?conversation=` links are also accepted and converted to `?conversationId=`. Explicit `conversationId` and `initialConversationId` props take precedence over the URL on mount. Without a conversation title, the host application controls the tab title.
|
|
49
|
+
|
|
48
50
|
### `<MeistrariChatEmbed>` props
|
|
49
51
|
|
|
50
52
|
Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload types are importable from the package root or `@meistrari/chat-nuxt/types/embed`.
|
package/dist/module.json
CHANGED
|
@@ -13,7 +13,8 @@ import ChatMessageInput from "../../messages/components/chat-message-input.vue";
|
|
|
13
13
|
import { normalizeConversationId } from "../../conversations/scope";
|
|
14
14
|
import { resolveChatFeatures } from "../feature-config";
|
|
15
15
|
import { resolveConversationCreator } from "../../conversations/creator";
|
|
16
|
-
import { buildConversationShareUrl } from "../conversation-share-url";
|
|
16
|
+
import { buildConversationShareUrl, getSharedConversationId } from "../conversation-share-url";
|
|
17
|
+
import { useConversationBrowserState } from "../composables/conversation-browser-state";
|
|
17
18
|
import {
|
|
18
19
|
resolveTelaAgentInputs,
|
|
19
20
|
useChat
|
|
@@ -133,7 +134,7 @@ const {
|
|
|
133
134
|
closePreview
|
|
134
135
|
} = useFilePreviewPanel();
|
|
135
136
|
const { isOpen: isCitationPanelOpen } = useCitationPanel();
|
|
136
|
-
const sharedConversationId =
|
|
137
|
+
const sharedConversationId = getSharedConversationId(route.query);
|
|
137
138
|
const activeConversationId = ref(
|
|
138
139
|
normalizeConversationId(props.conversationId) ?? normalizeConversationId(props.initialConversationId) ?? normalizeConversationId(sharedConversationId)
|
|
139
140
|
);
|
|
@@ -492,6 +493,11 @@ function dismissOutdatedBanner() {
|
|
|
492
493
|
}
|
|
493
494
|
}
|
|
494
495
|
const optimisticTitle = ref(null);
|
|
496
|
+
useConversationBrowserState(activeConversationId, computed(() => {
|
|
497
|
+
if (currentConversation.value?.id !== activeConversationId.value)
|
|
498
|
+
return null;
|
|
499
|
+
return optimisticTitle.value ?? currentConversation.value?.title ?? null;
|
|
500
|
+
}));
|
|
495
501
|
const displayTitle = computed(() => {
|
|
496
502
|
if (optimisticTitle.value !== null)
|
|
497
503
|
return optimisticTitle.value;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function useConversationBrowserState(conversationId, title) {
|
|
2
|
+
const route = useRoute();
|
|
3
|
+
const router = useRouter();
|
|
4
|
+
useHead(computed(() => title.value ? { title: title.value } : {}));
|
|
5
|
+
onMounted(() => {
|
|
6
|
+
watch(conversationId, async (id) => {
|
|
7
|
+
if (route.query.conversationId === (id ?? void 0) && route.query.conversation === void 0)
|
|
8
|
+
return;
|
|
9
|
+
const query = { ...route.query };
|
|
10
|
+
delete query.conversation;
|
|
11
|
+
if (id)
|
|
12
|
+
query.conversationId = id;
|
|
13
|
+
else
|
|
14
|
+
delete query.conversationId;
|
|
15
|
+
await router.replace({ path: route.path, query, hash: route.hash });
|
|
16
|
+
}, { immediate: true });
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { normalizeConversationId } from "../conversations/scope.js";
|
|
2
|
+
export function getSharedConversationId(query) {
|
|
3
|
+
return normalizeConversationId(typeof query.conversationId === "string" ? query.conversationId : null) ?? normalizeConversationId(typeof query.conversation === "string" ? query.conversation : null);
|
|
4
|
+
}
|
|
1
5
|
export function buildConversationShareUrl(currentHref, conversationId) {
|
|
2
6
|
const shareUrl = new URL(currentHref);
|
|
3
|
-
shareUrl.searchParams.
|
|
7
|
+
shareUrl.searchParams.delete("conversation");
|
|
8
|
+
shareUrl.searchParams.set("conversationId", conversationId);
|
|
4
9
|
return shareUrl.href;
|
|
5
10
|
}
|
|
@@ -184,7 +184,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
184
184
|
:reasoning-data="message.reasoningData ?? []"
|
|
185
185
|
:completed="!isPending"
|
|
186
186
|
:preparing-steps="preparingSteps"
|
|
187
|
-
:
|
|
187
|
+
:started-at="message.generationStartedAt ?? message.createdAt"
|
|
188
188
|
:updated-at="message.updatedAt"
|
|
189
189
|
/>
|
|
190
190
|
|
|
@@ -2,7 +2,7 @@ import { type ReasoningStep } from '../../reasoning.js';
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
reasoningData: ReasoningStep[];
|
|
4
4
|
completed: boolean;
|
|
5
|
-
|
|
5
|
+
startedAt: Date | string;
|
|
6
6
|
updatedAt?: Date | string | null;
|
|
7
7
|
preparingSteps?: readonly string[] | null;
|
|
8
8
|
};
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { resolveDisplayedReasoning } from "../../reasoning";
|
|
3
|
+
import { useReasoningTimer } from "../../composables/reasoning-timer";
|
|
3
4
|
import { resolveToolTitle } from "../../tool-registry";
|
|
4
5
|
import ChatReasoningCollapsibleContent from "./chat-reasoning-collapsible-content.vue";
|
|
5
6
|
import ChatReasoningSteps from "./chat-reasoning-steps.vue";
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
reasoningData: { type: Array, required: true },
|
|
8
9
|
completed: { type: Boolean, required: true },
|
|
9
|
-
|
|
10
|
+
startedAt: { type: [Date, String], required: true },
|
|
10
11
|
updatedAt: { type: [Date, String, null], required: false },
|
|
11
12
|
preparingSteps: { type: [Array, null], required: false }
|
|
12
13
|
});
|
|
13
14
|
const TEXT_TRUNCATE_LENGTH = 40;
|
|
14
15
|
const isExpanded = ref(false);
|
|
15
|
-
const elapsedTime =
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const { elapsedTime, thinkingDuration } = useReasoningTimer({
|
|
17
|
+
startedAt: computed(() => props.startedAt),
|
|
18
|
+
updatedAt: computed(() => props.updatedAt),
|
|
19
|
+
completed: computed(() => props.completed)
|
|
20
|
+
});
|
|
18
21
|
const displayedReasoning = computed(
|
|
19
22
|
() => resolveDisplayedReasoning(props.reasoningData, props.preparingSteps)
|
|
20
23
|
);
|
|
@@ -37,14 +40,6 @@ const currentStepName = computed(() => {
|
|
|
37
40
|
}
|
|
38
41
|
return "Trabalhando";
|
|
39
42
|
});
|
|
40
|
-
const thinkingDuration = computed(() => {
|
|
41
|
-
if (!props.updatedAt)
|
|
42
|
-
return null;
|
|
43
|
-
const start = new Date(props.createdAt).getTime();
|
|
44
|
-
const end = new Date(props.updatedAt).getTime();
|
|
45
|
-
const duration = end - start;
|
|
46
|
-
return Number.isFinite(duration) && duration >= 0 ? duration : null;
|
|
47
|
-
});
|
|
48
43
|
const formattedDuration = computed(() => {
|
|
49
44
|
if (!thinkingDuration.value)
|
|
50
45
|
return null;
|
|
@@ -61,37 +56,6 @@ const liveFormattedDuration = computed(() => {
|
|
|
61
56
|
const seconds = Math.round(elapsedTime.value / 1e3);
|
|
62
57
|
return seconds > 0 ? `${seconds}s` : "";
|
|
63
58
|
});
|
|
64
|
-
watch(() => props.completed, (isCompleted) => {
|
|
65
|
-
if (!isCompleted && !timerInterval) {
|
|
66
|
-
const startTime = new Date(props.createdAt).getTime();
|
|
67
|
-
if (typeof window !== "undefined") {
|
|
68
|
-
localStorage.setItem(storageKey.value, startTime.toString());
|
|
69
|
-
}
|
|
70
|
-
elapsedTime.value = Date.now() - startTime;
|
|
71
|
-
timerInterval = setInterval(() => {
|
|
72
|
-
elapsedTime.value = Date.now() - startTime;
|
|
73
|
-
}, 1e3);
|
|
74
|
-
} else if (isCompleted && timerInterval) {
|
|
75
|
-
clearInterval(timerInterval);
|
|
76
|
-
timerInterval = void 0;
|
|
77
|
-
if (typeof window !== "undefined") {
|
|
78
|
-
localStorage.removeItem(storageKey.value);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}, { immediate: true });
|
|
82
|
-
onMounted(() => {
|
|
83
|
-
if (!props.completed && typeof window !== "undefined") {
|
|
84
|
-
const storedStartTime = localStorage.getItem(storageKey.value);
|
|
85
|
-
if (storedStartTime) {
|
|
86
|
-
const startTime = Number.parseInt(storedStartTime, 10);
|
|
87
|
-
elapsedTime.value = Date.now() - startTime;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
onUnmounted(() => {
|
|
92
|
-
if (timerInterval)
|
|
93
|
-
clearInterval(timerInterval);
|
|
94
|
-
});
|
|
95
59
|
</script>
|
|
96
60
|
|
|
97
61
|
<template>
|
|
@@ -2,7 +2,7 @@ import { type ReasoningStep } from '../../reasoning.js';
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
reasoningData: ReasoningStep[];
|
|
4
4
|
completed: boolean;
|
|
5
|
-
|
|
5
|
+
startedAt: Date | string;
|
|
6
6
|
updatedAt?: Date | string | null;
|
|
7
7
|
preparingSteps?: readonly string[] | null;
|
|
8
8
|
};
|
|
@@ -495,6 +495,7 @@ export function useChat(options = {}) {
|
|
|
495
495
|
ttft: null,
|
|
496
496
|
createdBy: authUser.value?.email ?? null,
|
|
497
497
|
createdAt: /* @__PURE__ */ new Date(),
|
|
498
|
+
generationStartedAt: null,
|
|
498
499
|
updatedAt: null
|
|
499
500
|
};
|
|
500
501
|
const optimisticAssistantMessage = existingOptimisticAssistantMessage ?? {
|
|
@@ -509,6 +510,7 @@ export function useChat(options = {}) {
|
|
|
509
510
|
ttft: null,
|
|
510
511
|
createdBy: null,
|
|
511
512
|
createdAt: /* @__PURE__ */ new Date(),
|
|
513
|
+
generationStartedAt: /* @__PURE__ */ new Date(),
|
|
512
514
|
updatedAt: null
|
|
513
515
|
};
|
|
514
516
|
streamingMessageId.value = optimisticAssistantMessage.id;
|
|
@@ -873,10 +875,13 @@ export function useChat(options = {}) {
|
|
|
873
875
|
messages.value = retry.messages;
|
|
874
876
|
streamingMessageId.value = messageId;
|
|
875
877
|
directStream.beginMessage();
|
|
876
|
-
await $fetch(
|
|
878
|
+
const retriedMessage = await $fetch(
|
|
877
879
|
chatApi.path(`/conversations/${conversationId}/messages/${messageId}/retry`),
|
|
878
880
|
chatApi.withChatHeaders({ method: "POST" })
|
|
879
881
|
);
|
|
882
|
+
if (currentConversation.value?.id !== conversationId)
|
|
883
|
+
return true;
|
|
884
|
+
messages.value = messages.value.map((message) => message.id === messageId ? { ...message, ...retriedMessage } : message);
|
|
880
885
|
startPolling();
|
|
881
886
|
return true;
|
|
882
887
|
} catch (err) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
export declare function useReasoningTimer(options: {
|
|
3
|
+
startedAt: Readonly<Ref<Date | string>>;
|
|
4
|
+
updatedAt: Readonly<Ref<Date | string | null | undefined>>;
|
|
5
|
+
completed: Readonly<Ref<boolean>>;
|
|
6
|
+
}): {
|
|
7
|
+
elapsedTime: Ref<number, number>;
|
|
8
|
+
thinkingDuration: import("vue").ComputedRef<number | null>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { computed, ref, watch } from "vue";
|
|
2
|
+
export function useReasoningTimer(options) {
|
|
3
|
+
const elapsedTime = ref(0);
|
|
4
|
+
const startTime = computed(() => new Date(options.startedAt.value).getTime());
|
|
5
|
+
const thinkingDuration = computed(() => {
|
|
6
|
+
if (!options.updatedAt.value)
|
|
7
|
+
return null;
|
|
8
|
+
const duration = new Date(options.updatedAt.value).getTime() - startTime.value;
|
|
9
|
+
return Number.isFinite(duration) && duration >= 0 ? duration : null;
|
|
10
|
+
});
|
|
11
|
+
watch([startTime, options.completed], ([start, completed], _previous, onCleanup) => {
|
|
12
|
+
if (completed)
|
|
13
|
+
return;
|
|
14
|
+
const update = () => {
|
|
15
|
+
elapsedTime.value = Number.isFinite(start) ? Math.max(0, Date.now() - start) : 0;
|
|
16
|
+
};
|
|
17
|
+
update();
|
|
18
|
+
const interval = setInterval(update, 1e3);
|
|
19
|
+
onCleanup(() => clearInterval(interval));
|
|
20
|
+
}, { immediate: true });
|
|
21
|
+
return { elapsedTime, thinkingDuration };
|
|
22
|
+
}
|
|
@@ -37,7 +37,9 @@ export function beginAssistantMessageRetry(messages, messageId) {
|
|
|
37
37
|
content: "",
|
|
38
38
|
status: "pending",
|
|
39
39
|
failReason: null,
|
|
40
|
-
reasoningData: null
|
|
40
|
+
reasoningData: null,
|
|
41
|
+
generationStartedAt: /* @__PURE__ */ new Date(),
|
|
42
|
+
updatedAt: null
|
|
41
43
|
};
|
|
42
44
|
})
|
|
43
45
|
};
|
|
@@ -114,6 +114,7 @@ export declare const sendMessageV2ResponseSchema: z.ZodObject<{
|
|
|
114
114
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
115
115
|
createdAt: z.ZodDate;
|
|
116
116
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
117
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
117
118
|
}, z.core.$strip>;
|
|
118
119
|
assistantMessage: z.ZodObject<{
|
|
119
120
|
id: z.ZodString;
|
|
@@ -146,6 +147,7 @@ export declare const sendMessageV2ResponseSchema: z.ZodObject<{
|
|
|
146
147
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
147
148
|
createdAt: z.ZodDate;
|
|
148
149
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
150
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
149
151
|
}, z.core.$strip>;
|
|
150
152
|
turn: z.ZodNullable<z.ZodObject<{
|
|
151
153
|
id: z.ZodString;
|
|
@@ -110,6 +110,7 @@ export declare const messageSchema: z.ZodObject<{
|
|
|
110
110
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
111
111
|
createdAt: z.ZodDate;
|
|
112
112
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
113
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
113
114
|
}, z.core.$strip>;
|
|
114
115
|
export type Message = z.infer<typeof messageSchema>;
|
|
115
116
|
export declare const conversationCreatorFilterCountsSchema: z.ZodRecord<z.ZodEnum<{
|
|
@@ -222,6 +223,7 @@ export declare const conversationWithMessagesSchema: z.ZodObject<{
|
|
|
222
223
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
223
224
|
createdAt: z.ZodDate;
|
|
224
225
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
226
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
225
227
|
}, z.core.$strip>>;
|
|
226
228
|
}, z.core.$strip>;
|
|
227
229
|
export type ConversationWithMessages = z.infer<typeof conversationWithMessagesSchema>;
|
|
@@ -49,6 +49,7 @@ export const messageSchema = z.object({
|
|
|
49
49
|
createdBy: z.string().nullable(),
|
|
50
50
|
createdAt: z.date(),
|
|
51
51
|
updatedAt: z.date().nullable(),
|
|
52
|
+
generationStartedAt: z.date().nullable(),
|
|
52
53
|
});
|
|
53
54
|
export const conversationCreatorFilterCountsSchema = z.record(conversationCreatorFilterSchema, z.number());
|
|
54
55
|
export const conversationListMetadataSchema = z.object({
|
|
@@ -119,6 +119,7 @@ export declare const sendMessageResponseSchema: z.ZodObject<{
|
|
|
119
119
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
120
120
|
createdAt: z.ZodDate;
|
|
121
121
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
122
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
122
123
|
}, z.core.$strip>;
|
|
123
124
|
assistantMessage: z.ZodObject<{
|
|
124
125
|
id: z.ZodString;
|
|
@@ -151,6 +152,7 @@ export declare const sendMessageResponseSchema: z.ZodObject<{
|
|
|
151
152
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
152
153
|
createdAt: z.ZodDate;
|
|
153
154
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
155
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
154
156
|
}, z.core.$strip>;
|
|
155
157
|
}, z.core.$strip>;
|
|
156
158
|
export type SendMessageResponse = z.infer<typeof sendMessageResponseSchema>;
|
|
@@ -186,6 +188,7 @@ export declare const messagePollResponseSchema: z.ZodObject<{
|
|
|
186
188
|
createdBy: z.ZodNullable<z.ZodString>;
|
|
187
189
|
createdAt: z.ZodDate;
|
|
188
190
|
updatedAt: z.ZodNullable<z.ZodDate>;
|
|
191
|
+
generationStartedAt: z.ZodNullable<z.ZodDate>;
|
|
189
192
|
}, z.core.$strip>>;
|
|
190
193
|
newFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
191
194
|
url: z.ZodString;
|