@meistrari/chat-nuxt 4.2.0-rc.1 → 4.2.0-rc.2

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": "4.2.0-rc.1",
4
+ "version": "4.2.0-rc.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -20,7 +20,7 @@ const {
20
20
  confirmDelete,
21
21
  cancelDelete,
22
22
  handleConversationClick,
23
- handleConversationDoubleClick,
23
+ startRename,
24
24
  handleConversationDragStart,
25
25
  handleConversationDragEnd,
26
26
  cancelRename,
@@ -81,7 +81,7 @@ function setRenameInputRef(element) {
81
81
  class="group data-[active=true]:bg-muted data-[active=false]:hover:bg-subtle"
82
82
  :data-active="active"
83
83
  @click="handleConversationClick"
84
- @dblclick.prevent.stop="handleConversationDoubleClick"
84
+ @dblclick.prevent.stop="startRename"
85
85
  @dragstart="handleConversationDragStart"
86
86
  @dragend="handleConversationDragEnd"
87
87
  >
@@ -37,7 +37,6 @@ export declare function useConversationItem(options: ConversationItemOptions, em
37
37
  cancelDelete: () => void;
38
38
  handleConversationClick: (event: MouseEvent) => void;
39
39
  startRename: () => void;
40
- handleConversationDoubleClick: () => void;
41
40
  handleConversationDragStart: (event: DragEvent) => void;
42
41
  handleConversationDragEnd: () => void;
43
42
  cancelRename: () => void;
@@ -1,6 +1,5 @@
1
1
  import { resolveConversationRenameTitle } from "../title.js";
2
2
  import { capitalizeFirst } from "../../shared/string.js";
3
- const conversationNavigationDelayMs = 250;
4
3
  export function useConversationItem(options, emit) {
5
4
  const isRenamingInline = ref(false);
6
5
  const renameTitle = ref("");
@@ -9,7 +8,6 @@ export function useConversationItem(options, emit) {
9
8
  const showDeleteModal = ref(false);
10
9
  const conversationPath = computed(() => `/${options.conversation.id}`);
11
10
  const displayTitle = computed(() => capitalizeFirst(options.conversation.title));
12
- let conversationNavigationTimer = null;
13
11
  let renameBlurTimer = null;
14
12
  const escapeCancellable = computed(() => isRenamingInline.value || showDeleteModal.value);
15
13
  function handleEscapeKeydown(event) {
@@ -28,12 +26,6 @@ export function useConversationItem(options, emit) {
28
26
  else
29
27
  window.removeEventListener("keydown", handleEscapeKeydown);
30
28
  });
31
- function clearPendingConversationNavigation() {
32
- if (conversationNavigationTimer === null)
33
- return;
34
- clearTimeout(conversationNavigationTimer);
35
- conversationNavigationTimer = null;
36
- }
37
29
  function clearRenameBlurTimer() {
38
30
  if (renameBlurTimer === null)
39
31
  return;
@@ -58,17 +50,13 @@ export function useConversationItem(options, emit) {
58
50
  if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
59
51
  return;
60
52
  event.preventDefault();
61
- clearPendingConversationNavigation();
62
53
  if (event.detail > 1)
63
54
  return;
64
- conversationNavigationTimer = setTimeout(() => {
65
- if (options.selectConversation) {
66
- void options.selectConversation(options.conversation.id);
67
- } else {
68
- navigateTo(conversationPath.value);
69
- }
70
- conversationNavigationTimer = null;
71
- }, conversationNavigationDelayMs);
55
+ if (options.selectConversation) {
56
+ void options.selectConversation(options.conversation.id);
57
+ } else {
58
+ navigateTo(conversationPath.value);
59
+ }
72
60
  }
73
61
  async function focusRenameInput() {
74
62
  await nextTick();
@@ -77,17 +65,12 @@ export function useConversationItem(options, emit) {
77
65
  allowRenameBlurSoon();
78
66
  }
79
67
  function startRename() {
80
- clearPendingConversationNavigation();
81
68
  clearRenameBlurTimer();
82
69
  renameTitle.value = options.conversation.title;
83
70
  isIgnoringInitialRenameBlur.value = true;
84
71
  isRenamingInline.value = true;
85
72
  void focusRenameInput();
86
73
  }
87
- function handleConversationDoubleClick() {
88
- clearPendingConversationNavigation();
89
- startRename();
90
- }
91
74
  function handleConversationDragStart(event) {
92
75
  if (!options.draggable)
93
76
  return;
@@ -146,7 +129,6 @@ export function useConversationItem(options, emit) {
146
129
  }
147
130
  ];
148
131
  onBeforeUnmount(() => {
149
- clearPendingConversationNavigation();
150
132
  clearRenameBlurTimer();
151
133
  if (typeof window !== "undefined")
152
134
  window.removeEventListener("keydown", handleEscapeKeydown);
@@ -163,7 +145,6 @@ export function useConversationItem(options, emit) {
163
145
  cancelDelete,
164
146
  handleConversationClick,
165
147
  startRename,
166
- handleConversationDoubleClick,
167
148
  handleConversationDragStart,
168
149
  handleConversationDragEnd,
169
150
  cancelRename,
@@ -347,7 +347,9 @@ async function ensureConversationLoaded(conversationId) {
347
347
  }
348
348
  loadingConversationId.value = conversationId;
349
349
  try {
350
- await loadConversation(conversationId);
350
+ const ownsChatState = await loadConversation(conversationId);
351
+ if (!ownsChatState)
352
+ return;
351
353
  if (currentConversation.value?.id !== conversationId) {
352
354
  agentWake.skip();
353
355
  statusToast.update({
@@ -196,7 +196,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
196
196
  :completed="!isPending"
197
197
  :preparing-steps="preparingSteps"
198
198
  :started-at="message.generationStartedAt ?? message.createdAt"
199
- :updated-at="message.updatedAt"
199
+ :ended-at="message.generationEndedAt"
200
200
  />
201
201
 
202
202
  <div
@@ -3,7 +3,7 @@ type __VLS_Props = {
3
3
  reasoningData: ReasoningStep[];
4
4
  completed: boolean;
5
5
  startedAt: Date | string;
6
- updatedAt?: Date | string | null;
6
+ endedAt?: Date | string | null;
7
7
  preparingSteps?: readonly string[] | null;
8
8
  };
9
9
  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>;
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { resolveDisplayedReasoning } from "../../reasoning";
3
- import { useReasoningTimer } from "../../composables/reasoning-timer";
3
+ import { formatThinkingDuration, useReasoningTimer } from "../../composables/reasoning-timer";
4
4
  import { resolveToolTitle } from "../../tool-registry";
5
5
  import ChatReasoningCollapsibleContent from "./chat-reasoning-collapsible-content.vue";
6
6
  import ChatReasoningSteps from "./chat-reasoning-steps.vue";
@@ -8,14 +8,14 @@ const props = defineProps({
8
8
  reasoningData: { type: Array, required: true },
9
9
  completed: { type: Boolean, required: true },
10
10
  startedAt: { type: [Date, String], required: true },
11
- updatedAt: { type: [Date, String, null], required: false },
11
+ endedAt: { type: [Date, String, null], required: false },
12
12
  preparingSteps: { type: [Array, null], required: false }
13
13
  });
14
14
  const TEXT_TRUNCATE_LENGTH = 40;
15
15
  const isExpanded = ref(false);
16
16
  const { elapsedTime, thinkingDuration } = useReasoningTimer({
17
17
  startedAt: computed(() => props.startedAt),
18
- updatedAt: computed(() => props.updatedAt),
18
+ endedAt: computed(() => props.endedAt),
19
19
  completed: computed(() => props.completed)
20
20
  });
21
21
  const displayedReasoning = computed(
@@ -40,18 +40,7 @@ const currentStepName = computed(() => {
40
40
  }
41
41
  return "Trabalhando";
42
42
  });
43
- const formattedDuration = computed(() => {
44
- if (!thinkingDuration.value)
45
- return null;
46
- const seconds = Math.round(thinkingDuration.value / 1e3);
47
- if (seconds < 60)
48
- return `${seconds}s`;
49
- const minutes = Math.floor(seconds / 60);
50
- const remainingSeconds = seconds % 60;
51
- if (remainingSeconds === 0)
52
- return `${minutes}m`;
53
- return `${minutes}m ${remainingSeconds}s`;
54
- });
43
+ const formattedDuration = computed(() => formatThinkingDuration(thinkingDuration.value));
55
44
  const liveFormattedDuration = computed(() => {
56
45
  const seconds = Math.round(elapsedTime.value / 1e3);
57
46
  return seconds > 0 ? `${seconds}s` : "";
@@ -101,7 +90,8 @@ const liveFormattedDuration = computed(() => {
101
90
  group-hover:text-primary
102
91
  transition-colors
103
92
  >
104
- Trabalhou por {{ formattedDuration || "alguns momentos" }}
93
+ <template v-if="formattedDuration">Trabalhou por {{ formattedDuration }}</template>
94
+ <template v-else>Trabalhou</template>
105
95
  </span>
106
96
  <div
107
97
  v-else
@@ -3,7 +3,7 @@ type __VLS_Props = {
3
3
  reasoningData: ReasoningStep[];
4
4
  completed: boolean;
5
5
  startedAt: Date | string;
6
- updatedAt?: Date | string | null;
6
+ endedAt?: Date | string | null;
7
7
  preparingSteps?: readonly string[] | null;
8
8
  };
9
9
  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>;
@@ -30,7 +30,7 @@ export declare function useChat(options?: {
30
30
  cancelling: any;
31
31
  generatingTitle: any;
32
32
  animatedTitle: any;
33
- loadConversation: (id: string) => Promise<void>;
33
+ loadConversation: (id: string) => Promise<boolean>;
34
34
  setConversation: (conv: Conversation | ConversationWithMessages) => void;
35
35
  adoptConversation: (conv: Conversation) => void;
36
36
  setConversationModel: (model: string | null) => void;
@@ -427,11 +427,13 @@ export function useChat(options = {}) {
427
427
  agentSessionRewakeable.value = false;
428
428
  error.value = null;
429
429
  };
430
+ let loadGeneration = 0;
430
431
  const loadConversation = async (id) => {
431
432
  if (chatApi.conversationV2Enabled) {
432
433
  stopPolling();
433
434
  directStream.disconnect();
434
435
  }
436
+ const generation = ++loadGeneration;
435
437
  runtimeBlocked.value = false;
436
438
  loading.value = true;
437
439
  error.value = null;
@@ -440,6 +442,8 @@ export function useChat(options = {}) {
440
442
  chatApi.path(`/conversations/${id}`),
441
443
  chatApi.withChatHeaders()
442
444
  );
445
+ if (generation !== loadGeneration)
446
+ return false;
443
447
  currentConversation.value = data;
444
448
  messages.value = data.messages;
445
449
  pendingStreamReconciliationIds.value = /* @__PURE__ */ new Set();
@@ -457,14 +461,19 @@ export function useChat(options = {}) {
457
461
  streamingMessageId.value = null;
458
462
  directStream.disconnect();
459
463
  }
464
+ return true;
460
465
  } catch (err) {
466
+ Sentry.captureException(err);
467
+ if (generation !== loadGeneration)
468
+ return false;
461
469
  currentConversation.value = null;
462
470
  messages.value = [];
463
471
  const message = err instanceof Error ? err.message : "Erro ao carregar conversa";
464
472
  error.value = message;
465
- Sentry.captureException(err);
473
+ return true;
466
474
  } finally {
467
- loading.value = false;
475
+ if (generation === loadGeneration)
476
+ loading.value = false;
468
477
  }
469
478
  };
470
479
  const sendMessage = async (content, files, agentInputs, prepare, queuedSend) => {
@@ -1,7 +1,8 @@
1
1
  import { type Ref } from 'vue';
2
+ export declare function formatThinkingDuration(durationMs: number | null): string | null;
2
3
  export declare function useReasoningTimer(options: {
3
4
  startedAt: Readonly<Ref<Date | string>>;
4
- updatedAt: Readonly<Ref<Date | string | null | undefined>>;
5
+ endedAt: Readonly<Ref<Date | string | null | undefined>>;
5
6
  completed: Readonly<Ref<boolean>>;
6
7
  }): {
7
8
  elapsedTime: Ref<number, number>;
@@ -1,11 +1,29 @@
1
1
  import { computed, ref, watch } from "vue";
2
+ const MINUTE_SECONDS = 60;
3
+ const HOUR_SECONDS = 60 * MINUTE_SECONDS;
4
+ const DAY_SECONDS = 24 * HOUR_SECONDS;
5
+ function withRemainder(whole, wholeUnit, remainder, remainderUnit) {
6
+ return remainder === 0 ? `${whole}${wholeUnit}` : `${whole}${wholeUnit} ${remainder}${remainderUnit}`;
7
+ }
8
+ export function formatThinkingDuration(durationMs) {
9
+ if (!durationMs)
10
+ return null;
11
+ const seconds = Math.round(durationMs / 1e3);
12
+ if (seconds < MINUTE_SECONDS)
13
+ return `${seconds}s`;
14
+ if (seconds < HOUR_SECONDS)
15
+ return withRemainder(Math.floor(seconds / MINUTE_SECONDS), "m", seconds % MINUTE_SECONDS, "s");
16
+ if (seconds < DAY_SECONDS)
17
+ return withRemainder(Math.floor(seconds / HOUR_SECONDS), "h", Math.floor(seconds % HOUR_SECONDS / MINUTE_SECONDS), "m");
18
+ return withRemainder(Math.floor(seconds / DAY_SECONDS), "d", Math.floor(seconds % DAY_SECONDS / HOUR_SECONDS), "h");
19
+ }
2
20
  export function useReasoningTimer(options) {
3
21
  const elapsedTime = ref(0);
4
22
  const startTime = computed(() => new Date(options.startedAt.value).getTime());
5
23
  const thinkingDuration = computed(() => {
6
- if (!options.updatedAt.value)
24
+ if (!options.endedAt.value)
7
25
  return null;
8
- const duration = new Date(options.updatedAt.value).getTime() - startTime.value;
26
+ const duration = new Date(options.endedAt.value).getTime() - startTime.value;
9
27
  return Number.isFinite(duration) && duration >= 0 ? duration : null;
10
28
  });
11
29
  watch([startTime, options.completed], ([start, completed], _previous, onCleanup) => {
@@ -89,6 +89,7 @@ function message(baseTime, offsetMs, input) {
89
89
  externalUuid: null,
90
90
  ttft: null,
91
91
  generationStartedAt: null,
92
+ generationEndedAt: input.updatedAt ?? null,
92
93
  createdBy: input.role === "user" ? "debugger@tela.com" : null,
93
94
  createdAt,
94
95
  updatedAt: input.updatedAt ?? createdAt,
@@ -39,6 +39,7 @@ export function beginAssistantMessageRetry(messages, messageId) {
39
39
  failReason: null,
40
40
  reasoningData: null,
41
41
  generationStartedAt: /* @__PURE__ */ new Date(),
42
+ generationEndedAt: null,
42
43
  updatedAt: null
43
44
  };
44
45
  })
@@ -115,6 +115,7 @@ export declare const sendMessageV2ResponseSchema: z.ZodObject<{
115
115
  createdAt: z.ZodDate;
116
116
  updatedAt: z.ZodNullable<z.ZodDate>;
117
117
  generationStartedAt: z.ZodNullable<z.ZodDate>;
118
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
118
119
  }, z.core.$strip>;
119
120
  assistantMessage: z.ZodObject<{
120
121
  id: z.ZodString;
@@ -148,6 +149,7 @@ export declare const sendMessageV2ResponseSchema: z.ZodObject<{
148
149
  createdAt: z.ZodDate;
149
150
  updatedAt: z.ZodNullable<z.ZodDate>;
150
151
  generationStartedAt: z.ZodNullable<z.ZodDate>;
152
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
151
153
  }, z.core.$strip>;
152
154
  turn: z.ZodNullable<z.ZodObject<{
153
155
  id: z.ZodString;
@@ -228,6 +230,8 @@ export declare const conversationRuntimeWebhookPayloadSchema: z.ZodObject<{
228
230
  "session.waiting_messages": "session.waiting_messages";
229
231
  }>;
230
232
  sessionId: z.ZodString;
233
+ runId: z.ZodOptional<z.ZodString>;
234
+ timestamp: z.ZodOptional<z.ZodString>;
231
235
  status: z.ZodOptional<z.ZodString>;
232
236
  error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
233
237
  failReason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -58,6 +58,8 @@ export const conversationRuntimeWebhookPayloadSchema = z.object({
58
58
  'session.waiting_messages',
59
59
  ]),
60
60
  sessionId: z.string().min(1),
61
+ runId: z.string().optional(),
62
+ timestamp: z.string().optional(),
61
63
  status: z.string().optional(),
62
64
  error: z.string().nullable().optional(),
63
65
  failReason: z.string().nullable().optional(),
@@ -111,6 +111,7 @@ export declare const messageSchema: z.ZodObject<{
111
111
  createdAt: z.ZodDate;
112
112
  updatedAt: z.ZodNullable<z.ZodDate>;
113
113
  generationStartedAt: z.ZodNullable<z.ZodDate>;
114
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
114
115
  }, z.core.$strip>;
115
116
  export type Message = z.infer<typeof messageSchema>;
116
117
  export declare const conversationCreatorFilterCountsSchema: z.ZodRecord<z.ZodEnum<{
@@ -224,6 +225,7 @@ export declare const conversationWithMessagesSchema: z.ZodObject<{
224
225
  createdAt: z.ZodDate;
225
226
  updatedAt: z.ZodNullable<z.ZodDate>;
226
227
  generationStartedAt: z.ZodNullable<z.ZodDate>;
228
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
227
229
  }, z.core.$strip>>;
228
230
  }, z.core.$strip>;
229
231
  export type ConversationWithMessages = z.infer<typeof conversationWithMessagesSchema>;
@@ -50,6 +50,7 @@ export const messageSchema = z.object({
50
50
  createdAt: z.date(),
51
51
  updatedAt: z.date().nullable(),
52
52
  generationStartedAt: z.date().nullable(),
53
+ generationEndedAt: z.date().nullable().optional(),
53
54
  });
54
55
  export const conversationCreatorFilterCountsSchema = z.record(conversationCreatorFilterSchema, z.number());
55
56
  export const conversationListMetadataSchema = z.object({
@@ -120,6 +120,7 @@ export declare const sendMessageResponseSchema: z.ZodObject<{
120
120
  createdAt: z.ZodDate;
121
121
  updatedAt: z.ZodNullable<z.ZodDate>;
122
122
  generationStartedAt: z.ZodNullable<z.ZodDate>;
123
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
123
124
  }, z.core.$strip>;
124
125
  assistantMessage: z.ZodObject<{
125
126
  id: z.ZodString;
@@ -153,6 +154,7 @@ export declare const sendMessageResponseSchema: z.ZodObject<{
153
154
  createdAt: z.ZodDate;
154
155
  updatedAt: z.ZodNullable<z.ZodDate>;
155
156
  generationStartedAt: z.ZodNullable<z.ZodDate>;
157
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
156
158
  }, z.core.$strip>;
157
159
  }, z.core.$strip>;
158
160
  export type SendMessageResponse = z.infer<typeof sendMessageResponseSchema>;
@@ -189,6 +191,7 @@ export declare const messagePollResponseSchema: z.ZodObject<{
189
191
  createdAt: z.ZodDate;
190
192
  updatedAt: z.ZodNullable<z.ZodDate>;
191
193
  generationStartedAt: z.ZodNullable<z.ZodDate>;
194
+ generationEndedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
192
195
  }, z.core.$strip>>;
193
196
  newFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
194
197
  url: z.ZodString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "4.2.0-rc.1",
3
+ "version": "4.2.0-rc.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {