@meistrari/chat-nuxt 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +26 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +4 -3
  4. package/dist/runtime/citations/citation-links.d.ts +32 -0
  5. package/dist/runtime/citations/citation-links.js +56 -0
  6. package/dist/runtime/citations/components/chat-citation.d.vue.ts +9 -0
  7. package/dist/runtime/citations/components/chat-citation.vue +186 -0
  8. package/dist/runtime/citations/components/chat-citation.vue.d.ts +9 -0
  9. package/dist/runtime/citations/components/citation-document-panel.d.vue.ts +3 -0
  10. package/dist/runtime/citations/components/citation-document-panel.vue +241 -0
  11. package/dist/runtime/citations/components/citation-document-panel.vue.d.ts +3 -0
  12. package/dist/runtime/citations/components/citation-highlight.d.vue.ts +19 -0
  13. package/dist/runtime/citations/components/citation-highlight.vue +16 -0
  14. package/dist/runtime/citations/components/citation-highlight.vue.d.ts +19 -0
  15. package/dist/runtime/citations/composables/citation-panel.d.ts +8 -0
  16. package/dist/runtime/citations/composables/citation-panel.js +17 -0
  17. package/dist/runtime/citations/composables/citations.d.ts +17 -0
  18. package/dist/runtime/citations/composables/citations.js +44 -0
  19. package/dist/runtime/citations/document-highlight.d.ts +14 -0
  20. package/dist/runtime/citations/document-highlight.js +43 -0
  21. package/dist/runtime/citations/types.d.ts +57 -0
  22. package/dist/runtime/citations/types.js +0 -0
  23. package/dist/runtime/conversations/components/chat-conversation-list.vue +2 -2
  24. package/dist/runtime/embed/components/chat-embed-inner.vue +15 -0
  25. package/dist/runtime/embed/components/chat-embed.vue +2 -0
  26. package/dist/runtime/embed/components/configuration/chat-configuration-knowledge-sources-tab.d.vue.ts +2 -2
  27. package/dist/runtime/embed/components/configuration/chat-configuration-knowledge-sources-tab.vue.d.ts +2 -2
  28. package/dist/runtime/embed/components/meistrari-chat-embed.vue +3 -0
  29. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue +2 -2
  30. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +2 -2
  31. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +2 -2
  32. package/dist/runtime/embed/composables/embed-config.d.ts +3 -0
  33. package/dist/runtime/embed/composables/embed-config.js +9 -0
  34. package/dist/runtime/embed/types.d.ts +12 -1
  35. package/dist/runtime/files/components/chat-vault-link.vue +13 -0
  36. package/dist/runtime/files/content-detection.d.ts +9 -1
  37. package/dist/runtime/files/content-detection.js +29 -8
  38. package/dist/runtime/messages/components/chat-message-bubble.vue +7 -3
  39. package/dist/runtime/messages/components/reasoning/chat-reasoning-message.vue +22 -266
  40. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +2 -3
  41. package/dist/runtime/messages/tool-registry.d.ts +15 -0
  42. package/dist/runtime/messages/tool-registry.js +138 -0
  43. package/dist/runtime/types/citations.d.ts +1 -0
  44. package/dist/runtime/types/citations.js +0 -0
  45. package/dist/runtime/types/embed.d.ts +1 -0
  46. package/package.json +1 -1
  47. package/dist/runtime/messages/composables/tool-formatting.d.ts +0 -5
  48. package/dist/runtime/messages/composables/tool-formatting.js +0 -61
  49. package/dist/runtime/shared/composables/verb-conjugation.d.ts +0 -7
  50. package/dist/runtime/shared/composables/verb-conjugation.js +0 -269
@@ -0,0 +1,19 @@
1
+ import type { CustomMarkdownNode } from '../../rendering/markdown/custom-components.js';
2
+ type __VLS_Props = {
3
+ node: CustomMarkdownNode;
4
+ occurrence: string;
5
+ target?: boolean | string;
6
+ };
7
+ declare var __VLS_1: {};
8
+ type __VLS_Slots = {} & {
9
+ default?: (props: typeof __VLS_1) => any;
10
+ };
11
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
15
+ type __VLS_WithSlots<T, S> = T & {
16
+ new (): {
17
+ $slots: S;
18
+ };
19
+ };
@@ -0,0 +1,8 @@
1
+ import type { CitationRef } from '../citation-links.js';
2
+ import type { CitationMeta } from '../types.js';
3
+ export declare function useCitationPanel(): {
4
+ isOpen: any;
5
+ active: any;
6
+ openCitationDocument: (citation: CitationRef, meta: CitationMeta | null) => void;
7
+ closeCitationDocument: () => void;
8
+ };
@@ -0,0 +1,17 @@
1
+ const isOpen = ref(false);
2
+ const active = ref(null);
3
+ export function useCitationPanel() {
4
+ function openCitationDocument(citation, meta) {
5
+ active.value = { citation, meta };
6
+ isOpen.value = true;
7
+ }
8
+ function closeCitationDocument() {
9
+ isOpen.value = false;
10
+ }
11
+ return {
12
+ isOpen: readonly(isOpen),
13
+ active: readonly(active),
14
+ openCitationDocument,
15
+ closeCitationDocument
16
+ };
17
+ }
@@ -0,0 +1,17 @@
1
+ import type { Ref } from 'vue';
2
+ import { type CitationRef } from '../citation-links.js';
3
+ import type { ChatCitationsConfig, CitationMeta, CitationStatus } from '../types.js';
4
+ export type CitationEntry = {
5
+ status: Ref<CitationStatus>;
6
+ meta: Ref<CitationMeta | null>;
7
+ };
8
+ /**
9
+ * Resolution cache shared by every chip in the session: one `resolve` call per document,
10
+ * no matter how many times (or with which locators) it is cited. Keyed by config identity
11
+ * so embeds with different resolvers (different backends/authorization scopes) never
12
+ * reuse each other's results.
13
+ */
14
+ export declare function useCitations(): {
15
+ resolveCitation: (config: ChatCitationsConfig, citation: CitationRef) => CitationEntry;
16
+ retryCitation: (config: ChatCitationsConfig, citation: CitationRef) => CitationEntry;
17
+ };
@@ -0,0 +1,44 @@
1
+ import { citationCacheKey } from "../citation-links.js";
2
+ export function useCitations() {
3
+ const nuxtApp = useNuxtApp();
4
+ const caches = nuxtApp._chatCitationEntries ??= /* @__PURE__ */ new WeakMap();
5
+ function entriesFor(config) {
6
+ let entries = caches.get(config);
7
+ if (!entries) {
8
+ entries = /* @__PURE__ */ new Map();
9
+ caches.set(config, entries);
10
+ }
11
+ return entries;
12
+ }
13
+ function resolveInto(entry, config, citation) {
14
+ entry.status.value = "loading";
15
+ config.resolve(citation).then((meta) => {
16
+ entry.meta.value = meta;
17
+ entry.status.value = meta ? "resolved" : "unresolved";
18
+ }).catch(() => {
19
+ entry.status.value = "unresolved";
20
+ });
21
+ }
22
+ function resolveCitation(config, citation) {
23
+ const entries = entriesFor(config);
24
+ const key = citationCacheKey(citation);
25
+ const existing = entries.get(key);
26
+ if (existing)
27
+ return existing;
28
+ const entry = {
29
+ status: ref("loading"),
30
+ meta: ref(null)
31
+ };
32
+ entries.set(key, entry);
33
+ resolveInto(entry, config, citation);
34
+ return entry;
35
+ }
36
+ function retryCitation(config, citation) {
37
+ const existing = entriesFor(config).get(citationCacheKey(citation));
38
+ if (!existing)
39
+ return resolveCitation(config, citation);
40
+ resolveInto(existing, config, citation);
41
+ return existing;
42
+ }
43
+ return { resolveCitation, retryCitation };
44
+ }
@@ -0,0 +1,14 @@
1
+ import type { CitationOccurrence } from './types.js';
2
+ export type HighlightSegment = {
3
+ text: string;
4
+ /** 1-based occurrence number when this segment is a highlighted match. */
5
+ occurrence?: number;
6
+ };
7
+ export declare function escapeCitationMarkup(text: string, tagName: string, context: 'document' | 'highlight'): string;
8
+ /**
9
+ * Fallback when the host provides no `locate` callback. Plain case-insensitive matching:
10
+ * accent variants won't match, which degrades to "term not found" rather than breaking.
11
+ */
12
+ export declare function locateTermClientSide(content: string, term: string): CitationOccurrence[];
13
+ /** Occurrence ranges are host data: invalid, out-of-bounds and overlapping ranges are dropped. */
14
+ export declare function splitContentByOccurrences(content: string, occurrences: CitationOccurrence[]): HighlightSegment[];
@@ -0,0 +1,43 @@
1
+ export function escapeCitationMarkup(text, tagName, context) {
2
+ if (context === "highlight") {
3
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
4
+ }
5
+ const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6
+ const citationTag = new RegExp(`<\\/?${escapedTagName}(?=[\\s>])`, "gi");
7
+ return text.replace(citationTag, (match) => `&lt;${match.slice(1)}`);
8
+ }
9
+ export function locateTermClientSide(content, term) {
10
+ const needle = term.toLowerCase();
11
+ if (!needle)
12
+ return [];
13
+ const haystack = content.toLowerCase();
14
+ const occurrences = [];
15
+ let cursor = 0;
16
+ while (true) {
17
+ const index = haystack.indexOf(needle, cursor);
18
+ if (index === -1)
19
+ break;
20
+ occurrences.push({ offset: index, end: index + needle.length });
21
+ cursor = index + needle.length;
22
+ }
23
+ return occurrences;
24
+ }
25
+ export function splitContentByOccurrences(content, occurrences) {
26
+ const ordered = [...occurrences].filter((occurrence) => Number.isInteger(occurrence.offset) && Number.isInteger(occurrence.end) && occurrence.offset >= 0 && occurrence.end > occurrence.offset && occurrence.offset < content.length).sort((a, b) => a.offset - b.offset);
27
+ const segments = [];
28
+ let lastEnd = 0;
29
+ let count = 0;
30
+ for (const occurrence of ordered) {
31
+ if (occurrence.offset < lastEnd)
32
+ continue;
33
+ const end = Math.min(occurrence.end, content.length);
34
+ if (occurrence.offset > lastEnd)
35
+ segments.push({ text: content.slice(lastEnd, occurrence.offset) });
36
+ count += 1;
37
+ segments.push({ text: content.slice(occurrence.offset, end), occurrence: count });
38
+ lastEnd = end;
39
+ }
40
+ if (lastEnd < content.length)
41
+ segments.push({ text: content.slice(lastEnd) });
42
+ return segments.length > 0 ? segments : [{ text: content }];
43
+ }
@@ -0,0 +1,57 @@
1
+ import type { Component } from 'vue';
2
+ import type { CitationRef } from './citation-links.js';
3
+ export type { CitationLocator, CitationRef } from './citation-links.js';
4
+ export type CitationMeta = {
5
+ /** Human-readable document title. Hosts must never surface internal ids here. */
6
+ title: string;
7
+ /** Display name of the base/source the document belongs to. */
8
+ sourceLabel?: string;
9
+ /** Identifier of the document at its origin (case number, súmula number, ...). */
10
+ identifier?: string;
11
+ /** Link to the document at its original source, when one exists. */
12
+ url?: string;
13
+ /** ISO date associated with the document. */
14
+ date?: string;
15
+ /** What `date` refers to (julgamento, aprovação, publicação, ...). */
16
+ dateLabel?: string;
17
+ /** Short excerpt shown in the citation popover. */
18
+ snippet?: string;
19
+ /** Status badge, e.g. a súmula that is no longer in force. */
20
+ badge?: {
21
+ label: string;
22
+ tone?: 'neutral' | 'warning' | 'danger';
23
+ };
24
+ };
25
+ export type CitationDocument = {
26
+ /** Plain-text document content as stored by the host. */
27
+ content: string;
28
+ /** True when the host cut `content` short; the panel discloses it. */
29
+ truncated?: boolean;
30
+ };
31
+ export type CitationOccurrence = {
32
+ /** Inclusive char offset of the occurrence in the document content. */
33
+ offset: number;
34
+ /** Exclusive end offset of the occurrence. */
35
+ end: number;
36
+ };
37
+ /**
38
+ * Host-provided citation behavior. The lib owns detection, rendering and interaction
39
+ * states; the host owns data access (and with it, authorization: every callback runs
40
+ * against the host's own backend with the viewing user's identity).
41
+ *
42
+ * A `resolve` that returns `null` renders the citation as unverified — deliberately
43
+ * indistinguishable between "does not exist" and "not visible to this viewer".
44
+ */
45
+ export type ChatCitationsConfig = {
46
+ /** Resolve a citation to display metadata; `null` marks it unverified. */
47
+ resolve: (ref: CitationRef) => Promise<CitationMeta | null>;
48
+ /** Fetch the document body; providing this enables the built-in document panel. */
49
+ getDocument?: (ref: CitationRef) => Promise<CitationDocument | null>;
50
+ /** Locate a term's occurrences for highlighting; absent = client-side best effort. */
51
+ locate?: (ref: CitationRef, term: string) => Promise<CitationOccurrence[] | null>;
52
+ /** Replaces the built-in citation chip. */
53
+ component?: Component;
54
+ /** Takes over the click entirely; the built-in panel is not opened. */
55
+ onOpen?: (ref: CitationRef, meta: CitationMeta | null) => void;
56
+ };
57
+ export type CitationStatus = 'loading' | 'resolved' | 'unresolved';
File without changes
@@ -84,7 +84,7 @@ const {
84
84
 
85
85
  <template v-else>
86
86
  <div
87
- v-if="showGroupCreationPrompt"
87
+ v-if="showGroupCreationPrompt && !hideFolderHint"
88
88
  mx-8px
89
89
  flex="~ col" gap-10px
90
90
  >
@@ -113,7 +113,7 @@ const {
113
113
  <span h-10px w-84px rounded-full bg-neutral-100 />
114
114
  </div>
115
115
  </div>
116
- <p v-if="!hideFolderHint" px-4px text-14px text-neutral-500 leading-20px>
116
+ <p px-4px text-14px text-neutral-500 leading-20px>
117
117
  Suas pastas aparecem aqui. Crie uma para começar a agrupar conversas.
118
118
  </p>
119
119
  <ChatConversationNewGroupButton label="Criar pasta" variant="empty-state" />
@@ -81,6 +81,7 @@ const effectiveWorkspaceSettings = computed(() => {
81
81
  });
82
82
  const { closePanel: closeFilesPanel } = useConversationFilesPanel();
83
83
  const { isOpen: isPreviewPanelOpen, panelWidth: previewPanelWidth, currentFile: currentPreviewFile, closePreview } = useFilePreviewPanel();
84
+ const { isOpen: isCitationPanelOpen } = useCitationPanel();
84
85
  const activeConversationId = ref(
85
86
  normalizeConversationId(props.conversationId) ?? normalizeConversationId(props.initialConversationId)
86
87
  );
@@ -975,6 +976,20 @@ async function handleDelete() {
975
976
  >
976
977
  <FilePreviewPanel />
977
978
  </div>
979
+
980
+ <div
981
+ v-if="isCitationPanelOpen && !isMobile"
982
+ h-full flex-shrink-0 pt-64px
983
+ w-440px
984
+ >
985
+ <CitationDocumentPanel />
986
+ </div>
987
+ <div
988
+ v-else-if="isCitationPanelOpen && isMobile"
989
+ fixed inset-0 z-50 bg-neutral-50
990
+ >
991
+ <CitationDocumentPanel />
992
+ </div>
978
993
  </div>
979
994
  </template>
980
995
 
@@ -20,6 +20,7 @@ const props = defineProps({
20
20
  loadingMessagesMode: { type: [String, null], required: false },
21
21
  customComponents: { type: Object, required: false },
22
22
  customHtmlTags: { type: Array, required: false },
23
+ citations: { type: [Object, null], required: false },
23
24
  feedbackConfig: { type: [Object, null], required: false },
24
25
  messageFeedback: { type: [Object, null], required: false },
25
26
  telaAgentId: { type: null, required: false },
@@ -49,6 +50,7 @@ provideEmbedConfig({
49
50
  loadingMessagesMode: computed(() => props.loadingMessagesMode),
50
51
  customComponents: computed(() => props.customComponents),
51
52
  customHtmlTags: computed(() => props.customHtmlTags),
53
+ citations: computed(() => props.citations),
52
54
  feedbackConfig: computed(() => props.feedbackConfig),
53
55
  messageFeedback: computed(() => props.messageFeedback)
54
56
  }, { syncGlobal: !parentEmbedConfig });
@@ -8,11 +8,11 @@ type __VLS_Props = {
8
8
  getProjectNameForWorkstation: (w: TelaWorkstation) => string;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
11
- "update:searchQuery": (value: string) => any;
12
11
  toggle: (sourceId: string) => any;
12
+ "update:searchQuery": (value: string) => any;
13
13
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
14
- "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
15
14
  onToggle?: ((sourceId: string) => any) | undefined;
15
+ "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
16
16
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
17
  declare const _default: typeof __VLS_export;
18
18
  export default _default;
@@ -8,11 +8,11 @@ type __VLS_Props = {
8
8
  getProjectNameForWorkstation: (w: TelaWorkstation) => string;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
11
- "update:searchQuery": (value: string) => any;
12
11
  toggle: (sourceId: string) => any;
12
+ "update:searchQuery": (value: string) => any;
13
13
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
14
- "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
15
14
  onToggle?: ((sourceId: string) => any) | undefined;
15
+ "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
16
16
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
17
  declare const _default: typeof __VLS_export;
18
18
  export default _default;
@@ -13,6 +13,7 @@ const props = defineProps({
13
13
  loadingMessagesMode: { type: [String, null], required: false },
14
14
  customComponents: { type: Object, required: false },
15
15
  customHtmlTags: { type: Array, required: false },
16
+ citations: { type: [Object, null], required: false },
16
17
  feedbackConfig: { type: [Object, null], required: false },
17
18
  messageFeedback: { type: [Object, null], required: false },
18
19
  telaAgentId: { type: null, required: false },
@@ -44,6 +45,7 @@ const hideSettings = computed(() => props.hideSettings ?? false);
44
45
  :loading-messages-mode="loadingMessagesMode"
45
46
  :custom-components="customComponents"
46
47
  :custom-html-tags="customHtmlTags"
48
+ :citations="citations"
47
49
  :feedback-config="feedbackConfig"
48
50
  :message-feedback="messageFeedback"
49
51
  @update:conversation-id="$emit('update:conversationId', $event)"
@@ -72,6 +74,7 @@ const hideSettings = computed(() => props.hideSettings ?? false);
72
74
  :loading-messages-mode="loadingMessagesMode"
73
75
  :custom-components="customComponents"
74
76
  :custom-html-tags="customHtmlTags"
77
+ :citations="citations"
75
78
  :feedback-config="feedbackConfig"
76
79
  :message-feedback="messageFeedback"
77
80
  @update:conversation-id="$emit('update:conversationId', $event)"
@@ -310,7 +310,7 @@ async function handleConversationDrop(event, groupId) {
310
310
  </div>
311
311
  <div v-else flex="~ col" gap-8px>
312
312
  <div
313
- v-if="showGroupCreationPrompt"
313
+ v-if="showGroupCreationPrompt && !hideFolderHint"
314
314
  mx-4px
315
315
  flex="~ col" gap-10px
316
316
  >
@@ -339,7 +339,7 @@ async function handleConversationDrop(event, groupId) {
339
339
  <span h-10px w-84px rounded-full bg-neutral-100 />
340
340
  </div>
341
341
  </div>
342
- <p v-if="!hideFolderHint" px-4px text-14px text-neutral-500 leading-20px>
342
+ <p px-4px text-14px text-neutral-500 leading-20px>
343
343
  Suas pastas aparecem aqui. Crie uma para começar a agrupar conversas.
344
344
  </p>
345
345
  <ChatConversationNewGroupButton label="Criar pasta" variant="empty-state" />
@@ -57,12 +57,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
57
57
  "update:selectedModel": (value: string | null) => any;
58
58
  } & {
59
59
  delete: () => any;
60
+ cancel: () => any;
60
61
  rename: () => any;
61
62
  duplicate: () => any;
62
63
  export: () => any;
63
64
  copyLink: () => any;
64
65
  selectConversation: (id: string) => any;
65
- cancel: () => any;
66
66
  settings: () => any;
67
67
  send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
68
68
  debug: () => any;
@@ -78,12 +78,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
78
78
  "update:workspaceSheetOpen": (value: boolean) => any;
79
79
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
80
80
  onDelete?: (() => any) | undefined;
81
+ onCancel?: (() => any) | undefined;
81
82
  onRename?: (() => any) | undefined;
82
83
  onDuplicate?: (() => any) | undefined;
83
84
  onExport?: (() => any) | undefined;
84
85
  onCopyLink?: (() => any) | undefined;
85
86
  onSelectConversation?: ((id: string) => any) | undefined;
86
- onCancel?: (() => any) | undefined;
87
87
  onSettings?: (() => any) | undefined;
88
88
  onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
89
89
  "onUpdate:selectedModel"?: ((value: string | null) => any) | undefined;
@@ -57,12 +57,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
57
57
  "update:selectedModel": (value: string | null) => any;
58
58
  } & {
59
59
  delete: () => any;
60
+ cancel: () => any;
60
61
  rename: () => any;
61
62
  duplicate: () => any;
62
63
  export: () => any;
63
64
  copyLink: () => any;
64
65
  selectConversation: (id: string) => any;
65
- cancel: () => any;
66
66
  settings: () => any;
67
67
  send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
68
68
  debug: () => any;
@@ -78,12 +78,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
78
78
  "update:workspaceSheetOpen": (value: boolean) => any;
79
79
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
80
80
  onDelete?: (() => any) | undefined;
81
+ onCancel?: (() => any) | undefined;
81
82
  onRename?: (() => any) | undefined;
82
83
  onDuplicate?: (() => any) | undefined;
83
84
  onExport?: (() => any) | undefined;
84
85
  onCopyLink?: (() => any) | undefined;
85
86
  onSelectConversation?: ((id: string) => any) | undefined;
86
- onCancel?: (() => any) | undefined;
87
87
  onSettings?: (() => any) | undefined;
88
88
  onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
89
89
  "onUpdate:selectedModel"?: ((value: string | null) => any) | undefined;
@@ -3,6 +3,7 @@ import type { ConversationCreatorFilter } from '../../types/schemas/chat/primiti
3
3
  import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../types/schemas/chat/workspace-settings.js';
4
4
  import type { TelaAgentExecutionInput } from '../../types/schemas/integrations/tela-agent-api.js';
5
5
  import type { ChatLoadingMessageMode } from '../loading-messages.js';
6
+ import type { ChatCitationsConfig } from '../../citations/types.js';
6
7
  import type { MessageFeedbackConfig, MessageFeedbackRating } from '../../messages/composables/chat-feedback.js';
7
8
  type ConfiguredTelaAgentInputs = DeepReadonly<TelaAgentExecutionInput[]> | null | undefined;
8
9
  type ConfiguredMessageFeedback = Readonly<Record<string, MessageFeedbackRating>> | null | undefined;
@@ -19,6 +20,7 @@ export type EmbedConfig = {
19
20
  loadingMessagesMode: Ref<ChatLoadingMessageMode | null | undefined>;
20
21
  customComponents: Ref<Record<string, Component> | undefined>;
21
22
  customHtmlTags: Ref<readonly string[] | undefined>;
23
+ citations: Ref<ChatCitationsConfig | null | undefined>;
22
24
  feedbackConfig: Ref<MessageFeedbackConfig | null | undefined>;
23
25
  messageFeedback: Ref<ConfiguredMessageFeedback>;
24
26
  };
@@ -35,6 +37,7 @@ type EmbedConfigInput = {
35
37
  loadingMessagesMode?: MaybeRefOrGetter<ChatLoadingMessageMode | null | undefined>;
36
38
  customComponents?: MaybeRefOrGetter<Record<string, Component> | undefined>;
37
39
  customHtmlTags?: MaybeRefOrGetter<readonly string[] | undefined>;
40
+ citations?: MaybeRefOrGetter<ChatCitationsConfig | null | undefined>;
38
41
  feedbackConfig?: MaybeRefOrGetter<MessageFeedbackConfig | null | undefined>;
39
42
  messageFeedback?: MaybeRefOrGetter<ConfiguredMessageFeedback>;
40
43
  };
@@ -5,6 +5,11 @@ function useGlobalCustomComponentsState() {
5
5
  nuxtApp._chatEmbedConfigCustomComponents ??= shallowRef(void 0);
6
6
  return nuxtApp._chatEmbedConfigCustomComponents;
7
7
  }
8
+ function useGlobalCitationsState() {
9
+ const nuxtApp = useNuxtApp();
10
+ nuxtApp._chatEmbedConfigCitations ??= shallowRef(void 0);
11
+ return nuxtApp._chatEmbedConfigCitations;
12
+ }
8
13
  function useGlobalEmbedConfigState() {
9
14
  return {
10
15
  provided: useState("chat-embed-config-provided", () => false),
@@ -20,6 +25,7 @@ function useGlobalEmbedConfigState() {
20
25
  loadingMessagesMode: useState("chat-embed-config-loading-messages-mode", () => void 0),
21
26
  customComponents: useGlobalCustomComponentsState(),
22
27
  customHtmlTags: useState("chat-embed-config-custom-html-tags", () => void 0),
28
+ citations: useGlobalCitationsState(),
23
29
  feedbackConfig: useState("chat-embed-config-feedback-config", () => void 0),
24
30
  messageFeedback: useState("chat-embed-config-message-feedback", () => void 0)
25
31
  };
@@ -38,6 +44,7 @@ export function provideEmbedConfig(input, options = {}) {
38
44
  loadingMessagesMode: computed(() => toValue(input.loadingMessagesMode)),
39
45
  customComponents: computed(() => toValue(input.customComponents)),
40
46
  customHtmlTags: computed(() => toValue(input.customHtmlTags)),
47
+ citations: computed(() => toValue(input.citations)),
41
48
  feedbackConfig: computed(() => toValue(input.feedbackConfig)),
42
49
  messageFeedback: computed(() => toValue(input.messageFeedback))
43
50
  };
@@ -57,6 +64,7 @@ export function provideEmbedConfig(input, options = {}) {
57
64
  globalConfig.loadingMessagesMode.value = config.loadingMessagesMode.value;
58
65
  globalConfig.customComponents.value = config.customComponents.value;
59
66
  globalConfig.customHtmlTags.value = config.customHtmlTags.value;
67
+ globalConfig.citations.value = config.citations.value;
60
68
  globalConfig.feedbackConfig.value = config.feedbackConfig.value;
61
69
  globalConfig.messageFeedback.value = config.messageFeedback.value;
62
70
  });
@@ -83,6 +91,7 @@ export function useEmbedConfig() {
83
91
  loadingMessagesMode: computed(() => globalConfig.loadingMessagesMode.value),
84
92
  customComponents: computed(() => globalConfig.customComponents.value),
85
93
  customHtmlTags: computed(() => globalConfig.customHtmlTags.value),
94
+ citations: computed(() => globalConfig.citations.value),
86
95
  feedbackConfig: computed(() => globalConfig.feedbackConfig.value),
87
96
  messageFeedback: computed(() => globalConfig.messageFeedback.value)
88
97
  };
@@ -4,6 +4,7 @@ import type { TelaAgentExecutionInput } from '../types/schemas/integrations/tela
4
4
  import type { ConversationCreatorFilter } from '../types/schemas/chat/primitives.js';
5
5
  import type { ChatLoadingMessageMode } from './loading-messages.js';
6
6
  import type { ChatFeatureConfig } from './feature-config.js';
7
+ import type { ChatCitationsConfig } from '../citations/types.js';
7
8
  import type { MessageFeedbackConfig, MessageFeedbackRating } from '../messages/composables/chat-feedback.js';
8
9
  export type ChatActor = {
9
10
  name?: string | null;
@@ -44,7 +45,11 @@ export type ChatEmbedSharedProps = {
44
45
  sidebar?: ChatSidebarConfig;
45
46
  /** Hide workspace settings actions while keeping the chat header and content unchanged. */
46
47
  hideSettings?: boolean;
47
- /** Hide the folder onboarding hint while keeping folder creation actions available. */
48
+ /**
49
+ * Hide the empty-state folder onboarding block (PASTAS header, ghost rows, hint
50
+ * text and inline create button). Folder creation stays available through the
51
+ * new-group button in the sidebar header.
52
+ */
48
53
  hideFolderHint?: boolean;
49
54
  /** Custom pending-response messages shown while the assistant is working. */
50
55
  loadingMessages?: readonly string[] | null;
@@ -54,6 +59,12 @@ export type ChatEmbedSharedProps = {
54
59
  customComponents?: Record<string, Component>;
55
60
  /** Extra HTML tags allowed by the markdown renderer. */
56
61
  customHtmlTags?: readonly string[];
62
+ /**
63
+ * Enables `cite://` citations in assistant messages. The host resolves each
64
+ * citation against its own backend (carrying the viewing user's authorization);
65
+ * the lib renders chips, the metadata popover and the document panel.
66
+ */
67
+ citations?: ChatCitationsConfig | null;
57
68
  /**
58
69
  * Enables 👍/👎 feedback on assistant messages and configures the reason
59
70
  * popovers. Providing the prop (even `{}`) shows the controls; votes reach
@@ -1,11 +1,19 @@
1
1
  <script setup>
2
+ import { parseCitationUrl } from "../../citations/citation-links";
3
+ import { useEmbedConfig } from "../../embed/composables/embed-config";
2
4
  import { detectFileFromUrl } from "../content-detection";
3
5
  const props = defineProps({
4
6
  node: { type: Object, required: true },
5
7
  indexKey: { type: [Number, String], required: true },
6
8
  customId: { type: String, required: false }
7
9
  });
10
+ const embedConfig = useEmbedConfig();
8
11
  const detectedFile = computed(() => detectFileFromUrl(props.node.href));
12
+ const detectedCitation = computed(() => {
13
+ if (!embedConfig?.citations.value)
14
+ return null;
15
+ return parseCitationUrl(props.node.href);
16
+ });
9
17
  const linkText = computed(() => {
10
18
  if (props.node.children?.length > 0) {
11
19
  return props.node.children.map((c) => c.raw).join("");
@@ -23,6 +31,11 @@ const linkText = computed(() => {
23
31
  w-full
24
32
  my-4px
25
33
  />
34
+ <ChatCitation
35
+ v-else-if="detectedCitation"
36
+ :citation="detectedCitation"
37
+ :label="linkText"
38
+ />
26
39
  <a
27
40
  v-else
28
41
  :href="node.href"
@@ -1,3 +1,4 @@
1
+ import { type CitationRef } from '../citations/citation-links.js';
1
2
  import { type VaultHashParams } from './vault-links.js';
2
3
  export type DetectedFile = {
3
4
  type: 'vault' | 'url';
@@ -12,7 +13,14 @@ export type ContentSegment = {
12
13
  } | {
13
14
  type: 'file';
14
15
  file: DetectedFile;
16
+ } | {
17
+ type: 'citation';
18
+ citation: CitationRef;
19
+ };
20
+ export type ParseContentOptions = {
21
+ /** Detect bare `cite://` tokens as citation segments. Off by default: only hosts that configured citations opt in. */
22
+ detectCitations?: boolean;
15
23
  };
16
24
  export declare function detectFilesInText(text: string): DetectedFile[];
17
- export declare function parseContentWithFiles(text: string): ContentSegment[];
25
+ export declare function parseContentWithFiles(text: string, options?: ParseContentOptions): ContentSegment[];
18
26
  export declare function detectFileFromUrl(url: string): DetectedFile | null;
@@ -1,3 +1,4 @@
1
+ import { CITE_REGEX, parseCitationUrl } from "../citations/citation-links.js";
1
2
  import { getExtensionFromUrl, getMimeTypeFromExtension, isFileUrl } from "./mime.js";
2
3
  import {
3
4
  getPermalinkId,
@@ -128,7 +129,7 @@ export function detectFilesInText(text) {
128
129
  }
129
130
  return files;
130
131
  }
131
- export function parseContentWithFiles(text) {
132
+ export function parseContentWithFiles(text, options) {
132
133
  const segments = [];
133
134
  const fileMatches = collectMarkdownFileLinkMatches(text);
134
135
  for (const match of text.matchAll(VAULT_REGEX)) {
@@ -198,18 +199,38 @@ export function parseContentWithFiles(text) {
198
199
  });
199
200
  }
200
201
  }
201
- if (fileMatches.length === 0)
202
+ const matches = fileMatches.map((fm) => ({
203
+ start: fm.start,
204
+ end: fm.end,
205
+ segment: { type: "file", file: fm.file }
206
+ }));
207
+ if (options?.detectCitations) {
208
+ for (const match of text.matchAll(CITE_REGEX)) {
209
+ const url = stripTrailingPunctuation(match[0]);
210
+ if (overlapsFileMatch(match.index, match.index + url.length, fileMatches))
211
+ continue;
212
+ const citation = parseCitationUrl(url);
213
+ if (!citation)
214
+ continue;
215
+ matches.push({
216
+ start: match.index,
217
+ end: match.index + url.length,
218
+ segment: { type: "citation", citation }
219
+ });
220
+ }
221
+ }
222
+ if (matches.length === 0)
202
223
  return [{ type: "text", content: text }];
203
- fileMatches.sort((a, b) => a.start - b.start);
224
+ matches.sort((a, b) => a.start - b.start);
204
225
  let lastEnd = 0;
205
- for (const fm of fileMatches) {
206
- if (fm.start > lastEnd) {
207
- const textContent = text.slice(lastEnd, fm.start);
226
+ for (const m of matches) {
227
+ if (m.start > lastEnd) {
228
+ const textContent = text.slice(lastEnd, m.start);
208
229
  if (textContent)
209
230
  segments.push({ type: "text", content: textContent });
210
231
  }
211
- segments.push({ type: "file", file: fm.file });
212
- lastEnd = fm.end;
232
+ segments.push(m.segment);
233
+ lastEnd = m.end;
213
234
  }
214
235
  if (lastEnd < text.length) {
215
236
  const textContent = text.slice(lastEnd);