@meistrari/chat-nuxt 3.14.0-rc.2 → 3.14.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.
Files changed (55) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/css/markstream.css +1 -1
  3. package/dist/runtime/citations/components/chat-url-citation.d.vue.ts +7 -0
  4. package/dist/runtime/citations/components/chat-url-citation.vue +74 -0
  5. package/dist/runtime/citations/components/chat-url-citation.vue.d.ts +7 -0
  6. package/dist/runtime/citations/url-citation.d.ts +8 -0
  7. package/dist/runtime/citations/url-citation.js +32 -0
  8. package/dist/runtime/files/components/chat-vault-link.vue +12 -5
  9. package/dist/runtime/messages/components/chat-debug-workbench.vue +20 -2
  10. package/dist/runtime/messages/components/chat-message-bubble.vue +1 -2
  11. package/dist/runtime/messages/components/chat-message-list.d.vue.ts +4 -1
  12. package/dist/runtime/messages/components/chat-message-list.vue +77 -42
  13. package/dist/runtime/messages/components/chat-message-list.vue.d.ts +4 -1
  14. package/dist/runtime/messages/components/reasoning/chat-reasoning-collapsible-content.d.vue.ts +16 -0
  15. package/dist/runtime/messages/components/reasoning/chat-reasoning-collapsible-content.vue +25 -0
  16. package/dist/runtime/messages/components/reasoning/chat-reasoning-collapsible-content.vue.d.ts +16 -0
  17. package/dist/runtime/messages/components/reasoning/chat-reasoning-message.d.vue.ts +0 -4
  18. package/dist/runtime/messages/components/reasoning/chat-reasoning-message.vue +44 -59
  19. package/dist/runtime/messages/components/reasoning/chat-reasoning-message.vue.d.ts +0 -4
  20. package/dist/runtime/messages/components/reasoning/chat-reasoning-steps.d.vue.ts +1 -5
  21. package/dist/runtime/messages/components/reasoning/chat-reasoning-steps.vue +4 -26
  22. package/dist/runtime/messages/components/reasoning/chat-reasoning-steps.vue.d.ts +1 -5
  23. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.d.vue.ts +0 -1
  24. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +44 -84
  25. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue.d.ts +0 -1
  26. package/dist/runtime/messages/components/reasoning/chat-reasoning-tool-row.d.vue.ts +20 -0
  27. package/dist/runtime/messages/components/reasoning/chat-reasoning-tool-row.vue +91 -0
  28. package/dist/runtime/messages/components/reasoning/chat-reasoning-tool-row.vue.d.ts +20 -0
  29. package/dist/runtime/messages/components/widgets/chat-bash-widget.vue +33 -39
  30. package/dist/runtime/messages/components/widgets/chat-doc-sdk-widget.vue +19 -31
  31. package/dist/runtime/messages/components/widgets/chat-generic-tool-widget.vue +139 -213
  32. package/dist/runtime/messages/components/widgets/chat-grep-widget.vue +53 -61
  33. package/dist/runtime/messages/components/widgets/chat-read-widget.d.vue.ts +1 -0
  34. package/dist/runtime/messages/components/widgets/chat-read-widget.vue +57 -43
  35. package/dist/runtime/messages/components/widgets/chat-read-widget.vue.d.ts +1 -0
  36. package/dist/runtime/messages/components/widgets/chat-tela-skill-widget.vue +25 -42
  37. package/dist/runtime/messages/components/widgets/chat-thinking-widget.vue +11 -30
  38. package/dist/runtime/messages/components/widgets/chat-tool-section.d.vue.ts +23 -0
  39. package/dist/runtime/messages/components/widgets/chat-tool-section.vue +38 -0
  40. package/dist/runtime/messages/components/widgets/chat-tool-section.vue.d.ts +23 -0
  41. package/dist/runtime/messages/components/widgets/chat-web-search-widget.vue +22 -37
  42. package/dist/runtime/messages/components/widgets/chat-workstation-query-widget.vue +44 -54
  43. package/dist/runtime/messages/components/widgets/chat-write-widget.vue +23 -35
  44. package/dist/runtime/messages/composables/chat-scroll-stickiness.d.ts +28 -0
  45. package/dist/runtime/messages/composables/chat-scroll-stickiness.js +181 -0
  46. package/dist/runtime/messages/debug/chat-debug-scenarios.js +742 -2
  47. package/dist/runtime/messages/debug/use-chat-debug-diagnostics.d.ts +4 -0
  48. package/dist/runtime/messages/debug/use-chat-debug-diagnostics.js +19 -2
  49. package/dist/runtime/messages/reasoning-step-key.d.ts +2 -0
  50. package/dist/runtime/messages/reasoning-step-key.js +5 -0
  51. package/dist/runtime/messages/tool-registry.d.ts +1 -0
  52. package/dist/runtime/messages/tool-registry.js +29 -2
  53. package/dist/runtime/server/api/citation-favicon.get.d.ts +2 -0
  54. package/dist/runtime/server/api/citation-favicon.get.js +47 -0
  55. package/package.json +2 -2
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.0-rc.2",
4
+ "version": "3.14.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 .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{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}
@@ -0,0 +1,7 @@
1
+ import type { UrlCitationPreview } from '../url-citation.js';
2
+ type __VLS_Props = {
3
+ citation: UrlCitationPreview;
4
+ };
5
+ declare const __VLS_export: 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>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,74 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ citation: { type: Object, required: true }
4
+ });
5
+ const faviconFailed = ref(false);
6
+ const shouldLoadFavicon = ref(false);
7
+ function activatePreviewAssets() {
8
+ shouldLoadFavicon.value = true;
9
+ }
10
+ const accessibleLabel = computed(() => `${props.citation.sourceLabel}: ${props.citation.title}`);
11
+ </script>
12
+
13
+ <template>
14
+ <TelaHoverCard
15
+ align="start"
16
+ side="top"
17
+ :side-offset="8"
18
+ :open-delay="160"
19
+ :close-delay="100"
20
+ >
21
+ <a
22
+ :href="citation.href"
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ class="url-citation-pill"
26
+ body-11-medium
27
+ :aria-label="accessibleLabel"
28
+ @pointerenter="activatePreviewAssets"
29
+ @focus="activatePreviewAssets"
30
+ >
31
+ <img
32
+ v-if="shouldLoadFavicon && !faviconFailed"
33
+ :src="citation.faviconUrl"
34
+ alt=""
35
+ class="url-citation-favicon"
36
+ referrerpolicy="no-referrer"
37
+ @error="faviconFailed = true"
38
+ >
39
+ <span v-else i-ph-globe-simple class="url-citation-fallback" aria-hidden="true" />
40
+ <span>{{ citation.sourceLabel }}</span>
41
+ </a>
42
+
43
+ <template #content>
44
+ <div class="url-citation-card" bg-white flex="~ col" gap-10px p-12px>
45
+ <div flex items-center gap-7px min-w-0>
46
+ <img
47
+ v-if="shouldLoadFavicon && !faviconFailed"
48
+ :src="citation.faviconUrl"
49
+ alt=""
50
+ class="url-citation-card__favicon"
51
+ referrerpolicy="no-referrer"
52
+ @error="faviconFailed = true"
53
+ >
54
+ <span v-else i-ph-globe-simple class="url-citation-card__fallback" aria-hidden="true" />
55
+ <span body-12-medium text-secondary truncate>
56
+ {{ citation.sourceLabel }}
57
+ </span>
58
+ </div>
59
+
60
+ <p body-14-medium text-primary m-0 class="url-citation-card__title">
61
+ {{ citation.title }}
62
+ </p>
63
+
64
+ <p body-11-regular text-tertiary m-0 truncate>
65
+ {{ citation.displayUrl }}
66
+ </p>
67
+ </div>
68
+ </template>
69
+ </TelaHoverCard>
70
+ </template>
71
+
72
+ <style scoped>
73
+ .url-citation-pill{align-items:center;background:#f5f5f5;border-radius:999px;box-shadow:inset 0 0 0 .5px rgb(0 0 0/8%);box-sizing:border-box;color:#404040;display:inline-flex;gap:4px;height:20px;line-height:1;padding:1px 7px 1px 5px;text-decoration:none;transition:background-color .15s ease,transform .12s cubic-bezier(.2,0,0,1);vertical-align:middle;white-space:nowrap}.url-citation-pill:hover{background:#e5e5e5}.url-citation-pill:active{transform:scale(.98)}.url-citation-pill:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:2px}.url-citation-fallback,.url-citation-favicon{border-radius:50%;flex:0 0 12px;height:12px;width:12px}.url-citation-favicon{background:#fff;-o-object-fit:contain;object-fit:contain}.url-citation-fallback{color:#737373}.url-citation-card{border-radius:12px;box-shadow:0 0 0 .5px rgba(0,0,0,.1),0 12px 32px rgba(0,0,0,.12),0 2px 6px rgb(0 0 0/6%);width:min(320px,calc(100vw - 16px))}.url-citation-card__fallback,.url-citation-card__favicon{border-radius:50%;flex:0 0 18px;height:18px;width:18px}.url-citation-card__favicon{-o-object-fit:contain;object-fit:contain}.url-citation-card__fallback{color:#737373}.url-citation-card__title{text-wrap:balance}
74
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { UrlCitationPreview } from '../url-citation.js';
2
+ type __VLS_Props = {
3
+ citation: UrlCitationPreview;
4
+ };
5
+ declare const __VLS_export: 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>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,8 @@
1
+ export interface UrlCitationPreview {
2
+ href: string;
3
+ faviconUrl: string;
4
+ sourceLabel: string;
5
+ title: string;
6
+ displayUrl: string;
7
+ }
8
+ export declare function parseUrlCitation(href: string, label: string, markdownTitle?: string | null): UrlCitationPreview | null;
@@ -0,0 +1,32 @@
1
+ const URL_CITATION_MARKER = "#citation";
2
+ const FAVICON_PROXY_PATH = "/api/citation-favicon";
3
+ function titleCase(value) {
4
+ return value.replace(/[-_]+/g, " ").replace(/\b\p{L}/gu, (letter) => letter.toLocaleUpperCase());
5
+ }
6
+ function sourceLabelFor(hostname) {
7
+ const parts = hostname.replace(/^www\./i, "").split(".");
8
+ const compoundSuffix = parts.slice(-2).join(".");
9
+ const suffixParts = (/* @__PURE__ */ new Set(["co.uk", "com.au", "com.br", "com.mx", "com.tr", "co.jp"])).has(compoundSuffix) ? 2 : 1;
10
+ const brandIndex = Math.max(0, parts.length - suffixParts - 1);
11
+ return titleCase(parts[brandIndex] || hostname);
12
+ }
13
+ export function parseUrlCitation(href, label, markdownTitle) {
14
+ if (!href.endsWith(URL_CITATION_MARKER))
15
+ return null;
16
+ const cleanHref = href.slice(0, -URL_CITATION_MARKER.length);
17
+ let url;
18
+ try {
19
+ url = new URL(cleanHref);
20
+ } catch {
21
+ return null;
22
+ }
23
+ if (url.protocol !== "http:" && url.protocol !== "https:")
24
+ return null;
25
+ return {
26
+ href: url.href,
27
+ faviconUrl: `${FAVICON_PROXY_PATH}?domain=${encodeURIComponent(url.hostname)}`,
28
+ sourceLabel: sourceLabelFor(url.hostname),
29
+ title: markdownTitle?.trim() || label.trim() || url.hostname,
30
+ displayUrl: `${url.hostname}${url.pathname}${url.search}${url.hash}`
31
+ };
32
+ }
@@ -1,5 +1,7 @@
1
1
  <script setup>
2
2
  import { parseCitationUrl } from "../../citations/citation-links";
3
+ import ChatUrlCitation from "../../citations/components/chat-url-citation.vue";
4
+ import { parseUrlCitation } from "../../citations/url-citation";
3
5
  import { useEmbedConfig } from "../../embed/composables/embed-config";
4
6
  import { detectFileFromUrl } from "../content-detection";
5
7
  const props = defineProps({
@@ -9,17 +11,18 @@ const props = defineProps({
9
11
  });
10
12
  const embedConfig = useEmbedConfig();
11
13
  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
- });
17
14
  const linkText = computed(() => {
18
15
  if (props.node.children?.length > 0) {
19
16
  return props.node.children.map((c) => c.raw).join("");
20
17
  }
21
18
  return props.node.text || props.node.href;
22
19
  });
20
+ const detectedCitation = computed(() => {
21
+ if (!embedConfig?.citations.value)
22
+ return null;
23
+ return parseCitationUrl(props.node.href);
24
+ });
25
+ const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, linkText.value, props.node.title));
23
26
  </script>
24
27
 
25
28
  <template>
@@ -36,6 +39,10 @@ const linkText = computed(() => {
36
39
  :citation="detectedCitation"
37
40
  :label="linkText"
38
41
  />
42
+ <ChatUrlCitation
43
+ v-else-if="detectedUrlCitation"
44
+ :citation="detectedUrlCitation"
45
+ />
39
46
  <a
40
47
  v-else
41
48
  :href="node.href"
@@ -6,6 +6,7 @@ import { useChatDebugDiagnostics } from "../debug/use-chat-debug-diagnostics";
6
6
  import { formatChatDebugTime as formatTime, useChatDebugPlayback } from "../debug/use-chat-debug-playback";
7
7
  const scenarios = createChatDebugScenarios();
8
8
  const viewportWidth = ref("default");
9
+ const conversationVariant = ref("widget");
9
10
  const showBounds = ref(false);
10
11
  const rootRef = ref(null);
11
12
  const stageRef = ref(null);
@@ -66,6 +67,10 @@ const viewportOptions = [
66
67
  { id: "default", label: "Default" },
67
68
  { id: "wide", label: "Wide" }
68
69
  ];
70
+ const conversationVariantOptions = [
71
+ { id: "widget", label: "Widget" },
72
+ { id: "page", label: "Page" }
73
+ ];
69
74
  function eventToneClass(tone) {
70
75
  return {
71
76
  neutral: "bg-gray-400",
@@ -144,6 +149,19 @@ function eventToneClass(tone) {
144
149
  <span>{{ playbackState.messages.length }} messages</span>
145
150
  </div>
146
151
  <div flex items-center gap-12px>
152
+ <div flex items-center gap-10px>
153
+ <button
154
+ v-for="option in conversationVariantOptions"
155
+ :key="option.id"
156
+ type="button"
157
+ body-11-regular
158
+ :class="conversationVariant === option.id ? 'text-primary' : 'text-tertiary hover:text-secondary'"
159
+ transition-colors
160
+ @click="conversationVariant = option.id"
161
+ >
162
+ {{ option.label }}
163
+ </button>
164
+ </div>
147
165
  <div flex items-center gap-10px>
148
166
  <button
149
167
  v-for="option in viewportOptions"
@@ -177,13 +195,13 @@ function eventToneClass(tone) {
177
195
  showBounds && 'chat-under-test--bounded'
178
196
  ]"
179
197
  >
180
- <TelaChatBody variant="widget" h-full>
198
+ <TelaChatBody :key="conversationVariant" :variant="conversationVariant" h-full>
181
199
  <ChatMessageList
182
200
  :messages="playbackState.messages"
183
201
  :loading="playbackState.loading"
184
202
  :streaming-active-message-id="playbackState.streamingActiveMessageId"
185
203
  :preparing-steps="playbackState.preparingSteps"
186
- variant="widget"
204
+ :variant="conversationVariant"
187
205
  @retry="stepEvent(-1)"
188
206
  />
189
207
  </TelaChatBody>
@@ -188,7 +188,6 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
188
188
  <ChatReasoningThought
189
189
  v-if="hasReasoning || preparingSteps?.length && !message.content || isPending && !streamActive"
190
190
  :reasoning-data="message.reasoningData ?? []"
191
- :animate="wasEverPending"
192
191
  :completed="!isPending"
193
192
  :preparing-steps="preparingSteps"
194
193
  :created-at="message.createdAt"
@@ -196,7 +195,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
196
195
  />
197
196
 
198
197
  <div
199
- v-if="!isPending && message.content || isPending && streamActive"
198
+ v-if="message.content && (!isPending || streamActive)"
200
199
  max-w-prose
201
200
  :class="[
202
201
  isFailed ? 'flex gap-12px items-start bg-subtle border border-solid rounded-12px w-full' : 'rounded-12px code-theme'
@@ -12,7 +12,10 @@ type __VLS_Props = {
12
12
  preparingSteps?: readonly string[] | null;
13
13
  showTtft?: boolean;
14
14
  };
15
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
16
+ isPinned: import("vue").Ref<boolean, boolean>;
17
+ scrollToBottom: () => void;
18
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
16
19
  retry: (messageId: string) => any;
17
20
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
21
  onRetry?: ((messageId: string) => any) | undefined;
@@ -1,4 +1,5 @@
1
1
  <script setup>
2
+ import { useChatScrollStickiness } from "../composables/chat-scroll-stickiness";
2
3
  const props = defineProps({
3
4
  messages: { type: Array, required: true },
4
5
  loading: { type: Boolean, required: false },
@@ -9,60 +10,94 @@ const props = defineProps({
9
10
  });
10
11
  const emit = defineEmits(["retry"]);
11
12
  const conversationRef = ref(null);
13
+ const { isPinned, scrollToBottom } = useChatScrollStickiness(conversationRef);
12
14
  const skeletonMessages = [
13
15
  { id: "user-1", align: "end", width: "58%", height: "44px" },
14
16
  { id: "assistant-1", align: "start", width: "92%", height: "116px" },
15
17
  { id: "user-2", align: "end", width: "46%", height: "44px" },
16
18
  { id: "assistant-2", align: "start", width: "84%", height: "92px" }
17
19
  ];
18
- function scrollToBottom() {
19
- conversationRef.value?.scrollToBottom();
20
- }
21
- watch(
22
- () => [props.messages.length, props.messages.at(-1)?.content.length ?? 0],
20
+ let knownMessageIds = new Set(props.messages.map((message) => message.id));
21
+ watch(() => props.messages.map((message) => `${message.id}:${message.role}`).join("|"), () => {
22
+ const hasNewUserMessage = props.messages.some(
23
+ (message) => message.role === "user" && !knownMessageIds.has(message.id)
24
+ );
25
+ knownMessageIds = new Set(props.messages.map((message) => message.id));
26
+ if (hasNewUserMessage)
27
+ scrollToBottom();
28
+ });
29
+ defineExpose({
30
+ isPinned,
23
31
  scrollToBottom
24
- );
25
- onMounted(scrollToBottom);
32
+ });
26
33
  </script>
27
34
 
28
35
  <template>
29
- <TelaChatConversation
30
- ref="conversationRef"
31
- :class="{
36
+ <div
37
+ class="chat-message-list"
38
+ :data-variant="variant ?? 'page'"
39
+ flex="~ col"
40
+ flex-1
41
+ min-h-0
42
+ relative
43
+ >
44
+ <TelaChatConversation
45
+ ref="conversationRef"
46
+ :class="{
32
47
  'pt-72px!': (variant ?? 'page') === 'page',
33
48
  'pt-72px pb-24px px-16px': variant === 'mobile'
34
49
  }"
35
- >
36
- <TelaChatConversationContent class="max-w-700px! min-h-full">
37
- <template v-if="loading">
38
- <div
39
- v-for="message in skeletonMessages"
40
- :key="message.id"
41
- flex
42
- :class="message.align === 'end' ? 'justify-end' : 'justify-start'"
43
- >
44
- <TelaSkeleton :style="{ width: message.width, height: message.height }" />
45
- </div>
46
- </template>
50
+ >
51
+ <TelaChatConversationContent class="max-w-700px! min-h-full">
52
+ <template v-if="loading">
53
+ <div
54
+ v-for="message in skeletonMessages"
55
+ :key="message.id"
56
+ flex
57
+ :class="message.align === 'end' ? 'justify-end' : 'justify-start'"
58
+ >
59
+ <TelaSkeleton :style="{ width: message.width, height: message.height }" />
60
+ </div>
61
+ </template>
62
+
63
+ <template v-else>
64
+ <ChatMessageBubble
65
+ v-for="message in messages"
66
+ :key="message.id"
67
+ :message="message"
68
+ :stream-active="message.id === streamingActiveMessageId"
69
+ :preparing-steps="message.role === 'assistant' && message.status === 'pending' ? preparingSteps : null"
70
+ :show-ttft="showTtft"
71
+ @retry="emit('retry', $event)"
72
+ />
73
+ <p
74
+ v-if="messages.length > 0"
75
+ mt-auto mb--6px
76
+ text-center body-11-regular text-tertiary
77
+ >
78
+ As respostas podem conter erros. Verifique documentos originais.
79
+ </p>
80
+ </template>
81
+ </TelaChatConversationContent>
82
+ </TelaChatConversation>
47
83
 
48
- <template v-else>
49
- <ChatMessageBubble
50
- v-for="message in messages"
51
- :key="message.id"
52
- :message="message"
53
- :stream-active="message.id === streamingActiveMessageId"
54
- :preparing-steps="message.role === 'assistant' && message.status === 'pending' ? preparingSteps : null"
55
- :show-ttft="showTtft"
56
- @retry="emit('retry', $event)"
57
- />
58
- <p
59
- v-if="messages.length > 0"
60
- mt-auto mb--6px
61
- text-center body-11-regular text-tertiary
62
- >
63
- As respostas podem conter erros. Verifique documentos originais.
64
- </p>
65
- </template>
66
- </TelaChatConversationContent>
67
- </TelaChatConversation>
84
+ <Transition name="jump-to-latest">
85
+ <button
86
+ v-if="!isPinned"
87
+ type="button"
88
+ class="chat-message-list__jump"
89
+ bg
90
+ b=".5px border-default"
91
+ aria-label="Ir para a mensagem mais recente"
92
+ title="Ir para a mensagem mais recente"
93
+ @click="scrollToBottom"
94
+ >
95
+ <TelaIcon name="i-ph-arrow-down" size="16px" color="icon-secondary" />
96
+ </button>
97
+ </Transition>
98
+ </div>
68
99
  </template>
100
+
101
+ <style scoped>
102
+ .chat-message-list>:deep([data-chat-conversation]){flex:1 1 0%;min-height:0;overflow-y:auto}.chat-message-list__jump{align-items:center;border-radius:999px;bottom:16px;box-shadow:0 3px 12px rgba(0,0,0,.1);cursor:pointer;display:inline-flex;height:32px;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:32px;z-index:2}.jump-to-latest-enter-active,.jump-to-latest-leave-active{transition:opacity .12s ease,transform .12s ease}.jump-to-latest-enter-from,.jump-to-latest-leave-to{opacity:0;transform:translateX(-50%) translateY(4px)}
103
+ </style>
@@ -12,7 +12,10 @@ type __VLS_Props = {
12
12
  preparingSteps?: readonly string[] | null;
13
13
  showTtft?: boolean;
14
14
  };
15
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
16
+ isPinned: import("vue").Ref<boolean, boolean>;
17
+ scrollToBottom: () => void;
18
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
16
19
  retry: (messageId: string) => any;
17
20
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
21
  onRetry?: ((messageId: string) => any) | undefined;
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ open: boolean;
3
+ };
4
+ declare var __VLS_7: {};
5
+ type __VLS_Slots = {} & {
6
+ default?: (props: typeof __VLS_7) => any;
7
+ };
8
+ 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>;
9
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
12
+ type __VLS_WithSlots<T, S> = T & {
13
+ new (): {
14
+ $slots: S;
15
+ };
16
+ };
@@ -0,0 +1,25 @@
1
+ <script setup>
2
+ defineProps({
3
+ open: { type: Boolean, required: true }
4
+ });
5
+ </script>
6
+
7
+ <template>
8
+ <div
9
+ class="chat-reasoning-collapsible-content"
10
+ :data-state="open ? 'open' : 'closed'"
11
+ data-chat-disclosure-motion
12
+ >
13
+ <TelaCollapsibleContent
14
+ :force-mount="true"
15
+ :inert="open ? void 0 : true"
16
+ :aria-hidden="!open"
17
+ >
18
+ <slot />
19
+ </TelaCollapsibleContent>
20
+ </div>
21
+ </template>
22
+
23
+ <style>
24
+ .chat-reasoning-collapsible-content{display:grid;grid-template-rows:1fr;transition:grid-template-rows .2s cubic-bezier(.2,0,0,1)}.chat-reasoning-collapsible-content[data-state=closed]{grid-template-rows:0fr}.chat-reasoning-collapsible-content>[data-collapsible-content]{animation:none!important;height:auto;min-height:0;overflow:hidden}.chat-reasoning-collapsible-content [data-collapsible-content]>div{opacity:1;transform:translateY(0);transition:opacity .14s cubic-bezier(.2,0,0,1) 40ms,transform .18s cubic-bezier(.2,0,0,1);will-change:opacity,transform}.chat-reasoning-collapsible-content[data-state=closed] [data-collapsible-content]>div{opacity:0;transform:translateY(-2px);transition:opacity .1s cubic-bezier(.2,0,0,1),transform .16s cubic-bezier(.2,0,0,1)}@media (prefers-reduced-motion:reduce){.chat-reasoning-collapsible-content,.chat-reasoning-collapsible-content [data-collapsible-content]>div{transition-delay:0s;transition-duration:0s}}
25
+ </style>
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ open: boolean;
3
+ };
4
+ declare var __VLS_7: {};
5
+ type __VLS_Slots = {} & {
6
+ default?: (props: typeof __VLS_7) => any;
7
+ };
8
+ 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>;
9
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
12
+ type __VLS_WithSlots<T, S> = T & {
13
+ new (): {
14
+ $slots: S;
15
+ };
16
+ };
@@ -1,11 +1,7 @@
1
1
  import type { ReasoningStep } from '../../reasoning.js';
2
2
  type __VLS_Props = {
3
3
  step: ReasoningStep;
4
- stepNumber: number;
5
- isFirst?: boolean;
6
- isLast?: boolean;
7
4
  isActive?: boolean;
8
- isPreviewMode?: boolean;
9
5
  completed?: boolean;
10
6
  };
11
7
  declare const __VLS_export: 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>;