@meistrari/chat-nuxt 4.10.0-rc.2 → 4.10.0-rc.21

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 (135) hide show
  1. package/README.md +8 -6
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -0
  4. package/dist/runtime/conversations/components/chat-conversation-export-modal.d.vue.ts +10 -0
  5. package/dist/runtime/conversations/components/chat-conversation-export-modal.vue +126 -0
  6. package/dist/runtime/conversations/components/chat-conversation-export-modal.vue.d.ts +10 -0
  7. package/dist/runtime/conversations/components/chat-conversation-list.vue +8 -1
  8. package/dist/runtime/{rendering/components/openui/bar-chart.d.vue.ts → conversations/components/chat-export-document.d.vue.ts} +2 -5
  9. package/dist/runtime/conversations/components/chat-export-document.vue +26 -0
  10. package/dist/runtime/{rendering/components/openui/bar-chart.vue.d.ts → conversations/components/chat-export-document.vue.d.ts} +2 -5
  11. package/dist/runtime/{rendering/components/openui/checkbox.d.vue.ts → conversations/components/chat-export-file.d.vue.ts} +1 -1
  12. package/dist/runtime/conversations/components/chat-export-file.vue +12 -0
  13. package/dist/runtime/{rendering/components/openui/checkbox.vue.d.ts → conversations/components/chat-export-file.vue.d.ts} +1 -1
  14. package/dist/runtime/{rendering/components/openui/buttons.d.vue.ts → conversations/components/chat-export-message.d.vue.ts} +2 -3
  15. package/dist/runtime/conversations/components/chat-export-message.vue +84 -0
  16. package/dist/runtime/{rendering/components/openui/bar.vue.d.ts → conversations/components/chat-export-message.vue.d.ts} +2 -5
  17. package/dist/runtime/{rendering/components/openui/bar.d.vue.ts → conversations/components/chat-export-openui.d.vue.ts} +3 -4
  18. package/dist/runtime/conversations/components/chat-export-openui.vue +19 -0
  19. package/dist/runtime/conversations/components/chat-export-openui.vue.d.ts +9 -0
  20. package/dist/runtime/{rendering/components/openui/callout.d.vue.ts → conversations/components/chat-export-questions.d.vue.ts} +12 -4
  21. package/dist/runtime/conversations/components/chat-export-questions.vue +22 -0
  22. package/dist/runtime/{rendering/components/openui/callout.vue.d.ts → conversations/components/chat-export-questions.vue.d.ts} +12 -4
  23. package/dist/runtime/conversations/composables/conversation-list.d.ts +2 -1
  24. package/dist/runtime/conversations/composables/conversation-list.js +4 -13
  25. package/dist/runtime/conversations/export-document.d.ts +1 -0
  26. package/dist/runtime/conversations/export-document.js +154 -0
  27. package/dist/runtime/conversations/export.d.ts +4 -0
  28. package/dist/runtime/conversations/export.js +69 -4
  29. package/dist/runtime/embed/components/chat-embed-inner.vue +81 -7
  30. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +3 -2
  31. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +2 -0
  32. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +3 -2
  33. package/dist/runtime/embed/execution-controls.d.ts +2 -1
  34. package/dist/runtime/embed/execution-controls.js +2 -2
  35. package/dist/runtime/messages/components/chat-execution-mode-selector.vue +10 -6
  36. package/dist/runtime/messages/components/chat-message-bubble.vue +7 -0
  37. package/dist/runtime/messages/components/chat-message-input.d.vue.ts +2 -0
  38. package/dist/runtime/messages/components/chat-message-input.vue +18 -1
  39. package/dist/runtime/messages/components/chat-message-input.vue.d.ts +2 -0
  40. package/dist/runtime/messages/composables/chat.d.ts +15 -0
  41. package/dist/runtime/messages/composables/chat.js +49 -5
  42. package/dist/runtime/messages/composer-draft.d.ts +16 -0
  43. package/dist/runtime/messages/composer-draft.js +61 -0
  44. package/dist/runtime/messages/composer-pending-send.d.ts +8 -0
  45. package/dist/runtime/messages/composer-pending-send.js +34 -0
  46. package/dist/runtime/messages/conversation-timeline.js +1 -1
  47. package/dist/runtime/rendering/components/openui/{markdown.d.vue.ts → markdown-prose.d.vue.ts} +1 -3
  48. package/dist/runtime/rendering/components/openui/{markdown.vue → markdown-prose.vue} +3 -3
  49. package/dist/runtime/rendering/components/openui/{markdown.vue.d.ts → markdown-prose.vue.d.ts} +1 -3
  50. package/dist/runtime/rendering/components/openui-block.vue +26 -52
  51. package/dist/runtime/rendering/openui/context.d.ts +2 -17
  52. package/dist/runtime/rendering/openui/context.js +0 -2
  53. package/dist/runtime/server/utils/chat-api-proxy.d.ts +1 -1
  54. package/dist/runtime/server/utils/chat-api-proxy.js +3 -2
  55. package/dist/runtime/types/composer.d.ts +6 -0
  56. package/dist/runtime/types/schemas/chat/chat-config.d.ts +6 -2
  57. package/dist/runtime/types/schemas/chat/chat-config.js +1 -0
  58. package/dist/runtime/types/schemas/chat/conversation-runtime.d.ts +8 -0
  59. package/dist/runtime/types/schemas/chat/conversations.d.ts +8 -0
  60. package/dist/runtime/types/schemas/chat/messages.d.ts +12 -0
  61. package/dist/runtime/types/schemas/chat/ui-language.d.ts +19 -2
  62. package/dist/runtime/types/schemas/chat/ui-language.js +8 -3
  63. package/dist/runtime/types/schemas/chat/v3.d.ts +160 -2
  64. package/dist/runtime/types/schemas/chat/v3.js +91 -5
  65. package/dist/runtime/types/schemas/integrations/slack-apps.d.ts +53 -0
  66. package/dist/runtime/types/schemas/integrations/slack-apps.js +6 -0
  67. package/dist/runtime/types/schemas/integrations/slack-identity.d.ts +26 -0
  68. package/dist/runtime/types/schemas/integrations/slack-identity.js +23 -0
  69. package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +40 -0
  70. package/dist/runtime/types/schemas/integrations/tela-agent-api.js +3 -0
  71. package/dist/runtime/types/schemas/integrations/tela-api.d.ts +11 -0
  72. package/dist/runtime/types/schemas/integrations/tela-api.js +8 -0
  73. package/dist/runtime/types/schemas/integrations/tela-auth.d.ts +39 -0
  74. package/dist/runtime/types/schemas/integrations/tela-auth.js +12 -0
  75. package/dist/runtime/types/schemas/slack/apps.d.ts +144 -0
  76. package/dist/runtime/types/schemas/slack/apps.js +53 -0
  77. package/dist/runtime/types/schemas/slack/identities.d.ts +58 -0
  78. package/dist/runtime/types/schemas/slack/identities.js +19 -0
  79. package/dist/runtime/types/schemas/slack/installations.d.ts +39 -0
  80. package/dist/runtime/types/schemas/slack/installations.js +18 -0
  81. package/package.json +2 -2
  82. package/dist/runtime/rendering/components/openui/bar-chart.vue +0 -39
  83. package/dist/runtime/rendering/components/openui/bar.vue +0 -12
  84. package/dist/runtime/rendering/components/openui/button.d.vue.ts +0 -11
  85. package/dist/runtime/rendering/components/openui/button.vue +0 -30
  86. package/dist/runtime/rendering/components/openui/button.vue.d.ts +0 -11
  87. package/dist/runtime/rendering/components/openui/buttons.vue +0 -14
  88. package/dist/runtime/rendering/components/openui/buttons.vue.d.ts +0 -8
  89. package/dist/runtime/rendering/components/openui/callout.vue +0 -27
  90. package/dist/runtime/rendering/components/openui/card.d.vue.ts +0 -9
  91. package/dist/runtime/rendering/components/openui/card.vue +0 -19
  92. package/dist/runtime/rendering/components/openui/card.vue.d.ts +0 -9
  93. package/dist/runtime/rendering/components/openui/checkbox.vue +0 -26
  94. package/dist/runtime/rendering/components/openui/col.d.vue.ts +0 -10
  95. package/dist/runtime/rendering/components/openui/col.vue +0 -16
  96. package/dist/runtime/rendering/components/openui/col.vue.d.ts +0 -10
  97. package/dist/runtime/rendering/components/openui/form.d.vue.ts +0 -10
  98. package/dist/runtime/rendering/components/openui/form.vue +0 -60
  99. package/dist/runtime/rendering/components/openui/form.vue.d.ts +0 -10
  100. package/dist/runtime/rendering/components/openui/input.d.vue.ts +0 -10
  101. package/dist/runtime/rendering/components/openui/input.vue +0 -23
  102. package/dist/runtime/rendering/components/openui/input.vue.d.ts +0 -10
  103. package/dist/runtime/rendering/components/openui/metric.d.vue.ts +0 -11
  104. package/dist/runtime/rendering/components/openui/metric.vue +0 -20
  105. package/dist/runtime/rendering/components/openui/metric.vue.d.ts +0 -11
  106. package/dist/runtime/rendering/components/openui/option.d.vue.ts +0 -10
  107. package/dist/runtime/rendering/components/openui/option.vue +0 -12
  108. package/dist/runtime/rendering/components/openui/option.vue.d.ts +0 -10
  109. package/dist/runtime/rendering/components/openui/question.d.vue.ts +0 -11
  110. package/dist/runtime/rendering/components/openui/question.vue +0 -15
  111. package/dist/runtime/rendering/components/openui/question.vue.d.ts +0 -11
  112. package/dist/runtime/rendering/components/openui/questions.d.vue.ts +0 -9
  113. package/dist/runtime/rendering/components/openui/questions.vue +0 -91
  114. package/dist/runtime/rendering/components/openui/questions.vue.d.ts +0 -9
  115. package/dist/runtime/rendering/components/openui/select.d.vue.ts +0 -10
  116. package/dist/runtime/rendering/components/openui/select.vue +0 -27
  117. package/dist/runtime/rendering/components/openui/select.vue.d.ts +0 -10
  118. package/dist/runtime/rendering/components/openui/stack.d.vue.ts +0 -9
  119. package/dist/runtime/rendering/components/openui/stack.vue +0 -18
  120. package/dist/runtime/rendering/components/openui/stack.vue.d.ts +0 -9
  121. package/dist/runtime/rendering/components/openui/table.d.vue.ts +0 -9
  122. package/dist/runtime/rendering/components/openui/table.vue +0 -41
  123. package/dist/runtime/rendering/components/openui/table.vue.d.ts +0 -9
  124. package/dist/runtime/rendering/openui/actions.d.ts +0 -15
  125. package/dist/runtime/rendering/openui/actions.js +0 -40
  126. package/dist/runtime/rendering/openui/form-fields.d.ts +0 -10
  127. package/dist/runtime/rendering/openui/form-fields.js +0 -30
  128. package/dist/runtime/rendering/openui/program.d.ts +0 -15
  129. package/dist/runtime/rendering/openui/program.js +0 -74
  130. package/dist/runtime/rendering/openui/registry.d.ts +0 -5
  131. package/dist/runtime/rendering/openui/registry.js +0 -44
  132. package/dist/runtime/rendering/openui/render.d.ts +0 -21
  133. package/dist/runtime/rendering/openui/render.js +0 -45
  134. package/dist/runtime/rendering/openui/use-openui-context.d.ts +0 -2
  135. package/dist/runtime/rendering/openui/use-openui-context.js +0 -8
@@ -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)
@@ -569,6 +637,7 @@ async function dispatchMessage(outbound, files, resolvedAgentInputs) {
569
637
  const grouped = groupId && await moveConversationToGroup(conversation2.id, groupId);
570
638
  adoptConversation(grouped ? { ...conversation2, groupId } : conversation2);
571
639
  updateConversationId(conversation2.id);
640
+ moveUnconfirmedDraft(conversation2.id);
572
641
  }
573
642
  const conversation = currentConversation.value;
574
643
  if (!conversation)
@@ -836,7 +905,7 @@ async function handleDelete() {
836
905
  icon="i-ph-arrow-clockwise"
837
906
  @click="reconnect"
838
907
  >
839
- Reconectar
908
+ {{ outboundOccupied ? "Reenviar" : "Reconectar" }}
840
909
  </TelaButton>
841
910
  </div>
842
911
  <!-- Sidebar -->
@@ -943,6 +1012,7 @@ async function handleDelete() {
943
1012
  :generating-title="generatingTitle"
944
1013
  :has-messages="hasMessages"
945
1014
  :has-pending-message="composerLocked"
1015
+ :draft-storage-key="draftStorageKey"
946
1016
  :hide-settings="hideSettings"
947
1017
  :hide-sidebar="hideSidebar"
948
1018
  :is-editing="isEditing"
@@ -1030,7 +1100,8 @@ async function handleDelete() {
1030
1100
  ref="messageInputRef"
1031
1101
  v-model:selected-model="selectedModel"
1032
1102
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1033
- :loading="composerLocked"
1103
+ :draft-storage-key="draftStorageKey"
1104
+ :loading="loading || composerLocked"
1034
1105
  :disabled="composerDisabled"
1035
1106
  :send-blocked="sendBlocked"
1036
1107
  :creator-name="currentUser?.name ?? void 0"
@@ -1086,8 +1157,9 @@ async function handleDelete() {
1086
1157
  ref="messageInputRef"
1087
1158
  v-model:selected-model="selectedModel"
1088
1159
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1160
+ :draft-storage-key="draftStorageKey"
1089
1161
  class="!p-0"
1090
- :loading="composerLocked"
1162
+ :loading="loading || composerLocked"
1091
1163
  :disabled="composerDisabled"
1092
1164
  :send-blocked="sendBlocked"
1093
1165
  :creator-name="currentUser?.name ?? void 0"
@@ -1241,7 +1313,8 @@ async function handleDelete() {
1241
1313
  ref="messageInputRef"
1242
1314
  v-model:selected-model="selectedModel"
1243
1315
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1244
- :loading="composerLocked"
1316
+ :draft-storage-key="draftStorageKey"
1317
+ :loading="loading || composerLocked"
1245
1318
  :disabled="composerDisabled"
1246
1319
  :send-blocked="sendBlocked"
1247
1320
  :creator-name="currentUser?.name ?? void 0"
@@ -1300,8 +1373,9 @@ async function handleDelete() {
1300
1373
  ref="messageInputRef"
1301
1374
  v-model:selected-model="selectedModel"
1302
1375
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1376
+ :draft-storage-key="draftStorageKey"
1303
1377
  class="!p-0"
1304
- :loading="composerLocked"
1378
+ :loading="loading || composerLocked"
1305
1379
  :disabled="composerDisabled"
1306
1380
  :send-blocked="sendBlocked"
1307
1381
  :creator-name="currentUser?.name ?? 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;
@@ -71,8 +72,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
71
72
  copyLink: () => 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;
@@ -93,8 +94,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
93
94
  onCopyLink?: (() => 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;
@@ -71,8 +72,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
71
72
  copyLink: () => 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;
@@ -93,8 +94,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
93
94
  onCopyLink?: (() => 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"
@@ -26,6 +26,8 @@ type __VLS_Props = {
26
26
  hideReasoningEffortSelector?: boolean;
27
27
  executionControlsDisabled?: boolean;
28
28
  sendBlocked?: boolean;
29
+ /** Persists the text per conversation until the server confirms the send (see composer-draft.ts). */
30
+ draftStorageKey?: string | null;
29
31
  };
30
32
  declare function addFiles(files: File[], inputName?: string): void;
31
33
  declare function restoreDraft(draft: {
@@ -9,6 +9,7 @@ import { useFileUpload } from "../../files/composables/file-upload";
9
9
  import { useInputHistory } from "../composables/input-history";
10
10
  import { appendAttachmentMentions, useMentionAutocomplete } from "../composables/mention-autocomplete";
11
11
  import { getAgentInputReadiness } from "../input-readiness";
12
+ import { composerDraftText, readComposerDraft, writeComposerDraft } from "../composer-draft";
12
13
  import { buildFileMentionItems } from "../mention-file-items";
13
14
  import { formatFileSize } from "../../files/formatting";
14
15
  import { getFileIcon } from "../../files/icons";
@@ -31,7 +32,8 @@ const props = defineProps({
31
32
  hideModelSelector: { type: Boolean, required: false },
32
33
  hideReasoningEffortSelector: { type: Boolean, required: false },
33
34
  executionControlsDisabled: { type: Boolean, required: false },
34
- sendBlocked: { type: Boolean, required: false }
35
+ sendBlocked: { type: Boolean, required: false },
36
+ draftStorageKey: { type: [String, null], required: false }
35
37
  });
36
38
  const emit = defineEmits(["send", "cancel", "selectExecutionProfile"]);
37
39
  const selectedModel = defineModel("selectedModel", { type: [String, null], ...{ default: null } });
@@ -360,6 +362,7 @@ function setEditorContent(text) {
360
362
  if (remaining)
361
363
  editorRef.value.appendChild(document.createTextNode(remaining));
362
364
  content.value = (editorRef.value.textContent ?? "").replace(/\u00A0/g, " ");
365
+ writeComposerDraft(props.draftStorageKey ?? null, composerDraftText(editorRef.value));
363
366
  const range = document.createRange();
364
367
  range.selectNodeContents(editorRef.value);
365
368
  range.collapse(false);
@@ -422,6 +425,7 @@ function handleBlur() {
422
425
  }
423
426
  function handleInput() {
424
427
  content.value = (editorRef.value?.textContent ?? "").replace(/\u00A0/g, " ");
428
+ writeComposerDraft(props.draftStorageKey ?? null, composerDraftText(editorRef.value));
425
429
  if (!restoringHistory)
426
430
  inputHistory.resetNavigation();
427
431
  mentionAutocomplete.detectTrigger();
@@ -513,6 +517,7 @@ function restoreDraft(draft) {
513
517
  }
514
518
  function setDraft(text) {
515
519
  content.value = text;
520
+ writeComposerDraft(props.draftStorageKey ?? null, text);
516
521
  if (editorRef.value) {
517
522
  editorRef.value.textContent = text;
518
523
  const range = document.createRange();
@@ -527,6 +532,18 @@ function setDraft(text) {
527
532
  editorRef.value?.focus();
528
533
  });
529
534
  }
535
+ function showStoredDraft() {
536
+ const stored = props.loading ? "" : readComposerDraft(props.draftStorageKey ?? null);
537
+ content.value = stored;
538
+ if (editorRef.value)
539
+ editorRef.value.textContent = stored;
540
+ }
541
+ onMounted(showStoredDraft);
542
+ watch(() => props.draftStorageKey, showStoredDraft);
543
+ watch(() => props.loading, (loading) => {
544
+ if (!loading && !content.value.trim())
545
+ showStoredDraft();
546
+ });
530
547
  defineExpose({
531
548
  addFiles,
532
549
  restoreDraft,
@@ -26,6 +26,8 @@ type __VLS_Props = {
26
26
  hideReasoningEffortSelector?: boolean;
27
27
  executionControlsDisabled?: boolean;
28
28
  sendBlocked?: boolean;
29
+ /** Persists the text per conversation until the server confirms the send (see composer-draft.ts). */
30
+ draftStorageKey?: string | null;
29
31
  };
30
32
  declare function addFiles(files: File[], inputName?: string): void;
31
33
  declare function restoreDraft(draft: {