@meistrari/chat-nuxt 3.11.1 → 3.12.0-rc.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 (38) hide show
  1. package/README.md +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/assets/css/markstream.css +1 -1
  4. package/dist/runtime/embed/components/chat-embed-inner.vue +75 -17
  5. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +2 -2
  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 +2 -1
  8. package/dist/runtime/embed/composables/use-agent-wake.js +28 -9
  9. package/dist/runtime/embed/feature-config.d.ts +2 -0
  10. package/dist/runtime/embed/feature-config.js +2 -1
  11. package/dist/runtime/messages/components/chat-message-bubble.vue +4 -4
  12. package/dist/runtime/messages/components/chat-message-input.d.vue.ts +8 -2
  13. package/dist/runtime/messages/components/chat-message-input.vue +51 -2
  14. package/dist/runtime/messages/components/chat-message-input.vue.d.ts +8 -2
  15. package/dist/runtime/messages/components/chat-message-list.vue +3 -5
  16. package/dist/runtime/messages/components/reasoning/chat-reasoning-steps.vue +1 -1
  17. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +14 -66
  18. package/dist/runtime/messages/composables/chat-message-terminal.js +3 -3
  19. package/dist/runtime/messages/composables/chat.d.ts +17 -3
  20. package/dist/runtime/messages/composables/chat.js +289 -23
  21. package/dist/runtime/messages/composables/outbound-queue.d.ts +23 -0
  22. package/dist/runtime/messages/composables/outbound-queue.js +28 -0
  23. package/dist/runtime/messages/composables/use-direct-stream.d.ts +8 -1
  24. package/dist/runtime/messages/composables/use-direct-stream.js +70 -11
  25. package/dist/runtime/server/utils/chat-api-proxy.js +3 -1
  26. package/dist/runtime/shared/chat-api-error.d.ts +7 -0
  27. package/dist/runtime/shared/chat-api-error.js +24 -4
  28. package/dist/runtime/types/schemas/chat/chat-config.d.ts +1 -0
  29. package/dist/runtime/types/schemas/chat/chat-config.js +1 -0
  30. package/dist/runtime/types/schemas/chat/messages.d.ts +14 -0
  31. package/dist/runtime/types/schemas/chat/messages.js +6 -1
  32. package/dist/runtime/types/schemas/chat/ui.d.ts +2 -2
  33. package/dist/runtime/types/schemas/chat/ui.js +2 -2
  34. package/dist/runtime/types/schemas/index.d.ts +1 -0
  35. package/dist/runtime/types/schemas/index.js +1 -0
  36. package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +46 -0
  37. package/dist/runtime/types/schemas/integrations/tela-agent-api.js +26 -0
  38. package/package.json +2 -2
package/README.md CHANGED
@@ -101,7 +101,7 @@ Installing `@meistrari/chat-nuxt` installs the runtime packages used by the laye
101
101
 
102
102
  | Package | Version | Purpose |
103
103
  |---------|---------|---------|
104
- | `@meistrari/auth-nuxt` | `3.5.0` | Authentication (required) |
104
+ | `@meistrari/auth-nuxt` | `3.31.0` | Authentication (required) |
105
105
  | `@meistrari/logger` | `^2.1.3` | Structured logging for the server proxy routes |
106
106
  | `@meistrari/tela-build` | `^1.30.0` | UI component library (Nuxt layer) |
107
107
  | `@sentry/nuxt` | `^10.0.0` | Client-side error capture used by chat composables |
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "3.11.1",
4
+ "version": "3.12.0-rc.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -1 +1 @@
1
- .markstream-vue{color:#171717;font-family:Inter,"Inter Fallback: Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:-.15px;line-height:24px;--list-item-marker:#a3a3a3}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{font-weight:580}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{margin:0}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{font-size:24px;font-weight:540;letter-spacing:-.8px;line-height:28px;margin:24px 0 16px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{font-size:20px;font-weight:540;letter-spacing:-.5px;line-height:24px;margin:20px 0 12px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{font-size:20px;font-weight:540;letter-spacing:-.5px;line-height:24px;margin:16px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue h4{font-size:16px;font-weight:540;letter-spacing:-.2px;line-height:20px;margin:12px 0 6px}.markstream-vue .heading-node.heading-5,.markstream-vue h5{font-size:14px;font-weight:540;letter-spacing:-.15px;line-height:16px;margin:8px 0 4px}.markstream-vue .heading-node.heading-6,.markstream-vue h6{font-size:12px;font-weight:540;letter-spacing:-.1px;line-height:14px;margin:8px 0 4px}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
1
+ .markstream-vue{color:#171717;font-family:Inter,"Inter Fallback: Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:-.15px;line-height:24px;--list-item-marker:#a3a3a3;--list-item-counter-marker:#262626}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{font-weight:580}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{margin:0}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{font-size:24px;font-weight:540;letter-spacing:-.8px;line-height:28px;margin:24px 0 16px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{font-size:20px;font-weight:540;letter-spacing:-.5px;line-height:24px;margin:20px 0 12px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{font-size:20px;font-weight:540;letter-spacing:-.5px;line-height:24px;margin:16px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue h4{font-size:16px;font-weight:540;letter-spacing:-.2px;line-height:20px;margin:12px 0 6px}.markstream-vue .heading-node.heading-5,.markstream-vue h5{font-size:14px;font-weight:540;letter-spacing:-.15px;line-height:16px;margin:8px 0 4px}.markstream-vue .heading-node.heading-6,.markstream-vue h6{font-size:12px;font-weight:540;letter-spacing:-.1px;line-height:14px;margin:8px 0 4px}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
@@ -143,6 +143,9 @@ function applySuggestion(prompt) {
143
143
  const breakpoints = useBreakpoints({ mobile: CHAT_MOBILE_BREAKPOINT });
144
144
  const isMobile = breakpoints.smaller("mobile");
145
145
  const drawerOpen = ref(false);
146
+ function restoreQueuedSend(send) {
147
+ messageInputRef.value?.restoreDraft(send);
148
+ }
146
149
  const { isDragging, setupDropZone } = useFileDrop({
147
150
  onDrop: (files) => {
148
151
  messageInputRef.value?.addFiles(files);
@@ -155,7 +158,10 @@ const {
155
158
  loading,
156
159
  generatingTitle,
157
160
  animatedTitle,
158
- hasPendingMessage,
161
+ composerLocked,
162
+ outboundOccupied,
163
+ sessionReady,
164
+ agentSessionRewakeable,
159
165
  streamingActiveMessageId,
160
166
  preparingSteps,
161
167
  loadConversation,
@@ -164,12 +170,18 @@ const {
164
170
  setConversationModel,
165
171
  pendingModelSelection,
166
172
  sendMessage,
173
+ flushOutboundQueue,
174
+ failOutboundQueue,
167
175
  retryMessage,
168
176
  cancelConversation,
169
177
  startPolling,
170
178
  clearConversation,
171
179
  error: chatError
172
- } = useChat();
180
+ } = useChat({
181
+ outboundQueueEnabled: computed(() => resolvedFeatures.value.chatOutboundQueue),
182
+ onQueuedSendCancelled: restoreQueuedSend,
183
+ onQueuedSendFailed: restoreQueuedSend
184
+ });
173
185
  const agentWake = useAgentWake();
174
186
  const modelUpdateInFlight = ref(null);
175
187
  watch(chatError, (message) => {
@@ -186,9 +198,11 @@ const selectedModel = computed({
186
198
  if (conversation && conversation.model !== value) {
187
199
  const previousModel = conversation.model;
188
200
  setConversationModel(value);
189
- const update = updateConversationModel(conversation.id, value).then((updated) => {
201
+ const update = updateConversationModel(conversation.id, value).then(async (updated) => {
190
202
  if (!updated && currentConversation.value?.id === conversation.id && currentConversation.value.model === value) {
191
203
  setConversationModel(previousModel);
204
+ } else if (updated && value) {
205
+ await agentWake.wake(conversation.threadSessionId ?? void 0, value);
192
206
  }
193
207
  return updated;
194
208
  });
@@ -239,7 +253,8 @@ function updateConversationId(nextConversationId) {
239
253
  async function ensureConversationLoaded(conversationId) {
240
254
  if (currentConversation.value?.id === conversationId) {
241
255
  void agentWake.wake(
242
- currentConversation.value.threadSessionId ?? void 0
256
+ currentConversation.value.threadSessionId ?? void 0,
257
+ selectedModel.value
243
258
  );
244
259
  startPolling();
245
260
  return;
@@ -259,7 +274,8 @@ async function ensureConversationLoaded(conversationId) {
259
274
  return;
260
275
  }
261
276
  void agentWake.wake(
262
- currentConversation.value.threadSessionId ?? void 0
277
+ currentConversation.value.threadSessionId ?? void 0,
278
+ selectedModel.value
263
279
  );
264
280
  startPolling();
265
281
  } finally {
@@ -270,7 +286,8 @@ async function ensureConversationLoaded(conversationId) {
270
286
  }
271
287
  function resetConversation(shouldEmit = true) {
272
288
  clearConversation();
273
- void agentWake.wake();
289
+ agentWake.consume();
290
+ void agentWake.wake(void 0, selectedModel.value);
274
291
  activeTab.value = "chat";
275
292
  closeFilesPanel();
276
293
  closePreview();
@@ -453,13 +470,54 @@ async function handleSend(content, files, agentInputs) {
453
470
  const conversation = currentConversation.value;
454
471
  if (!conversation)
455
472
  throw new Error("Erro ao preparar conversa");
456
- if (!conversation.threadSessionId)
473
+ if (conversation.threadSessionId && resolvedFeatures.value.chatOutboundQueue && sessionReady.value === false) {
474
+ if (!agentSessionRewakeable.value)
475
+ return { defer: true };
476
+ const wake = await agentWake.wake(conversation.threadSessionId, selectedModel.value);
477
+ if (wake.status !== "ready")
478
+ throw new Error("N\xE3o foi poss\xEDvel acordar o agente. Tente novamente.");
479
+ return {};
480
+ }
481
+ if (!conversation.threadSessionId) {
457
482
  await agentWake.waitUntilSettled(signal);
483
+ if (agentWake.state.value === "idle")
484
+ await agentWake.wake(void 0, selectedModel.value);
485
+ }
458
486
  claimedWakeSessionId = claimableWakeSessionId();
487
+ if (!conversation.threadSessionId && !claimedWakeSessionId)
488
+ throw new Error("N\xE3o foi poss\xEDvel acordar o agente. Tente novamente.");
459
489
  return claimedWakeSessionId ? { wakeSessionId: claimedWakeSessionId } : {};
460
490
  });
461
491
  if (sent && claimedWakeSessionId)
462
492
  agentWake.consume();
493
+ if (!sent)
494
+ restoreQueuedSend({ content, files, agentInputs: resolvedAgentInputs });
495
+ }
496
+ watch(
497
+ [outboundOccupied, agentWake.state],
498
+ async ([occupied, wakeState]) => {
499
+ if (!occupied)
500
+ return;
501
+ if (wakeState === "ready") {
502
+ const wakeSessionId = claimableWakeSessionId();
503
+ const sent = await flushOutboundQueue(wakeSessionId);
504
+ if (sent && wakeSessionId)
505
+ agentWake.consume();
506
+ } else if (wakeState === "unavailable" && (agentSessionRewakeable.value || sessionReady.value === null)) {
507
+ failOutboundQueue("N\xE3o foi poss\xEDvel acordar o agente. Tente novamente.");
508
+ }
509
+ }
510
+ );
511
+ watch(
512
+ [outboundOccupied, agentSessionRewakeable],
513
+ ([occupied, rewakeable]) => {
514
+ const sessionId = currentConversation.value?.threadSessionId;
515
+ if (occupied && rewakeable && sessionId && agentWake.state.value !== "waking")
516
+ void agentWake.wake(sessionId, selectedModel.value);
517
+ }
518
+ );
519
+ async function handleCancel() {
520
+ await cancelConversation();
463
521
  }
464
522
  async function handleRetry(messageId) {
465
523
  if (!currentConversation.value?.threadSessionId)
@@ -751,7 +809,7 @@ async function handleDelete() {
751
809
  :files-count="filesCount"
752
810
  :generating-title="generatingTitle"
753
811
  :has-messages="hasMessages"
754
- :has-pending-message="hasPendingMessage"
812
+ :has-pending-message="composerLocked"
755
813
  :hide-settings="hideSettings"
756
814
  :hide-sidebar="hideSidebar"
757
815
  :is-editing="isEditing"
@@ -770,7 +828,7 @@ async function handleDelete() {
770
828
  :workspace-logo="workspaceLogo"
771
829
  :workspace-name="workspaceName"
772
830
  :workspace-settings="effectiveWorkspaceSettings"
773
- @cancel="cancelConversation"
831
+ @cancel="handleCancel"
774
832
  @cancel-editing="cancelEditing"
775
833
  @copy-link="handleShare"
776
834
  @debug="showDebugModal = true"
@@ -829,7 +887,7 @@ async function handleDelete() {
829
887
  <ChatMessageInput
830
888
  ref="messageInputRef"
831
889
  v-model:selected-model="selectedModel"
832
- :loading="hasPendingMessage"
890
+ :loading="composerLocked"
833
891
  :creator-name="currentUser?.name ?? void 0"
834
892
  :creator-email="
835
893
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -845,7 +903,7 @@ async function handleDelete() {
845
903
  :show-cancel-button="resolvedFeatures.showCancelButton"
846
904
  centered
847
905
  @send="handleSend"
848
- @cancel="cancelConversation"
906
+ @cancel="handleCancel"
849
907
  />
850
908
  </template>
851
909
  </TelaChatEmptyState>
@@ -862,7 +920,7 @@ async function handleDelete() {
862
920
  <ChatMessageInput
863
921
  ref="messageInputRef"
864
922
  v-model:selected-model="selectedModel"
865
- :loading="hasPendingMessage"
923
+ :loading="composerLocked"
866
924
  :creator-name="currentUser?.name ?? void 0"
867
925
  :creator-email="
868
926
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -878,7 +936,7 @@ async function handleDelete() {
878
936
  :show-cancel-button="resolvedFeatures.showCancelButton"
879
937
  has-disclaimer
880
938
  @send="handleSend"
881
- @cancel="cancelConversation"
939
+ @cancel="handleCancel"
882
940
  />
883
941
  </ChatMobileShell>
884
942
 
@@ -1026,7 +1084,7 @@ async function handleDelete() {
1026
1084
  <ChatMessageInput
1027
1085
  ref="messageInputRef"
1028
1086
  v-model:selected-model="selectedModel"
1029
- :loading="hasPendingMessage"
1087
+ :loading="composerLocked"
1030
1088
  :creator-name="currentUser?.name ?? void 0"
1031
1089
  :creator-email="
1032
1090
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -1042,7 +1100,7 @@ async function handleDelete() {
1042
1100
  :show-cancel-button="resolvedFeatures.showCancelButton"
1043
1101
  centered
1044
1102
  @send="handleSend"
1045
- @cancel="cancelConversation"
1103
+ @cancel="handleCancel"
1046
1104
  />
1047
1105
  </template>
1048
1106
  </TelaChatEmptyState>
@@ -1062,7 +1120,7 @@ async function handleDelete() {
1062
1120
  <ChatMessageInput
1063
1121
  ref="messageInputRef"
1064
1122
  v-model:selected-model="selectedModel"
1065
- :loading="hasPendingMessage"
1123
+ :loading="composerLocked"
1066
1124
  :creator-name="currentUser?.name ?? void 0"
1067
1125
  :creator-email="
1068
1126
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -1078,7 +1136,7 @@ async function handleDelete() {
1078
1136
  :show-cancel-button="resolvedFeatures.showCancelButton"
1079
1137
  has-disclaimer
1080
1138
  @send="handleSend"
1081
- @cancel="cancelConversation"
1139
+ @cancel="handleCancel"
1082
1140
  />
1083
1141
  </TelaChatBody>
1084
1142
 
@@ -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;
60
61
  rename: () => any;
61
62
  duplicate: () => any;
62
63
  export: () => any;
63
64
  copyLink: () => any;
64
65
  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;
81
82
  onRename?: (() => any) | undefined;
82
83
  onDuplicate?: (() => any) | undefined;
83
84
  onExport?: (() => any) | undefined;
84
85
  onCopyLink?: (() => any) | undefined;
85
86
  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;
@@ -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;
60
61
  rename: () => any;
61
62
  duplicate: () => any;
62
63
  export: () => any;
63
64
  copyLink: () => any;
64
65
  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;
81
82
  onRename?: (() => any) | undefined;
82
83
  onDuplicate?: (() => any) | undefined;
83
84
  onExport?: (() => any) | undefined;
84
85
  onCopyLink?: (() => any) | undefined;
85
86
  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;
@@ -5,6 +5,7 @@ export type AgentWakeSnapshot = {
5
5
  };
6
6
  export declare function pollAgentWake(options: {
7
7
  request: () => Promise<TelaAgentWakeResponse>;
8
+ requestStatus?: (sessionId: string) => Promise<TelaAgentWakeResponse>;
8
9
  onResponse?: (response: TelaAgentWakeResponse) => void;
9
10
  signal?: AbortSignal;
10
11
  pollIntervalMs?: number;
@@ -15,7 +16,7 @@ export declare function pollAgentWake(options: {
15
16
  export declare function useAgentWake(): {
16
17
  state: any;
17
18
  sessionId: any;
18
- wake: (requestedSessionId?: string) => Promise<TelaAgentWakeResponse>;
19
+ wake: (requestedSessionId?: string, model?: string | null) => Promise<TelaAgentWakeResponse>;
19
20
  skip: () => void;
20
21
  consume: () => void;
21
22
  waitUntilSettled: (signal?: AbortSignal) => Promise<AgentWakeSnapshot>;
@@ -21,21 +21,32 @@ export async function pollAgentWake(options) {
21
21
  else
22
22
  signal?.addEventListener("abort", onAbort, { once: true });
23
23
  }));
24
- try {
25
- while (!options.signal?.aborted && now() <= deadline) {
26
- const response = await options.request();
24
+ let unavailableAttempts = 0;
25
+ let wakeSessionId;
26
+ while (!options.signal?.aborted && now() <= deadline) {
27
+ try {
28
+ const response = wakeSessionId && options.requestStatus ? await options.requestStatus(wakeSessionId) : await options.request();
29
+ wakeSessionId = response.sessionId ?? wakeSessionId;
27
30
  options.onResponse?.(response);
31
+ if (response.status === "unavailable" && unavailableAttempts < 2) {
32
+ unavailableAttempts += 1;
33
+ await wait(Math.min(500 * 2 ** (unavailableAttempts - 1), Math.max(0, deadline - now())), options.signal);
34
+ continue;
35
+ }
28
36
  if (response.status !== "waking")
29
37
  return response;
30
38
  const remaining = deadline - now();
31
39
  if (remaining <= 0)
32
40
  break;
33
41
  await wait(Math.min(options.pollIntervalMs ?? WAKE_POLL_INTERVAL_MS, remaining), options.signal);
42
+ } catch (error) {
43
+ if (error instanceof DOMException && error.name === "AbortError")
44
+ throw error;
45
+ if (unavailableAttempts >= 2)
46
+ return { status: "unavailable" };
47
+ unavailableAttempts += 1;
48
+ await wait(Math.min(500 * 2 ** (unavailableAttempts - 1), Math.max(0, deadline - now())), options.signal);
34
49
  }
35
- } catch (error) {
36
- if (error instanceof DOMException && error.name === "AbortError")
37
- throw error;
38
- return { status: "unavailable" };
39
50
  }
40
51
  return { status: "unavailable" };
41
52
  }
@@ -67,7 +78,7 @@ export function useAgentWake() {
67
78
  activeWake = null;
68
79
  setSnapshot({ state: "idle" });
69
80
  }
70
- async function wake(requestedSessionId) {
81
+ async function wake(requestedSessionId, model) {
71
82
  generation++;
72
83
  const currentGeneration = generation;
73
84
  abortController?.abort();
@@ -93,10 +104,18 @@ export function useAgentWake() {
93
104
  method: "POST",
94
105
  signal: controller.signal,
95
106
  body: {
96
- ...wakeSessionId ? { sessionId: wakeSessionId } : {}
107
+ ...wakeSessionId ? { sessionId: wakeSessionId } : {},
108
+ ...model ? { model } : {}
97
109
  }
98
110
  })
99
111
  ),
112
+ requestStatus: (sessionId2) => $fetch(
113
+ chatApi.path(`/agent/wake/${encodeURIComponent(sessionId2)}/status`),
114
+ chatApi.withChatHeaders({
115
+ method: "GET",
116
+ signal: controller.signal
117
+ })
118
+ ),
100
119
  onResponse: (response) => {
101
120
  if (currentGeneration !== generation)
102
121
  return;
@@ -8,11 +8,13 @@ export type ChatFeatureConfig = {
8
8
  showCancelButton?: boolean;
9
9
  /** Time to first token shown in assistant message metadata. Defaults to true. */
10
10
  showTtft?: boolean;
11
+ chatOutboundQueue?: boolean;
11
12
  };
12
13
  export type ResolvedChatFeatureConfig = {
13
14
  showUsageTab: boolean;
14
15
  showDebugOption: boolean;
15
16
  showCancelButton: boolean;
16
17
  showTtft: boolean;
18
+ chatOutboundQueue: boolean;
17
19
  };
18
20
  export declare function resolveChatFeatures(features?: Partial<ChatFeatureConfig>): ResolvedChatFeatureConfig;
@@ -3,6 +3,7 @@ export function resolveChatFeatures(features) {
3
3
  showUsageTab: features?.showUsageTab ?? false,
4
4
  showDebugOption: features?.showDebugOption ?? false,
5
5
  showCancelButton: features?.showCancelButton ?? false,
6
- showTtft: features?.showTtft ?? true
6
+ showTtft: features?.showTtft ?? true,
7
+ chatOutboundQueue: features?.chatOutboundQueue ?? false
7
8
  };
8
9
  }
@@ -111,13 +111,13 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
111
111
  <template>
112
112
  <TelaChatMessage
113
113
  :from="message.role === 'user' ? 'user' : 'assistant'"
114
- :align="isOwnMessage ? 'end' : 'start'"
114
+ :align="message.role === 'user' ? 'end' : 'start'"
115
115
  >
116
116
  <template v-if="message.role === 'user'">
117
117
  <div
118
118
  max-w-prose
119
119
  flex="~ col"
120
- :class="isOwnMessage ? 'items-end' : 'items-start'"
120
+ items-end
121
121
  >
122
122
  <div
123
123
  v-if="!isOwnMessage"
@@ -138,7 +138,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
138
138
 
139
139
  <TelaChatMessageContent
140
140
  variant="contained"
141
- :align="isOwnMessage ? 'end' : 'start'"
141
+ align="end"
142
142
  :class="isPending && 'op-80'"
143
143
  >
144
144
  <div v-if="hasFiles" flex="~ col" gap-4px mb-8px>
@@ -171,7 +171,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
171
171
  mt-8px
172
172
  body-10-regular
173
173
  text-tertiary
174
- :class="isOwnMessage ? 'self-end' : 'self-start'"
174
+ self-end
175
175
  >
176
176
  {{ formatTime(message.createdAt) }}
177
177
  </div>
@@ -1,8 +1,8 @@
1
1
  import type { DeepReadonly } from 'vue';
2
2
  import type { Message } from '../../types/schemas/chat/conversations.js';
3
- import type { ConversationFile } from '../../types/schemas/chat/files.js';
3
+ import type { ConversationFile, FileUploadInput } from '../../types/schemas/chat/files.js';
4
4
  import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../types/schemas/chat/workspace-settings.js';
5
- import type { TelaAgentContextFile, TelaAgentInputVariable, TelaAgentSkill } from '../../types/schemas/integrations/tela-agent-api.js';
5
+ import type { TelaAgentContextFile, TelaAgentExecutionInput, TelaAgentInputVariable, TelaAgentSkill } from '../../types/schemas/integrations/tela-agent-api.js';
6
6
  type __VLS_Props = {
7
7
  disabled?: boolean;
8
8
  loading?: boolean;
@@ -21,6 +21,11 @@ type __VLS_Props = {
21
21
  showCancelButton?: boolean;
22
22
  };
23
23
  declare function addFiles(files: File[], inputName?: string): void;
24
+ declare function restoreDraft(draft: {
25
+ content: string;
26
+ files?: FileUploadInput[];
27
+ agentInputs?: TelaAgentExecutionInput[];
28
+ }): void;
24
29
  declare function setDraft(text: string): void;
25
30
  type __VLS_ModelProps = {
26
31
  'selectedModel'?: string | null;
@@ -28,6 +33,7 @@ type __VLS_ModelProps = {
28
33
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
29
34
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
30
35
  addFiles: typeof addFiles;
36
+ restoreDraft: typeof restoreDraft;
31
37
  setDraft: typeof setDraft;
32
38
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
33
39
  "update:selectedModel": (value: string | null) => any;
@@ -196,6 +196,15 @@ function handleFileSelect(event) {
196
196
  function createPendingFile(file, inputName) {
197
197
  return {
198
198
  id: `${file.name}-${file.lastModified}-${file.size}-${Math.random().toString(36).slice(2)}`,
199
+ source: "browser",
200
+ file,
201
+ inputName
202
+ };
203
+ }
204
+ function createUploadedPendingFile(file, inputName) {
205
+ return {
206
+ id: `${file.vaultReference ?? file.url}-${Math.random().toString(36).slice(2)}`,
207
+ source: "uploaded",
199
208
  file,
200
209
  inputName
201
210
  };
@@ -231,6 +240,22 @@ function handleVariableDrop(event, inputName) {
231
240
  function getVaultRef(file) {
232
241
  return file.vaultReference?.trim() || (file.url.startsWith("vault://") ? file.url : null);
233
242
  }
243
+ function getPendingFileMimeType(file) {
244
+ return file.source === "browser" ? file.file.type : file.file.mimeType;
245
+ }
246
+ async function resolvePendingFiles(files) {
247
+ const browserFiles = files.filter((file) => file.source === "browser");
248
+ const uploadedBrowserFiles = browserFiles.length > 0 ? await uploadFiles(browserFiles.map((file) => file.file)) : [];
249
+ let uploadedIndex = 0;
250
+ return files.map((file) => {
251
+ if (file.source === "uploaded")
252
+ return file.file;
253
+ const uploaded = uploadedBrowserFiles[uploadedIndex++];
254
+ if (!uploaded)
255
+ throw new Error("Falha ao preparar anexo");
256
+ return uploaded;
257
+ });
258
+ }
234
259
  function buildAgentInputs(files, queuedFiles) {
235
260
  if (!hasAgentInputs.value)
236
261
  return [];
@@ -266,7 +291,7 @@ async function handleSend() {
266
291
  const queuedFiles = [...pendingFiles.value];
267
292
  if (queuedFiles.length > 0) {
268
293
  try {
269
- files = await uploadFiles(queuedFiles.map((item) => item.file));
294
+ files = await resolvePendingFiles(queuedFiles);
270
295
  } catch {
271
296
  return;
272
297
  }
@@ -470,6 +495,29 @@ function addFiles(files, inputName) {
470
495
  });
471
496
  queueFiles(validFiles, inputName);
472
497
  }
498
+ function restoreDraft(draft) {
499
+ setDraft(draft.content);
500
+ const textInputs = (draft.agentInputs ?? []).filter((input) => input.type === "text");
501
+ agentInputTexts.value = {
502
+ ...agentInputTexts.value,
503
+ ...Object.fromEntries(textInputs.map((input) => [input.name, input.content]))
504
+ };
505
+ const availableSlots = MAX_FILES - pendingFiles.value.length;
506
+ if (availableSlots <= 0 || !draft.files?.length)
507
+ return;
508
+ const fileInputs = (draft.agentInputs ?? []).filter((input) => input.type === "file");
509
+ const claimedInputs = /* @__PURE__ */ new Set();
510
+ const restoredFiles = draft.files.slice(0, availableSlots).map((file) => {
511
+ const vaultRef = getVaultRef(file);
512
+ const inputIndex = fileInputs.findIndex(
513
+ (input, index) => !claimedInputs.has(index) && input.vaultRef === vaultRef
514
+ );
515
+ if (inputIndex >= 0)
516
+ claimedInputs.add(inputIndex);
517
+ return createUploadedPendingFile(file, inputIndex >= 0 ? fileInputs[inputIndex]?.name : void 0);
518
+ });
519
+ pendingFiles.value = [...pendingFiles.value, ...restoredFiles];
520
+ }
473
521
  function setDraft(text) {
474
522
  content.value = text;
475
523
  if (editorRef.value) {
@@ -488,6 +536,7 @@ function setDraft(text) {
488
536
  }
489
537
  defineExpose({
490
538
  addFiles,
539
+ restoreDraft,
491
540
  setDraft
492
541
  });
493
542
  </script>
@@ -641,7 +690,7 @@ defineExpose({
641
690
  v-for="pendingFile in pendingFiles"
642
691
  :key="pendingFile.id"
643
692
  :name="pendingFile.file.name"
644
- :icon="getFileIcon(pendingFile.file.type)"
693
+ :icon="getFileIcon(getPendingFileMimeType(pendingFile))"
645
694
  :meta="formatFileSize(pendingFile.file.size)"
646
695
  remove-label="Remover anexo"
647
696
  @remove="removeFile(pendingFile.id)"
@@ -1,8 +1,8 @@
1
1
  import type { DeepReadonly } from 'vue';
2
2
  import type { Message } from '../../types/schemas/chat/conversations.js';
3
- import type { ConversationFile } from '../../types/schemas/chat/files.js';
3
+ import type { ConversationFile, FileUploadInput } from '../../types/schemas/chat/files.js';
4
4
  import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../types/schemas/chat/workspace-settings.js';
5
- import type { TelaAgentContextFile, TelaAgentInputVariable, TelaAgentSkill } from '../../types/schemas/integrations/tela-agent-api.js';
5
+ import type { TelaAgentContextFile, TelaAgentExecutionInput, TelaAgentInputVariable, TelaAgentSkill } from '../../types/schemas/integrations/tela-agent-api.js';
6
6
  type __VLS_Props = {
7
7
  disabled?: boolean;
8
8
  loading?: boolean;
@@ -21,6 +21,11 @@ type __VLS_Props = {
21
21
  showCancelButton?: boolean;
22
22
  };
23
23
  declare function addFiles(files: File[], inputName?: string): void;
24
+ declare function restoreDraft(draft: {
25
+ content: string;
26
+ files?: FileUploadInput[];
27
+ agentInputs?: TelaAgentExecutionInput[];
28
+ }): void;
24
29
  declare function setDraft(text: string): void;
25
30
  type __VLS_ModelProps = {
26
31
  'selectedModel'?: string | null;
@@ -28,6 +33,7 @@ type __VLS_ModelProps = {
28
33
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
29
34
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
30
35
  addFiles: typeof addFiles;
36
+ restoreDraft: typeof restoreDraft;
31
37
  setDraft: typeof setDraft;
32
38
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
33
39
  "update:selectedModel": (value: string | null) => any;
@@ -18,9 +18,6 @@ const skeletonMessages = [
18
18
  function scrollToBottom() {
19
19
  conversationRef.value?.scrollToBottom();
20
20
  }
21
- const hasPendingMessage = computed(
22
- () => props.messages.some((message) => message.status === "pending")
23
- );
24
21
  watch(
25
22
  () => [props.messages.length, props.messages.at(-1)?.content.length ?? 0],
26
23
  scrollToBottom
@@ -36,7 +33,7 @@ onMounted(scrollToBottom);
36
33
  'pt-72px pb-24px px-16px': variant === 'mobile'
37
34
  }"
38
35
  >
39
- <TelaChatConversationContent class="max-w-700px!">
36
+ <TelaChatConversationContent class="max-w-700px! min-h-full">
40
37
  <template v-if="loading">
41
38
  <div
42
39
  v-for="message in skeletonMessages"
@@ -59,7 +56,8 @@ onMounted(scrollToBottom);
59
56
  @retry="emit('retry', $event)"
60
57
  />
61
58
  <p
62
- v-if="messages.length > 0 && !hasPendingMessage"
59
+ v-if="messages.length > 0"
60
+ mt-auto mb--6px
63
61
  text-center body-11-regular text-tertiary
64
62
  >
65
63
  As respostas podem conter erros. Verifique documentos originais.
@@ -24,7 +24,7 @@ defineExpose({
24
24
  </script>
25
25
 
26
26
  <template>
27
- <div v-if="groupedReasoning.length > 0" flex="~ col" w-full p-12px>
27
+ <div v-if="groupedReasoning.length > 0" flex="~ col" w-full>
28
28
  <ChatReasoningMessage
29
29
  v-for="(step, index) in groupedReasoning"
30
30
  :key="getStepKey(step, index)"