@meistrari/chat-nuxt 3.16.0 → 3.17.0-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.
Files changed (34) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/css/markstream.css +1 -1
  3. package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +3 -0
  4. package/dist/runtime/embed/components/chat-embed-inner.vue +8 -0
  5. package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +3 -0
  6. package/dist/runtime/embed/components/chat-embed.vue +4 -0
  7. package/dist/runtime/embed/components/meistrari-chat-embed.vue +4 -0
  8. package/dist/runtime/embed/types.d.ts +5 -0
  9. package/dist/runtime/messages/components/chat-conversation-event.d.vue.ts +7 -0
  10. package/dist/runtime/messages/components/chat-conversation-event.vue +31 -0
  11. package/dist/runtime/messages/components/chat-conversation-event.vue.d.ts +7 -0
  12. package/dist/runtime/messages/components/chat-debug-workbench.vue +3 -3
  13. package/dist/runtime/messages/components/chat-message-list.d.vue.ts +2 -1
  14. package/dist/runtime/messages/components/chat-message-list.vue +28 -11
  15. package/dist/runtime/messages/components/chat-message-list.vue.d.ts +2 -1
  16. package/dist/runtime/messages/components/reasoning/chat-reasoning-collapsible-content.vue +7 -2
  17. package/dist/runtime/messages/components/reasoning/chat-reasoning-steps.vue +10 -1
  18. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +80 -77
  19. package/dist/runtime/messages/components/widgets/chat-thinking-widget.vue +2 -1
  20. package/dist/runtime/messages/conversation-timeline.d.ts +13 -0
  21. package/dist/runtime/messages/conversation-timeline.js +13 -0
  22. package/dist/runtime/messages/debug/use-chat-debug-diagnostics.d.ts +8 -0
  23. package/dist/runtime/messages/debug/use-chat-debug-diagnostics.js +79 -22
  24. package/dist/runtime/messages/debug/use-chat-debug-playback.js +17 -4
  25. package/dist/runtime/rendering/components/app-markdown-render.vue +22 -1
  26. package/dist/runtime/rendering/markdown/streaming-text-node.d.vue.ts +9 -0
  27. package/dist/runtime/rendering/markdown/streaming-text-node.vue +82 -0
  28. package/dist/runtime/rendering/markdown/streaming-text-node.vue.d.ts +9 -0
  29. package/dist/runtime/rendering/markdown/streaming-text-segments.d.ts +47 -0
  30. package/dist/runtime/rendering/markdown/streaming-text-segments.js +51 -0
  31. package/dist/runtime/types/embed.d.ts +1 -0
  32. package/dist/runtime/types/schemas/chat/conversations.d.ts +130 -0
  33. package/dist/runtime/types/schemas/chat/conversations.js +31 -0
  34. package/package.json +1 -1
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.5",
4
+ "version": "3.17.0-rc.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -1 +1 @@
1
- .markstream-vue{font-family:inherit;--at-apply:"body-16-regular leading-24px text-primary";--list-item-marker:#a3a3a3;--list-item-counter-marker:#262626}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;letter-spacing:0;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{--at-apply:"font-580"}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{--at-apply:"body-16-regular leading-24px";margin:0}.markstream-vue>.node-slot[data-node-type=paragraph]:not(:first-child){margin-top:12px}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{--at-apply:"heading-h2-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{--at-apply:"heading-h3-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{--at-apply:"heading-h4-semibold text-primary";margin:20px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue .heading-node.heading-5,.markstream-vue .heading-node.heading-6,.markstream-vue h4,.markstream-vue h5,.markstream-vue h6{--at-apply:"heading-h5-semibold text-primary";margin:16px 0 8px}.markstream-vue>.node-slot:first-child .heading-node{margin-top:0}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
1
+ .markstream-vue{font-family:inherit;--at-apply:"body-16-regular leading-24px text-primary";--list-item-marker:#a3a3a3;--list-item-counter-marker:#262626}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;letter-spacing:0;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{--at-apply:"font-580"}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{--at-apply:"body-16-regular leading-24px";margin:0}.markstream-vue>.node-slot[data-node-type=paragraph]:not(:first-child){margin-top:12px}@keyframes chat-stream-chunk-in{0%{opacity:0}to{opacity:1}}.markstream-vue .chat-stream-chunk{animation:chat-stream-chunk-in var(--chat-stream-fade-duration,.32s) cubic-bezier(.2,0,0,1) both}@media (prefers-reduced-motion:reduce){.markstream-vue .chat-stream-chunk{animation:none}}.markstream-vue .typewriter-cursor,.markstream-vue .typewriter-simple-cursor .typewriter-simple-cursor-target:after{display:none}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{--at-apply:"heading-h2-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{--at-apply:"heading-h3-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{--at-apply:"heading-h4-semibold text-primary";margin:20px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue .heading-node.heading-5,.markstream-vue .heading-node.heading-6,.markstream-vue h4,.markstream-vue h5,.markstream-vue h6{--at-apply:"heading-h5-semibold text-primary";margin:16px 0 8px}.markstream-vue>.node-slot:first-child .heading-node{margin-top:0}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
@@ -1,8 +1,11 @@
1
+ import type { ConversationEvent } from '../../types/schemas/chat/conversations.js';
1
2
  import type { ChatFeatureConfig } from '../feature-config.js';
2
3
  import type { ChatActor, ChatSidebarConfig, ChatSuggestion } from '../types.js';
3
4
  type EmbedUser = ChatActor;
4
5
  type __VLS_Props = {
5
6
  conversationId?: string | null;
7
+ conversationEvents?: readonly ConversationEvent[] | null;
8
+ composerDisabled?: boolean;
6
9
  initialConversationId?: string | null;
7
10
  hideSidebar?: boolean;
8
11
  sidebar?: ChatSidebarConfig;
@@ -38,6 +38,8 @@ import { useWorkspaceSettings } from "../../workspace/composables/workspace-sett
38
38
  import { useWorkspaceSwitchModal } from "../../workspace/composables/workspace-switch-modal";
39
39
  const props = defineProps({
40
40
  conversationId: { type: [String, null], required: false },
41
+ conversationEvents: { type: [Array, null], required: false },
42
+ composerDisabled: { type: Boolean, required: false },
41
43
  initialConversationId: { type: [String, null], required: false, default: null },
42
44
  hideSidebar: { type: Boolean, required: false, default: false },
43
45
  sidebar: { type: Object, required: false },
@@ -991,6 +993,7 @@ async function handleDelete() {
991
993
  v-model:selected-model="selectedModel"
992
994
  v-model:selected-reasoning-effort="selectedReasoningEffort"
993
995
  :loading="composerLocked"
996
+ :disabled="composerDisabled"
994
997
  :creator-name="currentUser?.name ?? void 0"
995
998
  :creator-email="
996
999
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -1014,6 +1017,7 @@ async function handleDelete() {
1014
1017
 
1015
1018
  <ChatMessageList
1016
1019
  :messages="messages"
1020
+ :events="conversationEvents"
1017
1021
  :streaming-active-message-id="streamingActiveMessageId"
1018
1022
  :preparing-steps="preparingSteps"
1019
1023
  :show-ttft="resolvedFeatures.showTtft"
@@ -1025,6 +1029,7 @@ async function handleDelete() {
1025
1029
  v-model:selected-model="selectedModel"
1026
1030
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1027
1031
  :loading="composerLocked"
1032
+ :disabled="composerDisabled"
1028
1033
  :creator-name="currentUser?.name ?? void 0"
1029
1034
  :creator-email="
1030
1035
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -1191,6 +1196,7 @@ async function handleDelete() {
1191
1196
  v-model:selected-model="selectedModel"
1192
1197
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1193
1198
  :loading="composerLocked"
1199
+ :disabled="composerDisabled"
1194
1200
  :creator-name="currentUser?.name ?? void 0"
1195
1201
  :creator-email="
1196
1202
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -1217,6 +1223,7 @@ async function handleDelete() {
1217
1223
  >
1218
1224
  <ChatMessageList
1219
1225
  :messages="messages"
1226
+ :events="conversationEvents"
1220
1227
  :streaming-active-message-id="streamingActiveMessageId"
1221
1228
  :preparing-steps="preparingSteps"
1222
1229
  :show-ttft="resolvedFeatures.showTtft"
@@ -1228,6 +1235,7 @@ async function handleDelete() {
1228
1235
  v-model:selected-model="selectedModel"
1229
1236
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1230
1237
  :loading="composerLocked"
1238
+ :disabled="composerDisabled"
1231
1239
  :creator-name="currentUser?.name ?? void 0"
1232
1240
  :creator-email="
1233
1241
  currentConversation?.createdBy ?? currentUser?.email ?? void 0
@@ -1,8 +1,11 @@
1
+ import type { ConversationEvent } from '../../types/schemas/chat/conversations.js';
1
2
  import type { ChatFeatureConfig } from '../feature-config.js';
2
3
  import type { ChatActor, ChatSidebarConfig, ChatSuggestion } from '../types.js';
3
4
  type EmbedUser = ChatActor;
4
5
  type __VLS_Props = {
5
6
  conversationId?: string | null;
7
+ conversationEvents?: readonly ConversationEvent[] | null;
8
+ composerDisabled?: boolean;
6
9
  initialConversationId?: string | null;
7
10
  hideSidebar?: boolean;
8
11
  sidebar?: ChatSidebarConfig;
@@ -10,6 +10,8 @@ import { normalizeConversationScope, normalizeTelaAgentId, resolveConversationSc
10
10
  import ChatEmbedInner from "./chat-embed-inner.vue";
11
11
  const props = defineProps({
12
12
  conversationId: { type: [String, null], required: false },
13
+ conversationEvents: { type: [Array, null], required: false },
14
+ composerDisabled: { type: Boolean, required: false },
13
15
  initialConversationId: { type: [String, null], required: false },
14
16
  conversationScope: { type: [String, null], required: false },
15
17
  defaultConversationCreatorFilter: { type: null, required: false },
@@ -109,6 +111,8 @@ provideChatMessageTerminal((payload) => {
109
111
  v-if="workspaceId"
110
112
  :key="`${workspaceId}:${normalizedTelaAgentId ?? 'default-chat'}:scope:${resolveConversationScopeKeySegment(normalizedConversationScope)}`"
111
113
  :conversation-id="conversationId"
114
+ :conversation-events="conversationEvents"
115
+ :composer-disabled="composerDisabled"
112
116
  :initial-conversation-id="initialConversationId"
113
117
  :hide-sidebar="hideSidebar"
114
118
  :sidebar="sidebar"
@@ -3,6 +3,8 @@ import { computed, ref, watch } from "vue";
3
3
  import { provideConversationCreatorFilterState } from "../../conversations/composables/conversation-creator-filter-state";
4
4
  const { hideSidebar = void 0, ...props } = defineProps({
5
5
  conversationId: { type: [String, null], required: false },
6
+ conversationEvents: { type: [Array, null], required: false },
7
+ composerDisabled: { type: Boolean, required: false },
6
8
  initialConversationId: { type: [String, null], required: false },
7
9
  conversationScope: { type: [String, null], required: false },
8
10
  defaultConversationCreatorFilter: { type: null, required: false },
@@ -60,6 +62,8 @@ function startWidgetConversation() {
60
62
  }
61
63
  const sharedEmbedProps = computed(() => ({
62
64
  conversationId: props.conversationId,
65
+ conversationEvents: props.conversationEvents,
66
+ composerDisabled: props.composerDisabled,
63
67
  initialConversationId: props.initialConversationId,
64
68
  conversationScope: props.conversationScope,
65
69
  defaultConversationCreatorFilter: props.defaultConversationCreatorFilter,
@@ -1,4 +1,5 @@
1
1
  import type { Component, DeepReadonly } from 'vue';
2
+ import type { ConversationEvent } from '../types/schemas/chat/conversations.js';
2
3
  import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../types/schemas/chat/workspace-settings.js';
3
4
  import type { TelaAgentExecutionInput } from '../types/schemas/integrations/tela-agent-api.js';
4
5
  import type { ConversationCreatorFilter } from '../types/schemas/chat/primitives.js';
@@ -42,6 +43,10 @@ export type ChatSidebarConfig = {
42
43
  export type ChatEmbedSharedProps = {
43
44
  /** Controlled conversation id. Use with `v-model:conversation-id` when the host owns active conversation state. */
44
45
  conversationId?: string | null;
46
+ /** Controlled audit events rendered chronologically between conversation messages. */
47
+ conversationEvents?: readonly ConversationEvent[] | null;
48
+ /** Disables the message composer while the host completes an external state transition. */
49
+ composerDisabled?: boolean;
45
50
  /** Conversation id to open once on mount when `conversationId` is uncontrolled. */
46
51
  initialConversationId?: string | null;
47
52
  /**
@@ -0,0 +1,7 @@
1
+ import type { ConversationEvent } from '../../types/schemas/chat/conversations.js';
2
+ type __VLS_Props = {
3
+ event: ConversationEvent;
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,31 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ event: { type: null, required: true }
4
+ });
5
+ const eventDate = computed(() => new Date(props.event.createdAt));
6
+ const eventTime = computed(() => new Intl.DateTimeFormat("pt-BR", {
7
+ hour: "2-digit",
8
+ minute: "2-digit"
9
+ }).format(eventDate.value));
10
+ </script>
11
+
12
+ <template>
13
+ <div
14
+ data-chat-conversation-event
15
+ flex
16
+ items-center
17
+ gap-12px
18
+ py-6px
19
+ :aria-label="`${event.actorLabel ? `${event.actorLabel} ` : ''}${event.description}, ${eventTime}`"
20
+ >
21
+ <span h-0 flex-1 border-t-0.5px border />
22
+ <p body-12-regular text-tertiary text-center>
23
+ <span v-if="event.actorLabel" body-12-medium text-secondary>{{ event.actorLabel }}</span>
24
+ {{ event.description }}
25
+ <time :datetime="eventDate.toISOString()" :title="eventDate.toLocaleString('pt-BR')">
26
+ · {{ eventTime }}
27
+ </time>
28
+ </p>
29
+ <span h-0 flex-1 border-t-0.5px border />
30
+ </div>
31
+ </template>
@@ -0,0 +1,7 @@
1
+ import type { ConversationEvent } from '../../types/schemas/chat/conversations.js';
2
+ type __VLS_Props = {
3
+ event: ConversationEvent;
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;
@@ -286,7 +286,7 @@ function eventToneClass(tone) {
286
286
  <dl class="metric-grid">
287
287
  <div>
288
288
  <dt body-10-regular text-tertiary>
289
- Event → paint
289
+ Event → frame
290
290
  </dt>
291
291
  <dd heading-h3-semibold tabular-nums>
292
292
  {{ diagnostics.paintLatencyMs.toFixed(1) }}<small>ms</small>
@@ -294,7 +294,7 @@ function eventToneClass(tone) {
294
294
  </div>
295
295
  <div>
296
296
  <dt body-10-regular text-tertiary>
297
- Dropped frames
297
+ Est. missed frames
298
298
  </dt>
299
299
  <dd heading-h3-semibold tabular-nums>
300
300
  {{ diagnostics.droppedFrames }}
@@ -390,7 +390,7 @@ function eventToneClass(tone) {
390
390
  </div>
391
391
  <div>
392
392
  <dt text-tertiary>
393
- Largest move
393
+ Unexpected move
394
394
  </dt><dd text-primary tabular-nums>
395
395
  {{ diagnostics.maxScrollJump.toFixed(0) }} px
396
396
  </dd>
@@ -1,6 +1,7 @@
1
- import type { Message } from '../../types/schemas/chat/conversations.js';
1
+ import type { ConversationEvent, Message } from '../../types/schemas/chat/conversations.js';
2
2
  type __VLS_Props = {
3
3
  messages: readonly Message[];
4
+ events?: readonly ConversationEvent[] | null;
4
5
  loading?: boolean;
5
6
  /**
6
7
  * Where the list renders: `page`/`widget` sit inside a `TelaChatBody` that
@@ -1,7 +1,9 @@
1
1
  <script setup>
2
2
  import { useStickToBottom } from "markstream-vue/utils";
3
+ import { mergeConversationTimeline } from "../conversation-timeline";
3
4
  const props = defineProps({
4
5
  messages: { type: Array, required: true },
6
+ events: { type: [Array, null], required: false },
5
7
  loading: { type: Boolean, required: false },
6
8
  variant: { type: String, required: false },
7
9
  streamingActiveMessageId: { type: [String, null], required: false },
@@ -36,11 +38,20 @@ const skeletonMessages = [
36
38
  { id: "user-2", align: "end", width: "46%", height: "44px" },
37
39
  { id: "assistant-2", align: "start", width: "84%", height: "92px" }
38
40
  ];
41
+ const timelineItems = computed(() => mergeConversationTimeline(props.messages, props.events));
39
42
  const latestOutputSignal = computed(() => {
40
43
  const latestMessage = props.messages.at(-1);
41
- if (!latestMessage)
44
+ const latestEvent = props.events?.at(-1);
45
+ if (!latestMessage && !latestEvent)
42
46
  return "empty";
43
- return `${props.messages.length}:${latestMessage.id}:${latestMessage.status}:${latestMessage.content?.length ?? 0}`;
47
+ return [
48
+ props.messages.length,
49
+ latestMessage?.id ?? "",
50
+ latestMessage?.status ?? "",
51
+ latestMessage?.content?.length ?? 0,
52
+ props.events?.length ?? 0,
53
+ latestEvent?.id ?? ""
54
+ ].join(":");
44
55
  });
45
56
  watch(latestOutputSignal, async () => {
46
57
  await nextTick();
@@ -55,6 +66,7 @@ defineExpose({
55
66
  <template>
56
67
  <div
57
68
  class="chat-message-list"
69
+ :data-chat-bottom-pinned="isPinned"
58
70
  :data-variant="variant ?? 'page'"
59
71
  flex="~ col"
60
72
  flex-1
@@ -82,15 +94,20 @@ defineExpose({
82
94
  </template>
83
95
 
84
96
  <template v-else>
85
- <ChatMessageBubble
86
- v-for="message in messages"
87
- :key="message.id"
88
- :message="message"
89
- :stream-active="message.id === streamingActiveMessageId"
90
- :preparing-steps="message.role === 'assistant' && message.status === 'pending' ? preparingSteps : null"
91
- :show-ttft="showTtft"
92
- @retry="emit('retry', $event)"
93
- />
97
+ <template v-for="item in timelineItems" :key="`${item.kind}:${item.id}`">
98
+ <ChatConversationEvent
99
+ v-if="item.kind === 'event'"
100
+ :event="item.value"
101
+ />
102
+ <ChatMessageBubble
103
+ v-else
104
+ :message="item.value"
105
+ :stream-active="item.id === streamingActiveMessageId"
106
+ :preparing-steps="item.value.role === 'assistant' && item.value.status === 'pending' ? preparingSteps : null"
107
+ :show-ttft="showTtft"
108
+ @retry="emit('retry', $event)"
109
+ />
110
+ </template>
94
111
  <p
95
112
  v-if="messages.length > 0"
96
113
  mt-auto mb--6px
@@ -1,6 +1,7 @@
1
- import type { Message } from '../../types/schemas/chat/conversations.js';
1
+ import type { ConversationEvent, Message } from '../../types/schemas/chat/conversations.js';
2
2
  type __VLS_Props = {
3
3
  messages: readonly Message[];
4
+ events?: readonly ConversationEvent[] | null;
4
5
  loading?: boolean;
5
6
  /**
6
7
  * Where the list renders: `page`/`widget` sit inside a `TelaChatBody` that
@@ -1,7 +1,12 @@
1
1
  <script setup>
2
- defineProps({
2
+ const props = defineProps({
3
3
  open: { type: Boolean, required: true }
4
4
  });
5
+ const hasOpened = ref(props.open);
6
+ watch(() => props.open, (open) => {
7
+ if (open)
8
+ hasOpened.value = true;
9
+ });
5
10
  </script>
6
11
 
7
12
  <template>
@@ -15,7 +20,7 @@ defineProps({
15
20
  :inert="open ? void 0 : true"
16
21
  :aria-hidden="!open"
17
22
  >
18
- <slot />
23
+ <slot v-if="hasOpened" />
19
24
  </TelaCollapsibleContent>
20
25
  </div>
21
26
  </template>
@@ -13,7 +13,12 @@ const groupedReasoning = computed(() => {
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div v-if="groupedReasoning.length > 0" flex="~ col" gap-2px w-full>
16
+ <div
17
+ v-if="groupedReasoning.length > 0"
18
+ class="chat-reasoning-steps"
19
+ :class="{ 'chat-reasoning-steps--streaming': !completed }"
20
+ flex="~ col" gap-2px w-full
21
+ >
17
22
  <ChatReasoningMessage
18
23
  v-for="(step, index) in groupedReasoning"
19
24
  :key="getReasoningStepKey(step, index)"
@@ -23,3 +28,7 @@ const groupedReasoning = computed(() => {
23
28
  />
24
29
  </div>
25
30
  </template>
31
+
32
+ <style scoped>
33
+ @keyframes chat-reasoning-step-in{0%{opacity:0}to{opacity:1}}.chat-reasoning-steps--streaming>:deep(*){animation:chat-reasoning-step-in .24s cubic-bezier(.2,0,0,1) both}@media (prefers-reduced-motion:reduce){.chat-reasoning-steps--streaming>:deep(*){animation:none}}
34
+ </style>
@@ -94,93 +94,96 @@ onUnmounted(() => {
94
94
  </script>
95
95
 
96
96
  <template>
97
- <div flex="~ col" w-full>
98
- <!-- Sem reasoning ainda: ChatThinkingIndicator padrão -->
99
- <ChatThinkingIndicator v-if="!completed && !hasReasoning" key="thinking" />
97
+ <div class="chat-reasoning-stage" w-full>
98
+ <Transition name="chat-reasoning-presence" :appear="!completed">
99
+ <div v-if="!completed && !hasReasoning" key="thinking" class="chat-reasoning-placeholder">
100
+ <ChatThinkingIndicator />
101
+ </div>
100
102
 
101
- <!-- Com reasoning (loading ou completo): header clicável + steps colapsáveis -->
102
- <TelaCollapsible
103
- v-else-if="hasReasoning"
104
- key="reasoning"
105
- v-model:open="isExpanded"
106
- class="chat-reasoning-disclosure"
107
- data-chat-scroll-preserve
108
- >
109
- <TelaCollapsibleTrigger as-child>
110
- <button
111
- type="button"
112
- data-chat-scroll-preserve
113
- class="group"
114
- data-chat-scroll-preserve-trigger
115
- inline-flex
116
- self-start
117
- items-center
118
- gap-4px
119
- min-h-32px
120
- max-w-full
121
- py-6px
122
- text-left
123
- transition-colors
124
- cursor-pointer
125
- :aria-busy="!completed"
126
- :aria-label="isExpanded ? 'Ocultar detalhes do trabalho' : 'Mostrar detalhes do trabalho'"
127
- >
128
- <span
129
- v-if="completed"
130
- key="completed-text"
131
- body-14-regular
132
- text-secondary
133
- tabular-nums
134
- group-hover:text-primary
135
- transition-colors
136
- >
137
- Trabalhou por {{ formattedDuration || "alguns momentos" }}
138
- </span>
139
- <div
140
- v-else
141
- key="loading-text"
142
- flex
143
- min-w-0
103
+ <!-- Com reasoning (loading ou completo): header clicável + steps colapsáveis -->
104
+ <TelaCollapsible
105
+ v-else-if="hasReasoning"
106
+ key="reasoning"
107
+ v-model:open="isExpanded"
108
+ class="chat-reasoning-disclosure"
109
+ data-chat-scroll-preserve
110
+ >
111
+ <TelaCollapsibleTrigger as-child>
112
+ <button
113
+ type="button"
114
+ data-chat-scroll-preserve
115
+ class="group"
116
+ data-chat-scroll-preserve-trigger
117
+ inline-flex
118
+ self-start
144
119
  items-center
145
- gap-6px
120
+ gap-4px
121
+ min-h-32px
122
+ max-w-full
123
+ py-6px
124
+ text-left
125
+ transition-colors
126
+ cursor-pointer
127
+ :aria-busy="!completed"
128
+ :aria-label="isExpanded ? 'Ocultar detalhes do trabalho' : 'Mostrar detalhes do trabalho'"
146
129
  >
147
- <span v-if="hasReasoning" body-14-regular text-secondary truncate>
148
- {{ currentStepName }}
149
- </span>
150
130
  <span
151
- v-if="liveFormattedDuration"
152
- body-12-regular
153
- text-tertiary
154
- shrink-0
131
+ v-if="completed"
132
+ key="completed-text"
133
+ body-14-regular
134
+ text-secondary
135
+ tabular-nums
136
+ group-hover:text-primary
137
+ transition-colors
155
138
  >
156
- {{ liveFormattedDuration }}
139
+ Trabalhou por {{ formattedDuration || "alguns momentos" }}
157
140
  </span>
158
- </div>
141
+ <div
142
+ v-else
143
+ key="loading-text"
144
+ flex
145
+ min-w-0
146
+ items-center
147
+ gap-6px
148
+ >
149
+ <span v-if="hasReasoning" body-14-regular text-secondary truncate>
150
+ {{ currentStepName }}
151
+ </span>
152
+ <span
153
+ v-if="liveFormattedDuration"
154
+ body-12-regular
155
+ text-tertiary
156
+ shrink-0
157
+ >
158
+ {{ liveFormattedDuration }}
159
+ </span>
160
+ </div>
159
161
 
160
- <TelaIcon
161
- name="i-ph-caret-right"
162
- size="14px"
163
- color="icon-tertiary"
164
- shrink-0
165
- class="chat-reasoning-disclosure__caret"
166
- :class="{ 'rotate-90': isExpanded }"
167
- />
168
- </button>
169
- </TelaCollapsibleTrigger>
162
+ <TelaIcon
163
+ name="i-ph-caret-right"
164
+ size="14px"
165
+ color="icon-tertiary"
166
+ shrink-0
167
+ class="chat-reasoning-disclosure__caret"
168
+ :class="{ 'rotate-90': isExpanded }"
169
+ />
170
+ </button>
171
+ </TelaCollapsibleTrigger>
170
172
 
171
- <!-- Open while running, collapsible once done -->
172
- <ChatReasoningCollapsibleContent :open="isExpanded">
173
- <div class="chat-reasoning-details" pb-8px>
174
- <ChatReasoningSteps
175
- :reasoning-data="displayedReasoning"
176
- :completed="completed"
177
- />
178
- </div>
179
- </ChatReasoningCollapsibleContent>
180
- </TelaCollapsible>
173
+ <!-- Open while running, collapsible once done -->
174
+ <ChatReasoningCollapsibleContent :open="isExpanded">
175
+ <div class="chat-reasoning-details" pb-8px>
176
+ <ChatReasoningSteps
177
+ :reasoning-data="displayedReasoning"
178
+ :completed="completed"
179
+ />
180
+ </div>
181
+ </ChatReasoningCollapsibleContent>
182
+ </TelaCollapsible>
183
+ </Transition>
181
184
  </div>
182
185
  </template>
183
186
 
184
187
  <style scoped>
185
- .chat-reasoning-details{contain:layout style}.chat-reasoning-disclosure__caret{transition:transform .2s cubic-bezier(.2,0,0,1)}@media (prefers-reduced-motion:reduce){.chat-reasoning-disclosure__caret{transition-duration:0s}}
188
+ .chat-reasoning-stage{display:grid}.chat-reasoning-stage>*{grid-area:1/1;min-width:0}.chat-reasoning-placeholder{align-items:center;align-self:start;display:flex;min-height:32px}.chat-reasoning-presence-enter-active,.chat-reasoning-presence-leave-active{transition:opacity .22s cubic-bezier(.2,0,0,1)}.chat-reasoning-presence-leave-active{pointer-events:none}.chat-reasoning-presence-enter-from,.chat-reasoning-presence-leave-to{opacity:0}.chat-reasoning-details{contain:layout style}.chat-reasoning-disclosure__caret{transition:transform .2s cubic-bezier(.2,0,0,1)}@media (prefers-reduced-motion:reduce){.chat-reasoning-disclosure__caret,.chat-reasoning-presence-enter-active,.chat-reasoning-presence-leave-active{transition-duration:0s}}
186
189
  </style>
@@ -12,7 +12,8 @@ defineProps({
12
12
  <AppMarkdownRender
13
13
  :content="content"
14
14
  :render-code-blocks-as-pre="true"
15
- :typewriter="false"
15
+ :final="!isActive || !!completed"
16
+ :typewriter="!!isActive && !completed"
16
17
  />
17
18
  </ClientOnly>
18
19
  </div>
@@ -0,0 +1,13 @@
1
+ import type { ConversationEvent, Message } from '../types/schemas/chat/conversations.js';
2
+ export type ConversationTimelineItem = {
3
+ kind: 'event';
4
+ id: string;
5
+ createdAt: Date;
6
+ value: ConversationEvent;
7
+ } | {
8
+ kind: 'message';
9
+ id: string;
10
+ createdAt: Date;
11
+ value: Message;
12
+ };
13
+ export declare function mergeConversationTimeline(messages: readonly Message[], events?: readonly ConversationEvent[] | null): ConversationTimelineItem[];
@@ -0,0 +1,13 @@
1
+ export function mergeConversationTimeline(messages, events = []) {
2
+ return [
3
+ ...(events ?? []).map((event) => ({ kind: "event", id: event.id, createdAt: event.createdAt, value: event })),
4
+ ...messages.map((message) => ({ kind: "message", id: message.id, createdAt: message.createdAt, value: message }))
5
+ ].sort((left, right) => {
6
+ const difference = new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime();
7
+ if (difference !== 0)
8
+ return difference;
9
+ if (left.kind !== right.kind)
10
+ return left.kind === "event" ? -1 : 1;
11
+ return left.id.localeCompare(right.id);
12
+ });
13
+ }
@@ -11,6 +11,14 @@ export declare function shouldIgnoreChatDebugLayoutShift(entry: {
11
11
  hadRecentInput: boolean;
12
12
  startTime: number;
13
13
  }, ignoreUntil: number): boolean;
14
+ export interface ChatScrollSample {
15
+ top: number;
16
+ height: number;
17
+ viewport: number;
18
+ pinned?: boolean;
19
+ }
20
+ export declare function unexpectedChatScrollMovement(previous: ChatScrollSample, next: ChatScrollSample): number;
21
+ export declare function findChatDebugScrollRoot(stage: HTMLElement): HTMLElement | null;
14
22
  type ListenerTarget = {
15
23
  addEventListener: (type: string, listener: unknown, options?: unknown) => void;
16
24
  removeEventListener: (type: string, listener: unknown, options?: unknown) => void;