@eeacms/volto-eea-chatbot 1.0.9
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/.coverage.babel.config.js +9 -0
- package/.eslintrc.js +68 -0
- package/.husky/pre-commit +2 -0
- package/.release-it.json +17 -0
- package/AGENTS.md +89 -0
- package/CHANGELOG.md +770 -0
- package/DEVELOP.md +124 -0
- package/LICENSE.md +9 -0
- package/README.md +170 -0
- package/RELEASE.md +74 -0
- package/TESTING.md +5 -0
- package/babel.config.js +17 -0
- package/bootstrap +41 -0
- package/cypress.config.js +27 -0
- package/docker-compose.yml +32 -0
- package/jest-addon.config.js +465 -0
- package/jest.setup.js +65 -0
- package/locales/de/LC_MESSAGES/volto.po +14 -0
- package/locales/en/LC_MESSAGES/volto.po +14 -0
- package/locales/it/LC_MESSAGES/volto.po +14 -0
- package/locales/ro/LC_MESSAGES/volto.po +14 -0
- package/locales/volto.pot +16 -0
- package/package.json +98 -0
- package/razzle.extend.js +40 -0
- package/src/ChatBlock/ChatBlockEdit.jsx +46 -0
- package/src/ChatBlock/ChatBlockView.jsx +21 -0
- package/src/ChatBlock/chat/AIMessage.tsx +566 -0
- package/src/ChatBlock/chat/ChatMessage.tsx +35 -0
- package/src/ChatBlock/chat/ChatWindow.tsx +288 -0
- package/src/ChatBlock/chat/UserMessage.tsx +27 -0
- package/src/ChatBlock/chat/index.ts +4 -0
- package/src/ChatBlock/components/AutoResizeTextarea.jsx +67 -0
- package/src/ChatBlock/components/BlinkingDot.tsx +3 -0
- package/src/ChatBlock/components/ChatMessageFeedback.jsx +77 -0
- package/src/ChatBlock/components/EmptyState.jsx +70 -0
- package/src/ChatBlock/components/FeedbackModal.jsx +125 -0
- package/src/ChatBlock/components/HalloumiFeedback.jsx +126 -0
- package/src/ChatBlock/components/Icon.tsx +35 -0
- package/src/ChatBlock/components/QualityCheckToggle.jsx +26 -0
- package/src/ChatBlock/components/RelatedQuestions.jsx +59 -0
- package/src/ChatBlock/components/Source.jsx +93 -0
- package/src/ChatBlock/components/SourceChip.tsx +55 -0
- package/src/ChatBlock/components/Spinner.jsx +3 -0
- package/src/ChatBlock/components/UserActionsToolbar.jsx +44 -0
- package/src/ChatBlock/components/WebResultIcon.tsx +42 -0
- package/src/ChatBlock/components/markdown/Citation.jsx +70 -0
- package/src/ChatBlock/components/markdown/ClaimModal.jsx +98 -0
- package/src/ChatBlock/components/markdown/ClaimSegments.jsx +172 -0
- package/src/ChatBlock/components/markdown/RenderClaimView.jsx +96 -0
- package/src/ChatBlock/components/markdown/colors.js +29 -0
- package/src/ChatBlock/components/markdown/colors.less +52 -0
- package/src/ChatBlock/components/markdown/colors.test.js +69 -0
- package/src/ChatBlock/components/markdown/index.js +115 -0
- package/src/ChatBlock/fonts/DejaVuSans.ttf +0 -0
- package/src/ChatBlock/hocs/withOnyxData.jsx +46 -0
- package/src/ChatBlock/hooks/index.ts +7 -0
- package/src/ChatBlock/hooks/useChatController.ts +333 -0
- package/src/ChatBlock/hooks/useChatStreaming.ts +82 -0
- package/src/ChatBlock/hooks/useDeepCompareMemoize.js +17 -0
- package/src/ChatBlock/hooks/useMarked.js +44 -0
- package/src/ChatBlock/hooks/useQualityMarkers.js +119 -0
- package/src/ChatBlock/hooks/useScrollonStream.ts +131 -0
- package/src/ChatBlock/hooks/useToolDisplayTiming.ts +80 -0
- package/src/ChatBlock/index.js +32 -0
- package/src/ChatBlock/packets/MultiToolRenderer.tsx +235 -0
- package/src/ChatBlock/packets/RendererComponent.tsx +115 -0
- package/src/ChatBlock/packets/index.ts +4 -0
- package/src/ChatBlock/packets/renderers/CustomToolRenderer.tsx +63 -0
- package/src/ChatBlock/packets/renderers/FetchToolRenderer.tsx +59 -0
- package/src/ChatBlock/packets/renderers/ImageToolRenderer.tsx +62 -0
- package/src/ChatBlock/packets/renderers/MessageTextRenderer.tsx +172 -0
- package/src/ChatBlock/packets/renderers/ReasoningRenderer.tsx +122 -0
- package/src/ChatBlock/packets/renderers/SearchToolRenderer.tsx +323 -0
- package/src/ChatBlock/packets/renderers/index.ts +6 -0
- package/src/ChatBlock/schema.js +403 -0
- package/src/ChatBlock/services/index.ts +3 -0
- package/src/ChatBlock/services/messageProcessor.ts +348 -0
- package/src/ChatBlock/services/packetUtils.ts +48 -0
- package/src/ChatBlock/services/streamingService.ts +342 -0
- package/src/ChatBlock/style.less +1881 -0
- package/src/ChatBlock/tests/AIMessage.test.jsx +95 -0
- package/src/ChatBlock/tests/AutoResizeTextarea.test.jsx +49 -0
- package/src/ChatBlock/tests/BlinkingDot.test.jsx +71 -0
- package/src/ChatBlock/tests/ChatMessageFeedback.test.jsx +73 -0
- package/src/ChatBlock/tests/Citation.test.jsx +107 -0
- package/src/ChatBlock/tests/EmptyState.test.jsx +137 -0
- package/src/ChatBlock/tests/FeedbackModal.test.jsx +138 -0
- package/src/ChatBlock/tests/HalloumiFeedback.test.jsx +94 -0
- package/src/ChatBlock/tests/QualityCheckToggle.test.jsx +105 -0
- package/src/ChatBlock/tests/RelatedQuestions.test.jsx +215 -0
- package/src/ChatBlock/tests/Source.test.jsx +79 -0
- package/src/ChatBlock/tests/Spinner.test.jsx +18 -0
- package/src/ChatBlock/tests/index.test.js +51 -0
- package/src/ChatBlock/tests/messageProcessor.test.jsx +154 -0
- package/src/ChatBlock/tests/schema.test.js +166 -0
- package/src/ChatBlock/tests/useDeepCompareMemoize.test.js +107 -0
- package/src/ChatBlock/tests/useToolDisplayTiming.test.jsx +151 -0
- package/src/ChatBlock/types/cssmodules.d.ts +7 -0
- package/src/ChatBlock/types/interfaces.ts +154 -0
- package/src/ChatBlock/types/slate.d.ts +3 -0
- package/src/ChatBlock/types/streamingModels.ts +267 -0
- package/src/ChatBlock/types/volto.d.ts +3 -0
- package/src/ChatBlock/utils/citations.ts +25 -0
- package/src/ChatBlock/utils/index.tsx +114 -0
- package/src/halloumi/README.md +1 -0
- package/src/halloumi/generative.js +219 -0
- package/src/halloumi/generative.test.js +88 -0
- package/src/halloumi/middleware.js +70 -0
- package/src/halloumi/postprocessing.js +273 -0
- package/src/halloumi/postprocessing.test.js +441 -0
- package/src/halloumi/preprocessing.js +115 -0
- package/src/halloumi/preprocessing.test.js +245 -0
- package/src/icons/bot.svg +1 -0
- package/src/icons/check.svg +1 -0
- package/src/icons/chevron.svg +3 -0
- package/src/icons/clear.svg +1 -0
- package/src/icons/copy.svg +1 -0
- package/src/icons/done.svg +5 -0
- package/src/icons/external-link.svg +1 -0
- package/src/icons/file.svg +1 -0
- package/src/icons/glasses.svg +1 -0
- package/src/icons/globe.svg +1 -0
- package/src/icons/rotate.svg +1 -0
- package/src/icons/search.svg +5 -0
- package/src/icons/send.svg +1 -0
- package/src/icons/square-pen.svg +1 -0
- package/src/icons/stop.svg +9 -0
- package/src/icons/thumbs-down.svg +1 -0
- package/src/icons/thumbs-up.svg +1 -0
- package/src/icons/user.svg +1 -0
- package/src/index.js +58 -0
- package/src/middleware.js +250 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SearchToolPacket,
|
|
3
|
+
SearchToolStart,
|
|
4
|
+
SearchToolDelta,
|
|
5
|
+
SectionEnd,
|
|
6
|
+
OnyxDocument,
|
|
7
|
+
} from '../../types/streamingModels';
|
|
8
|
+
import type { MessageRenderer } from '../../types/interfaces';
|
|
9
|
+
import { useEffect, useState, useRef, useMemo } from 'react';
|
|
10
|
+
import { PacketType } from '../../types/streamingModels';
|
|
11
|
+
import { SourceChip } from '../../components/SourceChip';
|
|
12
|
+
import { BlinkingDot } from '../../components/BlinkingDot';
|
|
13
|
+
import SVGIcon from '../../components/Icon';
|
|
14
|
+
import { WebResultIcon } from '../../components/WebResultIcon';
|
|
15
|
+
import SearchIcon from '../../../icons/search.svg';
|
|
16
|
+
import GlobeIcon from '../../../icons/globe.svg';
|
|
17
|
+
import FileIcon from '../../../icons/file.svg';
|
|
18
|
+
|
|
19
|
+
const INITIAL_RESULTS_TO_SHOW = 3;
|
|
20
|
+
const RESULTS_PER_EXPANSION = 10;
|
|
21
|
+
|
|
22
|
+
const INITIAL_QUERIES_TO_SHOW = 3;
|
|
23
|
+
const QUERIES_PER_EXPANSION = 5;
|
|
24
|
+
|
|
25
|
+
const SEARCHING_MIN_DURATION_MS = 1000; // 1 second minimum for "Searching" state
|
|
26
|
+
const SEARCHED_MIN_DURATION_MS = 1000; // 1 second minimum for "Searched" state
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* ResultIcon component that displays either a favicon for web results or a FileIcon for internal documents
|
|
30
|
+
*/
|
|
31
|
+
const ResultIcon = ({
|
|
32
|
+
doc,
|
|
33
|
+
size,
|
|
34
|
+
isInternetSearch,
|
|
35
|
+
}: {
|
|
36
|
+
doc: OnyxDocument;
|
|
37
|
+
size: number;
|
|
38
|
+
isInternetSearch: boolean;
|
|
39
|
+
}) => {
|
|
40
|
+
// Check if this is a web/internet result
|
|
41
|
+
if (doc.link && (isInternetSearch || doc.source_type === 'web')) {
|
|
42
|
+
return <WebResultIcon url={doc.link} size={size} />;
|
|
43
|
+
}
|
|
44
|
+
// For internal documents without links, use FileIcon
|
|
45
|
+
return <SVGIcon name={FileIcon} size={size} />;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const constructCurrentSearchState = (
|
|
49
|
+
packets: SearchToolPacket[],
|
|
50
|
+
): {
|
|
51
|
+
queries: string[];
|
|
52
|
+
results: OnyxDocument[];
|
|
53
|
+
isSearching: boolean;
|
|
54
|
+
isComplete: boolean;
|
|
55
|
+
isInternetSearch: boolean;
|
|
56
|
+
} => {
|
|
57
|
+
const searchStart = packets.find(
|
|
58
|
+
(packet) => packet.obj.type === PacketType.SEARCH_TOOL_START,
|
|
59
|
+
)?.obj as SearchToolStart | null;
|
|
60
|
+
|
|
61
|
+
const searchDeltas = packets
|
|
62
|
+
.filter((packet) => packet.obj.type === PacketType.SEARCH_TOOL_DELTA)
|
|
63
|
+
.map((packet) => packet.obj as SearchToolDelta);
|
|
64
|
+
|
|
65
|
+
const searchEnd = packets.find(
|
|
66
|
+
(packet) => packet.obj.type === PacketType.SECTION_END,
|
|
67
|
+
)?.obj as SectionEnd | null;
|
|
68
|
+
|
|
69
|
+
// Extract queries from ToolDelta packets
|
|
70
|
+
const queries = searchDeltas
|
|
71
|
+
.flatMap((delta) => delta?.queries || [])
|
|
72
|
+
.filter((query, index, arr) => arr.indexOf(query) === index); // Remove duplicates
|
|
73
|
+
|
|
74
|
+
const seenDocIds = new Set<string>();
|
|
75
|
+
const results = searchDeltas
|
|
76
|
+
.flatMap((delta) => delta?.documents || [])
|
|
77
|
+
.filter((doc) => {
|
|
78
|
+
if (!doc || !doc.document_id) return false;
|
|
79
|
+
if (seenDocIds.has(doc.document_id)) return false;
|
|
80
|
+
seenDocIds.add(doc.document_id);
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const isSearching = Boolean(searchStart && !searchEnd);
|
|
85
|
+
const isComplete = Boolean(searchStart && searchEnd);
|
|
86
|
+
const isInternetSearch = searchStart?.is_internet_search || false;
|
|
87
|
+
|
|
88
|
+
return { queries, results, isSearching, isComplete, isInternetSearch };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const SearchToolRenderer: MessageRenderer<SearchToolPacket> = ({
|
|
92
|
+
packets,
|
|
93
|
+
onComplete,
|
|
94
|
+
animate,
|
|
95
|
+
children,
|
|
96
|
+
}) => {
|
|
97
|
+
const { queries, results, isSearching, isComplete, isInternetSearch } =
|
|
98
|
+
constructCurrentSearchState(packets);
|
|
99
|
+
|
|
100
|
+
// Track search timing for minimum display duration
|
|
101
|
+
const [searchStartTime, setSearchStartTime] = useState<number | null>(null);
|
|
102
|
+
const [shouldShowAsSearching, setShouldShowAsSearching] = useState(false);
|
|
103
|
+
const [shouldShowAsSearched, setShouldShowAsSearched] = useState(isComplete);
|
|
104
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
105
|
+
const searchedTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
106
|
+
const completionHandledRef = useRef(false);
|
|
107
|
+
|
|
108
|
+
// Track how many results to show
|
|
109
|
+
const [resultsToShow, setResultsToShow] = useState(INITIAL_RESULTS_TO_SHOW);
|
|
110
|
+
|
|
111
|
+
// Track how many queries to show
|
|
112
|
+
const [queriesToShow, setQueriesToShow] = useState(INITIAL_QUERIES_TO_SHOW);
|
|
113
|
+
|
|
114
|
+
// Track when search starts (even if the search completes instantly)
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if ((isSearching || isComplete) && searchStartTime === null) {
|
|
117
|
+
setSearchStartTime(Date.now());
|
|
118
|
+
setShouldShowAsSearching(true);
|
|
119
|
+
}
|
|
120
|
+
}, [isSearching, isComplete, searchStartTime]);
|
|
121
|
+
|
|
122
|
+
// Handle search completion with minimum duration
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (
|
|
125
|
+
isComplete &&
|
|
126
|
+
searchStartTime !== null &&
|
|
127
|
+
!completionHandledRef.current
|
|
128
|
+
) {
|
|
129
|
+
completionHandledRef.current = true;
|
|
130
|
+
const elapsedTime = Date.now() - searchStartTime;
|
|
131
|
+
const minimumSearchingDuration = animate ? SEARCHING_MIN_DURATION_MS : 0;
|
|
132
|
+
const minimumSearchedDuration = animate ? SEARCHED_MIN_DURATION_MS : 0;
|
|
133
|
+
|
|
134
|
+
const handleSearchingToSearched = () => {
|
|
135
|
+
setShouldShowAsSearching(false);
|
|
136
|
+
setShouldShowAsSearched(true);
|
|
137
|
+
|
|
138
|
+
searchedTimeoutRef.current = setTimeout(() => {
|
|
139
|
+
setShouldShowAsSearched(false);
|
|
140
|
+
onComplete();
|
|
141
|
+
}, minimumSearchedDuration);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (elapsedTime >= minimumSearchingDuration) {
|
|
145
|
+
// Enough time has passed for searching, transition to searched immediately
|
|
146
|
+
handleSearchingToSearched();
|
|
147
|
+
} else {
|
|
148
|
+
// Not enough time has passed for searching, delay the transition
|
|
149
|
+
const remainingTime = minimumSearchingDuration - elapsedTime;
|
|
150
|
+
timeoutRef.current = setTimeout(
|
|
151
|
+
handleSearchingToSearched,
|
|
152
|
+
remainingTime,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}, [isComplete, searchStartTime, animate, onComplete]);
|
|
157
|
+
|
|
158
|
+
// Cleanup timeouts on unmount
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
return () => {
|
|
161
|
+
if (timeoutRef.current) {
|
|
162
|
+
clearTimeout(timeoutRef.current);
|
|
163
|
+
}
|
|
164
|
+
if (searchedTimeoutRef.current) {
|
|
165
|
+
clearTimeout(searchedTimeoutRef.current);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
const status = useMemo(() => {
|
|
171
|
+
const searchType = isInternetSearch ? 'the web' : 'internal documents';
|
|
172
|
+
|
|
173
|
+
// If we have documents to show and we're in the searched state, show "Searched"
|
|
174
|
+
if (results.length > 0) {
|
|
175
|
+
// If we're still showing as searching (before transition), show "Searching"
|
|
176
|
+
if (shouldShowAsSearching) {
|
|
177
|
+
return `Searching ${searchType}`;
|
|
178
|
+
}
|
|
179
|
+
// Otherwise show "Searched"
|
|
180
|
+
return `Searched ${searchType}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle states based on timing
|
|
184
|
+
if (shouldShowAsSearched) {
|
|
185
|
+
return `Searched ${searchType}`;
|
|
186
|
+
}
|
|
187
|
+
if (isSearching || isComplete || shouldShowAsSearching) {
|
|
188
|
+
return `Searching ${searchType}`;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}, [
|
|
192
|
+
isSearching,
|
|
193
|
+
isComplete,
|
|
194
|
+
shouldShowAsSearching,
|
|
195
|
+
shouldShowAsSearched,
|
|
196
|
+
results.length,
|
|
197
|
+
isInternetSearch,
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
// Determine the icon based on search type
|
|
201
|
+
const IconComponent = ({ size }: { size: number }) => (
|
|
202
|
+
<SVGIcon name={isInternetSearch ? GlobeIcon : SearchIcon} size={size} />
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Don't render anything if search hasn't started
|
|
206
|
+
if (queries.length === 0) {
|
|
207
|
+
return children({
|
|
208
|
+
icon: IconComponent,
|
|
209
|
+
status: status,
|
|
210
|
+
content: <div></div>,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return children({
|
|
215
|
+
icon: IconComponent,
|
|
216
|
+
status,
|
|
217
|
+
content: (
|
|
218
|
+
<div className="search-tool-renderer">
|
|
219
|
+
<div className="queries-section">
|
|
220
|
+
<div className="queries-header">
|
|
221
|
+
<strong>Queries</strong>
|
|
222
|
+
</div>
|
|
223
|
+
<div className="queries-list">
|
|
224
|
+
{queries.slice(0, queriesToShow).map((query, index) => (
|
|
225
|
+
<div
|
|
226
|
+
key={index}
|
|
227
|
+
className="query-item"
|
|
228
|
+
style={{
|
|
229
|
+
animationDelay: `${index * 30}ms`,
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
<SourceChip
|
|
233
|
+
icon={<SVGIcon name={SearchIcon} size={16} />}
|
|
234
|
+
title={query}
|
|
235
|
+
/>
|
|
236
|
+
</div>
|
|
237
|
+
))}
|
|
238
|
+
{queries.length > queriesToShow && (
|
|
239
|
+
<div
|
|
240
|
+
className="query-item more-button"
|
|
241
|
+
style={{
|
|
242
|
+
animationDelay: `${queriesToShow * 30}ms`,
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
<SourceChip
|
|
246
|
+
title={`${queries.length - queriesToShow} more...`}
|
|
247
|
+
onClick={() => {
|
|
248
|
+
setQueriesToShow((prevQueries) =>
|
|
249
|
+
Math.min(
|
|
250
|
+
prevQueries + QUERIES_PER_EXPANSION,
|
|
251
|
+
queries.length,
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{queries.length === 0 && <BlinkingDot />}
|
|
262
|
+
|
|
263
|
+
<div className="results-section">
|
|
264
|
+
<div className="results-header">
|
|
265
|
+
<strong>{isInternetSearch ? 'Results' : 'Documents'}</strong>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<div className="results-list">
|
|
269
|
+
{results.slice(0, resultsToShow).map((result, index) => (
|
|
270
|
+
<div
|
|
271
|
+
key={result.document_id}
|
|
272
|
+
className="result-item"
|
|
273
|
+
style={{
|
|
274
|
+
animationDelay: `${index * 30}ms`,
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
<SourceChip
|
|
278
|
+
icon={
|
|
279
|
+
<ResultIcon
|
|
280
|
+
doc={result}
|
|
281
|
+
size={16}
|
|
282
|
+
isInternetSearch={isInternetSearch}
|
|
283
|
+
/>
|
|
284
|
+
}
|
|
285
|
+
title={result.semantic_identifier || ''}
|
|
286
|
+
onClick={() => {
|
|
287
|
+
if (result.link) {
|
|
288
|
+
window.open(result.link, '_blank');
|
|
289
|
+
}
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
))}
|
|
294
|
+
{results.length > resultsToShow && (
|
|
295
|
+
<div
|
|
296
|
+
className="result-item more-button"
|
|
297
|
+
style={{
|
|
298
|
+
animationDelay: `${
|
|
299
|
+
Math.min(resultsToShow, results.length) * 30
|
|
300
|
+
}ms`,
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
303
|
+
<SourceChip
|
|
304
|
+
title={`${results.length - resultsToShow} more...`}
|
|
305
|
+
onClick={() => {
|
|
306
|
+
setResultsToShow((prevResults) =>
|
|
307
|
+
Math.min(
|
|
308
|
+
prevResults + RESULTS_PER_EXPANSION,
|
|
309
|
+
results.length,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
}}
|
|
313
|
+
/>
|
|
314
|
+
</div>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
{results.length === 0 && queries.length > 0 && <BlinkingDot />}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
),
|
|
322
|
+
});
|
|
323
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { MessageTextRenderer } from './MessageTextRenderer';
|
|
2
|
+
export { SearchToolRenderer } from './SearchToolRenderer';
|
|
3
|
+
export { ReasoningRenderer } from './ReasoningRenderer';
|
|
4
|
+
export { CustomToolRenderer } from './CustomToolRenderer';
|
|
5
|
+
export { ImageToolRenderer } from './ImageToolRenderer';
|
|
6
|
+
export { FetchToolRenderer } from './FetchToolRenderer';
|