@meistrari/chat-nuxt 4.4.0-rc.4 → 4.4.0-rc.6

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 (28) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +2 -0
  3. package/dist/runtime/embed/components/chat-embed-inner.vue +59 -45
  4. package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +2 -0
  5. package/dist/runtime/embed/components/chat-embed.d.vue.ts +1 -42
  6. package/dist/runtime/embed/components/chat-embed.vue +3 -0
  7. package/dist/runtime/embed/components/chat-embed.vue.d.ts +1 -42
  8. package/dist/runtime/embed/components/meistrari-chat-embed.vue +5 -2
  9. package/dist/runtime/embed/composables/conversation-browser-state.d.ts +1 -1
  10. package/dist/runtime/embed/composables/conversation-browser-state.js +5 -3
  11. package/dist/runtime/embed/types.d.ts +4 -0
  12. package/dist/runtime/messages/answer.d.ts +23 -0
  13. package/dist/runtime/messages/answer.js +42 -0
  14. package/dist/runtime/messages/components/chat-answer.d.vue.ts +14 -0
  15. package/dist/runtime/messages/components/chat-answer.vue +154 -0
  16. package/dist/runtime/messages/components/chat-answer.vue.d.ts +14 -0
  17. package/dist/runtime/messages/components/chat-message-bubble.vue +4 -1
  18. package/dist/runtime/messages/components/chat-message-list.vue +3 -1
  19. package/dist/runtime/messages/composables/chat-message-terminal.d.ts +1 -0
  20. package/dist/runtime/messages/composables/chat-message-terminal.js +2 -1
  21. package/dist/runtime/rendering/components/app-markdown-render.vue +1 -7
  22. package/dist/runtime/rendering/markdown/escape-dollar-signs.d.ts +1 -0
  23. package/dist/runtime/rendering/markdown/escape-dollar-signs.js +7 -0
  24. package/dist/runtime/types/messages.d.ts +2 -0
  25. package/dist/runtime/types/messages.js +1 -0
  26. package/dist/runtime/types/schemas/chat/usage.d.ts +0 -15
  27. package/dist/runtime/types/schemas/chat/usage.js +0 -3
  28. package/package.json +6 -2
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.4.0-rc.4",
4
+ "version": "4.4.0-rc.6",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -4,6 +4,7 @@ import type { ChatFeatureConfig } from '../feature-config.js';
4
4
  import type { ChatActor, ChatSidebarConfig, ChatSuggestion } from '../types.js';
5
5
  type EmbedUser = ChatActor;
6
6
  type __VLS_Props = {
7
+ syncBrowserState?: boolean;
7
8
  conversationId?: string | null;
8
9
  conversationEvents?: readonly ConversationEvent[] | null;
9
10
  composerDisabled?: boolean;
@@ -40,6 +41,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
40
41
  compact: boolean;
41
42
  hideSettings: boolean;
42
43
  hideSidebar: boolean;
44
+ syncBrowserState: boolean;
43
45
  initialConversationId: string | null;
44
46
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
45
47
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -43,6 +43,7 @@ import { useWorkspaceMembers } from "../../workspace/composables/workspace-membe
43
43
  import { useWorkspaceSettings } from "../../workspace/composables/workspace-settings";
44
44
  import { useWorkspaceSwitchModal } from "../../workspace/composables/workspace-switch-modal";
45
45
  const props = defineProps({
46
+ syncBrowserState: { type: Boolean, required: false, default: true },
46
47
  conversationId: { type: [String, null], required: false },
47
48
  conversationEvents: { type: [Array, null], required: false },
48
49
  composerDisabled: { type: Boolean, required: false },
@@ -138,7 +139,7 @@ const {
138
139
  closePreview
139
140
  } = useFilePreviewPanel();
140
141
  const { isOpen: isCitationPanelOpen } = useCitationPanel();
141
- const sharedConversationId = getSharedConversationId(route.query);
142
+ const sharedConversationId = props.syncBrowserState === false ? null : getSharedConversationId(route.query);
142
143
  const activeConversationId = ref(
143
144
  normalizeConversationId(props.conversationId) ?? normalizeConversationId(props.initialConversationId) ?? normalizeConversationId(sharedConversationId)
144
145
  );
@@ -157,6 +158,11 @@ const {
157
158
  const resolvedFeatures = computed(() => resolveChatFeatures(props.features));
158
159
  const dropZoneRef = ref(null);
159
160
  const activeTab = ref("chat");
161
+ const answerTarget = ref(null);
162
+ provide("chat-answer-target", answerTarget);
163
+ function setAnswerTarget(el) {
164
+ answerTarget.value = el instanceof HTMLElement ? el : null;
165
+ }
160
166
  const messageInputRef = ref(null);
161
167
  const PROMPT_INPUT_HINTS = [
162
168
  { icon: "i-ph-at-bold", label: "Para mencionar arquivos" },
@@ -507,7 +513,7 @@ useConversationBrowserState(activeConversationId, computed(() => {
507
513
  if (currentConversation.value?.id !== activeConversationId.value)
508
514
  return null;
509
515
  return optimisticTitle.value ?? currentConversation.value?.title ?? null;
510
- }));
516
+ }), () => props.syncBrowserState !== false);
511
517
  const displayTitle = computed(() => {
512
518
  if (optimisticTitle.value !== null)
513
519
  return optimisticTitle.value;
@@ -1144,29 +1150,33 @@ async function handleDelete() {
1144
1150
  <slot name="failed-message-actions" v-bind="slotProps" />
1145
1151
  </template>
1146
1152
  </ChatMessageList>
1147
- <ChatMessageInput
1148
- ref="messageInputRef"
1149
- v-model:selected-model="selectedModel"
1150
- v-model:selected-reasoning-effort="selectedReasoningEffort"
1151
- :loading="composerLocked"
1152
- :disabled="composerDisabled"
1153
- :creator-name="currentUser?.name ?? void 0"
1154
- :creator-email="
1153
+ <div data-chat-composer flex="~ col" gap-12px min-h-0>
1154
+ <div :ref="setAnswerTarget" data-chat-answer-dock class="empty:hidden max-h-[50dvh] overflow-y-auto" />
1155
+ <ChatMessageInput
1156
+ ref="messageInputRef"
1157
+ v-model:selected-model="selectedModel"
1158
+ v-model:selected-reasoning-effort="selectedReasoningEffort"
1159
+ class="!p-0"
1160
+ :loading="composerLocked"
1161
+ :disabled="composerDisabled"
1162
+ :creator-name="currentUser?.name ?? void 0"
1163
+ :creator-email="
1155
1164
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
1156
1165
  "
1157
- :creator-image="currentUser?.image ?? void 0"
1158
- :messages="messages"
1159
- :conversation-files="conversationFiles"
1160
- :workspace-settings="effectiveWorkspaceSettings"
1161
- :agent-input-schema="visibleTelaAgentInputSchema"
1162
- :agent-input-schema-loading="visibleTelaAgentInputSchemaLoading"
1163
- :agent-skills="agentSkills"
1164
- :agent-context-files="agentContextFiles"
1165
- :show-cancel-button="resolvedFeatures.showCancelButton"
1166
- has-disclaimer
1167
- @send="handleSend"
1168
- @cancel="handleCancel"
1169
- />
1166
+ :creator-image="currentUser?.image ?? void 0"
1167
+ :messages="messages"
1168
+ :conversation-files="conversationFiles"
1169
+ :workspace-settings="effectiveWorkspaceSettings"
1170
+ :agent-input-schema="visibleTelaAgentInputSchema"
1171
+ :agent-input-schema-loading="visibleTelaAgentInputSchemaLoading"
1172
+ :agent-skills="agentSkills"
1173
+ :agent-context-files="agentContextFiles"
1174
+ :show-cancel-button="resolvedFeatures.showCancelButton"
1175
+ has-disclaimer
1176
+ @send="handleSend"
1177
+ @cancel="handleCancel"
1178
+ />
1179
+ </div>
1170
1180
  </ChatMobileShell>
1171
1181
 
1172
1182
  <ChatTopbar
@@ -1359,29 +1369,33 @@ async function handleDelete() {
1359
1369
  <slot name="failed-message-actions" v-bind="slotProps" />
1360
1370
  </template>
1361
1371
  </ChatMessageList>
1362
- <ChatMessageInput
1363
- ref="messageInputRef"
1364
- v-model:selected-model="selectedModel"
1365
- v-model:selected-reasoning-effort="selectedReasoningEffort"
1366
- :loading="composerLocked"
1367
- :disabled="composerDisabled"
1368
- :creator-name="currentUser?.name ?? void 0"
1369
- :creator-email="
1372
+ <div data-chat-composer flex="~ col" gap-12px min-h-0>
1373
+ <div :ref="setAnswerTarget" data-chat-answer-dock class="empty:hidden max-h-[50dvh] overflow-y-auto" />
1374
+ <ChatMessageInput
1375
+ ref="messageInputRef"
1376
+ v-model:selected-model="selectedModel"
1377
+ v-model:selected-reasoning-effort="selectedReasoningEffort"
1378
+ class="!p-0"
1379
+ :loading="composerLocked"
1380
+ :disabled="composerDisabled"
1381
+ :creator-name="currentUser?.name ?? void 0"
1382
+ :creator-email="
1370
1383
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
1371
1384
  "
1372
- :creator-image="currentUser?.image ?? void 0"
1373
- :messages="messages"
1374
- :conversation-files="conversationFiles"
1375
- :workspace-settings="effectiveWorkspaceSettings"
1376
- :agent-input-schema="visibleTelaAgentInputSchema"
1377
- :agent-input-schema-loading="visibleTelaAgentInputSchemaLoading"
1378
- :agent-skills="agentSkills"
1379
- :agent-context-files="agentContextFiles"
1380
- :show-cancel-button="resolvedFeatures.showCancelButton"
1381
- has-disclaimer
1382
- @send="handleSend"
1383
- @cancel="handleCancel"
1384
- />
1385
+ :creator-image="currentUser?.image ?? void 0"
1386
+ :messages="messages"
1387
+ :conversation-files="conversationFiles"
1388
+ :workspace-settings="effectiveWorkspaceSettings"
1389
+ :agent-input-schema="visibleTelaAgentInputSchema"
1390
+ :agent-input-schema-loading="visibleTelaAgentInputSchemaLoading"
1391
+ :agent-skills="agentSkills"
1392
+ :agent-context-files="agentContextFiles"
1393
+ :show-cancel-button="resolvedFeatures.showCancelButton"
1394
+ has-disclaimer
1395
+ @send="handleSend"
1396
+ @cancel="handleCancel"
1397
+ />
1398
+ </div>
1385
1399
  </TelaChatBody>
1386
1400
 
1387
1401
  <Transition name="fade">
@@ -1545,5 +1559,5 @@ async function handleDelete() {
1545
1559
  </style>
1546
1560
 
1547
1561
  <style>
1548
- [data-chat-body][data-variant=page] [data-chat-prompt-input]{margin-inline:auto!important;max-width:700px}
1562
+ [data-chat-body][data-variant=page] :is([data-chat-composer],[data-chat-prompt-input]){margin-inline:auto!important;max-width:700px}
1549
1563
  </style>
@@ -4,6 +4,7 @@ import type { ChatFeatureConfig } from '../feature-config.js';
4
4
  import type { ChatActor, ChatSidebarConfig, ChatSuggestion } from '../types.js';
5
5
  type EmbedUser = ChatActor;
6
6
  type __VLS_Props = {
7
+ syncBrowserState?: boolean;
7
8
  conversationId?: string | null;
8
9
  conversationEvents?: readonly ConversationEvent[] | null;
9
10
  composerDisabled?: boolean;
@@ -40,6 +41,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
40
41
  compact: boolean;
41
42
  hideSettings: boolean;
42
43
  hideSidebar: boolean;
44
+ syncBrowserState: boolean;
43
45
  initialConversationId: string | null;
44
46
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
45
47
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,7 +1,4 @@
1
1
  import { useChatAuth } from '#chat-auth';
2
- import { type ChatActionPayload } from '../../messages/composables/chat-action.js';
3
- import { type MessageFeedbackPayload } from '../../messages/composables/chat-feedback.js';
4
- import { type ChatMessageTerminalPayload } from '../../messages/composables/chat-message-terminal.js';
5
2
  declare var __VLS_13: {}, __VLS_16: {
6
3
  message: useChatAuth;
7
4
  };
@@ -10,45 +7,7 @@ type __VLS_Slots = {} & {
10
7
  } & {
11
8
  'failed-message-actions'?: (props: typeof __VLS_16) => any;
12
9
  };
13
- declare const __VLS_base: import("vue").DefineComponent<(import("../types.js").ChatEmbedSharedProps & {
14
- telaAgentId?: undefined;
15
- telaAgentInputs?: undefined;
16
- workspaceSettings?: import("vue").DeepReadonly<useChatAuth> | useChatAuth | null;
17
- user?: import("../types.js").ChatActor | null;
18
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
19
- } & import("../types.js").ChatEmbedVariantProps) | (import("../types.js").ChatEmbedSharedProps & {
20
- telaAgentId: string;
21
- telaAgentInputs?: import("vue").DeepReadonly<useChatAuth[]> | null;
22
- workspaceSettings?: never;
23
- user?: never;
24
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
25
- } & import("../types.js").ChatEmbedVariantProps), {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
26
- "update:conversationId": (value: string | null) => any;
27
- "update:conversationTitle": (value: string | null) => any;
28
- "update:conversationCreatorFilter": (value: "all" | "mine") => any;
29
- messageFeedback: (payload: MessageFeedbackPayload) => any;
30
- action: (payload: ChatActionPayload) => any;
31
- messageTerminal: (payload: ChatMessageTerminalPayload) => any;
32
- }, string, import("vue").PublicProps, Readonly<(import("../types.js").ChatEmbedSharedProps & {
33
- telaAgentId?: undefined;
34
- telaAgentInputs?: undefined;
35
- workspaceSettings?: import("vue").DeepReadonly<useChatAuth> | useChatAuth | null;
36
- user?: import("../types.js").ChatActor | null;
37
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
38
- } & import("../types.js").ChatEmbedVariantProps) | (import("../types.js").ChatEmbedSharedProps & {
39
- telaAgentId: string;
40
- telaAgentInputs?: import("vue").DeepReadonly<useChatAuth[]> | null;
41
- workspaceSettings?: never;
42
- user?: never;
43
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
44
- } & import("../types.js").ChatEmbedVariantProps)> & Readonly<{
45
- "onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
46
- "onUpdate:conversationTitle"?: ((value: string | null) => any) | undefined;
47
- "onUpdate:conversationCreatorFilter"?: ((value: "all" | "mine") => any) | undefined;
48
- onMessageFeedback?: ((payload: MessageFeedbackPayload) => any) | undefined;
49
- onAction?: ((payload: ChatActionPayload) => any) | undefined;
50
- onMessageTerminal?: ((payload: ChatMessageTerminalPayload) => any) | undefined;
51
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
52
11
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
53
12
  declare const _default: typeof __VLS_export;
54
13
  export default _default;
@@ -9,6 +9,7 @@ import { provideEmbedConfig, useEmbedConfig } from "../composables/embed-config"
9
9
  import { normalizeConversationScope, normalizeTelaAgentId, resolveConversationScopeKeySegment } from "../../conversations/scope";
10
10
  import ChatEmbedInner from "./chat-embed-inner.vue";
11
11
  const props = defineProps({
12
+ syncBrowserState: { type: Boolean, required: false, default: true },
12
13
  chatConfig: { type: Object, required: false },
13
14
  telaAgentBranch: { type: [String, null], required: false },
14
15
  conversationId: { type: [String, null], required: false },
@@ -42,6 +43,7 @@ const props = defineProps({
42
43
  features: { type: Object, required: false },
43
44
  variant: { type: String, required: false },
44
45
  defaultOpen: { type: Boolean, required: false },
46
+ closeOnOutsideClick: { type: Boolean, required: false },
45
47
  launcherLabel: { type: String, required: false }
46
48
  });
47
49
  const emit = defineEmits(["update:conversationId", "update:conversationTitle", "update:conversationCreatorFilter", "action", "messageFeedback", "messageTerminal"]);
@@ -120,6 +122,7 @@ provideChatMessageTerminal((payload) => {
120
122
  <ChatEmbedInner
121
123
  v-if="workspaceId"
122
124
  :key="`${workspaceId}:${normalizedTelaAgentId ?? 'default-chat'}:branch:${telaAgentBranch ?? ''}:scope:${resolveConversationScopeKeySegment(normalizedConversationScope)}`"
125
+ :sync-browser-state="syncBrowserState"
123
126
  :conversation-id="conversationId"
124
127
  :conversation-events="conversationEvents"
125
128
  :composer-disabled="composerDisabled"
@@ -1,7 +1,4 @@
1
1
  import { useChatAuth } from '#chat-auth';
2
- import { type ChatActionPayload } from '../../messages/composables/chat-action.js';
3
- import { type MessageFeedbackPayload } from '../../messages/composables/chat-feedback.js';
4
- import { type ChatMessageTerminalPayload } from '../../messages/composables/chat-message-terminal.js';
5
2
  declare var __VLS_13: {}, __VLS_16: {
6
3
  message: useChatAuth;
7
4
  };
@@ -10,45 +7,7 @@ type __VLS_Slots = {} & {
10
7
  } & {
11
8
  'failed-message-actions'?: (props: typeof __VLS_16) => any;
12
9
  };
13
- declare const __VLS_base: import("vue").DefineComponent<(import("../types.js").ChatEmbedSharedProps & {
14
- telaAgentId?: undefined;
15
- telaAgentInputs?: undefined;
16
- workspaceSettings?: import("vue").DeepReadonly<useChatAuth> | useChatAuth | null;
17
- user?: import("../types.js").ChatActor | null;
18
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
19
- } & import("../types.js").ChatEmbedVariantProps) | (import("../types.js").ChatEmbedSharedProps & {
20
- telaAgentId: string;
21
- telaAgentInputs?: import("vue").DeepReadonly<useChatAuth[]> | null;
22
- workspaceSettings?: never;
23
- user?: never;
24
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
25
- } & import("../types.js").ChatEmbedVariantProps), {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
26
- "update:conversationId": (value: string | null) => any;
27
- "update:conversationTitle": (value: string | null) => any;
28
- "update:conversationCreatorFilter": (value: "all" | "mine") => any;
29
- messageFeedback: (payload: MessageFeedbackPayload) => any;
30
- action: (payload: ChatActionPayload) => any;
31
- messageTerminal: (payload: ChatMessageTerminalPayload) => any;
32
- }, string, import("vue").PublicProps, Readonly<(import("../types.js").ChatEmbedSharedProps & {
33
- telaAgentId?: undefined;
34
- telaAgentInputs?: undefined;
35
- workspaceSettings?: import("vue").DeepReadonly<useChatAuth> | useChatAuth | null;
36
- user?: import("../types.js").ChatActor | null;
37
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
38
- } & import("../types.js").ChatEmbedVariantProps) | (import("../types.js").ChatEmbedSharedProps & {
39
- telaAgentId: string;
40
- telaAgentInputs?: import("vue").DeepReadonly<useChatAuth[]> | null;
41
- workspaceSettings?: never;
42
- user?: never;
43
- features?: Partial<import("../feature-config.js").ChatFeatureConfig>;
44
- } & import("../types.js").ChatEmbedVariantProps)> & Readonly<{
45
- "onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
46
- "onUpdate:conversationTitle"?: ((value: string | null) => any) | undefined;
47
- "onUpdate:conversationCreatorFilter"?: ((value: "all" | "mine") => any) | undefined;
48
- onMessageFeedback?: ((payload: MessageFeedbackPayload) => any) | undefined;
49
- onAction?: ((payload: ChatActionPayload) => any) | undefined;
50
- onMessageTerminal?: ((payload: ChatMessageTerminalPayload) => any) | undefined;
51
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
52
11
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
53
12
  declare const _default: typeof __VLS_export;
54
13
  export default _default;
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
2
  import { computed, ref, watch } from "vue";
3
3
  import { provideConversationCreatorFilterState } from "../../conversations/composables/conversation-creator-filter-state";
4
- const { hideSidebar = void 0, ...props } = defineProps({
4
+ const { hideSidebar = void 0, syncBrowserState = true, closeOnOutsideClick = true, ...props } = defineProps({
5
+ syncBrowserState: { type: Boolean, required: false },
5
6
  chatConfig: { type: Object, required: false },
6
7
  telaAgentBranch: { type: [String, null], required: false },
7
8
  conversationId: { type: [String, null], required: false },
@@ -35,6 +36,7 @@ const { hideSidebar = void 0, ...props } = defineProps({
35
36
  features: { type: Object, required: false },
36
37
  variant: { type: String, required: false },
37
38
  defaultOpen: { type: Boolean, required: false },
39
+ closeOnOutsideClick: { type: Boolean, required: false },
38
40
  launcherLabel: { type: String, required: false }
39
41
  });
40
42
  const emit = defineEmits(["update:conversationId", "update:conversationCreatorFilter", "update:open", "action", "messageFeedback", "messageTerminal"]);
@@ -67,6 +69,7 @@ function startWidgetConversation() {
67
69
  }
68
70
  const sharedEmbedProps = computed(() => ({
69
71
  telaAgentBranch: props.telaAgentBranch,
72
+ syncBrowserState,
70
73
  conversationId: props.conversationId,
71
74
  conversationEvents: props.conversationEvents,
72
75
  composerDisabled: props.composerDisabled,
@@ -123,7 +126,7 @@ const widgetEmbedProps = computed(() => ({
123
126
 
124
127
  <ClientOnly v-else>
125
128
  <!-- Floating: popover anchored to the launcher pill -->
126
- <TelaChatFloatingWindow v-if="variant === 'floating'" :open="isOpen" @update:open="setOpen">
129
+ <TelaChatFloatingWindow v-if="variant === 'floating'" :open="isOpen" :close-on-outside-click="closeOnOutsideClick" @update:open="setOpen">
127
130
  <template #launcher>
128
131
  <TelaChatFloatingWindowTrigger :open="isOpen" :label="props.launcherLabel" />
129
132
  </template>
@@ -1,2 +1,2 @@
1
1
  import type { Ref } from 'vue';
2
- export declare function useConversationBrowserState(conversationId: Ref<string | null>, title: Ref<string | null>): void;
2
+ export declare function useConversationBrowserState(conversationId: Ref<string | null>, title: Ref<string | null>, enabled?: () => boolean): void;
@@ -1,9 +1,11 @@
1
- export function useConversationBrowserState(conversationId, title) {
1
+ export function useConversationBrowserState(conversationId, title, enabled = () => true) {
2
2
  const route = useRoute();
3
3
  const router = useRouter();
4
- useHead(computed(() => title.value ? { title: title.value } : {}));
4
+ useHead(computed(() => enabled() && title.value ? { title: title.value } : {}));
5
5
  onMounted(() => {
6
- watch(conversationId, async (id) => {
6
+ watch([conversationId, enabled], async ([id, sync]) => {
7
+ if (!sync)
8
+ return;
7
9
  if (route.query.conversationId === (id ?? void 0) && route.query.conversation === void 0)
8
10
  return;
9
11
  const query = { ...route.query };
@@ -48,6 +48,8 @@ export type ChatSidebarConfig = {
48
48
  defaultCollapsed?: boolean;
49
49
  };
50
50
  export type ChatEmbedSharedProps = {
51
+ /** Let the embed synchronize its conversation with the browser URL and title. Defaults to true. */
52
+ syncBrowserState?: boolean;
51
53
  /** Defaults to Claude. Lightweight uses its own execution and rendering path. */
52
54
  chatConfig?: {
53
55
  harness?: ChatHarness;
@@ -182,6 +184,8 @@ export type ChatEmbedVariantProps = {
182
184
  variant?: ChatEmbedVariant;
183
185
  /** Floating variant only: start with the widget window open. */
184
186
  defaultOpen?: boolean;
187
+ /** Floating variant only: close on outside clicks. Defaults to true. */
188
+ closeOnOutsideClick?: boolean;
185
189
  /** Floating variant only: label of the launcher pill. Defaults to `'Ask Tela'`. */
186
190
  launcherLabel?: string;
187
191
  };
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ declare const questionSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ title: z.ZodString;
5
+ type: z.ZodDefault<z.ZodEnum<{
6
+ multiple: "multiple";
7
+ single: "single";
8
+ }>>;
9
+ recommendFirst: z.ZodDefault<z.ZodBoolean>;
10
+ options: z.ZodArray<z.ZodObject<{
11
+ id: z.ZodString;
12
+ label: z.ZodString;
13
+ description: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>>;
15
+ }, z.core.$strip>;
16
+ export type ChatQuestion = z.infer<typeof questionSchema>;
17
+ export type ChatQuestionAnswer = {
18
+ selected: string[];
19
+ skipped?: boolean;
20
+ };
21
+ export declare function parseChatQuestions(raw: unknown): ChatQuestion[];
22
+ export declare function formatChatAnswers(requestId: string, questions: ChatQuestion[], answers: Record<string, ChatQuestionAnswer>): string | null;
23
+ export {};
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ const optionSchema = z.object({
3
+ id: z.string().trim().min(1),
4
+ label: z.string().trim().min(1),
5
+ description: z.string().optional()
6
+ });
7
+ const questionSchema = z.object({
8
+ id: z.string().trim().min(1),
9
+ title: z.string().trim().min(1),
10
+ type: z.enum(["single", "multiple"]).default("single"),
11
+ recommendFirst: z.boolean().default(true),
12
+ options: z.array(optionSchema).min(1).max(3)
13
+ }).refine((question) => new Set(question.options.map((option) => option.id)).size === question.options.length);
14
+ const questionsSchema = z.array(questionSchema).min(1).max(3).refine((questions) => new Set(questions.map((question) => question.id)).size === questions.length);
15
+ export function parseChatQuestions(raw) {
16
+ if (typeof raw !== "string")
17
+ return [];
18
+ try {
19
+ const parsed = questionsSchema.safeParse(JSON.parse(raw));
20
+ return parsed.success ? parsed.data : [];
21
+ } catch {
22
+ return [];
23
+ }
24
+ }
25
+ export function formatChatAnswers(requestId, questions, answers) {
26
+ if (!requestId.trim() || questions.length === 0)
27
+ return null;
28
+ const lines = [];
29
+ for (const question of questions) {
30
+ const answer = answers[question.id];
31
+ if (answer?.skipped) {
32
+ lines.push(`${question.title}: Pulada pelo usu\xE1rio`);
33
+ continue;
34
+ }
35
+ const selected = question.options.filter((option) => answer?.selected.includes(option.id));
36
+ if (!selected.length || question.type === "single" && selected.length > 1)
37
+ return null;
38
+ lines.push(`${question.title}: ${selected.map((option) => option.label).join("; ")}`);
39
+ }
40
+ return `Respostas (${requestId.trim()}):
41
+ ${lines.join("\n")}`;
42
+ }
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ requestId?: string;
3
+ questions?: string;
4
+ loading?: boolean;
5
+ autoClosed?: boolean;
6
+ submitLabel?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ submitting: (value: boolean) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onSubmitting?: ((value: boolean) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -0,0 +1,154 @@
1
+ <script setup>
2
+ import { CHAT_MESSAGE_CONTEXT_KEY, CHAT_MESSAGE_SEND_KEY, CHAT_ANSWER_TARGET_KEY } from "../../types/messages";
3
+ import { formatChatAnswers, parseChatQuestions } from "../answer";
4
+ const props = defineProps({
5
+ requestId: { type: String, required: false },
6
+ questions: { type: String, required: false },
7
+ loading: { type: Boolean, required: false },
8
+ autoClosed: { type: Boolean, required: false },
9
+ submitLabel: { type: String, required: false }
10
+ });
11
+ const emit = defineEmits(["submitting"]);
12
+ const answerTarget = inject(CHAT_ANSWER_TARGET_KEY, null);
13
+ const send = inject(CHAT_MESSAGE_SEND_KEY, null);
14
+ const context = inject(CHAT_MESSAGE_CONTEXT_KEY, null);
15
+ const questions = computed(() => parseChatQuestions(props.questions));
16
+ const answers = reactive(/* @__PURE__ */ Object.create(null));
17
+ const sending = ref(false);
18
+ const sent = ref(false);
19
+ const error = ref(false);
20
+ const currentIndex = ref(0);
21
+ const expanded = ref(true);
22
+ const dismissed = ref(false);
23
+ const root = ref(null);
24
+ const keyboardTarget = ref(null);
25
+ watchEffect(() => {
26
+ keyboardTarget.value = root.value?.closest('[data-chat-body], [data-chat-message-list], [role="log"]') ?? null;
27
+ });
28
+ const question = computed(() => questions.value[currentIndex.value]);
29
+ const currentAnswer = computed(() => question.value ? formatChatAnswers(props.requestId ?? "", [question.value], answers) : null);
30
+ const isLast = computed(() => currentIndex.value === questions.value.length - 1);
31
+ const showContinue = computed(() => question.value?.type !== "single" || error.value);
32
+ watch(() => [props.requestId, props.questions], () => {
33
+ currentIndex.value = 0;
34
+ expanded.value = true;
35
+ dismissed.value = false;
36
+ sent.value = false;
37
+ error.value = false;
38
+ for (const id of Object.keys(answers))
39
+ delete answers[id];
40
+ for (const item of questions.value)
41
+ answers[item.id] = { selected: [] };
42
+ }, { immediate: true });
43
+ const interactive = computed(() => Boolean(
44
+ send && context?.role === "assistant" && context.isLatestMessage.value && !context.streaming.value && !props.loading && !props.autoClosed && !sent.value && !sending.value && !dismissed.value
45
+ ));
46
+ const message = computed(() => formatChatAnswers(props.requestId ?? "", questions.value, answers));
47
+ function selectSingle(id, value) {
48
+ if (!interactive.value)
49
+ return;
50
+ answers[id].skipped = false;
51
+ answers[id].selected = [value];
52
+ advance();
53
+ }
54
+ function skip() {
55
+ if (!interactive.value || !question.value)
56
+ return;
57
+ answers[question.value.id] = { selected: [], skipped: true };
58
+ advance();
59
+ }
60
+ function advance() {
61
+ if (!interactive.value || !currentAnswer.value)
62
+ return;
63
+ if (isLast.value)
64
+ void submit();
65
+ else
66
+ currentIndex.value++;
67
+ }
68
+ function selectMultiple(id, optionId, checked) {
69
+ if (!interactive.value)
70
+ return;
71
+ answers[id].skipped = false;
72
+ const selected = answers[id].selected.filter((value) => value !== optionId);
73
+ answers[id].selected = checked === true ? [...selected, optionId] : selected;
74
+ }
75
+ function navigateQuestion(direction) {
76
+ if (!interactive.value)
77
+ return;
78
+ if (direction === -1 && currentIndex.value > 0)
79
+ currentIndex.value--;
80
+ else if (direction === 1 && !isLast.value)
81
+ advance();
82
+ }
83
+ async function submit() {
84
+ if (!interactive.value || !send || !message.value)
85
+ return;
86
+ sending.value = true;
87
+ error.value = false;
88
+ emit("submitting", true);
89
+ try {
90
+ sent.value = await send(message.value);
91
+ error.value = !sent.value;
92
+ if (sent.value)
93
+ expanded.value = false;
94
+ } catch {
95
+ error.value = true;
96
+ } finally {
97
+ sending.value = false;
98
+ emit("submitting", false);
99
+ }
100
+ }
101
+ </script>
102
+
103
+ <template>
104
+ <div v-if="dismissed" />
105
+ <div v-else-if="context && !context.isLatestMessage.value && !sending && !error" my-12px flex="~ col" gap-4px>
106
+ <p v-for="item in questions" :key="item.id" body-14-medium text-secondary>
107
+ {{ item.title }}
108
+ </p>
109
+ </div>
110
+ <Teleport v-else :to="answerTarget" :disabled="!answerTarget || !question || !requestId">
111
+ <div ref="root" data-chat-answer>
112
+ <TelaChatAnswer
113
+ v-if="question && requestId"
114
+ v-model:expanded="expanded"
115
+ :question="question"
116
+ :selected="answers[question.id].selected"
117
+ :current="currentIndex + 1"
118
+ :total="questions.length"
119
+ :disabled="!interactive"
120
+ :submitting="sending"
121
+ :error="error ? 'N\xE3o foi poss\xEDvel enviar. Tente novamente.' : ''"
122
+ :show-navigation="currentIndex > 0"
123
+ :can-previous="currentIndex > 0"
124
+ :can-next="Boolean(currentAnswer) && !isLast"
125
+ :show-continue="showContinue"
126
+ :can-continue="Boolean(currentAnswer)"
127
+ :keyboard-target="keyboardTarget"
128
+ :labels="{
129
+ minimize: 'Minimizar respostas',
130
+ expand: 'Expandir respostas',
131
+ dismiss: 'Dispensar perguntas',
132
+ previous: 'Pergunta anterior',
133
+ next: 'Pr\xF3xima pergunta',
134
+ skip: 'Pular',
135
+ continue: isLast ? submitLabel || 'Enviar respostas' : 'Continuar',
136
+ recommended: '(recomendado)',
137
+ selected: (count) => `${count} selecionadas`,
138
+ progress: (current, total) => `${current} de ${total}`,
139
+ submitting: 'Enviando respostas\u2026'
140
+ }"
141
+ @select="selectSingle(question.id, $event)"
142
+ @toggle="(id, checked) => selectMultiple(question.id, id, checked)"
143
+ @previous="navigateQuestion(-1)"
144
+ @next="navigateQuestion(1)"
145
+ @skip="skip"
146
+ @continue="advance"
147
+ @dismiss="dismissed = true"
148
+ />
149
+ <TelaCard v-else size="sm">
150
+ <span body-14-regular text-secondary>Responda às perguntas pela conversa.</span>
151
+ </TelaCard>
152
+ </div>
153
+ </Teleport>
154
+ </template>
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ requestId?: string;
3
+ questions?: string;
4
+ loading?: boolean;
5
+ autoClosed?: boolean;
6
+ submitLabel?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ submitting: (value: boolean) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onSubmitting?: ((value: boolean) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -34,7 +34,10 @@ const mergedCustomComponents = computed(() => ({
34
34
  link: ChatVaultLink,
35
35
  ...embedConfig?.customComponents.value ?? {}
36
36
  }));
37
- const mergedCustomHtmlTags = computed(() => embedConfig?.customHtmlTags.value ?? []);
37
+ const mergedCustomHtmlTags = computed(() => {
38
+ const tags = embedConfig?.customHtmlTags.value ?? [];
39
+ return mergedCustomComponents.value["chat-answer"] ? [.../* @__PURE__ */ new Set([...tags, "chat-answer"])] : tags;
40
+ });
38
41
  const hasMessageFeedback = computed(() => embedConfig?.feedbackConfig.value != null);
39
42
  const senderMember = computed(() => {
40
43
  const createdBy = props.message.createdBy?.trim();
@@ -83,7 +83,9 @@ defineExpose({
83
83
 
84
84
  <template>
85
85
  <div
86
- class="chat-message-list"
86
+ class="chat-message-list outline-none"
87
+ data-chat-message-list
88
+ tabindex="-1"
87
89
  :data-chat-bottom-pinned="isPinned"
88
90
  :data-variant="variant ?? 'page'"
89
91
  flex="~ col"
@@ -7,6 +7,7 @@ export declare enum ChatMessageStatus {
7
7
  export type ChatMessageTerminalPayload = {
8
8
  conversationId: string;
9
9
  messageId: string;
10
+ content?: string | null;
10
11
  status: ChatMessageStatus.Completed | ChatMessageStatus.Failed;
11
12
  };
12
13
  export type ChatMessageTerminalHandler = (payload: ChatMessageTerminalPayload) => void;
@@ -16,7 +16,8 @@ export function terminalMessageTransitions(previousMessages, nextMessages) {
16
16
  transitions.push({
17
17
  conversationId: message.conversationId,
18
18
  messageId: message.id,
19
- status: "completed" /* Completed */
19
+ status: "completed" /* Completed */,
20
+ content: message.content
20
21
  });
21
22
  } else if (message.status === "failed" /* Failed */) {
22
23
  transitions.push({
@@ -2,6 +2,7 @@
2
2
  import MarkdownRender, { removeCustomComponents, setCustomComponents } from "markstream-vue";
3
3
  import { wrapCustomMarkdownComponents } from "../markdown/custom-components";
4
4
  import { STREAMING_TEXT_FADE_MS } from "../markdown/streaming-text-segments";
5
+ import { escapeSingleDollarSigns } from "../markdown/escape-dollar-signs";
5
6
  import StreamingTextNode from "../markdown/streaming-text-node.vue";
6
7
  const props = defineProps({
7
8
  content: { type: [String, null], required: false, default: "" },
@@ -14,13 +15,6 @@ const props = defineProps({
14
15
  const attrs = useAttrs();
15
16
  const instance = getCurrentInstance();
16
17
  const scopeId = `app-markdown-render-${instance?.uid ?? Math.random().toString(36).slice(2)}`;
17
- function escapeSingleDollarSigns(text) {
18
- return text.replace(/(```[\s\S]*?```|``+[^`]*``+|`[^`]*`|!?\[(?:[^\[\]]|\[[^\[\]]*\])*\]\([^)]*\))|(?<![$\\])\$(?!\$)/g, (_match, codeSpan) => {
19
- if (codeSpan)
20
- return codeSpan;
21
- return "\\$";
22
- });
23
- }
24
18
  const escapedContent = computed(() => escapeSingleDollarSigns(props.content ?? ""));
25
19
  const customMarkdownComponents = computed(() => wrapCustomMarkdownComponents(props.customComponents));
26
20
  const streamingTextActive = ref(props.typewriter);
@@ -0,0 +1 @@
1
+ export declare function escapeSingleDollarSigns(text: string): string;
@@ -0,0 +1,7 @@
1
+ export function escapeSingleDollarSigns(text) {
2
+ return text.replace(/(<\/?[a-zA-Z](?:[^"'<>]|"[^"]*"|'[^']*')*>|```[\s\S]*?```|``+[^`]*``+|`[^`]*`|!?\[(?:[^\[\]]|\[[^\[\]]*\])*\]\([^)]*\))|(?<![$\\])\$(?!\$)/g, (_match, protectedSpan) => {
3
+ if (protectedSpan)
4
+ return protectedSpan;
5
+ return "\\$";
6
+ });
7
+ }
@@ -2,6 +2,8 @@ import type { Ref } from 'vue';
2
2
  /** Stable string keys, also compatible with hosts injecting the literal key. */
3
3
  export declare const CHAT_MESSAGE_SEND_KEY = "chat-message-send";
4
4
  export declare const CHAT_MESSAGE_CONTEXT_KEY = "chat-message-context";
5
+ /** Embed-owned destination above the composer for the active Answer widget. */
6
+ export declare const CHAT_ANSWER_TARGET_KEY = "chat-answer-target";
5
7
  /**
6
8
  * Provided by the embed to components rendered inside message content
7
9
  * (`customComponents`). Sends `content` as the user through the same path as
@@ -1,2 +1,3 @@
1
1
  export const CHAT_MESSAGE_SEND_KEY = "chat-message-send";
2
2
  export const CHAT_MESSAGE_CONTEXT_KEY = "chat-message-context";
3
+ export const CHAT_ANSWER_TARGET_KEY = "chat-answer-target";
@@ -194,18 +194,3 @@ export declare const dailyUsageRowSchema: z.ZodObject<{
194
194
  userCount: z.ZodNumber;
195
195
  }, z.core.$strip>;
196
196
  export type DailyUsageRow = z.infer<typeof dailyUsageRowSchema>;
197
- export declare const dailyUsageResponseSchema: z.ZodObject<{
198
- rows: z.ZodArray<z.ZodObject<{
199
- workspaceId: z.ZodString;
200
- source: z.ZodNullable<z.ZodString>;
201
- totalTokens: z.ZodNumber;
202
- totalCost: z.ZodNumber;
203
- promptTokens: z.ZodNumber;
204
- completionTokens: z.ZodNumber;
205
- promptCost: z.ZodNumber;
206
- completionCost: z.ZodNumber;
207
- conversationCount: z.ZodNumber;
208
- userCount: z.ZodNumber;
209
- }, z.core.$strip>>;
210
- }, z.core.$strip>;
211
- export type DailyUsageResponse = z.infer<typeof dailyUsageResponseSchema>;
@@ -96,6 +96,3 @@ export const dailyUsageRowSchema = z.object({
96
96
  conversationCount: z.number(),
97
97
  userCount: z.number(),
98
98
  });
99
- export const dailyUsageResponseSchema = z.object({
100
- rows: z.array(dailyUsageRowSchema),
101
- });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "4.4.0-rc.4",
3
+ "version": "4.4.0-rc.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -18,6 +18,10 @@
18
18
  "./types/*": {
19
19
  "types": "./dist/runtime/types/*.d.ts",
20
20
  "import": "./dist/runtime/types/*.js"
21
+ },
22
+ "./components/ChatAnswer": {
23
+ "types": "./dist/runtime/messages/components/chat-answer.vue.d.ts",
24
+ "import": "./dist/runtime/messages/components/chat-answer.vue"
21
25
  }
22
26
  },
23
27
  "main": "./dist/module.mjs",
@@ -49,7 +53,7 @@
49
53
  "@iconify/vue": "^5.0.0",
50
54
  "@meistrari/auth-nuxt": "3.31.0",
51
55
  "@meistrari/logger": "^2.1.3",
52
- "@meistrari/tela-build": "^1.70.2",
56
+ "@meistrari/tela-build": "^1.74.2",
53
57
  "@sentry/nuxt": "^10.0.0",
54
58
  "@vueuse/components": "^12.8.0",
55
59
  "@vueuse/core": "^12.8.0",