@smart-cloud/ai-kit-ui 1.2.7 → 1.3.2

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.
@@ -83,6 +83,9 @@ export async function persistAttachmentBlob(
83
83
  blob: Blob,
84
84
  meta: { name: string; type: string; size: number },
85
85
  ): Promise<string | null> {
86
+ // Enforce storage quota before adding new attachment
87
+ await enforceStorageQuota();
88
+
86
89
  const record: AttachmentRecord = {
87
90
  id,
88
91
  blob,
@@ -170,3 +173,66 @@ export async function cleanupDanglingAttachments(
170
173
  console.warn("[AiChatbot] Failed to cleanup attachments", error);
171
174
  });
172
175
  }
176
+
177
+ // Storage quota management
178
+ const MAX_STORAGE_BYTES = 50 * 1024 * 1024; // 50 MB limit
179
+
180
+ export async function getAllAttachments(): Promise<StoredAttachment[]> {
181
+ const db = await getDatabase();
182
+ if (!db) return [];
183
+
184
+ return new Promise<StoredAttachment[]>((resolve, reject) => {
185
+ try {
186
+ const tx = db.transaction(STORE_NAME, "readonly");
187
+ const request = tx.objectStore(STORE_NAME).getAll();
188
+
189
+ request.onsuccess = () => {
190
+ const records = (request.result || []) as AttachmentRecord[];
191
+ resolve(records);
192
+ };
193
+
194
+ request.onerror = () => reject(request.error);
195
+ tx.onerror = () => reject(tx.error);
196
+ tx.onabort = () => reject(tx.error);
197
+ } catch (error) {
198
+ reject(error);
199
+ }
200
+ }).catch((error) => {
201
+ console.warn("[AiChatbot] Failed to get all attachments", error);
202
+ return [];
203
+ });
204
+ }
205
+
206
+ export async function enforceStorageQuota(): Promise<void> {
207
+ const attachments = await getAllAttachments();
208
+ if (attachments.length === 0) return;
209
+
210
+ // Sort by creation time (oldest first)
211
+ const sorted = attachments.sort((a, b) => a.createdAt - b.createdAt);
212
+
213
+ // Calculate total size
214
+ let totalSize = sorted.reduce((sum, att) => sum + att.size, 0);
215
+
216
+ // Delete oldest attachments until under quota
217
+ const toDelete: string[] = [];
218
+ let i = 0;
219
+ while (totalSize > MAX_STORAGE_BYTES && i < sorted.length) {
220
+ const attachment = sorted[i];
221
+ if (attachment) {
222
+ toDelete.push(attachment.id);
223
+ totalSize -= attachment.size;
224
+ }
225
+ i++;
226
+ }
227
+
228
+ // Delete the attachments
229
+ for (const id of toDelete) {
230
+ await deleteAttachmentBlob(id);
231
+ }
232
+
233
+ if (toDelete.length > 0) {
234
+ console.log(
235
+ `[AiChatbot] Deleted ${toDelete.length} old attachment(s) to enforce storage quota`,
236
+ );
237
+ }
238
+ }
@@ -7,6 +7,7 @@ import {
7
7
  Loader,
8
8
  Modal,
9
9
  Paper,
10
+ Progress,
10
11
  Stack,
11
12
  Text,
12
13
  TextInput,
@@ -26,6 +27,7 @@ import rehypeRaw from "rehype-raw";
26
27
  import remarkGfm from "remark-gfm";
27
28
 
28
29
  import { IconSearch } from "@tabler/icons-react";
30
+ import { IconMicrophone, IconMicrophoneOff } from "@tabler/icons-react";
29
31
 
30
32
  import { AiFeatureBorder } from "../ai-feature/AiFeatureBorder";
31
33
  import { translations } from "../i18n";
@@ -37,6 +39,8 @@ I18n.putVocabularies(translations);
37
39
 
38
40
  type Props = DocSearchProps & AiKitShellInjectedProps;
39
41
 
42
+ const USE_AUDIO = false; // Set to true to enable audio recording feature (requires backend support for audio input)
43
+
40
44
  function groupChunksByDoc(result: SearchResult | null) {
41
45
  const docs = result?.citations?.docs ?? [];
42
46
  const chunks = result?.citations?.chunks ?? [];
@@ -81,6 +85,22 @@ const DocSearchBase: FC<Props> = (props) => {
81
85
  } = props;
82
86
 
83
87
  const [query, setQuery] = useState<string>("");
88
+ const [recording, setRecording] = useState<boolean>(false);
89
+ const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
90
+ const [audioLevel, setAudioLevel] = useState<number>(0);
91
+ const mediaRecorderRef = useRef<MediaRecorder | null>(null);
92
+ const audioChunksRef = useRef<Blob[]>([]);
93
+ const audioContextRef = useRef<AudioContext | null>(null);
94
+ const analyserRef = useRef<AnalyserNode | null>(null);
95
+ const animationFrameRef = useRef<number | null>(null);
96
+
97
+ // Audio cache: store uploaded audio to avoid re-uploading within session
98
+ const audioCacheRef = useRef<{
99
+ blob: Blob;
100
+ uploadTimestamp: number;
101
+ } | null>(null);
102
+ const AUDIO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
103
+
84
104
  const { busy, error, statusEvent, result, run, cancel, reset } =
85
105
  useAiRun<SearchResult>();
86
106
 
@@ -127,25 +147,170 @@ const DocSearchBase: FC<Props> = (props) => {
127
147
 
128
148
  const canSearch = useMemo(() => {
129
149
  if (busy) return false;
150
+ // Can search if we have text OR audio
130
151
  const text = typeof inputText === "function" ? inputText() : inputText;
131
- return Boolean(text && text.trim().length > 0);
132
- }, [inputText, busy]);
152
+ return Boolean((text && text.trim().length > 0) || audioBlob);
153
+ }, [inputText, busy, audioBlob]);
154
+
155
+ const startRecording = useCallback(async () => {
156
+ try {
157
+ // Clear query input when starting audio recording
158
+ setQuery("");
159
+
160
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
161
+ const mediaRecorder = new MediaRecorder(stream, {
162
+ mimeType: "audio/webm",
163
+ });
164
+
165
+ // Setup audio analysis for visual feedback
166
+ const audioContext = new AudioContext();
167
+ const source = audioContext.createMediaStreamSource(stream);
168
+ const analyser = audioContext.createAnalyser();
169
+ analyser.fftSize = 2048; // Higher for smoother results
170
+ analyser.smoothingTimeConstant = 0.8; // Smoother transitions
171
+ source.connect(analyser);
172
+
173
+ audioContextRef.current = audioContext;
174
+ analyserRef.current = analyser;
175
+
176
+ // Monitor audio level using time domain data (more reliable)
177
+ const dataArray = new Uint8Array(analyser.fftSize);
178
+ const updateLevel = () => {
179
+ if (!analyserRef.current) return;
180
+ analyserRef.current.getByteTimeDomainData(dataArray);
181
+
182
+ // Calculate RMS (Root Mean Square) for accurate volume
183
+ let sum = 0;
184
+ for (let i = 0; i < dataArray.length; i++) {
185
+ const normalized = (dataArray[i]! - 128) / 128; // Normalize to -1 to 1
186
+ sum += normalized * normalized;
187
+ }
188
+ const rms = Math.sqrt(sum / dataArray.length);
189
+
190
+ // Convert to percentage (0-100) with scaling for better visibility
191
+ const level = Math.min(100, rms * 200);
192
+ setAudioLevel(level);
193
+
194
+ animationFrameRef.current = requestAnimationFrame(updateLevel);
195
+ };
196
+ updateLevel();
197
+
198
+ audioChunksRef.current = [];
199
+
200
+ mediaRecorder.ondataavailable = (event) => {
201
+ if (event.data.size > 0) {
202
+ audioChunksRef.current.push(event.data);
203
+ }
204
+ };
205
+
206
+ mediaRecorder.onstop = () => {
207
+ const audioBlob = new Blob(audioChunksRef.current, {
208
+ type: "audio/webm",
209
+ });
210
+ setAudioBlob(audioBlob);
211
+ stream.getTracks().forEach((track) => track.stop());
212
+
213
+ // Cleanup audio analysis
214
+ if (animationFrameRef.current) {
215
+ cancelAnimationFrame(animationFrameRef.current);
216
+ animationFrameRef.current = null;
217
+ }
218
+ if (audioContextRef.current) {
219
+ audioContextRef.current.close();
220
+ audioContextRef.current = null;
221
+ }
222
+ analyserRef.current = null;
223
+ setAudioLevel(0);
224
+ };
225
+
226
+ mediaRecorderRef.current = mediaRecorder;
227
+ mediaRecorder.start();
228
+ setRecording(true);
229
+ } catch (error) {
230
+ console.error("Failed to start recording:", error);
231
+ }
232
+ }, []);
233
+
234
+ const stopRecording = useCallback(() => {
235
+ if (mediaRecorderRef.current && recording) {
236
+ mediaRecorderRef.current.stop();
237
+ setRecording(false);
238
+ }
239
+ }, [recording]);
240
+
241
+ const clearAudio = useCallback(() => {
242
+ setAudioBlob(null);
243
+ audioChunksRef.current = [];
244
+ setAudioLevel(0);
245
+ // Clear audio cache when user manually clears audio
246
+ audioCacheRef.current = null;
247
+ }, []);
248
+
249
+ // Cleanup on unmount
250
+ useEffect(() => {
251
+ return () => {
252
+ if (animationFrameRef.current) {
253
+ cancelAnimationFrame(animationFrameRef.current);
254
+ }
255
+ if (audioContextRef.current) {
256
+ audioContextRef.current.close();
257
+ }
258
+ };
259
+ }, []);
133
260
 
134
261
  const onSearch = useCallback(async () => {
135
- const q =
136
- typeof inputText === "function"
137
- ? (inputText as () => string)()
138
- : inputText;
139
- if (!q) return;
140
- setQuery(q);
262
+ let q: string | undefined;
263
+
264
+ // Get text query if available (not recording audio)
265
+ if (!audioBlob) {
266
+ q =
267
+ typeof inputText === "function"
268
+ ? (inputText as () => string)()
269
+ : inputText;
270
+ if (!q) return;
271
+ setQuery(q);
272
+ }
273
+
274
+ console.log("Starting search with query:", q, "and audio:", audioBlob);
275
+
276
+ // Check if we can reuse cached audio (same blob within TTL)
277
+ const now = Date.now();
278
+ const isSameAudio =
279
+ audioBlob &&
280
+ audioCacheRef.current?.blob === audioBlob &&
281
+ now - audioCacheRef.current.uploadTimestamp < AUDIO_CACHE_TTL;
282
+
283
+ if (audioBlob && !isSameAudio) {
284
+ // Update cache for new audio blob
285
+ audioCacheRef.current = {
286
+ blob: audioBlob,
287
+ uploadTimestamp: now,
288
+ };
289
+ console.log("Audio cache updated for new recording");
290
+ } else if (isSameAudio) {
291
+ console.log(
292
+ "Reusing cached audio (no re-upload needed within",
293
+ Math.round(
294
+ (AUDIO_CACHE_TTL - (now - audioCacheRef.current!.uploadTimestamp)) /
295
+ 1000,
296
+ ),
297
+ "seconds)",
298
+ );
299
+ }
300
+
141
301
  reset();
142
302
  await run(async ({ signal, onStatus }) => {
143
303
  return await sendSearchMessage(
144
- { sessionId, query: q, topK },
304
+ {
305
+ sessionId,
306
+ ...(q && { query: q }),
307
+ ...(audioBlob && { audio: audioBlob }), // Pass Blob directly
308
+ topK,
309
+ },
145
310
  { signal, onStatus, context },
146
311
  );
147
312
  });
148
- }, [context, inputText, run, reset, topK, sessionId]);
313
+ }, [context, inputText, audioBlob, run, reset, topK, sessionId]);
149
314
 
150
315
  const close = useCallback(async () => {
151
316
  reset();
@@ -313,9 +478,19 @@ const DocSearchBase: FC<Props> = (props) => {
313
478
  <TextInput
314
479
  style={{ flex: 1 }}
315
480
  value={query}
316
- onChange={(e) => setQuery(e.currentTarget.value)}
317
- placeholder={I18n.get("Search the documentation…")}
318
- disabled={busy}
481
+ onChange={(e) => {
482
+ setQuery(e.currentTarget.value);
483
+ // Clear audio when typing
484
+ if (audioBlob) {
485
+ clearAudio();
486
+ }
487
+ }}
488
+ placeholder={
489
+ audioBlob
490
+ ? I18n.get("Audio recorded")
491
+ : I18n.get("Search the documentation…")
492
+ }
493
+ disabled={busy || recording || !!audioBlob}
319
494
  onKeyDown={(e) => {
320
495
  if (e.key === "Enter" && canSearch) {
321
496
  e.preventDefault();
@@ -324,6 +499,48 @@ const DocSearchBase: FC<Props> = (props) => {
324
499
  }}
325
500
  />
326
501
 
502
+ {
503
+ /* Microphone button */ USE_AUDIO && (
504
+ <>
505
+ {audioBlob ? (
506
+ <Button
507
+ variant="outline"
508
+ size="sm"
509
+ color="red"
510
+ onClick={clearAudio}
511
+ disabled={busy}
512
+ title={I18n.get("Clear audio")}
513
+ >
514
+ <IconMicrophoneOff size={18} />
515
+ </Button>
516
+ ) : (
517
+ <Button
518
+ variant={recording ? "filled" : "outline"}
519
+ size="sm"
520
+ color={recording ? "red" : "gray"}
521
+ onClick={recording ? stopRecording : startRecording}
522
+ disabled={busy}
523
+ title={
524
+ recording
525
+ ? I18n.get("Stop recording")
526
+ : I18n.get("Record audio")
527
+ }
528
+ style={
529
+ recording
530
+ ? {
531
+ transform: `scale(${1 + audioLevel / 300})`,
532
+ transition: "transform 0.1s ease-out",
533
+ }
534
+ : undefined
535
+ }
536
+ >
537
+ <IconMicrophone size={18} />
538
+ </Button>
539
+ )}
540
+ </>
541
+ )
542
+ }
543
+
327
544
  <Button
328
545
  variant="filled"
329
546
  size="sm"
@@ -346,6 +563,41 @@ const DocSearchBase: FC<Props> = (props) => {
346
563
  ) : null}
347
564
  </Group>
348
565
 
566
+ {
567
+ /* Audio level indicator when recording */ USE_AUDIO && (
568
+ <>
569
+ {recording && (
570
+ <Stack gap="xs">
571
+ <Text size="xs" c="dimmed">
572
+ {I18n.get("Recording...")} 🎤
573
+ </Text>
574
+ <Progress
575
+ value={audioLevel}
576
+ size="sm"
577
+ color="red"
578
+ animated
579
+ striped
580
+ />
581
+ </Stack>
582
+ )}
583
+
584
+ {/* Audio playback when recorded */}
585
+ {audioBlob && !recording && (
586
+ <Stack gap="xs">
587
+ <Text size="xs" c="dimmed">
588
+ {I18n.get("Recorded audio:")}
589
+ </Text>
590
+ <audio
591
+ controls
592
+ src={URL.createObjectURL(audioBlob)}
593
+ className="ai-kit-audio-player"
594
+ />
595
+ </Stack>
596
+ )}
597
+ </>
598
+ )
599
+ }
600
+
349
601
  {error ? (
350
602
  <Alert color="red" title={I18n.get("Error")}>
351
603
  {error}
package/src/i18n/ar.ts CHANGED
@@ -20,6 +20,9 @@ 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
26
  auto: "آلي",
24
27
  "Auto-detect": "الكشف التلقائي",
25
28
  Cancel: "يلغي",
@@ -28,6 +31,8 @@ export const arDict: Record<string, string> = {
28
31
  casual: "غير رسمي",
29
32
  "Checking capabilities…": "إمكانيات الفحص…",
30
33
  Chinese: "الصينية",
34
+ Clear: "مسح",
35
+ "Clear audio": "مسح الصوت",
31
36
  "Click again to confirm": "انقر مرة أخرى للتأكيد",
32
37
  Close: "يغلق",
33
38
  "Close chat": "إغلاق الدردشة",
@@ -72,6 +77,7 @@ export const arDict: Record<string, string> = {
72
77
  HTML: "HTML",
73
78
  Hungarian: "المجرية",
74
79
  "I'm ready to assist you.": "أنا جاهز لمساعدتك.",
80
+ "Image no longer available": "لم تعد الصورة متاحة",
75
81
  Indonesian: "إندونيسيا",
76
82
  "Initializing on-device AI…": "جارٍ تهيئة الذكاء الاصطناعي على الجهاز…",
77
83
  "Inlining images as base64": "تضمين الصور بصيغة base64",
@@ -123,6 +129,9 @@ export const arDict: Record<string, string> = {
123
129
  "Ready.": "جاهز.",
124
130
  "Received backend response.": "تم استلام رد من الخادم الخلفي.",
125
131
  "Receiving response...": "جارٍ استلام الرد...",
132
+ Record: "تسجيل",
133
+ "Record audio": "تسجيل صوت",
134
+ "Recording...": "جارٍ التسجيل…",
126
135
  Reference: "مرجع",
127
136
  References: "المراجع",
128
137
  Regenerate: "تجديد",
@@ -156,6 +165,7 @@ export const arDict: Record<string, string> = {
156
165
  Sources: "مصادر",
157
166
  Spanish: "الأسبانية",
158
167
  Stop: "قف",
168
+ "Stop recording": "إيقاف التسجيل",
159
169
  "Suggested change": "التغيير المقترح",
160
170
  Summarize: "لخص",
161
171
  "Summarize again": "أعد التلخيص",
package/src/i18n/de.ts CHANGED
@@ -20,6 +20,9 @@ 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
26
  auto: "automatisch",
24
27
  "Auto-detect": "Automatisch erkennen",
25
28
  Cancel: "Abbrechen",
@@ -28,6 +31,8 @@ export const deDict: Record<string, string> = {
28
31
  casual: "locker",
29
32
  "Checking capabilities…": "Fähigkeiten werden geprüft…",
30
33
  Chinese: "Chinesisch",
34
+ Clear: "Löschen",
35
+ "Clear audio": "Audio löschen",
31
36
  "Click again to confirm": "Zum Bestätigen erneut klicken",
32
37
  Close: "Schließen",
33
38
  "Close chat": "Chat schließen",
@@ -72,6 +77,7 @@ export const deDict: Record<string, string> = {
72
77
  HTML: "HTML",
73
78
  Hungarian: "Ungarisch",
74
79
  "I'm ready to assist you.": "Ich bin bereit, dir zu helfen.",
80
+ "Image no longer available": "Bild nicht mehr verfügbar",
75
81
  Indonesian: "Indonesisch",
76
82
  "Initializing on-device AI…": "On-Device-KI wird initialisiert…",
77
83
  "Inlining images as base64": "Bilder werden als Base64 eingebettet",
@@ -123,6 +129,9 @@ export const deDict: Record<string, string> = {
123
129
  "Ready.": "Bereit.",
124
130
  "Received backend response.": "Backend-Antwort erhalten.",
125
131
  "Receiving response...": "Antwort wird empfangen...",
132
+ Record: "Aufnehmen",
133
+ "Record audio": "Audio aufnehmen",
134
+ "Recording...": "Aufnahme…",
126
135
  Reference: "Referenz",
127
136
  References: "Referenzen",
128
137
  Regenerate: "Neu generieren",
@@ -156,6 +165,7 @@ export const deDict: Record<string, string> = {
156
165
  Sources: "Quellen",
157
166
  Spanish: "Spanisch",
158
167
  Stop: "Stoppen",
168
+ "Stop recording": "Aufnahme stoppen",
159
169
  "Suggested change": "Vorgeschlagene Änderung",
160
170
  Summarize: "Zusammenfassen",
161
171
  "Summarize again": "Erneut zusammenfassen",
package/src/i18n/en.ts CHANGED
@@ -20,6 +20,9 @@ export const enDict: Record<string, string> = {
20
20
  "Ask me": "Ask me",
21
21
  Assistant: "Assistant",
22
22
  "Assistant is thinking…": "Assistant is thinking…",
23
+ "Audio message": "Audio message",
24
+ "Audio no longer available": "Audio no longer available",
25
+ "Audio recorded": "Audio recorded",
23
26
  auto: "auto",
24
27
  "Auto-detect": "Auto-detect",
25
28
  Cancel: "Cancel",
@@ -28,6 +31,8 @@ export const enDict: Record<string, string> = {
28
31
  casual: "casual",
29
32
  "Checking capabilities…": "Checking capabilities…",
30
33
  Chinese: "Chinese",
34
+ Clear: "Clear",
35
+ "Clear audio": "Clear audio",
31
36
  "Click again to confirm": "Click again to confirm",
32
37
  Close: "Close",
33
38
  "Close chat": "Close chat",
@@ -72,6 +77,7 @@ export const enDict: Record<string, string> = {
72
77
  HTML: "HTML",
73
78
  Hungarian: "Hungarian",
74
79
  "I'm ready to assist you.": "I'm ready to assist you.",
80
+ "Image no longer available": "Image no longer available",
75
81
  Indonesian: "Indonesian",
76
82
  "Initializing on-device AI…": "Initializing on-device AI…",
77
83
  "Inlining images as base64": "Inlining images as base64",
@@ -123,6 +129,9 @@ export const enDict: Record<string, string> = {
123
129
  "Ready.": "Ready.",
124
130
  "Received backend response.": "Received backend response.",
125
131
  "Receiving response...": "Receiving response...",
132
+ Record: "Record",
133
+ "Record audio": "Record audio",
134
+ "Recording...": "Recording...",
126
135
  Reference: "Reference",
127
136
  References: "References",
128
137
  Regenerate: "Regenerate",
@@ -156,6 +165,7 @@ export const enDict: Record<string, string> = {
156
165
  Sources: "Sources",
157
166
  Spanish: "Spanish",
158
167
  Stop: "Stop",
168
+ "Stop recording": "Stop recording",
159
169
  "Suggested change": "Suggested change",
160
170
  Summarize: "Summarize",
161
171
  "Summarize again": "Summarize again",
package/src/i18n/es.ts CHANGED
@@ -20,6 +20,9 @@ export const esDict: Record<string, string> = {
20
20
  "Ask me": "Pregúntame",
21
21
  Assistant: "Asistente",
22
22
  "Assistant is thinking…": "El asistente está pensando…",
23
+ "Audio message": "Mensaje de audio",
24
+ "Audio no longer available": "El audio ya no está disponible",
25
+ "Audio recorded": "Audio grabado",
23
26
  auto: "Automático",
24
27
  "Auto-detect": "Detección automática",
25
28
  Cancel: "Cancelar",
@@ -28,6 +31,8 @@ export const esDict: Record<string, string> = {
28
31
  casual: "casual",
29
32
  "Checking capabilities…": "Comprobando capacidades…",
30
33
  Chinese: "Chino",
34
+ Clear: "Borrar",
35
+ "Clear audio": "Borrar audio",
31
36
  "Click again to confirm": "Haz clic de nuevo para confirmar",
32
37
  Close: "Cerrar",
33
38
  "Close chat": "Cerrar chat",
@@ -74,6 +79,7 @@ export const esDict: Record<string, string> = {
74
79
  HTML: "HTML",
75
80
  Hungarian: "húngaro",
76
81
  "I'm ready to assist you.": "Estoy listo para ayudarte.",
82
+ "Image no longer available": "La imagen ya no está disponible",
77
83
  Indonesian: "indonesio",
78
84
  "Initializing on-device AI…": "Inicializando IA en el dispositivo…",
79
85
  "Inlining images as base64": "Inserción de imágenes en base64",
@@ -125,6 +131,9 @@ export const esDict: Record<string, string> = {
125
131
  "Ready.": "Listo.",
126
132
  "Received backend response.": "Se recibió una respuesta del backend.",
127
133
  "Receiving response...": "Recibiendo respuesta...",
134
+ Record: "Grabar",
135
+ "Record audio": "Grabar audio",
136
+ "Recording...": "Grabando…",
128
137
  Reference: "Referencia",
129
138
  References: "Referencias",
130
139
  Regenerate: "Regenerado",
@@ -158,6 +167,7 @@ export const esDict: Record<string, string> = {
158
167
  Sources: "Fuentes",
159
168
  Spanish: "Español",
160
169
  Stop: "Detener",
170
+ "Stop recording": "Detener grabación",
161
171
  "Suggested change": "Cambio sugerido",
162
172
  Summarize: "Resumir",
163
173
  "Summarize again": "Resumir de nuevo",
package/src/i18n/fr.ts CHANGED
@@ -20,6 +20,9 @@ export const frDict: Record<string, string> = {
20
20
  "Ask me": "Demandez-moi",
21
21
  Assistant: "Assistant",
22
22
  "Assistant is thinking…": "L'assistant réfléchit…",
23
+ "Audio message": "Message audio",
24
+ "Audio no longer available": "L’audio n’est plus disponible",
25
+ "Audio recorded": "Audio enregistré",
23
26
  auto: "Automatique",
24
27
  "Auto-detect": "Détection automatique",
25
28
  Cancel: "Annuler",
@@ -28,6 +31,8 @@ export const frDict: Record<string, string> = {
28
31
  casual: "occasionnel",
29
32
  "Checking capabilities…": "Vérification des capacités…",
30
33
  Chinese: "Chinois",
34
+ Clear: "Effacer",
35
+ "Clear audio": "Effacer l’audio",
31
36
  "Click again to confirm": "Cliquez à nouveau pour confirmer",
32
37
  Close: "Fermer",
33
38
  "Close chat": "Fermer le chat",
@@ -74,6 +79,7 @@ export const frDict: Record<string, string> = {
74
79
  HTML: "HTML",
75
80
  Hungarian: "hongrois",
76
81
  "I'm ready to assist you.": "Je suis prêt à vous aider.",
82
+ "Image no longer available": "L’image n’est plus disponible",
77
83
  Indonesian: "indonésien",
78
84
  "Initializing on-device AI…": "Initialisation de l'IA embarquée…",
79
85
  "Inlining images as base64": "Intégration des images en base64",
@@ -125,6 +131,9 @@ export const frDict: Record<string, string> = {
125
131
  "Ready.": "Prêt.",
126
132
  "Received backend response.": "Réponse du serveur reçue.",
127
133
  "Receiving response...": "Réception de la réponse...",
134
+ Record: "Enregistrer",
135
+ "Record audio": "Enregistrer un audio",
136
+ "Recording...": "Enregistrement…",
128
137
  Reference: "Référence",
129
138
  References: "Références",
130
139
  Regenerate: "Régénérer",
@@ -158,6 +167,7 @@ export const frDict: Record<string, string> = {
158
167
  Sources: "Sources",
159
168
  Spanish: "Espagnol",
160
169
  Stop: "Arrêt",
170
+ "Stop recording": "Arrêter l’enregistrement",
161
171
  "Suggested change": "Changement suggéré",
162
172
  Summarize: "Résumer",
163
173
  "Summarize again": "Résumons à nouveau",
package/src/i18n/he.ts CHANGED
@@ -20,6 +20,9 @@ export const heDict: 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
26
  auto: "אוטומטי",
24
27
  "Auto-detect": "זיהוי אוטומטי",
25
28
  Cancel: "לְבַטֵל",
@@ -28,6 +31,8 @@ export const heDict: Record<string, string> = {
28
31
  casual: "אַגָבִי",
29
32
  "Checking capabilities…": "בודק יכולות…",
30
33
  Chinese: "סִינִית",
34
+ Clear: "נקה",
35
+ "Clear audio": "נקה שמע",
31
36
  "Click again to confirm": "לחץ שוב כדי לאשר",
32
37
  Close: "לִסְגוֹר",
33
38
  "Close chat": "סגור צ׳אט",
@@ -72,6 +77,7 @@ export const heDict: Record<string, string> = {
72
77
  HTML: "HTML",
73
78
  Hungarian: "הוּנגָרִי",
74
79
  "I'm ready to assist you.": "אני מוכן לעזור לך.",
80
+ "Image no longer available": "התמונה אינה זמינה עוד",
75
81
  Indonesian: "אינדונזית",
76
82
  "Initializing on-device AI…": "מאתחל את הבינה המלאכותית במכשיר…",
77
83
  "Inlining images as base64": "תמונות מוטבעות כ-base64",
@@ -123,6 +129,9 @@ export const heDict: Record<string, string> = {
123
129
  "Ready.": "מוכן.",
124
130
  "Received backend response.": "התקבלה תגובה מה-backend.",
125
131
  "Receiving response...": "מתקבלת תגובה...",
132
+ Record: "הקלט",
133
+ "Record audio": "הקלט שמע",
134
+ "Recording...": "מקליט...",
126
135
  Reference: "מקור",
127
136
  References: "מקורות",
128
137
  Regenerate: "לְהִתְחַדֵשׁ",
@@ -156,6 +165,7 @@ export const heDict: Record<string, string> = {
156
165
  Sources: "מקורות",
157
166
  Spanish: "סְפָרַדִית",
158
167
  Stop: "לְהַפְסִיק",
168
+ "Stop recording": "עצור הקלטה",
159
169
  "Suggested change": "שינוי מוצע",
160
170
  Summarize: "לְסַכֵּם",
161
171
  "Summarize again": "לסכם שוב",