@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.
- package/README.md +8 -6
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -0
- package/dist/runtime/conversations/components/chat-conversation-export-modal.d.vue.ts +10 -0
- package/dist/runtime/conversations/components/chat-conversation-export-modal.vue +126 -0
- package/dist/runtime/conversations/components/chat-conversation-export-modal.vue.d.ts +10 -0
- package/dist/runtime/conversations/components/chat-conversation-list.vue +8 -1
- package/dist/runtime/{rendering/components/openui/bar-chart.d.vue.ts → conversations/components/chat-export-document.d.vue.ts} +2 -5
- package/dist/runtime/conversations/components/chat-export-document.vue +26 -0
- package/dist/runtime/{rendering/components/openui/bar-chart.vue.d.ts → conversations/components/chat-export-document.vue.d.ts} +2 -5
- package/dist/runtime/{rendering/components/openui/checkbox.d.vue.ts → conversations/components/chat-export-file.d.vue.ts} +1 -1
- package/dist/runtime/conversations/components/chat-export-file.vue +12 -0
- package/dist/runtime/{rendering/components/openui/checkbox.vue.d.ts → conversations/components/chat-export-file.vue.d.ts} +1 -1
- package/dist/runtime/{rendering/components/openui/buttons.d.vue.ts → conversations/components/chat-export-message.d.vue.ts} +2 -3
- package/dist/runtime/conversations/components/chat-export-message.vue +84 -0
- package/dist/runtime/{rendering/components/openui/bar.vue.d.ts → conversations/components/chat-export-message.vue.d.ts} +2 -5
- package/dist/runtime/{rendering/components/openui/bar.d.vue.ts → conversations/components/chat-export-openui.d.vue.ts} +3 -4
- package/dist/runtime/conversations/components/chat-export-openui.vue +19 -0
- package/dist/runtime/conversations/components/chat-export-openui.vue.d.ts +9 -0
- package/dist/runtime/{rendering/components/openui/callout.d.vue.ts → conversations/components/chat-export-questions.d.vue.ts} +12 -4
- package/dist/runtime/conversations/components/chat-export-questions.vue +22 -0
- package/dist/runtime/{rendering/components/openui/callout.vue.d.ts → conversations/components/chat-export-questions.vue.d.ts} +12 -4
- package/dist/runtime/conversations/composables/conversation-list.d.ts +2 -1
- package/dist/runtime/conversations/composables/conversation-list.js +4 -13
- package/dist/runtime/conversations/export-document.d.ts +1 -0
- package/dist/runtime/conversations/export-document.js +154 -0
- package/dist/runtime/conversations/export.d.ts +4 -0
- package/dist/runtime/conversations/export.js +69 -4
- package/dist/runtime/embed/components/chat-embed-inner.vue +81 -7
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +3 -2
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +2 -0
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +3 -2
- package/dist/runtime/embed/execution-controls.d.ts +2 -1
- package/dist/runtime/embed/execution-controls.js +2 -2
- package/dist/runtime/messages/components/chat-execution-mode-selector.vue +10 -6
- package/dist/runtime/messages/components/chat-message-bubble.vue +7 -0
- package/dist/runtime/messages/components/chat-message-input.d.vue.ts +2 -0
- package/dist/runtime/messages/components/chat-message-input.vue +18 -1
- package/dist/runtime/messages/components/chat-message-input.vue.d.ts +2 -0
- package/dist/runtime/messages/composables/chat.d.ts +15 -0
- package/dist/runtime/messages/composables/chat.js +49 -5
- package/dist/runtime/messages/composer-draft.d.ts +16 -0
- package/dist/runtime/messages/composer-draft.js +61 -0
- package/dist/runtime/messages/composer-pending-send.d.ts +8 -0
- package/dist/runtime/messages/composer-pending-send.js +34 -0
- package/dist/runtime/messages/conversation-timeline.js +1 -1
- package/dist/runtime/rendering/components/openui/{markdown.d.vue.ts → markdown-prose.d.vue.ts} +1 -3
- package/dist/runtime/rendering/components/openui/{markdown.vue → markdown-prose.vue} +3 -3
- package/dist/runtime/rendering/components/openui/{markdown.vue.d.ts → markdown-prose.vue.d.ts} +1 -3
- package/dist/runtime/rendering/components/openui-block.vue +26 -52
- package/dist/runtime/rendering/openui/context.d.ts +2 -17
- package/dist/runtime/rendering/openui/context.js +0 -2
- package/dist/runtime/server/utils/chat-api-proxy.d.ts +1 -1
- package/dist/runtime/server/utils/chat-api-proxy.js +3 -2
- package/dist/runtime/types/composer.d.ts +6 -0
- package/dist/runtime/types/schemas/chat/chat-config.d.ts +6 -2
- package/dist/runtime/types/schemas/chat/chat-config.js +1 -0
- package/dist/runtime/types/schemas/chat/conversation-runtime.d.ts +8 -0
- package/dist/runtime/types/schemas/chat/conversations.d.ts +8 -0
- package/dist/runtime/types/schemas/chat/messages.d.ts +12 -0
- package/dist/runtime/types/schemas/chat/ui-language.d.ts +19 -2
- package/dist/runtime/types/schemas/chat/ui-language.js +8 -3
- package/dist/runtime/types/schemas/chat/v3.d.ts +160 -2
- package/dist/runtime/types/schemas/chat/v3.js +91 -5
- package/dist/runtime/types/schemas/integrations/slack-apps.d.ts +53 -0
- package/dist/runtime/types/schemas/integrations/slack-apps.js +6 -0
- package/dist/runtime/types/schemas/integrations/slack-identity.d.ts +26 -0
- package/dist/runtime/types/schemas/integrations/slack-identity.js +23 -0
- package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +40 -0
- package/dist/runtime/types/schemas/integrations/tela-agent-api.js +3 -0
- package/dist/runtime/types/schemas/integrations/tela-api.d.ts +11 -0
- package/dist/runtime/types/schemas/integrations/tela-api.js +8 -0
- package/dist/runtime/types/schemas/integrations/tela-auth.d.ts +39 -0
- package/dist/runtime/types/schemas/integrations/tela-auth.js +12 -0
- package/dist/runtime/types/schemas/slack/apps.d.ts +144 -0
- package/dist/runtime/types/schemas/slack/apps.js +53 -0
- package/dist/runtime/types/schemas/slack/identities.d.ts +58 -0
- package/dist/runtime/types/schemas/slack/identities.js +19 -0
- package/dist/runtime/types/schemas/slack/installations.d.ts +39 -0
- package/dist/runtime/types/schemas/slack/installations.js +18 -0
- package/package.json +2 -2
- package/dist/runtime/rendering/components/openui/bar-chart.vue +0 -39
- package/dist/runtime/rendering/components/openui/bar.vue +0 -12
- package/dist/runtime/rendering/components/openui/button.d.vue.ts +0 -11
- package/dist/runtime/rendering/components/openui/button.vue +0 -30
- package/dist/runtime/rendering/components/openui/button.vue.d.ts +0 -11
- package/dist/runtime/rendering/components/openui/buttons.vue +0 -14
- package/dist/runtime/rendering/components/openui/buttons.vue.d.ts +0 -8
- package/dist/runtime/rendering/components/openui/callout.vue +0 -27
- package/dist/runtime/rendering/components/openui/card.d.vue.ts +0 -9
- package/dist/runtime/rendering/components/openui/card.vue +0 -19
- package/dist/runtime/rendering/components/openui/card.vue.d.ts +0 -9
- package/dist/runtime/rendering/components/openui/checkbox.vue +0 -26
- package/dist/runtime/rendering/components/openui/col.d.vue.ts +0 -10
- package/dist/runtime/rendering/components/openui/col.vue +0 -16
- package/dist/runtime/rendering/components/openui/col.vue.d.ts +0 -10
- package/dist/runtime/rendering/components/openui/form.d.vue.ts +0 -10
- package/dist/runtime/rendering/components/openui/form.vue +0 -60
- package/dist/runtime/rendering/components/openui/form.vue.d.ts +0 -10
- package/dist/runtime/rendering/components/openui/input.d.vue.ts +0 -10
- package/dist/runtime/rendering/components/openui/input.vue +0 -23
- package/dist/runtime/rendering/components/openui/input.vue.d.ts +0 -10
- package/dist/runtime/rendering/components/openui/metric.d.vue.ts +0 -11
- package/dist/runtime/rendering/components/openui/metric.vue +0 -20
- package/dist/runtime/rendering/components/openui/metric.vue.d.ts +0 -11
- package/dist/runtime/rendering/components/openui/option.d.vue.ts +0 -10
- package/dist/runtime/rendering/components/openui/option.vue +0 -12
- package/dist/runtime/rendering/components/openui/option.vue.d.ts +0 -10
- package/dist/runtime/rendering/components/openui/question.d.vue.ts +0 -11
- package/dist/runtime/rendering/components/openui/question.vue +0 -15
- package/dist/runtime/rendering/components/openui/question.vue.d.ts +0 -11
- package/dist/runtime/rendering/components/openui/questions.d.vue.ts +0 -9
- package/dist/runtime/rendering/components/openui/questions.vue +0 -91
- package/dist/runtime/rendering/components/openui/questions.vue.d.ts +0 -9
- package/dist/runtime/rendering/components/openui/select.d.vue.ts +0 -10
- package/dist/runtime/rendering/components/openui/select.vue +0 -27
- package/dist/runtime/rendering/components/openui/select.vue.d.ts +0 -10
- package/dist/runtime/rendering/components/openui/stack.d.vue.ts +0 -9
- package/dist/runtime/rendering/components/openui/stack.vue +0 -18
- package/dist/runtime/rendering/components/openui/stack.vue.d.ts +0 -9
- package/dist/runtime/rendering/components/openui/table.d.vue.ts +0 -9
- package/dist/runtime/rendering/components/openui/table.vue +0 -41
- package/dist/runtime/rendering/components/openui/table.vue.d.ts +0 -9
- package/dist/runtime/rendering/openui/actions.d.ts +0 -15
- package/dist/runtime/rendering/openui/actions.js +0 -40
- package/dist/runtime/rendering/openui/form-fields.d.ts +0 -10
- package/dist/runtime/rendering/openui/form-fields.js +0 -30
- package/dist/runtime/rendering/openui/program.d.ts +0 -15
- package/dist/runtime/rendering/openui/program.js +0 -74
- package/dist/runtime/rendering/openui/registry.d.ts +0 -5
- package/dist/runtime/rendering/openui/registry.js +0 -44
- package/dist/runtime/rendering/openui/render.d.ts +0 -21
- package/dist/runtime/rendering/openui/render.js +0 -45
- package/dist/runtime/rendering/openui/use-openui-context.d.ts +0 -2
- 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 =
|
|
103
|
+
link.download = filename;
|
|
39
104
|
document.body.appendChild(link);
|
|
40
105
|
link.click();
|
|
41
|
-
|
|
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
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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: "
|
|
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 (
|
|
18
|
-
|
|
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: {
|