@meistrari/chat-nuxt 4.10.0-rc.8 → 4.10.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.
Files changed (71) hide show
  1. package/README.md +8 -6
  2. package/dist/module.d.mts +0 -7
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +1 -2
  5. package/dist/runtime/conversations/components/chat-conversation-export-modal.d.vue.ts +10 -0
  6. package/dist/runtime/conversations/components/chat-conversation-export-modal.vue +142 -0
  7. package/dist/runtime/conversations/components/chat-conversation-export-modal.vue.d.ts +10 -0
  8. package/dist/runtime/conversations/components/chat-conversation-list.vue +8 -1
  9. package/dist/runtime/conversations/components/chat-export-document.d.vue.ts +7 -0
  10. package/dist/runtime/conversations/components/chat-export-document.vue +26 -0
  11. package/dist/runtime/conversations/components/chat-export-document.vue.d.ts +7 -0
  12. package/dist/runtime/conversations/components/chat-export-file.d.vue.ts +9 -0
  13. package/dist/runtime/conversations/components/chat-export-file.vue +12 -0
  14. package/dist/runtime/conversations/components/chat-export-file.vue.d.ts +9 -0
  15. package/dist/runtime/conversations/components/chat-export-message.d.vue.ts +7 -0
  16. package/dist/runtime/conversations/components/chat-export-message.vue +84 -0
  17. package/dist/runtime/conversations/components/chat-export-message.vue.d.ts +7 -0
  18. package/dist/runtime/conversations/components/chat-export-openui.d.vue.ts +9 -0
  19. package/dist/runtime/conversations/components/chat-export-openui.vue +19 -0
  20. package/dist/runtime/conversations/components/chat-export-openui.vue.d.ts +9 -0
  21. package/dist/runtime/conversations/components/chat-export-questions.d.vue.ts +19 -0
  22. package/dist/runtime/conversations/components/chat-export-questions.vue +22 -0
  23. package/dist/runtime/conversations/components/chat-export-questions.vue.d.ts +19 -0
  24. package/dist/runtime/conversations/composables/conversation-list.d.ts +2 -1
  25. package/dist/runtime/conversations/composables/conversation-list.js +4 -13
  26. package/dist/runtime/conversations/export-document.d.ts +1 -0
  27. package/dist/runtime/conversations/export-document.js +154 -0
  28. package/dist/runtime/conversations/export.d.ts +4 -0
  29. package/dist/runtime/conversations/export.js +69 -4
  30. package/dist/runtime/embed/components/chat-embed-inner.vue +93 -8
  31. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.d.vue.ts +2 -2
  32. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue.d.ts +2 -2
  33. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +5 -4
  34. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +2 -0
  35. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +5 -4
  36. package/dist/runtime/embed/execution-controls.d.ts +2 -1
  37. package/dist/runtime/embed/execution-controls.js +2 -2
  38. package/dist/runtime/messages/components/chat-execution-mode-selector.vue +10 -6
  39. package/dist/runtime/messages/components/chat-message-bubble.vue +7 -0
  40. package/dist/runtime/messages/components/chat-message-input.d.vue.ts +2 -0
  41. package/dist/runtime/messages/components/chat-message-input.vue +18 -1
  42. package/dist/runtime/messages/components/chat-message-input.vue.d.ts +2 -0
  43. package/dist/runtime/messages/composables/chat.d.ts +15 -0
  44. package/dist/runtime/messages/composables/chat.js +59 -8
  45. package/dist/runtime/messages/composer-draft.d.ts +16 -0
  46. package/dist/runtime/messages/composer-draft.js +61 -0
  47. package/dist/runtime/messages/composer-pending-send.d.ts +8 -0
  48. package/dist/runtime/messages/composer-pending-send.js +34 -0
  49. package/dist/runtime/messages/conversation-timeline.js +1 -1
  50. package/dist/runtime/server/utils/chat-api-proxy.d.ts +1 -1
  51. package/dist/runtime/server/utils/chat-api-proxy.js +2 -4
  52. package/dist/runtime/types/composer.d.ts +6 -0
  53. package/dist/runtime/types/schemas/chat/v3.d.ts +83 -0
  54. package/dist/runtime/types/schemas/chat/v3.js +28 -1
  55. package/dist/runtime/types/schemas/integrations/agent-events.d.ts +16 -0
  56. package/dist/runtime/types/schemas/integrations/agent-events.js +12 -0
  57. package/dist/runtime/types/schemas/integrations/slack-apps.d.ts +53 -0
  58. package/dist/runtime/types/schemas/integrations/slack-apps.js +6 -0
  59. package/dist/runtime/types/schemas/integrations/slack-identity.d.ts +26 -0
  60. package/dist/runtime/types/schemas/integrations/slack-identity.js +23 -0
  61. package/dist/runtime/types/schemas/integrations/tela-api.d.ts +11 -0
  62. package/dist/runtime/types/schemas/integrations/tela-api.js +8 -0
  63. package/dist/runtime/types/schemas/integrations/tela-auth.d.ts +39 -0
  64. package/dist/runtime/types/schemas/integrations/tela-auth.js +12 -0
  65. package/dist/runtime/types/schemas/slack/apps.d.ts +144 -0
  66. package/dist/runtime/types/schemas/slack/apps.js +53 -0
  67. package/dist/runtime/types/schemas/slack/identities.d.ts +58 -0
  68. package/dist/runtime/types/schemas/slack/identities.js +19 -0
  69. package/dist/runtime/types/schemas/slack/installations.d.ts +39 -0
  70. package/dist/runtime/types/schemas/slack/installations.js +18 -0
  71. package/package.json +2 -2
@@ -0,0 +1,154 @@
1
+ export async function captureConversationDocument(root, signal, includeImages) {
2
+ const deadline = Date.now() + 15e3;
3
+ const resourceSignal = AbortSignal.any([signal, AbortSignal.timeout(15e3)]);
4
+ async function waitForResource(promise) {
5
+ resourceSignal.throwIfAborted();
6
+ let onAbort = () => {
7
+ };
8
+ try {
9
+ return await Promise.race([
10
+ promise,
11
+ new Promise((_resolve, reject) => {
12
+ onAbort = () => reject(resourceSignal.reason);
13
+ resourceSignal.addEventListener("abort", onAbort, { once: true });
14
+ })
15
+ ]);
16
+ } finally {
17
+ resourceSignal.removeEventListener("abort", onAbort);
18
+ }
19
+ }
20
+ await waitForResource(document.fonts.ready);
21
+ let previousHtml = "";
22
+ while (true) {
23
+ signal.throwIfAborted();
24
+ if (!root.isConnected)
25
+ throw new Error("A prepara\xE7\xE3o da conversa foi interrompida.");
26
+ if (Date.now() > deadline)
27
+ throw new Error("Alguns elementos n\xE3o terminaram de carregar. Tente exportar novamente.");
28
+ const pending = root.querySelector('[data-chat-file="loading"], [data-export-loading], [aria-busy="true"]');
29
+ const html = root.innerHTML;
30
+ if (!pending && html === previousHtml)
31
+ break;
32
+ previousHtml = html;
33
+ await new Promise((resolve) => setTimeout(resolve, 100));
34
+ }
35
+ const copy = root.cloneNode(true);
36
+ const originals = [root, ...root.querySelectorAll("*")];
37
+ const copies = [copy, ...copy.querySelectorAll("*")];
38
+ const properties = [
39
+ "display",
40
+ "color",
41
+ "background-color",
42
+ "font-size",
43
+ "font-weight",
44
+ "font-style",
45
+ "font-family",
46
+ "line-height",
47
+ "text-align",
48
+ "text-decoration",
49
+ "white-space",
50
+ "vertical-align",
51
+ "width",
52
+ "max-width",
53
+ "padding-top",
54
+ "padding-right",
55
+ "padding-bottom",
56
+ "padding-left",
57
+ "margin-top",
58
+ "margin-right",
59
+ "margin-bottom",
60
+ "margin-left",
61
+ "border-top",
62
+ "border-right",
63
+ "border-bottom",
64
+ "border-left",
65
+ "border-radius",
66
+ "border-collapse",
67
+ "flex-direction",
68
+ "flex-wrap",
69
+ "flex-grow",
70
+ "flex-shrink",
71
+ "flex-basis",
72
+ "align-items",
73
+ "justify-content",
74
+ "gap",
75
+ "grid-template-columns",
76
+ "grid-template-rows",
77
+ "fill",
78
+ "stroke",
79
+ "stroke-width"
80
+ ];
81
+ for (let index = 0; index < originals.length; index++) {
82
+ signal.throwIfAborted();
83
+ const original = originals[index];
84
+ const clone = copies[index];
85
+ if (!(clone instanceof HTMLElement || clone instanceof SVGElement))
86
+ continue;
87
+ const computed = getComputedStyle(original);
88
+ clone.removeAttribute("class");
89
+ clone.removeAttribute("id");
90
+ clone.removeAttribute("style");
91
+ for (const property of properties)
92
+ clone.style.setProperty(property, computed.getPropertyValue(property));
93
+ if (original instanceof HTMLElement && original.children.length === 0 && original.textContent?.trim() && !original.style.width)
94
+ clone.style.setProperty("width", "auto");
95
+ clone.style.setProperty("overflow", "visible");
96
+ clone.style.setProperty("max-width", "100%");
97
+ if (!original.textContent?.trim() && original.children.length === 0)
98
+ clone.style.setProperty("height", computed.height);
99
+ if (computed.whiteSpace === "nowrap")
100
+ clone.style.setProperty("white-space", "normal");
101
+ for (const attribute of [...clone.attributes]) {
102
+ if (attribute.name.startsWith("on") || ["tabindex", "autofocus", "contenteditable"].includes(attribute.name))
103
+ clone.removeAttribute(attribute.name);
104
+ }
105
+ if (original instanceof HTMLImageElement) {
106
+ if (includeImages) {
107
+ const response = await fetch(original.currentSrc || original.src, { signal: resourceSignal });
108
+ if (!response.ok)
109
+ throw new Error("N\xE3o foi poss\xEDvel carregar uma imagem da conversa.");
110
+ const blob = await response.blob();
111
+ if (blob.size > 8 * 1024 * 1024)
112
+ throw new Error("Uma imagem excede o tamanho permitido para exporta\xE7\xE3o.");
113
+ const imageUrl = URL.createObjectURL(blob);
114
+ try {
115
+ const image = new Image();
116
+ image.src = imageUrl;
117
+ await waitForResource(image.decode());
118
+ const canvas = document.createElement("canvas");
119
+ canvas.width = image.naturalWidth;
120
+ canvas.height = image.naturalHeight;
121
+ if (canvas.width * canvas.height > 16e6)
122
+ throw new Error("Uma imagem excede a resolu\xE7\xE3o permitida para exporta\xE7\xE3o.");
123
+ const context = canvas.getContext("2d");
124
+ if (!context)
125
+ throw new Error("N\xE3o foi poss\xEDvel preparar as imagens para exporta\xE7\xE3o.");
126
+ context.drawImage(image, 0, 0);
127
+ clone.setAttribute("src", canvas.toDataURL("image/png"));
128
+ } finally {
129
+ URL.revokeObjectURL(imageUrl);
130
+ }
131
+ } else {
132
+ clone.removeAttribute("src");
133
+ }
134
+ clone.removeAttribute("srcset");
135
+ clone.removeAttribute("loading");
136
+ }
137
+ if (original instanceof HTMLInputElement || original instanceof HTMLTextAreaElement || original instanceof HTMLSelectElement) {
138
+ const span = document.createElement("span");
139
+ span.textContent = original instanceof HTMLInputElement && ["checkbox", "radio"].includes(original.type) ? original.checked ? "\u2611" : "\u2610" : original.value || original.getAttribute("placeholder") || "";
140
+ span.setAttribute("style", clone.getAttribute("style") ?? "");
141
+ clone.replaceWith(span);
142
+ }
143
+ }
144
+ for (const element of copy.querySelectorAll('script, style, iframe, object, embed, audio, video, [aria-hidden="true"]'))
145
+ element.remove();
146
+ for (const button of copy.querySelectorAll("button")) {
147
+ const span = document.createElement("span");
148
+ span.setAttribute("style", button.getAttribute("style") ?? "");
149
+ span.append(...button.childNodes);
150
+ button.replaceWith(span);
151
+ }
152
+ signal.throwIfAborted();
153
+ return copy.outerHTML;
154
+ }
@@ -1,2 +1,6 @@
1
1
  import type { ConversationWithMessages } from '../types/schemas/chat/conversations.js';
2
2
  export declare function exportConversationToMarkdown(conversation: ConversationWithMessages): void;
3
+ export type ConversationExportFormat = 'md' | 'txt' | 'pdf';
4
+ export declare function conversationExportFilename(title: string, format: ConversationExportFormat): string;
5
+ export declare function renderedConversationToText(html: string): string;
6
+ export declare function downloadConversationFile(blob: Blob, filename: string): void;
@@ -1,3 +1,4 @@
1
+ import { Parser } from "htmlparser2";
1
2
  export function exportConversationToMarkdown(conversation) {
2
3
  const formatDate = (date) => {
3
4
  const d = new Date(date);
@@ -30,14 +31,78 @@ export function exportConversationToMarkdown(conversation) {
30
31
  lines.push("");
31
32
  }
32
33
  const content = lines.join("\n");
33
- const sanitizedTitle = conversation.title.replace(/[<>:"/\\|?*]/g, "_");
34
34
  const blob = new Blob([content], { type: "text/markdown;charset=utf-8" });
35
+ downloadConversationFile(blob, conversationExportFilename(conversation.title, "md"));
36
+ }
37
+ export function conversationExportFilename(title, format) {
38
+ return `${title.trim().replace(/[<>:"/\\|?*\p{Cc}]/gu, "_").slice(0, 150) || "Conversa"}.${format}`;
39
+ }
40
+ export function renderedConversationToText(html) {
41
+ const blocks = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6", "p", "div", "section", "article", "header", "footer", "table", "ul", "ol", "pre", "blockquote", "figure"]);
42
+ const ignored = /* @__PURE__ */ new Set(["script", "style", "svg"]);
43
+ const links = [];
44
+ let text = "";
45
+ let ignoredDepth = 0;
46
+ const whitespace = [];
47
+ let cells = 0;
48
+ const parser = new Parser({
49
+ onopentag(name, attributes) {
50
+ if (ignoredDepth || ignored.has(name)) {
51
+ ignoredDepth++;
52
+ return;
53
+ }
54
+ if (blocks.has(name))
55
+ text += "\n\n";
56
+ const style = attributes.style?.match(/(?:^|;)\s*white-space\s*:\s*([\w-]+)/i)?.[1]?.toLowerCase();
57
+ whitespace.push(style ?? (name === "pre" ? "pre" : whitespace.at(-1) ?? "normal"));
58
+ if (name === "tr") {
59
+ cells = 0;
60
+ text += "\n";
61
+ }
62
+ if (name === "td" || name === "th") {
63
+ if (cells++)
64
+ text += " ";
65
+ }
66
+ if (name === "li")
67
+ text += "\n- ";
68
+ if (name === "br" || name === "hr")
69
+ text += "\n";
70
+ if (name === "img" && attributes.alt)
71
+ text += `[${attributes.alt}]`;
72
+ if (name === "a")
73
+ links.push(attributes.href ?? "");
74
+ },
75
+ ontext(value) {
76
+ if (!ignoredDepth) {
77
+ const mode = whitespace.at(-1);
78
+ text += mode === "pre" || mode === "pre-wrap" || mode === "break-spaces" ? value : mode === "pre-line" ? value.replace(/[^\S\r\n]+/g, " ") : value.replace(/\s+/g, " ");
79
+ }
80
+ },
81
+ onclosetag(name) {
82
+ if (ignoredDepth) {
83
+ ignoredDepth--;
84
+ return;
85
+ }
86
+ whitespace.pop();
87
+ if (name === "a") {
88
+ const href = links.pop();
89
+ if (href && /^(?:https?:|mailto:|vault:|cite:)/.test(href))
90
+ text += ` (${href})`;
91
+ }
92
+ if (blocks.has(name))
93
+ text += "\n\n";
94
+ }
95
+ }, { decodeEntities: true });
96
+ parser.end(html);
97
+ return text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
98
+ }
99
+ export function downloadConversationFile(blob, filename) {
35
100
  const url = URL.createObjectURL(blob);
36
101
  const link = document.createElement("a");
37
102
  link.href = url;
38
- link.download = `${sanitizedTitle}.md`;
103
+ link.download = filename;
39
104
  document.body.appendChild(link);
40
105
  link.click();
41
- document.body.removeChild(link);
42
- URL.revokeObjectURL(url);
106
+ link.remove();
107
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
43
108
  }
@@ -15,6 +15,8 @@ import ChatMobileShell from "./mobile/chat-mobile-shell.vue";
15
15
  import ChatSidebarToggle from "./chat-sidebar-toggle.vue";
16
16
  import ChatDetailsPanel from "./chat-details-panel.vue";
17
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";
18
20
  import { normalizeConversationId } from "../../conversations/scope";
19
21
  import { resolveChatFeatures } from "../feature-config";
20
22
  import { resolveConversationCreator } from "../../conversations/creator";
@@ -196,14 +198,71 @@ const resolvedSuggestions = computed(
196
198
  function applySuggestion(prompt) {
197
199
  messageInputRef.value?.setDraft(prompt);
198
200
  }
201
+ watch([isReady, messageInputRef, activeConversationId], ([ready, input]) => {
202
+ if (!ready || !input || activeConversationId.value)
203
+ return;
204
+ const draft = composerExtension?.takeInitialDraft?.();
205
+ if (!draft)
206
+ return;
207
+ if (draft.executionProfile)
208
+ selectExecutionProfile(draft.executionProfile);
209
+ input.setDraft(draft.content);
210
+ }, { flush: "post" });
199
211
  const breakpoints = useBreakpoints({ mobile: CHAT_MOBILE_BREAKPOINT });
200
212
  const isMobile = breakpoints.smaller("mobile");
201
213
  const showTopbarSidebarToggle = computed(() => sidebarCollapsible.value && sidebarToggle.value === "topbar" && !props.hideSidebar && !isMobile.value && !props.compact);
202
214
  const slots = useSlots();
203
215
  const hasTopbarLeading = computed(() => showTopbarSidebarToggle.value || !!slots["topbar-leading"]);
204
216
  const drawerOpen = ref(false);
217
+ function composerKey(conversationId) {
218
+ return composerDraftStorageKey(
219
+ { userId: authUser.value?.id, workspaceId: activeOrganization.value?.id, telaAgentId: embedConfig?.telaAgentId.value, conversationScope: embedConfig?.conversationScope?.value },
220
+ conversationId
221
+ );
222
+ }
223
+ const draftStorageKey = computed(() => composerKey(activeConversationId.value));
224
+ let unconfirmedDraft = null;
225
+ function releaseUnconfirmedDraft(draft) {
226
+ if (readComposerDraft(draft.key) === draft.text)
227
+ writeComposerDraft(draft.key, "");
228
+ }
229
+ function confirmComposerDraft(send) {
230
+ confirmPendingComposerSend(composerKey(send.conversationId), send.messageId);
231
+ if (!unconfirmedDraft || unconfirmedDraft.content !== send.content)
232
+ return;
233
+ if (unconfirmedDraft.conversationId !== null && unconfirmedDraft.conversationId !== send.conversationId)
234
+ return;
235
+ releaseUnconfirmedDraft(unconfirmedDraft);
236
+ unconfirmedDraft = null;
237
+ }
238
+ function prepareComposerRequest(send) {
239
+ if (!unconfirmedDraft || unconfirmedDraft.content !== send.body.content)
240
+ return send.body.messageId;
241
+ return prepareComposerSend(unconfirmedDraft.key, unconfirmedDraft.text, send.body);
242
+ }
243
+ function moveUnconfirmedDraft(conversationId) {
244
+ if (!unconfirmedDraft || unconfirmedDraft.conversationId !== null)
245
+ return;
246
+ const key = draftStorageKey.value;
247
+ if (key === unconfirmedDraft.key || !unconfirmedDraft.text) {
248
+ unconfirmedDraft = { ...unconfirmedDraft, key, conversationId };
249
+ return;
250
+ }
251
+ if (!writeComposerDraft(key, unconfirmedDraft.text)) {
252
+ unconfirmedDraft = { ...unconfirmedDraft, conversationId };
253
+ return;
254
+ }
255
+ releaseUnconfirmedDraft(unconfirmedDraft);
256
+ unconfirmedDraft = { ...unconfirmedDraft, key, conversationId };
257
+ }
205
258
  function restoreQueuedSend(send) {
206
- messageInputRef.value?.restoreDraft(send);
259
+ const input = messageInputRef.value;
260
+ if (!input)
261
+ return;
262
+ input.restoreDraft(send);
263
+ if (unconfirmedDraft && unconfirmedDraft.key !== draftStorageKey.value && readComposerDraft(draftStorageKey.value))
264
+ releaseUnconfirmedDraft(unconfirmedDraft);
265
+ unconfirmedDraft = null;
207
266
  }
208
267
  const { isDragging, setupDropZone } = useFileDrop({
209
268
  onDrop: (files) => {
@@ -244,7 +303,10 @@ const {
244
303
  } = createChat({
245
304
  outboundQueueEnabled: computed(() => resolvedFeatures.value.chatOutboundQueue),
246
305
  onQueuedSendCancelled: restoreQueuedSend,
247
- onQueuedSendFailed: restoreQueuedSend
306
+ onQueuedSendFailed: restoreQueuedSend,
307
+ onSendConfirmed: confirmComposerDraft,
308
+ onSendPrepared: prepareComposerRequest,
309
+ onMessagesReceived: ({ conversationId, messages: messages2 }) => reconcileComposerSend(composerKey(conversationId), messages2)
248
310
  });
249
311
  const agentWake = harnessAdapter?.requiresSandboxWake === false ? null : useAgentWake();
250
312
  watch(chatError, (message) => {
@@ -544,6 +606,12 @@ function claimableWakeSessionId() {
544
606
  async function handleSend(content, files, agentInputs) {
545
607
  const prepared = composerExtension?.prepareSend?.(content);
546
608
  const outbound = prepared?.content ?? content;
609
+ unconfirmedDraft = {
610
+ key: draftStorageKey.value,
611
+ conversationId: activeConversationId.value,
612
+ content: outbound,
613
+ text: readComposerDraft(draftStorageKey.value)
614
+ };
547
615
  const resolvedAgentInputs = resolveTelaAgentInputs(agentInputs, embedConfig?.telaAgentInputs?.value);
548
616
  const sent = await dispatchMessage(outbound, files, resolvedAgentInputs);
549
617
  if (sent)
@@ -555,8 +623,13 @@ async function dispatchMessage(outbound, files, resolvedAgentInputs) {
555
623
  const executionSettings = { model: selectedModel.value, reasoningEffort: selectedReasoningEffort.value };
556
624
  if (props.sendBlocked)
557
625
  return false;
626
+ const requestedConversationId = activeConversationId.value;
627
+ if (requestedConversationId && currentConversation.value?.id !== requestedConversationId)
628
+ return null;
558
629
  let claimedWakeSessionId;
559
630
  const sent = await sendMessage(outbound, files.length > 0 ? files : void 0, resolvedAgentInputs, async (signal) => {
631
+ if (requestedConversationId && (activeConversationId.value !== requestedConversationId || currentConversation.value?.id !== requestedConversationId))
632
+ throw new Error("A conversa mudou. Abra o hist\xF3rico novamente.");
560
633
  if (!activeConversationId.value || !currentConversation.value) {
561
634
  const conversation2 = await createConversation({
562
635
  ...executionSettings,
@@ -569,14 +642,20 @@ async function dispatchMessage(outbound, files, resolvedAgentInputs) {
569
642
  const grouped = groupId && await moveConversationToGroup(conversation2.id, groupId);
570
643
  adoptConversation(grouped ? { ...conversation2, groupId } : conversation2);
571
644
  updateConversationId(conversation2.id);
645
+ moveUnconfirmedDraft(conversation2.id);
572
646
  }
573
647
  const conversation = currentConversation.value;
574
- if (!conversation)
648
+ if (!conversation || requestedConversationId && (activeConversationId.value !== requestedConversationId || conversation.id !== requestedConversationId))
575
649
  throw new Error("Erro ao preparar conversa");
576
650
  if (!agentWake || chatApi.conversationV2Enabled)
577
651
  return { executionSettings };
652
+ const assertConversationStillOpen = () => {
653
+ if (activeConversationId.value !== conversation.id || currentConversation.value?.id !== conversation.id)
654
+ throw new Error("A conversa mudou. Abra o hist\xF3rico novamente.");
655
+ };
578
656
  if (conversation.threadSessionId && resolvedFeatures.value.chatOutboundQueue) {
579
657
  const wake = await agentWake.ensureReady(conversation.threadSessionId, executionSettings.model);
658
+ assertConversationStillOpen();
580
659
  if (wake.status !== "ready") {
581
660
  if (wake.wokenSandbox && !wake.wokenSandbox.rewakeable)
582
661
  return { defer: true, executionSettings };
@@ -589,6 +668,7 @@ async function dispatchMessage(outbound, files, resolvedAgentInputs) {
589
668
  if (agentWake.state.value === "idle")
590
669
  await agentWake.wake(void 0, executionSettings.model);
591
670
  }
671
+ assertConversationStillOpen();
592
672
  claimedWakeSessionId = claimableWakeSessionId();
593
673
  if (!conversation.threadSessionId && !claimedWakeSessionId)
594
674
  throw new Error("N\xE3o foi poss\xEDvel acordar o agente. Tente novamente.");
@@ -836,7 +916,7 @@ async function handleDelete() {
836
916
  icon="i-ph-arrow-clockwise"
837
917
  @click="reconnect"
838
918
  >
839
- Reconectar
919
+ {{ outboundOccupied ? "Reenviar" : "Reconectar" }}
840
920
  </TelaButton>
841
921
  </div>
842
922
  <!-- Sidebar -->
@@ -943,6 +1023,7 @@ async function handleDelete() {
943
1023
  :generating-title="generatingTitle"
944
1024
  :has-messages="hasMessages"
945
1025
  :has-pending-message="composerLocked"
1026
+ :draft-storage-key="draftStorageKey"
946
1027
  :hide-settings="hideSettings"
947
1028
  :hide-sidebar="hideSidebar"
948
1029
  :is-editing="isEditing"
@@ -1030,7 +1111,8 @@ async function handleDelete() {
1030
1111
  ref="messageInputRef"
1031
1112
  v-model:selected-model="selectedModel"
1032
1113
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1033
- :loading="composerLocked"
1114
+ :draft-storage-key="draftStorageKey"
1115
+ :loading="loading || composerLocked"
1034
1116
  :disabled="composerDisabled"
1035
1117
  :send-blocked="sendBlocked"
1036
1118
  :creator-name="currentUser?.name ?? void 0"
@@ -1086,8 +1168,9 @@ async function handleDelete() {
1086
1168
  ref="messageInputRef"
1087
1169
  v-model:selected-model="selectedModel"
1088
1170
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1171
+ :draft-storage-key="draftStorageKey"
1089
1172
  class="!p-0"
1090
- :loading="composerLocked"
1173
+ :loading="loading || composerLocked"
1091
1174
  :disabled="composerDisabled"
1092
1175
  :send-blocked="sendBlocked"
1093
1176
  :creator-name="currentUser?.name ?? void 0"
@@ -1241,7 +1324,8 @@ async function handleDelete() {
1241
1324
  ref="messageInputRef"
1242
1325
  v-model:selected-model="selectedModel"
1243
1326
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1244
- :loading="composerLocked"
1327
+ :draft-storage-key="draftStorageKey"
1328
+ :loading="loading || composerLocked"
1245
1329
  :disabled="composerDisabled"
1246
1330
  :send-blocked="sendBlocked"
1247
1331
  :creator-name="currentUser?.name ?? void 0"
@@ -1300,8 +1384,9 @@ async function handleDelete() {
1300
1384
  ref="messageInputRef"
1301
1385
  v-model:selected-model="selectedModel"
1302
1386
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1387
+ :draft-storage-key="draftStorageKey"
1303
1388
  class="!p-0"
1304
- :loading="composerLocked"
1389
+ :loading="loading || composerLocked"
1305
1390
  :disabled="composerDisabled"
1306
1391
  :send-blocked="sendBlocked"
1307
1392
  :creator-name="currentUser?.name ?? void 0"
@@ -10,12 +10,12 @@ type __VLS_Props = {
10
10
  userImage?: string;
11
11
  };
12
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
13
- close: () => any;
14
13
  select: (id: string) => any;
14
+ close: () => any;
15
15
  newConvo: (groupId?: string | null | undefined) => any;
16
16
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
- onClose?: (() => any) | undefined;
18
17
  onSelect?: ((id: string) => any) | undefined;
18
+ onClose?: (() => any) | undefined;
19
19
  onNewConvo?: ((groupId?: string | null | undefined) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
21
  declare const _default: typeof __VLS_export;
@@ -10,12 +10,12 @@ type __VLS_Props = {
10
10
  userImage?: string;
11
11
  };
12
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
13
- close: () => any;
14
13
  select: (id: string) => any;
14
+ close: () => any;
15
15
  newConvo: (groupId?: string | null | undefined) => any;
16
16
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
- onClose?: (() => any) | undefined;
18
17
  onSelect?: ((id: string) => any) | undefined;
18
+ onClose?: (() => any) | undefined;
19
19
  onNewConvo?: ((groupId?: string | null | undefined) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
21
  declare const _default: typeof __VLS_export;
@@ -22,6 +22,7 @@ type __VLS_Props = {
22
22
  conversationsLoading?: boolean;
23
23
  conversationFiles: ConversationFile[];
24
24
  drawerOpen: boolean;
25
+ draftStorageKey?: string | null;
25
26
  editTitle: string;
26
27
  filesCount: number;
27
28
  generatingTitle?: boolean;
@@ -64,15 +65,15 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
64
65
  "update:selectedReasoningEffort": (value: any) => any;
65
66
  } & {
66
67
  delete: () => any;
68
+ cancel: () => any;
67
69
  rename: () => any;
68
70
  duplicate: () => any;
69
71
  export: () => any;
70
72
  copyLink: () => any;
71
- cancel: () => any;
72
73
  debug: () => any;
73
74
  selectConversation: (id: string) => any;
74
- settings: () => any;
75
75
  send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
76
+ settings: () => any;
76
77
  selectExecutionProfile: (profile: ChatExecutionProfile) => any;
77
78
  tabChange: (tab: TopbarTab) => any;
78
79
  newConversation: (groupId?: string | null | undefined) => any;
@@ -86,15 +87,15 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
86
87
  "update:workspaceSheetOpen": (value: boolean) => any;
87
88
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
88
89
  onDelete?: (() => any) | undefined;
90
+ onCancel?: (() => any) | undefined;
89
91
  onRename?: (() => any) | undefined;
90
92
  onDuplicate?: (() => any) | undefined;
91
93
  onExport?: (() => any) | undefined;
92
94
  onCopyLink?: (() => any) | undefined;
93
- onCancel?: (() => any) | undefined;
94
95
  onDebug?: (() => any) | undefined;
95
96
  onSelectConversation?: ((id: string) => any) | undefined;
96
- onSettings?: (() => any) | undefined;
97
97
  onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
98
+ onSettings?: (() => any) | undefined;
98
99
  onSelectExecutionProfile?: ((profile: ChatExecutionProfile) => any) | undefined;
99
100
  "onUpdate:selectedModel"?: ((value: string | null) => any) | undefined;
100
101
  "onUpdate:selectedReasoningEffort"?: ((value: any) => any) | undefined;
@@ -27,6 +27,7 @@ const props = defineProps({
27
27
  conversationsLoading: { type: Boolean, required: false },
28
28
  conversationFiles: { type: Array, required: true },
29
29
  drawerOpen: { type: Boolean, required: true },
30
+ draftStorageKey: { type: [String, null], required: false },
30
31
  editTitle: { type: String, required: true },
31
32
  filesCount: { type: Number, required: true },
32
33
  generatingTitle: { type: Boolean, required: false },
@@ -235,6 +236,7 @@ function startNewConversationFromOutdatedSettings() {
235
236
  v-model:selected-model="selectedModel"
236
237
  v-model:selected-reasoning-effort="selectedReasoningEffort"
237
238
  :loading="hasPendingMessage"
239
+ :draft-storage-key="draftStorageKey"
238
240
  :creator-name="currentUser?.name ?? void 0"
239
241
  :creator-email="currentUser?.email ?? void 0"
240
242
  :creator-image="currentUser?.image ?? void 0"
@@ -22,6 +22,7 @@ type __VLS_Props = {
22
22
  conversationsLoading?: boolean;
23
23
  conversationFiles: ConversationFile[];
24
24
  drawerOpen: boolean;
25
+ draftStorageKey?: string | null;
25
26
  editTitle: string;
26
27
  filesCount: number;
27
28
  generatingTitle?: boolean;
@@ -64,15 +65,15 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
64
65
  "update:selectedReasoningEffort": (value: any) => any;
65
66
  } & {
66
67
  delete: () => any;
68
+ cancel: () => any;
67
69
  rename: () => any;
68
70
  duplicate: () => any;
69
71
  export: () => any;
70
72
  copyLink: () => any;
71
- cancel: () => any;
72
73
  debug: () => any;
73
74
  selectConversation: (id: string) => any;
74
- settings: () => any;
75
75
  send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
76
+ settings: () => any;
76
77
  selectExecutionProfile: (profile: ChatExecutionProfile) => any;
77
78
  tabChange: (tab: TopbarTab) => any;
78
79
  newConversation: (groupId?: string | null | undefined) => any;
@@ -86,15 +87,15 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
86
87
  "update:workspaceSheetOpen": (value: boolean) => any;
87
88
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
88
89
  onDelete?: (() => any) | undefined;
90
+ onCancel?: (() => any) | undefined;
89
91
  onRename?: (() => any) | undefined;
90
92
  onDuplicate?: (() => any) | undefined;
91
93
  onExport?: (() => any) | undefined;
92
94
  onCopyLink?: (() => any) | undefined;
93
- onCancel?: (() => any) | undefined;
94
95
  onDebug?: (() => any) | undefined;
95
96
  onSelectConversation?: ((id: string) => any) | undefined;
96
- onSettings?: (() => any) | undefined;
97
97
  onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
98
+ onSettings?: (() => any) | undefined;
98
99
  onSelectExecutionProfile?: ((profile: ChatExecutionProfile) => any) | undefined;
99
100
  "onUpdate:selectedModel"?: ((value: string | null) => any) | undefined;
100
101
  "onUpdate:selectedReasoningEffort"?: ((value: any) => any) | undefined;
@@ -11,6 +11,7 @@ export type ChatExecutionControls = {
11
11
  profiles: {
12
12
  default: ChatExecutionProfile;
13
13
  deep: ChatExecutionProfile;
14
+ intensive?: ChatExecutionProfile;
14
15
  };
15
16
  } | {
16
17
  mode: 'advanced';
@@ -23,4 +24,4 @@ export declare function resolveChatExecutionMode(profiles: Extract<ChatExecution
23
24
  }>['profiles'], selection: {
24
25
  model: string | null;
25
26
  reasoningEffort?: ChatReasoningEffort | null;
26
- }): 'default' | 'deep' | 'custom';
27
+ }): 'default' | 'deep' | 'intensive' | 'custom';
@@ -9,9 +9,9 @@ export function resolveChatExecutionControls(controls, features) {
9
9
  };
10
10
  }
11
11
  export function resolveChatExecutionMode(profiles, selection) {
12
- for (const mode of ["default", "deep"]) {
12
+ for (const mode of ["default", "deep", "intensive"]) {
13
13
  const profile = profiles[mode];
14
- if (profile.model === selection.model && profile.reasoningEffort === (selection.reasoningEffort ?? null))
14
+ if (profile && profile.model === selection.model && profile.reasoningEffort === (selection.reasoningEffort ?? null))
15
15
  return mode;
16
16
  }
17
17
  return "custom";
@@ -7,15 +7,19 @@ const props = defineProps({
7
7
  disabled: { type: Boolean, required: false }
8
8
  });
9
9
  const emit = defineEmits(["select"]);
10
- const options = [
10
+ const options = computed(() => [
11
11
  { value: "default", label: "Padr\xE3o", description: "Para as tarefas do dia a dia." },
12
- { value: "deep", label: "Aprofundar", description: "Dedica mais racioc\xEDnio a tarefas complexas. Pode demorar mais." }
13
- ];
12
+ { value: "deep", label: "Aprofundado", description: "Dedica mais racioc\xEDnio a tarefas complexas. Pode demorar mais." },
13
+ ...props.profiles.intensive ? [{ value: "intensive", label: "Intensivo", description: "Maior esfor\xE7o de racioc\xEDnio para quest\xF5es complexas." }] : []
14
+ ]);
14
15
  const mode = computed(() => resolveChatExecutionMode(props.profiles, props));
15
- const label = computed(() => mode.value === "custom" ? "Personalizado" : options.find((option) => option.value === mode.value).label);
16
+ const label = computed(() => mode.value === "custom" ? "Personalizado" : options.value.find((option) => option.value === mode.value).label);
16
17
  function select(value) {
17
- if (!props.disabled && (value === "default" || value === "deep"))
18
- emit("select", { ...props.profiles[value] });
18
+ if (props.disabled || value !== "default" && value !== "deep" && value !== "intensive")
19
+ return;
20
+ const profile = props.profiles[value];
21
+ if (profile)
22
+ emit("select", { ...profile });
19
23
  }
20
24
  </script>
21
25
 
@@ -338,6 +338,13 @@ const attachedFiles = computed(() => {
338
338
  <span v-if="showTtft && formattedTtft" flex items-center gap-4px>
339
339
  {{ formattedTtft }}
340
340
  </span>
341
+ <TelaCopyButton
342
+ :content="message.content"
343
+ variant="ghost"
344
+ size="sm"
345
+ aria-label="Copiar mensagem"
346
+ class="text-icon-tertiary hover:text-primary [&_.i-ph-copy]:!opacity-100"
347
+ />
341
348
  <ChatMessageFeedback v-if="hasMessageFeedback" :message="message" />
342
349
  <ChatMessageActions
343
350
  v-if="message.role === 'assistant' && message.status === 'completed' && !streamActive"