@meistrari/chat-nuxt 3.17.0-rc.9 → 4.0.0-rc.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.
- package/README.md +2 -0
- package/dist/module.json +1 -1
- package/dist/runtime/assets/css/markstream.css +1 -1
- package/dist/runtime/embed/components/chat-embed-inner.vue +9 -2
- package/dist/runtime/files/content-detection.d.ts +2 -0
- package/dist/runtime/files/content-detection.js +27 -20
- package/dist/runtime/files/mime.d.ts +0 -1
- package/dist/runtime/files/mime.js +0 -4
- package/dist/runtime/files/vault-links.js +1 -1
- package/dist/runtime/messages/components/chat-message-bubble.vue +1 -1
- package/dist/runtime/messages/components/chat-message-input.vue +16 -2
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +2 -1
- package/dist/runtime/messages/composables/chat.js +34 -3
- package/dist/runtime/types/composer.d.ts +22 -0
- package/dist/runtime/types/composer.js +1 -0
- package/dist/runtime/types/schemas/chat/conversations.d.ts +6 -0
- package/dist/runtime/types/schemas/chat/models.d.ts +4 -1
- package/dist/runtime/types/schemas/chat/models.js +3 -0
- package/dist/runtime/types/schemas/chat/ui.d.ts +30 -0
- package/dist/runtime/types/schemas/chat/ui.js +4 -0
- package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +3 -0
- package/dist/runtime/usage/formatting.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,9 @@ Runtime internals under `src/runtime/**`, generated Nuxt aliases, and workspace-
|
|
|
28
28
|
| [Citations](docs/guides/citations.md) | `cite://` chips, metadata popover, document panel |
|
|
29
29
|
| [Feature flags](docs/guides/feature-flags.md) | Usage tab, debug entry, cancel button, PostHog wiring |
|
|
30
30
|
| [Message feedback](docs/guides/message-feedback.md) | 👍/👎 controls and host-owned persistence |
|
|
31
|
+
| [Composer extension](docs/guides/composer-extension.md) | Host actions, shared attachment strip, outbound content and retry lifecycle |
|
|
31
32
|
| [Workspace settings](docs/guides/workspace-settings.md) | Settings overrides, agent runtime ownership, `<ChatConfigurationModal>` |
|
|
33
|
+
| [Upgrading to 4.0](docs/guides/upgrading-to-4.md) | Required Chat API version for v2 sends, upgrade order, vue-i18n removal |
|
|
32
34
|
|
|
33
35
|
## API Reference
|
|
34
36
|
|
package/dist/module.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.markstream-vue{font-family:inherit;--at-apply:"body-16-regular leading-24px text-primary";--list-item-marker:#a3a3a3;--list-item-counter-marker:#262626}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;letter-spacing:0;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{--at-apply:"font-580"}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{--at-apply:"body-16-regular leading-24px";margin:0}.markstream-vue>.node-slot[data-node-type=paragraph]:not(:first-child){margin-top:12px}@keyframes chat-stream-chunk-in{0%{opacity:0}to{opacity:1}}.markstream-vue .chat-stream-chunk{animation:chat-stream-chunk-in var(--chat-stream-fade-duration,.32s) cubic-bezier(.2,0,0,1) both}@media (prefers-reduced-motion:reduce){.markstream-vue .chat-stream-chunk{animation:none}}.markstream-vue .typewriter-cursor,.markstream-vue .typewriter-simple-cursor .typewriter-simple-cursor-target:after{display:none}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{--at-apply:"heading-h2-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{--at-apply:"heading-h3-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{--at-apply:"heading-h4-semibold text-primary";margin:20px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue .heading-node.heading-5,.markstream-vue .heading-node.heading-6,.markstream-vue h4,.markstream-vue h5,.markstream-vue h6{--at-apply:"heading-h5-semibold text-primary";margin:16px 0 8px}.markstream-vue>.node-slot:first-child .heading-node{margin-top:0}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
|
|
1
|
+
.markstream-vue{font-family:inherit;--at-apply:"body-16-regular leading-24px text-primary";--list-item-marker:#a3a3a3;--list-item-counter-marker:#262626}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;letter-spacing:0;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{--at-apply:"font-580"}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ol.list-node.list-node{padding-left:3em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{--at-apply:"body-16-regular leading-24px";margin:0}.markstream-vue>.node-slot[data-node-type=paragraph]:not(:first-child){margin-top:12px}@keyframes chat-stream-chunk-in{0%{opacity:0}to{opacity:1}}.markstream-vue .chat-stream-chunk{animation:chat-stream-chunk-in var(--chat-stream-fade-duration,.32s) cubic-bezier(.2,0,0,1) both}@media (prefers-reduced-motion:reduce){.markstream-vue .chat-stream-chunk{animation:none}}.markstream-vue .typewriter-cursor,.markstream-vue .typewriter-simple-cursor .typewriter-simple-cursor-target:after{display:none}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{--at-apply:"heading-h2-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{--at-apply:"heading-h3-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{--at-apply:"heading-h4-semibold text-primary";margin:20px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue .heading-node.heading-5,.markstream-vue .heading-node.heading-6,.markstream-vue h4,.markstream-vue h5,.markstream-vue h6{--at-apply:"heading-h5-semibold text-primary";margin:16px 0 8px}.markstream-vue>.node-slot:first-child .heading-node{margin-top:0}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { inject } from "vue";
|
|
3
|
+
import { CHAT_COMPOSER_EXTENSION_KEY } from "../../types/composer";
|
|
2
4
|
import { useChatAuth } from "#chat-auth";
|
|
3
5
|
import { useBreakpoints } from "@vueuse/core";
|
|
4
6
|
import { useCitationPanel } from "../../citations/composables/citation-panel";
|
|
@@ -68,6 +70,7 @@ function toggleSidebarCollapsed() {
|
|
|
68
70
|
isSidebarCollapsed.value = !isSidebarCollapsed.value;
|
|
69
71
|
}
|
|
70
72
|
const chatApi = useChatApi();
|
|
73
|
+
const composerExtension = inject(CHAT_COMPOSER_EXTENSION_KEY, null);
|
|
71
74
|
const route = useRoute();
|
|
72
75
|
const embedConfig = useEmbedConfig();
|
|
73
76
|
const chatAction = useChatAction();
|
|
@@ -531,9 +534,11 @@ function claimableWakeSessionId() {
|
|
|
531
534
|
return agentWake.state.value === "ready" && !currentConversation.value?.threadSessionId ? agentWake.sessionId.value : void 0;
|
|
532
535
|
}
|
|
533
536
|
async function handleSend(content, files, agentInputs) {
|
|
537
|
+
const prepared = composerExtension?.prepareSend?.(content);
|
|
538
|
+
const outbound = prepared?.content ?? content;
|
|
534
539
|
const resolvedAgentInputs = resolveTelaAgentInputs(agentInputs, embedConfig?.telaAgentInputs?.value);
|
|
535
540
|
let claimedWakeSessionId;
|
|
536
|
-
const sent = await sendMessage(
|
|
541
|
+
const sent = await sendMessage(outbound, files.length > 0 ? files : void 0, resolvedAgentInputs, async (signal) => {
|
|
537
542
|
if (!activeConversationId.value || !currentConversation.value) {
|
|
538
543
|
const conversation2 = await createConversation({
|
|
539
544
|
model: selectedModel.value,
|
|
@@ -581,8 +586,10 @@ async function handleSend(content, files, agentInputs) {
|
|
|
581
586
|
});
|
|
582
587
|
if (sent && claimedWakeSessionId)
|
|
583
588
|
agentWake.consume();
|
|
589
|
+
if (sent)
|
|
590
|
+
prepared?.commit?.();
|
|
584
591
|
if (!sent)
|
|
585
|
-
restoreQueuedSend({ content, files, agentInputs: resolvedAgentInputs });
|
|
592
|
+
restoreQueuedSend({ content: prepared?.retryContent?.() ?? content, files, agentInputs: resolvedAgentInputs });
|
|
586
593
|
}
|
|
587
594
|
watch(
|
|
588
595
|
[outboundOccupied, agentWake.state],
|
|
@@ -20,6 +20,8 @@ export type ContentSegment = {
|
|
|
20
20
|
export type ParseContentOptions = {
|
|
21
21
|
/** Detect bare `cite://` tokens as citation segments. Off by default: only hosts that configured citations opt in. */
|
|
22
22
|
detectCitations?: boolean;
|
|
23
|
+
/** Keep Markdown links in text so the Markdown renderer can preserve enclosing structures such as tables and lists. */
|
|
24
|
+
preserveMarkdownLinks?: boolean;
|
|
23
25
|
};
|
|
24
26
|
export declare function detectFilesInText(text: string): DetectedFile[];
|
|
25
27
|
export declare function parseContentWithFiles(text: string, options?: ParseContentOptions): ContentSegment[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CITE_REGEX, parseCitationUrl } from "../citations/citation-links.js";
|
|
2
|
-
import { getExtensionFromUrl, getMimeTypeFromExtension
|
|
2
|
+
import { getExtensionFromUrl, getMimeTypeFromExtension } from "./mime.js";
|
|
3
3
|
import {
|
|
4
4
|
getPermalinkId,
|
|
5
5
|
isVaultPermalink,
|
|
@@ -18,6 +18,20 @@ function getNameFromUrl(url) {
|
|
|
18
18
|
const parts = cleanUrl.split("/");
|
|
19
19
|
return parts[parts.length - 1] ?? url;
|
|
20
20
|
}
|
|
21
|
+
function detectExternalFileUrl(url) {
|
|
22
|
+
const ext = getExtensionFromUrl(url);
|
|
23
|
+
if (!ext)
|
|
24
|
+
return null;
|
|
25
|
+
const mimeType = getMimeTypeFromExtension(ext);
|
|
26
|
+
if (mimeType === "unknown" || mimeType === "text/html")
|
|
27
|
+
return null;
|
|
28
|
+
return {
|
|
29
|
+
type: "url",
|
|
30
|
+
reference: url,
|
|
31
|
+
name: getNameFromUrl(url),
|
|
32
|
+
mimeType
|
|
33
|
+
};
|
|
34
|
+
}
|
|
21
35
|
function getNameFromVaultReference(reference) {
|
|
22
36
|
const parts = reference.replace("vault://", "").split("/");
|
|
23
37
|
return parts[parts.length - 1] || "Arquivo";
|
|
@@ -116,27 +130,24 @@ export function detectFilesInText(text) {
|
|
|
116
130
|
});
|
|
117
131
|
continue;
|
|
118
132
|
}
|
|
119
|
-
|
|
133
|
+
const file = detectExternalFileUrl(match);
|
|
134
|
+
if (!file)
|
|
120
135
|
continue;
|
|
121
136
|
seen.add(match);
|
|
122
|
-
|
|
123
|
-
files.push({
|
|
124
|
-
type: "url",
|
|
125
|
-
reference: match,
|
|
126
|
-
name: getNameFromUrl(match),
|
|
127
|
-
mimeType: ext ? getMimeTypeFromExtension(ext) : "unknown"
|
|
128
|
-
});
|
|
137
|
+
files.push(file);
|
|
129
138
|
}
|
|
130
139
|
return files;
|
|
131
140
|
}
|
|
132
141
|
export function parseContentWithFiles(text, options) {
|
|
133
142
|
const segments = [];
|
|
134
|
-
const
|
|
143
|
+
const markdownFileLinkMatches = collectMarkdownFileLinkMatches(text);
|
|
144
|
+
const fileMatches = options?.preserveMarkdownLinks ? [] : [...markdownFileLinkMatches];
|
|
135
145
|
for (const match of text.matchAll(VAULT_REGEX)) {
|
|
136
146
|
const rawUrl = match[0];
|
|
137
147
|
const url = stripTrailingPunctuation(rawUrl);
|
|
138
|
-
if (overlapsFileMatch(match.index, match.index + url.length, fileMatches))
|
|
148
|
+
if (overlapsFileMatch(match.index, match.index + url.length, markdownFileLinkMatches) || overlapsFileMatch(match.index, match.index + url.length, fileMatches)) {
|
|
139
149
|
continue;
|
|
150
|
+
}
|
|
140
151
|
const hashParams = parseVaultHashParams(url);
|
|
141
152
|
const filenameExt = hashParams?.filename?.split(".").pop()?.toLowerCase();
|
|
142
153
|
const urlExt = getExtensionFromUrl(url);
|
|
@@ -156,8 +167,9 @@ export function parseContentWithFiles(text, options) {
|
|
|
156
167
|
for (const match of text.matchAll(URL_REGEX)) {
|
|
157
168
|
const rawUrl = match[0];
|
|
158
169
|
const url = stripTrailingPunctuation(rawUrl);
|
|
159
|
-
if (overlapsFileMatch(match.index, match.index + url.length, fileMatches))
|
|
170
|
+
if (overlapsFileMatch(match.index, match.index + url.length, markdownFileLinkMatches) || overlapsFileMatch(match.index, match.index + url.length, fileMatches)) {
|
|
160
171
|
continue;
|
|
172
|
+
}
|
|
161
173
|
if (isVaultS3Url(url)) {
|
|
162
174
|
const { name, mimeType } = getFileInfoFromVaultS3Url(url);
|
|
163
175
|
fileMatches.push({
|
|
@@ -185,17 +197,12 @@ export function parseContentWithFiles(text, options) {
|
|
|
185
197
|
});
|
|
186
198
|
continue;
|
|
187
199
|
}
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
const file = detectExternalFileUrl(url);
|
|
201
|
+
if (file) {
|
|
190
202
|
fileMatches.push({
|
|
191
203
|
start: match.index,
|
|
192
204
|
end: match.index + url.length,
|
|
193
|
-
file
|
|
194
|
-
type: "url",
|
|
195
|
-
reference: url,
|
|
196
|
-
name: getNameFromUrl(url),
|
|
197
|
-
mimeType: ext ? getMimeTypeFromExtension(ext) : "unknown"
|
|
198
|
-
}
|
|
205
|
+
file
|
|
199
206
|
});
|
|
200
207
|
}
|
|
201
208
|
}
|
|
@@ -3,4 +3,3 @@ export declare const MIME_TO_EXTENSION: Record<string, string>;
|
|
|
3
3
|
export declare function normalizeMimeType(mimeType: string): string;
|
|
4
4
|
export declare function getExtensionFromUrl(url: string): string | undefined;
|
|
5
5
|
export declare function getMimeTypeFromExtension(ext: string): string;
|
|
6
|
-
export declare function isFileUrl(url: string): boolean;
|
|
@@ -72,7 +72,3 @@ export function getExtensionFromUrl(url) {
|
|
|
72
72
|
export function getMimeTypeFromExtension(ext) {
|
|
73
73
|
return FILE_EXTENSIONS[ext] ?? "unknown";
|
|
74
74
|
}
|
|
75
|
-
export function isFileUrl(url) {
|
|
76
|
-
const ext = getExtensionFromUrl(url);
|
|
77
|
-
return ext !== void 0 && ext in FILE_EXTENSIONS;
|
|
78
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const URL_CHAR_CLASS = "[^\\s\"'<>`\\[\\]()*_~]+";
|
|
2
2
|
export const VAULT_REGEX = /(?<!\]\()vault:\/\/[^\s"'<>`\[\]()*_~]+/gi;
|
|
3
|
-
export const URL_REGEX = /https?:\/\/[^\s"'<>`\[\]()*_~]+/gi;
|
|
3
|
+
export const URL_REGEX = /(?<!\]\(|<)https?:\/\/[^\s"'<>`\[\]()*_~]+/gi;
|
|
4
4
|
export const VAULT_PATTERN = new RegExp(`vault://${URL_CHAR_CLASS}`, "gi");
|
|
5
5
|
export const VAULT_PERMALINK_PATTERN = new RegExp(`https://(?:[a-z0-9-]+\\.)*(?:vault|api)\\.tela(?:staging)?\\.com/(?:[a-z0-9_-]+/)*permalinks/${URL_CHAR_CLASS}`, "gi");
|
|
6
6
|
export function stripTrailingPunctuation(url) {
|
|
@@ -100,7 +100,7 @@ const contentSegments = computed(() => {
|
|
|
100
100
|
const content = displayContent.value;
|
|
101
101
|
if (!content)
|
|
102
102
|
return [];
|
|
103
|
-
return parseContentWithFiles(content);
|
|
103
|
+
return parseContentWithFiles(content, { preserveMarkdownLinks: true });
|
|
104
104
|
});
|
|
105
105
|
const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type !== "text"));
|
|
106
106
|
</script>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { inject, unref } from "vue";
|
|
3
|
+
import { CHAT_COMPOSER_EXTENSION_KEY } from "../../types/composer";
|
|
2
4
|
import { useFileUpload } from "../../files/composables/file-upload";
|
|
3
5
|
import { MAX_FILE_SIZE, MAX_FILES } from "../../files/file-limits";
|
|
4
6
|
import { useInputHistory } from "../composables/input-history";
|
|
@@ -26,6 +28,7 @@ const props = defineProps({
|
|
|
26
28
|
});
|
|
27
29
|
const emit = defineEmits(["send", "cancel"]);
|
|
28
30
|
const selectedModel = defineModel("selectedModel", { type: [String, null], ...{ default: null } });
|
|
31
|
+
const composerExtension = inject(CHAT_COMPOSER_EXTENSION_KEY, null);
|
|
29
32
|
const selectedReasoningEffort = defineModel("selectedReasoningEffort", { type: null, ...{ default: null } });
|
|
30
33
|
const { uploadFiles, uploading, uploadError } = useFileUpload();
|
|
31
34
|
const isCancelEnabled = computed(() => props.showCancelButton === true);
|
|
@@ -34,6 +37,7 @@ const promptTextareaRef = ref(null);
|
|
|
34
37
|
const editorRef = computed(() => promptTextareaRef.value?.el ?? null);
|
|
35
38
|
const fileInputRef = ref(null);
|
|
36
39
|
const pendingFiles = ref([]);
|
|
40
|
+
const hasAttachments = computed(() => pendingFiles.value.length > 0 || !!unref(composerExtension?.hasAttachments));
|
|
37
41
|
const selectedUploadInputName = ref(void 0);
|
|
38
42
|
const agentInputTexts = ref({});
|
|
39
43
|
const isAgentInputsOpen = ref(false);
|
|
@@ -661,7 +665,7 @@ defineExpose({
|
|
|
661
665
|
/>
|
|
662
666
|
|
|
663
667
|
<TelaChatPromptInput :locked="promptShellLocked" :class="centered && 'bg-white'">
|
|
664
|
-
<TelaChatPromptInputAttachments v-if="
|
|
668
|
+
<TelaChatPromptInputAttachments v-if="hasAttachments">
|
|
665
669
|
<TelaChatPromptInputAttachment
|
|
666
670
|
v-for="pendingFile in pendingFiles"
|
|
667
671
|
:key="pendingFile.id"
|
|
@@ -691,13 +695,18 @@ defineExpose({
|
|
|
691
695
|
</option>
|
|
692
696
|
</select>
|
|
693
697
|
</TelaChatPromptInputAttachment>
|
|
698
|
+
<component
|
|
699
|
+
:is="composerExtension.attachmentsComponent"
|
|
700
|
+
v-if="composerExtension?.attachmentsComponent"
|
|
701
|
+
:disabled="isLocked"
|
|
702
|
+
/>
|
|
694
703
|
</TelaChatPromptInputAttachments>
|
|
695
704
|
|
|
696
705
|
<TelaChatPromptInputTextarea
|
|
697
706
|
ref="promptTextareaRef"
|
|
698
707
|
placeholder="Escreva sua mensagem..."
|
|
699
708
|
:empty="!content.trim()"
|
|
700
|
-
:flush="
|
|
709
|
+
:flush="hasAttachments"
|
|
701
710
|
:disabled="isLocked"
|
|
702
711
|
@keydown="handleKeydown"
|
|
703
712
|
@input="handleInput"
|
|
@@ -755,6 +764,11 @@ defineExpose({
|
|
|
755
764
|
</TelaChatPromptInputActionMenuSub>
|
|
756
765
|
</TelaChatPromptInputActionMenuContent>
|
|
757
766
|
</TelaChatPromptInputActionMenu>
|
|
767
|
+
<component
|
|
768
|
+
:is="composerExtension.component"
|
|
769
|
+
v-if="composerExtension?.component"
|
|
770
|
+
:disabled="isLocked"
|
|
771
|
+
/>
|
|
758
772
|
</TelaChatPromptInputTools>
|
|
759
773
|
|
|
760
774
|
<TelaChatPromptInputActions>
|
|
@@ -42,7 +42,8 @@ const thinkingDuration = computed(() => {
|
|
|
42
42
|
return null;
|
|
43
43
|
const start = new Date(props.createdAt).getTime();
|
|
44
44
|
const end = new Date(props.updatedAt).getTime();
|
|
45
|
-
|
|
45
|
+
const duration = end - start;
|
|
46
|
+
return Number.isFinite(duration) && duration >= 0 ? duration : null;
|
|
46
47
|
});
|
|
47
48
|
const formattedDuration = computed(() => {
|
|
48
49
|
if (!thinkingDuration.value)
|
|
@@ -259,6 +259,17 @@ export function useChat(options = {}) {
|
|
|
259
259
|
agentSessionExecutable.value = response.agentSession.executable;
|
|
260
260
|
agentSessionRewakeable.value = response.agentSession.rewakeable ?? false;
|
|
261
261
|
}
|
|
262
|
+
const queuedV2Send = chatApi.conversationV2Enabled ? outbound.slot.value : null;
|
|
263
|
+
if (queuedV2Send) {
|
|
264
|
+
const admitted = response.messages.some((message) => message.id === queuedV2Send.optimisticIds.user);
|
|
265
|
+
if (admitted) {
|
|
266
|
+
outbound.clear();
|
|
267
|
+
gateHolding.value = false;
|
|
268
|
+
} else {
|
|
269
|
+
void flushOutboundQueue();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
262
273
|
if (gateHolding.value)
|
|
263
274
|
return;
|
|
264
275
|
const currentMessagesById = new Map(messages.value.map((message) => [message.id, message]));
|
|
@@ -629,9 +640,29 @@ export function useChat(options = {}) {
|
|
|
629
640
|
const code = getChatApiErrorCode(err);
|
|
630
641
|
const status = getChatApiErrorStatus(err);
|
|
631
642
|
if (v2AdmissionRequested && !controller.signal.aborted && (status === null || status === 408 || status === 429 || status >= 500)) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
643
|
+
const attempts = (queuedSend?.attempts ?? 0) + 1;
|
|
644
|
+
const queuedAt = queuedSend?.queuedAt ?? Date.now();
|
|
645
|
+
if (outboundQueueEnabled.value) {
|
|
646
|
+
if (attempts < 3 && Date.now() - queuedAt <= 9e4) {
|
|
647
|
+
outbound.replace({
|
|
648
|
+
content,
|
|
649
|
+
files,
|
|
650
|
+
agentInputs,
|
|
651
|
+
wakeSessionId: void 0,
|
|
652
|
+
optimisticIds: { user: optimisticUserMessage.id, assistant: optimisticAssistantMessage.id },
|
|
653
|
+
queuedAt,
|
|
654
|
+
attempts
|
|
655
|
+
});
|
|
656
|
+
gateHolding.value = true;
|
|
657
|
+
error.value = "Confirma\xE7\xE3o do envio pendente. Reconectando \xE0 conversa\u2026";
|
|
658
|
+
startPolling();
|
|
659
|
+
return optimisticUserMessage;
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
error.value = "Confirma\xE7\xE3o do envio pendente. Reconectando \xE0 conversa\u2026";
|
|
663
|
+
startPolling();
|
|
664
|
+
return optimisticUserMessage;
|
|
665
|
+
}
|
|
635
666
|
}
|
|
636
667
|
const queuedAttempts = (queuedSend?.attempts ?? 0) + 1;
|
|
637
668
|
const queuedAgeMs = Date.now() - (queuedSend?.queuedAt ?? Date.now());
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Component, MaybeRef } from 'vue';
|
|
2
|
+
/** Stable string key, also compatible with hosts providing the literal key. */
|
|
3
|
+
export declare const CHAT_COMPOSER_EXTENSION_KEY = "chat-composer-extension";
|
|
4
|
+
/** A synchronous, per-send snapshot owned by the host application. */
|
|
5
|
+
export interface ChatPreparedSend {
|
|
6
|
+
content: string;
|
|
7
|
+
/** Called only after acceptance, including acceptance into the outbound queue. */
|
|
8
|
+
commit?: () => void;
|
|
9
|
+
/** Read after rejection. Defaults to the original composer text when omitted. */
|
|
10
|
+
retryContent?: () => string;
|
|
11
|
+
}
|
|
12
|
+
/** Provide above MeistrariChatEmbed to extend its composer without replacing it. */
|
|
13
|
+
export interface ChatComposerExtension {
|
|
14
|
+
/** Action next to the file menu. Receives the composer's `disabled` state. */
|
|
15
|
+
component?: Component;
|
|
16
|
+
/** Items inside the shared attachment strip. Receives `disabled` as well. */
|
|
17
|
+
attachmentsComponent?: Component;
|
|
18
|
+
/** Controls attachment-strip visibility and the textarea's flush layout. */
|
|
19
|
+
hasAttachments?: MaybeRef<boolean>;
|
|
20
|
+
/** Prepare without consuming host state; commit after the send is accepted. */
|
|
21
|
+
prepareSend?: (content: string) => ChatPreparedSend | null | undefined;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CHAT_COMPOSER_EXTENSION_KEY = "chat-composer-extension";
|
|
@@ -232,6 +232,9 @@ export declare const createConversationRequestSchema: z.ZodObject<{
|
|
|
232
232
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
233
233
|
"claude-sonnet-4-6": "claude-sonnet-4-6";
|
|
234
234
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
235
|
+
"openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
|
|
236
|
+
"openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
|
|
237
|
+
"openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
|
|
235
238
|
}>>>;
|
|
236
239
|
reasoningEffort: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
237
240
|
low: "low";
|
|
@@ -407,6 +410,9 @@ export declare const updateConversationRequestSchema: z.ZodObject<{
|
|
|
407
410
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
408
411
|
"claude-sonnet-4-6": "claude-sonnet-4-6";
|
|
409
412
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
413
|
+
"openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
|
|
414
|
+
"openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
|
|
415
|
+
"openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
|
|
410
416
|
}>>>;
|
|
411
417
|
reasoningEffort: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
412
418
|
low: "low";
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const chatModelIds: readonly ["claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5"];
|
|
2
|
+
export declare const chatModelIds: readonly ["claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5", "openai/gpt-5.6-sol", "openai/gpt-5.6-terra", "openai/gpt-5.6-luna"];
|
|
3
3
|
export declare const chatModelSchema: z.ZodEnum<{
|
|
4
4
|
"claude-opus-5": "claude-opus-5";
|
|
5
5
|
"claude-opus-4-8": "claude-opus-4-8";
|
|
6
6
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
7
7
|
"claude-sonnet-4-6": "claude-sonnet-4-6";
|
|
8
8
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
9
|
+
"openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
|
|
10
|
+
"openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
|
|
11
|
+
"openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
|
|
9
12
|
}>;
|
|
10
13
|
export type ChatModel = z.infer<typeof chatModelSchema>;
|
|
11
14
|
export declare const defaultChatModel = "claude-opus-5";
|
|
@@ -5,6 +5,9 @@ export const chatModelIds = [
|
|
|
5
5
|
'claude-sonnet-5',
|
|
6
6
|
'claude-sonnet-4-6',
|
|
7
7
|
'claude-haiku-4-5',
|
|
8
|
+
'openai/gpt-5.6-sol',
|
|
9
|
+
'openai/gpt-5.6-terra',
|
|
10
|
+
'openai/gpt-5.6-luna',
|
|
8
11
|
];
|
|
9
12
|
export const chatModelSchema = z.enum(chatModelIds);
|
|
10
13
|
export const defaultChatModel = 'claude-opus-5';
|
|
@@ -69,6 +69,36 @@ export declare const chatModelOptions: readonly [{
|
|
|
69
69
|
readonly maxOutputTokens: 64000;
|
|
70
70
|
readonly isMultiModal: true;
|
|
71
71
|
readonly tabs: readonly ["all", "cheap", "multimodal"];
|
|
72
|
+
}, {
|
|
73
|
+
readonly value: "openai/gpt-5.6-sol";
|
|
74
|
+
readonly label: "GPT-5.6 Sol";
|
|
75
|
+
readonly description: "Modelo OpenAI para tarefas complexas";
|
|
76
|
+
readonly provider: "OpenAI";
|
|
77
|
+
readonly cost: 5;
|
|
78
|
+
readonly maxInputTokens: 1050000;
|
|
79
|
+
readonly maxOutputTokens: 128000;
|
|
80
|
+
readonly isMultiModal: true;
|
|
81
|
+
readonly tabs: readonly ["all", "advanced", "multimodal"];
|
|
82
|
+
}, {
|
|
83
|
+
readonly value: "openai/gpt-5.6-terra";
|
|
84
|
+
readonly label: "GPT-5.6 Terra";
|
|
85
|
+
readonly description: "Equilíbrio entre capacidade e custo";
|
|
86
|
+
readonly provider: "OpenAI";
|
|
87
|
+
readonly cost: 3;
|
|
88
|
+
readonly maxInputTokens: 1050000;
|
|
89
|
+
readonly maxOutputTokens: 128000;
|
|
90
|
+
readonly isMultiModal: true;
|
|
91
|
+
readonly tabs: readonly ["all", "advanced", "multimodal"];
|
|
92
|
+
}, {
|
|
93
|
+
readonly value: "openai/gpt-5.6-luna";
|
|
94
|
+
readonly label: "GPT-5.6 Luna";
|
|
95
|
+
readonly description: "Modelo OpenAI rápido e econômico";
|
|
96
|
+
readonly provider: "OpenAI";
|
|
97
|
+
readonly cost: 1;
|
|
98
|
+
readonly maxInputTokens: 1050000;
|
|
99
|
+
readonly maxOutputTokens: 128000;
|
|
100
|
+
readonly isMultiModal: true;
|
|
101
|
+
readonly tabs: readonly ["all", "cheap", "multimodal"];
|
|
72
102
|
}];
|
|
73
103
|
export type ChatReasoningEffortOption = {
|
|
74
104
|
value: ChatReasoningEffort;
|
|
@@ -11,6 +11,10 @@ export const chatModelOptions = [
|
|
|
11
11
|
{ value: 'claude-sonnet-5', label: 'Claude Sonnet 5', description: 'Agente rápido para tarefas avançadas', provider: 'Anthropic', cost: 4, maxInputTokens: 1_000_000, maxOutputTokens: 64_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
|
|
12
12
|
{ value: 'claude-sonnet-4-6', label: 'Claude 4.6 Sonnet', description: 'Agente rápido para tarefas avançadas', provider: 'Anthropic', cost: 4, maxInputTokens: 1_000_000, maxOutputTokens: 64_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
|
|
13
13
|
{ value: 'claude-haiku-4-5', label: 'Claude 4.5 Haiku', description: 'Agente rápido para tarefas simples', provider: 'Anthropic', cost: 3, maxInputTokens: 200_000, maxOutputTokens: 64_000, isMultiModal: true, tabs: ['all', 'cheap', 'multimodal'] },
|
|
14
|
+
// Agent API catalog: openai/ IDs select the existing gateway route; cost is its 1–5 tier.
|
|
15
|
+
{ value: 'openai/gpt-5.6-sol', label: 'GPT-5.6 Sol', description: 'Modelo OpenAI para tarefas complexas', provider: 'OpenAI', cost: 5, maxInputTokens: 1_050_000, maxOutputTokens: 128_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
|
|
16
|
+
{ value: 'openai/gpt-5.6-terra', label: 'GPT-5.6 Terra', description: 'Equilíbrio entre capacidade e custo', provider: 'OpenAI', cost: 3, maxInputTokens: 1_050_000, maxOutputTokens: 128_000, isMultiModal: true, tabs: ['all', 'advanced', 'multimodal'] },
|
|
17
|
+
{ value: 'openai/gpt-5.6-luna', label: 'GPT-5.6 Luna', description: 'Modelo OpenAI rápido e econômico', provider: 'OpenAI', cost: 1, maxInputTokens: 1_050_000, maxOutputTokens: 128_000, isMultiModal: true, tabs: ['all', 'cheap', 'multimodal'] },
|
|
14
18
|
];
|
|
15
19
|
// Ordered from least to most effort so the composer can lay them out as slider stops.
|
|
16
20
|
export const chatReasoningEffortOptions = [
|
|
@@ -280,6 +280,9 @@ export declare const telaAgentWakeRequestSchema: z.ZodObject<{
|
|
|
280
280
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
281
281
|
"claude-sonnet-4-6": "claude-sonnet-4-6";
|
|
282
282
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
283
|
+
"openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
|
|
284
|
+
"openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
|
|
285
|
+
"openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
|
|
283
286
|
}>>;
|
|
284
287
|
}, z.core.$strip>;
|
|
285
288
|
export type TelaAgentWakeRequest = z.infer<typeof telaAgentWakeRequestSchema>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { chatModelOptions } from "../types/schemas/chat/ui.js";
|
|
1
2
|
export function formatTokenCount(count) {
|
|
2
3
|
if (count >= 1e6) {
|
|
3
4
|
const val = count / 1e6;
|
|
@@ -19,6 +20,9 @@ export function formatCredits(credits) {
|
|
|
19
20
|
return `${Math.round(credits)} cr\xE9ditos`;
|
|
20
21
|
}
|
|
21
22
|
export function formatModelName(model) {
|
|
23
|
+
const openAiModel = chatModelOptions.find((option) => option.provider === "OpenAI" && (option.value === model || option.value === `openai/${model}`));
|
|
24
|
+
if (openAiModel)
|
|
25
|
+
return openAiModel.label;
|
|
22
26
|
const id = model.replace(/^claude-/, "");
|
|
23
27
|
const newFormat = id.match(/^(opus|sonnet|haiku)-(\d+)-(\d+)/);
|
|
24
28
|
if (newFormat) {
|