@meistrari/chat-nuxt 4.10.0-rc.0 → 4.10.0-rc.10

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 (90) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +2 -0
  3. package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +9 -9
  4. package/dist/runtime/embed/components/chat-embed-inner.vue +85 -10
  5. package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +9 -9
  6. package/dist/runtime/files/components/{audio-preview.d.vue.ts → chat-file-audio.d.vue.ts} +8 -7
  7. package/dist/runtime/files/components/chat-file-audio.vue +94 -0
  8. package/dist/runtime/files/components/{image-preview.d.vue.ts → chat-file-audio.vue.d.ts} +8 -7
  9. package/dist/runtime/files/components/chat-file-card.d.vue.ts +31 -0
  10. package/dist/runtime/files/components/chat-file-card.vue +44 -0
  11. package/dist/runtime/files/components/chat-file-card.vue.d.ts +31 -0
  12. package/dist/runtime/files/components/chat-file-preview.vue +90 -196
  13. package/dist/runtime/files/composables/pdf.d.ts +5 -842
  14. package/dist/runtime/files/content-detection.js +7 -3
  15. package/dist/runtime/files/formatting.d.ts +1 -0
  16. package/dist/runtime/files/formatting.js +4 -0
  17. package/dist/runtime/files/presentation.d.ts +3 -0
  18. package/dist/runtime/files/presentation.js +24 -0
  19. package/dist/runtime/messages/answer.d.ts +0 -5
  20. package/dist/runtime/messages/answer.js +0 -18
  21. package/dist/runtime/messages/components/chat-answer.vue +4 -3
  22. package/dist/runtime/messages/components/chat-message-bubble.vue +12 -0
  23. package/dist/runtime/messages/components/chat-message-input.d.vue.ts +2 -0
  24. package/dist/runtime/messages/components/chat-message-input.vue +18 -1
  25. package/dist/runtime/messages/components/chat-message-input.vue.d.ts +2 -0
  26. package/dist/runtime/messages/components/widgets/chat-thinking-widget.vue +4 -0
  27. package/dist/runtime/messages/composables/chat.d.ts +15 -0
  28. package/dist/runtime/messages/composables/chat.js +49 -5
  29. package/dist/runtime/messages/composables/use-stick-to-bottom.d.ts +5 -0
  30. package/dist/runtime/messages/composables/use-stick-to-bottom.js +3 -0
  31. package/dist/runtime/messages/composer-draft.d.ts +16 -0
  32. package/dist/runtime/messages/composer-draft.js +61 -0
  33. package/dist/runtime/messages/composer-pending-send.d.ts +8 -0
  34. package/dist/runtime/messages/composer-pending-send.js +34 -0
  35. package/dist/runtime/rendering/components/openui/citation.d.vue.ts +9 -0
  36. package/dist/runtime/rendering/components/openui/citation.vue +13 -0
  37. package/dist/runtime/rendering/components/openui/citation.vue.d.ts +9 -0
  38. package/dist/runtime/rendering/components/openui/file-card.d.vue.ts +9 -0
  39. package/dist/runtime/rendering/components/openui/file-card.vue +15 -0
  40. package/dist/runtime/rendering/components/openui/file-card.vue.d.ts +9 -0
  41. package/dist/runtime/rendering/components/openui/image.d.vue.ts +10 -0
  42. package/dist/runtime/rendering/components/openui/image.vue +40 -0
  43. package/dist/runtime/rendering/components/openui/image.vue.d.ts +10 -0
  44. package/dist/runtime/rendering/components/openui/markdown-prose.d.vue.ts +7 -0
  45. package/dist/runtime/rendering/components/openui/markdown-prose.vue +14 -0
  46. package/dist/runtime/rendering/components/openui/markdown-prose.vue.d.ts +7 -0
  47. package/dist/runtime/rendering/components/openui-block.d.vue.ts +12 -0
  48. package/dist/runtime/rendering/components/openui-block.vue +47 -0
  49. package/dist/runtime/rendering/components/openui-block.vue.d.ts +12 -0
  50. package/dist/runtime/rendering/components/openui-reasoning-placeholder.d.vue.ts +3 -0
  51. package/dist/runtime/rendering/components/openui-reasoning-placeholder.vue +10 -0
  52. package/dist/runtime/rendering/components/openui-reasoning-placeholder.vue.d.ts +3 -0
  53. package/dist/runtime/rendering/components/renderers/artifact-display.vue +1 -1
  54. package/dist/runtime/rendering/openui/context.d.ts +7 -0
  55. package/dist/runtime/rendering/openui/context.js +2 -0
  56. package/dist/runtime/rendering/openui/markdown-link.d.ts +2 -0
  57. package/dist/runtime/rendering/openui/markdown-link.js +5 -0
  58. package/dist/runtime/rendering/openui/use-widget-prose.d.ts +2 -0
  59. package/dist/runtime/rendering/openui/use-widget-prose.js +7 -0
  60. package/dist/runtime/server/utils/chat-api-proxy.js +3 -2
  61. package/dist/runtime/shared/composables/polling.d.ts +1 -1
  62. package/dist/runtime/types/schemas/chat/chat-config.d.ts +6 -2
  63. package/dist/runtime/types/schemas/chat/chat-config.js +1 -0
  64. package/dist/runtime/types/schemas/chat/conversation-runtime.d.ts +20 -0
  65. package/dist/runtime/types/schemas/chat/conversations.d.ts +20 -0
  66. package/dist/runtime/types/schemas/chat/conversations.js +3 -0
  67. package/dist/runtime/types/schemas/chat/messages.d.ts +30 -0
  68. package/dist/runtime/types/schemas/chat/ui-language.d.ts +31 -0
  69. package/dist/runtime/types/schemas/chat/ui-language.js +13 -0
  70. package/dist/runtime/types/schemas/chat/v3.d.ts +160 -2
  71. package/dist/runtime/types/schemas/chat/v3.js +91 -5
  72. package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +40 -0
  73. package/dist/runtime/types/schemas/integrations/tela-agent-api.js +3 -0
  74. package/package.json +3 -1
  75. package/dist/runtime/files/components/audio-preview.vue +0 -52
  76. package/dist/runtime/files/components/audio-preview.vue.d.ts +0 -14
  77. package/dist/runtime/files/components/csv-preview.d.vue.ts +0 -20
  78. package/dist/runtime/files/components/csv-preview.vue +0 -157
  79. package/dist/runtime/files/components/csv-preview.vue.d.ts +0 -20
  80. package/dist/runtime/files/components/image-preview.vue +0 -54
  81. package/dist/runtime/files/components/image-preview.vue.d.ts +0 -14
  82. package/dist/runtime/files/components/pdf-preview.client.d.vue.ts +0 -20
  83. package/dist/runtime/files/components/pdf-preview.client.vue +0 -90
  84. package/dist/runtime/files/components/pdf-preview.client.vue.d.ts +0 -20
  85. package/dist/runtime/files/components/text-preview.d.vue.ts +0 -19
  86. package/dist/runtime/files/components/text-preview.vue +0 -105
  87. package/dist/runtime/files/components/text-preview.vue.d.ts +0 -19
  88. package/dist/runtime/files/components/video-preview.d.vue.ts +0 -14
  89. package/dist/runtime/files/components/video-preview.vue +0 -55
  90. package/dist/runtime/files/components/video-preview.vue.d.ts +0 -14
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.10.0-rc.0",
4
+ "version": "4.10.0-rc.10",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, mkdirSync, symlinkSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import { join, dirname, isAbsolute } from 'node:path';
4
4
  import { defineNuxtModule, createResolver, addTemplate, addTypeTemplate, addPlugin, addImportsDir, addComponent, addComponentsDir } from 'nuxt/kit';
5
+ import { registerOpenuiWidget } from '@meistrari/chat-ui-lang/nuxt';
5
6
  import { TOOL_ICON_SAFELIST } from '../dist/runtime/messages/tool-registry.js';
6
7
  export { ChatMessageStatus } from '../dist/runtime/types/message-status.js';
7
8
 
@@ -454,6 +455,7 @@ const module$1 = defineNuxtModule({
454
455
  const runtimeDirRegExp = new RegExp(runtimeDir.replace(/\\/g, "/").replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
455
456
  normalizedImports.transform.include.push(runtimeDirRegExp);
456
457
  }
458
+ registerOpenuiWidget(nuxt);
457
459
  const rc = nuxt.options.runtimeConfig;
458
460
  rc.chatApiUrl = options.chatApiUrl || rc.chatApiUrl || process.env.CHAT_API_URL || "";
459
461
  rc.public = rc.public || {};
@@ -19,27 +19,27 @@ type __VLS_Props = {
19
19
  user?: EmbedUser | null;
20
20
  features?: Partial<ChatFeatureConfig>;
21
21
  };
22
- declare var __VLS_14: {}, __VLS_92: {}, __VLS_105: {
22
+ declare var __VLS_14: {}, __VLS_92: {}, __VLS_107: {
23
23
  message: useChatAuth;
24
- }, __VLS_118: {}, __VLS_127: {}, __VLS_191: {}, __VLS_210: {
24
+ }, __VLS_120: {}, __VLS_129: {}, __VLS_193: {}, __VLS_214: {
25
25
  message: useChatAuth;
26
- }, __VLS_223: {};
26
+ }, __VLS_227: {};
27
27
  type __VLS_Slots = {} & {
28
28
  'sidebar-bottom'?: (props: typeof __VLS_14) => any;
29
29
  } & {
30
30
  'composer-bottom'?: (props: typeof __VLS_92) => any;
31
31
  } & {
32
- 'failed-message-actions'?: (props: typeof __VLS_105) => any;
32
+ 'failed-message-actions'?: (props: typeof __VLS_107) => any;
33
33
  } & {
34
- 'composer-bottom'?: (props: typeof __VLS_118) => any;
34
+ 'composer-bottom'?: (props: typeof __VLS_120) => any;
35
35
  } & {
36
- 'topbar-leading'?: (props: typeof __VLS_127) => any;
36
+ 'topbar-leading'?: (props: typeof __VLS_129) => any;
37
37
  } & {
38
- 'composer-bottom'?: (props: typeof __VLS_191) => any;
38
+ 'composer-bottom'?: (props: typeof __VLS_193) => any;
39
39
  } & {
40
- 'failed-message-actions'?: (props: typeof __VLS_210) => any;
40
+ 'failed-message-actions'?: (props: typeof __VLS_214) => any;
41
41
  } & {
42
- 'composer-bottom'?: (props: typeof __VLS_223) => any;
42
+ 'composer-bottom'?: (props: typeof __VLS_227) => any;
43
43
  };
44
44
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
45
45
  "update:conversationId": (value: string | null) => any;
@@ -2,10 +2,11 @@
2
2
  import { inject, provide, useSlots } from "vue";
3
3
  import { useChatHarnessAdapter } from "../composables/harness-adapter";
4
4
  import ChatMessageList from "../../messages/components/chat-message-list.vue";
5
+ import { shouldScrollOnComposerResize } from "../../messages/composables/use-stick-to-bottom";
5
6
  import { CHAT_COMPOSER_EXTENSION_KEY } from "../../types/composer";
6
7
  import { CHAT_MESSAGE_SEND_KEY } from "../../types/messages";
7
8
  import { useChatAuth } from "#chat-auth";
8
- import { useBreakpoints } from "@vueuse/core";
9
+ import { useBreakpoints, useResizeObserver } from "@vueuse/core";
9
10
  import { useCitationPanel } from "../../citations/composables/citation-panel";
10
11
  import { exportConversationToMarkdown } from "../../conversations/export";
11
12
  import { CHAT_MOBILE_BREAKPOINT } from "../../shared/breakpoints";
@@ -14,6 +15,8 @@ import ChatMobileShell from "./mobile/chat-mobile-shell.vue";
14
15
  import ChatSidebarToggle from "./chat-sidebar-toggle.vue";
15
16
  import ChatDetailsPanel from "./chat-details-panel.vue";
16
17
  import ChatMessageInput from "../../messages/components/chat-message-input.vue";
18
+ import { confirmPendingComposerSend, prepareComposerSend, reconcileComposerSend } from "../../messages/composer-pending-send";
19
+ import { composerDraftStorageKey, readComposerDraft, writeComposerDraft } from "../../messages/composer-draft";
17
20
  import { normalizeConversationId } from "../../conversations/scope";
18
21
  import { resolveChatFeatures } from "../feature-config";
19
22
  import { resolveConversationCreator } from "../../conversations/creator";
@@ -175,6 +178,15 @@ provide("chat-answer-target", answerTarget);
175
178
  function setAnswerTarget(el) {
176
179
  answerTarget.value = el instanceof HTMLElement ? el : null;
177
180
  }
181
+ const messageListRef = ref(null);
182
+ const composerRef = ref(null);
183
+ let composerHeights = { composer: 0, dock: 0 };
184
+ useResizeObserver(composerRef, ([entry]) => {
185
+ const next = { composer: entry?.contentRect.height ?? 0, dock: answerTarget.value?.offsetHeight ?? 0 };
186
+ if (shouldScrollOnComposerResize(composerHeights, next, Boolean(messageListRef.value?.isPinned)))
187
+ requestAnimationFrame(() => messageListRef.value?.scrollToBottom());
188
+ composerHeights = next;
189
+ });
178
190
  const messageInputRef = ref(null);
179
191
  const PROMPT_INPUT_HINTS = [
180
192
  { icon: "i-ph-at-bold", label: "Para mencionar arquivos" },
@@ -192,8 +204,55 @@ const showTopbarSidebarToggle = computed(() => sidebarCollapsible.value && sideb
192
204
  const slots = useSlots();
193
205
  const hasTopbarLeading = computed(() => showTopbarSidebarToggle.value || !!slots["topbar-leading"]);
194
206
  const drawerOpen = ref(false);
207
+ function composerKey(conversationId) {
208
+ return composerDraftStorageKey(
209
+ { userId: authUser.value?.id, workspaceId: activeOrganization.value?.id, telaAgentId: embedConfig?.telaAgentId.value, conversationScope: embedConfig?.conversationScope?.value },
210
+ conversationId
211
+ );
212
+ }
213
+ const draftStorageKey = computed(() => composerKey(activeConversationId.value));
214
+ let unconfirmedDraft = null;
215
+ function releaseUnconfirmedDraft(draft) {
216
+ if (readComposerDraft(draft.key) === draft.text)
217
+ writeComposerDraft(draft.key, "");
218
+ }
219
+ function confirmComposerDraft(send) {
220
+ confirmPendingComposerSend(composerKey(send.conversationId), send.messageId);
221
+ if (!unconfirmedDraft || unconfirmedDraft.content !== send.content)
222
+ return;
223
+ if (unconfirmedDraft.conversationId !== null && unconfirmedDraft.conversationId !== send.conversationId)
224
+ return;
225
+ releaseUnconfirmedDraft(unconfirmedDraft);
226
+ unconfirmedDraft = null;
227
+ }
228
+ function prepareComposerRequest(send) {
229
+ if (!unconfirmedDraft || unconfirmedDraft.content !== send.body.content)
230
+ return send.body.messageId;
231
+ return prepareComposerSend(unconfirmedDraft.key, unconfirmedDraft.text, send.body);
232
+ }
233
+ function moveUnconfirmedDraft(conversationId) {
234
+ if (!unconfirmedDraft || unconfirmedDraft.conversationId !== null)
235
+ return;
236
+ const key = draftStorageKey.value;
237
+ if (key === unconfirmedDraft.key || !unconfirmedDraft.text) {
238
+ unconfirmedDraft = { ...unconfirmedDraft, key, conversationId };
239
+ return;
240
+ }
241
+ if (!writeComposerDraft(key, unconfirmedDraft.text)) {
242
+ unconfirmedDraft = { ...unconfirmedDraft, conversationId };
243
+ return;
244
+ }
245
+ releaseUnconfirmedDraft(unconfirmedDraft);
246
+ unconfirmedDraft = { ...unconfirmedDraft, key, conversationId };
247
+ }
195
248
  function restoreQueuedSend(send) {
196
- messageInputRef.value?.restoreDraft(send);
249
+ const input = messageInputRef.value;
250
+ if (!input)
251
+ return;
252
+ input.restoreDraft(send);
253
+ if (unconfirmedDraft && unconfirmedDraft.key !== draftStorageKey.value && readComposerDraft(draftStorageKey.value))
254
+ releaseUnconfirmedDraft(unconfirmedDraft);
255
+ unconfirmedDraft = null;
197
256
  }
198
257
  const { isDragging, setupDropZone } = useFileDrop({
199
258
  onDrop: (files) => {
@@ -234,7 +293,10 @@ const {
234
293
  } = createChat({
235
294
  outboundQueueEnabled: computed(() => resolvedFeatures.value.chatOutboundQueue),
236
295
  onQueuedSendCancelled: restoreQueuedSend,
237
- onQueuedSendFailed: restoreQueuedSend
296
+ onQueuedSendFailed: restoreQueuedSend,
297
+ onSendConfirmed: confirmComposerDraft,
298
+ onSendPrepared: prepareComposerRequest,
299
+ onMessagesReceived: ({ conversationId, messages: messages2 }) => reconcileComposerSend(composerKey(conversationId), messages2)
238
300
  });
239
301
  const agentWake = harnessAdapter?.requiresSandboxWake === false ? null : useAgentWake();
240
302
  watch(chatError, (message) => {
@@ -534,6 +596,12 @@ function claimableWakeSessionId() {
534
596
  async function handleSend(content, files, agentInputs) {
535
597
  const prepared = composerExtension?.prepareSend?.(content);
536
598
  const outbound = prepared?.content ?? content;
599
+ unconfirmedDraft = {
600
+ key: draftStorageKey.value,
601
+ conversationId: activeConversationId.value,
602
+ content: outbound,
603
+ text: readComposerDraft(draftStorageKey.value)
604
+ };
537
605
  const resolvedAgentInputs = resolveTelaAgentInputs(agentInputs, embedConfig?.telaAgentInputs?.value);
538
606
  const sent = await dispatchMessage(outbound, files, resolvedAgentInputs);
539
607
  if (sent)
@@ -559,6 +627,7 @@ async function dispatchMessage(outbound, files, resolvedAgentInputs) {
559
627
  const grouped = groupId && await moveConversationToGroup(conversation2.id, groupId);
560
628
  adoptConversation(grouped ? { ...conversation2, groupId } : conversation2);
561
629
  updateConversationId(conversation2.id);
630
+ moveUnconfirmedDraft(conversation2.id);
562
631
  }
563
632
  const conversation = currentConversation.value;
564
633
  if (!conversation)
@@ -826,7 +895,7 @@ async function handleDelete() {
826
895
  icon="i-ph-arrow-clockwise"
827
896
  @click="reconnect"
828
897
  >
829
- Reconectar
898
+ {{ outboundOccupied ? "Reenviar" : "Reconectar" }}
830
899
  </TelaButton>
831
900
  </div>
832
901
  <!-- Sidebar -->
@@ -1020,7 +1089,8 @@ async function handleDelete() {
1020
1089
  ref="messageInputRef"
1021
1090
  v-model:selected-model="selectedModel"
1022
1091
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1023
- :loading="composerLocked"
1092
+ :draft-storage-key="draftStorageKey"
1093
+ :loading="loading || composerLocked"
1024
1094
  :disabled="composerDisabled"
1025
1095
  :send-blocked="sendBlocked"
1026
1096
  :creator-name="currentUser?.name ?? void 0"
@@ -1053,6 +1123,7 @@ async function handleDelete() {
1053
1123
 
1054
1124
  <ChatMessageList
1055
1125
  :key="currentConversation?.id ?? 'draft'"
1126
+ ref="messageListRef"
1056
1127
  :messages="messages"
1057
1128
  :events="displayConversationEvents"
1058
1129
  :streaming-active-message-id="streamingActiveMessageId"
@@ -1069,14 +1140,15 @@ async function handleDelete() {
1069
1140
  <slot name="failed-message-actions" v-bind="slotProps" />
1070
1141
  </template>
1071
1142
  </ChatMessageList>
1072
- <div data-chat-composer flex="~ col" gap-12px min-h-0>
1143
+ <div ref="composerRef" data-chat-composer flex="~ col" gap-12px min-h-0>
1073
1144
  <div :ref="setAnswerTarget" data-chat-answer-dock class="empty:hidden max-h-[50dvh] overflow-y-auto" />
1074
1145
  <ChatMessageInput
1075
1146
  ref="messageInputRef"
1076
1147
  v-model:selected-model="selectedModel"
1077
1148
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1149
+ :draft-storage-key="draftStorageKey"
1078
1150
  class="!p-0"
1079
- :loading="composerLocked"
1151
+ :loading="loading || composerLocked"
1080
1152
  :disabled="composerDisabled"
1081
1153
  :send-blocked="sendBlocked"
1082
1154
  :creator-name="currentUser?.name ?? void 0"
@@ -1230,7 +1302,8 @@ async function handleDelete() {
1230
1302
  ref="messageInputRef"
1231
1303
  v-model:selected-model="selectedModel"
1232
1304
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1233
- :loading="composerLocked"
1305
+ :draft-storage-key="draftStorageKey"
1306
+ :loading="loading || composerLocked"
1234
1307
  :disabled="composerDisabled"
1235
1308
  :send-blocked="sendBlocked"
1236
1309
  :creator-name="currentUser?.name ?? void 0"
@@ -1266,6 +1339,7 @@ async function handleDelete() {
1266
1339
  >
1267
1340
  <ChatMessageList
1268
1341
  :key="currentConversation?.id ?? 'draft'"
1342
+ ref="messageListRef"
1269
1343
  :messages="messages"
1270
1344
  :events="displayConversationEvents"
1271
1345
  :streaming-active-message-id="streamingActiveMessageId"
@@ -1282,14 +1356,15 @@ async function handleDelete() {
1282
1356
  <slot name="failed-message-actions" v-bind="slotProps" />
1283
1357
  </template>
1284
1358
  </ChatMessageList>
1285
- <div data-chat-composer flex="~ col" gap-12px min-h-0>
1359
+ <div ref="composerRef" data-chat-composer flex="~ col" gap-12px min-h-0>
1286
1360
  <div :ref="setAnswerTarget" data-chat-answer-dock class="empty:hidden max-h-[50dvh] overflow-y-auto" />
1287
1361
  <ChatMessageInput
1288
1362
  ref="messageInputRef"
1289
1363
  v-model:selected-model="selectedModel"
1290
1364
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1365
+ :draft-storage-key="draftStorageKey"
1291
1366
  class="!p-0"
1292
- :loading="composerLocked"
1367
+ :loading="loading || composerLocked"
1293
1368
  :disabled="composerDisabled"
1294
1369
  :send-blocked="sendBlocked"
1295
1370
  :creator-name="currentUser?.name ?? void 0"
@@ -19,27 +19,27 @@ type __VLS_Props = {
19
19
  user?: EmbedUser | null;
20
20
  features?: Partial<ChatFeatureConfig>;
21
21
  };
22
- declare var __VLS_14: {}, __VLS_92: {}, __VLS_105: {
22
+ declare var __VLS_14: {}, __VLS_92: {}, __VLS_107: {
23
23
  message: useChatAuth;
24
- }, __VLS_118: {}, __VLS_127: {}, __VLS_191: {}, __VLS_210: {
24
+ }, __VLS_120: {}, __VLS_129: {}, __VLS_193: {}, __VLS_214: {
25
25
  message: useChatAuth;
26
- }, __VLS_223: {};
26
+ }, __VLS_227: {};
27
27
  type __VLS_Slots = {} & {
28
28
  'sidebar-bottom'?: (props: typeof __VLS_14) => any;
29
29
  } & {
30
30
  'composer-bottom'?: (props: typeof __VLS_92) => any;
31
31
  } & {
32
- 'failed-message-actions'?: (props: typeof __VLS_105) => any;
32
+ 'failed-message-actions'?: (props: typeof __VLS_107) => any;
33
33
  } & {
34
- 'composer-bottom'?: (props: typeof __VLS_118) => any;
34
+ 'composer-bottom'?: (props: typeof __VLS_120) => any;
35
35
  } & {
36
- 'topbar-leading'?: (props: typeof __VLS_127) => any;
36
+ 'topbar-leading'?: (props: typeof __VLS_129) => any;
37
37
  } & {
38
- 'composer-bottom'?: (props: typeof __VLS_191) => any;
38
+ 'composer-bottom'?: (props: typeof __VLS_193) => any;
39
39
  } & {
40
- 'failed-message-actions'?: (props: typeof __VLS_210) => any;
40
+ 'failed-message-actions'?: (props: typeof __VLS_214) => any;
41
41
  } & {
42
- 'composer-bottom'?: (props: typeof __VLS_223) => any;
42
+ 'composer-bottom'?: (props: typeof __VLS_227) => any;
43
43
  };
44
44
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
45
45
  "update:conversationId": (value: string | null) => any;
@@ -1,14 +1,15 @@
1
+ import type { ChatFileMenuItem } from './chat-file-card.vue.js';
1
2
  type __VLS_Props = {
2
- url: string;
3
- name?: string;
4
- clickable?: boolean;
3
+ url?: string;
4
+ iconSrc: string;
5
+ name: string;
6
+ meta: string;
7
+ menuItems: ChatFileMenuItem[];
5
8
  };
6
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
- click: () => any;
8
- loaded: () => any;
10
+ open: () => any;
9
11
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
- onClick?: (() => any) | undefined;
11
- onLoaded?: (() => any) | undefined;
12
+ onOpen?: (() => any) | undefined;
12
13
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
14
  declare const _default: typeof __VLS_export;
14
15
  export default _default;
@@ -0,0 +1,94 @@
1
+ <script setup>
2
+ import ChatFileCard from "./chat-file-card.vue";
3
+ import { formatPlaybackTime } from "../formatting";
4
+ const props = defineProps({
5
+ url: { type: String, required: false },
6
+ iconSrc: { type: String, required: true },
7
+ name: { type: String, required: true },
8
+ meta: { type: String, required: true },
9
+ menuItems: { type: Array, required: true }
10
+ });
11
+ const emit = defineEmits(["open"]);
12
+ const player = ref();
13
+ const playing = ref(false);
14
+ const currentTime = ref(0);
15
+ const duration = ref(0);
16
+ const failed = ref(false);
17
+ const progress = computed(() => duration.value > 0 ? currentTime.value / duration.value * 100 : 0);
18
+ function togglePlayback() {
19
+ const audio = player.value;
20
+ if (!audio)
21
+ return;
22
+ if (audio.paused)
23
+ void audio.play();
24
+ else
25
+ audio.pause();
26
+ }
27
+ function seek(event) {
28
+ const audio = player.value;
29
+ if (!audio || duration.value <= 0)
30
+ return;
31
+ audio.currentTime = Number(event.target.value) / 100 * duration.value;
32
+ }
33
+ watch(() => props.url, () => {
34
+ playing.value = false;
35
+ currentTime.value = 0;
36
+ duration.value = 0;
37
+ failed.value = false;
38
+ });
39
+ </script>
40
+
41
+ <template>
42
+ <ChatFileCard
43
+ :icon-src="iconSrc"
44
+ :name="name"
45
+ :meta="failed ? 'Falha ao carregar \xE1udio' : meta"
46
+ :menu-items="menuItems"
47
+ :disabled="!url"
48
+ @open="emit('open')"
49
+ >
50
+ <template #actions>
51
+ <TelaIconButton
52
+ :icon="playing ? 'i-ph-pause-fill' : 'i-ph-play-fill'"
53
+ color="secondary"
54
+ size="xs"
55
+ :aria-label="playing ? 'Pausar \xE1udio' : 'Reproduzir \xE1udio'"
56
+ :disabled="!url || failed"
57
+ @click="togglePlayback"
58
+ />
59
+ </template>
60
+ <div flex items-center gap-12px px-12px py-10px border-t-0.5px border>
61
+ <input
62
+ type="range"
63
+ min="0"
64
+ max="100"
65
+ step="0.1"
66
+ :value="progress"
67
+ :disabled="!url || failed || duration <= 0"
68
+ aria-label="Posição do áudio"
69
+ flex-1 cursor-pointer text-primary
70
+ class="chat-file-audio__track"
71
+ @input="seek"
72
+ >
73
+ <span body-12-regular text-secondary tabular-nums shrink-0>
74
+ {{ formatPlaybackTime(currentTime) }} / {{ formatPlaybackTime(duration) }}
75
+ </span>
76
+ </div>
77
+ <audio
78
+ v-if="url"
79
+ ref="player"
80
+ :src="url"
81
+ preload="metadata"
82
+ @play="playing = true"
83
+ @pause="playing = false"
84
+ @ended="playing = false"
85
+ @timeupdate="currentTime = player?.currentTime ?? 0"
86
+ @loadedmetadata="duration = player?.duration ?? 0"
87
+ @error="failed = true"
88
+ />
89
+ </ChatFileCard>
90
+ </template>
91
+
92
+ <style scoped>
93
+ .chat-file-audio__track{accent-color:currentColor;height:4px}
94
+ </style>
@@ -1,14 +1,15 @@
1
+ import type { ChatFileMenuItem } from './chat-file-card.vue.js';
1
2
  type __VLS_Props = {
2
- url: string;
3
- maxHeight?: number;
4
- clickable?: boolean;
3
+ url?: string;
4
+ iconSrc: string;
5
+ name: string;
6
+ meta: string;
7
+ menuItems: ChatFileMenuItem[];
5
8
  };
6
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
- click: () => any;
8
- loaded: () => any;
10
+ open: () => any;
9
11
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
- onClick?: (() => any) | undefined;
11
- onLoaded?: (() => any) | undefined;
12
+ onOpen?: (() => any) | undefined;
12
13
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
14
  declare const _default: typeof __VLS_export;
14
15
  export default _default;
@@ -0,0 +1,31 @@
1
+ export interface ChatFileMenuItem {
2
+ label: string;
3
+ icon: string;
4
+ click: () => void;
5
+ }
6
+ type __VLS_Props = {
7
+ iconSrc: string;
8
+ name: string;
9
+ meta: string;
10
+ menuItems: ChatFileMenuItem[];
11
+ disabled?: boolean;
12
+ };
13
+ declare var __VLS_1: {}, __VLS_14: {};
14
+ type __VLS_Slots = {} & {
15
+ actions?: (props: typeof __VLS_1) => any;
16
+ } & {
17
+ default?: (props: typeof __VLS_14) => any;
18
+ };
19
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
+ open: () => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
+ onOpen?: (() => any) | undefined;
23
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
25
+ declare const _default: typeof __VLS_export;
26
+ export default _default;
27
+ type __VLS_WithSlots<T, S> = T & {
28
+ new (): {
29
+ $slots: S;
30
+ };
31
+ };
@@ -0,0 +1,44 @@
1
+ <script setup>
2
+ defineProps({
3
+ iconSrc: { type: String, required: true },
4
+ name: { type: String, required: true },
5
+ meta: { type: String, required: true },
6
+ menuItems: { type: Array, required: true },
7
+ disabled: { type: Boolean, required: false }
8
+ });
9
+ const emit = defineEmits(["open"]);
10
+ </script>
11
+
12
+ <template>
13
+ <div w-full max-w-480px rounded-12px border-0.5px border bg overflow-hidden data-chat-file="card">
14
+ <div flex items-center gap-12px p-12px>
15
+ <button
16
+ type="button"
17
+ flex items-center gap-12px flex-1 min-w-0 text-left
18
+ :class="disabled ? 'cursor-default' : 'cursor-pointer'"
19
+ :disabled="disabled"
20
+ :aria-label="`Abrir ${name}`"
21
+ @click="emit('open')"
22
+ >
23
+ <span size-40px rounded-8px bg-subtle flex items-center justify-center shrink-0>
24
+ <img :src="iconSrc" alt="" size-20px>
25
+ </span>
26
+ <span flex flex-col min-w-0>
27
+ <span body-14-medium text-primary truncate :title="name">{{ name }}</span>
28
+ <span body-12-regular text-secondary truncate>{{ meta }}</span>
29
+ </span>
30
+ </button>
31
+ <slot name="actions" />
32
+ <TelaDropdownMenu :items="menuItems" align="end">
33
+ <TelaIconButton
34
+ icon="i-ph-dots-three"
35
+ color="secondary"
36
+ size="3xs"
37
+ aria-label="Ações do arquivo"
38
+ :disabled="disabled"
39
+ />
40
+ </TelaDropdownMenu>
41
+ </div>
42
+ <slot />
43
+ </div>
44
+ </template>
@@ -0,0 +1,31 @@
1
+ export interface ChatFileMenuItem {
2
+ label: string;
3
+ icon: string;
4
+ click: () => void;
5
+ }
6
+ type __VLS_Props = {
7
+ iconSrc: string;
8
+ name: string;
9
+ meta: string;
10
+ menuItems: ChatFileMenuItem[];
11
+ disabled?: boolean;
12
+ };
13
+ declare var __VLS_1: {}, __VLS_14: {};
14
+ type __VLS_Slots = {} & {
15
+ actions?: (props: typeof __VLS_1) => any;
16
+ } & {
17
+ default?: (props: typeof __VLS_14) => any;
18
+ };
19
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
+ open: () => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
+ onOpen?: (() => any) | undefined;
23
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
25
+ declare const _default: typeof __VLS_export;
26
+ export default _default;
27
+ type __VLS_WithSlots<T, S> = T & {
28
+ new (): {
29
+ $slots: S;
30
+ };
31
+ };