@meistrari/chat-nuxt 3.13.1 → 3.14.0-rc.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "3.13.1-rc.0",
4
+ "version": "3.14.0-rc.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,428 @@
1
+ <script setup>
2
+ import ChatMessageList from "./chat-message-list.vue";
3
+ import { provideEmbedConfig } from "../../embed/composables/embed-config";
4
+ import { createChatDebugScenarios } from "../debug/chat-debug-scenarios";
5
+ import { useChatDebugDiagnostics } from "../debug/use-chat-debug-diagnostics";
6
+ import { formatChatDebugTime as formatTime, useChatDebugPlayback } from "../debug/use-chat-debug-playback";
7
+ const scenarios = createChatDebugScenarios();
8
+ const viewportWidth = ref("default");
9
+ const showBounds = ref(false);
10
+ const rootRef = ref(null);
11
+ const stageRef = ref(null);
12
+ provideEmbedConfig({
13
+ workspaceId: "chat-debug-workbench",
14
+ hideSidebar: true
15
+ }, { syncGlobal: false });
16
+ const {
17
+ selectedScenario,
18
+ elapsedMs,
19
+ playing,
20
+ speed,
21
+ seekRevision,
22
+ playbackState,
23
+ appliedEventCount,
24
+ currentEvent,
25
+ progress,
26
+ seek,
27
+ restart,
28
+ selectScenario,
29
+ togglePlayback,
30
+ jumpToEnd,
31
+ stepEvent,
32
+ selectSpeed
33
+ } = useChatDebugPlayback(scenarios, {
34
+ onFrame: handlePlaybackFrame,
35
+ onReset: handlePlaybackReset
36
+ });
37
+ const { diagnostics, reset: resetDiagnostics, recordFrame } = useChatDebugDiagnostics({
38
+ stageRef,
39
+ scenario: selectedScenario,
40
+ playbackState,
41
+ appliedEventCount,
42
+ elapsedMs,
43
+ playing,
44
+ speed,
45
+ seekRevision
46
+ });
47
+ function handlePlaybackFrame(frameDeltaMs) {
48
+ recordFrame(frameDeltaMs);
49
+ }
50
+ function handlePlaybackReset() {
51
+ resetDiagnostics();
52
+ }
53
+ const messageCharacters = computed(() => playbackState.value.messages.reduce(
54
+ (total, message) => total + message.content.length,
55
+ 0
56
+ ));
57
+ const estimatedTokens = computed(() => Math.ceil(messageCharacters.value / 4));
58
+ const pendingMessages = computed(() => playbackState.value.messages.filter((message) => message.status === "pending").length);
59
+ const speedOptions = [
60
+ { id: "1x", label: "1\xD7", title: "Real time" },
61
+ { id: "4x", label: "4\xD7", title: "Four times faster" },
62
+ { id: "gap", label: "Gaps", title: "Keep event cadence and compress idle gaps" }
63
+ ];
64
+ const viewportOptions = [
65
+ { id: "narrow", label: "Narrow" },
66
+ { id: "default", label: "Default" },
67
+ { id: "wide", label: "Wide" }
68
+ ];
69
+ function eventToneClass(tone) {
70
+ return {
71
+ neutral: "bg-gray-400",
72
+ user: "bg-blue-500",
73
+ assistant: "bg-violet-500",
74
+ reasoning: "bg-amber-500",
75
+ tool: "bg-emerald-500",
76
+ error: "bg-red-500"
77
+ }[tone];
78
+ }
79
+ </script>
80
+
81
+ <template>
82
+ <section
83
+ ref="rootRef"
84
+ class="chat-debug-workbench"
85
+ w-full
86
+ h-full
87
+ bg
88
+ text-primary
89
+ >
90
+ <aside class="scenario-rail" px-20px py-22px overflow-y-auto>
91
+ <div body-caption-9-medium text-tertiary mb-10px>
92
+ Scenarios
93
+ </div>
94
+ <div flex="~ col" gap-4px>
95
+ <button
96
+ v-for="scenario in scenarios"
97
+ :key="scenario.id"
98
+ type="button"
99
+ :title="scenario.intent"
100
+ py-5px
101
+ text-left
102
+ group
103
+ @click="selectScenario(scenario.id)"
104
+ >
105
+ <div flex items-center gap-7px>
106
+ <span
107
+ w-4px h-4px rounded-full shrink-0
108
+ :class="scenario.id === selectedScenario.id ? 'bg-gray-900' : 'bg-gray-300 group-hover:bg-gray-500'"
109
+ transition-colors
110
+ />
111
+ <span
112
+ body-12-medium
113
+ truncate
114
+ :class="scenario.id === selectedScenario.id ? 'text-primary' : 'text-secondary group-hover:text-primary'"
115
+ transition-colors
116
+ >
117
+ {{ scenario.name }}
118
+ </span>
119
+ <span ml-auto shrink-0 body-caption-9-medium text-tertiary tabular-nums>
120
+ {{ formatTime(scenario.durationMs) }}
121
+ </span>
122
+ </div>
123
+ </button>
124
+ </div>
125
+
126
+ <div mt-24px>
127
+ <div body-caption-9-medium text-tertiary mb-10px>
128
+ Shortcuts
129
+ </div>
130
+ <dl class="shortcut-list" body-11-regular text-secondary>
131
+ <dt>Play / pause</dt><dd><TelaKbd>Space</TelaKbd></dd>
132
+ <dt>Previous event</dt><dd><TelaKbd>,</TelaKbd></dd>
133
+ <dt>Next event</dt><dd><TelaKbd>.</TelaKbd></dd>
134
+ <dt>Restart</dt><dd><TelaKbd>R</TelaKbd></dd>
135
+ </dl>
136
+ </div>
137
+ </aside>
138
+
139
+ <main class="stage-column" min-w-0 min-h-0 flex="~ col">
140
+ <div h-48px px-24px flex items-center justify-between shrink-0>
141
+ <div flex items-center gap-8px body-11-regular text-tertiary>
142
+ <span>{{ selectedScenario.name }}</span>
143
+ <TelaDotSeparator />
144
+ <span>{{ playbackState.messages.length }} messages</span>
145
+ </div>
146
+ <div flex items-center gap-12px>
147
+ <div flex items-center gap-10px>
148
+ <button
149
+ v-for="option in viewportOptions"
150
+ :key="option.id"
151
+ type="button"
152
+ body-11-regular
153
+ :class="viewportWidth === option.id ? 'text-primary' : 'text-tertiary hover:text-secondary'"
154
+ transition-colors
155
+ @click="viewportWidth = option.id"
156
+ >
157
+ {{ option.label }}
158
+ </button>
159
+ </div>
160
+ <button
161
+ type="button"
162
+ body-11-regular
163
+ :class="showBounds ? 'text-primary' : 'text-tertiary hover:text-secondary'"
164
+ transition-colors
165
+ @click="showBounds = !showBounds"
166
+ >
167
+ Bounds
168
+ </button>
169
+ </div>
170
+ </div>
171
+
172
+ <div ref="stageRef" class="stage-viewport" min-h-0 grow flex justify-center px-24px pb-24px>
173
+ <div
174
+ class="chat-under-test"
175
+ :class="[
176
+ `chat-under-test--${viewportWidth}`,
177
+ showBounds && 'chat-under-test--bounded'
178
+ ]"
179
+ >
180
+ <TelaChatBody variant="widget" h-full>
181
+ <ChatMessageList
182
+ :messages="playbackState.messages"
183
+ :loading="playbackState.loading"
184
+ :streaming-active-message-id="playbackState.streamingActiveMessageId"
185
+ :preparing-steps="playbackState.preparingSteps"
186
+ variant="widget"
187
+ @retry="stepEvent(-1)"
188
+ />
189
+ </TelaChatBody>
190
+ </div>
191
+ </div>
192
+ </main>
193
+
194
+ <aside class="diagnostics-rail" px-24px py-22px overflow-y-auto>
195
+ <div flex items-start justify-between gap-12px>
196
+ <div>
197
+ <h2 heading-h5-semibold text-wrap-balance>
198
+ Diagnostics
199
+ </h2>
200
+ <p body-11-regular text-tertiary mt-4px>
201
+ Passive probes around the mounted component.
202
+ </p>
203
+ </div>
204
+ <span
205
+ body-10-regular
206
+ :class="speed === '1x' ? 'text-emerald-700' : 'text-amber-700'"
207
+ >
208
+ {{ speed === "1x" ? "representative" : "accelerated" }}
209
+ </span>
210
+ </div>
211
+
212
+ <section mt-28px>
213
+ <div body-10-regular text-tertiary uppercase tracking-0.08em mb-16px>
214
+ Vitals
215
+ </div>
216
+ <dl class="metric-grid">
217
+ <div>
218
+ <dt body-10-regular text-tertiary>
219
+ Event → paint
220
+ </dt>
221
+ <dd heading-h3-semibold tabular-nums>
222
+ {{ diagnostics.paintLatencyMs.toFixed(1) }}<small>ms</small>
223
+ </dd>
224
+ </div>
225
+ <div>
226
+ <dt body-10-regular text-tertiary>
227
+ Dropped frames
228
+ </dt>
229
+ <dd heading-h3-semibold tabular-nums>
230
+ {{ diagnostics.droppedFrames }}
231
+ </dd>
232
+ </div>
233
+ <div>
234
+ <dt body-10-regular text-tertiary>
235
+ DOM nodes
236
+ </dt>
237
+ <dd heading-h3-semibold tabular-nums>
238
+ {{ diagnostics.domNodes }}
239
+ </dd>
240
+ </div>
241
+ <div>
242
+ <dt body-10-regular text-tertiary>
243
+ Layout shift
244
+ </dt>
245
+ <dd heading-h3-semibold tabular-nums>
246
+ {{ diagnostics.cumulativeLayoutShift.toFixed(3) }}
247
+ </dd>
248
+ </div>
249
+ </dl>
250
+ </section>
251
+
252
+ <section mt-28px>
253
+ <div body-10-regular text-tertiary uppercase tracking-0.08em mb-14px>
254
+ Conversation
255
+ </div>
256
+ <dl class="detail-list" body-12-regular>
257
+ <div>
258
+ <dt text-tertiary>
259
+ Messages
260
+ </dt><dd text-primary tabular-nums>
261
+ {{ playbackState.messages.length }}
262
+ </dd>
263
+ </div>
264
+ <div>
265
+ <dt text-tertiary>
266
+ Pending
267
+ </dt><dd text-primary tabular-nums>
268
+ {{ pendingMessages }}
269
+ </dd>
270
+ </div>
271
+ <div>
272
+ <dt text-tertiary>
273
+ Characters
274
+ </dt><dd text-primary tabular-nums>
275
+ {{ messageCharacters }}
276
+ </dd>
277
+ </div>
278
+ <div>
279
+ <dt text-tertiary>
280
+ Est. tokens
281
+ </dt><dd text-primary tabular-nums>
282
+ ~{{ estimatedTokens }}
283
+ </dd>
284
+ </div>
285
+ <div>
286
+ <dt text-tertiary>
287
+ Clock drift
288
+ </dt><dd text-primary tabular-nums>
289
+ {{ (diagnostics.clockDriftMs / 1e3).toFixed(1) }} s
290
+ </dd>
291
+ </div>
292
+ </dl>
293
+ </section>
294
+
295
+ <section mt-28px>
296
+ <div body-10-regular text-tertiary uppercase tracking-0.08em mb-14px>
297
+ Scroll & anchoring
298
+ </div>
299
+ <div flex items-center gap-8px body-12-medium>
300
+ <span
301
+ w-6px h-6px rounded-full
302
+ :class="diagnostics.scrollPosition === 'pinned' ? 'bg-emerald-500' : diagnostics.scrollPosition === 'detached' ? 'bg-amber-500' : 'bg-gray-400'"
303
+ />
304
+ {{ diagnostics.scrollPosition }}
305
+ </div>
306
+ <dl class="detail-list" body-12-regular mt-12px>
307
+ <div>
308
+ <dt text-tertiary>
309
+ Position
310
+ </dt><dd text-primary tabular-nums>
311
+ {{ diagnostics.scrollTop }} px
312
+ </dd>
313
+ </div>
314
+ <div>
315
+ <dt text-tertiary>
316
+ Content height
317
+ </dt><dd text-primary tabular-nums>
318
+ {{ diagnostics.scrollHeight }} px
319
+ </dd>
320
+ </div>
321
+ <div>
322
+ <dt text-tertiary>
323
+ Largest move
324
+ </dt><dd text-primary tabular-nums>
325
+ {{ diagnostics.maxScrollJump.toFixed(0) }} px
326
+ </dd>
327
+ </div>
328
+ </dl>
329
+ </section>
330
+
331
+ <section mt-28px>
332
+ <div flex items-center justify-between gap-12px mb-14px>
333
+ <div body-10-regular text-tertiary uppercase tracking-0.08em>
334
+ Findings
335
+ </div>
336
+ <span body-10-regular text-tertiary>{{ diagnostics.findings.length }}</span>
337
+ </div>
338
+ <p v-if="diagnostics.findings.length === 0" body-11-regular text-tertiary leading-16px>
339
+ No thresholds crossed in this run.
340
+ </p>
341
+ <button
342
+ v-for="finding in diagnostics.findings"
343
+ v-else
344
+ :key="finding.kind"
345
+ type="button"
346
+ w-full
347
+ py-7px
348
+ flex items-start gap-8px
349
+ text-left
350
+ @click="seek(finding.atMs)"
351
+ >
352
+ <span i-ph-warning-circle text-amber-600 mt-1px />
353
+ <span grow body-11-regular text-secondary leading-16px>{{ finding.label }}</span>
354
+ <span body-10-regular text-tertiary tabular-nums>{{ formatTime(finding.atMs) }}</span>
355
+ </button>
356
+ </section>
357
+ </aside>
358
+
359
+ <footer class="transport" px-24px>
360
+ <div flex items-center gap-6px>
361
+ <TelaIconButton
362
+ :icon="playing ? 'i-ph-pause-fill' : 'i-ph-play-fill'"
363
+ size="sm"
364
+ color="primary"
365
+ :aria-label="playing ? 'Pause playback' : 'Play playback'"
366
+ @click="togglePlayback"
367
+ />
368
+ <TelaIconButton icon="i-ph-arrow-counter-clockwise" size="xs" color="secondary" aria-label="Restart" @click="restart" />
369
+ <TelaIconButton icon="i-ph-caret-left" size="xs" color="secondary" aria-label="Previous event" @click="stepEvent(-1)" />
370
+ <TelaIconButton icon="i-ph-caret-right" size="xs" color="secondary" aria-label="Next event" @click="stepEvent(1)" />
371
+ </div>
372
+
373
+ <div class="timeline" min-w-0>
374
+ <div flex items-center justify-between gap-12px mb-7px body-10-regular text-tertiary tabular-nums>
375
+ <span>{{ formatTime(elapsedMs) }}</span>
376
+ <span v-if="currentEvent" truncate>{{ currentEvent.label }}</span>
377
+ <span>{{ formatTime(selectedScenario.durationMs) }}</span>
378
+ </div>
379
+ <div class="timeline-track">
380
+ <div class="timeline-progress" :style="{ transform: `scaleX(${progress})` }" />
381
+ <button
382
+ v-for="event in selectedScenario.events"
383
+ :key="event.id"
384
+ type="button"
385
+ class="timeline-marker"
386
+ :class="eventToneClass(event.tone)"
387
+ :style="{ left: `${event.atMs / selectedScenario.durationMs * 100}%` }"
388
+ :title="`${formatTime(event.atMs)} \xB7 ${event.label}`"
389
+ @click="seek(event.atMs)"
390
+ />
391
+ <input
392
+ :value="elapsedMs"
393
+ type="range"
394
+ min="0"
395
+ :max="selectedScenario.durationMs"
396
+ step="1"
397
+ aria-label="Playback timeline"
398
+ @input="seek(Number($event.target.value))"
399
+ >
400
+ </div>
401
+ </div>
402
+
403
+ <div flex items-center justify-end gap-14px>
404
+ <div flex items-center gap-10px>
405
+ <button
406
+ v-for="option in speedOptions"
407
+ :key="option.id"
408
+ type="button"
409
+ body-11-medium
410
+ :title="option.title"
411
+ :class="speed === option.id ? 'text-primary' : 'text-tertiary hover:text-secondary'"
412
+ transition-colors
413
+ @click="selectSpeed(option.id)"
414
+ >
415
+ {{ option.label }}
416
+ </button>
417
+ </div>
418
+ <button type="button" body-11-medium text-secondary hover:text-primary transition-colors @click="jumpToEnd">
419
+ End
420
+ </button>
421
+ </div>
422
+ </footer>
423
+ </section>
424
+ </template>
425
+
426
+ <style scoped>
427
+ .chat-debug-workbench{display:grid;grid-template-columns:188px minmax(480px,1fr) 280px;grid-template-rows:minmax(0,1fr) 82px;overflow:hidden}.diagnostics-rail,.scenario-rail,.stage-column{grid-row:1}.scenario-rail{grid-column:1}.stage-column{grid-column:2}.diagnostics-rail{grid-column:3}.transport{align-items:center;display:grid;gap:24px;grid-column:1/-1;grid-row:2;grid-template-columns:190px minmax(280px,1fr) 220px}.detail-list,.shortcut-list{display:flex;flex-direction:column;gap:9px}.detail-list>div,.shortcut-list :deep(div),.shortcut-list>div{display:contents}.shortcut-list{align-items:center;display:grid;grid-template-columns:minmax(0,1fr) auto}.detail-list dd,.shortcut-list dd{margin:0}.detail-list>div{align-items:baseline;display:flex;gap:16px;justify-content:space-between}.stage-viewport{overflow:hidden}.chat-under-test{height:100%;min-height:0;overflow:hidden;width:100%}.chat-under-test--narrow{max-width:520px}.chat-under-test--default{max-width:760px}.chat-under-test--wide{max-width:1040px}.chat-under-test--bounded{outline:1px dashed rgba(156,163,175,.8);outline-offset:-1px}.metric-grid{display:grid;gap:22px 18px;grid-template-columns:1fr 1fr}.metric-grid dd{margin:5px 0 0}.metric-grid small{color:#6b7280;font-size:10px;font-weight:400;margin-left:3px}.timeline-track{height:18px;position:relative}.timeline-progress,.timeline-track:before{content:"";height:2px;left:0;position:absolute;right:0;top:8px}.timeline-track:before{background:#e5e7eb}.timeline-progress{background:#1f2937;transform-origin:left center;z-index:1}.timeline-marker{height:8px;padding:0;position:absolute;top:5px;transform:translateX(-1px);width:2px;z-index:2}.timeline-track input[type=range]{cursor:ew-resize;height:18px;inset:0;margin:0;opacity:0;position:absolute;width:100%;z-index:3}@media (max-width:1180px){.chat-debug-workbench{grid-template-columns:168px minmax(480px,1fr) 260px}.diagnostics-rail,.scenario-rail{padding-left:16px;padding-right:16px}}@media (max-width:900px){.chat-debug-workbench{grid-template-columns:minmax(480px,1fr) 260px}.scenario-rail{display:none}.stage-column{grid-column:1}.diagnostics-rail{grid-column:2}}
428
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import type { Message } from '../../types/schemas/chat/conversations.js';
2
+ export type ChatDebugEventTone = 'neutral' | 'user' | 'assistant' | 'reasoning' | 'tool' | 'error';
3
+ type ChatDebugEventBase = {
4
+ id: string;
5
+ atMs: number;
6
+ label: string;
7
+ tone: ChatDebugEventTone;
8
+ };
9
+ export type ChatDebugEvent = ChatDebugEventBase & ({
10
+ type: 'message.upsert';
11
+ message: Message;
12
+ } | {
13
+ type: 'message.patch';
14
+ messageId: string;
15
+ patch: Partial<Message>;
16
+ } | {
17
+ type: 'playback.patch';
18
+ patch: Partial<Omit<ChatDebugPlaybackState, 'messages'>>;
19
+ });
20
+ export type ChatDebugScenario = {
21
+ id: string;
22
+ name: string;
23
+ intent: string;
24
+ durationMs: number;
25
+ events: ChatDebugEvent[];
26
+ };
27
+ export type ChatDebugPlaybackState = {
28
+ messages: Message[];
29
+ loading: boolean;
30
+ streamingActiveMessageId: string | null;
31
+ preparingSteps: string[] | null;
32
+ };
33
+ export declare const EMPTY_CHAT_DEBUG_PLAYBACK_STATE: ChatDebugPlaybackState;
34
+ export declare function applyChatDebugEvent(state: ChatDebugPlaybackState, event: ChatDebugEvent): ChatDebugPlaybackState;
35
+ export declare function resolveChatDebugPlayback(scenario: ChatDebugScenario, elapsedMs: number): ChatDebugPlaybackState;
36
+ export declare function countAppliedChatDebugEvents(scenario: ChatDebugScenario, elapsedMs: number): number;
37
+ export declare function createChatDebugScenarios(baseTime?: number): ChatDebugScenario[];
38
+ export {};