@smart-cloud/ai-kit-ui 1.3.5 → 1.3.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smart-cloud/ai-kit-ui",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "@emotion/cache": "^11.14.0",
21
21
  "@emotion/react": "^11.14.0",
22
22
  "@mantine/colors-generator": "^8.3.15",
23
- "@smart-cloud/ai-kit-core": "^1.3.3",
23
+ "@smart-cloud/ai-kit-core": "^1.3.6",
24
24
  "@smart-cloud/wpsuite-core": "^2.2.6",
25
25
  "@tabler/icons-react": "^3.36.1",
26
26
  "chroma-js": "^3.2.0",
@@ -2,10 +2,13 @@ import {
2
2
  Alert,
3
3
  Anchor,
4
4
  Button,
5
+ Checkbox,
6
+ Collapse,
5
7
  Divider,
6
8
  Group,
7
9
  Loader,
8
10
  Modal,
11
+ MultiSelect,
9
12
  Paper,
10
13
  Progress,
11
14
  Stack,
@@ -15,6 +18,9 @@ import {
15
18
  } from "@mantine/core";
16
19
  import {
17
20
  AiKitDocSearchIcon,
21
+ CapabilityDecision,
22
+ dispatchBackend,
23
+ resolveBackend,
18
24
  sendSearchMessage,
19
25
  type AiKitStatusEvent,
20
26
  type DocSearchProps,
@@ -26,14 +32,20 @@ import ReactMarkdown from "react-markdown";
26
32
  import rehypeRaw from "rehype-raw";
27
33
  import remarkGfm from "remark-gfm";
28
34
 
29
- import { IconSearch } from "@tabler/icons-react";
30
- import { IconMicrophone, IconMicrophoneOff } from "@tabler/icons-react";
35
+ import {
36
+ IconChevronDown,
37
+ IconChevronRight,
38
+ IconMicrophone,
39
+ IconMicrophoneOff,
40
+ IconSearch,
41
+ IconX,
42
+ } from "@tabler/icons-react";
31
43
 
32
44
  import { AiFeatureBorder } from "../ai-feature/AiFeatureBorder";
33
45
  import { translations } from "../i18n";
46
+ import { PoweredBy } from "../poweredBy";
34
47
  import { useAiRun } from "../useAiRun";
35
48
  import { AiKitShellInjectedProps, withAiKitShell } from "../withAiKitShell";
36
- import { PoweredBy } from "../poweredBy";
37
49
 
38
50
  I18n.putVocabularies(translations);
39
51
 
@@ -69,12 +81,20 @@ const DocSearchBase: FC<Props> = (props) => {
69
81
  autoRun = true,
70
82
  context,
71
83
  title,
84
+ showOpenButton = false,
85
+ openButtonTitle,
86
+ showOpenButtonTitle = true,
87
+ openButtonIcon,
88
+ showOpenButtonIcon = true,
72
89
  searchButtonIcon,
73
90
  showSearchButtonTitle = true,
74
91
  showSearchButtonIcon = true,
75
92
  showSources = true,
76
93
  topK = 10,
77
94
  getSearchText,
95
+ enableUserFilters = false,
96
+ availableCategories,
97
+ availableTags,
78
98
 
79
99
  variation,
80
100
  rootElement,
@@ -82,17 +102,10 @@ const DocSearchBase: FC<Props> = (props) => {
82
102
  language,
83
103
  onClose,
84
104
  onClickDoc,
85
-
86
- // Open button props (from AiWorkerProps)
87
- showOpenButton = false,
88
- openButtonTitle,
89
- openButtonIcon,
90
- showOpenButtonTitle = true,
91
- showOpenButtonIcon = true,
92
105
  } = props;
93
106
 
94
- const [query, setQuery] = useState<string>("");
95
107
  const [featureOpen, setFeatureOpen] = useState<boolean>(!showOpenButton);
108
+ const [query, setQuery] = useState<string>("");
96
109
  const [recording, setRecording] = useState<boolean>(false);
97
110
  const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
98
111
  const [audioLevel, setAudioLevel] = useState<number>(0);
@@ -100,6 +113,20 @@ const DocSearchBase: FC<Props> = (props) => {
100
113
  const audioChunksRef = useRef<Blob[]>([]);
101
114
  const audioContextRef = useRef<AudioContext | null>(null);
102
115
  const analyserRef = useRef<AnalyserNode | null>(null);
116
+
117
+ // User filter states
118
+ const [filtersOpen, setFiltersOpen] = useState(false);
119
+ const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
120
+ const [selectedSubcategories, setSelectedSubcategories] = useState<string[]>(
121
+ [],
122
+ );
123
+ const [selectedTags, setSelectedTags] = useState<string[]>([]);
124
+ const [tagSearchValue, setTagSearchValue] = useState<string>("");
125
+ const [metadataOptions, setMetadataOptions] = useState<{
126
+ allowedCategories: Record<string, string[]>;
127
+ allowedTags: string[];
128
+ } | null>(null);
129
+ const [loadingMetadata, setLoadingMetadata] = useState(false);
103
130
  const animationFrameRef = useRef<number | null>(null);
104
131
 
105
132
  // Audio cache: store uploaded audio to avoid re-uploading within session
@@ -142,10 +169,6 @@ const DocSearchBase: FC<Props> = (props) => {
142
169
  return I18n.get(title || "Search with AI-Kit");
143
170
  }, [language]);
144
171
 
145
- const getOpenButtonDefaultIcon = useCallback((className?: string) => {
146
- return <IconSearch className={className} size={18} />;
147
- }, []);
148
-
149
172
  const statusText = useMemo(() => {
150
173
  const e: AiKitStatusEvent | null = statusEvent;
151
174
  if (!e) return null;
@@ -159,10 +182,11 @@ const DocSearchBase: FC<Props> = (props) => {
159
182
 
160
183
  const canSearch = useMemo(() => {
161
184
  if (busy) return false;
185
+ if (!featureOpen) return false;
162
186
  // Can search if we have text OR audio
163
187
  const text = typeof inputText === "function" ? inputText() : inputText;
164
188
  return Boolean((text && text.trim().length > 0) || audioBlob);
165
- }, [inputText, busy, audioBlob]);
189
+ }, [inputText, busy, audioBlob, featureOpen]);
166
190
 
167
191
  const startRecording = useCallback(async () => {
168
192
  try {
@@ -270,6 +294,68 @@ const DocSearchBase: FC<Props> = (props) => {
270
294
  };
271
295
  }, []);
272
296
 
297
+ // Load metadata options when user filters are enabled
298
+ useEffect(() => {
299
+ if (!enableUserFilters) return;
300
+
301
+ // Use provided options if available
302
+ if (availableCategories || availableTags) {
303
+ setMetadataOptions({
304
+ allowedCategories: availableCategories || {},
305
+ allowedTags: availableTags || [],
306
+ });
307
+ return;
308
+ }
309
+
310
+ // Otherwise fetch from backend
311
+ const loadMetadata = async () => {
312
+ setLoadingMetadata(true);
313
+ try {
314
+ const backend = await resolveBackend();
315
+
316
+ if (!backend.available) {
317
+ console.error("Backend not available for metadata options");
318
+ return;
319
+ }
320
+
321
+ const decision: CapabilityDecision = {
322
+ feature: "prompt",
323
+ source: "backend",
324
+ mode: "backend-only",
325
+ onDeviceAvailable: false,
326
+ backendAvailable: backend.available,
327
+ backendTransport: backend.transport,
328
+ backendApiName: backend.apiName,
329
+ backendBaseUrl: backend.baseUrl,
330
+ reason: backend.reason ?? "",
331
+ };
332
+
333
+ const data = (await dispatchBackend(
334
+ decision,
335
+ context ?? "frontend",
336
+ "/kb/metadata-options",
337
+ "GET",
338
+ null,
339
+ {},
340
+ )) as {
341
+ allowedCategories: Record<string, string[]>;
342
+ allowedTags: string[];
343
+ };
344
+
345
+ setMetadataOptions({
346
+ allowedCategories: data.allowedCategories || {},
347
+ allowedTags: data.allowedTags || [],
348
+ });
349
+ } catch (error) {
350
+ console.error("Failed to load metadata options:", error);
351
+ } finally {
352
+ setLoadingMetadata(false);
353
+ }
354
+ };
355
+
356
+ void loadMetadata();
357
+ }, [enableUserFilters, availableCategories, availableTags, context]);
358
+
273
359
  const onSearch = useCallback(async () => {
274
360
  let q: string | undefined;
275
361
 
@@ -318,16 +404,39 @@ const DocSearchBase: FC<Props> = (props) => {
318
404
  ...(q && { query: q }),
319
405
  ...(audioBlob && { audio: audioBlob }), // Pass Blob directly
320
406
  topK,
407
+ // Include user-selected filters if enabled
408
+ ...(enableUserFilters &&
409
+ selectedCategories.length > 0 && {
410
+ userSelectedCategories: selectedCategories,
411
+ }),
412
+ ...(enableUserFilters &&
413
+ selectedSubcategories.length > 0 && {
414
+ userSelectedSubcategories: selectedSubcategories,
415
+ }),
416
+ ...(enableUserFilters &&
417
+ selectedTags.length > 0 && { userSelectedTags: selectedTags }),
321
418
  },
322
419
  { signal, onStatus, context },
323
420
  );
324
421
  });
325
- }, [context, inputText, audioBlob, run, reset, topK, sessionId]);
422
+ }, [
423
+ context,
424
+ inputText,
425
+ audioBlob,
426
+ run,
427
+ reset,
428
+ topK,
429
+ sessionId,
430
+ enableUserFilters,
431
+ selectedCategories,
432
+ selectedSubcategories,
433
+ selectedTags,
434
+ ]);
326
435
 
327
436
  const close = useCallback(async () => {
328
437
  setFeatureOpen(false);
329
438
  reset();
330
- autoRunOnceRef.current = false;
439
+ //autoRunOnceRef.current = false;
331
440
  if (!showOpenButton) {
332
441
  onClose();
333
442
  }
@@ -443,7 +552,7 @@ const DocSearchBase: FC<Props> = (props) => {
443
552
  document.body.style.overflow = "";
444
553
  document.body.onkeydown = null;
445
554
  };
446
- }, [close, variation]);
555
+ }, [close, variation, featureOpen]);
447
556
 
448
557
  return (
449
558
  <>
@@ -454,13 +563,13 @@ const DocSearchBase: FC<Props> = (props) => {
454
563
  (openButtonIcon ? (
455
564
  <span dangerouslySetInnerHTML={{ __html: openButtonIcon }} />
456
565
  ) : (
457
- getOpenButtonDefaultIcon()
566
+ <IconSearch size={18} />
458
567
  ))
459
568
  }
460
569
  className={
461
570
  showOpenButtonTitle
462
- ? "doc-search-button"
463
- : "doc-search-button-no-title"
571
+ ? "ai-feature-open-button"
572
+ : "ai-feature-open-button-no-title"
464
573
  }
465
574
  variant={"filled"}
466
575
  disabled={featureOpen}
@@ -492,6 +601,8 @@ const DocSearchBase: FC<Props> = (props) => {
492
601
  w="100%"
493
602
  style={{
494
603
  left: 0,
604
+ ...(variation === "modal" &&
605
+ !result?.result && { overflow: "visible" }),
495
606
  }}
496
607
  >
497
608
  {variation === "modal" && (
@@ -501,7 +612,14 @@ const DocSearchBase: FC<Props> = (props) => {
501
612
  <Modal.CloseButton />
502
613
  </Modal.Header>
503
614
  )}
504
- <BodyComponent w="100%" style={{ zIndex: 1001 }}>
615
+ <BodyComponent
616
+ w="100%"
617
+ style={{
618
+ zIndex: 1001,
619
+ ...(variation === "modal" &&
620
+ !result?.result && { overflow: "visible" }),
621
+ }}
622
+ >
505
623
  <AiFeatureBorder
506
624
  enabled={variation !== "modal"}
507
625
  working={busy}
@@ -510,9 +628,37 @@ const DocSearchBase: FC<Props> = (props) => {
510
628
  <Paper shadow="sm" radius="md" p="md">
511
629
  <Stack gap="sm">
512
630
  {variation !== "modal" && (
513
- <Title order={4} style={{ margin: 0 }}>
514
- {I18n.get(defaultTitle)}
515
- </Title>
631
+ <Group justify="space-between">
632
+ <Title order={4} style={{ margin: 0 }}>
633
+ {I18n.get(defaultTitle)}
634
+ </Title>
635
+ {showOpenButton && (
636
+ <Button
637
+ variant="subtle"
638
+ color="gray"
639
+ size="xs"
640
+ onClick={close}
641
+ style={{
642
+ minWidth: 32,
643
+ padding: 0,
644
+ display: "flex",
645
+ alignItems: "center",
646
+ justifyContent: "center",
647
+ }}
648
+ aria-label={I18n.get("Close")}
649
+ >
650
+ <IconX
651
+ size={18}
652
+ style={{
653
+ color:
654
+ colorMode === "dark"
655
+ ? "var(--mantine-color-dark-1)"
656
+ : "var(--mantine-color-gray-7)",
657
+ }}
658
+ />
659
+ </Button>
660
+ )}
661
+ </Group>
516
662
  )}
517
663
 
518
664
  <Group gap="sm" align="flex-end" wrap="nowrap">
@@ -606,6 +752,151 @@ const DocSearchBase: FC<Props> = (props) => {
606
752
  ) : null}
607
753
  </Group>
608
754
 
755
+ {/* Empty state */}
756
+ {!busy && !error && !result?.result ? (
757
+ <Text size="sm" c="dimmed" data-doc-search-no-results>
758
+ {I18n.get("Enter a search query to start.")}
759
+ </Text>
760
+ ) : null}
761
+
762
+ {/* User filter collapse */}
763
+ {enableUserFilters && metadataOptions && (
764
+ <Stack gap="xs">
765
+ <Button
766
+ variant="subtle"
767
+ size="xs"
768
+ onClick={() => setFiltersOpen(!filtersOpen)}
769
+ leftSection={
770
+ filtersOpen ? (
771
+ <IconChevronDown size={14} />
772
+ ) : (
773
+ <IconChevronRight size={14} />
774
+ )
775
+ }
776
+ style={{ alignSelf: "flex-start" }}
777
+ >
778
+ {I18n.get("Filters")}
779
+ </Button>
780
+
781
+ <Collapse in={filtersOpen}>
782
+ <Stack gap="md">
783
+ {/* Main categories as checkboxes */}
784
+ {Object.keys(metadataOptions.allowedCategories)
785
+ .length > 0 && (
786
+ <div>
787
+ <Text size="sm" fw={500} mb="xs">
788
+ {I18n.get("Categories")}
789
+ </Text>
790
+ <Group gap="md">
791
+ {Object.keys(
792
+ metadataOptions.allowedCategories,
793
+ ).map((category) => (
794
+ <Checkbox
795
+ key={category}
796
+ label={category}
797
+ checked={selectedCategories.includes(
798
+ category,
799
+ )}
800
+ onChange={(e) => {
801
+ if (e.currentTarget.checked) {
802
+ setSelectedCategories([
803
+ ...selectedCategories,
804
+ category,
805
+ ]);
806
+ } else {
807
+ setSelectedCategories(
808
+ selectedCategories.filter(
809
+ (c) => c !== category,
810
+ ),
811
+ );
812
+ // Remove subcategories of unchecked category
813
+ const subcatsToRemove =
814
+ metadataOptions.allowedCategories[
815
+ category
816
+ ] || [];
817
+ setSelectedSubcategories(
818
+ selectedSubcategories.filter(
819
+ (sc) =>
820
+ !subcatsToRemove.includes(sc),
821
+ ),
822
+ );
823
+ }
824
+ }}
825
+ disabled={busy || loadingMetadata}
826
+ />
827
+ ))}
828
+ </Group>
829
+ </div>
830
+ )}
831
+
832
+ {/* Subcategories for selected categories */}
833
+ {selectedCategories.length > 0 && (
834
+ <div>
835
+ <Text size="sm" fw={500} mb="xs">
836
+ {I18n.get("Subcategories")}
837
+ </Text>
838
+ <Group gap="md">
839
+ {selectedCategories
840
+ .flatMap(
841
+ (cat) =>
842
+ metadataOptions.allowedCategories[
843
+ cat
844
+ ] || [],
845
+ )
846
+ .filter(
847
+ (subcat, index, self) =>
848
+ self.indexOf(subcat) === index,
849
+ )
850
+ .map((subcat) => (
851
+ <Checkbox
852
+ key={subcat}
853
+ label={subcat}
854
+ checked={selectedSubcategories.includes(
855
+ subcat,
856
+ )}
857
+ onChange={(e) => {
858
+ if (e.currentTarget.checked) {
859
+ setSelectedSubcategories([
860
+ ...selectedSubcategories,
861
+ subcat,
862
+ ]);
863
+ } else {
864
+ setSelectedSubcategories(
865
+ selectedSubcategories.filter(
866
+ (sc) => sc !== subcat,
867
+ ),
868
+ );
869
+ }
870
+ }}
871
+ disabled={busy || loadingMetadata}
872
+ />
873
+ ))}
874
+ </Group>
875
+ </div>
876
+ )}
877
+
878
+ {/* Tags input */}
879
+ {metadataOptions.allowedTags.length > 0 && (
880
+ <MultiSelect
881
+ label={I18n.get("Tags")}
882
+ placeholder={I18n.get("Select or type tags...")}
883
+ data={metadataOptions.allowedTags}
884
+ value={selectedTags}
885
+ onChange={setSelectedTags}
886
+ searchValue={tagSearchValue}
887
+ onSearchChange={setTagSearchValue}
888
+ disabled={busy || loadingMetadata}
889
+ searchable
890
+ clearable
891
+ maxDropdownHeight={200}
892
+ limit={20}
893
+ />
894
+ )}
895
+ </Stack>
896
+ </Collapse>
897
+ </Stack>
898
+ )}
899
+
609
900
  {
610
901
  /* Audio level indicator when recording */ USE_AUDIO && (
611
902
  <>
@@ -793,11 +1084,6 @@ const DocSearchBase: FC<Props> = (props) => {
793
1084
  </Stack>
794
1085
  </>
795
1086
  ) : null}
796
- {!busy && !error && !result?.result ? (
797
- <Text size="sm" c="dimmed" data-doc-search-no-results>
798
- {I18n.get("Enter a search query to start.")}
799
- </Text>
800
- ) : null}
801
1087
  <PoweredBy variation={variation} />
802
1088
  </Stack>
803
1089
  </Paper>
package/src/i18n/ar.ts CHANGED
@@ -20,19 +20,20 @@ export const arDict: Record<string, string> = {
20
20
  "Ask me": "اسألني",
21
21
  Assistant: "المساعد",
22
22
  "Assistant is thinking…": "المساعد يفكر…",
23
- "Audio message": "رسالة صوتية",
24
- "Audio no longer available": "لم يعد الصوت متاحًا",
25
- "Audio recorded": "تم تسجيل الصوت",
23
+ "Audio message": "",
24
+ "Audio no longer available": "",
25
+ "Audio recorded": "",
26
26
  auto: "آلي",
27
27
  "Auto-detect": "الكشف التلقائي",
28
28
  Cancel: "يلغي",
29
29
  Caption: "التسمية التوضيحية",
30
30
  Casual: "غير رسمي",
31
31
  casual: "غير رسمي",
32
+ Categories: "الفئات",
32
33
  "Checking capabilities…": "إمكانيات الفحص…",
33
34
  Chinese: "الصينية",
34
- Clear: "مسح",
35
- "Clear audio": "مسح الصوت",
35
+ Clear: "",
36
+ "Clear audio": "",
36
37
  "Click again to confirm": "انقر مرة أخرى للتأكيد",
37
38
  Close: "يغلق",
38
39
  "Close chat": "إغلاق الدردشة",
@@ -57,6 +58,7 @@ export const arDict: Record<string, string> = {
57
58
  "Enter a search query to start.": "أدخل عبارة بحث للبدء.",
58
59
  Error: "خطأ",
59
60
  Excerpt: "مقتطفات",
61
+ Filters: "عوامل التصفية",
60
62
  Formal: "رَسمِيّ",
61
63
  formal: "رَسمِيّ",
62
64
  French: "فرنسي",
@@ -77,7 +79,7 @@ export const arDict: Record<string, string> = {
77
79
  HTML: "HTML",
78
80
  Hungarian: "المجرية",
79
81
  "I'm ready to assist you.": "أنا جاهز لمساعدتك.",
80
- "Image no longer available": "لم تعد الصورة متاحة",
82
+ "Image no longer available": "",
81
83
  Indonesian: "إندونيسيا",
82
84
  "Initializing on-device AI…": "جارٍ تهيئة الذكاء الاصطناعي على الجهاز…",
83
85
  "Inlining images as base64": "تضمين الصور بصيغة base64",
@@ -129,9 +131,10 @@ export const arDict: Record<string, string> = {
129
131
  "Ready.": "جاهز.",
130
132
  "Received backend response.": "تم استلام رد من الخادم الخلفي.",
131
133
  "Receiving response...": "جارٍ استلام الرد...",
132
- Record: "تسجيل",
133
- "Record audio": "تسجيل صوت",
134
- "Recording...": "جارٍ التسجيل…",
134
+ Record: "",
135
+ "Record audio": "",
136
+ "Recorded audio:": "صوت مسجّل:",
137
+ "Recording...": "",
135
138
  Reference: "مرجع",
136
139
  References: "المراجع",
137
140
  Regenerate: "تجديد",
@@ -153,6 +156,7 @@ export const arDict: Record<string, string> = {
153
156
  "Search the documentation…": "ابحث في الوثائق…",
154
157
  "Search with AI-Kit": "البحث باستخدام مجموعة أدوات الذكاء الاصطناعي",
155
158
  "Searching…": "جارٍ البحث…",
159
+ "Select or type tags…": "اختر أو اكتب الوسوم…",
156
160
  Send: "إرسال",
157
161
  "Sending request to backend…": "إرسال الطلب إلى الواجهة الخلفية…",
158
162
  "Sending request to server...": "جارٍ إرسال الطلب إلى الخادم...",
@@ -165,13 +169,15 @@ export const arDict: Record<string, string> = {
165
169
  Sources: "مصادر",
166
170
  Spanish: "الأسبانية",
167
171
  Stop: "قف",
168
- "Stop recording": "إيقاف التسجيل",
172
+ "Stop recording": "",
173
+ Subcategories: "الفئات الفرعية",
169
174
  "Suggested change": "التغيير المقترح",
170
175
  Summarize: "لخص",
171
176
  "Summarize again": "أعد التلخيص",
172
177
  "Summarize on Backend": "تلخيص على الواجهة الخلفية",
173
178
  Summarizing: "ملخص",
174
179
  Swedish: "السويدية",
180
+ Tags: "الوسوم",
175
181
  Teaser: "إعلان تشويقي",
176
182
  teaser: "إعلان تشويقي",
177
183
  Thai: "تايلاندي",
package/src/i18n/de.ts CHANGED
@@ -20,19 +20,20 @@ export const deDict: Record<string, string> = {
20
20
  "Ask me": "Frag mich",
21
21
  Assistant: "Assistent",
22
22
  "Assistant is thinking…": "Der Assistent denkt nach…",
23
- "Audio message": "Sprachnachricht",
24
- "Audio no longer available": "Audio nicht mehr verfügbar",
25
- "Audio recorded": "Audio aufgenommen",
23
+ "Audio message": "",
24
+ "Audio no longer available": "",
25
+ "Audio recorded": "",
26
26
  auto: "automatisch",
27
27
  "Auto-detect": "Automatisch erkennen",
28
28
  Cancel: "Abbrechen",
29
29
  Caption: "Bildunterschrift",
30
30
  Casual: "Locker",
31
31
  casual: "locker",
32
+ Categories: "Kategorien",
32
33
  "Checking capabilities…": "Fähigkeiten werden geprüft…",
33
34
  Chinese: "Chinesisch",
34
- Clear: "Löschen",
35
- "Clear audio": "Audio löschen",
35
+ Clear: "",
36
+ "Clear audio": "",
36
37
  "Click again to confirm": "Zum Bestätigen erneut klicken",
37
38
  Close: "Schließen",
38
39
  "Close chat": "Chat schließen",
@@ -57,6 +58,7 @@ export const deDict: Record<string, string> = {
57
58
  "Enter a search query to start.": "Geben Sie zunächst eine Suchanfrage ein.",
58
59
  Error: "Fehler",
59
60
  Excerpt: "Auszug",
61
+ Filters: "Filter",
60
62
  Formal: "Formell",
61
63
  formal: "formell",
62
64
  French: "Französisch",
@@ -77,7 +79,7 @@ export const deDict: Record<string, string> = {
77
79
  HTML: "HTML",
78
80
  Hungarian: "Ungarisch",
79
81
  "I'm ready to assist you.": "Ich bin bereit, dir zu helfen.",
80
- "Image no longer available": "Bild nicht mehr verfügbar",
82
+ "Image no longer available": "",
81
83
  Indonesian: "Indonesisch",
82
84
  "Initializing on-device AI…": "On-Device-KI wird initialisiert…",
83
85
  "Inlining images as base64": "Bilder werden als Base64 eingebettet",
@@ -129,9 +131,10 @@ export const deDict: Record<string, string> = {
129
131
  "Ready.": "Bereit.",
130
132
  "Received backend response.": "Backend-Antwort erhalten.",
131
133
  "Receiving response...": "Antwort wird empfangen...",
132
- Record: "Aufnehmen",
133
- "Record audio": "Audio aufnehmen",
134
- "Recording...": "Aufnahme…",
134
+ Record: "",
135
+ "Record audio": "",
136
+ "Recorded audio:": "Aufgenommenes Audio:",
137
+ "Recording...": "",
135
138
  Reference: "Referenz",
136
139
  References: "Referenzen",
137
140
  Regenerate: "Neu generieren",
@@ -153,6 +156,7 @@ export const deDict: Record<string, string> = {
153
156
  "Search the documentation…": "Durchsuchen Sie die Dokumentation…",
154
157
  "Search with AI-Kit": "Suche mit AI-Kit",
155
158
  "Searching…": "Suche…",
159
+ "Select or type tags…": "Tags auswählen oder eingeben…",
156
160
  Send: "Senden",
157
161
  "Sending request to backend…": "Anfrage wird an das Backend gesendet…",
158
162
  "Sending request to server...": "Anfrage wird an den Server gesendet...",
@@ -165,13 +169,15 @@ export const deDict: Record<string, string> = {
165
169
  Sources: "Quellen",
166
170
  Spanish: "Spanisch",
167
171
  Stop: "Stoppen",
168
- "Stop recording": "Aufnahme stoppen",
172
+ "Stop recording": "",
173
+ Subcategories: "Unterkategorien",
169
174
  "Suggested change": "Vorgeschlagene Änderung",
170
175
  Summarize: "Zusammenfassen",
171
176
  "Summarize again": "Erneut zusammenfassen",
172
177
  "Summarize on Backend": "Serverseitig zusammenfassen",
173
178
  Summarizing: "Zusammenfassung",
174
179
  Swedish: "Schwedisch",
180
+ Tags: "Tags",
175
181
  Teaser: "Teaser",
176
182
  teaser: "Teaser",
177
183
  Thai: "Thailändisch",