@meistrari/chat-nuxt 3.16.0-rc.3 → 3.16.0-rc.5
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/assets/css/markstream.css +1 -1
- package/dist/runtime/citations/citation-links.d.ts +7 -0
- package/dist/runtime/citations/citation-links.js +24 -0
- 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/files/components/chat-vault-link.vue +15 -4
- 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 +127 -18
- package/dist/runtime/messages/components/chat-message-bubble.vue +10 -4
- package/dist/runtime/messages/debug/chat-debug-citations.d.ts +8 -0
- package/dist/runtime/messages/debug/chat-debug-citations.js +60 -0
- package/dist/runtime/messages/debug/chat-debug-scenarios.d.ts +29 -1
- package/dist/runtime/messages/debug/chat-debug-scenarios.js +57 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
.markstream-vue{
|
|
1
|
+
.markstream-vue{font-family:inherit;--at-apply:"body-16-regular leading-24px text-primary";--list-item-marker:#a3a3a3;--list-item-counter-marker:#262626}.markstream-vue code,.markstream-vue kbd,.markstream-vue pre,.markstream-vue samp{font-family:Geist Mono Variable,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:13px;font-variation-settings:normal;letter-spacing:0;line-height:1.5}.markstream-vue ol,.markstream-vue ul{margin:8px 0;padding-left:1.5em}.markstream-vue .hr-node{border-color:#e5e5e5;border-top-width:.5px;margin-bottom:24px;margin-top:24px}.markstream-vue .strong-node{--at-apply:"font-580"}.markstream-vue .list-node.list-node{margin-bottom:8px;margin-left:4px;margin-top:8px;padding-left:1em}.markstream-vue ul{list-style:disc;list-style-position:outside}.markstream-vue ol{list-style:decimal;list-style-position:outside}.markstream-vue li{margin:4px 0}.markstream-vue .paragraph-node{--at-apply:"body-16-regular leading-24px";margin:0}.markstream-vue>.node-slot[data-node-type=paragraph]:not(:first-child){margin-top:12px}.markstream-vue .my-8{margin-bottom:8px;margin-top:8px}.markstream-vue .heading-node.heading-1,.markstream-vue h1{--at-apply:"heading-h2-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-2,.markstream-vue h2{--at-apply:"heading-h3-semibold text-primary";margin:24px 0 8px}.markstream-vue .heading-node.heading-3,.markstream-vue h3{--at-apply:"heading-h4-semibold text-primary";margin:20px 0 8px}.markstream-vue .heading-node.heading-4,.markstream-vue .heading-node.heading-5,.markstream-vue .heading-node.heading-6,.markstream-vue h4,.markstream-vue h5,.markstream-vue h6{--at-apply:"heading-h5-semibold text-primary";margin:16px 0 8px}.markstream-vue>.node-slot:first-child .heading-node{margin-top:0}.markstream-vue [class*=tooltip],[class*=tooltip-element]{background-color:var(--neutral-900,#1a1a1a)!important;border-radius:4px;color:#fff!important;font-size:12px;padding:4px 8px}.mention-tag{align-items:center;border-radius:9999px;display:inline-flex;font-size:.875em;font-weight:500;gap:4px;padding:2px 8px;vertical-align:middle;white-space:nowrap}.mention-tag-file{background-color:rgba(127,189,247,.12);color:#7fbdf7}.mention-tag-skill{background-color:rgba(155,111,222,.12);color:#9b6fde}.mention-tag-canvas{background-color:rgba(234,161,65,.12);color:#d4882e}.mention-tag>span[class*=i-ph]{flex-shrink:0}
|
|
@@ -30,3 +30,10 @@ export declare function isCitationUrl(url: string): boolean;
|
|
|
30
30
|
export declare function parseCitationUrl(url: string): CitationRef | null;
|
|
31
31
|
/** Cache key for resolution state: the locator addresses a spot inside the same document. */
|
|
32
32
|
export declare function citationCacheKey(ref: Pick<CitationRef, 'namespace' | 'id'>): string;
|
|
33
|
+
/**
|
|
34
|
+
* Rewrites bare `cite://` tokens into `[id](cite://...)` markdown links so they render inline
|
|
35
|
+
* through the link node instead of splitting the surrounding paragraph into block segments.
|
|
36
|
+
* Square brackets that only wrap a token are dropped, since the citation chip already reads as
|
|
37
|
+
* a reference.
|
|
38
|
+
*/
|
|
39
|
+
export declare function linkifyBareCitations(text: string): string;
|
|
@@ -54,3 +54,27 @@ function parsePositiveInt(value) {
|
|
|
54
54
|
export function citationCacheKey(ref) {
|
|
55
55
|
return `${ref.namespace}/${ref.id}`;
|
|
56
56
|
}
|
|
57
|
+
const BARE_CITE_REWRITE_REGEX = /\[[ \t]*(cite:\/\/[^\s"'<>`\[\]()*~]+)[ \t]*\](?!\()|(?<!\]\()(?<!<)(?<!\]:[ \t]*)(cite:\/\/[^\s"'<>`\[\]()*~]+)(?!\]\()/gi;
|
|
58
|
+
const MARKDOWN_CODE_REGEX = /(```|~~~)[\s\S]*?(?:\1|$)|``+[^`]*``+|`[^`]*`/g;
|
|
59
|
+
export function linkifyBareCitations(text) {
|
|
60
|
+
if (!text.toLowerCase().includes(CITE_SCHEME))
|
|
61
|
+
return text;
|
|
62
|
+
let result = "";
|
|
63
|
+
let lastEnd = 0;
|
|
64
|
+
for (const match of text.matchAll(MARKDOWN_CODE_REGEX)) {
|
|
65
|
+
result += linkifyProse(text.slice(lastEnd, match.index)) + match[0];
|
|
66
|
+
lastEnd = match.index + match[0].length;
|
|
67
|
+
}
|
|
68
|
+
return result + linkifyProse(text.slice(lastEnd));
|
|
69
|
+
}
|
|
70
|
+
function linkifyProse(text) {
|
|
71
|
+
return text.replace(BARE_CITE_REWRITE_REGEX, (match, bracketed, bare) => {
|
|
72
|
+
const token = bracketed ?? bare ?? "";
|
|
73
|
+
const url = token.replace(/[.,:;!?]+$/, "");
|
|
74
|
+
const trailing = token.slice(url.length);
|
|
75
|
+
const citation = parseCitationUrl(url);
|
|
76
|
+
if (!citation)
|
|
77
|
+
return match;
|
|
78
|
+
return `[${citation.id}](${url})${trailing}`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -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,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { parseCitationUrl } from "../../citations/citation-links";
|
|
2
|
+
import { isCitationUrl, parseCitationUrl } from "../../citations/citation-links";
|
|
3
3
|
import ChatUrlCitation from "../../citations/components/chat-url-citation.vue";
|
|
4
4
|
import { parseUrlCitation } from "../../citations/url-citation";
|
|
5
5
|
import { useEmbedConfig } from "../../embed/composables/embed-config";
|
|
@@ -22,6 +22,13 @@ const detectedCitation = computed(() => {
|
|
|
22
22
|
return null;
|
|
23
23
|
return parseCitationUrl(props.node.href);
|
|
24
24
|
});
|
|
25
|
+
const citationLabel = computed(() => {
|
|
26
|
+
const citation = detectedCitation.value;
|
|
27
|
+
const label = linkText.value.trim();
|
|
28
|
+
if (!citation || !label || label === citation.id || isCitationUrl(label))
|
|
29
|
+
return void 0;
|
|
30
|
+
return label;
|
|
31
|
+
});
|
|
25
32
|
const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, linkText.value, props.node.title));
|
|
26
33
|
</script>
|
|
27
34
|
|
|
@@ -37,7 +44,7 @@ const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, lin
|
|
|
37
44
|
<ChatCitation
|
|
38
45
|
v-else-if="detectedCitation"
|
|
39
46
|
:citation="detectedCitation"
|
|
40
|
-
:label="
|
|
47
|
+
:label="citationLabel"
|
|
41
48
|
/>
|
|
42
49
|
<ChatUrlCitation
|
|
43
50
|
v-else-if="detectedUrlCitation"
|
|
@@ -49,6 +56,10 @@ const detectedUrlCitation = computed(() => parseUrlCitation(props.node.href, lin
|
|
|
49
56
|
:title="node.title || void 0"
|
|
50
57
|
target="_blank"
|
|
51
58
|
rel="noopener noreferrer"
|
|
52
|
-
class="
|
|
53
|
-
>{{ linkText }}
|
|
59
|
+
class="chat-inline-link"
|
|
60
|
+
>{{ linkText }}<span class="chat-inline-link__icon" i-ph-arrow-up-right-bold aria-hidden="true" /></a>
|
|
54
61
|
</template>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
.chat-inline-link{color:inherit;text-decoration:underline;text-decoration-color:#a3a3a3;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:3px;transition:text-decoration-color .15s ease}.chat-inline-link:hover{text-decoration-color:#525252}.chat-inline-link__icon{display:inline-block;font-size:.7em;margin-left:.2em;vertical-align:.05em}
|
|
65
|
+
</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;
|
|
@@ -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,8 +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";
|
|
5
|
+
import { createChatDebugCitationsConfig } from "../debug/chat-debug-citations";
|
|
6
|
+
import { createReasoningCache, resolveReasoningDisplayMessages } from "../message-reasoning";
|
|
4
7
|
import { createChatDebugScenarios } from "../debug/chat-debug-scenarios";
|
|
5
|
-
import { useChatDebugDiagnostics } from "../debug/use-chat-debug-diagnostics";
|
|
8
|
+
import { useChatDebugDiagnostics, useChatDebugWindowListenerProbe } from "../debug/use-chat-debug-diagnostics";
|
|
6
9
|
import { formatChatDebugTime as formatTime, useChatDebugPlayback } from "../debug/use-chat-debug-playback";
|
|
7
10
|
const scenarios = createChatDebugScenarios();
|
|
8
11
|
const viewportWidth = ref("default");
|
|
@@ -12,7 +15,8 @@ const rootRef = ref(null);
|
|
|
12
15
|
const stageRef = ref(null);
|
|
13
16
|
provideEmbedConfig({
|
|
14
17
|
workspaceId: "chat-debug-workbench",
|
|
15
|
-
hideSidebar: true
|
|
18
|
+
hideSidebar: true,
|
|
19
|
+
citations: createChatDebugCitationsConfig()
|
|
16
20
|
}, { syncGlobal: false });
|
|
17
21
|
const {
|
|
18
22
|
selectedScenario,
|
|
@@ -30,7 +34,8 @@ const {
|
|
|
30
34
|
togglePlayback,
|
|
31
35
|
jumpToEnd,
|
|
32
36
|
stepEvent,
|
|
33
|
-
selectSpeed
|
|
37
|
+
selectSpeed,
|
|
38
|
+
applyManualEvent
|
|
34
39
|
} = useChatDebugPlayback(scenarios, {
|
|
35
40
|
onFrame: handlePlaybackFrame,
|
|
36
41
|
onReset: handlePlaybackReset
|
|
@@ -45,11 +50,47 @@ const { diagnostics, reset: resetDiagnostics, recordFrame } = useChatDebugDiagno
|
|
|
45
50
|
speed,
|
|
46
51
|
seekRevision
|
|
47
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
|
+
}
|
|
48
87
|
function handlePlaybackFrame(frameDeltaMs) {
|
|
49
88
|
recordFrame(frameDeltaMs);
|
|
50
89
|
}
|
|
51
90
|
function handlePlaybackReset() {
|
|
52
91
|
resetDiagnostics();
|
|
92
|
+
switchCount.value = 0;
|
|
93
|
+
railResetKey.value += 1;
|
|
53
94
|
}
|
|
54
95
|
const messageCharacters = computed(() => playbackState.value.messages.reduce(
|
|
55
96
|
(total, message) => total + message.content.length,
|
|
@@ -187,24 +228,35 @@ function eventToneClass(tone) {
|
|
|
187
228
|
</div>
|
|
188
229
|
</div>
|
|
189
230
|
|
|
190
|
-
<div
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
:
|
|
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="[
|
|
194
245
|
`chat-under-test--${viewportWidth}`,
|
|
195
246
|
showBounds && 'chat-under-test--bounded'
|
|
196
247
|
]"
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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>
|
|
208
260
|
</div>
|
|
209
261
|
</div>
|
|
210
262
|
</main>
|
|
@@ -346,6 +398,63 @@ function eventToneClass(tone) {
|
|
|
346
398
|
</dl>
|
|
347
399
|
</section>
|
|
348
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
|
+
|
|
349
458
|
<section mt-28px>
|
|
350
459
|
<div flex items-center justify-between gap-12px mb-14px>
|
|
351
460
|
<div body-10-regular text-tertiary uppercase tracking-0.08em>
|
|
@@ -3,6 +3,7 @@ import { useChatAuth } from "#chat-auth";
|
|
|
3
3
|
import { useEmbedConfig } from "../../embed/composables/embed-config";
|
|
4
4
|
import { useWorkspaceMembers } from "../../workspace/composables/workspace-members";
|
|
5
5
|
import { isPromptTooLongFailure } from "../../types/schemas/chat/messages.js";
|
|
6
|
+
import { linkifyBareCitations } from "../../citations/citation-links";
|
|
6
7
|
import { parseContentWithFiles } from "../../files/content-detection";
|
|
7
8
|
import ChatVaultLink from "../../files/components/chat-vault-link.vue";
|
|
8
9
|
const props = defineProps({
|
|
@@ -56,7 +57,12 @@ function formatTime(date) {
|
|
|
56
57
|
const d = date instanceof Date ? date : new Date(date);
|
|
57
58
|
return d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
|
|
58
59
|
}
|
|
59
|
-
const displayContent = computed(() =>
|
|
60
|
+
const displayContent = computed(() => {
|
|
61
|
+
const content = props.message.content ?? "";
|
|
62
|
+
if (props.message.role !== "assistant" || !embedConfig?.citations.value)
|
|
63
|
+
return content;
|
|
64
|
+
return linkifyBareCitations(content);
|
|
65
|
+
});
|
|
60
66
|
const userContentHtml = computed(() => {
|
|
61
67
|
if (props.message.role !== "user")
|
|
62
68
|
return "";
|
|
@@ -93,7 +99,7 @@ const contentSegments = computed(() => {
|
|
|
93
99
|
const content = displayContent.value;
|
|
94
100
|
if (!content)
|
|
95
101
|
return [];
|
|
96
|
-
return parseContentWithFiles(content
|
|
102
|
+
return parseContentWithFiles(content);
|
|
97
103
|
});
|
|
98
104
|
const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type !== "text"));
|
|
99
105
|
</script>
|
|
@@ -227,7 +233,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
227
233
|
</div>
|
|
228
234
|
</template>
|
|
229
235
|
<template v-else>
|
|
230
|
-
<div v-if="hasDetectedFiles" body-16-regular text-
|
|
236
|
+
<div v-if="hasDetectedFiles" body-16-regular text-primary>
|
|
231
237
|
<template v-for="(segment, index) in contentSegments" :key="index">
|
|
232
238
|
<ClientOnly v-if="segment.type === 'text'">
|
|
233
239
|
<AppMarkdownRender
|
|
@@ -266,7 +272,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
266
272
|
:smooth-streaming="isPending ? 'auto' : false"
|
|
267
273
|
:typewriter="isPending"
|
|
268
274
|
mode="chat"
|
|
269
|
-
text-
|
|
275
|
+
text-primary
|
|
270
276
|
/>
|
|
271
277
|
</ClientOnly>
|
|
272
278
|
<div
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CitationRef } from '../../citations/citation-links.js';
|
|
2
|
+
import type { ChatCitationsConfig, CitationMeta } from '../../citations/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Metadata derived from the reference itself, so any `cite://` a scenario emits resolves
|
|
5
|
+
* without a backend or a per-document fixture table.
|
|
6
|
+
*/
|
|
7
|
+
export declare function chatDebugCitationMeta(ref: CitationRef): CitationMeta;
|
|
8
|
+
export declare function createChatDebugCitationsConfig(): ChatCitationsConfig;
|