@meistrari/chat-nuxt 3.10.0 → 3.11.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 +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/embed/components/chat-embed-inner.vue +93 -38
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +0 -1
- package/dist/runtime/embed/composables/use-agent-wake.d.ts +23 -0
- package/dist/runtime/embed/composables/use-agent-wake.js +182 -0
- package/dist/runtime/embed/feature-config.d.ts +4 -4
- package/dist/runtime/embed/feature-config.js +2 -1
- package/dist/runtime/embed/loading-messages.d.ts +16 -0
- package/dist/runtime/embed/loading-messages.js +10 -0
- package/dist/runtime/messages/components/chat-message-bubble.d.vue.ts +10 -1
- package/dist/runtime/messages/components/chat-message-bubble.vue +18 -7
- package/dist/runtime/messages/components/chat-message-bubble.vue.d.ts +10 -1
- package/dist/runtime/messages/components/chat-message-list.d.vue.ts +6 -1
- package/dist/runtime/messages/components/chat-message-list.vue +11 -2
- package/dist/runtime/messages/components/chat-message-list.vue.d.ts +6 -1
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.d.vue.ts +2 -1
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +9 -8
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue.d.ts +2 -1
- package/dist/runtime/messages/composables/chat.d.ts +8 -2
- package/dist/runtime/messages/composables/chat.js +180 -25
- package/dist/runtime/messages/composables/use-direct-stream.d.ts +41 -0
- package/dist/runtime/messages/composables/use-direct-stream.js +263 -0
- package/dist/runtime/messages/reasoning.d.ts +7 -0
- package/dist/runtime/messages/reasoning.js +5 -0
- package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -0
- package/dist/runtime/types/schemas/chat/messages.d.ts +39 -0
- package/dist/runtime/types/schemas/chat/messages.js +24 -0
- package/dist/runtime/types/schemas/chat/models.d.ts +3 -2
- package/dist/runtime/types/schemas/chat/models.js +2 -1
- package/dist/runtime/types/schemas/chat/ui.d.ts +10 -0
- package/dist/runtime/types/schemas/chat/ui.js +1 -0
- package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +35 -0
- package/dist/runtime/types/schemas/integrations/tela-agent-api.js +17 -0
- package/package.json +1 -1
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { Message } from '../../types/schemas/chat/conversations.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
message: Message;
|
|
4
|
+
streamActive?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Stand-in steps shown while this turn is still working towards its first
|
|
7
|
+
* output. They yield as soon as the run emits real reasoning or content.
|
|
8
|
+
*/
|
|
9
|
+
preparingSteps?: readonly string[] | null;
|
|
10
|
+
showTtft?: boolean;
|
|
4
11
|
};
|
|
5
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
6
13
|
retry: (messageId: string) => any;
|
|
7
14
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
15
|
onRetry?: ((messageId: string) => any) | undefined;
|
|
9
|
-
}>, {
|
|
16
|
+
}>, {
|
|
17
|
+
showTtft: boolean;
|
|
18
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
19
|
declare const _default: typeof __VLS_export;
|
|
11
20
|
export default _default;
|
|
@@ -8,11 +8,16 @@ type __VLS_Props = {
|
|
|
8
8
|
* own spacing. Defaults to `page`.
|
|
9
9
|
*/
|
|
10
10
|
variant?: 'page' | 'widget' | 'mobile';
|
|
11
|
+
streamingActiveMessageId?: string | null;
|
|
12
|
+
preparingSteps?: readonly string[] | null;
|
|
13
|
+
showTtft?: boolean;
|
|
11
14
|
};
|
|
12
15
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
13
16
|
retry: (messageId: string) => any;
|
|
14
17
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
18
|
onRetry?: ((messageId: string) => any) | undefined;
|
|
16
|
-
}>, {
|
|
19
|
+
}>, {
|
|
20
|
+
showTtft: boolean;
|
|
21
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
22
|
declare const _default: typeof __VLS_export;
|
|
18
23
|
export default _default;
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
const props = defineProps({
|
|
3
3
|
messages: { type: Array, required: true },
|
|
4
4
|
loading: { type: Boolean, required: false },
|
|
5
|
-
variant: { type: String, required: false }
|
|
5
|
+
variant: { type: String, required: false },
|
|
6
|
+
streamingActiveMessageId: { type: [String, null], required: false },
|
|
7
|
+
preparingSteps: { type: [Array, null], required: false },
|
|
8
|
+
showTtft: { type: Boolean, required: false, default: true }
|
|
6
9
|
});
|
|
7
10
|
const emit = defineEmits(["retry"]);
|
|
8
11
|
const conversationRef = ref(null);
|
|
@@ -18,7 +21,10 @@ function scrollToBottom() {
|
|
|
18
21
|
const hasPendingMessage = computed(
|
|
19
22
|
() => props.messages.some((message) => message.status === "pending")
|
|
20
23
|
);
|
|
21
|
-
watch(
|
|
24
|
+
watch(
|
|
25
|
+
() => [props.messages.length, props.messages.at(-1)?.content.length ?? 0],
|
|
26
|
+
scrollToBottom
|
|
27
|
+
);
|
|
22
28
|
onMounted(scrollToBottom);
|
|
23
29
|
</script>
|
|
24
30
|
|
|
@@ -47,6 +53,9 @@ onMounted(scrollToBottom);
|
|
|
47
53
|
v-for="message in messages"
|
|
48
54
|
:key="message.id"
|
|
49
55
|
:message="message"
|
|
56
|
+
:stream-active="message.id === streamingActiveMessageId"
|
|
57
|
+
:preparing-steps="message.role === 'assistant' && message.status === 'pending' ? preparingSteps : null"
|
|
58
|
+
:show-ttft="showTtft"
|
|
50
59
|
@retry="emit('retry', $event)"
|
|
51
60
|
/>
|
|
52
61
|
<p
|
|
@@ -8,11 +8,16 @@ type __VLS_Props = {
|
|
|
8
8
|
* own spacing. Defaults to `page`.
|
|
9
9
|
*/
|
|
10
10
|
variant?: 'page' | 'widget' | 'mobile';
|
|
11
|
+
streamingActiveMessageId?: string | null;
|
|
12
|
+
preparingSteps?: readonly string[] | null;
|
|
13
|
+
showTtft?: boolean;
|
|
11
14
|
};
|
|
12
15
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
13
16
|
retry: (messageId: string) => any;
|
|
14
17
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
18
|
onRetry?: ((messageId: string) => any) | undefined;
|
|
16
|
-
}>, {
|
|
19
|
+
}>, {
|
|
20
|
+
showTtft: boolean;
|
|
21
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
22
|
declare const _default: typeof __VLS_export;
|
|
18
23
|
export default _default;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReasoningStep } from '../../reasoning.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
reasoningData: ReasoningStep[];
|
|
4
4
|
completed: boolean;
|
|
5
5
|
animate?: boolean;
|
|
6
6
|
createdAt: Date | string;
|
|
7
7
|
updatedAt?: Date | string | null;
|
|
8
|
+
preparingSteps?: readonly string[] | null;
|
|
8
9
|
};
|
|
9
10
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
11
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { resolveDisplayedReasoning } from "../../reasoning";
|
|
2
3
|
import { resolveToolTitle } from "../../tool-registry";
|
|
3
4
|
import ChatReasoningSteps from "./chat-reasoning-steps.vue";
|
|
4
5
|
const props = defineProps({
|
|
@@ -6,7 +7,8 @@ const props = defineProps({
|
|
|
6
7
|
completed: { type: Boolean, required: true },
|
|
7
8
|
animate: { type: Boolean, required: false },
|
|
8
9
|
createdAt: { type: [Date, String], required: true },
|
|
9
|
-
updatedAt: { type: [Date, String, null], required: false }
|
|
10
|
+
updatedAt: { type: [Date, String, null], required: false },
|
|
11
|
+
preparingSteps: { type: [Array, null], required: false }
|
|
10
12
|
});
|
|
11
13
|
const SUCCESS_ANIMATION_DURATION_MS = 1e3;
|
|
12
14
|
const DOT_ANIMATION_INTERVAL_MS = 500;
|
|
@@ -23,11 +25,10 @@ let dotInterval;
|
|
|
23
25
|
let timerInterval;
|
|
24
26
|
const storageKey = computed(() => `reasoning-timer-${new Date(props.createdAt).getTime()}`);
|
|
25
27
|
const showGradient = computed(() => !props.completed && !isExpanded.value);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
28
|
+
const displayedReasoning = computed(
|
|
29
|
+
() => resolveDisplayedReasoning(props.reasoningData, props.preparingSteps)
|
|
30
|
+
);
|
|
31
|
+
const groupedReasoning = computed(() => displayedReasoning.value);
|
|
31
32
|
const hasReasoning = computed(() => groupedReasoning.value.length > 0);
|
|
32
33
|
const lastStep = computed(() => {
|
|
33
34
|
const steps = groupedReasoning.value;
|
|
@@ -247,7 +248,7 @@ onUnmounted(() => {
|
|
|
247
248
|
:class="{ 'with-gradient': showGradient }"
|
|
248
249
|
>
|
|
249
250
|
<ChatReasoningSteps
|
|
250
|
-
:reasoning-data="
|
|
251
|
+
:reasoning-data="displayedReasoning"
|
|
251
252
|
:animate="animate && !completed"
|
|
252
253
|
:completed="completed"
|
|
253
254
|
:is-preview-mode="true"
|
|
@@ -262,7 +263,7 @@ onUnmounted(() => {
|
|
|
262
263
|
mb-16px
|
|
263
264
|
>
|
|
264
265
|
<ChatReasoningSteps
|
|
265
|
-
:reasoning-data="
|
|
266
|
+
:reasoning-data="displayedReasoning"
|
|
266
267
|
:animate="animate && !completed"
|
|
267
268
|
:completed="completed"
|
|
268
269
|
/>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReasoningStep } from '../../reasoning.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
reasoningData: ReasoningStep[];
|
|
4
4
|
completed: boolean;
|
|
5
5
|
animate?: boolean;
|
|
6
6
|
createdAt: Date | string;
|
|
7
7
|
updatedAt?: Date | string | null;
|
|
8
|
+
preparingSteps?: readonly string[] | null;
|
|
8
9
|
};
|
|
9
10
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
11
|
declare const _default: typeof __VLS_export;
|
|
@@ -4,6 +4,7 @@ import type { FileUploadInput } from '../../types/schemas/chat/files.js';
|
|
|
4
4
|
import type { SendMessageBody } from '../../types/schemas/chat/messages.js';
|
|
5
5
|
import type { TelaAgentExecutionInput } from '../../types/schemas/integrations/tela-agent-api.js';
|
|
6
6
|
export declare function resolveTelaAgentInputs(messageInputs: TelaAgentExecutionInput[] | undefined, configuredInputs: DeepReadonly<TelaAgentExecutionInput[]> | null | undefined): SendMessageBody['agentInputs'];
|
|
7
|
+
export declare function resolveStreamingContent(authoritative: string, provisional: string): string;
|
|
7
8
|
export declare function useChat(): {
|
|
8
9
|
currentConversation: any;
|
|
9
10
|
messages: any;
|
|
@@ -12,15 +13,20 @@ export declare function useChat(): {
|
|
|
12
13
|
error: any;
|
|
13
14
|
isPolling: any;
|
|
14
15
|
hasPendingMessage: any;
|
|
16
|
+
streamingActiveMessageId: any;
|
|
17
|
+
preparingSteps: any;
|
|
15
18
|
cancelling: any;
|
|
16
19
|
generatingTitle: any;
|
|
17
20
|
animatedTitle: any;
|
|
18
21
|
loadConversation: (id: string) => Promise<void>;
|
|
19
22
|
setConversation: (conv: Conversation | ConversationWithMessages) => void;
|
|
23
|
+
adoptConversation: (conv: Conversation) => void;
|
|
20
24
|
setConversationModel: (model: string | null) => void;
|
|
21
25
|
pendingModelSelection: any;
|
|
22
|
-
sendMessage: (content: string, files?: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[]) => Promise<
|
|
23
|
-
|
|
26
|
+
sendMessage: (content: string, files?: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[], prepare?: (signal: AbortSignal) => Promise<{
|
|
27
|
+
wakeSessionId?: string;
|
|
28
|
+
}>) => Promise<Message | null>;
|
|
29
|
+
retryMessage: (messageId: string, wakeSessionId?: string) => Promise<boolean>;
|
|
24
30
|
cancelConversation: () => Promise<boolean>;
|
|
25
31
|
startPolling: () => void;
|
|
26
32
|
stopPolling: () => void;
|
|
@@ -20,13 +20,22 @@ import { getChatApiErrorCode, getChatApiErrorMessage } from "../../shared/chat-a
|
|
|
20
20
|
import { useChatApi } from "../../shared/composables/chat-api.js";
|
|
21
21
|
import { useConversationFileActions } from "../../files/composables/conversation-files.js";
|
|
22
22
|
import { useEmbedConfig } from "../../embed/composables/embed-config.js";
|
|
23
|
+
import { CHAT_PREPARING_STEPS, nextChatPreparingStepDelay } from "../../embed/loading-messages.js";
|
|
23
24
|
import { usePolling } from "../../shared/composables/polling.js";
|
|
24
25
|
import { terminalMessageTransitions, useChatMessageTerminal } from "./chat-message-terminal.js";
|
|
26
|
+
import { useDirectStream } from "./use-direct-stream.js";
|
|
25
27
|
const WORKSPACE_AGENT_NOT_READY_TEXT = "O agente deste workspace ainda n\xE3o foi configurado no Tela. Abra as configura\xE7\xF5es do workspace para criar o agente.";
|
|
26
28
|
export function resolveTelaAgentInputs(messageInputs, configuredInputs) {
|
|
27
29
|
const inputs = messageInputs ?? configuredInputs;
|
|
28
30
|
return inputs === void 0 || inputs === null ? void 0 : inputs.map((input) => ({ ...input }));
|
|
29
31
|
}
|
|
32
|
+
export function resolveStreamingContent(authoritative, provisional) {
|
|
33
|
+
if (provisional.startsWith(authoritative))
|
|
34
|
+
return provisional;
|
|
35
|
+
if (authoritative.startsWith(provisional))
|
|
36
|
+
return authoritative;
|
|
37
|
+
return authoritative || provisional;
|
|
38
|
+
}
|
|
30
39
|
export function useChat() {
|
|
31
40
|
const chatApi = useChatApi();
|
|
32
41
|
const chatMessageTerminal = useChatMessageTerminal();
|
|
@@ -60,6 +69,34 @@ export function useChat() {
|
|
|
60
69
|
});
|
|
61
70
|
const loading = ref(false);
|
|
62
71
|
const sending = ref(false);
|
|
72
|
+
const gateHolding = ref(false);
|
|
73
|
+
const preparingSteps = ref([]);
|
|
74
|
+
let preparingTimer;
|
|
75
|
+
let gateController = null;
|
|
76
|
+
let sendStartedAt = 0;
|
|
77
|
+
let standInOutputAtMs = null;
|
|
78
|
+
function clearPreparingSteps() {
|
|
79
|
+
if (preparingTimer)
|
|
80
|
+
clearTimeout(preparingTimer);
|
|
81
|
+
preparingTimer = void 0;
|
|
82
|
+
preparingSteps.value = [];
|
|
83
|
+
standInOutputAtMs = null;
|
|
84
|
+
}
|
|
85
|
+
function schedulePreparingStep(index) {
|
|
86
|
+
if (index >= CHAT_PREPARING_STEPS.length)
|
|
87
|
+
return;
|
|
88
|
+
const delay = nextChatPreparingStepDelay(index);
|
|
89
|
+
if (index === 0)
|
|
90
|
+
standInOutputAtMs = delay;
|
|
91
|
+
preparingTimer = setTimeout(() => {
|
|
92
|
+
preparingSteps.value = CHAT_PREPARING_STEPS.slice(0, index + 1);
|
|
93
|
+
schedulePreparingStep(index + 1);
|
|
94
|
+
}, delay);
|
|
95
|
+
}
|
|
96
|
+
function schedulePreparingSteps() {
|
|
97
|
+
clearPreparingSteps();
|
|
98
|
+
schedulePreparingStep(0);
|
|
99
|
+
}
|
|
63
100
|
const error = ref(null);
|
|
64
101
|
const titleGenerated = ref(false);
|
|
65
102
|
const generatingTitle = ref(false);
|
|
@@ -67,12 +104,29 @@ export function useChat() {
|
|
|
67
104
|
const hasPendingMessage = computed(
|
|
68
105
|
() => messages.value.some((m) => m.status === "pending")
|
|
69
106
|
);
|
|
107
|
+
const streamingMessageId = ref(null);
|
|
108
|
+
let requestDurableRecovery = () => {
|
|
109
|
+
};
|
|
110
|
+
const directStream = useDirectStream({
|
|
111
|
+
onResult: () => requestDurableRecovery(),
|
|
112
|
+
onDisconnect: () => requestDurableRecovery()
|
|
113
|
+
});
|
|
114
|
+
const streamingActiveMessageId = computed(
|
|
115
|
+
() => directStream.activity.value === "active" ? streamingMessageId.value : null
|
|
116
|
+
);
|
|
70
117
|
const reasoningCache = createReasoningCache();
|
|
71
|
-
const processedMessages = computed(() =>
|
|
118
|
+
const processedMessages = computed(() => {
|
|
119
|
+
const resolved = resolveReasoningDisplayMessages(messages.value, reasoningCache);
|
|
120
|
+
const messageId = streamingMessageId.value;
|
|
121
|
+
const content = directStream.streamingText.value;
|
|
122
|
+
if (!messageId || !content)
|
|
123
|
+
return resolved;
|
|
124
|
+
return resolved.map((message) => message.id === messageId && message.status === "pending" ? { ...message, content: resolveStreamingContent(message.content, content) } : message);
|
|
125
|
+
});
|
|
72
126
|
const stopCondition = computed(() => !hasPendingMessage.value);
|
|
73
127
|
let pollGeneration = 0;
|
|
74
128
|
const pollMessages = async () => {
|
|
75
|
-
if (!currentConversation.value)
|
|
129
|
+
if (!currentConversation.value || gateHolding.value)
|
|
76
130
|
return;
|
|
77
131
|
const generation = pollGeneration;
|
|
78
132
|
try {
|
|
@@ -84,6 +138,13 @@ export function useChat() {
|
|
|
84
138
|
return;
|
|
85
139
|
const terminalMessages = terminalMessageTransitions(messages.value, response.messages);
|
|
86
140
|
messages.value = response.messages;
|
|
141
|
+
const latestSessionId = response.messages.findLast((message) => message.agentSessionId)?.agentSessionId;
|
|
142
|
+
if (latestSessionId && currentConversation.value.threadSessionId === null) {
|
|
143
|
+
currentConversation.value = {
|
|
144
|
+
...currentConversation.value,
|
|
145
|
+
threadSessionId: latestSessionId
|
|
146
|
+
};
|
|
147
|
+
}
|
|
87
148
|
for (const terminalMessage of terminalMessages)
|
|
88
149
|
chatMessageTerminal.emit(terminalMessage);
|
|
89
150
|
if (response.newFiles && response.newFiles.length > 0) {
|
|
@@ -123,6 +184,10 @@ export function useChat() {
|
|
|
123
184
|
pollGeneration++;
|
|
124
185
|
endPolling();
|
|
125
186
|
}
|
|
187
|
+
requestDurableRecovery = () => {
|
|
188
|
+
startPolling();
|
|
189
|
+
void pollMessages();
|
|
190
|
+
};
|
|
126
191
|
const pendingModelBuckets = useState("chat-pending-model-selection", () => ({}));
|
|
127
192
|
const pendingModelSelection = computed({
|
|
128
193
|
get: () => {
|
|
@@ -142,7 +207,15 @@ export function useChat() {
|
|
|
142
207
|
return;
|
|
143
208
|
currentConversation.value = { ...conversation, model };
|
|
144
209
|
};
|
|
210
|
+
const adoptConversation = (conv) => {
|
|
211
|
+
currentConversation.value = { ...conv, messages: messages.value };
|
|
212
|
+
titleGenerated.value = false;
|
|
213
|
+
};
|
|
145
214
|
const setConversation = (conv) => {
|
|
215
|
+
if (currentConversation.value && currentConversation.value.id !== conv.id) {
|
|
216
|
+
directStream.disconnect();
|
|
217
|
+
streamingMessageId.value = null;
|
|
218
|
+
}
|
|
146
219
|
const conversationMessages = "messages" in conv ? conv.messages : [];
|
|
147
220
|
currentConversation.value = { ...conv, messages: conversationMessages };
|
|
148
221
|
messages.value = conversationMessages;
|
|
@@ -163,8 +236,14 @@ export function useChat() {
|
|
|
163
236
|
if (shouldTreatLoadedConversationTitleAsGenerated(data.messages)) {
|
|
164
237
|
titleGenerated.value = true;
|
|
165
238
|
}
|
|
166
|
-
|
|
239
|
+
const pendingAssistant = data.messages.findLast((m) => m.role === "assistant" && m.status === "pending");
|
|
240
|
+
if (pendingAssistant) {
|
|
241
|
+
streamingMessageId.value = pendingAssistant.id;
|
|
242
|
+
directStream.beginMessage();
|
|
167
243
|
startPolling();
|
|
244
|
+
} else {
|
|
245
|
+
streamingMessageId.value = null;
|
|
246
|
+
directStream.disconnect();
|
|
168
247
|
}
|
|
169
248
|
} catch (err) {
|
|
170
249
|
currentConversation.value = null;
|
|
@@ -176,15 +255,18 @@ export function useChat() {
|
|
|
176
255
|
loading.value = false;
|
|
177
256
|
}
|
|
178
257
|
};
|
|
179
|
-
const sendMessage = async (content, files, agentInputs) => {
|
|
180
|
-
if (!currentConversation.value)
|
|
258
|
+
const sendMessage = async (content, files, agentInputs, prepare) => {
|
|
259
|
+
if (!currentConversation.value && !prepare)
|
|
181
260
|
return null;
|
|
182
261
|
sending.value = true;
|
|
183
262
|
error.value = null;
|
|
263
|
+
sendStartedAt = Date.now();
|
|
264
|
+
schedulePreparingSteps();
|
|
184
265
|
const filesWithSource = files?.map((f) => ({ ...f, source: "user" })) ?? null;
|
|
266
|
+
const conversationId = currentConversation.value?.id ?? "";
|
|
185
267
|
const optimisticUserMessage = {
|
|
186
268
|
id: crypto.randomUUID(),
|
|
187
|
-
conversationId
|
|
269
|
+
conversationId,
|
|
188
270
|
role: "user",
|
|
189
271
|
content,
|
|
190
272
|
status: "completed",
|
|
@@ -198,7 +280,7 @@ export function useChat() {
|
|
|
198
280
|
};
|
|
199
281
|
const optimisticAssistantMessage = {
|
|
200
282
|
id: crypto.randomUUID(),
|
|
201
|
-
conversationId
|
|
283
|
+
conversationId,
|
|
202
284
|
role: "assistant",
|
|
203
285
|
content: "",
|
|
204
286
|
status: "pending",
|
|
@@ -210,28 +292,68 @@ export function useChat() {
|
|
|
210
292
|
createdAt: /* @__PURE__ */ new Date(),
|
|
211
293
|
updatedAt: null
|
|
212
294
|
};
|
|
295
|
+
streamingMessageId.value = optimisticAssistantMessage.id;
|
|
296
|
+
directStream.beginMessage();
|
|
213
297
|
messages.value = [...messages.value, optimisticUserMessage, optimisticAssistantMessage];
|
|
298
|
+
const controller = new AbortController();
|
|
299
|
+
gateController = controller;
|
|
214
300
|
try {
|
|
301
|
+
let wakeSessionId;
|
|
302
|
+
if (prepare) {
|
|
303
|
+
gateHolding.value = true;
|
|
304
|
+
try {
|
|
305
|
+
wakeSessionId = (await prepare(controller.signal)).wakeSessionId;
|
|
306
|
+
} finally {
|
|
307
|
+
gateHolding.value = false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (controller.signal.aborted)
|
|
311
|
+
throw new DOMException("Aborted", "AbortError");
|
|
312
|
+
if (!currentConversation.value)
|
|
313
|
+
throw new Error("Erro ao enviar mensagem");
|
|
215
314
|
const resolvedAgentInputs = resolveTelaAgentInputs(agentInputs, embedConfig?.telaAgentInputs?.value);
|
|
216
315
|
const body = {
|
|
217
316
|
content,
|
|
218
317
|
files,
|
|
219
|
-
...resolvedAgentInputs !== void 0 ? { agentInputs: resolvedAgentInputs } : {}
|
|
318
|
+
...resolvedAgentInputs !== void 0 ? { agentInputs: resolvedAgentInputs } : {},
|
|
319
|
+
...wakeSessionId ? { wakeSessionId } : {},
|
|
320
|
+
clientTiming: {
|
|
321
|
+
// Everything the gate held goes in here: whatever the server
|
|
322
|
+
// measures, the user has already been waiting this long.
|
|
323
|
+
sinceSendMs: Math.max(0, Date.now() - sendStartedAt),
|
|
324
|
+
...standInOutputAtMs !== null ? { standInOutputAtMs } : {}
|
|
325
|
+
}
|
|
220
326
|
};
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
327
|
+
const acceptMessagePair = (response2) => {
|
|
328
|
+
messages.value = messages.value.map((msg) => {
|
|
329
|
+
if (msg.id === optimisticUserMessage.id)
|
|
330
|
+
return response2.userMessage;
|
|
331
|
+
if (msg.id === optimisticAssistantMessage.id)
|
|
332
|
+
return response2.assistantMessage;
|
|
333
|
+
return msg;
|
|
334
|
+
});
|
|
335
|
+
streamingMessageId.value = response2.assistantMessage.id;
|
|
336
|
+
};
|
|
337
|
+
let response;
|
|
338
|
+
let acceptedByStream = false;
|
|
339
|
+
try {
|
|
340
|
+
response = await directStream.connectMessage(
|
|
341
|
+
chatApi.path(`/conversations/${currentConversation.value.id}/messages/stream`),
|
|
342
|
+
chatApi.withChatHeaders({
|
|
343
|
+
method: "POST",
|
|
344
|
+
headers: { "Content-Type": "application/json" },
|
|
345
|
+
body: JSON.stringify(body)
|
|
346
|
+
}),
|
|
347
|
+
acceptMessagePair
|
|
348
|
+
);
|
|
349
|
+
acceptedByStream = true;
|
|
350
|
+
} catch {
|
|
351
|
+
response = await $fetch(
|
|
352
|
+
chatApi.path(`/conversations/${currentConversation.value.id}/messages`),
|
|
353
|
+
chatApi.withChatHeaders({ method: "POST", body })
|
|
354
|
+
);
|
|
355
|
+
acceptMessagePair(response);
|
|
356
|
+
}
|
|
235
357
|
if (files && files.length > 0) {
|
|
236
358
|
addFilesToConversation(
|
|
237
359
|
currentConversation.value.id,
|
|
@@ -240,29 +362,46 @@ export function useChat() {
|
|
|
240
362
|
"user"
|
|
241
363
|
);
|
|
242
364
|
}
|
|
243
|
-
|
|
365
|
+
if (!acceptedByStream)
|
|
366
|
+
startPolling();
|
|
244
367
|
return response.userMessage;
|
|
245
368
|
} catch (err) {
|
|
246
369
|
messages.value = messages.value.filter(
|
|
247
370
|
(msg) => msg.id !== optimisticUserMessage.id && msg.id !== optimisticAssistantMessage.id
|
|
248
371
|
);
|
|
372
|
+
streamingMessageId.value = null;
|
|
373
|
+
clearPreparingSteps();
|
|
374
|
+
directStream.disconnect();
|
|
249
375
|
stopPolling();
|
|
376
|
+
if (err instanceof DOMException && err.name === "AbortError")
|
|
377
|
+
return null;
|
|
250
378
|
const message = getChatApiErrorCode(err) === "workspace_agent_not_ready" ? WORKSPACE_AGENT_NOT_READY_TEXT : err instanceof Error ? err.message : "Erro ao enviar mensagem";
|
|
251
379
|
error.value = message;
|
|
252
380
|
Sentry.captureException(err);
|
|
253
381
|
return null;
|
|
254
382
|
} finally {
|
|
255
383
|
sending.value = false;
|
|
384
|
+
gateHolding.value = false;
|
|
385
|
+
if (gateController === controller)
|
|
386
|
+
gateController = null;
|
|
256
387
|
}
|
|
257
388
|
};
|
|
258
389
|
const clearConversation = () => {
|
|
259
390
|
stopPolling();
|
|
391
|
+
gateController?.abort();
|
|
392
|
+
streamingMessageId.value = null;
|
|
393
|
+
clearPreparingSteps();
|
|
394
|
+
directStream.disconnect();
|
|
260
395
|
currentConversation.value = null;
|
|
261
396
|
messages.value = [];
|
|
262
397
|
error.value = null;
|
|
263
398
|
};
|
|
264
399
|
const cancelling = ref(false);
|
|
265
400
|
const cancelConversation = async () => {
|
|
401
|
+
if (gateHolding.value) {
|
|
402
|
+
gateController?.abort();
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
266
405
|
if (!currentConversation.value || !hasPendingMessage.value)
|
|
267
406
|
return false;
|
|
268
407
|
cancelling.value = true;
|
|
@@ -278,6 +417,8 @@ export function useChat() {
|
|
|
278
417
|
return false;
|
|
279
418
|
}
|
|
280
419
|
messages.value = markPendingAssistantMessagesInterrupted(messages.value);
|
|
420
|
+
streamingMessageId.value = null;
|
|
421
|
+
directStream.disconnect();
|
|
281
422
|
stopPolling();
|
|
282
423
|
return true;
|
|
283
424
|
} catch (err) {
|
|
@@ -304,6 +445,8 @@ export function useChat() {
|
|
|
304
445
|
messages: []
|
|
305
446
|
};
|
|
306
447
|
messages.value = [];
|
|
448
|
+
streamingMessageId.value = null;
|
|
449
|
+
directStream.disconnect();
|
|
307
450
|
return true;
|
|
308
451
|
} catch (err) {
|
|
309
452
|
const message = err instanceof Error ? err.message : "Erro ao limpar conversa";
|
|
@@ -312,7 +455,7 @@ export function useChat() {
|
|
|
312
455
|
return false;
|
|
313
456
|
}
|
|
314
457
|
};
|
|
315
|
-
const retryMessage = async (messageId) => {
|
|
458
|
+
const retryMessage = async (messageId, wakeSessionId) => {
|
|
316
459
|
if (!currentConversation.value)
|
|
317
460
|
return false;
|
|
318
461
|
error.value = null;
|
|
@@ -320,15 +463,22 @@ export function useChat() {
|
|
|
320
463
|
if (!retry)
|
|
321
464
|
return false;
|
|
322
465
|
messages.value = retry.messages;
|
|
466
|
+
streamingMessageId.value = messageId;
|
|
467
|
+
directStream.beginMessage();
|
|
323
468
|
try {
|
|
324
469
|
await $fetch(
|
|
325
470
|
chatApi.path(`/conversations/${currentConversation.value.id}/messages/${messageId}/retry`),
|
|
326
|
-
chatApi.withChatHeaders({
|
|
471
|
+
chatApi.withChatHeaders({
|
|
472
|
+
method: "POST",
|
|
473
|
+
body: wakeSessionId ? { wakeSessionId } : {}
|
|
474
|
+
})
|
|
327
475
|
);
|
|
328
476
|
startPolling();
|
|
329
477
|
return true;
|
|
330
478
|
} catch (err) {
|
|
331
479
|
messages.value = restoreAssistantMessageAfterRetryFailure(messages.value, retry.originalMessage);
|
|
480
|
+
streamingMessageId.value = null;
|
|
481
|
+
directStream.disconnect();
|
|
332
482
|
const errorMessage = getChatApiErrorCode(err) === "workspace_agent_not_ready" ? WORKSPACE_AGENT_NOT_READY_TEXT : err instanceof Error ? err.message : "Erro ao reenviar mensagem";
|
|
333
483
|
error.value = errorMessage;
|
|
334
484
|
Sentry.captureException(err);
|
|
@@ -400,6 +550,8 @@ export function useChat() {
|
|
|
400
550
|
};
|
|
401
551
|
watch(hasPendingMessage, (isPending, wasPending) => {
|
|
402
552
|
if (wasPending && !isPending) {
|
|
553
|
+
streamingMessageId.value = null;
|
|
554
|
+
directStream.disconnect();
|
|
403
555
|
void generateTitle();
|
|
404
556
|
}
|
|
405
557
|
});
|
|
@@ -414,11 +566,14 @@ export function useChat() {
|
|
|
414
566
|
error: readonly(error),
|
|
415
567
|
isPolling: readonly(isPolling),
|
|
416
568
|
hasPendingMessage,
|
|
569
|
+
streamingActiveMessageId,
|
|
570
|
+
preparingSteps: readonly(preparingSteps),
|
|
417
571
|
cancelling: readonly(cancelling),
|
|
418
572
|
generatingTitle: readonly(generatingTitle),
|
|
419
573
|
animatedTitle: readonly(animatedTitle),
|
|
420
574
|
loadConversation,
|
|
421
575
|
setConversation,
|
|
576
|
+
adoptConversation,
|
|
422
577
|
setConversationModel,
|
|
423
578
|
pendingModelSelection,
|
|
424
579
|
sendMessage,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SendMessageResponse } from '../../types/schemas/chat/messages.js';
|
|
2
|
+
export type DirectStreamActivity = 'idle' | 'connected' | 'active';
|
|
3
|
+
export type SseFrame = {
|
|
4
|
+
event?: string;
|
|
5
|
+
data: string;
|
|
6
|
+
};
|
|
7
|
+
export type MessagePartsEvent = {
|
|
8
|
+
kind: 'message-parts';
|
|
9
|
+
runId: string;
|
|
10
|
+
deltaSeq: number;
|
|
11
|
+
parentToolUseId?: string | null;
|
|
12
|
+
event: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
export type MessagePartsAccumulator = {
|
|
15
|
+
runId: string | null;
|
|
16
|
+
lastDeltaSeq: number;
|
|
17
|
+
textByIndex: Record<number, string>;
|
|
18
|
+
thinkingByIndex: Record<number, string>;
|
|
19
|
+
};
|
|
20
|
+
export declare function parseSseFrames(source: string): {
|
|
21
|
+
frames: SseFrame[];
|
|
22
|
+
rest: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function createMessagePartsAccumulator(runId?: string | null): MessagePartsAccumulator;
|
|
25
|
+
export declare function applyMessagePartsEvent(accumulator: MessagePartsAccumulator, input: MessagePartsEvent): {
|
|
26
|
+
text: string;
|
|
27
|
+
thinking: string;
|
|
28
|
+
changed: boolean;
|
|
29
|
+
};
|
|
30
|
+
export declare function useDirectStream(options?: {
|
|
31
|
+
onResult?: () => void;
|
|
32
|
+
onDisconnect?: () => void;
|
|
33
|
+
}): {
|
|
34
|
+
activity: any;
|
|
35
|
+
streamingText: any;
|
|
36
|
+
streamingThinking: any;
|
|
37
|
+
activeRunId: any;
|
|
38
|
+
connectMessage: (url: string, init: RequestInit, onAccepted: (response: SendMessageResponse) => void) => Promise<SendMessageResponse>;
|
|
39
|
+
beginMessage: () => void;
|
|
40
|
+
disconnect: (reset?: boolean) => void;
|
|
41
|
+
};
|