@meistrari/chat-nuxt 3.14.2-rc.2 → 3.15.0-rc.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 CHANGED
@@ -55,6 +55,7 @@ Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload
55
55
  | `initialConversationId` | `string \| null` | `null` | Conversation to open once on mount when `conversationId` is uncontrolled |
56
56
  | `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`, `.`, `_`, `:`, `/`, `-` |
57
57
  | `defaultConversationCreatorFilter` | `'all' \| 'mine'` | `'all'` | Initial sidebar creator filter: every conversation in scope, or only the current user's |
58
+ | `availableModels` | `AvailableChatModels` | Full catalog | Non-empty ordered list of models this embed may display and execute. A missing or invalid restored selection falls back to the first entry; an empty list fails fast |
58
59
  | `conversationCreatorFilter` | `'all' \| 'mine'` | — | Controlled sidebar creator filter; use with `v-model:conversation-creator-filter` when the host changes it at runtime |
59
60
  | `hideConversationCreatorFilter` | `boolean` | `false` | Hide the creator selector without changing the active filter |
60
61
  | `telaAgentId` | `string` | — | Use Tela agent mode for this embed |
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ChatMessageStatus } from '../dist/runtime/messages/composables/chat-message-terminal.js';
2
- export { ChatActionPayload, ChatActor, ChatEmbedSharedProps, ChatEmbedVariant, ChatEmbedVariantProps, ChatFeatureConfig, ChatMessageTerminalPayload, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, MessageFeedbackConfig, MessageFeedbackPayload, MessageFeedbackRating, MessageFeedbackSubmitHandler, MessageFeedbackSubmitResult, MessageOverviewResponse, TelaAgentChatEmbedProps, TelaAgentExecutionInput } from '../dist/runtime/types/embed.js';
2
+ export { AvailableChatModels, ChatActionPayload, ChatActor, ChatEmbedSharedProps, ChatEmbedVariant, ChatEmbedVariantProps, ChatFeatureConfig, ChatMessageTerminalPayload, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, MessageFeedbackConfig, MessageFeedbackPayload, MessageFeedbackRating, MessageFeedbackSubmitHandler, MessageFeedbackSubmitResult, MessageOverviewResponse, TelaAgentChatEmbedProps, TelaAgentExecutionInput } from '../dist/runtime/types/embed.js';
3
3
 
4
4
  type ChatNuxtConfigValidationMode = false | 'warn' | 'error';
5
5
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "3.14.2-rc.2",
4
+ "version": "3.15.0-rc.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -56,6 +56,7 @@ function createOptimisticConversationId() {
56
56
  return `optimistic-${Date.now()}-${Math.random().toString(36).slice(2)}`;
57
57
  }
58
58
  export function useConversations() {
59
+ const nuxtApp = useNuxtApp();
59
60
  const chatApi = useChatApi();
60
61
  const embedConfig = useEmbedConfig();
61
62
  const { user: authUser, activeOrganization } = useChatAuth();
@@ -69,6 +70,7 @@ export function useConversations() {
69
70
  const metadataBuckets = useState("conversations-metadata", () => ({}));
70
71
  const metadataLoadingBuckets = useState("conversations-metadata-loading", () => ({}));
71
72
  const metadataFetchedBuckets = useState("conversations-metadata-fetched", () => ({}));
73
+ const modelUpdateTails = nuxtApp._chatConversationModelUpdateTails ??= /* @__PURE__ */ new Map();
72
74
  const providedConversationCreatorFilter = useProvidedConversationCreatorFilterState();
73
75
  const conversationCreatorFilter = providedConversationCreatorFilter ?? useState(
74
76
  "conversation-creator-filter",
@@ -462,31 +464,41 @@ export function useConversations() {
462
464
  return false;
463
465
  }
464
466
  };
465
- const updateConversationModel = async (id, model) => {
466
- error.value = null;
467
- const requestRuntimeScope = runtimeScope.value;
468
- const affectedScopes = activeAndLoadedRuntimeConversationScopes(requestRuntimeScope);
469
- const conversationSnapshot = snapshotConversationBuckets(affectedScopes);
470
- const conversation = findConversationInLoadedBuckets(id, requestRuntimeScope);
471
- if (conversation)
472
- updateConversationInLoadedBuckets({ ...conversation, model, updatedAt: /* @__PURE__ */ new Date() }, requestRuntimeScope);
473
- try {
474
- const updated = await $fetch(
475
- chatApi.path(`/conversations/${id}`),
476
- chatApi.withChatHeaders({
477
- method: "PATCH",
478
- body: { model }
479
- })
480
- );
481
- updateConversationInLoadedBuckets(updated, requestRuntimeScope);
482
- return true;
483
- } catch (err) {
484
- restoreConversationBuckets(conversationSnapshot, id);
485
- const message = err instanceof Error ? err.message : "Erro ao atualizar modelo da conversa";
486
- error.value = message;
487
- Sentry.captureException(err);
488
- return false;
489
- }
467
+ const updateConversationModel = (id, model) => {
468
+ const previousUpdate = modelUpdateTails.get(id) ?? Promise.resolve(true);
469
+ const update = previousUpdate.catch(() => false).then(async () => {
470
+ error.value = null;
471
+ const requestRuntimeScope = runtimeScope.value;
472
+ const affectedScopes = activeAndLoadedRuntimeConversationScopes(requestRuntimeScope);
473
+ const conversationSnapshot = snapshotConversationBuckets(affectedScopes);
474
+ const conversation = findConversationInLoadedBuckets(id, requestRuntimeScope);
475
+ if (conversation)
476
+ updateConversationInLoadedBuckets({ ...conversation, model, updatedAt: /* @__PURE__ */ new Date() }, requestRuntimeScope);
477
+ try {
478
+ const updated = await $fetch(
479
+ chatApi.path(`/conversations/${id}`),
480
+ chatApi.withChatHeaders({
481
+ method: "PATCH",
482
+ body: { model }
483
+ })
484
+ );
485
+ updateConversationInLoadedBuckets(updated, requestRuntimeScope);
486
+ return true;
487
+ } catch (err) {
488
+ restoreConversationBuckets(conversationSnapshot, id);
489
+ const message = err instanceof Error ? err.message : "Erro ao atualizar modelo da conversa";
490
+ error.value = message;
491
+ Sentry.captureException(err);
492
+ return false;
493
+ }
494
+ });
495
+ const clearUpdate = () => {
496
+ if (modelUpdateTails.get(id) === update)
497
+ modelUpdateTails.delete(id);
498
+ };
499
+ modelUpdateTails.set(id, update);
500
+ void update.then(clearUpdate, clearUpdate);
501
+ return update;
490
502
  };
491
503
  const duplicateConversation = async (id) => {
492
504
  error.value = null;
@@ -15,6 +15,11 @@ import {
15
15
  resolveTelaAgentInputs,
16
16
  useChat
17
17
  } from "../../messages/composables/chat";
18
+ import {
19
+ ensureConversationUsesSelectedModel,
20
+ MODEL_UPDATE_FAILED_MESSAGE,
21
+ resolveChatModelSelection
22
+ } from "../../messages/model-availability";
18
23
  import { useChatAction } from "../../messages/composables/chat-action";
19
24
  import { useChatApi } from "../../shared/composables/chat-api";
20
25
  import { useConversationFiles } from "../../files/composables/conversation-files";
@@ -201,30 +206,45 @@ watch(chatError, (message) => {
201
206
  });
202
207
  const conversationIdRef = computed(() => currentConversation.value?.id ?? null);
203
208
  const { total, files: conversationFiles } = useConversationFiles(conversationIdRef);
209
+ function persistConversationModel(conversation, value, wakeAgent) {
210
+ const previousModel = conversation.model;
211
+ setConversationModel(value);
212
+ const update = updateConversationModel(conversation.id, value).then(async (updated) => {
213
+ if (!updated && currentConversation.value?.id === conversation.id && currentConversation.value.model === value) {
214
+ setConversationModel(previousModel);
215
+ } else if (updated && value && wakeAgent) {
216
+ if (conversation.threadSessionId)
217
+ await agentWake.ensureReady(conversation.threadSessionId, value);
218
+ else
219
+ await agentWake.wake(void 0, value);
220
+ }
221
+ return updated;
222
+ });
223
+ const trackedUpdate = update.finally(() => {
224
+ if (modelUpdateInFlight.value === trackedUpdate)
225
+ modelUpdateInFlight.value = null;
226
+ });
227
+ modelUpdateInFlight.value = trackedUpdate;
228
+ return trackedUpdate;
229
+ }
230
+ async function waitForPendingModelUpdate() {
231
+ while (modelUpdateInFlight.value) {
232
+ const update = modelUpdateInFlight.value;
233
+ if (!await update)
234
+ throw new Error(MODEL_UPDATE_FAILED_MESSAGE);
235
+ }
236
+ }
204
237
  const selectedModel = computed({
205
- get: () => currentConversation.value ? currentConversation.value.model : pendingModelSelection.value,
238
+ get: () => resolveChatModelSelection(
239
+ currentConversation.value ? currentConversation.value.model : pendingModelSelection.value,
240
+ embedConfig?.availableModels.value
241
+ ),
206
242
  set: (value) => {
207
- pendingModelSelection.value = value;
243
+ const resolvedValue = resolveChatModelSelection(value, embedConfig?.availableModels.value);
244
+ pendingModelSelection.value = resolvedValue;
208
245
  const conversation = currentConversation.value;
209
- if (conversation && conversation.model !== value) {
210
- const previousModel = conversation.model;
211
- setConversationModel(value);
212
- const update = updateConversationModel(conversation.id, value).then(async (updated) => {
213
- if (!updated && currentConversation.value?.id === conversation.id && currentConversation.value.model === value) {
214
- setConversationModel(previousModel);
215
- } else if (updated && value) {
216
- if (conversation.threadSessionId)
217
- await agentWake.ensureReady(conversation.threadSessionId, value);
218
- else
219
- await agentWake.wake(void 0, value);
220
- }
221
- return updated;
222
- });
223
- modelUpdateInFlight.value = update.finally(() => {
224
- if (modelUpdateInFlight.value === update)
225
- modelUpdateInFlight.value = null;
226
- });
227
- }
246
+ if (conversation && conversation.model !== resolvedValue)
247
+ void persistConversationModel(conversation, resolvedValue, true);
228
248
  }
229
249
  });
230
250
  const filesCount = computed(() => total.value);
@@ -479,11 +499,15 @@ async function handleSend(content, files, agentInputs) {
479
499
  adoptConversation(grouped ? { ...conversation2, groupId } : conversation2);
480
500
  updateConversationId(conversation2.id);
481
501
  }
482
- if (modelUpdateInFlight.value)
483
- await modelUpdateInFlight.value;
502
+ await waitForPendingModelUpdate();
484
503
  const conversation = currentConversation.value;
485
504
  if (!conversation)
486
505
  throw new Error("Erro ao preparar conversa");
506
+ await ensureConversationUsesSelectedModel(
507
+ conversation.model,
508
+ selectedModel.value,
509
+ (value) => persistConversationModel(conversation, value, false)
510
+ );
487
511
  if (conversation.threadSessionId && resolvedFeatures.value.chatOutboundQueue) {
488
512
  const wake = await agentWake.ensureReady(conversation.threadSessionId, selectedModel.value);
489
513
  if (wake.status !== "ready") {
@@ -535,16 +559,25 @@ async function handleCancel() {
535
559
  await cancelConversation();
536
560
  }
537
561
  async function handleRetry(messageId) {
538
- if (currentConversation.value?.threadSessionId) {
539
- const wake = await agentWake.ensureReady(currentConversation.value.threadSessionId, selectedModel.value);
540
- if (wake.status !== "ready") {
541
- statusToast.update({ text: "N\xE3o foi poss\xEDvel acordar o agente. Tente novamente.", icon: "i-ph-warning" });
542
- return;
562
+ await retryMessage(messageId, async () => {
563
+ await waitForPendingModelUpdate();
564
+ const conversation = currentConversation.value;
565
+ if (!conversation)
566
+ throw new Error("Erro ao preparar conversa");
567
+ await ensureConversationUsesSelectedModel(
568
+ conversation.model,
569
+ selectedModel.value,
570
+ (value) => persistConversationModel(conversation, value, false)
571
+ );
572
+ if (conversation.threadSessionId) {
573
+ const wake = await agentWake.ensureReady(conversation.threadSessionId, selectedModel.value);
574
+ if (wake.status !== "ready")
575
+ throw new Error("N\xE3o foi poss\xEDvel acordar o agente. Tente novamente.");
576
+ } else {
577
+ await agentWake.waitUntilSettled();
543
578
  }
544
- } else {
545
- await agentWake.waitUntilSettled();
546
- }
547
- await retryMessage(messageId, claimableWakeSessionId());
579
+ return claimableWakeSessionId();
580
+ });
548
581
  }
549
582
  async function handleNewConversation(groupId) {
550
583
  setPendingConversationGroup(groupId);
@@ -13,6 +13,7 @@ const props = defineProps({
13
13
  initialConversationId: { type: [String, null], required: false },
14
14
  conversationScope: { type: [String, null], required: false },
15
15
  defaultConversationCreatorFilter: { type: null, required: false },
16
+ availableModels: { type: Array, required: false },
16
17
  conversationCreatorFilter: { type: null, required: false },
17
18
  hideConversationCreatorFilter: { type: Boolean, required: false },
18
19
  hideSidebar: { type: Boolean, required: false },
@@ -53,6 +54,7 @@ provideEmbedConfig({
53
54
  telaAgentId: normalizedTelaAgentId,
54
55
  conversationScope: normalizedConversationScope,
55
56
  defaultConversationCreatorFilter: computed(() => props.defaultConversationCreatorFilter),
57
+ availableModels: computed(() => props.availableModels),
56
58
  conversationCreatorFilter: computed(() => props.conversationCreatorFilter),
57
59
  telaAgentInputs: computed(() => props.telaAgentInputs),
58
60
  workspaceSettings: computed(() => props.workspaceSettings),
@@ -3,7 +3,7 @@ import type { ChatActionPayload } from '../../messages/composables/chat-action.j
3
3
  import type { MessageFeedbackPayload } from '../../messages/composables/chat-feedback.js';
4
4
  import type { ChatMessageTerminalPayload } from '../../messages/composables/chat-message-terminal.js';
5
5
  import type { ChatFeatureConfig } from '../feature-config.js';
6
- import type { ChatEmbedVariant, ChatEmbedVariantProps, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, TelaAgentChatEmbedProps } from '../types.js';
6
+ import type { AvailableChatModels, ChatEmbedVariant, ChatEmbedVariantProps, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, TelaAgentChatEmbedProps } from '../types.js';
7
7
 
8
8
  type MeistrariChatEmbedEmits = {
9
9
  /** Fired when the active conversation changes. Pairs with `v-model:conversation-id`. */
@@ -54,4 +54,4 @@ type MeistrariChatEmbedSlots = SlotsType<{
54
54
  declare const MeistrariChatEmbed: DefineSetupFnComponent<MeistrariChatEmbedProps, MeistrariChatEmbedEmits, MeistrariChatEmbedSlots>;
55
55
 
56
56
  export default MeistrariChatEmbed;
57
- export type { ChatActionPayload, ChatEmbedVariant, ChatEmbedVariantProps, ChatFeatureConfig, ChatMessageTerminalPayload, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, MessageFeedbackPayload, TelaAgentChatEmbedProps };
57
+ export type { AvailableChatModels, ChatActionPayload, ChatEmbedVariant, ChatEmbedVariantProps, ChatFeatureConfig, ChatMessageTerminalPayload, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, MessageFeedbackPayload, TelaAgentChatEmbedProps };
@@ -6,6 +6,7 @@ const { hideSidebar = void 0, ...props } = defineProps({
6
6
  initialConversationId: { type: [String, null], required: false },
7
7
  conversationScope: { type: [String, null], required: false },
8
8
  defaultConversationCreatorFilter: { type: null, required: false },
9
+ availableModels: { type: Array, required: false },
9
10
  conversationCreatorFilter: { type: null, required: false },
10
11
  hideConversationCreatorFilter: { type: Boolean, required: false },
11
12
  hideSidebar: { type: Boolean, required: false },
@@ -62,6 +63,7 @@ const sharedEmbedProps = computed(() => ({
62
63
  initialConversationId: props.initialConversationId,
63
64
  conversationScope: props.conversationScope,
64
65
  defaultConversationCreatorFilter: props.defaultConversationCreatorFilter,
66
+ availableModels: props.availableModels,
65
67
  conversationCreatorFilter: props.conversationCreatorFilter,
66
68
  hideConversationCreatorFilter: props.hideConversationCreatorFilter,
67
69
  hideSettings: hideSettings.value,
@@ -3,7 +3,7 @@ import type { ChatActionPayload } from '../../messages/composables/chat-action.j
3
3
  import type { MessageFeedbackPayload } from '../../messages/composables/chat-feedback.js';
4
4
  import type { ChatMessageTerminalPayload } from '../../messages/composables/chat-message-terminal.js';
5
5
  import type { ChatFeatureConfig } from '../feature-config.js';
6
- import type { ChatEmbedVariant, ChatEmbedVariantProps, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, TelaAgentChatEmbedProps } from '../types.js';
6
+ import type { AvailableChatModels, ChatEmbedVariant, ChatEmbedVariantProps, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, TelaAgentChatEmbedProps } from '../types.js';
7
7
 
8
8
  type MeistrariChatEmbedEmits = {
9
9
  /** Fired when the active conversation changes. Pairs with `v-model:conversation-id`. */
@@ -54,4 +54,4 @@ type MeistrariChatEmbedSlots = SlotsType<{
54
54
  declare const MeistrariChatEmbed: DefineSetupFnComponent<MeistrariChatEmbedProps, MeistrariChatEmbedEmits, MeistrariChatEmbedSlots>;
55
55
 
56
56
  export default MeistrariChatEmbed;
57
- export type { ChatActionPayload, ChatEmbedVariant, ChatEmbedVariantProps, ChatFeatureConfig, ChatMessageTerminalPayload, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, MessageFeedbackPayload, TelaAgentChatEmbedProps };
57
+ export type { AvailableChatModels, ChatActionPayload, ChatEmbedVariant, ChatEmbedVariantProps, ChatFeatureConfig, ChatMessageTerminalPayload, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, MessageFeedbackPayload, TelaAgentChatEmbedProps };
@@ -5,6 +5,7 @@ import type { TelaAgentExecutionInput } from '../../types/schemas/integrations/t
5
5
  import type { ChatLoadingMessageMode } from '../loading-messages.js';
6
6
  import type { ChatCitationsConfig } from '../../citations/types.js';
7
7
  import type { MessageFeedbackConfig, MessageFeedbackRating } from '../../messages/composables/chat-feedback.js';
8
+ import type { AvailableChatModels } from '../../messages/model-availability.js';
8
9
  type ConfiguredTelaAgentInputs = DeepReadonly<TelaAgentExecutionInput[]> | null | undefined;
9
10
  type ConfiguredMessageFeedback = Readonly<Record<string, MessageFeedbackRating>> | null | undefined;
10
11
  export type EmbedConfig = {
@@ -12,6 +13,7 @@ export type EmbedConfig = {
12
13
  telaAgentId: Ref<string | null>;
13
14
  conversationScope: Ref<string | null>;
14
15
  defaultConversationCreatorFilter: Ref<ConversationCreatorFilter>;
16
+ availableModels: Ref<AvailableChatModels | undefined>;
15
17
  conversationCreatorFilter: Ref<ConversationCreatorFilter | undefined>;
16
18
  telaAgentInputs: Ref<ConfiguredTelaAgentInputs>;
17
19
  workspaceSettings: Ref<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
@@ -31,6 +33,7 @@ type EmbedConfigInput = {
31
33
  telaAgentId?: MaybeRefOrGetter<string | null | undefined>;
32
34
  conversationScope?: MaybeRefOrGetter<string | null | undefined>;
33
35
  defaultConversationCreatorFilter?: MaybeRefOrGetter<ConversationCreatorFilter | null | undefined>;
36
+ availableModels?: MaybeRefOrGetter<AvailableChatModels | undefined>;
34
37
  conversationCreatorFilter?: MaybeRefOrGetter<ConversationCreatorFilter | undefined>;
35
38
  telaAgentInputs?: MaybeRefOrGetter<ConfiguredTelaAgentInputs>;
36
39
  workspaceSettings?: MaybeRefOrGetter<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
@@ -22,6 +22,7 @@ function useGlobalEmbedConfigState() {
22
22
  telaAgentId: useState("chat-embed-config-tela-agent-id", () => null),
23
23
  conversationScope: useState("chat-embed-config-conversation-scope", () => null),
24
24
  defaultConversationCreatorFilter: useState("chat-embed-config-default-conversation-creator-filter", () => void 0),
25
+ availableModels: useState("chat-embed-config-available-models", () => void 0),
25
26
  conversationCreatorFilter: useState("chat-embed-config-conversation-creator-filter", () => void 0),
26
27
  telaAgentInputs: useState("chat-embed-config-tela-agent-inputs", () => void 0),
27
28
  workspaceSettings: useState("chat-embed-config-workspace-settings", () => void 0),
@@ -43,6 +44,7 @@ export function provideEmbedConfig(input, options = {}) {
43
44
  telaAgentId: computed(() => toValue(input.telaAgentId) ?? null),
44
45
  conversationScope: computed(() => normalizeConversationScope(toValue(input.conversationScope))),
45
46
  defaultConversationCreatorFilter: computed(() => toValue(input.defaultConversationCreatorFilter) ?? "all"),
47
+ availableModels: computed(() => toValue(input.availableModels)),
46
48
  conversationCreatorFilter: computed(() => toValue(input.conversationCreatorFilter)),
47
49
  telaAgentInputs: computed(() => toValue(input.telaAgentInputs)),
48
50
  workspaceSettings: computed(() => toValue(input.workspaceSettings)),
@@ -65,6 +67,7 @@ export function provideEmbedConfig(input, options = {}) {
65
67
  globalConfig.telaAgentId.value = config.telaAgentId.value;
66
68
  globalConfig.conversationScope.value = config.conversationScope.value;
67
69
  globalConfig.defaultConversationCreatorFilter.value = config.defaultConversationCreatorFilter.value;
70
+ globalConfig.availableModels.value = config.availableModels.value;
68
71
  globalConfig.conversationCreatorFilter.value = config.conversationCreatorFilter.value;
69
72
  globalConfig.telaAgentInputs.value = config.telaAgentInputs.value;
70
73
  globalConfig.workspaceSettings.value = config.workspaceSettings.value;
@@ -94,6 +97,7 @@ export function useEmbedConfig() {
94
97
  telaAgentId: computed(() => globalConfig.telaAgentId.value),
95
98
  conversationScope: computed(() => globalConfig.conversationScope.value),
96
99
  defaultConversationCreatorFilter: computed(() => globalConfig.defaultConversationCreatorFilter.value ?? "all"),
100
+ availableModels: computed(() => globalConfig.availableModels.value),
97
101
  conversationCreatorFilter: computed(() => globalConfig.conversationCreatorFilter.value),
98
102
  telaAgentInputs: computed(() => globalConfig.telaAgentInputs.value),
99
103
  workspaceSettings: computed(() => globalConfig.workspaceSettings.value),
@@ -6,6 +6,8 @@ import type { ChatLoadingMessageMode } from './loading-messages.js';
6
6
  import type { ChatFeatureConfig } from './feature-config.js';
7
7
  import type { ChatCitationsConfig } from '../citations/types.js';
8
8
  import type { MessageFeedbackConfig, MessageFeedbackRating } from '../messages/composables/chat-feedback.js';
9
+ import type { AvailableChatModels } from '../messages/model-availability.js';
10
+ export type { AvailableChatModels } from '../messages/model-availability.js';
9
11
  export type ChatEmbedVariant = 'page' | 'floating' | 'sidepanel';
10
12
  export type ChatSuggestion = {
11
13
  /** Short label rendered on the suggestion pill. */
@@ -55,6 +57,12 @@ export type ChatEmbedSharedProps = {
55
57
  * conversation in scope, `'mine'` shows only those created by the current user.
56
58
  */
57
59
  defaultConversationCreatorFilter?: ConversationCreatorFilter;
60
+ /**
61
+ * Non-empty ordered list of models this embed may display and execute.
62
+ * When omitted, the full chat catalog remains available. The first model is
63
+ * used when a new or restored selection is not allowed by this list.
64
+ */
65
+ availableModels?: AvailableChatModels;
58
66
  /** Controlled creator filter. Use with `v-model:conversation-creator-filter` when the host changes it at runtime. */
59
67
  conversationCreatorFilter?: ConversationCreatorFilter;
60
68
  /** Hide the conversation creator filter while preserving its active value. */
@@ -1,5 +1,7 @@
1
1
  <script setup>
2
2
  import { chatModelOptions } from "../../types/schemas/chat/ui.js";
3
+ import { useEmbedConfig } from "../../embed/composables/embed-config";
4
+ import { resolveAvailableChatModels } from "../model-availability";
3
5
  import TelaChatIconsAnthropic from "@meistrari/tela-build/components/tela/chat/icons/anthropic.vue";
4
6
  import TelaChatIconsGemini from "@meistrari/tela-build/components/tela/chat/icons/gemini.vue";
5
7
  import TelaChatIconsOpenai from "@meistrari/tela-build/components/tela/chat/icons/openai.vue";
@@ -7,23 +9,32 @@ defineProps({
7
9
  disabled: { type: Boolean, required: false }
8
10
  });
9
11
  const model = defineModel({ type: [String, null], ...{ default: null } });
12
+ const embedConfig = useEmbedConfig();
10
13
  const providerIcons = {
11
14
  Anthropic: TelaChatIconsAnthropic,
12
15
  OpenAI: TelaChatIconsOpenai,
13
16
  Google: TelaChatIconsGemini
14
17
  };
15
- const options = chatModelOptions.map((option) => ({
16
- value: option.value,
17
- label: option.label,
18
- description: option.description,
19
- group: option.provider,
20
- icon: providerIcons[option.provider],
21
- cost: option.cost,
22
- maxInputTokens: option.maxInputTokens,
23
- maxOutputTokens: option.maxOutputTokens,
24
- isMultiModal: option.isMultiModal,
25
- tabs: [...option.tabs]
26
- }));
18
+ const options = computed(() => {
19
+ const availableModels = resolveAvailableChatModels(embedConfig?.availableModels.value);
20
+ return availableModels.map((model2) => {
21
+ const option = chatModelOptions.find((candidate) => candidate.value === model2);
22
+ if (!option)
23
+ throw new Error(`${model2} is missing from the chat model catalog`);
24
+ return {
25
+ value: option.value,
26
+ label: option.label,
27
+ description: option.description,
28
+ group: option.provider,
29
+ icon: providerIcons[option.provider],
30
+ cost: option.cost,
31
+ maxInputTokens: option.maxInputTokens,
32
+ maxOutputTokens: option.maxOutputTokens,
33
+ isMultiModal: option.isMultiModal,
34
+ tabs: [...option.tabs]
35
+ };
36
+ });
37
+ });
27
38
  const tabLabels = {
28
39
  all: "Todos",
29
40
  advanced: "Avan\xE7ados",
@@ -40,7 +40,7 @@ export declare function useChat(options?: {
40
40
  }>, queuedSend?: QueuedSend) => Promise<Message | null>;
41
41
  flushOutboundQueue: (wakeSessionId?: string) => Promise<Message | null>;
42
42
  failOutboundQueue: (message: string) => boolean;
43
- retryMessage: (messageId: string, wakeSessionId?: string) => Promise<boolean>;
43
+ retryMessage: (messageId: string, prepare?: () => Promise<string | undefined>) => Promise<boolean>;
44
44
  cancelConversation: () => Promise<boolean>;
45
45
  startPolling: () => void;
46
46
  stopPolling: () => void;
@@ -713,19 +713,24 @@ export function useChat(options = {}) {
713
713
  return false;
714
714
  }
715
715
  };
716
- const retryMessage = async (messageId, wakeSessionId) => {
716
+ const retryMessage = async (messageId, prepare) => {
717
717
  if (!currentConversation.value)
718
718
  return false;
719
+ const conversationId = currentConversation.value.id;
719
720
  error.value = null;
720
- const retry = beginAssistantMessageRetry(messages.value, messageId);
721
- if (!retry)
722
- return false;
723
- messages.value = retry.messages;
724
- streamingMessageId.value = messageId;
725
- directStream.beginMessage();
721
+ let retry = null;
726
722
  try {
723
+ const wakeSessionId = prepare ? await prepare() : void 0;
724
+ if (currentConversation.value?.id !== conversationId)
725
+ return false;
726
+ retry = beginAssistantMessageRetry(messages.value, messageId);
727
+ if (!retry)
728
+ return false;
729
+ messages.value = retry.messages;
730
+ streamingMessageId.value = messageId;
731
+ directStream.beginMessage();
727
732
  await $fetch(
728
- chatApi.path(`/conversations/${currentConversation.value.id}/messages/${messageId}/retry`),
733
+ chatApi.path(`/conversations/${conversationId}/messages/${messageId}/retry`),
729
734
  chatApi.withChatHeaders({
730
735
  method: "POST",
731
736
  body: wakeSessionId ? { wakeSessionId } : {}
@@ -734,9 +739,11 @@ export function useChat(options = {}) {
734
739
  startPolling();
735
740
  return true;
736
741
  } catch (err) {
737
- messages.value = restoreAssistantMessageAfterRetryFailure(messages.value, retry.originalMessage);
738
- streamingMessageId.value = null;
739
- directStream.disconnect();
742
+ if (retry) {
743
+ messages.value = restoreAssistantMessageAfterRetryFailure(messages.value, retry.originalMessage);
744
+ streamingMessageId.value = null;
745
+ directStream.disconnect();
746
+ }
740
747
  const errorMessage = getChatApiErrorCode(err) === "workspace_agent_not_ready" ? WORKSPACE_AGENT_NOT_READY_TEXT : err instanceof Error ? err.message : "Erro ao reenviar mensagem";
741
748
  error.value = errorMessage;
742
749
  Sentry.captureException(err);
@@ -0,0 +1,6 @@
1
+ import type { ChatModel } from '../types/schemas/chat/models.js';
2
+ export type AvailableChatModels = readonly [ChatModel, ...ChatModel[]];
3
+ export declare const MODEL_UPDATE_FAILED_MESSAGE = "N\u00E3o foi poss\u00EDvel atualizar o modelo da conversa";
4
+ export declare function resolveAvailableChatModels(availableModels?: readonly ChatModel[]): readonly ChatModel[];
5
+ export declare function resolveChatModelSelection(selectedModel: string | null, availableModels?: readonly ChatModel[]): string | null;
6
+ export declare function ensureConversationUsesSelectedModel(conversationModel: string | null, selectedModel: string | null, updateModel: (model: string | null) => Promise<boolean>): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { chatModelIds, isValidChatModel } from "../types/schemas/chat/models.js";
2
+ export const MODEL_UPDATE_FAILED_MESSAGE = "N\xE3o foi poss\xEDvel atualizar o modelo da conversa";
3
+ export function resolveAvailableChatModels(availableModels) {
4
+ if (availableModels === void 0)
5
+ return chatModelIds;
6
+ if (availableModels.length === 0)
7
+ throw new Error("availableModels must contain at least one model");
8
+ const uniqueModels = [];
9
+ for (const model of availableModels) {
10
+ if (!isValidChatModel(model))
11
+ throw new Error(`${model} is not a supported chat model`);
12
+ if (!uniqueModels.includes(model))
13
+ uniqueModels.push(model);
14
+ }
15
+ return uniqueModels;
16
+ }
17
+ export function resolveChatModelSelection(selectedModel, availableModels) {
18
+ if (availableModels === void 0)
19
+ return selectedModel;
20
+ const resolvedModels = resolveAvailableChatModels(availableModels);
21
+ return selectedModel && resolvedModels.includes(selectedModel) ? selectedModel : resolvedModels[0];
22
+ }
23
+ export async function ensureConversationUsesSelectedModel(conversationModel, selectedModel, updateModel) {
24
+ if (conversationModel === selectedModel)
25
+ return;
26
+ if (!await updateModel(selectedModel))
27
+ throw new Error(MODEL_UPDATE_FAILED_MESSAGE);
28
+ }
@@ -1,6 +1,6 @@
1
1
  export type { ChatCitationsConfig, CitationMeta, CitationRef, } from '../citations/types.js';
2
2
  export type { ChatFeatureConfig } from '../embed/feature-config.js';
3
- export type { ChatActor, ChatEmbedSharedProps, ChatEmbedVariant, ChatEmbedVariantProps, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, TelaAgentChatEmbedProps, } from '../embed/types.js';
3
+ export type { AvailableChatModels, ChatActor, ChatEmbedSharedProps, ChatEmbedVariant, ChatEmbedVariantProps, ChatSidebarConfig, ChatSuggestion, DefaultChatEmbedProps, MeistrariChatEmbedProps, TelaAgentChatEmbedProps, } from '../embed/types.js';
4
4
  export type { ChatActionPayload } from '../messages/composables/chat-action.js';
5
5
  export type { MessageFeedbackConfig, MessageFeedbackPayload, MessageFeedbackRating, MessageFeedbackSubmitHandler, MessageFeedbackSubmitResult, } from '../messages/composables/chat-feedback.js';
6
6
  export { ChatMessageStatus } from '../messages/composables/chat-message-terminal.js';
@@ -216,6 +216,7 @@ export declare const createConversationRequestSchema: z.ZodObject<{
216
216
  "claude-opus-5": "claude-opus-5";
217
217
  "claude-opus-4-8": "claude-opus-4-8";
218
218
  "claude-sonnet-5": "claude-sonnet-5";
219
+ "claude-sonnet-4-6": "claude-sonnet-4-6";
219
220
  "claude-haiku-4-5": "claude-haiku-4-5";
220
221
  }>>>;
221
222
  agentInputs: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -253,6 +254,7 @@ export declare const updateConversationRequestSchema: z.ZodObject<{
253
254
  "claude-opus-5": "claude-opus-5";
254
255
  "claude-opus-4-8": "claude-opus-4-8";
255
256
  "claude-sonnet-5": "claude-sonnet-5";
257
+ "claude-sonnet-4-6": "claude-sonnet-4-6";
256
258
  "claude-haiku-4-5": "claude-haiku-4-5";
257
259
  }>>>;
258
260
  }, z.core.$strict>;
@@ -1,9 +1,10 @@
1
1
  import { z } from 'zod';
2
- export declare const chatModelIds: readonly ["claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5"];
2
+ export declare const chatModelIds: readonly ["claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5"];
3
3
  export declare const chatModelSchema: z.ZodEnum<{
4
4
  "claude-opus-5": "claude-opus-5";
5
5
  "claude-opus-4-8": "claude-opus-4-8";
6
6
  "claude-sonnet-5": "claude-sonnet-5";
7
+ "claude-sonnet-4-6": "claude-sonnet-4-6";
7
8
  "claude-haiku-4-5": "claude-haiku-4-5";
8
9
  }>;
9
10
  export type ChatModel = z.infer<typeof chatModelSchema>;
@@ -3,6 +3,7 @@ export const chatModelIds = [
3
3
  'claude-opus-5',
4
4
  'claude-opus-4-8',
5
5
  'claude-sonnet-5',
6
+ 'claude-sonnet-4-6',
6
7
  'claude-haiku-4-5',
7
8
  ];
8
9
  export const chatModelSchema = z.enum(chatModelIds);
@@ -49,6 +49,16 @@ export declare const chatModelOptions: readonly [{
49
49
  readonly maxOutputTokens: 64000;
50
50
  readonly isMultiModal: true;
51
51
  readonly tabs: readonly ["all", "advanced", "multimodal"];
52
+ }, {
53
+ readonly value: "claude-sonnet-4-6";
54
+ readonly label: "Claude 4.6 Sonnet";
55
+ readonly description: "Agente rápido para tarefas avançadas";
56
+ readonly provider: "Anthropic";
57
+ readonly cost: 4;
58
+ readonly maxInputTokens: 1000000;
59
+ readonly maxOutputTokens: 64000;
60
+ readonly isMultiModal: true;
61
+ readonly tabs: readonly ["all", "advanced", "multimodal"];
52
62
  }, {
53
63
  readonly value: "claude-haiku-4-5";
54
64
  readonly label: "Claude 4.5 Haiku";
@@ -9,6 +9,7 @@ export const chatModelOptions = [
9
9
  { value: 'claude-opus-5', label: 'Claude Opus 5', description: 'Agente de ponta para trabalho complexo', provider: 'Anthropic', cost: 5, maxInputTokens: 1_000_000, maxOutputTokens: 128_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
10
10
  { value: 'claude-opus-4-8', label: 'Claude 4.8 Opus', description: 'Agente de ponta para tarefas complexas', provider: 'Anthropic', cost: 4, maxInputTokens: 1_000_000, maxOutputTokens: 128_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
11
11
  { value: 'claude-sonnet-5', label: 'Claude Sonnet 5', description: 'Agente rápido para tarefas avançadas', provider: 'Anthropic', cost: 4, maxInputTokens: 1_000_000, maxOutputTokens: 64_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
12
+ { value: 'claude-sonnet-4-6', label: 'Claude 4.6 Sonnet', description: 'Agente rápido para tarefas avançadas', provider: 'Anthropic', cost: 4, maxInputTokens: 1_000_000, maxOutputTokens: 64_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
12
13
  { value: 'claude-haiku-4-5', label: 'Claude 4.5 Haiku', description: 'Agente rápido para tarefas simples', provider: 'Anthropic', cost: 3, maxInputTokens: 200_000, maxOutputTokens: 64_000, isMultiModal: true, tabs: ['all', 'cheap', 'multimodal'] },
13
14
  ];
14
15
  export const topbarTabs = ['chat', 'files', 'usage'];
@@ -278,6 +278,7 @@ export declare const telaAgentWakeRequestSchema: z.ZodObject<{
278
278
  "claude-opus-5": "claude-opus-5";
279
279
  "claude-opus-4-8": "claude-opus-4-8";
280
280
  "claude-sonnet-5": "claude-sonnet-5";
281
+ "claude-sonnet-4-6": "claude-sonnet-4-6";
281
282
  "claude-haiku-4-5": "claude-haiku-4-5";
282
283
  }>>;
283
284
  }, z.core.$strip>;
package/dist/types.d.mts CHANGED
@@ -6,7 +6,7 @@ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<
6
6
 
7
7
  export { ChatMessageStatus } from '../dist/runtime/messages/composables/chat-message-terminal.js'
8
8
 
9
- export { type ChatActionPayload, type ChatActor, type ChatEmbedSharedProps, type ChatEmbedVariant, type ChatEmbedVariantProps, type ChatFeatureConfig, type ChatMessageTerminalPayload, type ChatSidebarConfig, type ChatSuggestion, type DefaultChatEmbedProps, type MeistrariChatEmbedProps, type MessageFeedbackConfig, type MessageFeedbackPayload, type MessageFeedbackRating, type MessageFeedbackSubmitHandler, type MessageFeedbackSubmitResult, type MessageOverviewResponse, type TelaAgentChatEmbedProps, type TelaAgentExecutionInput } from '../dist/runtime/types/embed.js'
9
+ export { type AvailableChatModels, type ChatActionPayload, type ChatActor, type ChatEmbedSharedProps, type ChatEmbedVariant, type ChatEmbedVariantProps, type ChatFeatureConfig, type ChatMessageTerminalPayload, type ChatSidebarConfig, type ChatSuggestion, type DefaultChatEmbedProps, type MeistrariChatEmbedProps, type MessageFeedbackConfig, type MessageFeedbackPayload, type MessageFeedbackRating, type MessageFeedbackSubmitHandler, type MessageFeedbackSubmitResult, type MessageOverviewResponse, type TelaAgentChatEmbedProps, type TelaAgentExecutionInput } from '../dist/runtime/types/embed.js'
10
10
 
11
11
  export { default } from './module.mjs'
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "3.14.2-rc.2",
3
+ "version": "3.15.0-rc.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {