@meistrari/chat-nuxt 3.10.0 → 3.11.1

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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/embed/components/chat-embed-inner.vue +76 -40
  4. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +2 -2
  5. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +0 -1
  6. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +2 -2
  7. package/dist/runtime/embed/composables/use-agent-wake.d.ts +22 -0
  8. package/dist/runtime/embed/composables/use-agent-wake.js +178 -0
  9. package/dist/runtime/embed/feature-config.d.ts +4 -4
  10. package/dist/runtime/embed/feature-config.js +2 -1
  11. package/dist/runtime/embed/loading-messages.d.ts +16 -0
  12. package/dist/runtime/embed/loading-messages.js +10 -0
  13. package/dist/runtime/messages/components/chat-message-bubble.d.vue.ts +10 -1
  14. package/dist/runtime/messages/components/chat-message-bubble.vue +18 -7
  15. package/dist/runtime/messages/components/chat-message-bubble.vue.d.ts +10 -1
  16. package/dist/runtime/messages/components/chat-message-list.d.vue.ts +6 -1
  17. package/dist/runtime/messages/components/chat-message-list.vue +11 -2
  18. package/dist/runtime/messages/components/chat-message-list.vue.d.ts +6 -1
  19. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.d.vue.ts +2 -1
  20. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +9 -8
  21. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue.d.ts +2 -1
  22. package/dist/runtime/messages/composables/chat.d.ts +8 -2
  23. package/dist/runtime/messages/composables/chat.js +180 -25
  24. package/dist/runtime/messages/composables/use-direct-stream.d.ts +41 -0
  25. package/dist/runtime/messages/composables/use-direct-stream.js +263 -0
  26. package/dist/runtime/messages/reasoning.d.ts +7 -0
  27. package/dist/runtime/messages/reasoning.js +5 -0
  28. package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -0
  29. package/dist/runtime/types/schemas/chat/messages.d.ts +39 -0
  30. package/dist/runtime/types/schemas/chat/messages.js +24 -0
  31. package/dist/runtime/types/schemas/chat/models.d.ts +3 -2
  32. package/dist/runtime/types/schemas/chat/models.js +2 -1
  33. package/dist/runtime/types/schemas/chat/ui.d.ts +10 -0
  34. package/dist/runtime/types/schemas/chat/ui.js +1 -0
  35. package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +35 -0
  36. package/dist/runtime/types/schemas/integrations/tela-agent-api.js +17 -0
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -59,7 +59,7 @@ Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload
59
59
  | `telaAgentInputs` | `TelaAgentExecutionInput[] \| null` | `undefined` | Inputs sent with each Tela agent run; hides the variables panel when non-null |
60
60
  | `workspaceSettings` | `WorkspaceSettings \| null` | `null` | Override settings from host app; not accepted in Tela agent mode |
61
61
  | `user` | `ChatActor \| null` | `null` | Override current user display info; not accepted in Tela agent mode |
62
- | `features` | `Partial<ChatFeatureConfig>` | `{}` | Toggle optional features: `showUsageTab`, `showDebugOption`, `showCancelButton` (all `false`) |
62
+ | `features` | `Partial<ChatFeatureConfig>` | `{}` | Toggle optional features: `showUsageTab`, `showDebugOption`, `showCancelButton` (default `false`) and `showTtft` (default `true`) |
63
63
  | `loadingMessages` | `readonly string[] \| null` | Default chat messages | Override pending-response messages |
64
64
  | `loadingMessagesMode` | `'ordered' \| 'random' \| null` | `'ordered'` | Rotate custom messages in order or at random; only applies when `loadingMessages` has a non-empty message |
65
65
  | `customComponents` | `Record<string, Component>` | `undefined` | Custom markdown renderers keyed by markdown node or tag name |
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "3.10.0",
4
+ "version": "3.11.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -18,6 +18,7 @@ import { useConversationFiles } from "../../files/composables/conversation-files
18
18
  import { useConversationFilesPanel } from "../../files/composables/conversation-files-panel";
19
19
  import { useConversations } from "../../conversations/composables/conversations";
20
20
  import { useEmbedConfig } from "../composables/embed-config";
21
+ import { useAgentWake } from "../composables/use-agent-wake";
21
22
  import { useFileDrop } from "../../files/composables/file-drop";
22
23
  import { useFilePreviewPanel } from "../../files/composables/file-preview-panel";
23
24
  import { usePendingConversationGroup } from "../../conversations/composables/pending-conversation-group";
@@ -155,16 +156,26 @@ const {
155
156
  generatingTitle,
156
157
  animatedTitle,
157
158
  hasPendingMessage,
159
+ streamingActiveMessageId,
160
+ preparingSteps,
158
161
  loadConversation,
159
162
  setConversation,
163
+ adoptConversation,
160
164
  setConversationModel,
161
165
  pendingModelSelection,
162
166
  sendMessage,
163
167
  retryMessage,
164
168
  cancelConversation,
165
169
  startPolling,
166
- clearConversation
170
+ clearConversation,
171
+ error: chatError
167
172
  } = useChat();
173
+ const agentWake = useAgentWake();
174
+ const modelUpdateInFlight = ref(null);
175
+ watch(chatError, (message) => {
176
+ if (message)
177
+ statusToast.update({ text: message, icon: "i-ph-warning" });
178
+ });
168
179
  const conversationIdRef = computed(() => currentConversation.value?.id ?? null);
169
180
  const { total, files: conversationFiles } = useConversationFiles(conversationIdRef);
170
181
  const selectedModel = computed({
@@ -175,10 +186,15 @@ const selectedModel = computed({
175
186
  if (conversation && conversation.model !== value) {
176
187
  const previousModel = conversation.model;
177
188
  setConversationModel(value);
178
- void updateConversationModel(conversation.id, value).then((updated) => {
189
+ const update = updateConversationModel(conversation.id, value).then((updated) => {
179
190
  if (!updated && currentConversation.value?.id === conversation.id && currentConversation.value.model === value) {
180
191
  setConversationModel(previousModel);
181
192
  }
193
+ return updated;
194
+ });
195
+ modelUpdateInFlight.value = update.finally(() => {
196
+ if (modelUpdateInFlight.value === update)
197
+ modelUpdateInFlight.value = null;
182
198
  });
183
199
  }
184
200
  }
@@ -222,6 +238,9 @@ function updateConversationId(nextConversationId) {
222
238
  }
223
239
  async function ensureConversationLoaded(conversationId) {
224
240
  if (currentConversation.value?.id === conversationId) {
241
+ void agentWake.wake(
242
+ currentConversation.value.threadSessionId ?? void 0
243
+ );
225
244
  startPolling();
226
245
  return;
227
246
  }
@@ -232,12 +251,16 @@ async function ensureConversationLoaded(conversationId) {
232
251
  try {
233
252
  await loadConversation(conversationId);
234
253
  if (currentConversation.value?.id !== conversationId) {
254
+ agentWake.skip();
235
255
  statusToast.update({
236
256
  text: "Erro ao carregar conversa",
237
257
  icon: "i-ph-warning"
238
258
  });
239
259
  return;
240
260
  }
261
+ void agentWake.wake(
262
+ currentConversation.value.threadSessionId ?? void 0
263
+ );
241
264
  startPolling();
242
265
  } finally {
243
266
  if (loadingConversationId.value === conversationId) {
@@ -247,6 +270,7 @@ async function ensureConversationLoaded(conversationId) {
247
270
  }
248
271
  function resetConversation(shouldEmit = true) {
249
272
  clearConversation();
273
+ void agentWake.wake();
250
274
  activeTab.value = "chat";
251
275
  closeFilesPanel();
252
276
  closePreview();
@@ -404,43 +428,43 @@ const conversationCreator = computed(
404
428
  getMember
405
429
  })
406
430
  );
431
+ function claimableWakeSessionId() {
432
+ return agentWake.state.value === "ready" && !currentConversation.value?.threadSessionId ? agentWake.sessionId.value : void 0;
433
+ }
407
434
  async function handleSend(content, files, agentInputs) {
408
- let createdConversationId = null;
409
- const resolvedAgentInputs = resolveTelaAgentInputs(
410
- agentInputs,
411
- embedConfig?.telaAgentInputs?.value
412
- );
413
- if (!activeConversationId.value || !currentConversation.value) {
414
- const conversation = await createConversation({
415
- model: selectedModel.value,
416
- ...resolvedAgentInputs !== void 0 ? { agentInputs: resolvedAgentInputs } : {}
417
- });
418
- if (!conversation)
419
- return;
420
- const groupId = pendingConversationGroupId.value;
421
- clearPendingConversationGroup();
422
- if (groupId && await moveConversationToGroup(conversation.id, groupId)) {
423
- const groupedConversation = { ...conversation, groupId };
424
- setConversation(groupedConversation);
425
- } else {
426
- setConversation(conversation);
435
+ const resolvedAgentInputs = resolveTelaAgentInputs(agentInputs, embedConfig?.telaAgentInputs?.value);
436
+ let claimedWakeSessionId;
437
+ const sent = await sendMessage(content, files.length > 0 ? files : void 0, resolvedAgentInputs, async (signal) => {
438
+ if (!activeConversationId.value || !currentConversation.value) {
439
+ const conversation2 = await createConversation({
440
+ model: selectedModel.value,
441
+ ...resolvedAgentInputs !== void 0 ? { agentInputs: resolvedAgentInputs } : {}
442
+ });
443
+ if (!conversation2)
444
+ throw new Error("Erro ao criar conversa");
445
+ const groupId = pendingConversationGroupId.value;
446
+ clearPendingConversationGroup();
447
+ const grouped = groupId && await moveConversationToGroup(conversation2.id, groupId);
448
+ adoptConversation(grouped ? { ...conversation2, groupId } : conversation2);
449
+ updateConversationId(conversation2.id);
427
450
  }
428
- createdConversationId = conversation.id;
429
- }
430
- if (!currentConversation.value)
431
- return;
432
- const sendPromise = sendMessage(
433
- content,
434
- files.length > 0 ? files : void 0,
435
- resolvedAgentInputs
436
- );
437
- if (createdConversationId) {
438
- updateConversationId(createdConversationId);
439
- }
440
- await sendPromise;
451
+ if (modelUpdateInFlight.value)
452
+ await modelUpdateInFlight.value;
453
+ const conversation = currentConversation.value;
454
+ if (!conversation)
455
+ throw new Error("Erro ao preparar conversa");
456
+ if (!conversation.threadSessionId)
457
+ await agentWake.waitUntilSettled(signal);
458
+ claimedWakeSessionId = claimableWakeSessionId();
459
+ return claimedWakeSessionId ? { wakeSessionId: claimedWakeSessionId } : {};
460
+ });
461
+ if (sent && claimedWakeSessionId)
462
+ agentWake.consume();
441
463
  }
442
464
  async function handleRetry(messageId) {
443
- await retryMessage(messageId);
465
+ if (!currentConversation.value?.threadSessionId)
466
+ await agentWake.waitUntilSettled();
467
+ await retryMessage(messageId, claimableWakeSessionId());
444
468
  }
445
469
  async function handleNewConversation(groupId) {
446
470
  setPendingConversationGroup(groupId);
@@ -765,7 +789,14 @@ async function handleDelete() {
765
789
  @tab-change="handleTabChange"
766
790
  >
767
791
  <template #loading>
768
- <ChatMessageList :messages="[]" loading variant="mobile" />
792
+ <ChatMessageList
793
+ :messages="[]"
794
+ :streaming-active-message-id="streamingActiveMessageId"
795
+ :preparing-steps="preparingSteps"
796
+ :show-ttft="resolvedFeatures.showTtft"
797
+ loading
798
+ variant="mobile"
799
+ />
769
800
  <ChatMessageInput
770
801
  v-model:selected-model="selectedModel"
771
802
  :disabled="true"
@@ -798,7 +829,6 @@ async function handleDelete() {
798
829
  <ChatMessageInput
799
830
  ref="messageInputRef"
800
831
  v-model:selected-model="selectedModel"
801
- :disabled="hasPendingMessage"
802
832
  :loading="hasPendingMessage"
803
833
  :creator-name="currentUser?.name ?? void 0"
804
834
  :creator-email="
@@ -823,13 +853,15 @@ async function handleDelete() {
823
853
 
824
854
  <ChatMessageList
825
855
  :messages="messages"
856
+ :streaming-active-message-id="streamingActiveMessageId"
857
+ :preparing-steps="preparingSteps"
858
+ :show-ttft="resolvedFeatures.showTtft"
826
859
  variant="mobile"
827
860
  @retry="handleRetry"
828
861
  />
829
862
  <ChatMessageInput
830
863
  ref="messageInputRef"
831
864
  v-model:selected-model="selectedModel"
832
- :disabled="hasPendingMessage"
833
865
  :loading="hasPendingMessage"
834
866
  :creator-name="currentUser?.name ?? void 0"
835
867
  :creator-email="
@@ -950,6 +982,9 @@ async function handleDelete() {
950
982
  >
951
983
  <ChatMessageList
952
984
  :messages="[]"
985
+ :streaming-active-message-id="streamingActiveMessageId"
986
+ :preparing-steps="preparingSteps"
987
+ :show-ttft="resolvedFeatures.showTtft"
953
988
  loading
954
989
  :variant="compact ? 'widget' : 'page'"
955
990
  />
@@ -991,7 +1026,6 @@ async function handleDelete() {
991
1026
  <ChatMessageInput
992
1027
  ref="messageInputRef"
993
1028
  v-model:selected-model="selectedModel"
994
- :disabled="hasPendingMessage"
995
1029
  :loading="hasPendingMessage"
996
1030
  :creator-name="currentUser?.name ?? void 0"
997
1031
  :creator-email="
@@ -1019,13 +1053,15 @@ async function handleDelete() {
1019
1053
  >
1020
1054
  <ChatMessageList
1021
1055
  :messages="messages"
1056
+ :streaming-active-message-id="streamingActiveMessageId"
1057
+ :preparing-steps="preparingSteps"
1058
+ :show-ttft="resolvedFeatures.showTtft"
1022
1059
  :variant="compact ? 'widget' : 'page'"
1023
1060
  @retry="handleRetry"
1024
1061
  />
1025
1062
  <ChatMessageInput
1026
1063
  ref="messageInputRef"
1027
1064
  v-model:selected-model="selectedModel"
1028
- :disabled="hasPendingMessage"
1029
1065
  :loading="hasPendingMessage"
1030
1066
  :creator-name="currentUser?.name ?? void 0"
1031
1067
  :creator-email="
@@ -57,12 +57,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
57
57
  "update:selectedModel": (value: string | null) => any;
58
58
  } & {
59
59
  delete: () => any;
60
- cancel: () => any;
61
60
  rename: () => any;
62
61
  duplicate: () => any;
63
62
  export: () => any;
64
63
  copyLink: () => any;
65
64
  selectConversation: (id: string) => any;
65
+ cancel: () => any;
66
66
  settings: () => any;
67
67
  send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
68
68
  debug: () => any;
@@ -78,12 +78,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
78
78
  "update:workspaceSheetOpen": (value: boolean) => any;
79
79
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
80
80
  onDelete?: (() => any) | undefined;
81
- onCancel?: (() => any) | undefined;
82
81
  onRename?: (() => any) | undefined;
83
82
  onDuplicate?: (() => any) | undefined;
84
83
  onExport?: (() => any) | undefined;
85
84
  onCopyLink?: (() => any) | undefined;
86
85
  onSelectConversation?: ((id: string) => any) | undefined;
86
+ onCancel?: (() => any) | undefined;
87
87
  onSettings?: (() => any) | undefined;
88
88
  onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
89
89
  "onUpdate:selectedModel"?: ((value: string | null) => any) | undefined;
@@ -225,7 +225,6 @@ function startNewConversationFromOutdatedSettings() {
225
225
  <ChatMessageInput
226
226
  ref="homeInputRef"
227
227
  v-model:selected-model="selectedModel"
228
- :disabled="hasPendingMessage"
229
228
  :loading="hasPendingMessage"
230
229
  :creator-name="currentUser?.name ?? void 0"
231
230
  :creator-email="currentUser?.email ?? void 0"
@@ -57,12 +57,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
57
57
  "update:selectedModel": (value: string | null) => any;
58
58
  } & {
59
59
  delete: () => any;
60
- cancel: () => any;
61
60
  rename: () => any;
62
61
  duplicate: () => any;
63
62
  export: () => any;
64
63
  copyLink: () => any;
65
64
  selectConversation: (id: string) => any;
65
+ cancel: () => any;
66
66
  settings: () => any;
67
67
  send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
68
68
  debug: () => any;
@@ -78,12 +78,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
78
78
  "update:workspaceSheetOpen": (value: boolean) => any;
79
79
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
80
80
  onDelete?: (() => any) | undefined;
81
- onCancel?: (() => any) | undefined;
82
81
  onRename?: (() => any) | undefined;
83
82
  onDuplicate?: (() => any) | undefined;
84
83
  onExport?: (() => any) | undefined;
85
84
  onCopyLink?: (() => any) | undefined;
86
85
  onSelectConversation?: ((id: string) => any) | undefined;
86
+ onCancel?: (() => any) | undefined;
87
87
  onSettings?: (() => any) | undefined;
88
88
  onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
89
89
  "onUpdate:selectedModel"?: ((value: string | null) => any) | undefined;
@@ -0,0 +1,22 @@
1
+ import type { TelaAgentWakeResponse } from '../../types/schemas/integrations/tela-agent-api.js';
2
+ export type AgentWakeSnapshot = {
3
+ state: TelaAgentWakeResponse['status'] | 'idle';
4
+ sessionId?: string;
5
+ };
6
+ export declare function pollAgentWake(options: {
7
+ request: () => Promise<TelaAgentWakeResponse>;
8
+ onResponse?: (response: TelaAgentWakeResponse) => void;
9
+ signal?: AbortSignal;
10
+ pollIntervalMs?: number;
11
+ budgetMs?: number;
12
+ now?: () => number;
13
+ wait?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
14
+ }): Promise<TelaAgentWakeResponse>;
15
+ export declare function useAgentWake(): {
16
+ state: any;
17
+ sessionId: any;
18
+ wake: (requestedSessionId?: string) => Promise<TelaAgentWakeResponse>;
19
+ skip: () => void;
20
+ consume: () => void;
21
+ waitUntilSettled: (signal?: AbortSignal) => Promise<AgentWakeSnapshot>;
22
+ };
@@ -0,0 +1,178 @@
1
+ import { useChatApi } from "../../shared/composables/chat-api.js";
2
+ const WAKE_POLL_INTERVAL_MS = 1500;
3
+ const WAKE_BUDGET_MS = 3e4;
4
+ const WAKE_SETTLE_TIMEOUT_MS = 35e3;
5
+ export async function pollAgentWake(options) {
6
+ const now = options.now ?? Date.now;
7
+ const deadline = now() + (options.budgetMs ?? WAKE_BUDGET_MS);
8
+ const wait = options.wait ?? ((delayMs, signal) => new Promise((resolve, reject) => {
9
+ let timer;
10
+ const onAbort = () => {
11
+ if (timer)
12
+ clearTimeout(timer);
13
+ reject(new DOMException("Aborted", "AbortError"));
14
+ };
15
+ timer = setTimeout(() => {
16
+ signal?.removeEventListener("abort", onAbort);
17
+ resolve();
18
+ }, delayMs);
19
+ if (signal?.aborted)
20
+ onAbort();
21
+ else
22
+ signal?.addEventListener("abort", onAbort, { once: true });
23
+ }));
24
+ try {
25
+ while (!options.signal?.aborted && now() <= deadline) {
26
+ const response = await options.request();
27
+ options.onResponse?.(response);
28
+ if (response.status !== "waking")
29
+ return response;
30
+ const remaining = deadline - now();
31
+ if (remaining <= 0)
32
+ break;
33
+ await wait(Math.min(options.pollIntervalMs ?? WAKE_POLL_INTERVAL_MS, remaining), options.signal);
34
+ }
35
+ } catch (error) {
36
+ if (error instanceof DOMException && error.name === "AbortError")
37
+ throw error;
38
+ return { status: "unavailable" };
39
+ }
40
+ return { status: "unavailable" };
41
+ }
42
+ export function useAgentWake() {
43
+ const chatApi = useChatApi();
44
+ const snapshot = ref({ state: "idle" });
45
+ const state = computed(() => snapshot.value.state);
46
+ const sessionId = computed(() => snapshot.value.sessionId);
47
+ let generation = 0;
48
+ let abortController = null;
49
+ let activeWake = null;
50
+ function setSnapshot(next) {
51
+ snapshot.value = next;
52
+ }
53
+ function skip() {
54
+ generation++;
55
+ abortController?.abort();
56
+ abortController = null;
57
+ activeWake = null;
58
+ setSnapshot({
59
+ state: "idle",
60
+ ...snapshot.value.sessionId ? { sessionId: snapshot.value.sessionId } : {}
61
+ });
62
+ }
63
+ function consume() {
64
+ generation++;
65
+ abortController?.abort();
66
+ abortController = null;
67
+ activeWake = null;
68
+ setSnapshot({ state: "idle" });
69
+ }
70
+ async function wake(requestedSessionId) {
71
+ generation++;
72
+ const currentGeneration = generation;
73
+ abortController?.abort();
74
+ const controller = new AbortController();
75
+ abortController = controller;
76
+ let wakeSessionId = requestedSessionId ?? snapshot.value.sessionId;
77
+ setSnapshot({
78
+ state: "waking",
79
+ ...wakeSessionId ? { sessionId: wakeSessionId } : {}
80
+ });
81
+ if (import.meta.server) {
82
+ const unavailable = { status: "unavailable" };
83
+ setSnapshot({ state: unavailable.status });
84
+ return unavailable;
85
+ }
86
+ const run = async () => {
87
+ try {
88
+ const result = await pollAgentWake({
89
+ signal: controller.signal,
90
+ request: () => $fetch(
91
+ chatApi.path("/agent/wake"),
92
+ chatApi.withChatHeaders({
93
+ method: "POST",
94
+ signal: controller.signal,
95
+ body: {
96
+ ...wakeSessionId ? { sessionId: wakeSessionId } : {}
97
+ }
98
+ })
99
+ ),
100
+ onResponse: (response) => {
101
+ if (currentGeneration !== generation)
102
+ return;
103
+ wakeSessionId = response.sessionId ?? wakeSessionId;
104
+ setSnapshot({
105
+ state: response.status,
106
+ ...wakeSessionId ? { sessionId: wakeSessionId } : {}
107
+ });
108
+ }
109
+ });
110
+ if (currentGeneration === generation) {
111
+ setSnapshot({
112
+ state: result.status,
113
+ ...result.sessionId ? { sessionId: result.sessionId } : {}
114
+ });
115
+ }
116
+ return result;
117
+ } catch (error) {
118
+ if (!(error instanceof DOMException && error.name === "AbortError") && currentGeneration === generation)
119
+ setSnapshot({ state: "unavailable" });
120
+ return { status: "unavailable" };
121
+ } finally {
122
+ if (abortController === controller)
123
+ abortController = null;
124
+ }
125
+ };
126
+ const pending = run();
127
+ activeWake = pending;
128
+ try {
129
+ return await pending;
130
+ } finally {
131
+ if (activeWake === pending)
132
+ activeWake = null;
133
+ }
134
+ }
135
+ async function waitUntilSettled(signal) {
136
+ if (snapshot.value.state !== "waking" || !activeWake)
137
+ return snapshot.value;
138
+ const pending = activeWake;
139
+ await new Promise((resolve, reject) => {
140
+ let settled = false;
141
+ let timer;
142
+ let onAbort = () => {
143
+ };
144
+ const finish = (error) => {
145
+ if (settled)
146
+ return;
147
+ settled = true;
148
+ if (timer)
149
+ clearTimeout(timer);
150
+ signal?.removeEventListener("abort", onAbort);
151
+ if (error)
152
+ reject(error);
153
+ else
154
+ resolve();
155
+ };
156
+ onAbort = () => finish(new DOMException("Aborted", "AbortError"));
157
+ timer = setTimeout(() => finish(), WAKE_SETTLE_TIMEOUT_MS);
158
+ if (signal?.aborted)
159
+ onAbort();
160
+ else
161
+ signal?.addEventListener("abort", onAbort, { once: true });
162
+ pending.then(() => finish(), () => finish());
163
+ });
164
+ return snapshot.value;
165
+ }
166
+ onUnmounted(() => {
167
+ generation++;
168
+ abortController?.abort();
169
+ });
170
+ return {
171
+ state,
172
+ sessionId,
173
+ wake,
174
+ skip,
175
+ consume,
176
+ waitUntilSettled
177
+ };
178
+ }
@@ -1,7 +1,4 @@
1
- /**
2
- * Optional UI toggles the host passes per embed instance. All default to `false`;
3
- * the host owns the policy (hardcoded, role-gated, or from a feature-flag provider).
4
- */
1
+ /** Optional UI toggles the host passes per embed instance. */
5
2
  export type ChatFeatureConfig = {
6
3
  /** Usage tab in the chat topbar, with per-conversation token and cost stats. */
7
4
  showUsageTab?: boolean;
@@ -9,10 +6,13 @@ export type ChatFeatureConfig = {
9
6
  showDebugOption?: boolean;
10
7
  /** "Stop generation" button while a response streams. */
11
8
  showCancelButton?: boolean;
9
+ /** Time to first token shown in assistant message metadata. Defaults to true. */
10
+ showTtft?: boolean;
12
11
  };
13
12
  export type ResolvedChatFeatureConfig = {
14
13
  showUsageTab: boolean;
15
14
  showDebugOption: boolean;
16
15
  showCancelButton: boolean;
16
+ showTtft: boolean;
17
17
  };
18
18
  export declare function resolveChatFeatures(features?: Partial<ChatFeatureConfig>): ResolvedChatFeatureConfig;
@@ -2,6 +2,7 @@ export function resolveChatFeatures(features) {
2
2
  return {
3
3
  showUsageTab: features?.showUsageTab ?? false,
4
4
  showDebugOption: features?.showDebugOption ?? false,
5
- showCancelButton: features?.showCancelButton ?? false
5
+ showCancelButton: features?.showCancelButton ?? false,
6
+ showTtft: features?.showTtft ?? true
6
7
  };
7
8
  }
@@ -1,4 +1,20 @@
1
1
  export declare const DEFAULT_CHAT_LOADING_MESSAGES: readonly ["Analisando sua pergunta", "Processando informações", "Consultando base de conhecimento", "Organizando ideias", "Elaborando resposta", "Refinando detalhes", "Verificando contexto", "Conectando conceitos", "Preparando resultado", "Quase lá", "Finalizando análise", "Estruturando resposta"];
2
+ /**
3
+ * Stand-in steps for a turn that has not produced its own reasoning yet. They
4
+ * accumulate, so the bubble reads as an agent working through the wait rather
5
+ * than as a loading placeholder. The user message is already delivered at this
6
+ * point, so the wait belongs to the answer.
7
+ */
8
+ export declare const CHAT_PREPARING_STEPS: readonly ["Preparando a resposta", "Analisando a mensagem"];
9
+ /**
10
+ * The first delay is measured from the send, every later one from the step
11
+ * before it — so the sequence paces itself instead of racing a shared clock.
12
+ * Late enough that a turn answering promptly never shows a stand-in, and
13
+ * jittered so it does not look like a fixed-timer artifact.
14
+ */
15
+ export declare const CHAT_PREPARING_STEP_DELAYS_MS: readonly [2500, 4000];
16
+ export declare const CHAT_PREPARING_STEP_JITTER_MS = 500;
17
+ export declare function nextChatPreparingStepDelay(stepIndex: number, random?: () => number): number;
2
18
  export type ChatLoadingMessageMode = 'ordered' | 'random';
3
19
  export type ChatLoadingMessagesInput = {
4
20
  loadingMessages?: readonly string[] | null;
@@ -12,6 +12,16 @@ export const DEFAULT_CHAT_LOADING_MESSAGES = [
12
12
  "Finalizando an\xE1lise",
13
13
  "Estruturando resposta"
14
14
  ];
15
+ export const CHAT_PREPARING_STEPS = [
16
+ "Preparando a resposta",
17
+ "Analisando a mensagem"
18
+ ];
19
+ export const CHAT_PREPARING_STEP_DELAYS_MS = [2500, 4e3];
20
+ export const CHAT_PREPARING_STEP_JITTER_MS = 500;
21
+ export function nextChatPreparingStepDelay(stepIndex, random = Math.random) {
22
+ const base = CHAT_PREPARING_STEP_DELAYS_MS[stepIndex] ?? CHAT_PREPARING_STEP_DELAYS_MS.at(-1);
23
+ return base + Math.floor(random() * CHAT_PREPARING_STEP_JITTER_MS);
24
+ }
15
25
  function normalizeMessages(messages) {
16
26
  if (!messages?.length)
17
27
  return [];
@@ -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
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ }>, {
17
+ showTtft: boolean;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
19
  declare const _default: typeof __VLS_export;
11
20
  export default _default;