@smart-cloud/ai-kit-ui 1.1.39 → 1.1.41
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/ai-kit-ui.css +22 -11
- package/dist/index.cjs +9 -9
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +9 -9
- package/package.json +9 -8
- package/src/ai-feature/AiFeature.tsx +63 -62
- package/src/doc-search/DocSearch.tsx +498 -0
- package/src/doc-search/index.tsx +1 -0
- package/src/i18n/ar.ts +55 -44
- package/src/i18n/de.ts +56 -45
- package/src/i18n/en.ts +56 -45
- package/src/i18n/es.ts +56 -44
- package/src/i18n/fr.ts +56 -44
- package/src/i18n/he.ts +55 -44
- package/src/i18n/hi.ts +55 -44
- package/src/i18n/hu.ts +63 -51
- package/src/i18n/id.ts +56 -44
- package/src/i18n/it.ts +56 -44
- package/src/i18n/ja.ts +56 -45
- package/src/i18n/ko.ts +55 -44
- package/src/i18n/nb.ts +56 -45
- package/src/i18n/nl.ts +56 -45
- package/src/i18n/pl.ts +57 -45
- package/src/i18n/pt.ts +55 -44
- package/src/i18n/ru.ts +56 -45
- package/src/i18n/sv.ts +56 -45
- package/src/i18n/th.ts +55 -44
- package/src/i18n/tr.ts +55 -44
- package/src/i18n/ua.ts +55 -46
- package/src/i18n/zh.ts +54 -43
- package/src/index.tsx +1 -0
- package/src/poweredBy.tsx +39 -0
- package/src/styles/ai-kit-ui.css +22 -11
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Alert,
|
|
3
|
+
Anchor,
|
|
4
|
+
Button,
|
|
5
|
+
Divider,
|
|
6
|
+
Group,
|
|
7
|
+
Loader,
|
|
8
|
+
Modal,
|
|
9
|
+
Paper,
|
|
10
|
+
Stack,
|
|
11
|
+
Text,
|
|
12
|
+
TextInput,
|
|
13
|
+
Title,
|
|
14
|
+
} from "@mantine/core";
|
|
15
|
+
import {
|
|
16
|
+
AiKitDocSearchIcon,
|
|
17
|
+
sendSearchMessage,
|
|
18
|
+
type AiKitStatusEvent,
|
|
19
|
+
type DocSearchProps,
|
|
20
|
+
type SearchResult,
|
|
21
|
+
} from "@smart-cloud/ai-kit-core";
|
|
22
|
+
import { I18n } from "aws-amplify/utils";
|
|
23
|
+
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
24
|
+
import ReactMarkdown from "react-markdown";
|
|
25
|
+
import rehypeRaw from "rehype-raw";
|
|
26
|
+
import remarkGfm from "remark-gfm";
|
|
27
|
+
|
|
28
|
+
import { IconSearch } from "@tabler/icons-react";
|
|
29
|
+
|
|
30
|
+
import { AiFeatureBorder } from "../ai-feature/AiFeatureBorder";
|
|
31
|
+
import { translations } from "../i18n";
|
|
32
|
+
import { useAiRun } from "../useAiRun";
|
|
33
|
+
import { AiKitShellInjectedProps, withAiKitShell } from "../withAiKitShell";
|
|
34
|
+
import { PoweredBy } from "../poweredBy";
|
|
35
|
+
|
|
36
|
+
I18n.putVocabularies(translations);
|
|
37
|
+
|
|
38
|
+
type Props = DocSearchProps & AiKitShellInjectedProps;
|
|
39
|
+
|
|
40
|
+
function groupChunksByDoc(result: SearchResult | null) {
|
|
41
|
+
const docs = result?.citations?.docs ?? [];
|
|
42
|
+
const chunks = result?.citations?.chunks ?? [];
|
|
43
|
+
const byDoc = new Map<
|
|
44
|
+
string,
|
|
45
|
+
{ doc: (typeof docs)[number]; chunks: typeof chunks }
|
|
46
|
+
>();
|
|
47
|
+
|
|
48
|
+
for (const doc of docs) {
|
|
49
|
+
byDoc.set(doc.docId, { doc, chunks: [] });
|
|
50
|
+
}
|
|
51
|
+
for (const ch of chunks) {
|
|
52
|
+
const cur = byDoc.get(ch.docId);
|
|
53
|
+
if (cur) {
|
|
54
|
+
cur.chunks.push(ch);
|
|
55
|
+
} else {
|
|
56
|
+
// fallback if doc list is incomplete
|
|
57
|
+
byDoc.set(ch.docId, { doc: { docId: ch.docId }, chunks: [ch] });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return Array.from(byDoc.values());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DocSearchBase: FC<Props> = (props) => {
|
|
64
|
+
const {
|
|
65
|
+
autoRun = true,
|
|
66
|
+
context,
|
|
67
|
+
title,
|
|
68
|
+
searchButtonIcon,
|
|
69
|
+
showSearchButtonTitle = true,
|
|
70
|
+
showSearchButtonIcon = true,
|
|
71
|
+
showSources = true,
|
|
72
|
+
topK = 10,
|
|
73
|
+
getSearchText,
|
|
74
|
+
|
|
75
|
+
variation,
|
|
76
|
+
rootElement,
|
|
77
|
+
colorMode,
|
|
78
|
+
language,
|
|
79
|
+
onClose,
|
|
80
|
+
onClickDoc,
|
|
81
|
+
} = props;
|
|
82
|
+
|
|
83
|
+
const [query, setQuery] = useState<string>("");
|
|
84
|
+
const { busy, error, statusEvent, result, run, cancel, reset } =
|
|
85
|
+
useAiRun<SearchResult>();
|
|
86
|
+
|
|
87
|
+
const autoRunOnceRef = useRef(false);
|
|
88
|
+
|
|
89
|
+
const sessionId = result?.sessionId;
|
|
90
|
+
const citationDocs = result?.citations?.docs ?? [];
|
|
91
|
+
const citationChunks = result?.citations?.chunks ?? [];
|
|
92
|
+
const citationAnchors = result?.citations?.anchors ?? [];
|
|
93
|
+
const summaryText = result?.result ?? "";
|
|
94
|
+
|
|
95
|
+
const buttonLeftIcon = useMemo(() => {
|
|
96
|
+
if (!showSearchButtonIcon) return undefined;
|
|
97
|
+
|
|
98
|
+
if (searchButtonIcon?.trim()) {
|
|
99
|
+
return (
|
|
100
|
+
<img
|
|
101
|
+
src={searchButtonIcon}
|
|
102
|
+
alt=""
|
|
103
|
+
style={{ width: 18, height: 18, objectFit: "contain" }}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return <IconSearch size={18} />;
|
|
108
|
+
}, [searchButtonIcon, showSearchButtonIcon]);
|
|
109
|
+
|
|
110
|
+
const defaultTitle = useMemo(() => {
|
|
111
|
+
if (language) {
|
|
112
|
+
I18n.setLanguage(language || "en");
|
|
113
|
+
}
|
|
114
|
+
return I18n.get(title || "Search with AI-Kit");
|
|
115
|
+
}, [language]);
|
|
116
|
+
|
|
117
|
+
const statusText = useMemo(() => {
|
|
118
|
+
const e: AiKitStatusEvent | null = statusEvent;
|
|
119
|
+
if (!e) return null;
|
|
120
|
+
// Keep this generic and short (backend / on-device messages differ).
|
|
121
|
+
return I18n.get("Searching…");
|
|
122
|
+
}, [language, statusEvent]);
|
|
123
|
+
|
|
124
|
+
const inputText = useMemo(() => {
|
|
125
|
+
return query || getSearchText;
|
|
126
|
+
}, [query, getSearchText]);
|
|
127
|
+
|
|
128
|
+
const canSearch = useMemo(() => {
|
|
129
|
+
if (busy) return false;
|
|
130
|
+
const text = typeof inputText === "function" ? inputText() : inputText;
|
|
131
|
+
return Boolean(text && text.trim().length > 0);
|
|
132
|
+
}, [inputText, busy]);
|
|
133
|
+
|
|
134
|
+
const onSearch = useCallback(async () => {
|
|
135
|
+
const q =
|
|
136
|
+
typeof inputText === "function"
|
|
137
|
+
? (inputText as () => string)()
|
|
138
|
+
: inputText;
|
|
139
|
+
if (!q) return;
|
|
140
|
+
setQuery(q);
|
|
141
|
+
reset();
|
|
142
|
+
await run(async ({ signal, onStatus }) => {
|
|
143
|
+
return await sendSearchMessage(
|
|
144
|
+
{ sessionId, query: q, topK },
|
|
145
|
+
{ signal, onStatus, context },
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
}, [context, inputText, run, reset, topK, sessionId]);
|
|
149
|
+
|
|
150
|
+
const close = useCallback(async () => {
|
|
151
|
+
reset();
|
|
152
|
+
onClose();
|
|
153
|
+
autoRunOnceRef.current = false;
|
|
154
|
+
}, [onClose, reset, autoRunOnceRef]);
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!autoRun || !canSearch || busy || autoRunOnceRef.current) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
autoRunOnceRef.current = true;
|
|
161
|
+
queueMicrotask(() => {
|
|
162
|
+
void onSearch();
|
|
163
|
+
});
|
|
164
|
+
}, [busy, autoRunOnceRef, canSearch, autoRun, onSearch]);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!canSearch) {
|
|
168
|
+
autoRunOnceRef.current = true;
|
|
169
|
+
}
|
|
170
|
+
}, [canSearch]);
|
|
171
|
+
|
|
172
|
+
const grouped = useMemo(() => groupChunksByDoc(result), [result]);
|
|
173
|
+
|
|
174
|
+
const docNumberMap = useMemo(() => {
|
|
175
|
+
const map = new Map<string, number>();
|
|
176
|
+
citationDocs.forEach((doc, index) => {
|
|
177
|
+
if (doc?.docId) {
|
|
178
|
+
map.set(doc.docId, index + 1);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return map;
|
|
182
|
+
}, [citationDocs]);
|
|
183
|
+
|
|
184
|
+
const chunkDocMap = useMemo(() => {
|
|
185
|
+
const map = new Map<string, string>();
|
|
186
|
+
citationChunks.forEach((chunk) => {
|
|
187
|
+
if (chunk?.chunkId && chunk?.docId) {
|
|
188
|
+
map.set(chunk.chunkId, chunk.docId);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return map;
|
|
192
|
+
}, [citationChunks]);
|
|
193
|
+
|
|
194
|
+
const annotatedSummary = useMemo(() => {
|
|
195
|
+
if (!summaryText) return "";
|
|
196
|
+
if (!citationAnchors.length || docNumberMap.size === 0) {
|
|
197
|
+
return summaryText;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const sortedAnchors = [...citationAnchors]
|
|
201
|
+
.filter(
|
|
202
|
+
(anchor) =>
|
|
203
|
+
Array.isArray(anchor?.chunkIds) && anchor?.span?.end !== undefined,
|
|
204
|
+
)
|
|
205
|
+
.sort((a, b) => {
|
|
206
|
+
const aEnd = a.span?.end ?? 0;
|
|
207
|
+
const bEnd = b.span?.end ?? 0;
|
|
208
|
+
return aEnd - bEnd;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
let cursor = 0;
|
|
212
|
+
const segments: string[] = [];
|
|
213
|
+
|
|
214
|
+
for (const anchor of sortedAnchors) {
|
|
215
|
+
const end = anchor.span?.end;
|
|
216
|
+
if (typeof end !== "number" || end < cursor) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const refs = Array.from(
|
|
221
|
+
new Set(
|
|
222
|
+
(anchor.chunkIds ?? [])
|
|
223
|
+
.map((chunkId) => (chunkId ? chunkDocMap.get(chunkId) : undefined))
|
|
224
|
+
.map((docId) => (docId ? docNumberMap.get(docId) : undefined))
|
|
225
|
+
.filter((num): num is number => typeof num === "number"),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (!refs.length) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const safeEnd = Math.min(end, summaryText.length);
|
|
234
|
+
segments.push(summaryText.slice(cursor, safeEnd));
|
|
235
|
+
segments.push(`<sup>${refs.join(",")}</sup>`);
|
|
236
|
+
cursor = safeEnd;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
segments.push(summaryText.slice(cursor));
|
|
240
|
+
return segments.join("");
|
|
241
|
+
}, [citationAnchors, summaryText]);
|
|
242
|
+
|
|
243
|
+
const RootComponent: typeof Modal.Root | typeof Group =
|
|
244
|
+
variation === "modal" ? Modal.Root : Group;
|
|
245
|
+
const ContentComponent: typeof Modal.Content | typeof Group =
|
|
246
|
+
variation === "modal" ? Modal.Content : Group;
|
|
247
|
+
const BodyComponent: typeof Modal.Body | typeof Group =
|
|
248
|
+
variation === "modal" ? Modal.Body : Group;
|
|
249
|
+
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (variation !== "modal") {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
document.body.style.overflow = "hidden";
|
|
255
|
+
document.body.onkeydown = (e: KeyboardEvent) => {
|
|
256
|
+
if (e.key === "Escape") {
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
close();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
return () => {
|
|
262
|
+
// remove overflow: hidden; from body element
|
|
263
|
+
document.body.style.overflow = "";
|
|
264
|
+
document.body.onkeydown = null;
|
|
265
|
+
};
|
|
266
|
+
}, [close, variation]);
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<RootComponent
|
|
270
|
+
opened={true}
|
|
271
|
+
className="doc-search-root"
|
|
272
|
+
onClose={close}
|
|
273
|
+
padding="md"
|
|
274
|
+
gap="md"
|
|
275
|
+
size="md"
|
|
276
|
+
portalProps={
|
|
277
|
+
variation === "modal"
|
|
278
|
+
? { target: rootElement, reuseTargetNode: true }
|
|
279
|
+
: undefined
|
|
280
|
+
}
|
|
281
|
+
data-ai-kit-theme={colorMode}
|
|
282
|
+
data-ai-kit-variation={variation}
|
|
283
|
+
>
|
|
284
|
+
{variation === "modal" && <Modal.Overlay />}
|
|
285
|
+
<ContentComponent
|
|
286
|
+
w="100%"
|
|
287
|
+
style={{
|
|
288
|
+
left: 0,
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
{variation === "modal" && (
|
|
292
|
+
<Modal.Header style={{ zIndex: 1000 }}>
|
|
293
|
+
<AiKitDocSearchIcon className="doc-search-title-icon" />
|
|
294
|
+
<Modal.Title>{I18n.get(defaultTitle)}</Modal.Title>
|
|
295
|
+
<Modal.CloseButton />
|
|
296
|
+
</Modal.Header>
|
|
297
|
+
)}
|
|
298
|
+
<BodyComponent w="100%" style={{ zIndex: 1001 }}>
|
|
299
|
+
<AiFeatureBorder
|
|
300
|
+
enabled={variation !== "modal"}
|
|
301
|
+
working={busy}
|
|
302
|
+
variation={variation}
|
|
303
|
+
>
|
|
304
|
+
<Paper shadow="sm" radius="md" p="md">
|
|
305
|
+
<Stack gap="sm">
|
|
306
|
+
{variation !== "modal" && (
|
|
307
|
+
<Title order={4} style={{ margin: 0 }}>
|
|
308
|
+
{I18n.get(defaultTitle)}
|
|
309
|
+
</Title>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
<Group gap="sm" align="flex-end" wrap="nowrap">
|
|
313
|
+
<TextInput
|
|
314
|
+
style={{ flex: 1 }}
|
|
315
|
+
value={query}
|
|
316
|
+
onChange={(e) => setQuery(e.currentTarget.value)}
|
|
317
|
+
placeholder={I18n.get("Search the documentation…")}
|
|
318
|
+
disabled={busy}
|
|
319
|
+
onKeyDown={(e) => {
|
|
320
|
+
if (e.key === "Enter" && canSearch) {
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
void onSearch();
|
|
323
|
+
}
|
|
324
|
+
}}
|
|
325
|
+
/>
|
|
326
|
+
|
|
327
|
+
<Button
|
|
328
|
+
leftSection={buttonLeftIcon}
|
|
329
|
+
onClick={() => void onSearch()}
|
|
330
|
+
disabled={!canSearch}
|
|
331
|
+
className={
|
|
332
|
+
showSearchButtonTitle
|
|
333
|
+
? "doc-search-button"
|
|
334
|
+
: "doc-search-button-no-title"
|
|
335
|
+
}
|
|
336
|
+
>
|
|
337
|
+
{showSearchButtonTitle ? I18n.get("Search") : null}
|
|
338
|
+
</Button>
|
|
339
|
+
|
|
340
|
+
{busy ? (
|
|
341
|
+
<Button variant="light" color="red" onClick={cancel}>
|
|
342
|
+
{I18n.get("Stop")}
|
|
343
|
+
</Button>
|
|
344
|
+
) : null}
|
|
345
|
+
</Group>
|
|
346
|
+
|
|
347
|
+
{error ? (
|
|
348
|
+
<Alert color="red" title={I18n.get("Error")}>
|
|
349
|
+
{error}
|
|
350
|
+
</Alert>
|
|
351
|
+
) : null}
|
|
352
|
+
|
|
353
|
+
{busy && statusText && (
|
|
354
|
+
<AiFeatureBorder
|
|
355
|
+
enabled={variation === "modal"}
|
|
356
|
+
working={true}
|
|
357
|
+
variation={variation}
|
|
358
|
+
>
|
|
359
|
+
<Group
|
|
360
|
+
justify="center"
|
|
361
|
+
align="center"
|
|
362
|
+
gap="sm"
|
|
363
|
+
m="sm"
|
|
364
|
+
pr="lg"
|
|
365
|
+
>
|
|
366
|
+
<Loader size="sm" />
|
|
367
|
+
<Text size="sm" c="dimmed">
|
|
368
|
+
{statusText}
|
|
369
|
+
</Text>
|
|
370
|
+
</Group>
|
|
371
|
+
</AiFeatureBorder>
|
|
372
|
+
)}
|
|
373
|
+
|
|
374
|
+
{result?.result ? (
|
|
375
|
+
<>
|
|
376
|
+
<Divider />
|
|
377
|
+
<Stack gap="xs" data-doc-search-result>
|
|
378
|
+
<Text size="sm" c="dimmed" data-doc-search-result-title>
|
|
379
|
+
{I18n.get("AI Summary")}
|
|
380
|
+
</Text>
|
|
381
|
+
<ReactMarkdown
|
|
382
|
+
remarkPlugins={[remarkGfm]}
|
|
383
|
+
rehypePlugins={[rehypeRaw]}
|
|
384
|
+
data-doc-search-result-content
|
|
385
|
+
>
|
|
386
|
+
{annotatedSummary || summaryText}
|
|
387
|
+
</ReactMarkdown>
|
|
388
|
+
</Stack>
|
|
389
|
+
</>
|
|
390
|
+
) : null}
|
|
391
|
+
|
|
392
|
+
{showSources &&
|
|
393
|
+
(result?.citations?.docs?.length ||
|
|
394
|
+
result?.citations?.chunks?.length) ? (
|
|
395
|
+
<>
|
|
396
|
+
<Divider />
|
|
397
|
+
<Stack gap="sm" data-doc-search-sources>
|
|
398
|
+
<Text size="sm" c="dimmed" data-doc-search-sources-title>
|
|
399
|
+
{I18n.get("Sources")}
|
|
400
|
+
</Text>
|
|
401
|
+
|
|
402
|
+
{grouped.map(({ doc }) => {
|
|
403
|
+
const href = doc.sourceUrl?.trim() || undefined;
|
|
404
|
+
const docNumber = doc.docId
|
|
405
|
+
? docNumberMap.get(doc.docId)
|
|
406
|
+
: undefined;
|
|
407
|
+
const titleText = doc.title?.trim() || doc.docId;
|
|
408
|
+
const titleNode = (
|
|
409
|
+
<Text fw={600} style={{ display: "inline" }}>
|
|
410
|
+
{docNumber ? `${docNumber}. ` : ""}
|
|
411
|
+
{titleText}
|
|
412
|
+
</Text>
|
|
413
|
+
);
|
|
414
|
+
return (
|
|
415
|
+
<Paper key={doc.docId} withBorder radius="md" p="sm">
|
|
416
|
+
<Stack gap="xs">
|
|
417
|
+
<Group justify="space-between" align="flex-start">
|
|
418
|
+
<Stack
|
|
419
|
+
gap={2}
|
|
420
|
+
style={{ flex: 1 }}
|
|
421
|
+
data-doc-search-source
|
|
422
|
+
>
|
|
423
|
+
{href ? (
|
|
424
|
+
<Anchor
|
|
425
|
+
href={href}
|
|
426
|
+
target="_blank"
|
|
427
|
+
rel="noreferrer"
|
|
428
|
+
style={{ textDecoration: "none" }}
|
|
429
|
+
onClick={(e) => {
|
|
430
|
+
if (!onClickDoc) return;
|
|
431
|
+
e.preventDefault();
|
|
432
|
+
onClickDoc?.(doc);
|
|
433
|
+
}}
|
|
434
|
+
data-doc-search-source-title
|
|
435
|
+
>
|
|
436
|
+
{titleNode}
|
|
437
|
+
</Anchor>
|
|
438
|
+
) : (
|
|
439
|
+
titleNode
|
|
440
|
+
)}
|
|
441
|
+
<Anchor
|
|
442
|
+
href={href}
|
|
443
|
+
target="_blank"
|
|
444
|
+
rel="noreferrer"
|
|
445
|
+
style={{ textDecoration: "none" }}
|
|
446
|
+
onClick={(e) => {
|
|
447
|
+
if (!onClickDoc) return;
|
|
448
|
+
e.preventDefault();
|
|
449
|
+
onClickDoc?.(doc);
|
|
450
|
+
}}
|
|
451
|
+
data-doc-search-source-url
|
|
452
|
+
>
|
|
453
|
+
{doc.sourceUrl}
|
|
454
|
+
</Anchor>
|
|
455
|
+
{doc.author ? (
|
|
456
|
+
<Text
|
|
457
|
+
size="xs"
|
|
458
|
+
c="dimmed"
|
|
459
|
+
data-doc-search-source-author
|
|
460
|
+
>
|
|
461
|
+
{doc.author}
|
|
462
|
+
</Text>
|
|
463
|
+
) : null}
|
|
464
|
+
{doc.description ? (
|
|
465
|
+
<Text
|
|
466
|
+
size="sm"
|
|
467
|
+
c="dimmed"
|
|
468
|
+
fs="italic"
|
|
469
|
+
data-doc-search-source-description
|
|
470
|
+
>
|
|
471
|
+
{doc.description}
|
|
472
|
+
</Text>
|
|
473
|
+
) : null}
|
|
474
|
+
</Stack>
|
|
475
|
+
</Group>
|
|
476
|
+
</Stack>
|
|
477
|
+
</Paper>
|
|
478
|
+
);
|
|
479
|
+
})}
|
|
480
|
+
</Stack>
|
|
481
|
+
</>
|
|
482
|
+
) : null}
|
|
483
|
+
{!busy && !error && !result?.result ? (
|
|
484
|
+
<Text size="sm" c="dimmed" data-doc-search-no-results>
|
|
485
|
+
{I18n.get("Enter a search query to start.")}
|
|
486
|
+
</Text>
|
|
487
|
+
) : null}
|
|
488
|
+
<PoweredBy variation={variation} />
|
|
489
|
+
</Stack>
|
|
490
|
+
</Paper>
|
|
491
|
+
</AiFeatureBorder>
|
|
492
|
+
</BodyComponent>
|
|
493
|
+
</ContentComponent>
|
|
494
|
+
</RootComponent>
|
|
495
|
+
);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export const DocSearch = withAiKitShell(DocSearchBase);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DocSearch } from "./DocSearch";
|