@meistrari/chat-nuxt 4.5.0-rc.0 → 4.5.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,7 +88,7 @@ Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload
88
88
  | `initialConversationId` | `string \| null` | `null` | Conversation to open once on mount when `conversationId` is uncontrolled |
89
89
  | `conversationScope` | `string \| null` | `null` | Isolate conversation history by technical scope within the current workspace and runtime. Scoped embeds only see same-scope conversations; values are trimmed, blank becomes `null`, and the server accepts 1-200 chars from `A-Z`, `a-z`, `0-9`, `.`, `_`, `:`, `/`, `-` |
90
90
  | `defaultConversationCreatorFilter` | `'all' \| 'mine'` | `'all'` | Initial sidebar creator filter: every conversation in scope, or only the current user's |
91
- | `availableModels` | `AvailableChatModels` | Workspace catalog | Optional non-empty ordered subset of models served for the active workspace. Unknown ids fail at runtime; a restored unavailable selection stays visible and blocked until the user chooses an available model |
91
+ | `availableModels` | `AvailableChatModels` | Workspace catalog | Optional ordered subset of models for the active workspace. With the served catalog (lightweight harness), ids the catalog does not offer as available are dropped, and the full catalog is used when none remain; otherwise unknown ids fail at runtime. A restored unavailable selection stays visible and blocked until the user chooses an available model |
92
92
  | `defaultModel` | `ChatModel` | Package default | Initial model for untouched drafts. Explicit selections and existing conversations take precedence; `availableModels` still limits the selection |
93
93
  | `defaultReasoningEffort` | `ChatReasoningEffort` | Automatic (`null`) | Initial reasoning effort for untouched drafts. Explicit user choices (including automatic) take precedence |
94
94
  | `conversationCreatorFilter` | `'all' \| 'mine'` | — | Controlled sidebar creator filter; use with `v-model:conversation-creator-filter` when the host changes it at runtime |
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.5.0-rc.0",
4
+ "version": "4.5.0-rc.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -19,29 +19,29 @@ type __VLS_Props = {
19
19
  user?: EmbedUser | null;
20
20
  features?: Partial<ChatFeatureConfig>;
21
21
  };
22
- declare var __VLS_6: {}, __VLS_80: {}, __VLS_93: {
22
+ declare var __VLS_14: {}, __VLS_88: {}, __VLS_101: {
23
23
  message: useChatAuth;
24
- }, __VLS_105: {}, __VLS_133: {}, __VLS_142: {}, __VLS_206: {}, __VLS_225: {
24
+ }, __VLS_113: {}, __VLS_141: {}, __VLS_150: {}, __VLS_214: {}, __VLS_233: {
25
25
  message: useChatAuth;
26
- }, __VLS_237: {};
26
+ }, __VLS_245: {};
27
27
  type __VLS_Slots = {} & {
28
- 'sidebar-bottom'?: (props: typeof __VLS_6) => any;
28
+ 'sidebar-bottom'?: (props: typeof __VLS_14) => any;
29
29
  } & {
30
- 'composer-bottom'?: (props: typeof __VLS_80) => any;
30
+ 'composer-bottom'?: (props: typeof __VLS_88) => any;
31
31
  } & {
32
- 'failed-message-actions'?: (props: typeof __VLS_93) => any;
32
+ 'failed-message-actions'?: (props: typeof __VLS_101) => any;
33
33
  } & {
34
- 'composer-bottom'?: (props: typeof __VLS_105) => any;
34
+ 'composer-bottom'?: (props: typeof __VLS_113) => any;
35
35
  } & {
36
- 'topbar-leading'?: (props: typeof __VLS_133) => any;
36
+ 'topbar-leading'?: (props: typeof __VLS_141) => any;
37
37
  } & {
38
- 'topbar-leading'?: (props: typeof __VLS_142) => any;
38
+ 'topbar-leading'?: (props: typeof __VLS_150) => any;
39
39
  } & {
40
- 'composer-bottom'?: (props: typeof __VLS_206) => any;
40
+ 'composer-bottom'?: (props: typeof __VLS_214) => any;
41
41
  } & {
42
- 'failed-message-actions'?: (props: typeof __VLS_225) => any;
42
+ 'failed-message-actions'?: (props: typeof __VLS_233) => any;
43
43
  } & {
44
- 'composer-bottom'?: (props: typeof __VLS_237) => any;
44
+ 'composer-bottom'?: (props: typeof __VLS_245) => any;
45
45
  };
46
46
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
47
47
  "update:conversationId": (value: string | null) => any;
@@ -203,6 +203,7 @@ const {
203
203
  generatingTitle,
204
204
  animatedTitle,
205
205
  composerLocked,
206
+ isPolling,
206
207
  outboundOccupied,
207
208
  sessionReady,
208
209
  agentSessionRewakeable,
@@ -221,6 +222,7 @@ const {
221
222
  retryMessage,
222
223
  cancelConversation,
223
224
  startPolling,
225
+ reconnect,
224
226
  clearConversation,
225
227
  error: chatError
226
228
  } = createChat({
@@ -906,6 +908,15 @@ async function handleDelete() {
906
908
  rounded-8px bg-error text-error p-12px body-14-regular
907
909
  >
908
910
  {{ chatError }}
911
+ <TelaButton
912
+ v-if="composerLocked && !isPolling"
913
+ variant="ghost-danger"
914
+ size="sm"
915
+ icon="i-ph-arrow-clockwise"
916
+ @click="reconnect"
917
+ >
918
+ Reconectar
919
+ </TelaButton>
909
920
  </div>
910
921
  <!-- Sidebar -->
911
922
  <div
@@ -19,29 +19,29 @@ type __VLS_Props = {
19
19
  user?: EmbedUser | null;
20
20
  features?: Partial<ChatFeatureConfig>;
21
21
  };
22
- declare var __VLS_6: {}, __VLS_80: {}, __VLS_93: {
22
+ declare var __VLS_14: {}, __VLS_88: {}, __VLS_101: {
23
23
  message: useChatAuth;
24
- }, __VLS_105: {}, __VLS_133: {}, __VLS_142: {}, __VLS_206: {}, __VLS_225: {
24
+ }, __VLS_113: {}, __VLS_141: {}, __VLS_150: {}, __VLS_214: {}, __VLS_233: {
25
25
  message: useChatAuth;
26
- }, __VLS_237: {};
26
+ }, __VLS_245: {};
27
27
  type __VLS_Slots = {} & {
28
- 'sidebar-bottom'?: (props: typeof __VLS_6) => any;
28
+ 'sidebar-bottom'?: (props: typeof __VLS_14) => any;
29
29
  } & {
30
- 'composer-bottom'?: (props: typeof __VLS_80) => any;
30
+ 'composer-bottom'?: (props: typeof __VLS_88) => any;
31
31
  } & {
32
- 'failed-message-actions'?: (props: typeof __VLS_93) => any;
32
+ 'failed-message-actions'?: (props: typeof __VLS_101) => any;
33
33
  } & {
34
- 'composer-bottom'?: (props: typeof __VLS_105) => any;
34
+ 'composer-bottom'?: (props: typeof __VLS_113) => any;
35
35
  } & {
36
- 'topbar-leading'?: (props: typeof __VLS_133) => any;
36
+ 'topbar-leading'?: (props: typeof __VLS_141) => any;
37
37
  } & {
38
- 'topbar-leading'?: (props: typeof __VLS_142) => any;
38
+ 'topbar-leading'?: (props: typeof __VLS_150) => any;
39
39
  } & {
40
- 'composer-bottom'?: (props: typeof __VLS_206) => any;
40
+ 'composer-bottom'?: (props: typeof __VLS_214) => any;
41
41
  } & {
42
- 'failed-message-actions'?: (props: typeof __VLS_225) => any;
42
+ 'failed-message-actions'?: (props: typeof __VLS_233) => any;
43
43
  } & {
44
- 'composer-bottom'?: (props: typeof __VLS_237) => any;
44
+ 'composer-bottom'?: (props: typeof __VLS_245) => any;
45
45
  };
46
46
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
47
47
  "update:conversationId": (value: string | null) => any;
@@ -14,7 +14,7 @@ export function useChatModelCatalog() {
14
14
  const catalogKey = computed(
15
15
  () => chatApi.withChatHeaders().headers?.["x-chat-workspace-id"] ?? "__default__"
16
16
  );
17
- const models = computed(() => catalogs.value[catalogKey.value] ?? []);
17
+ const models = computed(() => (catalogs.value[catalogKey.value] ?? []).filter((model) => model.status === "available"));
18
18
  const isLoaded = computed(() => loadedCatalogs.value[catalogKey.value] ?? false);
19
19
  const isLoading = computed(() => loadingCatalogs.value[catalogKey.value] ?? false);
20
20
  const defaultModel = computed(
@@ -35,6 +35,7 @@ export declare function useChat(options?: Parameters<typeof useSharedChat>[0]):
35
35
  retryMessage: (messageId: string, prepare?: () => Promise<void>) => Promise<boolean>;
36
36
  cancelConversation: () => Promise<boolean>;
37
37
  startPolling: () => void;
38
+ reconnect: () => void;
38
39
  stopPolling: () => void;
39
40
  clearConversation: () => void;
40
41
  clearConversationThread: () => Promise<boolean>;
@@ -57,6 +57,7 @@ export declare function useChat(options?: {
57
57
  retryMessage: (messageId: string, prepare?: () => Promise<void>) => Promise<boolean>;
58
58
  cancelConversation: () => Promise<boolean>;
59
59
  startPolling: () => void;
60
+ reconnect: () => void;
60
61
  stopPolling: () => void;
61
62
  clearConversation: () => void;
62
63
  clearConversationThread: () => Promise<boolean>;
@@ -28,6 +28,12 @@ import { usePolling } from "../../shared/composables/polling.js";
28
28
  import { ChatMessageStatus, terminalMessageTransitions, useChatMessageTerminal } from "./chat-message-terminal.js";
29
29
  import { useDirectStream } from "./use-direct-stream.js";
30
30
  import { useOutboundQueue } from "./outbound-queue.js";
31
+ const POLL_OUTAGE_NOTICE_FAILURES = 5;
32
+ const POLL_OUTAGE_BUDGET_MS = 6e4;
33
+ const POLL_INTERVAL_MS = 500;
34
+ const POLL_MAX_BACKOFF_MS = 5e3;
35
+ const CHAT_REQUEST_TIMEOUT_MS = 15e3;
36
+ const CHAT_UNAVAILABLE_TEXT = "Chat temporariamente indispon\xEDvel. Tente novamente.";
31
37
  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.";
32
38
  export function resolveTelaAgentInputs(messageInputs, configuredInputs) {
33
39
  const inputs = messageInputs ?? configuredInputs;
@@ -241,6 +247,9 @@ export function useChat(options = {}, adapter = {}) {
241
247
  }
242
248
  }
243
249
  let pollGeneration = 0;
250
+ let consecutivePollFailures = 0;
251
+ let pollOutageStartedAt = null;
252
+ const pollInterval = ref(POLL_INTERVAL_MS);
244
253
  const pollMessages = async () => {
245
254
  if (!currentConversation.value)
246
255
  return;
@@ -251,14 +260,17 @@ export function useChat(options = {}, adapter = {}) {
251
260
  try {
252
261
  const runtime = chatApi.conversationV2Enabled ? await $fetch(
253
262
  chatApi.runtimePath(`/conversations/${conversationId}/runtime`),
254
- chatApi.withChatHeaders()
263
+ chatApi.withChatHeaders({ timeout: CHAT_REQUEST_TIMEOUT_MS, retry: 0 })
255
264
  ) : null;
256
265
  const response = await $fetch(
257
266
  chatApi.path(`/conversations/${conversationId}/messages`),
258
- chatApi.withChatHeaders()
267
+ chatApi.withChatHeaders({ timeout: CHAT_REQUEST_TIMEOUT_MS, retry: 0 })
259
268
  );
260
269
  if (generation !== pollGeneration || currentConversation.value?.id !== conversationId)
261
270
  return;
271
+ resetPollOutage();
272
+ if (error.value === CHAT_UNAVAILABLE_TEXT)
273
+ error.value = null;
262
274
  if (runtime)
263
275
  runtimeBlocked.value = runtime.conversation.runtimeState === "blocked";
264
276
  if (response.agentSession) {
@@ -332,16 +344,28 @@ export function useChat(options = {}, adapter = {}) {
332
344
  Sentry.captureException(err);
333
345
  if (generation !== pollGeneration)
334
346
  return;
335
- const errorRecord = err && typeof err === "object" ? err : null;
336
- const statusCode = typeof errorRecord?.statusCode === "number" ? errorRecord.statusCode : typeof errorRecord?.status === "number" ? errorRecord.status : typeof errorRecord?.response?.status === "number" ? errorRecord.response.status : null;
337
- if (statusCode && statusCode >= 400 && statusCode < 500 && statusCode !== 408 && statusCode !== 429) {
347
+ const statusCode = getChatApiErrorStatus(err);
348
+ const terminalError = statusCode === 501 || statusCode !== null && statusCode >= 400 && statusCode < 500 && statusCode !== 408 && statusCode !== 429;
349
+ consecutivePollFailures++;
350
+ pollOutageStartedAt ??= Date.now();
351
+ pollInterval.value = Math.min(POLL_INTERVAL_MS * 2 ** consecutivePollFailures, POLL_MAX_BACKOFF_MS);
352
+ if (!terminalError && consecutivePollFailures >= POLL_OUTAGE_NOTICE_FAILURES)
353
+ error.value = CHAT_UNAVAILABLE_TEXT;
354
+ const outageExhausted = Date.now() - pollOutageStartedAt >= POLL_OUTAGE_BUDGET_MS;
355
+ if (terminalError || outageExhausted) {
356
+ const message = statusCode === 501 || !terminalError ? CHAT_UNAVAILABLE_TEXT : getChatApiErrorMessage(err) ?? "Erro ao atualizar mensagens";
357
+ if (!terminalError || statusCode === 501 || outbound.occupied.value) {
358
+ error.value = message;
359
+ stopPolling();
360
+ return;
361
+ }
338
362
  const previousMessages = messages.value;
339
- const failedMessages = markPendingAssistantMessagesFailed(previousMessages).map((message) => {
340
- if (message.role !== "assistant" || message.status !== ChatMessageStatus.Completed || !pendingStreamReconciliationIds.value.has(message.id)) {
341
- return message;
363
+ const failedMessages = markPendingAssistantMessagesFailed(previousMessages).map((message2) => {
364
+ if (message2.role !== "assistant" || message2.status !== ChatMessageStatus.Completed || !pendingStreamReconciliationIds.value.has(message2.id)) {
365
+ return message2;
342
366
  }
343
367
  return {
344
- ...message,
368
+ ...message2,
345
369
  content: GENERATION_FAILED_TEXT,
346
370
  status: ChatMessageStatus.Failed
347
371
  };
@@ -352,20 +376,30 @@ export function useChat(options = {}, adapter = {}) {
352
376
  messages.value = failedMessages;
353
377
  for (const terminalMessage of terminalMessages)
354
378
  chatMessageTerminal.emit(terminalMessage);
355
- error.value = getChatApiErrorMessage(err) ?? "Erro ao atualizar mensagens";
379
+ error.value = message;
356
380
  stopPolling();
357
381
  }
358
382
  }
359
383
  };
360
384
  const { isPolling, start: beginPolling, stop: endPolling } = usePolling({
361
385
  callback: pollMessages,
362
- interval: 500,
386
+ interval: pollInterval,
363
387
  stopCondition
364
388
  });
389
+ function resetPollOutage() {
390
+ consecutivePollFailures = 0;
391
+ pollOutageStartedAt = null;
392
+ pollInterval.value = POLL_INTERVAL_MS;
393
+ }
365
394
  function startPolling() {
395
+ resetPollOutage();
366
396
  pollGeneration++;
367
397
  beginPolling();
368
398
  }
399
+ function reconnect() {
400
+ error.value = null;
401
+ startPolling();
402
+ }
369
403
  function stopPolling() {
370
404
  pollGeneration++;
371
405
  endPolling();
@@ -614,7 +648,7 @@ export function useChat(options = {}, adapter = {}) {
614
648
  v2AdmissionRequested = true;
615
649
  const accepted = await $fetch(
616
650
  chatApi.runtimePath(`/conversations/${conversationId2}/messages`),
617
- chatApi.withChatHeaders({ method: "POST", body: v2Body, retry: 1 })
651
+ chatApi.withChatHeaders({ method: "POST", body: v2Body, retry: 1, timeout: CHAT_REQUEST_TIMEOUT_MS })
618
652
  );
619
653
  response = accepted;
620
654
  if (currentConversation.value?.id !== conversationId2)
@@ -661,7 +695,7 @@ export function useChat(options = {}, adapter = {}) {
661
695
  } catch (err) {
662
696
  const code = getChatApiErrorCode(err);
663
697
  const status = getChatApiErrorStatus(err);
664
- if (v2AdmissionRequested && !controller.signal.aborted && (status === null || status === 408 || status === 429 || status >= 500)) {
698
+ if (v2AdmissionRequested && !controller.signal.aborted && (status === null || status === 408 || status === 429 || status >= 500 && status !== 501)) {
665
699
  const attempts = (queuedSend?.attempts ?? 0) + 1;
666
700
  const queuedAt = queuedSend?.queuedAt ?? Date.now();
667
701
  if (outboundQueueEnabled.value) {
@@ -1029,6 +1063,7 @@ export function useChat(options = {}, adapter = {}) {
1029
1063
  retryMessage,
1030
1064
  cancelConversation,
1031
1065
  startPolling,
1066
+ reconnect,
1032
1067
  stopPolling,
1033
1068
  clearConversation,
1034
1069
  clearConversationThread,
@@ -7,13 +7,16 @@ export function resolveAvailableChatModels(availableModels, catalogModels) {
7
7
  throw new Error("Agent API model catalog must contain at least one model");
8
8
  if (availableModels === void 0)
9
9
  return [...new Set(catalog)];
10
+ if (catalogModels) {
11
+ const offeredModels = [...new Set(availableModels.filter((model) => catalog.includes(model)))];
12
+ return offeredModels.length ? offeredModels : [...new Set(catalog)];
13
+ }
10
14
  if (availableModels.length === 0)
11
15
  throw new Error("availableModels must contain at least one model");
12
16
  const uniqueModels = [];
13
17
  for (const model of availableModels) {
14
- if (!catalog.includes(model)) {
15
- throw new Error(catalogModels ? `${model} is not available in this workspace` : `${model} is not a supported chat model`);
16
- }
18
+ if (!catalog.includes(model))
19
+ throw new Error(`${model} is not a supported chat model`);
17
20
  if (!uniqueModels.includes(model))
18
21
  uniqueModels.push(model);
19
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "4.5.0-rc.0",
3
+ "version": "4.5.0-rc.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {