@meistrari/chat-nuxt 3.16.0-rc.4 → 3.16.0
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 +1 -1
- package/dist/runtime/conversations/components/chat-conversation-item.vue +3 -10
- package/dist/runtime/conversations/composables/conversation-item.d.ts +0 -2
- package/dist/runtime/conversations/composables/conversation-item.js +16 -19
- package/dist/runtime/messages/components/chat-debug-conversation-rail.d.vue.ts +16 -0
- package/dist/runtime/messages/components/chat-debug-conversation-rail.vue +104 -0
- package/dist/runtime/messages/components/chat-debug-conversation-rail.vue.d.ts +16 -0
- package/dist/runtime/messages/components/chat-debug-workbench.vue +124 -17
- package/dist/runtime/messages/debug/chat-debug-citations.js +45 -1
- package/dist/runtime/messages/debug/chat-debug-scenarios.d.ts +29 -1
- package/dist/runtime/messages/debug/chat-debug-scenarios.js +14 -1
- package/dist/runtime/messages/debug/chat-debug-switch-scenario.d.ts +21 -0
- package/dist/runtime/messages/debug/chat-debug-switch-scenario.js +271 -0
- package/dist/runtime/messages/debug/use-chat-debug-diagnostics.d.ts +32 -0
- package/dist/runtime/messages/debug/use-chat-debug-diagnostics.js +58 -0
- package/dist/runtime/messages/debug/use-chat-debug-playback.d.ts +3 -2
- package/dist/runtime/messages/debug/use-chat-debug-playback.js +19 -5
- package/dist/runtime/messages/message-reasoning.js +11 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -14,8 +14,6 @@ const {
|
|
|
14
14
|
renameTitle,
|
|
15
15
|
renameInputRef,
|
|
16
16
|
showDeleteModal,
|
|
17
|
-
menuButtonRef,
|
|
18
|
-
isMenuOpen,
|
|
19
17
|
conversationPath,
|
|
20
18
|
displayTitle,
|
|
21
19
|
menuItems,
|
|
@@ -32,9 +30,6 @@ const {
|
|
|
32
30
|
function setRenameInputRef(element) {
|
|
33
31
|
renameInputRef.value = element;
|
|
34
32
|
}
|
|
35
|
-
function setMenuButtonRef(element) {
|
|
36
|
-
menuButtonRef.value = element;
|
|
37
|
-
}
|
|
38
33
|
</script>
|
|
39
34
|
|
|
40
35
|
<template>
|
|
@@ -115,23 +110,21 @@ function setMenuButtonRef(element) {
|
|
|
115
110
|
mt-2px
|
|
116
111
|
flex
|
|
117
112
|
items-center
|
|
118
|
-
class="opacity-0 group-hover:opacity-100"
|
|
119
|
-
:class="isMenuOpen && 'opacity-100'"
|
|
113
|
+
class="opacity-0 group-hover:opacity-100 focus-within:opacity-100 [&:has([data-state=open])]:opacity-100"
|
|
120
114
|
>
|
|
121
115
|
<TelaDropdownMenu :items="menuItems" content-class="chat-sidebar-floating">
|
|
122
116
|
<button
|
|
123
|
-
:ref="setMenuButtonRef"
|
|
124
117
|
p-4px
|
|
125
118
|
rounded-8px
|
|
126
119
|
hover:bg-lowered
|
|
127
120
|
cursor-pointer
|
|
128
|
-
|
|
121
|
+
class="group data-[state=open]:!bg-lowered"
|
|
129
122
|
@click.prevent.stop
|
|
130
123
|
>
|
|
131
124
|
<TelaIcon
|
|
132
125
|
name="i-ph-dots-three"
|
|
133
126
|
size="18px"
|
|
134
|
-
|
|
127
|
+
color="icon-subtle group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
|
|
135
128
|
/>
|
|
136
129
|
</button>
|
|
137
130
|
</TelaDropdownMenu>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useMagicKeys, useMutationObserver } from "@vueuse/core";
|
|
2
1
|
import { resolveConversationRenameTitle } from "../title.js";
|
|
3
2
|
import { capitalizeFirst } from "../../shared/string.js";
|
|
4
3
|
const conversationNavigationDelayMs = 250;
|
|
@@ -8,28 +7,27 @@ export function useConversationItem(options, emit) {
|
|
|
8
7
|
const renameInputRef = ref(null);
|
|
9
8
|
const isIgnoringInitialRenameBlur = ref(false);
|
|
10
9
|
const showDeleteModal = ref(false);
|
|
11
|
-
const menuButtonRef = ref(null);
|
|
12
|
-
const isMenuOpen = ref(false);
|
|
13
10
|
const conversationPath = computed(() => `/${options.conversation.id}`);
|
|
14
11
|
const displayTitle = computed(() => capitalizeFirst(options.conversation.title));
|
|
15
|
-
const { escape } = useMagicKeys();
|
|
16
12
|
let conversationNavigationTimer = null;
|
|
17
13
|
let renameBlurTimer = null;
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const escapeCancellable = computed(() => isRenamingInline.value || showDeleteModal.value);
|
|
15
|
+
function handleEscapeKeydown(event) {
|
|
16
|
+
if (event.key !== "Escape")
|
|
17
|
+
return;
|
|
18
|
+
if (isRenamingInline.value)
|
|
20
19
|
cancelRename();
|
|
21
|
-
|
|
22
|
-
if (pressed && showDeleteModal.value) {
|
|
20
|
+
if (showDeleteModal.value)
|
|
23
21
|
cancelDelete();
|
|
24
|
-
|
|
22
|
+
}
|
|
23
|
+
watch(escapeCancellable, (active) => {
|
|
24
|
+
if (typeof window === "undefined")
|
|
25
|
+
return;
|
|
26
|
+
if (active)
|
|
27
|
+
window.addEventListener("keydown", handleEscapeKeydown);
|
|
28
|
+
else
|
|
29
|
+
window.removeEventListener("keydown", handleEscapeKeydown);
|
|
25
30
|
});
|
|
26
|
-
useMutationObserver(menuButtonRef, (mutations) => {
|
|
27
|
-
for (const mutation of mutations) {
|
|
28
|
-
if (mutation.attributeName === "data-state") {
|
|
29
|
-
isMenuOpen.value = menuButtonRef.value?.getAttribute("data-state") === "open";
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}, { attributes: true });
|
|
33
31
|
function clearPendingConversationNavigation() {
|
|
34
32
|
if (conversationNavigationTimer === null)
|
|
35
33
|
return;
|
|
@@ -79,7 +77,6 @@ export function useConversationItem(options, emit) {
|
|
|
79
77
|
allowRenameBlurSoon();
|
|
80
78
|
}
|
|
81
79
|
function startRename() {
|
|
82
|
-
isMenuOpen.value = false;
|
|
83
80
|
clearPendingConversationNavigation();
|
|
84
81
|
clearRenameBlurTimer();
|
|
85
82
|
renameTitle.value = options.conversation.title;
|
|
@@ -151,14 +148,14 @@ export function useConversationItem(options, emit) {
|
|
|
151
148
|
onBeforeUnmount(() => {
|
|
152
149
|
clearPendingConversationNavigation();
|
|
153
150
|
clearRenameBlurTimer();
|
|
151
|
+
if (typeof window !== "undefined")
|
|
152
|
+
window.removeEventListener("keydown", handleEscapeKeydown);
|
|
154
153
|
});
|
|
155
154
|
return {
|
|
156
155
|
isRenamingInline,
|
|
157
156
|
renameTitle,
|
|
158
157
|
renameInputRef,
|
|
159
158
|
showDeleteModal,
|
|
160
|
-
menuButtonRef,
|
|
161
|
-
isMenuOpen,
|
|
162
159
|
conversationPath,
|
|
163
160
|
displayTitle,
|
|
164
161
|
menuItems,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ChatDebugConversationFixture, ChatDebugSidebarFixture } from '../debug/chat-debug-scenarios.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
sidebar: ChatDebugSidebarFixture;
|
|
4
|
+
activeConversationId: string | null;
|
|
5
|
+
/** Bumped by the workbench on restart so local rename/delete state rewinds with the timeline. */
|
|
6
|
+
resetKey: number;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
switch: (fixture: ChatDebugConversationFixture) => any;
|
|
10
|
+
"update:mountedCount": (count: number) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onSwitch?: ((fixture: ChatDebugConversationFixture) => any) | undefined;
|
|
13
|
+
"onUpdate:mountedCount"?: ((count: number) => any) | undefined;
|
|
14
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ChatConversationItem from "../../conversations/components/chat-conversation-item.vue";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
sidebar: { type: Object, required: true },
|
|
5
|
+
activeConversationId: { type: [String, null], required: true },
|
|
6
|
+
resetKey: { type: Number, required: true }
|
|
7
|
+
});
|
|
8
|
+
const emit = defineEmits(["switch", "update:mountedCount"]);
|
|
9
|
+
const stress = ref(false);
|
|
10
|
+
const titleOverrides = ref({});
|
|
11
|
+
const deletedIds = ref(/* @__PURE__ */ new Set());
|
|
12
|
+
const lastAction = ref(null);
|
|
13
|
+
const fixturesById = computed(() => new Map(
|
|
14
|
+
props.sidebar.conversations.map((fixture) => [fixture.conversation.id, fixture])
|
|
15
|
+
));
|
|
16
|
+
const visibleItems = computed(() => {
|
|
17
|
+
const count = stress.value ? props.sidebar.stressCount : props.sidebar.defaultCount;
|
|
18
|
+
return props.sidebar.conversations.slice(0, count).filter((fixture) => !deletedIds.value.has(fixture.conversation.id)).map((fixture) => {
|
|
19
|
+
const title = titleOverrides.value[fixture.conversation.id];
|
|
20
|
+
return title ? { ...fixture.conversation, title } : fixture.conversation;
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
watch(() => visibleItems.value.length, (count) => emit("update:mountedCount", count), { immediate: true });
|
|
24
|
+
watch(() => props.resetKey, () => {
|
|
25
|
+
titleOverrides.value = {};
|
|
26
|
+
deletedIds.value = /* @__PURE__ */ new Set();
|
|
27
|
+
lastAction.value = null;
|
|
28
|
+
});
|
|
29
|
+
function selectConversation(id) {
|
|
30
|
+
const fixture = fixturesById.value.get(id);
|
|
31
|
+
if (!fixture)
|
|
32
|
+
return;
|
|
33
|
+
lastAction.value = `switch \u2192 ${fixture.conversation.title}`;
|
|
34
|
+
emit("switch", fixture);
|
|
35
|
+
}
|
|
36
|
+
function handleRename(id, title) {
|
|
37
|
+
titleOverrides.value = { ...titleOverrides.value, [id]: title };
|
|
38
|
+
lastAction.value = `rename \u2192 ${title}`;
|
|
39
|
+
}
|
|
40
|
+
function handleDelete(id) {
|
|
41
|
+
deletedIds.value = /* @__PURE__ */ new Set([...deletedIds.value, id]);
|
|
42
|
+
lastAction.value = `delete \u2192 ${id}`;
|
|
43
|
+
}
|
|
44
|
+
function recordAction(action) {
|
|
45
|
+
lastAction.value = action;
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<aside class="debug-conversation-rail" flex="~ col" min-h-0 shrink-0>
|
|
51
|
+
<div flex items-center justify-between gap-8px px-8px pb-8px>
|
|
52
|
+
<span body-caption-9-medium text-tertiary>
|
|
53
|
+
Conversas · {{ visibleItems.length }}
|
|
54
|
+
</span>
|
|
55
|
+
<div flex items-center gap-8px>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
body-10-regular
|
|
59
|
+
:class="stress ? 'text-tertiary hover:text-secondary' : 'text-primary'"
|
|
60
|
+
transition-colors
|
|
61
|
+
@click="stress = false"
|
|
62
|
+
>
|
|
63
|
+
Default ({{ sidebar.defaultCount }})
|
|
64
|
+
</button>
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
body-10-regular
|
|
68
|
+
:class="stress ? 'text-primary' : 'text-tertiary hover:text-secondary'"
|
|
69
|
+
transition-colors
|
|
70
|
+
@click="stress = true"
|
|
71
|
+
>
|
|
72
|
+
Stress ({{ sidebar.stressCount }})
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div flex="~ col" gap-1px min-h-0 grow overflow-y-auto px-4px>
|
|
78
|
+
<ChatConversationItem
|
|
79
|
+
v-for="item in visibleItems"
|
|
80
|
+
:key="item.id"
|
|
81
|
+
:conversation="item"
|
|
82
|
+
:active="item.id === activeConversationId"
|
|
83
|
+
:draggable="true"
|
|
84
|
+
:select-conversation="selectConversation"
|
|
85
|
+
@rename="handleRename"
|
|
86
|
+
@delete="handleDelete"
|
|
87
|
+
@duplicate="recordAction(`duplicate \u2192 ${$event}`)"
|
|
88
|
+
@export="recordAction(`export \u2192 ${$event}`)"
|
|
89
|
+
@drag-start="recordAction(`drag \u2192 ${$event}`)"
|
|
90
|
+
@drag-end="recordAction('drag end')"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div h-24px px-8px flex items-center shrink-0>
|
|
95
|
+
<span v-if="lastAction" body-10-regular text-tertiary truncate>
|
|
96
|
+
{{ lastAction }}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
</aside>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<style scoped>
|
|
103
|
+
.debug-conversation-rail{border-right:1px solid rgba(229,231,235,.7);padding-top:8px;width:232px}
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ChatDebugConversationFixture, ChatDebugSidebarFixture } from '../debug/chat-debug-scenarios.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
sidebar: ChatDebugSidebarFixture;
|
|
4
|
+
activeConversationId: string | null;
|
|
5
|
+
/** Bumped by the workbench on restart so local rename/delete state rewinds with the timeline. */
|
|
6
|
+
resetKey: number;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
switch: (fixture: ChatDebugConversationFixture) => any;
|
|
10
|
+
"update:mountedCount": (count: number) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onSwitch?: ((fixture: ChatDebugConversationFixture) => any) | undefined;
|
|
13
|
+
"onUpdate:mountedCount"?: ((count: number) => any) | undefined;
|
|
14
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import ChatDebugConversationRail from "./chat-debug-conversation-rail.vue";
|
|
2
3
|
import ChatMessageList from "./chat-message-list.vue";
|
|
3
4
|
import { provideEmbedConfig } from "../../embed/composables/embed-config";
|
|
4
5
|
import { createChatDebugCitationsConfig } from "../debug/chat-debug-citations";
|
|
6
|
+
import { createReasoningCache, resolveReasoningDisplayMessages } from "../message-reasoning";
|
|
5
7
|
import { createChatDebugScenarios } from "../debug/chat-debug-scenarios";
|
|
6
|
-
import { useChatDebugDiagnostics } from "../debug/use-chat-debug-diagnostics";
|
|
8
|
+
import { useChatDebugDiagnostics, useChatDebugWindowListenerProbe } from "../debug/use-chat-debug-diagnostics";
|
|
7
9
|
import { formatChatDebugTime as formatTime, useChatDebugPlayback } from "../debug/use-chat-debug-playback";
|
|
8
10
|
const scenarios = createChatDebugScenarios();
|
|
9
11
|
const viewportWidth = ref("default");
|
|
@@ -32,7 +34,8 @@ const {
|
|
|
32
34
|
togglePlayback,
|
|
33
35
|
jumpToEnd,
|
|
34
36
|
stepEvent,
|
|
35
|
-
selectSpeed
|
|
37
|
+
selectSpeed,
|
|
38
|
+
applyManualEvent
|
|
36
39
|
} = useChatDebugPlayback(scenarios, {
|
|
37
40
|
onFrame: handlePlaybackFrame,
|
|
38
41
|
onReset: handlePlaybackReset
|
|
@@ -47,11 +50,47 @@ const { diagnostics, reset: resetDiagnostics, recordFrame } = useChatDebugDiagno
|
|
|
47
50
|
speed,
|
|
48
51
|
seekRevision
|
|
49
52
|
});
|
|
53
|
+
const windowListeners = useChatDebugWindowListenerProbe();
|
|
54
|
+
const reasoningCache = createReasoningCache();
|
|
55
|
+
const displayMessages = computed(() => selectedScenario.value.reasoningFormat === "raw" ? resolveReasoningDisplayMessages(playbackState.value.messages, reasoningCache) : playbackState.value.messages);
|
|
56
|
+
const reasoningCacheEntries = ref(0);
|
|
57
|
+
watch(displayMessages, () => {
|
|
58
|
+
reasoningCacheEntries.value = reasoningCache.bySignature.size;
|
|
59
|
+
}, { immediate: true });
|
|
60
|
+
const switchCount = ref(0);
|
|
61
|
+
const railResetKey = ref(0);
|
|
62
|
+
const sidebarMountedCount = ref(0);
|
|
63
|
+
let manualSwitchSequence = 0;
|
|
64
|
+
watch(() => playbackState.value.activeConversationId, (next, previous) => {
|
|
65
|
+
if (next && next !== previous)
|
|
66
|
+
switchCount.value += 1;
|
|
67
|
+
});
|
|
68
|
+
const renderedConversationId = computed(() => displayMessages.value[0]?.conversationId ?? null);
|
|
69
|
+
const renderedConversationStale = computed(() => {
|
|
70
|
+
const active = playbackState.value.activeConversationId;
|
|
71
|
+
return Boolean(active && renderedConversationId.value && renderedConversationId.value !== active);
|
|
72
|
+
});
|
|
73
|
+
function handleRailSwitch(fixture) {
|
|
74
|
+
if (playing.value)
|
|
75
|
+
togglePlayback();
|
|
76
|
+
manualSwitchSequence += 1;
|
|
77
|
+
applyManualEvent({
|
|
78
|
+
id: `manual-switch-${manualSwitchSequence}`,
|
|
79
|
+
atMs: elapsedMs.value,
|
|
80
|
+
label: `Manual: ${fixture.conversation.title}`,
|
|
81
|
+
tone: "user",
|
|
82
|
+
type: "conversation.switch",
|
|
83
|
+
conversationId: fixture.conversation.id,
|
|
84
|
+
messages: fixture.messages
|
|
85
|
+
});
|
|
86
|
+
}
|
|
50
87
|
function handlePlaybackFrame(frameDeltaMs) {
|
|
51
88
|
recordFrame(frameDeltaMs);
|
|
52
89
|
}
|
|
53
90
|
function handlePlaybackReset() {
|
|
54
91
|
resetDiagnostics();
|
|
92
|
+
switchCount.value = 0;
|
|
93
|
+
railResetKey.value += 1;
|
|
55
94
|
}
|
|
56
95
|
const messageCharacters = computed(() => playbackState.value.messages.reduce(
|
|
57
96
|
(total, message) => total + message.content.length,
|
|
@@ -189,24 +228,35 @@ function eventToneClass(tone) {
|
|
|
189
228
|
</div>
|
|
190
229
|
</div>
|
|
191
230
|
|
|
192
|
-
<div
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
:
|
|
231
|
+
<div class="stage-body" min-h-0 grow flex>
|
|
232
|
+
<ChatDebugConversationRail
|
|
233
|
+
v-if="selectedScenario.sidebar"
|
|
234
|
+
:sidebar="selectedScenario.sidebar"
|
|
235
|
+
:active-conversation-id="playbackState.activeConversationId"
|
|
236
|
+
:reset-key="railResetKey"
|
|
237
|
+
ml-24px mb-24px
|
|
238
|
+
@switch="handleRailSwitch"
|
|
239
|
+
@update:mounted-count="sidebarMountedCount = $event"
|
|
240
|
+
/>
|
|
241
|
+
<div ref="stageRef" class="stage-viewport" min-h-0 min-w-0 grow flex justify-center px-24px pb-24px>
|
|
242
|
+
<div
|
|
243
|
+
class="chat-under-test"
|
|
244
|
+
:class="[
|
|
196
245
|
`chat-under-test--${viewportWidth}`,
|
|
197
246
|
showBounds && 'chat-under-test--bounded'
|
|
198
247
|
]"
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
248
|
+
>
|
|
249
|
+
<TelaChatBody :key="conversationVariant" :variant="conversationVariant" h-full>
|
|
250
|
+
<ChatMessageList
|
|
251
|
+
:messages="displayMessages"
|
|
252
|
+
:loading="playbackState.loading"
|
|
253
|
+
:streaming-active-message-id="playbackState.streamingActiveMessageId"
|
|
254
|
+
:preparing-steps="playbackState.preparingSteps"
|
|
255
|
+
:variant="conversationVariant"
|
|
256
|
+
@retry="stepEvent(-1)"
|
|
257
|
+
/>
|
|
258
|
+
</TelaChatBody>
|
|
259
|
+
</div>
|
|
210
260
|
</div>
|
|
211
261
|
</div>
|
|
212
262
|
</main>
|
|
@@ -348,6 +398,63 @@ function eventToneClass(tone) {
|
|
|
348
398
|
</dl>
|
|
349
399
|
</section>
|
|
350
400
|
|
|
401
|
+
<section v-if="selectedScenario.sidebar" mt-28px>
|
|
402
|
+
<div body-10-regular text-tertiary uppercase tracking-0.08em mb-14px>
|
|
403
|
+
Switching
|
|
404
|
+
</div>
|
|
405
|
+
<div flex items-center gap-8px body-12-medium>
|
|
406
|
+
<span
|
|
407
|
+
w-6px h-6px rounded-full
|
|
408
|
+
:class="renderedConversationStale ? 'bg-amber-500' : 'bg-emerald-500'"
|
|
409
|
+
/>
|
|
410
|
+
{{ renderedConversationStale ? "stale content" : "thread in sync" }}
|
|
411
|
+
</div>
|
|
412
|
+
<dl class="detail-list" body-12-regular mt-12px>
|
|
413
|
+
<div>
|
|
414
|
+
<dt text-tertiary>
|
|
415
|
+
Switches
|
|
416
|
+
</dt><dd text-primary tabular-nums>
|
|
417
|
+
{{ switchCount }}
|
|
418
|
+
</dd>
|
|
419
|
+
</div>
|
|
420
|
+
<div>
|
|
421
|
+
<dt text-tertiary>
|
|
422
|
+
Sidebar items
|
|
423
|
+
</dt><dd text-primary tabular-nums>
|
|
424
|
+
{{ sidebarMountedCount }}
|
|
425
|
+
</dd>
|
|
426
|
+
</div>
|
|
427
|
+
<div>
|
|
428
|
+
<dt text-tertiary>
|
|
429
|
+
Reasoning cache
|
|
430
|
+
</dt><dd text-primary tabular-nums>
|
|
431
|
+
{{ reasoningCacheEntries }}
|
|
432
|
+
</dd>
|
|
433
|
+
</div>
|
|
434
|
+
<div>
|
|
435
|
+
<dt text-tertiary>
|
|
436
|
+
Window listeners Δ
|
|
437
|
+
</dt><dd text-primary tabular-nums>
|
|
438
|
+
{{ windowListeners.active }} (peak {{ windowListeners.peak }})
|
|
439
|
+
</dd>
|
|
440
|
+
</div>
|
|
441
|
+
<div>
|
|
442
|
+
<dt text-tertiary>
|
|
443
|
+
Keydown listeners
|
|
444
|
+
</dt><dd text-primary tabular-nums>
|
|
445
|
+
{{ windowListeners.byType.keydown ?? 0 }}
|
|
446
|
+
</dd>
|
|
447
|
+
</div>
|
|
448
|
+
<div>
|
|
449
|
+
<dt text-tertiary>
|
|
450
|
+
Rendered thread
|
|
451
|
+
</dt><dd text-primary truncate max-w-120px>
|
|
452
|
+
{{ renderedConversationId ?? "\u2014" }}
|
|
453
|
+
</dd>
|
|
454
|
+
</div>
|
|
455
|
+
</dl>
|
|
456
|
+
</section>
|
|
457
|
+
|
|
351
458
|
<section mt-28px>
|
|
352
459
|
<div flex items-center justify-between gap-12px mb-14px>
|
|
353
460
|
<div body-10-regular text-tertiary uppercase tracking-0.08em>
|
|
@@ -1,7 +1,47 @@
|
|
|
1
|
+
const DEBUG_DOCUMENTS = {
|
|
2
|
+
"debug_fixture/parecer-7": {
|
|
3
|
+
meta: {
|
|
4
|
+
title: "Parecer sint\xE9tico n\xBA 7 \u2014 microrrede de Ilha Verde",
|
|
5
|
+
sourceLabel: "Fixture de depura\xE7\xE3o",
|
|
6
|
+
identifier: "PARECER-7",
|
|
7
|
+
date: "2026-05-14",
|
|
8
|
+
dateLabel: "emiss\xE3o",
|
|
9
|
+
snippet: "A microrrede fict\xEDcia de Ilha Verde mant\xE9m autonomia de 36 horas com o banco de baterias descrito no anexo B."
|
|
10
|
+
},
|
|
11
|
+
content: [
|
|
12
|
+
"Parecer sint\xE9tico n\xBA 7 \u2014 microrrede de Ilha Verde.",
|
|
13
|
+
"Documento gerado para o workbench de depura\xE7\xE3o; nenhum dado real.",
|
|
14
|
+
"A microrrede fict\xEDcia de Ilha Verde mant\xE9m autonomia de 36 horas com o banco de baterias descrito no anexo B.",
|
|
15
|
+
"A proje\xE7\xE3o de expans\xE3o considera 120 unidades consumidoras e um fator de simultaneidade de 0,62."
|
|
16
|
+
].join("\n\n")
|
|
17
|
+
},
|
|
18
|
+
"debug_fixture/relatorio-12": {
|
|
19
|
+
meta: {
|
|
20
|
+
title: "Relat\xF3rio sint\xE9tico n\xBA 12 \u2014 inspe\xE7\xE3o dos inversores",
|
|
21
|
+
sourceLabel: "Fixture de depura\xE7\xE3o",
|
|
22
|
+
identifier: "RELATORIO-12",
|
|
23
|
+
date: "2026-06-02",
|
|
24
|
+
dateLabel: "inspe\xE7\xE3o",
|
|
25
|
+
snippet: "Os doze inversores fict\xEDcios operaram dentro da faixa nominal durante o ciclo de testes."
|
|
26
|
+
},
|
|
27
|
+
content: [
|
|
28
|
+
"Relat\xF3rio sint\xE9tico n\xBA 12 \u2014 inspe\xE7\xE3o dos inversores.",
|
|
29
|
+
"Documento gerado para o workbench de depura\xE7\xE3o; nenhum dado real.",
|
|
30
|
+
"Os doze inversores fict\xEDcios operaram dentro da faixa nominal durante o ciclo de testes.",
|
|
31
|
+
"O desvio m\xE1ximo de tens\xE3o registrado foi de 1,8% no alimentador norte."
|
|
32
|
+
].join("\n\n")
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function debugDocument(ref) {
|
|
36
|
+
return DEBUG_DOCUMENTS[`${ref.namespace}/${ref.id}`];
|
|
37
|
+
}
|
|
1
38
|
function humanizeNamespace(namespace) {
|
|
2
39
|
return namespace.split(/[_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
3
40
|
}
|
|
4
41
|
export function chatDebugCitationMeta(ref) {
|
|
42
|
+
const document = debugDocument(ref);
|
|
43
|
+
if (document)
|
|
44
|
+
return document.meta;
|
|
5
45
|
return {
|
|
6
46
|
title: `Documento ${ref.id}`,
|
|
7
47
|
sourceLabel: humanizeNamespace(ref.namespace),
|
|
@@ -11,6 +51,10 @@ export function chatDebugCitationMeta(ref) {
|
|
|
11
51
|
}
|
|
12
52
|
export function createChatDebugCitationsConfig() {
|
|
13
53
|
return {
|
|
14
|
-
resolve: async (ref) => chatDebugCitationMeta(ref)
|
|
54
|
+
resolve: async (ref) => chatDebugCitationMeta(ref),
|
|
55
|
+
getDocument: async (ref) => {
|
|
56
|
+
const document = debugDocument(ref);
|
|
57
|
+
return document ? { content: document.content } : null;
|
|
58
|
+
}
|
|
15
59
|
};
|
|
16
60
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message } from '../../types/schemas/chat/conversations.js';
|
|
1
|
+
import type { Conversation, Message } from '../../types/schemas/chat/conversations.js';
|
|
2
2
|
export type ChatDebugEventTone = 'neutral' | 'user' | 'assistant' | 'reasoning' | 'tool' | 'error';
|
|
3
3
|
type ChatDebugEventBase = {
|
|
4
4
|
id: string;
|
|
@@ -16,19 +16,47 @@ export type ChatDebugEvent = ChatDebugEventBase & ({
|
|
|
16
16
|
} | {
|
|
17
17
|
type: 'playback.patch';
|
|
18
18
|
patch: Partial<Omit<ChatDebugPlaybackState, 'messages'>>;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'conversation.switch';
|
|
21
|
+
conversationId: string;
|
|
22
|
+
messages: Message[];
|
|
19
23
|
});
|
|
24
|
+
/** One switchable conversation of a sidebar fixture: identity plus its full history. */
|
|
25
|
+
export type ChatDebugConversationFixture = {
|
|
26
|
+
conversation: Conversation;
|
|
27
|
+
messages: Message[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Sidebar fixture for switch scenarios. Only `defaultCount` items mount until
|
|
31
|
+
* the stress control is engaged, so a field-scale fixture never makes the
|
|
32
|
+
* workbench itself unusable.
|
|
33
|
+
*/
|
|
34
|
+
export type ChatDebugSidebarFixture = {
|
|
35
|
+
defaultCount: number;
|
|
36
|
+
stressCount: number;
|
|
37
|
+
conversations: ChatDebugConversationFixture[];
|
|
38
|
+
};
|
|
20
39
|
export type ChatDebugScenario = {
|
|
21
40
|
id: string;
|
|
22
41
|
name: string;
|
|
23
42
|
intent: string;
|
|
24
43
|
durationMs: number;
|
|
25
44
|
events: ChatDebugEvent[];
|
|
45
|
+
/** Present on switch scenarios: mounts the conversation rail around the stage. */
|
|
46
|
+
sidebar?: ChatDebugSidebarFixture;
|
|
47
|
+
/**
|
|
48
|
+
* `raw` marks fixtures whose reasoningData is the polling wire format, to be
|
|
49
|
+
* routed through `resolveReasoningDisplayMessages` (and its cache) exactly
|
|
50
|
+
* like production. Absent means the fixture ships pre-parsed steps.
|
|
51
|
+
*/
|
|
52
|
+
reasoningFormat?: 'raw';
|
|
26
53
|
};
|
|
27
54
|
export type ChatDebugPlaybackState = {
|
|
28
55
|
messages: Message[];
|
|
29
56
|
loading: boolean;
|
|
30
57
|
streamingActiveMessageId: string | null;
|
|
31
58
|
preparingSteps: string[] | null;
|
|
59
|
+
activeConversationId: string | null;
|
|
32
60
|
};
|
|
33
61
|
export declare const EMPTY_CHAT_DEBUG_PLAYBACK_STATE: ChatDebugPlaybackState;
|
|
34
62
|
export declare function applyChatDebugEvent(state: ChatDebugPlaybackState, event: ChatDebugEvent): ChatDebugPlaybackState;
|
|
@@ -3,11 +3,13 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __template = (cooked, raw) => __freeze(__defProp(cooked, "raw", { value: __freeze(raw || cooked.slice()) }));
|
|
4
4
|
var _a;
|
|
5
5
|
import { CHAT_PREPARING_STEPS } from "../../embed/loading-messages.js";
|
|
6
|
+
import { createConversationSwitchScenario } from "./chat-debug-switch-scenario.js";
|
|
6
7
|
export const EMPTY_CHAT_DEBUG_PLAYBACK_STATE = {
|
|
7
8
|
messages: [],
|
|
8
9
|
loading: false,
|
|
9
10
|
streamingActiveMessageId: null,
|
|
10
|
-
preparingSteps: null
|
|
11
|
+
preparingSteps: null,
|
|
12
|
+
activeConversationId: null
|
|
11
13
|
};
|
|
12
14
|
function cloneMessage(message2) {
|
|
13
15
|
return {
|
|
@@ -17,6 +19,16 @@ function cloneMessage(message2) {
|
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
21
|
export function applyChatDebugEvent(state, event2) {
|
|
22
|
+
if (event2.type === "conversation.switch") {
|
|
23
|
+
return {
|
|
24
|
+
...state,
|
|
25
|
+
messages: event2.messages.map(cloneMessage),
|
|
26
|
+
loading: false,
|
|
27
|
+
streamingActiveMessageId: null,
|
|
28
|
+
preparingSteps: null,
|
|
29
|
+
activeConversationId: event2.conversationId
|
|
30
|
+
};
|
|
31
|
+
}
|
|
20
32
|
if (event2.type === "playback.patch") {
|
|
21
33
|
const preparingSteps = "preparingSteps" in event2.patch ? event2.patch.preparingSteps ? [...event2.patch.preparingSteps] : null : state.preparingSteps;
|
|
22
34
|
return {
|
|
@@ -1265,6 +1277,7 @@ export function createChatDebugScenarios(baseTime = Date.now()) {
|
|
|
1265
1277
|
})
|
|
1266
1278
|
]
|
|
1267
1279
|
},
|
|
1280
|
+
createConversationSwitchScenario(baseTime),
|
|
1268
1281
|
{
|
|
1269
1282
|
id: "failure",
|
|
1270
1283
|
name: "Failure + recovery",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ChatDebugScenario, ChatDebugSidebarFixture } from './chat-debug-scenarios.js';
|
|
2
|
+
export declare const CONVERSATION_SWITCH_SCENARIO_ID = "conversation-switch";
|
|
3
|
+
export declare const SWITCH_HEAVY_CONVERSATION_ID = "switch-heavy";
|
|
4
|
+
export declare const SWITCH_LIGHT_CONVERSATION_ID = "switch-light";
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors the field report that motivated this fixture: ~346 conversation
|
|
7
|
+
* links rendered eagerly in the sidebar. Everything here is synthetic — no
|
|
8
|
+
* customer text, identifiers or staging data.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SWITCH_SIDEBAR_STRESS_COUNT = 346;
|
|
11
|
+
export declare const SWITCH_SIDEBAR_DEFAULT_COUNT = 24;
|
|
12
|
+
export declare function createConversationSwitchSidebar(baseTime: number): ChatDebugSidebarFixture;
|
|
13
|
+
/**
|
|
14
|
+
* The conversation-switch freeze reproducer. The timeline performs the same
|
|
15
|
+
* switch a user performs by clicking the sidebar, alternating between a
|
|
16
|
+
* markdown-heavy conversation, a light one and a filler, at a cadence close to
|
|
17
|
+
* the switching that froze the field deployment. Play = repeated switching;
|
|
18
|
+
* event stepping = one switch per press; the rail items themselves are the
|
|
19
|
+
* manual control.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createConversationSwitchScenario(baseTime?: number): ChatDebugScenario;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
export const CONVERSATION_SWITCH_SCENARIO_ID = "conversation-switch";
|
|
2
|
+
export const SWITCH_HEAVY_CONVERSATION_ID = "switch-heavy";
|
|
3
|
+
export const SWITCH_LIGHT_CONVERSATION_ID = "switch-light";
|
|
4
|
+
export const SWITCH_SIDEBAR_STRESS_COUNT = 346;
|
|
5
|
+
export const SWITCH_SIDEBAR_DEFAULT_COUNT = 24;
|
|
6
|
+
const SWITCH_CADENCE_MS = 1500;
|
|
7
|
+
function conversation(baseTime, input) {
|
|
8
|
+
return {
|
|
9
|
+
workspaceId: "chat-debug-workbench",
|
|
10
|
+
groupId: null,
|
|
11
|
+
telaAgentId: null,
|
|
12
|
+
userId: "debug-user",
|
|
13
|
+
threadSessionId: null,
|
|
14
|
+
model: "claude-sonnet-4-5",
|
|
15
|
+
appliedSettingsAt: null,
|
|
16
|
+
appliedSettingsSnapshot: null,
|
|
17
|
+
createdBy: "debugger@tela.com",
|
|
18
|
+
conversationScope: null,
|
|
19
|
+
createdAt: new Date(baseTime - 864e5),
|
|
20
|
+
updatedAt: new Date(baseTime - 36e5),
|
|
21
|
+
...input
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function message(baseTime, offsetMs, input) {
|
|
25
|
+
const createdAt = new Date(baseTime + offsetMs);
|
|
26
|
+
return {
|
|
27
|
+
content: "",
|
|
28
|
+
status: "completed",
|
|
29
|
+
failReason: null,
|
|
30
|
+
reasoningData: null,
|
|
31
|
+
files: null,
|
|
32
|
+
externalUuid: null,
|
|
33
|
+
ttft: null,
|
|
34
|
+
createdBy: input.role === "user" ? "debugger@tela.com" : null,
|
|
35
|
+
createdAt,
|
|
36
|
+
updatedAt: createdAt,
|
|
37
|
+
...input
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function rawThinkingReasoning(text) {
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
type: "assistant",
|
|
44
|
+
message: { content: [{ type: "thinking", text }] }
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
function rawHeavyFinalReasoning(answer) {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
type: "assistant",
|
|
52
|
+
message: {
|
|
53
|
+
content: [
|
|
54
|
+
{ type: "thinking", text: "Consolidando as medi\xE7\xF5es sint\xE9ticas das tr\xEAs visitas antes de montar o comparativo final." },
|
|
55
|
+
{
|
|
56
|
+
type: "tool_use",
|
|
57
|
+
id: "switch-heavy-survey",
|
|
58
|
+
name: "WebSearch",
|
|
59
|
+
input: { query: "microrrede sint\xE9tica Ilha Verde comparativo de autonomia" }
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "user",
|
|
66
|
+
message: {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "tool_result",
|
|
70
|
+
tool_use_id: "switch-heavy-survey",
|
|
71
|
+
content: "18 resultados sint\xE9ticos sobre a microrrede fict\xEDcia de Ilha Verde.",
|
|
72
|
+
is_error: false
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "assistant",
|
|
79
|
+
message: { content: [{ type: "text", text: answer }] }
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
function heavyAnswerMarkdown() {
|
|
84
|
+
const inverterRows = Array.from({ length: 36 }, (_, index) => {
|
|
85
|
+
const id = String(index + 1).padStart(2, "0");
|
|
86
|
+
const load = 42 + index * 7 % 31;
|
|
87
|
+
const drift = index * 13 % 20;
|
|
88
|
+
return `| INV-${id} | Alimentador ${index % 2 === 0 ? "norte" : "sul"} | ${load} kW | 0,${String(drift).padStart(2, "0")}% | ${index % 5 === 0 ? "revisar" : "nominal"} |`;
|
|
89
|
+
});
|
|
90
|
+
const configLines = Array.from({ length: 24 }, (_, index) => ` { feeder: '${index % 2 === 0 ? "norte" : "sul"}', inverter: 'INV-${String(index + 1).padStart(2, "0")}', limitKw: ${40 + index}, curtailment: ${index % 3 === 0} },`);
|
|
91
|
+
return [
|
|
92
|
+
"# Comparativo final da microrrede sint\xE9tica de Ilha Verde",
|
|
93
|
+
"",
|
|
94
|
+
"Este \xE9 um cen\xE1rio totalmente fict\xEDcio para o workbench de depura\xE7\xE3o. O comparativo abaixo consolida as tr\xEAs visitas simuladas e serve para estressar a renderiza\xE7\xE3o de Markdown longo em uma \xFAnica mensagem final.",
|
|
95
|
+
"",
|
|
96
|
+
"A autonomia projetada permanece em **36 horas**, conforme o parecer t\xE9cnico sint\xE9tico (cite://debug_fixture/parecer-7#q=autonomia), e a inspe\xE7\xE3o dos inversores n\xE3o registrou desvio relevante de tens\xE3o (cite://debug_fixture/relatorio-12#q=desvio).",
|
|
97
|
+
"",
|
|
98
|
+
"## 1. Panorama das visitas",
|
|
99
|
+
"",
|
|
100
|
+
"- **Visita 1** \u2014 levantamento das cargas e do fator de simultaneidade.",
|
|
101
|
+
"- **Visita 2** \u2014 medi\xE7\xE3o t\xE9rmica dos inversores em regime de ponta.",
|
|
102
|
+
" - Termografia dos barramentos norte e sul.",
|
|
103
|
+
" - Verifica\xE7\xE3o das prote\xE7\xF5es de sobrecorrente.",
|
|
104
|
+
"- **Visita 3** \u2014 ensaio de ilhamento com transfer\xEAncia autom\xE1tica.",
|
|
105
|
+
"",
|
|
106
|
+
"> Nota sint\xE9tica: nenhuma das medi\xE7\xF5es abaixo corresponde a um equipamento real; os n\xFAmeros s\xE3o gerados deterministicamente pelo fixture.",
|
|
107
|
+
"",
|
|
108
|
+
"## 2. Invent\xE1rio de inversores",
|
|
109
|
+
"",
|
|
110
|
+
"| Inversor | Alimentador | Carga de ponta | Desvio | Situa\xE7\xE3o |",
|
|
111
|
+
"| --- | --- | --- | --- | --- |",
|
|
112
|
+
...inverterRows,
|
|
113
|
+
"",
|
|
114
|
+
"## 3. Configura\xE7\xE3o proposta de despacho",
|
|
115
|
+
"",
|
|
116
|
+
"```ts",
|
|
117
|
+
"export const despachoIlhaVerde = {",
|
|
118
|
+
" autonomiaAlvoHoras: 36,",
|
|
119
|
+
" fatorSimultaneidade: 0.62,",
|
|
120
|
+
" alimentadores: [",
|
|
121
|
+
...configLines,
|
|
122
|
+
" ],",
|
|
123
|
+
"}",
|
|
124
|
+
"```",
|
|
125
|
+
"",
|
|
126
|
+
"## 4. Riscos e mitiga\xE7\xE3o",
|
|
127
|
+
"",
|
|
128
|
+
"1. **Degrada\xE7\xE3o do banco de baterias** \u2014 repetir o ensaio de capacidade a cada seis meses.",
|
|
129
|
+
"2. **Curtailment excessivo no alimentador sul** \u2014 rebalancear as cargas INV-13 a INV-21.",
|
|
130
|
+
"3. **Perda de telemetria** \u2014 o gateway fict\xEDcio deve armazenar 48 horas de buffer local.",
|
|
131
|
+
"",
|
|
132
|
+
"## 5. Conclus\xE3o",
|
|
133
|
+
"",
|
|
134
|
+
"O arranjo proposto mant\xE9m a microrrede sint\xE9tica dentro da autonomia alvo com margem de 11%. Os detalhes de medi\xE7\xE3o est\xE3o no [Parecer sint\xE9tico n\xBA 7](cite://debug_fixture/parecer-7) e no [Relat\xF3rio sint\xE9tico n\xBA 12](cite://debug_fixture/relatorio-12); ambos s\xE3o documentos gerados pelo pr\xF3prio fixture."
|
|
135
|
+
].join("\n");
|
|
136
|
+
}
|
|
137
|
+
function heavyConversation(baseTime) {
|
|
138
|
+
const id = SWITCH_HEAVY_CONVERSATION_ID;
|
|
139
|
+
const answer = heavyAnswerMarkdown();
|
|
140
|
+
return {
|
|
141
|
+
conversation: conversation(baseTime, { id, title: "An\xE1lise longa da microrrede (fixture)" }),
|
|
142
|
+
messages: [
|
|
143
|
+
message(baseTime, 0, {
|
|
144
|
+
id: `${id}-user-1`,
|
|
145
|
+
conversationId: id,
|
|
146
|
+
role: "user",
|
|
147
|
+
content: "Levante as cargas da microrrede sint\xE9tica de Ilha Verde e me diga se a autonomia alvo \xE9 vi\xE1vel."
|
|
148
|
+
}),
|
|
149
|
+
message(baseTime, 4e4, {
|
|
150
|
+
id: `${id}-assistant-1`,
|
|
151
|
+
conversationId: id,
|
|
152
|
+
role: "assistant",
|
|
153
|
+
content: "Com o fator de simultaneidade medido em 0,62, a autonomia alvo de 36 horas \xE9 vi\xE1vel desde que o alimentador sul n\xE3o ultrapasse 480 kW de ponta.",
|
|
154
|
+
reasoningData: rawThinkingReasoning("Estimando a ponta agregada a partir das 120 unidades consumidoras sint\xE9ticas."),
|
|
155
|
+
ttft: "1900"
|
|
156
|
+
}),
|
|
157
|
+
message(baseTime, 9e4, {
|
|
158
|
+
id: `${id}-user-2`,
|
|
159
|
+
conversationId: id,
|
|
160
|
+
role: "user",
|
|
161
|
+
content: "E a termografia dos inversores? Algum ponto de aten\xE7\xE3o?"
|
|
162
|
+
}),
|
|
163
|
+
message(baseTime, 13e4, {
|
|
164
|
+
id: `${id}-assistant-2`,
|
|
165
|
+
conversationId: id,
|
|
166
|
+
role: "assistant",
|
|
167
|
+
content: "A termografia sint\xE9tica apontou aquecimento moderado apenas nos inversores INV-06 e INV-16; nada fora da faixa nominal.",
|
|
168
|
+
reasoningData: rawThinkingReasoning("Cruzando as leituras t\xE9rmicas simuladas com os limites nominais de cada inversor."),
|
|
169
|
+
ttft: "1600"
|
|
170
|
+
}),
|
|
171
|
+
message(baseTime, 18e4, {
|
|
172
|
+
id: `${id}-user-3`,
|
|
173
|
+
conversationId: id,
|
|
174
|
+
role: "user",
|
|
175
|
+
content: "Feche o comparativo completo das tr\xEAs visitas com a configura\xE7\xE3o de despacho recomendada."
|
|
176
|
+
}),
|
|
177
|
+
message(baseTime, 26e4, {
|
|
178
|
+
id: `${id}-assistant-3`,
|
|
179
|
+
conversationId: id,
|
|
180
|
+
role: "assistant",
|
|
181
|
+
content: answer,
|
|
182
|
+
reasoningData: rawHeavyFinalReasoning(answer),
|
|
183
|
+
ttft: "2400"
|
|
184
|
+
})
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function lightConversation(baseTime) {
|
|
189
|
+
const id = SWITCH_LIGHT_CONVERSATION_ID;
|
|
190
|
+
return {
|
|
191
|
+
conversation: conversation(baseTime, { id, title: "Resposta curta (fixture)" }),
|
|
192
|
+
messages: [
|
|
193
|
+
message(baseTime, 0, {
|
|
194
|
+
id: `${id}-user-1`,
|
|
195
|
+
conversationId: id,
|
|
196
|
+
role: "user",
|
|
197
|
+
content: "Qual \xE9 a autonomia alvo da microrrede sint\xE9tica?"
|
|
198
|
+
}),
|
|
199
|
+
message(baseTime, 2e4, {
|
|
200
|
+
id: `${id}-assistant-1`,
|
|
201
|
+
conversationId: id,
|
|
202
|
+
role: "assistant",
|
|
203
|
+
content: "A autonomia alvo do fixture \xE9 de **36 horas**.",
|
|
204
|
+
reasoningData: rawThinkingReasoning("Respondendo direto do par\xE2metro do fixture."),
|
|
205
|
+
ttft: "800"
|
|
206
|
+
})
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function fillerConversation(baseTime, index) {
|
|
211
|
+
const id = `switch-filler-${String(index).padStart(3, "0")}`;
|
|
212
|
+
return {
|
|
213
|
+
conversation: conversation(baseTime, { id, title: `Conversa sint\xE9tica ${String(index).padStart(3, "0")}` }),
|
|
214
|
+
messages: [
|
|
215
|
+
message(baseTime, 0, {
|
|
216
|
+
id: `${id}-user-1`,
|
|
217
|
+
conversationId: id,
|
|
218
|
+
role: "user",
|
|
219
|
+
content: `Pergunta sint\xE9tica n\xFAmero ${index}.`
|
|
220
|
+
}),
|
|
221
|
+
message(baseTime, 15e3, {
|
|
222
|
+
id: `${id}-assistant-1`,
|
|
223
|
+
conversationId: id,
|
|
224
|
+
role: "assistant",
|
|
225
|
+
content: `Resposta sint\xE9tica n\xFAmero ${index}, sem conte\xFAdo adicional.`,
|
|
226
|
+
ttft: "700"
|
|
227
|
+
})
|
|
228
|
+
]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
export function createConversationSwitchSidebar(baseTime) {
|
|
232
|
+
const conversations = [
|
|
233
|
+
heavyConversation(baseTime),
|
|
234
|
+
lightConversation(baseTime)
|
|
235
|
+
];
|
|
236
|
+
for (let index = 3; conversations.length < SWITCH_SIDEBAR_STRESS_COUNT; index++)
|
|
237
|
+
conversations.push(fillerConversation(baseTime, index));
|
|
238
|
+
return {
|
|
239
|
+
defaultCount: SWITCH_SIDEBAR_DEFAULT_COUNT,
|
|
240
|
+
stressCount: SWITCH_SIDEBAR_STRESS_COUNT,
|
|
241
|
+
conversations
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function switchEvent(index, atMs, fixture) {
|
|
245
|
+
return {
|
|
246
|
+
id: `switch-${index}-${fixture.conversation.id}`,
|
|
247
|
+
atMs,
|
|
248
|
+
label: `Abre "${fixture.conversation.title}"`,
|
|
249
|
+
tone: "user",
|
|
250
|
+
type: "conversation.switch",
|
|
251
|
+
conversationId: fixture.conversation.id,
|
|
252
|
+
messages: fixture.messages
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
export function createConversationSwitchScenario(baseTime = Date.now()) {
|
|
256
|
+
const sidebar = createConversationSwitchSidebar(baseTime);
|
|
257
|
+
const heavy = sidebar.conversations[0];
|
|
258
|
+
const light = sidebar.conversations[1];
|
|
259
|
+
const filler = sidebar.conversations[2];
|
|
260
|
+
const sequence = [heavy, light, heavy, light, filler, heavy, light, heavy, filler, light, heavy];
|
|
261
|
+
const events = sequence.map((fixture, index) => switchEvent(index + 1, index * SWITCH_CADENCE_MS, fixture));
|
|
262
|
+
return {
|
|
263
|
+
id: CONVERSATION_SWITCH_SCENARIO_ID,
|
|
264
|
+
name: "Conversation switching",
|
|
265
|
+
intent: "Repeated switches between a markdown-heavy and a light conversation, with a field-scale sidebar behind a stress control.",
|
|
266
|
+
durationMs: events.at(-1).atMs,
|
|
267
|
+
events,
|
|
268
|
+
sidebar,
|
|
269
|
+
reasoningFormat: "raw"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
@@ -11,6 +11,37 @@ export declare function shouldIgnoreChatDebugLayoutShift(entry: {
|
|
|
11
11
|
hadRecentInput: boolean;
|
|
12
12
|
startTime: number;
|
|
13
13
|
}, ignoreUntil: number): boolean;
|
|
14
|
+
type ListenerTarget = {
|
|
15
|
+
addEventListener: (type: string, listener: unknown, options?: unknown) => void;
|
|
16
|
+
removeEventListener: (type: string, listener: unknown, options?: unknown) => void;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Counts the target's listener registrations from `install()` onwards by
|
|
20
|
+
* wrapping add/removeEventListener. Mirrors the platform's identity rules —
|
|
21
|
+
* duplicate (type, listener, capture) triples are ignored, and a remove only
|
|
22
|
+
* counts against a registration this tracker saw. The net count is the signal
|
|
23
|
+
* the conversation-switch reproducer watches: per-item global listeners used
|
|
24
|
+
* to grow with the sidebar and with every switch.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createChatDebugListenerTracker(target: ListenerTarget): {
|
|
27
|
+
counters: {
|
|
28
|
+
active: number;
|
|
29
|
+
peak: number;
|
|
30
|
+
byType: Record<string, number>;
|
|
31
|
+
};
|
|
32
|
+
install: () => void;
|
|
33
|
+
uninstall: () => void;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Window-level listener counter for the workbench's lifetime. Dev-only
|
|
37
|
+
* instrumentation: the wrap is installed on mount and fully restored on
|
|
38
|
+
* unmount, and it never alters listener behavior — only counts it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useChatDebugWindowListenerProbe(): {
|
|
41
|
+
active: number;
|
|
42
|
+
peak: number;
|
|
43
|
+
byType: Record<string, number>;
|
|
44
|
+
};
|
|
14
45
|
/**
|
|
15
46
|
* Passive probes around the mounted chat component: event-to-paint latency,
|
|
16
47
|
* dropped frames (reported by playback through `recordFrame`), DOM size,
|
|
@@ -46,3 +77,4 @@ export declare function useChatDebugDiagnostics(options: {
|
|
|
46
77
|
reset: () => void;
|
|
47
78
|
recordFrame: (frameDeltaMs: number) => void;
|
|
48
79
|
};
|
|
80
|
+
export {};
|
|
@@ -2,6 +2,64 @@ import { nextTick, onBeforeUnmount, onMounted, reactive, watch } from "vue";
|
|
|
2
2
|
export function shouldIgnoreChatDebugLayoutShift(entry, ignoreUntil) {
|
|
3
3
|
return entry.hadRecentInput || entry.startTime <= ignoreUntil;
|
|
4
4
|
}
|
|
5
|
+
function resolveListenerCapture(options) {
|
|
6
|
+
if (typeof options === "boolean")
|
|
7
|
+
return options;
|
|
8
|
+
return Boolean(options?.capture);
|
|
9
|
+
}
|
|
10
|
+
export function createChatDebugListenerTracker(target) {
|
|
11
|
+
const counters = reactive({ active: 0, peak: 0, byType: {} });
|
|
12
|
+
const tracked = [];
|
|
13
|
+
let originalAdd = null;
|
|
14
|
+
let originalRemove = null;
|
|
15
|
+
function findIndex(type, listener, capture) {
|
|
16
|
+
return tracked.findIndex(
|
|
17
|
+
(entry) => entry.type === type && entry.listener === listener && entry.capture === capture
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function install() {
|
|
21
|
+
if (originalAdd)
|
|
22
|
+
return;
|
|
23
|
+
originalAdd = target.addEventListener;
|
|
24
|
+
originalRemove = target.removeEventListener;
|
|
25
|
+
target.addEventListener = function(type, listener, options) {
|
|
26
|
+
const capture = resolveListenerCapture(options);
|
|
27
|
+
if (listener && findIndex(type, listener, capture) === -1) {
|
|
28
|
+
tracked.push({ type, listener, capture });
|
|
29
|
+
counters.active = tracked.length;
|
|
30
|
+
counters.peak = Math.max(counters.peak, counters.active);
|
|
31
|
+
counters.byType[type] = (counters.byType[type] ?? 0) + 1;
|
|
32
|
+
}
|
|
33
|
+
return originalAdd.call(this, type, listener, options);
|
|
34
|
+
};
|
|
35
|
+
target.removeEventListener = function(type, listener, options) {
|
|
36
|
+
const index = findIndex(type, listener, resolveListenerCapture(options));
|
|
37
|
+
if (index !== -1) {
|
|
38
|
+
const [removed] = tracked.splice(index, 1);
|
|
39
|
+
counters.active = tracked.length;
|
|
40
|
+
counters.byType[removed.type] = Math.max(0, (counters.byType[removed.type] ?? 1) - 1);
|
|
41
|
+
}
|
|
42
|
+
return originalRemove.call(this, type, listener, options);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function uninstall() {
|
|
46
|
+
if (!originalAdd || !originalRemove)
|
|
47
|
+
return;
|
|
48
|
+
target.addEventListener = originalAdd;
|
|
49
|
+
target.removeEventListener = originalRemove;
|
|
50
|
+
originalAdd = null;
|
|
51
|
+
originalRemove = null;
|
|
52
|
+
}
|
|
53
|
+
return { counters, install, uninstall };
|
|
54
|
+
}
|
|
55
|
+
export function useChatDebugWindowListenerProbe() {
|
|
56
|
+
const tracker = typeof window === "undefined" ? createChatDebugListenerTracker({ addEventListener: () => {
|
|
57
|
+
}, removeEventListener: () => {
|
|
58
|
+
} }) : createChatDebugListenerTracker(window);
|
|
59
|
+
onMounted(tracker.install);
|
|
60
|
+
onBeforeUnmount(tracker.uninstall);
|
|
61
|
+
return tracker.counters;
|
|
62
|
+
}
|
|
5
63
|
export function useChatDebugDiagnostics(options) {
|
|
6
64
|
const diagnostics = reactive({
|
|
7
65
|
paintLatencyMs: 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChatDebugScenario } from './chat-debug-scenarios.js';
|
|
1
|
+
import type { ChatDebugEvent, ChatDebugScenario } from './chat-debug-scenarios.js';
|
|
2
2
|
export type ChatDebugPlaybackSpeed = '1x' | '4x' | 'gap';
|
|
3
3
|
export declare function formatChatDebugTime(value: number): string;
|
|
4
4
|
/**
|
|
@@ -19,7 +19,7 @@ export declare function useChatDebugPlayback(scenarios: ChatDebugScenario[], opt
|
|
|
19
19
|
seekRevision: import("vue").Ref<number, number>;
|
|
20
20
|
playbackState: import("vue").ComputedRef<import("./chat-debug-scenarios.js").ChatDebugPlaybackState>;
|
|
21
21
|
appliedEventCount: import("vue").ComputedRef<number>;
|
|
22
|
-
currentEvent: import("vue").ComputedRef<
|
|
22
|
+
currentEvent: import("vue").ComputedRef<ChatDebugEvent | null>;
|
|
23
23
|
progress: import("vue").ComputedRef<number>;
|
|
24
24
|
seek: (nextElapsedMs: number) => void;
|
|
25
25
|
restart: () => void;
|
|
@@ -28,4 +28,5 @@ export declare function useChatDebugPlayback(scenarios: ChatDebugScenario[], opt
|
|
|
28
28
|
jumpToEnd: () => void;
|
|
29
29
|
stepEvent: (direction: -1 | 1) => void;
|
|
30
30
|
selectSpeed: (nextSpeed: ChatDebugPlaybackSpeed) => void;
|
|
31
|
+
applyManualEvent: (event: ChatDebugEvent) => void;
|
|
31
32
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
2
|
-
import { countAppliedChatDebugEvents, resolveChatDebugPlayback } from "./chat-debug-scenarios.js";
|
|
1
|
+
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
|
|
2
|
+
import { applyChatDebugEvent, countAppliedChatDebugEvents, resolveChatDebugPlayback } from "./chat-debug-scenarios.js";
|
|
3
3
|
export function formatChatDebugTime(value) {
|
|
4
4
|
const totalSeconds = Math.max(0, value) / 1e3;
|
|
5
5
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -15,20 +15,32 @@ export function useChatDebugPlayback(scenarios, options = {}) {
|
|
|
15
15
|
const selectedScenario = computed(
|
|
16
16
|
() => scenarios.find((scenario) => scenario.id === selectedScenarioId.value) ?? scenarios[0]
|
|
17
17
|
);
|
|
18
|
-
const
|
|
18
|
+
const manualEvents = shallowRef([]);
|
|
19
|
+
const playbackState = computed(() => manualEvents.value.reduce(
|
|
20
|
+
applyChatDebugEvent,
|
|
21
|
+
resolveChatDebugPlayback(selectedScenario.value, elapsedMs.value)
|
|
22
|
+
));
|
|
19
23
|
const appliedEventCount = computed(() => countAppliedChatDebugEvents(selectedScenario.value, elapsedMs.value));
|
|
20
24
|
const currentEvent = computed(() => selectedScenario.value.events[appliedEventCount.value - 1] ?? null);
|
|
21
25
|
const progress = computed(() => selectedScenario.value.durationMs === 0 ? 0 : elapsedMs.value / selectedScenario.value.durationMs);
|
|
22
26
|
const uniqueEventTimes = computed(() => Array.from(new Set(selectedScenario.value.events.map((event) => event.atMs))));
|
|
23
27
|
let animationFrame = 0;
|
|
24
28
|
let previousFrameAt = 0;
|
|
29
|
+
function applyManualEvent(event) {
|
|
30
|
+
manualEvents.value = [...manualEvents.value, event];
|
|
31
|
+
}
|
|
25
32
|
function setElapsed(nextElapsedMs) {
|
|
26
|
-
|
|
33
|
+
const clamped = Math.min(Math.max(nextElapsedMs, 0), selectedScenario.value.durationMs);
|
|
34
|
+
if (manualEvents.value.length > 0 && countAppliedChatDebugEvents(selectedScenario.value, clamped) !== appliedEventCount.value) {
|
|
35
|
+
manualEvents.value = [];
|
|
36
|
+
}
|
|
37
|
+
elapsedMs.value = clamped;
|
|
27
38
|
if (elapsedMs.value >= selectedScenario.value.durationMs)
|
|
28
39
|
playing.value = false;
|
|
29
40
|
}
|
|
30
41
|
function seek(nextElapsedMs) {
|
|
31
42
|
seekRevision.value += 1;
|
|
43
|
+
manualEvents.value = [];
|
|
32
44
|
setElapsed(nextElapsedMs);
|
|
33
45
|
}
|
|
34
46
|
function restart() {
|
|
@@ -43,6 +55,7 @@ export function useChatDebugPlayback(scenarios, options = {}) {
|
|
|
43
55
|
function togglePlayback() {
|
|
44
56
|
if (elapsedMs.value >= selectedScenario.value.durationMs) {
|
|
45
57
|
elapsedMs.value = 0;
|
|
58
|
+
manualEvents.value = [];
|
|
46
59
|
options.onReset?.();
|
|
47
60
|
}
|
|
48
61
|
playing.value = !playing.value;
|
|
@@ -130,6 +143,7 @@ export function useChatDebugPlayback(scenarios, options = {}) {
|
|
|
130
143
|
togglePlayback,
|
|
131
144
|
jumpToEnd,
|
|
132
145
|
stepEvent,
|
|
133
|
-
selectSpeed
|
|
146
|
+
selectSpeed,
|
|
147
|
+
applyManualEvent
|
|
134
148
|
};
|
|
135
149
|
}
|
|
@@ -46,6 +46,16 @@ function groupReasoningSteps(parsedSteps) {
|
|
|
46
46
|
function getReasoningCacheKey(messageId, reasoningData) {
|
|
47
47
|
return `${messageId}:${JSON.stringify(reasoningData)}`;
|
|
48
48
|
}
|
|
49
|
+
function pruneReasoningCache(cache, messages) {
|
|
50
|
+
if (cache.bySignature.size === 0)
|
|
51
|
+
return;
|
|
52
|
+
const liveMessageIds = new Set(messages.map((message) => message.id));
|
|
53
|
+
for (const key of cache.bySignature.keys()) {
|
|
54
|
+
const messageId = key.slice(0, key.indexOf(":"));
|
|
55
|
+
if (!liveMessageIds.has(messageId))
|
|
56
|
+
cache.bySignature.delete(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
function getParsedReasoning(cache, messageId, reasoningData) {
|
|
50
60
|
const referenceHit = cache.byReference.get(reasoningData);
|
|
51
61
|
if (referenceHit?.messageId === messageId)
|
|
@@ -176,6 +186,7 @@ function parseReasoningFromPolling(reasoning) {
|
|
|
176
186
|
return { steps: groupReasoningSteps(allSteps), thinkingTexts, currentAnswerParts, currentAnswerStepTexts };
|
|
177
187
|
}
|
|
178
188
|
export function resolveReasoningDisplayMessages(messages, cache = createReasoningCache()) {
|
|
189
|
+
pruneReasoningCache(cache, messages);
|
|
179
190
|
const renderedSteps = /* @__PURE__ */ new Set();
|
|
180
191
|
return messages.map((message) => {
|
|
181
192
|
if (message.role !== "assistant" || !message.reasoningData || !Array.isArray(message.reasoningData))
|