@meistrari/chat-nuxt 4.11.0-rc.4 → 4.11.0-rc.6

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 (28) hide show
  1. package/dist/module.d.mts +2 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -0
  4. package/dist/runtime/conversations/composables/conversations.d.ts +3 -1
  5. package/dist/runtime/conversations/composables/conversations.js +41 -3
  6. package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +10 -10
  7. package/dist/runtime/embed/components/chat-embed-inner.vue +119 -37
  8. package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +10 -10
  9. package/dist/runtime/embed/components/chat-fork-preparation.d.vue.ts +14 -0
  10. package/dist/runtime/embed/components/chat-fork-preparation.vue +48 -0
  11. package/dist/runtime/embed/components/chat-fork-preparation.vue.d.ts +14 -0
  12. package/dist/runtime/messages/components/chat-conversation-event.vue +2 -0
  13. package/dist/runtime/messages/components/chat-message-bubble.d.vue.ts +1 -0
  14. package/dist/runtime/messages/components/chat-message-bubble.vue +26 -19
  15. package/dist/runtime/messages/components/chat-message-bubble.vue.d.ts +1 -0
  16. package/dist/runtime/messages/components/chat-message-list.d.vue.ts +1 -0
  17. package/dist/runtime/messages/components/chat-message-list.vue +3 -1
  18. package/dist/runtime/messages/components/chat-message-list.vue.d.ts +1 -0
  19. package/dist/runtime/messages/conversation-timeline.d.ts +1 -0
  20. package/dist/runtime/messages/conversation-timeline.js +12 -3
  21. package/dist/runtime/server/utils/chat-api-proxy.js +4 -1
  22. package/dist/runtime/types/schemas/chat/chat-config.d.ts +1 -0
  23. package/dist/runtime/types/schemas/chat/chat-config.js +1 -0
  24. package/dist/runtime/types/schemas/chat/conversation-runtime.d.ts +22 -0
  25. package/dist/runtime/types/schemas/chat/conversation-runtime.js +3 -3
  26. package/dist/runtime/types/schemas/chat/conversations.d.ts +34 -0
  27. package/dist/runtime/types/schemas/chat/conversations.js +8 -0
  28. package/package.json +1 -1
package/dist/module.d.mts CHANGED
@@ -41,6 +41,8 @@ interface ChatNuxtModuleOptions {
41
41
  * to `false`; also settable via `NUXT_CHAT_NUXT_AGENT_API_V5`.
42
42
  */
43
43
  agentApiV5?: boolean;
44
+ /** Opt this host into whole-session lightweight v4 forks via x-chat-config. */
45
+ lightweightSessionFork?: boolean;
44
46
  /** Opt in to the 24-hour server-side pre-wake lookup cache. Defaults to false. */
45
47
  preWakeCache?: boolean;
46
48
  /**
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.11.0-rc.4",
4
+ "version": "4.11.0-rc.6",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -469,6 +469,7 @@ const module$1 = defineNuxtModule({
469
469
  // Explicit boolean default keeps the key in runtime config so the
470
470
  // NUXT_CHAT_NUXT_SECRETLESS_EXECUTION env override can apply.
471
471
  secretlessExecution: options.secretlessExecution ?? existingChatNuxtRuntimeConfig?.secretlessExecution ?? false,
472
+ lightweightSessionFork: options.lightweightSessionFork ?? existingChatNuxtRuntimeConfig?.lightweightSessionFork ?? false,
472
473
  preWakeCache: options.preWakeCache ?? existingChatNuxtRuntimeConfig?.preWakeCache ?? false,
473
474
  agentApiV5: options.agentApiV5 ?? existingChatNuxtRuntimeConfig?.agentApiV5 ?? false,
474
475
  // Explicit boolean default keeps the key in runtime config so the
@@ -17,7 +17,9 @@ export declare function useConversations(): {
17
17
  updateConversationReasoningEffort: (id: string, reasoningEffort: ChatReasoningEffort | null) => Promise<boolean>;
18
18
  updateConversationExecutionProfile: (id: string, profile: ChatExecutionProfile) => Promise<boolean>;
19
19
  moveConversationToGroup: (id: string, groupId: string | null) => Promise<boolean>;
20
- duplicateConversation: (id: string) => Promise<ConversationWithMessages | null>;
20
+ duplicateConversation: (id: string, options?: {
21
+ newAttempt?: boolean;
22
+ }) => Promise<ConversationWithMessages | null>;
21
23
  forkConversation: (id: string, messageId: string) => Promise<ConversationWithMessages | null>;
22
24
  conversationCreatorFilter: any;
23
25
  conversationCreatorFilterCounts: any;
@@ -47,6 +47,31 @@ function removeBucketValue(bucket, key) {
47
47
  function hasBucketValue(bucket, key) {
48
48
  return Object.prototype.hasOwnProperty.call(bucket.value, key);
49
49
  }
50
+ const duplicateKeys = /* @__PURE__ */ new Map();
51
+ const duplicatesInFlight = /* @__PURE__ */ new Set();
52
+ function reserveDuplicateKey(requestKey, newAttempt) {
53
+ let key = newAttempt ? void 0 : duplicateKeys.get(requestKey);
54
+ if (!key && !newAttempt) {
55
+ try {
56
+ key = sessionStorage.getItem(requestKey) ?? void 0;
57
+ } catch {
58
+ }
59
+ }
60
+ key ??= crypto.randomUUID();
61
+ duplicateKeys.set(requestKey, key);
62
+ try {
63
+ sessionStorage.setItem(requestKey, key);
64
+ } catch {
65
+ }
66
+ return key;
67
+ }
68
+ function releaseDuplicateKey(requestKey) {
69
+ duplicateKeys.delete(requestKey);
70
+ try {
71
+ sessionStorage.removeItem(requestKey);
72
+ } catch {
73
+ }
74
+ }
50
75
  function conversationBucketKeyForRuntime(runtimeScope, filter) {
51
76
  return `${runtimeScope}:creator:${filter}`;
52
77
  }
@@ -524,17 +549,24 @@ export function useConversations() {
524
549
  return null;
525
550
  }
526
551
  };
527
- const duplicateConversation = async (id) => {
552
+ const duplicateConversation = async (id, options = {}) => {
528
553
  error.value = null;
529
554
  const statusToast = useStatusToast();
530
555
  const requestRuntimeScope = runtimeScope.value;
556
+ const requestKey = `chat-duplicate:${requestRuntimeScope}:${id}`;
557
+ if (duplicatesInFlight.has(requestKey))
558
+ return null;
559
+ duplicatesInFlight.add(requestKey);
560
+ const idempotencyKey = reserveDuplicateKey(requestKey, options.newAttempt === true);
531
561
  try {
532
562
  const duplicated = await $fetch(
533
563
  chatApi.path(`/conversations/${id}/duplicate`),
534
564
  chatApi.withChatHeaders({
535
- method: "POST"
565
+ method: "POST",
566
+ headers: { "Idempotency-Key": idempotencyKey }
536
567
  })
537
568
  );
569
+ releaseDuplicateKey(requestKey);
538
570
  upsertConversationInLoadedBuckets(duplicated, requestRuntimeScope);
539
571
  updateConversationCounts(requestRuntimeScope, duplicated, 1);
540
572
  statusToast.update({
@@ -543,13 +575,19 @@ export function useConversations() {
543
575
  });
544
576
  return duplicated;
545
577
  } catch (err) {
578
+ const status = err?.statusCode;
579
+ if (status !== void 0 && status >= 400 && status < 500)
580
+ releaseDuplicateKey(requestKey);
546
581
  const message = err instanceof Error ? err.message : "Erro ao duplicar conversa";
547
582
  error.value = message;
583
+ const code = err?.data?.code;
548
584
  statusToast.update({
549
- text: "Erro ao duplicar conversa",
585
+ text: code === "session_fork_source_running" ? "Aguarde a resposta terminar para duplicar a conversa." : "Erro ao duplicar conversa",
550
586
  icon: "i-ph-warning"
551
587
  });
552
588
  return null;
589
+ } finally {
590
+ duplicatesInFlight.delete(requestKey);
553
591
  }
554
592
  };
555
593
  return {
@@ -19,27 +19,27 @@ type __VLS_Props = {
19
19
  user?: EmbedUser | null;
20
20
  features?: Partial<ChatFeatureConfig>;
21
21
  };
22
- declare var __VLS_14: {}, __VLS_92: {}, __VLS_107: {
22
+ declare var __VLS_14: {}, __VLS_99: {}, __VLS_114: {
23
23
  message: useChatAuth;
24
- }, __VLS_120: {}, __VLS_129: {}, __VLS_193: {}, __VLS_214: {
24
+ }, __VLS_127: {}, __VLS_136: {}, __VLS_207: {}, __VLS_228: {
25
25
  message: useChatAuth;
26
- }, __VLS_227: {};
26
+ }, __VLS_241: {};
27
27
  type __VLS_Slots = {} & {
28
28
  'sidebar-bottom'?: (props: typeof __VLS_14) => any;
29
29
  } & {
30
- 'composer-bottom'?: (props: typeof __VLS_92) => any;
30
+ 'composer-bottom'?: (props: typeof __VLS_99) => any;
31
31
  } & {
32
- 'failed-message-actions'?: (props: typeof __VLS_107) => any;
32
+ 'failed-message-actions'?: (props: typeof __VLS_114) => any;
33
33
  } & {
34
- 'composer-bottom'?: (props: typeof __VLS_120) => any;
34
+ 'composer-bottom'?: (props: typeof __VLS_127) => any;
35
35
  } & {
36
- 'topbar-leading'?: (props: typeof __VLS_129) => any;
36
+ 'topbar-leading'?: (props: typeof __VLS_136) => any;
37
37
  } & {
38
- 'composer-bottom'?: (props: typeof __VLS_193) => any;
38
+ 'composer-bottom'?: (props: typeof __VLS_207) => any;
39
39
  } & {
40
- 'failed-message-actions'?: (props: typeof __VLS_214) => any;
40
+ 'failed-message-actions'?: (props: typeof __VLS_228) => any;
41
41
  } & {
42
- 'composer-bottom'?: (props: typeof __VLS_227) => any;
42
+ 'composer-bottom'?: (props: typeof __VLS_241) => any;
43
43
  };
44
44
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
45
45
  "update:conversationId": (value: string | null) => any;
@@ -14,6 +14,7 @@ import ChatMobileFilePreview from "./mobile/chat-mobile-file-preview.vue";
14
14
  import ChatMobileShell from "./mobile/chat-mobile-shell.vue";
15
15
  import ChatSidebarToggle from "./chat-sidebar-toggle.vue";
16
16
  import ChatDetailsPanel from "./chat-details-panel.vue";
17
+ import ChatForkPreparation from "./chat-fork-preparation.vue";
17
18
  import ChatMessageInput from "../../messages/components/chat-message-input.vue";
18
19
  import { confirmPendingComposerSend, prepareComposerSend, reconcileComposerSend } from "../../messages/composer-pending-send";
19
20
  import { composerDraftStorageKey, readComposerDraft, writeComposerDraft } from "../../messages/composer-draft";
@@ -600,10 +601,17 @@ const conversationCreator = computed(
600
601
  getMember
601
602
  })
602
603
  );
604
+ const pendingFork = computed(() => {
605
+ const fork = currentConversation.value?.fork;
606
+ return fork && fork.status !== "ready" ? fork : null;
607
+ });
608
+ const forkBlocked = computed(() => Boolean(pendingFork.value));
603
609
  function claimableWakeSessionId() {
604
610
  return agentWake?.state.value === "ready" && !currentConversation.value?.threadSessionId ? agentWake.sessionId.value : void 0;
605
611
  }
606
612
  async function handleSend(content, files, agentInputs) {
613
+ if (forkBlocked.value)
614
+ return;
607
615
  const prepared = composerExtension?.prepareSend?.(content);
608
616
  const outbound = prepared?.content ?? content;
609
617
  unconfirmedDraft = {
@@ -705,7 +713,7 @@ if (agentWake && !chatApi.conversationV2Enabled) {
705
713
  }
706
714
  const sendChatMessageFromContent = async (content) => {
707
715
  const trimmed = content.trim();
708
- if (!trimmed || props.composerDisabled || composerLocked.value)
716
+ if (!trimmed || props.composerDisabled || composerLocked.value || forkBlocked.value)
709
717
  return false;
710
718
  const resolvedAgentInputs = resolveTelaAgentInputs(void 0, embedConfig?.telaAgentInputs?.value);
711
719
  return Boolean(await dispatchMessage(trimmed, [], resolvedAgentInputs));
@@ -715,6 +723,8 @@ async function handleCancel() {
715
723
  await cancelConversation();
716
724
  }
717
725
  async function handleRetry(messageId) {
726
+ if (forkBlocked.value)
727
+ return;
718
728
  const executionSettings = { model: selectedModel.value, reasoningEffort: selectedReasoningEffort.value };
719
729
  await retryMessage(messageId, async () => {
720
730
  const conversation = currentConversation.value;
@@ -848,6 +858,56 @@ watch([
848
858
  }
849
859
  }, { immediate: true });
850
860
  const forking = ref(false);
861
+ const isLightweightHarness = computed(() => harnessAdapter?.requiresSandboxWake === false);
862
+ const forkPollError = ref(null);
863
+ async function retryForkPreparation(sourceId) {
864
+ if (forking.value)
865
+ return;
866
+ forking.value = true;
867
+ const origin = currentConversation.value?.id;
868
+ const scope = JSON.stringify(chatApi.withChatHeaders().headers);
869
+ try {
870
+ const duplicated = await duplicateConversation(sourceId, { newAttempt: true });
871
+ if (!duplicated || currentConversation.value?.id !== origin || JSON.stringify(chatApi.withChatHeaders().headers) !== scope)
872
+ return;
873
+ closePreview();
874
+ clearPendingConversationGroup();
875
+ setConversation(duplicated);
876
+ updateConversationId(duplicated.id);
877
+ } finally {
878
+ forking.value = false;
879
+ }
880
+ }
881
+ watch(() => [currentConversation.value?.id, currentConversation.value?.fork?.status, JSON.stringify(chatApi.withChatHeaders().headers)], ([id, status], _, onCleanup) => {
882
+ forkPollError.value = null;
883
+ if (!id || status !== "initializing")
884
+ return;
885
+ let stopped = false;
886
+ let timer;
887
+ const poll = async () => {
888
+ try {
889
+ const conversation = await $fetch(chatApi.path(`/conversations/${id}`), chatApi.withChatHeaders());
890
+ if (stopped || currentConversation.value?.id !== id)
891
+ return;
892
+ forkPollError.value = null;
893
+ if (conversation.fork?.status !== "initializing") {
894
+ setConversation(conversation);
895
+ return;
896
+ }
897
+ } catch {
898
+ if (!stopped)
899
+ forkPollError.value = "N\xE3o foi poss\xEDvel atualizar a prepara\xE7\xE3o. Tentando novamente\u2026";
900
+ }
901
+ if (!stopped)
902
+ timer = setTimeout(poll, 1500);
903
+ };
904
+ onCleanup(() => {
905
+ stopped = true;
906
+ if (timer)
907
+ clearTimeout(timer);
908
+ });
909
+ void poll();
910
+ }, { immediate: true });
851
911
  async function handleFork(messageId) {
852
912
  const sourceId = currentConversation.value?.id;
853
913
  if (!sourceId || forking.value)
@@ -1092,21 +1152,31 @@ async function handleDelete() {
1092
1152
  <template #empty>
1093
1153
  <TelaChatEmptyState
1094
1154
  compact
1095
- :title="emptyStateLabels.title"
1096
- :description="emptyStateLabels.description"
1155
+ :title="pendingFork ? void 0 : emptyStateLabels.title"
1156
+ :description="pendingFork ? void 0 : emptyStateLabels.description"
1097
1157
  >
1098
- <TelaChatSuggestions v-if="resolvedSuggestions.length">
1099
- <TelaChatSuggestion
1100
- v-for="suggestion in resolvedSuggestions"
1101
- :key="suggestion.title"
1102
- :suggestion="suggestion.prompt"
1103
- @click="applySuggestion($event)"
1104
- >
1105
- {{ suggestion.title }}
1106
- </TelaChatSuggestion>
1107
- </TelaChatSuggestions>
1158
+ <ChatForkPreparation
1159
+ v-if="pendingFork"
1160
+ compact
1161
+ :fork="pendingFork"
1162
+ :poll-error="forkPollError"
1163
+ :retrying="forking"
1164
+ @retry="retryForkPreparation(pendingFork.sourceConversationId)"
1165
+ />
1166
+ <template v-else>
1167
+ <TelaChatSuggestions v-if="resolvedSuggestions.length">
1168
+ <TelaChatSuggestion
1169
+ v-for="suggestion in resolvedSuggestions"
1170
+ :key="suggestion.title"
1171
+ :suggestion="suggestion.prompt"
1172
+ @click="applySuggestion($event)"
1173
+ >
1174
+ {{ suggestion.title }}
1175
+ </TelaChatSuggestion>
1176
+ </TelaChatSuggestions>
1108
1177
 
1109
- <TelaChatPromptInputHints v-if="resolvedFeatures.showPromptInputHints" size="sm" :hints="PROMPT_INPUT_HINTS" />
1178
+ <TelaChatPromptInputHints v-if="resolvedFeatures.showPromptInputHints" size="sm" :hints="PROMPT_INPUT_HINTS" />
1179
+ </template>
1110
1180
 
1111
1181
  <template #input>
1112
1182
  <ChatMessageInput
@@ -1115,7 +1185,7 @@ async function handleDelete() {
1115
1185
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1116
1186
  :draft-storage-key="draftStorageKey"
1117
1187
  :loading="loading || composerLocked"
1118
- :disabled="composerDisabled"
1188
+ :disabled="composerDisabled || forkBlocked"
1119
1189
  :send-blocked="sendBlocked"
1120
1190
  :creator-name="currentUser?.name ?? void 0"
1121
1191
  :creator-email="
@@ -1156,8 +1226,9 @@ async function handleDelete() {
1156
1226
  :show-timestamps="resolvedFeatures.showMessageTimestamps"
1157
1227
  :show-ai-mistakes-disclaimer="resolvedFeatures.showAiMistakesDisclaimer"
1158
1228
  variant="mobile"
1159
- :fork-enabled="chatApi.conversationV2Enabled"
1229
+ :fork-enabled="chatApi.conversationV2Enabled && !isLightweightHarness"
1160
1230
  :fork-disabled="forking"
1231
+ :retry-disabled="forkBlocked"
1161
1232
  @fork="handleFork"
1162
1233
  @open-conversation="openConversation"
1163
1234
  @retry="handleRetry"
@@ -1175,7 +1246,7 @@ async function handleDelete() {
1175
1246
  :draft-storage-key="draftStorageKey"
1176
1247
  class="!p-0"
1177
1248
  :loading="loading || composerLocked"
1178
- :disabled="composerDisabled"
1249
+ :disabled="composerDisabled || forkBlocked"
1179
1250
  :send-blocked="sendBlocked"
1180
1251
  :creator-name="currentUser?.name ?? void 0"
1181
1252
  :creator-email="
@@ -1305,25 +1376,35 @@ async function handleDelete() {
1305
1376
  <TelaChatEmptyState
1306
1377
  v-else-if="!isMobile && !hasMessages"
1307
1378
  :compact="compact"
1308
- :title="emptyStateLabels.title"
1309
- :description="emptyStateLabels.description"
1379
+ :title="pendingFork ? void 0 : emptyStateLabels.title"
1380
+ :description="pendingFork ? void 0 : emptyStateLabels.description"
1310
1381
  >
1311
- <TelaChatSuggestions v-if="resolvedSuggestions.length">
1312
- <TelaChatSuggestion
1313
- v-for="suggestion in resolvedSuggestions"
1314
- :key="suggestion.title"
1315
- :suggestion="suggestion.prompt"
1316
- @click="applySuggestion($event)"
1317
- >
1318
- {{ suggestion.title }}
1319
- </TelaChatSuggestion>
1320
- </TelaChatSuggestions>
1321
-
1322
- <TelaChatPromptInputHints
1323
- v-if="resolvedFeatures.showPromptInputHints"
1324
- :size="compact ? 'sm' : 'md'"
1325
- :hints="PROMPT_INPUT_HINTS"
1382
+ <ChatForkPreparation
1383
+ v-if="pendingFork"
1384
+ :compact="compact"
1385
+ :fork="pendingFork"
1386
+ :poll-error="forkPollError"
1387
+ :retrying="forking"
1388
+ @retry="retryForkPreparation(pendingFork.sourceConversationId)"
1326
1389
  />
1390
+ <template v-else>
1391
+ <TelaChatSuggestions v-if="resolvedSuggestions.length">
1392
+ <TelaChatSuggestion
1393
+ v-for="suggestion in resolvedSuggestions"
1394
+ :key="suggestion.title"
1395
+ :suggestion="suggestion.prompt"
1396
+ @click="applySuggestion($event)"
1397
+ >
1398
+ {{ suggestion.title }}
1399
+ </TelaChatSuggestion>
1400
+ </TelaChatSuggestions>
1401
+
1402
+ <TelaChatPromptInputHints
1403
+ v-if="resolvedFeatures.showPromptInputHints"
1404
+ :size="compact ? 'sm' : 'md'"
1405
+ :hints="PROMPT_INPUT_HINTS"
1406
+ />
1407
+ </template>
1327
1408
 
1328
1409
  <template #input>
1329
1410
  <ChatMessageInput
@@ -1332,7 +1413,7 @@ async function handleDelete() {
1332
1413
  v-model:selected-reasoning-effort="selectedReasoningEffort"
1333
1414
  :draft-storage-key="draftStorageKey"
1334
1415
  :loading="loading || composerLocked"
1335
- :disabled="composerDisabled"
1416
+ :disabled="composerDisabled || forkBlocked"
1336
1417
  :send-blocked="sendBlocked"
1337
1418
  :creator-name="currentUser?.name ?? void 0"
1338
1419
  :creator-email="
@@ -1376,8 +1457,9 @@ async function handleDelete() {
1376
1457
  :show-timestamps="resolvedFeatures.showMessageTimestamps"
1377
1458
  :show-ai-mistakes-disclaimer="resolvedFeatures.showAiMistakesDisclaimer"
1378
1459
  :variant="compact ? 'widget' : 'page'"
1379
- :fork-enabled="chatApi.conversationV2Enabled"
1460
+ :fork-enabled="chatApi.conversationV2Enabled && !isLightweightHarness"
1380
1461
  :fork-disabled="forking"
1462
+ :retry-disabled="forkBlocked"
1381
1463
  @fork="handleFork"
1382
1464
  @open-conversation="openConversation"
1383
1465
  @retry="handleRetry"
@@ -1395,7 +1477,7 @@ async function handleDelete() {
1395
1477
  :draft-storage-key="draftStorageKey"
1396
1478
  class="!p-0"
1397
1479
  :loading="loading || composerLocked"
1398
- :disabled="composerDisabled"
1480
+ :disabled="composerDisabled || forkBlocked"
1399
1481
  :send-blocked="sendBlocked"
1400
1482
  :creator-name="currentUser?.name ?? void 0"
1401
1483
  :creator-email="
@@ -19,27 +19,27 @@ type __VLS_Props = {
19
19
  user?: EmbedUser | null;
20
20
  features?: Partial<ChatFeatureConfig>;
21
21
  };
22
- declare var __VLS_14: {}, __VLS_92: {}, __VLS_107: {
22
+ declare var __VLS_14: {}, __VLS_99: {}, __VLS_114: {
23
23
  message: useChatAuth;
24
- }, __VLS_120: {}, __VLS_129: {}, __VLS_193: {}, __VLS_214: {
24
+ }, __VLS_127: {}, __VLS_136: {}, __VLS_207: {}, __VLS_228: {
25
25
  message: useChatAuth;
26
- }, __VLS_227: {};
26
+ }, __VLS_241: {};
27
27
  type __VLS_Slots = {} & {
28
28
  'sidebar-bottom'?: (props: typeof __VLS_14) => any;
29
29
  } & {
30
- 'composer-bottom'?: (props: typeof __VLS_92) => any;
30
+ 'composer-bottom'?: (props: typeof __VLS_99) => any;
31
31
  } & {
32
- 'failed-message-actions'?: (props: typeof __VLS_107) => any;
32
+ 'failed-message-actions'?: (props: typeof __VLS_114) => any;
33
33
  } & {
34
- 'composer-bottom'?: (props: typeof __VLS_120) => any;
34
+ 'composer-bottom'?: (props: typeof __VLS_127) => any;
35
35
  } & {
36
- 'topbar-leading'?: (props: typeof __VLS_129) => any;
36
+ 'topbar-leading'?: (props: typeof __VLS_136) => any;
37
37
  } & {
38
- 'composer-bottom'?: (props: typeof __VLS_193) => any;
38
+ 'composer-bottom'?: (props: typeof __VLS_207) => any;
39
39
  } & {
40
- 'failed-message-actions'?: (props: typeof __VLS_214) => any;
40
+ 'failed-message-actions'?: (props: typeof __VLS_228) => any;
41
41
  } & {
42
- 'composer-bottom'?: (props: typeof __VLS_227) => any;
42
+ 'composer-bottom'?: (props: typeof __VLS_241) => any;
43
43
  };
44
44
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
45
45
  "update:conversationId": (value: string | null) => any;
@@ -0,0 +1,14 @@
1
+ import type { ConversationFork } from '../../types/schemas/chat/conversations.js';
2
+ type __VLS_Props = {
3
+ fork: ConversationFork;
4
+ compact?: boolean;
5
+ pollError?: string | null;
6
+ retrying?: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ retry: () => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onRetry?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -0,0 +1,48 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ fork: { type: null, required: true },
4
+ compact: { type: Boolean, required: false },
5
+ pollError: { type: [String, null], required: false },
6
+ retrying: { type: Boolean, required: false }
7
+ });
8
+ const emit = defineEmits(["retry"]);
9
+ const failed = computed(() => props.fork.status === "failed");
10
+ const failureReason = computed(() => {
11
+ switch (props.fork.error) {
12
+ case "session_fork_restore_failed":
13
+ return "Alguns arquivos ou dados da conversa original n\xE3o puderam ser copiados.";
14
+ case "session_fork_checkpoint_unavailable":
15
+ return "O estado mais recente da conversa original n\xE3o est\xE1 dispon\xEDvel.";
16
+ default:
17
+ return "A prepara\xE7\xE3o foi interrompida.";
18
+ }
19
+ });
20
+ </script>
21
+
22
+ <template>
23
+ <div role="status" aria-live="polite" flex="~ col" items-center gap-16px text-center>
24
+ <div size-40px rounded-12px bg-neutral-100 flex items-center justify-center>
25
+ <span v-if="failed" i-ph-warning-circle text-20px text-red-600 />
26
+ <span v-else i-ph-spinner animate-spin text-20px text-neutral-500 />
27
+ </div>
28
+ <div flex="~ col" items-center gap-4px>
29
+ <component :is="compact ? 'h5' : 'h1'" text-primary :class="compact ? 'heading-h4-semibold' : 'heading-h3-semibold'">
30
+ {{ failed ? "N\xE3o foi poss\xEDvel preparar a c\xF3pia" : "Preparando a c\xF3pia" }}
31
+ </component>
32
+ <p text-secondary body-14-regular max-w-440px>
33
+ <template v-if="failed">
34
+ {{ failureReason }} Tente novamente para criar uma nova cópia.
35
+ </template>
36
+ <template v-else>
37
+ Copiando o histórico, os arquivos e o contexto da conversa original. Você poderá continuar em instantes.
38
+ </template>
39
+ </p>
40
+ <p v-if="!failed && pollError" text-secondary body-12-regular>
41
+ {{ pollError }}
42
+ </p>
43
+ </div>
44
+ <TelaButton v-if="failed" size="sm" variant="secondary" :disabled="retrying" @click="emit('retry')">
45
+ Tentar novamente
46
+ </TelaButton>
47
+ </div>
48
+ </template>
@@ -0,0 +1,14 @@
1
+ import type { ConversationFork } from '../../types/schemas/chat/conversations.js';
2
+ type __VLS_Props = {
3
+ fork: ConversationFork;
4
+ compact?: boolean;
5
+ pollError?: string | null;
6
+ retrying?: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ retry: () => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onRetry?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -31,6 +31,7 @@ const eventTime = computed(() => new Intl.DateTimeFormat("pt-BR", {
31
31
  <p v-if="forkSource" body-12-regular text-secondary text-center min-w-0>
32
32
  Conversa iniciada a partir de
33
33
  <NuxtLink
34
+ v-if="forkSource.sourceAccessible !== false"
34
35
  :to="sourceLocation"
35
36
  body-12-medium
36
37
  underline
@@ -39,6 +40,7 @@ const eventTime = computed(() => new Intl.DateTimeFormat("pt-BR", {
39
40
  >
40
41
  {{ forkSource.sourceTitle }}
41
42
  </NuxtLink>
43
+ <span v-else>{{ forkSource.sourceTitle }}</span>
42
44
  </p>
43
45
  <p v-else body-12-regular text-tertiary text-center>
44
46
  <span v-if="event.actorLabel" body-12-medium text-secondary>{{ event.actorLabel }}</span>
@@ -13,6 +13,7 @@ type __VLS_Props = {
13
13
  showTimestamps?: boolean;
14
14
  forkEnabled?: boolean;
15
15
  forkDisabled?: boolean;
16
+ retryDisabled?: boolean;
16
17
  };
17
18
  type __VLS_Slots = {
18
19
  /** Host actions rendered inside a failed assistant message, next to the retry button. */
@@ -21,7 +21,8 @@ const props = defineProps({
21
21
  showTtft: { type: Boolean, required: false, default: true },
22
22
  showTimestamps: { type: Boolean, required: false, default: true },
23
23
  forkEnabled: { type: Boolean, required: false },
24
- forkDisabled: { type: Boolean, required: false }
24
+ forkDisabled: { type: Boolean, required: false },
25
+ retryDisabled: { type: Boolean, required: false }
25
26
  });
26
27
  const emit = defineEmits(["retry", "fork"]);
27
28
  defineSlots();
@@ -170,24 +171,29 @@ const attachedFiles = computed(() => {
170
171
  :class="[isPending && 'op-80']"
171
172
  >
172
173
  <div v-if="hasFiles" flex="~ col" gap-4px mb-8px>
173
- <a
174
- v-for="file in message.files"
175
- :key="file.url"
176
- :href="file.url"
177
- target="_blank"
178
- rel="noopener noreferrer"
179
- flex
180
- items-center
181
- gap-6px
182
- body-12-regular
183
- text-gray-600
184
- op-80
185
- hover:op-100
186
- transition-opacity
187
- >
188
- <span i-ph-paperclip text-14px />
189
- <span truncate max-w-200px>{{ file.name }}</span>
190
- </a>
174
+ <template v-for="file in message.files" :key="file.url">
175
+ <ChatFilePreview
176
+ v-if="file.url.startsWith('vault://')"
177
+ :file="{ type: 'vault', reference: file.url, name: file.name, mimeType: file.mimeType ?? 'unknown' }"
178
+ />
179
+ <a
180
+ v-else
181
+ :href="file.url"
182
+ target="_blank"
183
+ rel="noopener noreferrer"
184
+ flex
185
+ items-center
186
+ gap-6px
187
+ body-12-regular
188
+ text-gray-600
189
+ op-80
190
+ hover:op-100
191
+ transition-opacity
192
+ >
193
+ <span i-ph-paperclip text-14px />
194
+ <span truncate max-w-200px>{{ file.name }}</span>
195
+ </a>
196
+ </template>
191
197
  </div>
192
198
  <div body-16-regular whitespace-pre-wrap break-words>
193
199
  <template v-for="(segment, index) in userContentSegments" :key="`${index}-${segment.type === 'citation' ? segment.citation.raw : 'text'}`">
@@ -264,6 +270,7 @@ const attachedFiles = computed(() => {
264
270
  text-13px text-gray-700
265
271
  transition-colors
266
272
  cursor-pointer
273
+ :disabled="retryDisabled"
267
274
  @click="emit('retry', message.id)"
268
275
  >
269
276
  <div i-ph-arrow-clockwise text-14px />
@@ -13,6 +13,7 @@ type __VLS_Props = {
13
13
  showTimestamps?: boolean;
14
14
  forkEnabled?: boolean;
15
15
  forkDisabled?: boolean;
16
+ retryDisabled?: boolean;
16
17
  };
17
18
  type __VLS_Slots = {
18
19
  /** Host actions rendered inside a failed assistant message, next to the retry button. */
@@ -16,6 +16,7 @@ type __VLS_Props = {
16
16
  showAiMistakesDisclaimer?: boolean;
17
17
  forkEnabled?: boolean;
18
18
  forkDisabled?: boolean;
19
+ retryDisabled?: boolean;
19
20
  };
20
21
  type __VLS_Slots = {
21
22
  'failed-message-actions'?: (props: {
@@ -16,7 +16,8 @@ const props = defineProps({
16
16
  showTimestamps: { type: Boolean, required: false, default: true },
17
17
  showAiMistakesDisclaimer: { type: Boolean, required: false, default: true },
18
18
  forkEnabled: { type: Boolean, required: false },
19
- forkDisabled: { type: Boolean, required: false }
19
+ forkDisabled: { type: Boolean, required: false },
20
+ retryDisabled: { type: Boolean, required: false }
20
21
  });
21
22
  const emit = defineEmits(["retry", "fork", "openConversation"]);
22
23
  defineSlots();
@@ -145,6 +146,7 @@ defineExpose({
145
146
  :show-timestamps="showTimestamps"
146
147
  :fork-enabled="forkEnabled"
147
148
  :fork-disabled="forkDisabled"
149
+ :retry-disabled="retryDisabled"
148
150
  @fork="emit('fork', $event)"
149
151
  @retry="emit('retry', $event)"
150
152
  >
@@ -16,6 +16,7 @@ type __VLS_Props = {
16
16
  showAiMistakesDisclaimer?: boolean;
17
17
  forkEnabled?: boolean;
18
18
  forkDisabled?: boolean;
19
+ retryDisabled?: boolean;
19
20
  };
20
21
  type __VLS_Slots = {
21
22
  'failed-message-actions'?: (props: {
@@ -4,6 +4,7 @@ export declare function getConversationForkSource(event: ConversationEvent): {
4
4
  sourceConversationId: string;
5
5
  sourceTitle: string;
6
6
  copiedMessageId: string;
7
+ sourceAccessible?: boolean | undefined;
7
8
  } | null;
8
9
  export type ConversationTimelineItem = {
9
10
  kind: 'event';
@@ -4,6 +4,7 @@ import { resolveChatExecutionMode } from "../embed/execution-controls.js";
4
4
  const forkSourceSchema = z.object({
5
5
  sourceConversationId: z.string().uuid(),
6
6
  sourceTitle: z.string(),
7
+ sourceAccessible: z.boolean().optional(),
7
8
  copiedMessageId: z.string().uuid()
8
9
  });
9
10
  export function getConversationForkSource(event) {
@@ -41,13 +42,21 @@ export function mergeConversationTimeline(messages, events = [], controls) {
41
42
  const targetIndex = items.findIndex((candidate) => candidate.kind === "message" && candidate.id === item.value.payload.messageId);
42
43
  items.splice(targetIndex, 0, item);
43
44
  }
45
+ const forkMarkers = /* @__PURE__ */ new Map();
44
46
  for (const event of events ?? []) {
45
47
  const source = getConversationForkSource(event);
46
48
  if (!source)
47
49
  continue;
48
- const index = items.findIndex((item) => item.kind === "message" && item.id === source.copiedMessageId);
49
- if (index !== -1)
50
- items.splice(index + 1, 0, { kind: "event", id: event.id, createdAt: event.createdAt, value: event });
50
+ const markers = forkMarkers.get(source.copiedMessageId) ?? [];
51
+ markers.push({ kind: "event", id: event.id, createdAt: event.createdAt, value: event });
52
+ forkMarkers.set(source.copiedMessageId, markers);
53
+ }
54
+ for (const [messageId, markers] of forkMarkers) {
55
+ const index = items.findIndex((item) => item.kind === "message" && item.id === messageId);
56
+ if (index !== -1) {
57
+ markers.sort((left, right) => new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime() || left.id.localeCompare(right.id));
58
+ items.splice(index + 1, 0, ...markers);
59
+ }
51
60
  }
52
61
  if (controls?.mode === "simple") {
53
62
  const seen = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import { AGENT_API_V5_CONFIG_KEY, PRE_WAKE_CACHE_CONFIG_KEY, CONVERSATION_V2_CONFIG_KEY, PREPARED_AGENT_RUNTIME_CONFIG_KEY, ROUTE_CLAUDE_VIA_GATEWAY_CONFIG_KEY, SECRETLESS_EXECUTION_CONFIG_KEY, UI_LANGUAGE_CONFIG_KEY } from "../../types/schemas/chat/chat-config.js";
1
+ import { AGENT_API_V5_CONFIG_KEY, LIGHTWEIGHT_SESSION_FORK_CONFIG_KEY, PRE_WAKE_CACHE_CONFIG_KEY, CONVERSATION_V2_CONFIG_KEY, PREPARED_AGENT_RUNTIME_CONFIG_KEY, ROUTE_CLAUDE_VIA_GATEWAY_CONFIG_KEY, SECRETLESS_EXECUTION_CONFIG_KEY, UI_LANGUAGE_CONFIG_KEY } from "../../types/schemas/chat/chat-config.js";
2
2
  import { createError, defineEventHandler, getHeader, getRequestURL, getRouterParam, proxyRequest } from "h3";
3
3
  import { useRuntimeConfig } from "nitropack/runtime";
4
4
  import { resolveAuthToken } from "./auth-token.js";
@@ -99,6 +99,9 @@ function buildProxyHeaders(event) {
99
99
  if (config.chatNuxt?.secretlessExecution) {
100
100
  chatConfig[SECRETLESS_EXECUTION_CONFIG_KEY] = true;
101
101
  }
102
+ if (config.chatNuxt?.lightweightSessionFork === true) {
103
+ chatConfig[LIGHTWEIGHT_SESSION_FORK_CONFIG_KEY] = true;
104
+ }
102
105
  if (config.chatNuxt?.preWakeCache === true) {
103
106
  chatConfig[PRE_WAKE_CACHE_CONFIG_KEY] = true;
104
107
  }
@@ -3,6 +3,7 @@ export declare const PREPARED_AGENT_RUNTIME_CONFIG_KEY = "prepared-agent-runtime
3
3
  export declare const SECRETLESS_EXECUTION_CONFIG_KEY = "secretless-execution";
4
4
  export declare const AGENT_API_V5_CONFIG_KEY = "agent-api-v5";
5
5
  export declare const CONVERSATION_V2_CONFIG_KEY = "conversation-v2";
6
+ export declare const LIGHTWEIGHT_SESSION_FORK_CONFIG_KEY = "lightweight-session-fork";
6
7
  export declare const PRE_WAKE_CACHE_CONFIG_KEY = "pre-wake-cache";
7
8
  export declare const ROUTE_CLAUDE_VIA_GATEWAY_CONFIG_KEY = "route-claude-via-gateway";
8
9
  export type ChatHarness = 'claude' | 'lightweight';
@@ -2,6 +2,7 @@ export const PREPARED_AGENT_RUNTIME_CONFIG_KEY = 'prepared-agent-runtime';
2
2
  export const SECRETLESS_EXECUTION_CONFIG_KEY = 'secretless-execution';
3
3
  export const AGENT_API_V5_CONFIG_KEY = 'agent-api-v5';
4
4
  export const CONVERSATION_V2_CONFIG_KEY = 'conversation-v2';
5
+ export const LIGHTWEIGHT_SESSION_FORK_CONFIG_KEY = 'lightweight-session-fork';
5
6
  export const PRE_WAKE_CACHE_CONFIG_KEY = 'pre-wake-cache';
6
7
  export const ROUTE_CLAUDE_VIA_GATEWAY_CONFIG_KEY = 'route-claude-via-gateway';
7
8
  export function resolveChatHarness(config) {
@@ -206,6 +206,17 @@ export declare const sendMessageV2ResponseSchema: z.ZodObject<{
206
206
  waiting_messages: "waiting_messages";
207
207
  blocked: "blocked";
208
208
  }>;
209
+ fork: z.ZodOptional<z.ZodNullable<z.ZodObject<{
210
+ sourceConversationId: z.ZodString;
211
+ baseSessionId: z.ZodString;
212
+ checkpointId: z.ZodNullable<z.ZodString>;
213
+ status: z.ZodEnum<{
214
+ failed: "failed";
215
+ ready: "ready";
216
+ initializing: "initializing";
217
+ }>;
218
+ error: z.ZodNullable<z.ZodString>;
219
+ }, z.core.$strip>>>;
209
220
  }, z.core.$strip>;
210
221
  }, z.core.$strip>;
211
222
  export type SendMessageV2Response = z.infer<typeof sendMessageV2ResponseSchema>;
@@ -215,6 +226,17 @@ export declare const conversationRuntimeV2ResponseSchema: z.ZodObject<{
215
226
  waiting_messages: "waiting_messages";
216
227
  blocked: "blocked";
217
228
  }>;
229
+ fork: z.ZodOptional<z.ZodNullable<z.ZodObject<{
230
+ sourceConversationId: z.ZodString;
231
+ baseSessionId: z.ZodString;
232
+ checkpointId: z.ZodNullable<z.ZodString>;
233
+ status: z.ZodEnum<{
234
+ failed: "failed";
235
+ ready: "ready";
236
+ initializing: "initializing";
237
+ }>;
238
+ error: z.ZodNullable<z.ZodString>;
239
+ }, z.core.$strip>>>;
218
240
  }, z.core.$strip>;
219
241
  turn: z.ZodNullable<z.ZodObject<{
220
242
  id: z.ZodString;
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { telaAgentExecutionInputSchema } from '../integrations/tela-agent-api';
3
- import { messageSchema } from './conversations';
3
+ import { conversationForkSchema, messageSchema } from './conversations';
4
4
  import { fileUploadInputSchema } from './files';
5
5
  import { executionSettingsSchema, sendMessageClientTimingSchema } from './messages';
6
6
  export const conversationRuntimeStateSchema = z.enum(['blocked', 'waiting_messages']);
@@ -39,10 +39,10 @@ export const sendMessageV2ResponseSchema = z.object({
39
39
  userMessage: messageSchema,
40
40
  assistantMessage: messageSchema,
41
41
  turn: turnSnapshotSchema.nullable(),
42
- conversation: z.object({ runtimeState: conversationRuntimeStateSchema }),
42
+ conversation: z.object({ runtimeState: conversationRuntimeStateSchema, fork: conversationForkSchema.nullable().optional() }),
43
43
  });
44
44
  export const conversationRuntimeV2ResponseSchema = z.object({
45
- conversation: z.object({ runtimeState: conversationRuntimeStateSchema }),
45
+ conversation: z.object({ runtimeState: conversationRuntimeStateSchema, fork: conversationForkSchema.nullable().optional() }),
46
46
  turn: turnSnapshotSchema.nullable(),
47
47
  });
48
48
  export const cancelConversationV2ResponseSchema = z.object({
@@ -1,5 +1,28 @@
1
1
  import { z } from 'zod';
2
+ export declare const conversationForkSchema: z.ZodObject<{
3
+ sourceConversationId: z.ZodString;
4
+ baseSessionId: z.ZodString;
5
+ checkpointId: z.ZodNullable<z.ZodString>;
6
+ status: z.ZodEnum<{
7
+ failed: "failed";
8
+ ready: "ready";
9
+ initializing: "initializing";
10
+ }>;
11
+ error: z.ZodNullable<z.ZodString>;
12
+ }, z.core.$strip>;
13
+ export type ConversationFork = z.infer<typeof conversationForkSchema>;
2
14
  export declare const conversationSchema: z.ZodObject<{
15
+ fork: z.ZodOptional<z.ZodNullable<z.ZodObject<{
16
+ sourceConversationId: z.ZodString;
17
+ baseSessionId: z.ZodString;
18
+ checkpointId: z.ZodNullable<z.ZodString>;
19
+ status: z.ZodEnum<{
20
+ failed: "failed";
21
+ ready: "ready";
22
+ initializing: "initializing";
23
+ }>;
24
+ error: z.ZodNullable<z.ZodString>;
25
+ }, z.core.$strip>>>;
3
26
  id: z.ZodString;
4
27
  workspaceId: z.ZodString;
5
28
  groupId: z.ZodNullable<z.ZodString>;
@@ -138,6 +161,17 @@ export declare const conversationListMetadataSchema: z.ZodObject<{
138
161
  }, z.core.$strip>;
139
162
  export type ConversationListMetadata = z.infer<typeof conversationListMetadataSchema>;
140
163
  export declare const conversationWithMessagesSchema: z.ZodObject<{
164
+ fork: z.ZodOptional<z.ZodNullable<z.ZodObject<{
165
+ sourceConversationId: z.ZodString;
166
+ baseSessionId: z.ZodString;
167
+ checkpointId: z.ZodNullable<z.ZodString>;
168
+ status: z.ZodEnum<{
169
+ failed: "failed";
170
+ ready: "ready";
171
+ initializing: "initializing";
172
+ }>;
173
+ error: z.ZodNullable<z.ZodString>;
174
+ }, z.core.$strip>>>;
141
175
  id: z.ZodString;
142
176
  workspaceId: z.ZodString;
143
177
  groupId: z.ZodNullable<z.ZodString>;
@@ -6,7 +6,15 @@ import { chatModelSchema, chatReasoningEffortSchema } from './models';
6
6
  import { chatModelSchema as lightweightModelSchema } from './lightweight-models';
7
7
  import { conversationCreatorFilterSchema, messageRoleSchema, messageStatusSchema, } from './primitives';
8
8
  import { resolvedWorkspaceSettingsSchema } from './workspace-settings';
9
+ export const conversationForkSchema = z.object({
10
+ sourceConversationId: z.string(),
11
+ baseSessionId: z.string(),
12
+ checkpointId: z.string().nullable(),
13
+ status: z.enum(['initializing', 'ready', 'failed']),
14
+ error: z.string().nullable(),
15
+ });
9
16
  export const conversationSchema = z.object({
17
+ fork: conversationForkSchema.nullable().optional(),
10
18
  id: z.string(),
11
19
  workspaceId: z.string(),
12
20
  groupId: z.string().nullable(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "4.11.0-rc.4",
3
+ "version": "4.11.0-rc.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {