@meistrari/chat-nuxt 3.16.0-rc.2 → 3.16.0-rc.4

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.16.0-rc.2",
4
+ "version": "3.16.0-rc.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -1 +1 @@
1
- .markstream-vue{color:#171717;font-family:Inter,"Inter Fallback: Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:-.15px;line-height:24px;--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;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{font-weight: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{margin:0}.markstream-vue>.node-slot[data-node-type=paragraph]:not(:first-child){margin-top:12px}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{font-size:24px;font-weight:540;letter-spacing:-.8px;line-height:28px;margin:24px 0 16px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{font-size:20px;font-weight:540;letter-spacing:-.5px;line-height:24px;margin:20px 0 12px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{font-size:20px;font-weight:540;letter-spacing:-.5px;line-height:24px;margin:16px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue h4{font-size:16px;font-weight:540;letter-spacing:-.2px;line-height:20px;margin:12px 0 6px}.markstream-vue .heading-node.heading-5,.markstream-vue h5{font-size:14px;font-weight:540;letter-spacing:-.15px;line-height:16px;margin:8px 0 4px}.markstream-vue .heading-node.heading-6,.markstream-vue h6{font-size:12px;font-weight:540;letter-spacing:-.1px;line-height:14px;margin:8px 0 4px}.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 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}.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}
@@ -30,3 +30,10 @@ export declare function isCitationUrl(url: string): boolean;
30
30
  export declare function parseCitationUrl(url: string): CitationRef | null;
31
31
  /** Cache key for resolution state: the locator addresses a spot inside the same document. */
32
32
  export declare function citationCacheKey(ref: Pick<CitationRef, 'namespace' | 'id'>): string;
33
+ /**
34
+ * Rewrites bare `cite://` tokens into `[id](cite://...)` markdown links so they render inline
35
+ * through the link node instead of splitting the surrounding paragraph into block segments.
36
+ * Square brackets that only wrap a token are dropped, since the citation chip already reads as
37
+ * a reference.
38
+ */
39
+ export declare function linkifyBareCitations(text: string): string;
@@ -54,3 +54,27 @@ function parsePositiveInt(value) {
54
54
  export function citationCacheKey(ref) {
55
55
  return `${ref.namespace}/${ref.id}`;
56
56
  }
57
+ const BARE_CITE_REWRITE_REGEX = /\[[ \t]*(cite:\/\/[^\s"'<>`\[\]()*~]+)[ \t]*\](?!\()|(?<!\]\()(?<!<)(?<!\]:[ \t]*)(cite:\/\/[^\s"'<>`\[\]()*~]+)(?!\]\()/gi;
58
+ const MARKDOWN_CODE_REGEX = /(```|~~~)[\s\S]*?(?:\1|$)|``+[^`]*``+|`[^`]*`/g;
59
+ export function linkifyBareCitations(text) {
60
+ if (!text.toLowerCase().includes(CITE_SCHEME))
61
+ return text;
62
+ let result = "";
63
+ let lastEnd = 0;
64
+ for (const match of text.matchAll(MARKDOWN_CODE_REGEX)) {
65
+ result += linkifyProse(text.slice(lastEnd, match.index)) + match[0];
66
+ lastEnd = match.index + match[0].length;
67
+ }
68
+ return result + linkifyProse(text.slice(lastEnd));
69
+ }
70
+ function linkifyProse(text) {
71
+ return text.replace(BARE_CITE_REWRITE_REGEX, (match, bracketed, bare) => {
72
+ const token = bracketed ?? bare ?? "";
73
+ const url = token.replace(/[.,:;!?]+$/, "");
74
+ const trailing = token.slice(url.length);
75
+ const citation = parseCitationUrl(url);
76
+ if (!citation)
77
+ return match;
78
+ return `[${citation.id}](${url})${trailing}`;
79
+ });
80
+ }
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { parseCitationUrl } from "../../citations/citation-links";
2
+ import { isCitationUrl, parseCitationUrl } from "../../citations/citation-links";
3
3
  import ChatUrlCitation from "../../citations/components/chat-url-citation.vue";
4
4
  import { parseUrlCitation } from "../../citations/url-citation";
5
5
  import { useEmbedConfig } from "../../embed/composables/embed-config";
@@ -22,6 +22,13 @@ const detectedCitation = computed(() => {
22
22
  return null;
23
23
  return parseCitationUrl(props.node.href);
24
24
  });
25
+ const citationLabel = computed(() => {
26
+ const citation = detectedCitation.value;
27
+ const label = linkText.value.trim();
28
+ if (!citation || !label || label === citation.id || isCitationUrl(label))
29
+ return void 0;
30
+ return label;
31
+ });
25
32
  const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, linkText.value, props.node.title));
26
33
  </script>
27
34
 
@@ -37,7 +44,7 @@ const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, lin
37
44
  <ChatCitation
38
45
  v-else-if="detectedCitation"
39
46
  :citation="detectedCitation"
40
- :label="linkText"
47
+ :label="citationLabel"
41
48
  />
42
49
  <ChatUrlCitation
43
50
  v-else-if="detectedUrlCitation"
@@ -49,6 +56,10 @@ const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, lin
49
56
  :title="node.title || void 0"
50
57
  target="_blank"
51
58
  rel="noopener noreferrer"
52
- class="text-blue-600 hover:text-blue-800 underline"
53
- >{{ linkText }}</a>
59
+ class="chat-inline-link"
60
+ >{{ linkText }}<span class="chat-inline-link__icon" i-ph-arrow-up-right-bold aria-hidden="true" /></a>
54
61
  </template>
62
+
63
+ <style scoped>
64
+ .chat-inline-link{color:inherit;text-decoration:underline;text-decoration-color:#a3a3a3;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:3px;transition:text-decoration-color .15s ease}.chat-inline-link:hover{text-decoration-color:#525252}.chat-inline-link__icon{display:inline-block;font-size:.7em;margin-left:.2em;vertical-align:.05em}
65
+ </style>
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import ChatMessageList from "./chat-message-list.vue";
3
3
  import { provideEmbedConfig } from "../../embed/composables/embed-config";
4
+ import { createChatDebugCitationsConfig } from "../debug/chat-debug-citations";
4
5
  import { createChatDebugScenarios } from "../debug/chat-debug-scenarios";
5
6
  import { useChatDebugDiagnostics } from "../debug/use-chat-debug-diagnostics";
6
7
  import { formatChatDebugTime as formatTime, useChatDebugPlayback } from "../debug/use-chat-debug-playback";
@@ -12,7 +13,8 @@ const rootRef = ref(null);
12
13
  const stageRef = ref(null);
13
14
  provideEmbedConfig({
14
15
  workspaceId: "chat-debug-workbench",
15
- hideSidebar: true
16
+ hideSidebar: true,
17
+ citations: createChatDebugCitationsConfig()
16
18
  }, { syncGlobal: false });
17
19
  const {
18
20
  selectedScenario,
@@ -3,6 +3,7 @@ import { useChatAuth } from "#chat-auth";
3
3
  import { useEmbedConfig } from "../../embed/composables/embed-config";
4
4
  import { useWorkspaceMembers } from "../../workspace/composables/workspace-members";
5
5
  import { isPromptTooLongFailure } from "../../types/schemas/chat/messages.js";
6
+ import { linkifyBareCitations } from "../../citations/citation-links";
6
7
  import { parseContentWithFiles } from "../../files/content-detection";
7
8
  import ChatVaultLink from "../../files/components/chat-vault-link.vue";
8
9
  const props = defineProps({
@@ -56,7 +57,12 @@ function formatTime(date) {
56
57
  const d = date instanceof Date ? date : new Date(date);
57
58
  return d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
58
59
  }
59
- const displayContent = computed(() => props.message.content ?? "");
60
+ const displayContent = computed(() => {
61
+ const content = props.message.content ?? "";
62
+ if (props.message.role !== "assistant" || !embedConfig?.citations.value)
63
+ return content;
64
+ return linkifyBareCitations(content);
65
+ });
60
66
  const userContentHtml = computed(() => {
61
67
  if (props.message.role !== "user")
62
68
  return "";
@@ -93,7 +99,7 @@ const contentSegments = computed(() => {
93
99
  const content = displayContent.value;
94
100
  if (!content)
95
101
  return [];
96
- return parseContentWithFiles(content, { detectCitations: Boolean(embedConfig?.citations.value) });
102
+ return parseContentWithFiles(content);
97
103
  });
98
104
  const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type !== "text"));
99
105
  </script>
@@ -227,7 +233,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
227
233
  </div>
228
234
  </template>
229
235
  <template v-else>
230
- <div v-if="hasDetectedFiles" body-16-regular text-black-900>
236
+ <div v-if="hasDetectedFiles" body-16-regular text-primary>
231
237
  <template v-for="(segment, index) in contentSegments" :key="index">
232
238
  <ClientOnly v-if="segment.type === 'text'">
233
239
  <AppMarkdownRender
@@ -266,7 +272,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
266
272
  :smooth-streaming="isPending ? 'auto' : false"
267
273
  :typewriter="isPending"
268
274
  mode="chat"
269
- text-black-900
275
+ text-primary
270
276
  />
271
277
  </ClientOnly>
272
278
  <div
@@ -0,0 +1,8 @@
1
+ import type { CitationRef } from '../../citations/citation-links.js';
2
+ import type { ChatCitationsConfig, CitationMeta } from '../../citations/types.js';
3
+ /**
4
+ * Metadata derived from the reference itself, so any `cite://` a scenario emits resolves
5
+ * without a backend or a per-document fixture table.
6
+ */
7
+ export declare function chatDebugCitationMeta(ref: CitationRef): CitationMeta;
8
+ export declare function createChatDebugCitationsConfig(): ChatCitationsConfig;
@@ -0,0 +1,16 @@
1
+ function humanizeNamespace(namespace) {
2
+ return namespace.split(/[_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3
+ }
4
+ export function chatDebugCitationMeta(ref) {
5
+ return {
6
+ title: `Documento ${ref.id}`,
7
+ sourceLabel: humanizeNamespace(ref.namespace),
8
+ identifier: ref.id,
9
+ snippet: `Metadados sint\xE9ticos do workbench para ${ref.raw}.`
10
+ };
11
+ }
12
+ export function createChatDebugCitationsConfig() {
13
+ return {
14
+ resolve: async (ref) => chatDebugCitationMeta(ref)
15
+ };
16
+ }
@@ -1447,6 +1447,49 @@ export function createChatDebugScenarios(baseTime = Date.now()) {
1447
1447
  })
1448
1448
  })
1449
1449
  ]
1450
+ },
1451
+ {
1452
+ id: "citations",
1453
+ name: "Citations",
1454
+ intent: "Bare cite:// tokens, cite links and external links inside prose.",
1455
+ durationMs: 1800,
1456
+ events: [
1457
+ event({
1458
+ id: "citations-user",
1459
+ atMs: 0,
1460
+ label: "User message",
1461
+ tone: "user",
1462
+ type: "message.upsert",
1463
+ message: message(baseTime, 0, {
1464
+ id: "citations-user-message",
1465
+ role: "user",
1466
+ content: "A empresa pode monitorar os acessos dos empregados \xE0 internet?"
1467
+ })
1468
+ }),
1469
+ event({
1470
+ id: "citations-assistant",
1471
+ atMs: 900,
1472
+ label: "Assistant answer",
1473
+ tone: "assistant",
1474
+ type: "message.upsert",
1475
+ message: message(baseTime, 900, {
1476
+ id: "citations-assistant-message",
1477
+ role: "assistant",
1478
+ ttft: "860",
1479
+ content: [
1480
+ "## O marco constitucional",
1481
+ "",
1482
+ "O [artigo 5\xBA, inciso X, da Constitui\xE7\xE3o Federal](https://www.planalto.gov.br/ccivil_03/constituicao/constituicao.htm) protege a inviolabilidade da intimidade, vida privada, honra e imagem das pessoas. Essa prote\xE7\xE3o vale tamb\xE9m no ambiente de trabalho. O STF tem consolidado que n\xE3o h\xE1 hierarquia entre direitos fundamentais, e quando dois colidem \u2014 como o poder de dire\xE7\xE3o empresarial e o direito \xE0 privacidade \u2014 exige-se balanceamento proporcional, n\xE3o a supress\xE3o de um pelo outro [cite://stf_jurisprudencia/sjur336558].",
1483
+ "",
1484
+ "## O entendimento do BNDES",
1485
+ "",
1486
+ "O parecer da Consultoria Jur\xEDdica do BNDES (AJ/CONSULT n\xBA 013/2019), que analisou especificamente o monitoramento de acessos de empregados, \xE9 particularmente relevante [cite://bndes_pareceres/7154]. O banco concluiu que **a amplia\xE7\xE3o irrestrita do monitoramento pode configurar viola\xE7\xE3o \xE0 privacidade e intimidade constitucionalmente protegidas**, mesmo quando justificada por seguran\xE7a corporativa. A recomenda\xE7\xE3o foi pelo monitoramento **formal** \u2014 bloqueio de p\xE1ginas espec\xEDficas por normativos claros \u2014 acompanhado de regulamenta\xE7\xE3o e divulga\xE7\xE3o transparente, em lugar de coleta oculta de metadados.",
1487
+ "",
1488
+ "Na esfera trabalhista, a [S\xFAmula 331 do TST](cite://tst_sumulas/331) e o precedente cite://stf_jurisprudencia/sjur336558 apontam na mesma dire\xE7\xE3o. Ver tamb\xE9m [JSL S.A. no Escavador](https://www.escavador.com/sobre/jsl-sa) para o hist\xF3rico processual."
1489
+ ].join("\n")
1490
+ })
1491
+ })
1492
+ ]
1450
1493
  }
1451
1494
  ];
1452
1495
  }
@@ -21,9 +21,7 @@ function escapeSingleDollarSigns(text) {
21
21
  }
22
22
  const escapedContent = computed(() => escapeSingleDollarSigns(props.content ?? ""));
23
23
  const customMarkdownComponents = computed(() => wrapCustomMarkdownComponents(props.customComponents));
24
- watchEffect(() => {
25
- setCustomComponents(scopeId, customMarkdownComponents.value);
26
- });
24
+ watch(customMarkdownComponents, (components) => setCustomComponents(scopeId, components), { immediate: true });
27
25
  onBeforeUnmount(() => {
28
26
  removeCustomComponents(scopeId);
29
27
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "3.16.0-rc.2",
3
+ "version": "3.16.0-rc.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {