@flamingo-stack/openframe-frontend-core 0.0.584-1973.5461.1 → 0.0.584
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/chat-protocol/events.d.ts +2 -8
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +0 -188
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.js +0 -188
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/{chunk-U7MJV2V7.cjs → chunk-33DHEEMP.cjs} +33 -33
- package/dist/{chunk-U7MJV2V7.cjs.map → chunk-33DHEEMP.cjs.map} +1 -1
- package/dist/{chunk-23VBGHBL.js → chunk-3ELW4VML.js} +2 -2
- package/dist/{chunk-7KBKLN2Z.cjs → chunk-3ZA2OYRE.cjs} +3 -3
- package/dist/{chunk-7KBKLN2Z.cjs.map → chunk-3ZA2OYRE.cjs.map} +1 -1
- package/dist/{chunk-73XDYWYG.js → chunk-4T5KOD3G.js} +5 -5
- package/dist/{chunk-MI6PPL3P.js → chunk-5CJKZHWO.js} +5 -5
- package/dist/{chunk-E7Q24T4G.js → chunk-6BJYPG2V.js} +3 -3
- package/dist/{chunk-YXNMLGFI.cjs → chunk-7X5TCK4R.cjs} +10 -10
- package/dist/{chunk-YXNMLGFI.cjs.map → chunk-7X5TCK4R.cjs.map} +1 -1
- package/dist/{chunk-7DEGEI7G.js → chunk-EJKEDCTH.js} +6 -6
- package/dist/{chunk-XIV5VDWL.cjs → chunk-HCKEDTKJ.cjs} +10 -10
- package/dist/{chunk-XIV5VDWL.cjs.map → chunk-HCKEDTKJ.cjs.map} +1 -1
- package/dist/{chunk-EQGAYDTC.cjs → chunk-IIT5HWLY.cjs} +9 -9
- package/dist/{chunk-EQGAYDTC.cjs.map → chunk-IIT5HWLY.cjs.map} +1 -1
- package/dist/{chunk-7SMYBCYH.cjs → chunk-IKUSKKFZ.cjs} +540 -883
- package/dist/chunk-IKUSKKFZ.cjs.map +1 -0
- package/dist/{chunk-JKYALZB7.cjs → chunk-JAJQ5P2Q.cjs} +22 -22
- package/dist/{chunk-JKYALZB7.cjs.map → chunk-JAJQ5P2Q.cjs.map} +1 -1
- package/dist/{chunk-WAXL3E3B.cjs → chunk-JFKK3ZOA.cjs} +43 -43
- package/dist/{chunk-WAXL3E3B.cjs.map → chunk-JFKK3ZOA.cjs.map} +1 -1
- package/dist/{chunk-AWAHYAII.js → chunk-JVWRMLQZ.js} +2 -2
- package/dist/{chunk-PZ27ELFP.cjs → chunk-JWKAJTNA.cjs} +43 -33
- package/dist/chunk-JWKAJTNA.cjs.map +1 -0
- package/dist/{chunk-PISJGXXI.js → chunk-KP6VJP36.js} +3 -3
- package/dist/{chunk-NSDZAUJK.js → chunk-LNKULR4V.js} +2 -2
- package/dist/{chunk-AFQB4YL7.cjs → chunk-MEJFHD5N.cjs} +15 -15
- package/dist/{chunk-AFQB4YL7.cjs.map → chunk-MEJFHD5N.cjs.map} +1 -1
- package/dist/{chunk-5VHCSN44.js → chunk-MYEG5ZDT.js} +2 -2
- package/dist/{chunk-MMA3MTJD.cjs → chunk-P2VRLSAP.cjs} +21 -21
- package/dist/{chunk-MMA3MTJD.cjs.map → chunk-P2VRLSAP.cjs.map} +1 -1
- package/dist/{chunk-7Q34BVL7.cjs → chunk-Q66AMADB.cjs} +4 -4
- package/dist/{chunk-7Q34BVL7.cjs.map → chunk-Q66AMADB.cjs.map} +1 -1
- package/dist/{chunk-4ARYZTGV.cjs → chunk-QJR32JM2.cjs} +159 -159
- package/dist/{chunk-4ARYZTGV.cjs.map → chunk-QJR32JM2.cjs.map} +1 -1
- package/dist/{chunk-M4IIK3B7.js → chunk-RURZ6QLN.js} +3 -3
- package/dist/{chunk-TCEU6DCF.js → chunk-TLKHLGAB.js} +3 -3
- package/dist/{chunk-JCWUJHOD.js → chunk-TOUD3AGK.js} +23 -13
- package/dist/chunk-TOUD3AGK.js.map +1 -0
- package/dist/{chunk-CHI7GJ3K.cjs → chunk-UCNEQKIT.cjs} +5 -5
- package/dist/{chunk-CHI7GJ3K.cjs.map → chunk-UCNEQKIT.cjs.map} +1 -1
- package/dist/{chunk-ZRGBLM6W.cjs → chunk-VOAZU46B.cjs} +69 -69
- package/dist/{chunk-ZRGBLM6W.cjs.map → chunk-VOAZU46B.cjs.map} +1 -1
- package/dist/{chunk-GYJVLFK5.js → chunk-VZSTL5VG.js} +2 -2
- package/dist/{chunk-3CAC3AAE.js → chunk-XPJKQ63S.js} +3 -3
- package/dist/{chunk-KSC5LNSY.js → chunk-XWBND4G5.js} +187 -530
- package/dist/chunk-XWBND4G5.js.map +1 -0
- package/dist/{chunk-22DWXIV3.js → chunk-YFZDIEVC.js} +5 -5
- package/dist/{chunk-7J7XDGY6.cjs → chunk-ZHUBNWEX.cjs} +3 -3
- package/dist/{chunk-7J7XDGY6.cjs.map → chunk-ZHUBNWEX.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +1 -4
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-slash-commands.d.ts +3 -3
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +28 -2
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +0 -4
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +1 -35
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +3 -3
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +36 -36
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +21 -21
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/hover-dropdown.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/index.cjs +7 -7
- package/dist/index.js +6 -6
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -130
- package/src/chat-protocol/events.ts +1 -9
- package/src/chat-protocol/nats-decoder.ts +0 -7
- package/src/components/chat/__tests__/chat-message-list.test.tsx +0 -70
- package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -86
- package/src/components/chat/chat-message-enhanced.tsx +13 -28
- package/src/components/chat/chat-message-list.tsx +22 -27
- package/src/components/chat/embeddable-chat.tsx +36 -55
- package/src/components/chat/entity-cards/dispatch.tsx +17 -86
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +0 -7
- package/src/components/chat/hooks/use-slash-commands.ts +5 -9
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +29 -2
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -56
- package/src/components/chat/stream/chat-stream-reducer.ts +0 -31
- package/src/components/chat/tool-execution-display.tsx +2 -5
- package/src/components/chat/types/component.types.ts +0 -4
- package/src/components/chat/types/message.types.ts +0 -39
- package/src/components/chat/types/unified-chat-state.types.ts +3 -4
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -57
- package/src/components/chat/utils/process-historical-messages.ts +1 -19
- package/src/components/navigation/navigation-sidebar-item.tsx +19 -9
- package/src/components/ui/hover-dropdown.tsx +3 -8
- package/dist/chat-protocol/source-metadata.d.ts +0 -5
- package/dist/chat-protocol/source-metadata.d.ts.map +0 -1
- package/dist/chunk-7SMYBCYH.cjs.map +0 -1
- package/dist/chunk-JCWUJHOD.js.map +0 -1
- package/dist/chunk-KSC5LNSY.js.map +0 -1
- package/dist/chunk-PZ27ELFP.cjs.map +0 -1
- package/dist/components/chat/utils/chat-source-metadata.d.ts +0 -5
- package/dist/components/chat/utils/chat-source-metadata.d.ts.map +0 -1
- package/src/chat-protocol/__tests__/source-metadata.test.ts +0 -74
- package/src/chat-protocol/source-metadata.ts +0 -202
- package/src/components/chat/__tests__/embeddable-chat-sources.test.tsx +0 -411
- package/src/components/chat/__tests__/tool-execution-display.test.tsx +0 -93
- package/src/components/chat/entity-cards/__tests__/dispatch-video-refs.test.tsx +0 -74
- package/src/components/chat/hooks/__tests__/use-slash-commands.test.ts +0 -64
- package/src/components/chat/utils/chat-source-metadata.ts +0 -19
- /package/dist/{chunk-23VBGHBL.js.map → chunk-3ELW4VML.js.map} +0 -0
- /package/dist/{chunk-73XDYWYG.js.map → chunk-4T5KOD3G.js.map} +0 -0
- /package/dist/{chunk-MI6PPL3P.js.map → chunk-5CJKZHWO.js.map} +0 -0
- /package/dist/{chunk-E7Q24T4G.js.map → chunk-6BJYPG2V.js.map} +0 -0
- /package/dist/{chunk-7DEGEI7G.js.map → chunk-EJKEDCTH.js.map} +0 -0
- /package/dist/{chunk-AWAHYAII.js.map → chunk-JVWRMLQZ.js.map} +0 -0
- /package/dist/{chunk-PISJGXXI.js.map → chunk-KP6VJP36.js.map} +0 -0
- /package/dist/{chunk-NSDZAUJK.js.map → chunk-LNKULR4V.js.map} +0 -0
- /package/dist/{chunk-5VHCSN44.js.map → chunk-MYEG5ZDT.js.map} +0 -0
- /package/dist/{chunk-M4IIK3B7.js.map → chunk-RURZ6QLN.js.map} +0 -0
- /package/dist/{chunk-TCEU6DCF.js.map → chunk-TLKHLGAB.js.map} +0 -0
- /package/dist/{chunk-GYJVLFK5.js.map → chunk-VZSTL5VG.js.map} +0 -0
- /package/dist/{chunk-3CAC3AAE.js.map → chunk-XPJKQ63S.js.map} +0 -0
- /package/dist/{chunk-22DWXIV3.js.map → chunk-YFZDIEVC.js.map} +0 -0
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import type { ChatRef } from '../components/chat/chat-ref.types';
|
|
2
|
-
import type { ChatSource } from '../components/chat/types/message.types';
|
|
3
|
-
import type { SourcesEvent } from './events';
|
|
4
|
-
|
|
5
|
-
const CARD_REFERENCE = /^\[card:\/\/([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)\]$/;
|
|
6
|
-
const SOURCE_METADATA_MATCHED = 'sourceMetadataMatched';
|
|
7
|
-
|
|
8
|
-
export function isSourceMetadataCardRef(ref: ChatRef): boolean {
|
|
9
|
-
return ref.metadata?.[SOURCE_METADATA_MATCHED] === true;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function requiredString(value: unknown): string | null {
|
|
13
|
-
if (typeof value !== 'string') return null;
|
|
14
|
-
const normalized = value.trim();
|
|
15
|
-
return normalized ? normalized : null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function optionalString(value: unknown): string | undefined {
|
|
19
|
-
return requiredString(value) ?? undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function httpsUrl(value: unknown): string | undefined {
|
|
23
|
-
const candidate = optionalString(value);
|
|
24
|
-
if (!candidate) return undefined;
|
|
25
|
-
try {
|
|
26
|
-
return new URL(candidate).protocol === 'https:' ? candidate : undefined;
|
|
27
|
-
} catch {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function youtubeId(value: unknown): string | undefined {
|
|
33
|
-
const candidate = optionalString(value);
|
|
34
|
-
if (!candidate) return undefined;
|
|
35
|
-
if (/^[A-Za-z0-9_-]{11}$/.test(candidate)) return candidate;
|
|
36
|
-
const url = httpsUrl(candidate);
|
|
37
|
-
if (!url) return undefined;
|
|
38
|
-
const parsed = new URL(url);
|
|
39
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
40
|
-
const youtubeHost = hostname === 'youtu.be' || hostname === 'youtube.com' || hostname.endsWith('.youtube.com');
|
|
41
|
-
if (!youtubeHost) return undefined;
|
|
42
|
-
const pathId =
|
|
43
|
-
hostname === 'youtu.be'
|
|
44
|
-
? parsed.pathname.split('/').filter(Boolean)[0]
|
|
45
|
-
: parsed.pathname.match(/^\/(?:embed|v|shorts)\/([A-Za-z0-9_-]{11})(?:\/|$)/)?.[1];
|
|
46
|
-
const id = pathId ?? parsed.searchParams.get('v') ?? undefined;
|
|
47
|
-
return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function nullableString(value: unknown): string | null | undefined {
|
|
51
|
-
return value === null ? null : optionalString(value);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function normalizeSourceItems(value: unknown): NonNullable<ChatSource['items']> | undefined {
|
|
55
|
-
if (!Array.isArray(value)) return undefined;
|
|
56
|
-
const items = value.flatMap(candidate => {
|
|
57
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return [];
|
|
58
|
-
const item = candidate as Record<string, unknown>;
|
|
59
|
-
const id = requiredString(item.id);
|
|
60
|
-
const documentType = requiredString(item.documentType);
|
|
61
|
-
const name = requiredString(item.name);
|
|
62
|
-
if (!id || !documentType || !name) return [];
|
|
63
|
-
const externalUrl = httpsUrl(item.externalUrl);
|
|
64
|
-
const targetPlatform = nullableString(item.targetPlatform);
|
|
65
|
-
const path = nullableString(item.path);
|
|
66
|
-
return [
|
|
67
|
-
{
|
|
68
|
-
id,
|
|
69
|
-
documentType,
|
|
70
|
-
name,
|
|
71
|
-
...(externalUrl ? { externalUrl } : {}),
|
|
72
|
-
...(targetPlatform !== undefined ? { targetPlatform } : {}),
|
|
73
|
-
...(path !== undefined ? { path } : {}),
|
|
74
|
-
},
|
|
75
|
-
];
|
|
76
|
-
});
|
|
77
|
-
return items.length > 0 ? items : undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function normalizeSources(value: unknown): ChatSource[] {
|
|
81
|
-
if (!Array.isArray(value)) return [];
|
|
82
|
-
const seen = new Set<number>();
|
|
83
|
-
const sources: ChatSource[] = [];
|
|
84
|
-
for (const candidate of value) {
|
|
85
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) continue;
|
|
86
|
-
const source = candidate as Record<string, unknown>;
|
|
87
|
-
const index = source.index;
|
|
88
|
-
const name = requiredString(source.name);
|
|
89
|
-
const path = requiredString(source.path);
|
|
90
|
-
const documentType = requiredString(source.documentType);
|
|
91
|
-
const validIndex =
|
|
92
|
-
typeof index === 'number' && Number.isFinite(index) && Number.isInteger(index) && index > 0 && !seen.has(index);
|
|
93
|
-
if (!validIndex || !name || !path || !documentType) continue;
|
|
94
|
-
seen.add(index);
|
|
95
|
-
const externalUrl = httpsUrl(source.externalUrl);
|
|
96
|
-
const targetPlatform = nullableString(source.targetPlatform);
|
|
97
|
-
const id = optionalString(source.id);
|
|
98
|
-
const sourceRepo = optionalString(source.sourceRepo);
|
|
99
|
-
const label = optionalString(source.label);
|
|
100
|
-
const items = normalizeSourceItems(source.items);
|
|
101
|
-
sources.push({
|
|
102
|
-
index,
|
|
103
|
-
name,
|
|
104
|
-
path,
|
|
105
|
-
documentType,
|
|
106
|
-
...(externalUrl ? { externalUrl } : {}),
|
|
107
|
-
...(targetPlatform !== undefined ? { targetPlatform } : {}),
|
|
108
|
-
...(id ? { id } : {}),
|
|
109
|
-
...(sourceRepo ? { sourceRepo } : {}),
|
|
110
|
-
...(label ? { label } : {}),
|
|
111
|
-
...(items ? { items } : {}),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
return sources;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function normalizeVideoMetadata(value: unknown): Record<string, string> {
|
|
118
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
119
|
-
const input = value as Record<string, unknown>;
|
|
120
|
-
const metadata: Record<string, string> = {};
|
|
121
|
-
for (const key of ['videoUrl', 'highlightVideoUrl', 'videoPoster', 'highlightVideoPoster'] as const) {
|
|
122
|
-
const url = httpsUrl(input[key]);
|
|
123
|
-
if (url) metadata[key] = url;
|
|
124
|
-
}
|
|
125
|
-
const id = youtubeId(input.youtubeUrl);
|
|
126
|
-
if (id) metadata.youtubeUrl = id;
|
|
127
|
-
return metadata;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function normalizeVideos(value: unknown): ChatRef[] {
|
|
131
|
-
if (!Array.isArray(value)) return [];
|
|
132
|
-
return value.flatMap(candidate => {
|
|
133
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return [];
|
|
134
|
-
const video = candidate as Record<string, unknown>;
|
|
135
|
-
const match = typeof video.ref === 'string' ? CARD_REFERENCE.exec(video.ref) : null;
|
|
136
|
-
if (!match || match[1] !== 'video') return [];
|
|
137
|
-
const id = match[2];
|
|
138
|
-
const title = optionalString(video.title) ?? id;
|
|
139
|
-
const url = httpsUrl(video.url) ?? null;
|
|
140
|
-
const sourceRepo = optionalString(video.sourceRepo);
|
|
141
|
-
const metadata = normalizeVideoMetadata(video.metadata);
|
|
142
|
-
if (url) {
|
|
143
|
-
const idFromUrl = youtubeId(url);
|
|
144
|
-
if (idFromUrl) {
|
|
145
|
-
metadata.youtubeUrl = idFromUrl;
|
|
146
|
-
delete metadata.videoUrl;
|
|
147
|
-
} else {
|
|
148
|
-
metadata.videoUrl = url;
|
|
149
|
-
delete metadata.youtubeUrl;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return [
|
|
153
|
-
{
|
|
154
|
-
type: 'video',
|
|
155
|
-
id,
|
|
156
|
-
title,
|
|
157
|
-
url,
|
|
158
|
-
...(sourceRepo ? { sourceRepo } : {}),
|
|
159
|
-
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function normalizeCards(value: unknown, sources: ChatSource[]): ChatRef[] {
|
|
166
|
-
if (!Array.isArray(value)) return [];
|
|
167
|
-
return value.flatMap(candidate => {
|
|
168
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return [];
|
|
169
|
-
const card = candidate as Record<string, unknown>;
|
|
170
|
-
const match = typeof card.ref === 'string' ? CARD_REFERENCE.exec(card.ref) : null;
|
|
171
|
-
if (!match || match[1] === 'video') return [];
|
|
172
|
-
const type = requiredString(card.entityType);
|
|
173
|
-
const id = requiredString(card.entityId);
|
|
174
|
-
if (!type || !id || type !== match[1] || id !== match[2]) return [];
|
|
175
|
-
const source = sources.find(item => item.id === id && item.documentType === type);
|
|
176
|
-
const ref: ChatRef = {
|
|
177
|
-
type,
|
|
178
|
-
id,
|
|
179
|
-
title: source?.name ?? id,
|
|
180
|
-
url: source?.externalUrl ?? null,
|
|
181
|
-
...(source?.sourceRepo ? { sourceRepo: source.sourceRepo } : {}),
|
|
182
|
-
...(source?.targetPlatform !== undefined ? { targetPlatform: source.targetPlatform } : {}),
|
|
183
|
-
...(source?.path ? { metadata: { path: source.path, [SOURCE_METADATA_MATCHED]: true } } : {}),
|
|
184
|
-
};
|
|
185
|
-
return [ref];
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function sourceMetadataEvent(payload: unknown): SourcesEvent | null {
|
|
190
|
-
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null;
|
|
191
|
-
const metadata = payload as Record<string, unknown>;
|
|
192
|
-
const sources = normalizeSources(metadata.sources);
|
|
193
|
-
const refs = [...normalizeVideos(metadata.videos), ...normalizeCards(metadata.cards, sources)];
|
|
194
|
-
const refsByIdentity = new Map<string, ChatRef>();
|
|
195
|
-
refs.forEach(ref => {
|
|
196
|
-
const identity = `${ref.type}:${ref.id}`;
|
|
197
|
-
if (!refsByIdentity.has(identity)) refsByIdentity.set(identity, ref);
|
|
198
|
-
});
|
|
199
|
-
const uniqueRefs = Array.from(refsByIdentity.values());
|
|
200
|
-
if (sources.length === 0 && uniqueRefs.length === 0) return null;
|
|
201
|
-
return { type: 'sources', sources, ...(uniqueRefs.length > 0 ? { refs: uniqueRefs } : {}) };
|
|
202
|
-
}
|
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
2
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
4
|
-
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { decodeNatsChunk } from '../../../chat-protocol/nats-decoder';
|
|
6
|
-
import { type ChatRuntime, ChatRuntimeContext } from '../../../contexts/chat-runtime-context';
|
|
7
|
-
import { EmbeddableChat } from '../embeddable-chat';
|
|
8
|
-
import { createChatStreamReducer } from '../stream/chat-stream-reducer';
|
|
9
|
-
import type { UnifiedChatMessage, UnifiedChatState } from '../types/unified-chat-state.types';
|
|
10
|
-
|
|
11
|
-
vi.mock('@mux/mux-player-react', () => ({
|
|
12
|
-
default: ({ src }: { src?: string }) => <div data-testid="mux-player" data-src={src} />,
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
const runtime: ChatRuntime = {
|
|
16
|
-
endpoints: {
|
|
17
|
-
chatStreamUrl: '/test/chat',
|
|
18
|
-
approvalToolUrl: '/test/approve',
|
|
19
|
-
commandsUrl: '/test/commands',
|
|
20
|
-
buildListUrl: () => null,
|
|
21
|
-
attachmentUploadUrl: '/test/upload',
|
|
22
|
-
attachmentViewUrlPrefix: '/test/view/',
|
|
23
|
-
identityUrl: '/test/identity',
|
|
24
|
-
},
|
|
25
|
-
navigation: { mode: 'host' },
|
|
26
|
-
source: 'openframe',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function stateFor(messages: UnifiedChatMessage[]): UnifiedChatState {
|
|
30
|
-
const noop = () => {};
|
|
31
|
-
const asyncNoop = async () => {};
|
|
32
|
-
return {
|
|
33
|
-
messages,
|
|
34
|
-
isLoading: false,
|
|
35
|
-
streamingPhase: 'idle',
|
|
36
|
-
sendMessage: asyncNoop,
|
|
37
|
-
stopMessage: noop,
|
|
38
|
-
clearMessages: noop,
|
|
39
|
-
discussRef: noop,
|
|
40
|
-
displayRef: noop,
|
|
41
|
-
currentProvider: null,
|
|
42
|
-
currentModelLabel: null,
|
|
43
|
-
currentContextWindowMaxTokens: null,
|
|
44
|
-
currentInputTokens: null,
|
|
45
|
-
currentOutputTokens: null,
|
|
46
|
-
currentCacheHitRatePct: null,
|
|
47
|
-
currentUsageBreakdown: null,
|
|
48
|
-
dialogs: [{ id: 'dialog-1', title: 'Sources' }],
|
|
49
|
-
activeDialogId: 'dialog-1',
|
|
50
|
-
selectDialog: noop,
|
|
51
|
-
startNewDialog: () => Promise.resolve(null),
|
|
52
|
-
deleteDialog: asyncNoop,
|
|
53
|
-
renameDialog: asyncNoop,
|
|
54
|
-
archiveDialog: asyncNoop,
|
|
55
|
-
isDialogsLoading: false,
|
|
56
|
-
dialogsError: false,
|
|
57
|
-
reloadDialogs: noop,
|
|
58
|
-
isMessagesLoading: false,
|
|
59
|
-
hasMoreDialogs: false,
|
|
60
|
-
loadMoreDialogs: asyncNoop,
|
|
61
|
-
hasMoreMessages: false,
|
|
62
|
-
loadMoreMessages: asyncNoop,
|
|
63
|
-
approveRequest: asyncNoop,
|
|
64
|
-
rejectRequest: asyncNoop,
|
|
65
|
-
dialogTokenUsage: null,
|
|
66
|
-
connectionState: 'connected',
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function renderChat(messages: UnifiedChatMessage[]) {
|
|
71
|
-
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
72
|
-
return render(
|
|
73
|
-
<QueryClientProvider client={queryClient}>
|
|
74
|
-
<ChatRuntimeContext.Provider value={runtime}>
|
|
75
|
-
<DialogPrimitive.Root open>
|
|
76
|
-
<EmbeddableChat
|
|
77
|
-
shell="none"
|
|
78
|
-
defaultOpen
|
|
79
|
-
defaultActiveMode="mingo"
|
|
80
|
-
showInternalTrigger={false}
|
|
81
|
-
previewMode
|
|
82
|
-
mingoState={stateFor(messages)}
|
|
83
|
-
/>
|
|
84
|
-
</DialogPrimitive.Root>
|
|
85
|
-
</ChatRuntimeContext.Provider>
|
|
86
|
-
</QueryClientProvider>,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
beforeAll(() => {
|
|
91
|
-
vi.stubGlobal('matchMedia', () => ({
|
|
92
|
-
matches: false,
|
|
93
|
-
addEventListener: vi.fn(),
|
|
94
|
-
removeEventListener: vi.fn(),
|
|
95
|
-
}));
|
|
96
|
-
vi.stubGlobal(
|
|
97
|
-
'ResizeObserver',
|
|
98
|
-
class {
|
|
99
|
-
observe(): void {}
|
|
100
|
-
disconnect(): void {}
|
|
101
|
-
unobserve(): void {}
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
105
|
-
new Response(JSON.stringify({ commands: [] }), {
|
|
106
|
-
status: 200,
|
|
107
|
-
headers: { 'Content-Type': 'application/json' },
|
|
108
|
-
}),
|
|
109
|
-
);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
afterAll(() => {
|
|
113
|
-
vi.restoreAllMocks();
|
|
114
|
-
vi.unstubAllGlobals();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('EmbeddableChat source strip', () => {
|
|
118
|
-
it('keeps sources attached to their assistant turn after later messages', () => {
|
|
119
|
-
renderChat([
|
|
120
|
-
{
|
|
121
|
-
id: 'assistant-1',
|
|
122
|
-
role: 'assistant',
|
|
123
|
-
content: 'Use the first guide [1].',
|
|
124
|
-
sources: [
|
|
125
|
-
{
|
|
126
|
-
index: 1,
|
|
127
|
-
name: 'First turn source',
|
|
128
|
-
path: 'docs/first',
|
|
129
|
-
documentType: 'docs',
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
id: 'assistant-2',
|
|
135
|
-
role: 'assistant',
|
|
136
|
-
content: 'Use the second guide [1].',
|
|
137
|
-
sources: [
|
|
138
|
-
{
|
|
139
|
-
index: 1,
|
|
140
|
-
name: 'Second turn source',
|
|
141
|
-
path: 'docs/second',
|
|
142
|
-
documentType: 'docs',
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: 'assistant-3',
|
|
148
|
-
role: 'assistant',
|
|
149
|
-
content: 'Here is a plain follow-up answer.',
|
|
150
|
-
},
|
|
151
|
-
]);
|
|
152
|
-
|
|
153
|
-
expect(screen.getByText('[1] First turn source')).toBeInTheDocument();
|
|
154
|
-
expect(screen.getByText('[1] Second turn source')).toBeInTheDocument();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('renders live SOURCES metadata in citation order with uncited overflow', () => {
|
|
158
|
-
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
159
|
-
for (const chunk of [
|
|
160
|
-
{ type: 'MESSAGE_START', streamSeq: 1 },
|
|
161
|
-
{
|
|
162
|
-
type: 'SOURCES',
|
|
163
|
-
payload: {
|
|
164
|
-
sources: [
|
|
165
|
-
{ index: 1, name: 'First source', path: 'first', documentType: 'docs' },
|
|
166
|
-
{ index: 2, name: 'Second source', path: 'second', documentType: 'docs' },
|
|
167
|
-
{ index: 3, name: 'Third source', path: 'third', documentType: 'docs' },
|
|
168
|
-
{ index: 4, name: 'Fourth source', path: 'fourth', documentType: 'docs' },
|
|
169
|
-
],
|
|
170
|
-
videos: [
|
|
171
|
-
{
|
|
172
|
-
ref: '[card://video:mux-9b6586b494]',
|
|
173
|
-
title: 'Product overview',
|
|
174
|
-
url: 'https://stream.mux.com/product-overview.m3u8',
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
streamSeq: 2,
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
type: 'TEXT',
|
|
182
|
-
text:
|
|
183
|
-
'Compare the third source [3] with the first [1] and cite [3] once. ' +
|
|
184
|
-
'Watch [card://video:mux-9b6586b494].',
|
|
185
|
-
streamSeq: 3,
|
|
186
|
-
},
|
|
187
|
-
{ type: 'MESSAGE_END', streamSeq: 4 },
|
|
188
|
-
]) {
|
|
189
|
-
const event = decodeNatsChunk(chunk);
|
|
190
|
-
if (event) reducer.apply(event);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
renderChat(reducer.state.messages);
|
|
194
|
-
|
|
195
|
-
const chips = screen.getAllByTitle(/source$/i);
|
|
196
|
-
expect(chips.map(chip => chip.textContent)).toEqual(['[3] Third source', '[1] First source']);
|
|
197
|
-
expect(screen.queryByText('[2] Second source')).not.toBeInTheDocument();
|
|
198
|
-
const expander = screen.getByRole('button', { name: 'Show 2 additional retrieved sources' });
|
|
199
|
-
expect(expander).toHaveTextContent('+2 more retrieved sources');
|
|
200
|
-
|
|
201
|
-
fireEvent.click(expander);
|
|
202
|
-
|
|
203
|
-
expect(screen.getByText('[2] Second source')).toBeInTheDocument();
|
|
204
|
-
expect(screen.getByText('[4] Fourth source')).toBeInTheDocument();
|
|
205
|
-
expect(screen.getByTestId('mux-player')).toHaveAttribute(
|
|
206
|
-
'data-src',
|
|
207
|
-
'https://stream.mux.com/product-overview.m3u8',
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('keeps every uncited source accessible behind the top-three fallback', () => {
|
|
212
|
-
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
213
|
-
for (const chunk of [
|
|
214
|
-
{ type: 'MESSAGE_START', streamSeq: 1 },
|
|
215
|
-
{
|
|
216
|
-
type: 'SOURCES',
|
|
217
|
-
payload: {
|
|
218
|
-
sources: Array.from({ length: 5 }, (_, index) => ({
|
|
219
|
-
index: index + 1,
|
|
220
|
-
name: `Source ${index + 1}`,
|
|
221
|
-
path: `source-${index + 1}`,
|
|
222
|
-
documentType: 'docs',
|
|
223
|
-
})),
|
|
224
|
-
},
|
|
225
|
-
streamSeq: 2,
|
|
226
|
-
},
|
|
227
|
-
{ type: 'TEXT', text: 'Here are the relevant materials.', streamSeq: 3 },
|
|
228
|
-
{ type: 'MESSAGE_END', streamSeq: 4 },
|
|
229
|
-
]) {
|
|
230
|
-
const event = decodeNatsChunk(chunk);
|
|
231
|
-
if (event) reducer.apply(event);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
renderChat(reducer.state.messages);
|
|
235
|
-
|
|
236
|
-
expect(screen.getByText('[1] Source 1')).toBeInTheDocument();
|
|
237
|
-
expect(screen.getByText('[3] Source 3')).toBeInTheDocument();
|
|
238
|
-
expect(screen.queryByText('[4] Source 4')).not.toBeInTheDocument();
|
|
239
|
-
|
|
240
|
-
const expander = screen.getByRole('button', { name: 'Show 2 additional retrieved sources' });
|
|
241
|
-
fireEvent.click(expander);
|
|
242
|
-
|
|
243
|
-
expect(screen.getByText('[4] Source 4')).toBeInTheDocument();
|
|
244
|
-
expect(screen.getByText('[5] Source 5')).toBeInTheDocument();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('renders a markdown card from matching source metadata when hydration is unavailable', () => {
|
|
248
|
-
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
249
|
-
for (const chunk of [
|
|
250
|
-
{ type: 'MESSAGE_START', streamSeq: 1 },
|
|
251
|
-
{
|
|
252
|
-
type: 'SOURCES',
|
|
253
|
-
payload: {
|
|
254
|
-
sources: [
|
|
255
|
-
{
|
|
256
|
-
index: 1,
|
|
257
|
-
id: 'install-agent',
|
|
258
|
-
name: 'Install the OpenFrame agent on Windows',
|
|
259
|
-
path: 'getting-started/install-agent.md',
|
|
260
|
-
documentType: 'markdown',
|
|
261
|
-
sourceRepo: 'openframe-docs',
|
|
262
|
-
},
|
|
263
|
-
],
|
|
264
|
-
cards: [
|
|
265
|
-
{
|
|
266
|
-
ref: '[card://markdown:install-agent]',
|
|
267
|
-
entityType: 'markdown',
|
|
268
|
-
entityId: 'install-agent',
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
},
|
|
272
|
-
streamSeq: 2,
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
type: 'TEXT',
|
|
276
|
-
text: 'Follow [card://markdown:install-agent].',
|
|
277
|
-
streamSeq: 3,
|
|
278
|
-
},
|
|
279
|
-
{ type: 'MESSAGE_END', streamSeq: 4 },
|
|
280
|
-
]) {
|
|
281
|
-
const event = decodeNatsChunk(chunk);
|
|
282
|
-
if (event) reducer.apply(event);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
renderChat(reducer.state.messages);
|
|
286
|
-
|
|
287
|
-
expect(screen.getByText('Install the OpenFrame agent on Windows')).toBeInTheDocument();
|
|
288
|
-
expect(screen.queryByText('FAILED')).not.toBeInTheDocument();
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('renders a matched source card when its display name equals its id', () => {
|
|
292
|
-
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
293
|
-
for (const chunk of [
|
|
294
|
-
{ type: 'MESSAGE_START', streamSeq: 1 },
|
|
295
|
-
{
|
|
296
|
-
type: 'SOURCES',
|
|
297
|
-
payload: {
|
|
298
|
-
sources: [
|
|
299
|
-
{
|
|
300
|
-
index: 1,
|
|
301
|
-
id: 'install-agent',
|
|
302
|
-
name: 'install-agent',
|
|
303
|
-
path: 'getting-started/install-agent.md',
|
|
304
|
-
documentType: 'markdown',
|
|
305
|
-
},
|
|
306
|
-
],
|
|
307
|
-
cards: [
|
|
308
|
-
{
|
|
309
|
-
ref: '[card://markdown:install-agent]',
|
|
310
|
-
entityType: 'markdown',
|
|
311
|
-
entityId: 'install-agent',
|
|
312
|
-
},
|
|
313
|
-
],
|
|
314
|
-
},
|
|
315
|
-
streamSeq: 2,
|
|
316
|
-
},
|
|
317
|
-
{ type: 'TEXT', text: 'Follow [card://markdown:install-agent].', streamSeq: 3 },
|
|
318
|
-
{ type: 'MESSAGE_END', streamSeq: 4 },
|
|
319
|
-
]) {
|
|
320
|
-
const event = decodeNatsChunk(chunk);
|
|
321
|
-
if (event) reducer.apply(event);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
renderChat(reducer.state.messages);
|
|
325
|
-
|
|
326
|
-
expect(screen.getAllByText('install-agent').length).toBeGreaterThan(0);
|
|
327
|
-
expect(screen.queryByText('FAILED')).not.toBeInTheDocument();
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('does not treat an arbitrary enriched ref as matched source metadata', () => {
|
|
331
|
-
renderChat([
|
|
332
|
-
{
|
|
333
|
-
id: 'assistant-1',
|
|
334
|
-
role: 'assistant',
|
|
335
|
-
content: 'Follow [card://markdown:unmatched-doc].',
|
|
336
|
-
refs: [
|
|
337
|
-
{
|
|
338
|
-
type: 'markdown',
|
|
339
|
-
id: 'unmatched-doc',
|
|
340
|
-
title: 'Plausible but unmatched document',
|
|
341
|
-
url: null,
|
|
342
|
-
metadata: { path: 'docs/unmatched.md' },
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
},
|
|
346
|
-
]);
|
|
347
|
-
|
|
348
|
-
expect(screen.getByText('FAILED')).toBeInTheDocument();
|
|
349
|
-
expect(screen.queryByText('Plausible but unmatched document')).not.toBeInTheDocument();
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('opens every item carried by a grouped source', () => {
|
|
353
|
-
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
354
|
-
for (const chunk of [
|
|
355
|
-
{ type: 'MESSAGE_START', streamSeq: 1 },
|
|
356
|
-
{
|
|
357
|
-
type: 'SOURCES',
|
|
358
|
-
payload: {
|
|
359
|
-
sources: [
|
|
360
|
-
{
|
|
361
|
-
index: 1,
|
|
362
|
-
name: 'Case Studies (3 records)',
|
|
363
|
-
path: 'case-studies',
|
|
364
|
-
documentType: 'case-study',
|
|
365
|
-
items: [
|
|
366
|
-
{
|
|
367
|
-
id: 'case-study-1',
|
|
368
|
-
name: 'Northstar MSP',
|
|
369
|
-
path: 'case-studies/northstar',
|
|
370
|
-
documentType: 'case-study',
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
id: 'case-study-2',
|
|
374
|
-
name: 'Blue Harbor IT',
|
|
375
|
-
path: 'case-studies/blue-harbor',
|
|
376
|
-
documentType: 'case-study',
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
id: 'case-study-3',
|
|
380
|
-
name: 'Summit Technology',
|
|
381
|
-
path: 'case-studies/summit',
|
|
382
|
-
documentType: 'case-study',
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
],
|
|
387
|
-
},
|
|
388
|
-
streamSeq: 2,
|
|
389
|
-
},
|
|
390
|
-
{ type: 'TEXT', text: 'Here are three customer stories [1].', streamSeq: 3 },
|
|
391
|
-
{ type: 'MESSAGE_END', streamSeq: 4 },
|
|
392
|
-
]) {
|
|
393
|
-
const event = decodeNatsChunk(chunk);
|
|
394
|
-
if (event) reducer.apply(event);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
renderChat(reducer.state.messages);
|
|
398
|
-
|
|
399
|
-
const groupedSource = screen.getByText('[1] Case Studies (3 records)');
|
|
400
|
-
fireEvent.mouseEnter(groupedSource);
|
|
401
|
-
fireEvent.click(groupedSource);
|
|
402
|
-
|
|
403
|
-
expect(screen.getByText('Northstar MSP')).toBeInTheDocument();
|
|
404
|
-
expect(screen.getByText('Blue Harbor IT')).toBeInTheDocument();
|
|
405
|
-
expect(screen.getByText('Summit Technology')).toBeInTheDocument();
|
|
406
|
-
|
|
407
|
-
fireEvent.click(groupedSource);
|
|
408
|
-
|
|
409
|
-
expect(screen.queryByText('Northstar MSP')).not.toBeInTheDocument();
|
|
410
|
-
});
|
|
411
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { decodeNatsChunk } from '../../../chat-protocol/nats-decoder';
|
|
4
|
-
import { createChatStreamReducer } from '../stream/chat-stream-reducer';
|
|
5
|
-
import { ToolExecutionDisplay } from '../tool-execution-display';
|
|
6
|
-
import type { ToolExecutionData } from '../types';
|
|
7
|
-
|
|
8
|
-
const execution = (overrides: Partial<ToolExecutionData>): ToolExecutionData => ({
|
|
9
|
-
type: 'EXECUTING_TOOL',
|
|
10
|
-
integratedToolType: 'OPENFRAME',
|
|
11
|
-
toolFunction: 'search_docs',
|
|
12
|
-
...overrides,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
beforeAll(() => {
|
|
16
|
-
vi.stubGlobal(
|
|
17
|
-
'ResizeObserver',
|
|
18
|
-
class {
|
|
19
|
-
observe(): void {}
|
|
20
|
-
disconnect(): void {}
|
|
21
|
-
unobserve(): void {}
|
|
22
|
-
},
|
|
23
|
-
);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterAll(() => {
|
|
27
|
-
vi.unstubAllGlobals();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('ToolExecutionDisplay remote read activity', () => {
|
|
31
|
-
it('keeps a completed remote read concise through the real wire pipeline', () => {
|
|
32
|
-
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
33
|
-
for (const chunk of [
|
|
34
|
-
{
|
|
35
|
-
type: 'EXECUTING_TOOL',
|
|
36
|
-
integratedToolType: null,
|
|
37
|
-
toolFunction: 'remote_read',
|
|
38
|
-
title: 'Searching product documentation',
|
|
39
|
-
parameters: null,
|
|
40
|
-
toolExecutionRequestId: 'remote-read-1',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
type: 'EXECUTED_TOOL',
|
|
44
|
-
integratedToolType: null,
|
|
45
|
-
toolFunction: 'remote_read',
|
|
46
|
-
result: null,
|
|
47
|
-
success: true,
|
|
48
|
-
toolExecutionRequestId: 'remote-read-1',
|
|
49
|
-
},
|
|
50
|
-
]) {
|
|
51
|
-
const event = decodeNatsChunk(chunk);
|
|
52
|
-
if (event) reducer.apply(event);
|
|
53
|
-
}
|
|
54
|
-
const toolSegment = reducer.state.messages
|
|
55
|
-
.flatMap(message => message.segments ?? [])
|
|
56
|
-
.find(segment => segment.type === 'tool_execution');
|
|
57
|
-
|
|
58
|
-
expect(toolSegment?.type).toBe('tool_execution');
|
|
59
|
-
if (toolSegment?.type !== 'tool_execution') return;
|
|
60
|
-
render(<ToolExecutionDisplay message={toolSegment.data} />);
|
|
61
|
-
|
|
62
|
-
expect(screen.getByText('Searching product documentation')).toBeInTheDocument();
|
|
63
|
-
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
64
|
-
expect(screen.queryByText('Remote read')).not.toBeInTheDocument();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('keeps a typed local tool expandable when its body is absent', () => {
|
|
68
|
-
render(
|
|
69
|
-
<ToolExecutionDisplay
|
|
70
|
-
message={execution({
|
|
71
|
-
integratedToolType: 'OPENFRAME',
|
|
72
|
-
toolTitle: 'Refresh device inventory',
|
|
73
|
-
})}
|
|
74
|
-
/>,
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('keeps a backend-described remote read concise while running', () => {
|
|
81
|
-
render(
|
|
82
|
-
<ToolExecutionDisplay
|
|
83
|
-
message={execution({
|
|
84
|
-
integratedToolType: undefined,
|
|
85
|
-
toolTitle: 'Searching product documentation',
|
|
86
|
-
})}
|
|
87
|
-
/>,
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
expect(screen.getByText('Searching product documentation')).toBeInTheDocument();
|
|
91
|
-
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
92
|
-
});
|
|
93
|
-
});
|