@meistrari/chat-nuxt 3.14.1 → 3.14.2-rc.1

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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "3.14.1-rc.0",
4
+ "version": "3.14.2-rc.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -1,7 +1,7 @@
1
1
  import { useChatApi } from "../../shared/composables/chat-api.js";
2
2
  const WAKE_POLL_INTERVAL_MS = 1500;
3
- const WAKE_BUDGET_MS = 3e4;
4
- const WAKE_SETTLE_TIMEOUT_MS = 35e3;
3
+ const WAKE_BUDGET_MS = 6e4;
4
+ const WAKE_SETTLE_TIMEOUT_MS = 65e3;
5
5
  export async function pollAgentWake(options) {
6
6
  const now = options.now ?? Date.now;
7
7
  const deadline = now() + (options.budgetMs ?? WAKE_BUDGET_MS);
@@ -2,10 +2,11 @@
2
2
  import { useFileUpload } from "../../files/composables/file-upload";
3
3
  import { MAX_FILE_SIZE, MAX_FILES } from "../../files/file-limits";
4
4
  import { useInputHistory } from "../composables/input-history";
5
- import { useMentionAutocomplete } from "../composables/mention-autocomplete";
5
+ import { appendAttachmentMentions, useMentionAutocomplete } from "../composables/mention-autocomplete";
6
6
  import { getAgentInputReadiness } from "../input-readiness";
7
+ import { buildFileMentionItems } from "../mention-file-items";
7
8
  import { formatFileSize } from "../../files/formatting";
8
- import { getFileIcon, getMimeIcon, getMimeLabel } from "../../files/icons";
9
+ import { getFileIcon } from "../../files/icons";
9
10
  const props = defineProps({
10
11
  disabled: { type: Boolean, required: false },
11
12
  loading: { type: Boolean, required: false },
@@ -61,51 +62,16 @@ const hasUnmappedAgentFiles = computed(() => agentInputReadiness.value.hasUnmapp
61
62
  const messagesRef = computed(() => props.messages ?? []);
62
63
  const inputHistory = useInputHistory(messagesRef);
63
64
  let restoringHistory = false;
64
- const fileItems = computed(() => {
65
- const items = [];
66
- if (props.conversationFiles) {
67
- for (const f of props.conversationFiles) {
68
- items.push({
69
- id: f.url,
70
- label: f.name,
71
- description: getMimeLabel(f.mimeType),
72
- icon: getMimeIcon(f.mimeType),
73
- iconUrl: getFileIcon(f.mimeType),
74
- category: "file"
75
- });
76
- }
77
- }
78
- if (props.workspaceSettings?.contextFiles) {
79
- for (const f of props.workspaceSettings.contextFiles) {
80
- if (!items.some((i) => i.id === f.url)) {
81
- items.push({
82
- id: f.url,
83
- label: f.name,
84
- description: "Contexto",
85
- icon: getMimeIcon(f.mimeType),
86
- iconUrl: getFileIcon(f.mimeType),
87
- category: "file"
88
- });
89
- }
90
- }
91
- }
92
- if (props.agentContextFiles) {
93
- for (const f of props.agentContextFiles) {
94
- if (!items.some((i) => i.id === f.path)) {
95
- items.push({
96
- id: f.path,
97
- label: f.name,
98
- description: "Contexto do agente",
99
- icon: getMimeIcon(),
100
- iconUrl: getFileIcon(),
101
- category: "file",
102
- idKind: "path"
103
- });
104
- }
105
- }
106
- }
107
- return items;
108
- });
65
+ const fileItems = computed(() => buildFileMentionItems({
66
+ pendingFiles: pendingFiles.value.map((file) => ({
67
+ id: file.id,
68
+ name: file.file.name,
69
+ mimeType: getPendingFileMimeType(file)
70
+ })),
71
+ conversationFiles: props.conversationFiles,
72
+ workspaceContextFiles: props.workspaceSettings?.contextFiles,
73
+ agentContextFiles: props.agentContextFiles
74
+ }));
109
75
  const skillItems = computed(() => {
110
76
  const items = [];
111
77
  if (props.agentSkills) {
@@ -221,6 +187,8 @@ function queueFiles(files, inputName) {
221
187
  }
222
188
  function removeFile(id) {
223
189
  pendingFiles.value = pendingFiles.value.filter((file) => file.id !== id);
190
+ mentionAutocomplete.removeFileMentions(id);
191
+ handleInput();
224
192
  }
225
193
  function updatePendingFileInput(id, event) {
226
194
  const value = event.target.value;
@@ -295,12 +263,19 @@ async function handleSend() {
295
263
  } catch {
296
264
  return;
297
265
  }
266
+ const mentionResolutions = {};
267
+ queuedFiles.forEach((queued, index) => {
268
+ const url = files[index]?.url;
269
+ if (url)
270
+ mentionResolutions[queued.id] = url;
271
+ });
272
+ mentionAutocomplete.resolveFileMentions(mentionResolutions);
298
273
  }
299
- const taggedContent = mentionAutocomplete.buildTaggedContent();
274
+ const taggedContent = appendAttachmentMentions(mentionAutocomplete.buildTaggedContent().trim(), files);
300
275
  const agentInputs = buildAgentInputs(files, queuedFiles);
301
276
  emit(
302
277
  "send",
303
- taggedContent.trim(),
278
+ taggedContent,
304
279
  files,
305
280
  agentInputs.length > 0 ? agentInputs : void 0
306
281
  );
@@ -12,5 +12,9 @@ export declare function findMentionTrigger(textBeforeCursor: string): {
12
12
  query: string;
13
13
  offset: number;
14
14
  } | null;
15
+ export declare function appendAttachmentMentions(content: string, files: readonly {
16
+ name: string;
17
+ url: string;
18
+ }[]): string;
15
19
  export declare function useMentionAutocomplete(options: UseMentionAutocompleteOptions): any;
16
20
  export {};
@@ -21,6 +21,13 @@ export function findMentionTrigger(textBeforeCursor) {
21
21
  }
22
22
  return null;
23
23
  }
24
+ function escapeAttr(value) {
25
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
26
+ }
27
+ export function appendAttachmentMentions(content, files) {
28
+ const tags = files.filter((file) => !content.includes(`url="${escapeAttr(file.url)}"`)).map((file) => `<file_mention name="${escapeAttr(file.name)}" url="${escapeAttr(file.url)}">O usu\xE1rio anexou este arquivo a esta mensagem.</file_mention>`);
29
+ return [content, ...tags].filter(Boolean).join("\n");
30
+ }
24
31
  export function useMentionAutocomplete(options) {
25
32
  const { editorRef, fileItems, skillItems, disabled, onInsert } = options;
26
33
  const activeTrigger = ref(null);
@@ -144,9 +151,6 @@ export function useMentionAutocomplete(options) {
144
151
  }
145
152
  return walkNode(editor);
146
153
  }
147
- function escapeAttr(value) {
148
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
149
- }
150
154
  function selectItem(item) {
151
155
  if (!editorRef.value || triggerTextOffset < 0)
152
156
  return;
@@ -225,6 +229,8 @@ export function useMentionAutocomplete(options) {
225
229
  const pathAttr = mentionId ? ` path="${escapeAttr(mentionId)}"` : "";
226
230
  return `<file_mention name="${escapeAttr(mentionLabel ?? "")}"${pathAttr}>Este arquivo est\xE1 no seu reposit\xF3rio de trabalho. Leia-o pelo caminho e foque nele na sua resposta.</file_mention>`;
227
231
  }
232
+ if (mentionIdKind === "pending")
233
+ return `<file_mention name="${escapeAttr(mentionLabel ?? "")}">Foque neste arquivo espec\xEDfico na sua resposta.</file_mention>`;
228
234
  const urlAttr = mentionId ? ` url="${escapeAttr(mentionId)}"` : "";
229
235
  return `<file_mention name="${escapeAttr(mentionLabel ?? "")}"${urlAttr}>Foque neste arquivo espec\xEDfico na sua resposta.</file_mention>`;
230
236
  }
@@ -268,6 +274,54 @@ ${varLines}
268
274
  result += walkNode(child);
269
275
  return result.replace(/\u00A0/g, " ");
270
276
  }
277
+ function walkMentionPills(node, visit) {
278
+ if (node.nodeType !== Node.ELEMENT_NODE)
279
+ return;
280
+ const el = node;
281
+ if (el.contentEditable === "false" && el.dataset.mentionCategory) {
282
+ visit(el);
283
+ return;
284
+ }
285
+ for (const child of el.childNodes ?? [])
286
+ walkMentionPills(child, visit);
287
+ }
288
+ function resolveFileMentions(resolutions) {
289
+ if (!editorRef.value)
290
+ return;
291
+ for (const child of editorRef.value.childNodes) {
292
+ walkMentionPills(child, (el) => {
293
+ if (el.dataset.mentionIdKind !== "pending")
294
+ return;
295
+ const resolvedUrl = el.dataset.mentionId ? resolutions[el.dataset.mentionId] : void 0;
296
+ if (!resolvedUrl)
297
+ return;
298
+ el.dataset.mentionId = resolvedUrl;
299
+ delete el.dataset.mentionIdKind;
300
+ });
301
+ }
302
+ }
303
+ function removeFileMentions(mentionId) {
304
+ if (!editorRef.value)
305
+ return;
306
+ const pills = [];
307
+ for (const child of editorRef.value.childNodes) {
308
+ walkMentionPills(child, (el) => {
309
+ if (el.dataset.mentionCategory === "file" && el.dataset.mentionId === mentionId)
310
+ pills.push(el);
311
+ });
312
+ }
313
+ for (const pill of pills) {
314
+ const next = pill.nextSibling;
315
+ if (next?.nodeType === Node.TEXT_NODE && next.textContent?.startsWith("\xA0")) {
316
+ const rest = next.textContent.slice(1);
317
+ if (rest)
318
+ next.textContent = rest;
319
+ else
320
+ next.remove();
321
+ }
322
+ pill.remove();
323
+ }
324
+ }
271
325
  function resetMentions() {
272
326
  if (editorRef.value)
273
327
  editorRef.value.innerHTML = "";
@@ -325,6 +379,8 @@ ${varLines}
325
379
  dismiss,
326
380
  detectTrigger,
327
381
  buildTaggedContent,
382
+ resolveFileMentions,
383
+ removeFileMentions,
328
384
  resetMentions
329
385
  });
330
386
  }
@@ -0,0 +1,25 @@
1
+ import type { MentionItem } from '../types/schemas/chat/ui.js';
2
+ export type FileMentionItem = MentionItem & {
3
+ iconUrl: string;
4
+ };
5
+ type PendingFileSource = {
6
+ id: string;
7
+ name: string;
8
+ mimeType?: string;
9
+ };
10
+ type UrlFileSource = {
11
+ url: string;
12
+ name: string;
13
+ mimeType?: string;
14
+ };
15
+ type AgentFileSource = {
16
+ path: string;
17
+ name: string;
18
+ };
19
+ export declare function buildFileMentionItems(options: {
20
+ pendingFiles?: readonly PendingFileSource[];
21
+ conversationFiles?: readonly UrlFileSource[];
22
+ workspaceContextFiles?: readonly UrlFileSource[];
23
+ agentContextFiles?: readonly AgentFileSource[];
24
+ }): FileMentionItem[];
25
+ export {};
@@ -0,0 +1,59 @@
1
+ import { getFileIcon, getMimeIcon, getMimeLabel } from "../files/icons.js";
2
+ export function buildFileMentionItems(options) {
3
+ const items = [];
4
+ if (options.pendingFiles) {
5
+ for (const f of options.pendingFiles) {
6
+ items.push({
7
+ id: f.id,
8
+ label: f.name,
9
+ description: "Anexo desta mensagem",
10
+ icon: getMimeIcon(f.mimeType),
11
+ iconUrl: getFileIcon(f.mimeType),
12
+ category: "file",
13
+ idKind: "pending"
14
+ });
15
+ }
16
+ }
17
+ if (options.conversationFiles) {
18
+ for (const f of options.conversationFiles) {
19
+ items.push({
20
+ id: f.url,
21
+ label: f.name,
22
+ description: getMimeLabel(f.mimeType),
23
+ icon: getMimeIcon(f.mimeType),
24
+ iconUrl: getFileIcon(f.mimeType),
25
+ category: "file"
26
+ });
27
+ }
28
+ }
29
+ if (options.workspaceContextFiles) {
30
+ for (const f of options.workspaceContextFiles) {
31
+ if (!items.some((i) => i.id === f.url)) {
32
+ items.push({
33
+ id: f.url,
34
+ label: f.name,
35
+ description: "Contexto",
36
+ icon: getMimeIcon(f.mimeType),
37
+ iconUrl: getFileIcon(f.mimeType),
38
+ category: "file"
39
+ });
40
+ }
41
+ }
42
+ }
43
+ if (options.agentContextFiles) {
44
+ for (const f of options.agentContextFiles) {
45
+ if (!items.some((i) => i.id === f.path)) {
46
+ items.push({
47
+ id: f.path,
48
+ label: f.name,
49
+ description: "Contexto do agente",
50
+ icon: getMimeIcon(),
51
+ iconUrl: getFileIcon(),
52
+ category: "file",
53
+ idKind: "path"
54
+ });
55
+ }
56
+ }
57
+ }
58
+ return items;
59
+ }
@@ -88,9 +88,10 @@ export declare const mentionItemVariableSchema: z.ZodObject<{
88
88
  text: "text";
89
89
  }>;
90
90
  }, z.core.$strip>;
91
- export declare const mentionItemIdKinds: readonly ["url", "path"];
91
+ export declare const mentionItemIdKinds: readonly ["url", "path", "pending"];
92
92
  export declare const mentionItemIdKindSchema: z.ZodEnum<{
93
93
  path: "path";
94
+ pending: "pending";
94
95
  url: "url";
95
96
  }>;
96
97
  export type MentionItemIdKind = z.infer<typeof mentionItemIdKindSchema>;
@@ -106,6 +107,7 @@ export declare const mentionItemSchema: z.ZodObject<{
106
107
  }>;
107
108
  idKind: z.ZodOptional<z.ZodEnum<{
108
109
  path: "path";
110
+ pending: "pending";
109
111
  url: "url";
110
112
  }>>;
111
113
  variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -22,7 +22,7 @@ export const mentionItemVariableSchema = z.object({
22
22
  description: z.string(),
23
23
  type: mentionItemVariableTypeSchema,
24
24
  });
25
- export const mentionItemIdKinds = ['url', 'path'];
25
+ export const mentionItemIdKinds = ['url', 'path', 'pending'];
26
26
  export const mentionItemIdKindSchema = z.enum(mentionItemIdKinds);
27
27
  export const mentionItemSchema = z.object({
28
28
  id: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "3.14.1",
3
+ "version": "3.14.2-rc.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {