@smart-cloud/ai-kit-ui 1.3.0 → 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,
@@ -38,6 +39,8 @@ I18n.putVocabularies(translations);
38
39
 
39
40
  type Props = DocSearchProps & AiKitShellInjectedProps;
40
41
 
42
+ const USE_AUDIO = false; // Set to true to enable audio recording feature (requires backend support for audio input)
43
+
41
44
  function groupChunksByDoc(result: SearchResult | null) {
42
45
  const docs = result?.citations?.docs ?? [];
43
46
  const chunks = result?.citations?.chunks ?? [];
@@ -84,9 +87,20 @@ const DocSearchBase: FC<Props> = (props) => {
84
87
  const [query, setQuery] = useState<string>("");
85
88
  const [recording, setRecording] = useState<boolean>(false);
86
89
  const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
90
+ const [audioLevel, setAudioLevel] = useState<number>(0);
87
91
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
88
92
  const audioChunksRef = useRef<Blob[]>([]);
89
-
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
+
90
104
  const { busy, error, statusEvent, result, run, cancel, reset } =
91
105
  useAiRun<SearchResult>();
92
106
 
@@ -140,25 +154,75 @@ const DocSearchBase: FC<Props> = (props) => {
140
154
 
141
155
  const startRecording = useCallback(async () => {
142
156
  try {
157
+ // Clear query input when starting audio recording
158
+ setQuery("");
159
+
143
160
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
144
161
  const mediaRecorder = new MediaRecorder(stream, {
145
162
  mimeType: "audio/webm",
146
163
  });
147
-
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
+
148
198
  audioChunksRef.current = [];
149
-
199
+
150
200
  mediaRecorder.ondataavailable = (event) => {
151
201
  if (event.data.size > 0) {
152
202
  audioChunksRef.current.push(event.data);
153
203
  }
154
204
  };
155
-
205
+
156
206
  mediaRecorder.onstop = () => {
157
- const audioBlob = new Blob(audioChunksRef.current, { type: "audio/webm" });
207
+ const audioBlob = new Blob(audioChunksRef.current, {
208
+ type: "audio/webm",
209
+ });
158
210
  setAudioBlob(audioBlob);
159
- stream.getTracks().forEach(track => track.stop());
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);
160
224
  };
161
-
225
+
162
226
  mediaRecorderRef.current = mediaRecorder;
163
227
  mediaRecorder.start();
164
228
  setRecording(true);
@@ -177,46 +241,71 @@ const DocSearchBase: FC<Props> = (props) => {
177
241
  const clearAudio = useCallback(() => {
178
242
  setAudioBlob(null);
179
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
+ };
180
259
  }, []);
181
260
 
182
261
  const onSearch = useCallback(async () => {
183
262
  let q: string | undefined;
184
- let audio: { format: string; data: string } | undefined;
185
263
 
186
- // Get text query if available
264
+ // Get text query if available (not recording audio)
187
265
  if (!audioBlob) {
188
- q = typeof inputText === "function"
189
- ? (inputText as () => string)()
190
- : inputText;
266
+ q =
267
+ typeof inputText === "function"
268
+ ? (inputText as () => string)()
269
+ : inputText;
191
270
  if (!q) return;
192
271
  setQuery(q);
193
272
  }
194
273
 
195
- // Convert audio blob to base64 if available
196
- if (audioBlob) {
197
- const reader = new FileReader();
198
- await new Promise<void>((resolve, reject) => {
199
- reader.onload = () => {
200
- const base64 = (reader.result as string).split(",")[1];
201
- audio = {
202
- format: "audio/webm",
203
- data: base64,
204
- };
205
- resolve();
206
- };
207
- reader.onerror = reject;
208
- reader.readAsDataURL(audioBlob);
209
- });
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
+ );
210
299
  }
211
300
 
212
301
  reset();
213
302
  await run(async ({ signal, onStatus }) => {
214
303
  return await sendSearchMessage(
215
- {
216
- sessionId,
304
+ {
305
+ sessionId,
217
306
  ...(q && { query: q }),
218
- audio,
219
- topK
307
+ ...(audioBlob && { audio: audioBlob }), // Pass Blob directly
308
+ topK,
220
309
  },
221
310
  { signal, onStatus, context },
222
311
  );
@@ -410,34 +499,47 @@ const DocSearchBase: FC<Props> = (props) => {
410
499
  }}
411
500
  />
412
501
 
413
- {/* Microphone button */}
414
- {audioBlob ? (
415
- <Button
416
- variant="outline"
417
- size="sm"
418
- color="red"
419
- onClick={clearAudio}
420
- disabled={busy}
421
- title={I18n.get("Clear audio")}
422
- >
423
- <IconMicrophoneOff size={18} />
424
- </Button>
425
- ) : (
426
- <Button
427
- variant={recording ? "filled" : "outline"}
428
- size="sm"
429
- color={recording ? "red" : "gray"}
430
- onClick={recording ? stopRecording : startRecording}
431
- disabled={busy}
432
- title={
433
- recording
434
- ? I18n.get("Stop recording")
435
- : I18n.get("Record audio")
436
- }
437
- >
438
- <IconMicrophone size={18} />
439
- </Button>
440
- )}
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
+ }
441
543
 
442
544
  <Button
443
545
  variant="filled"
@@ -461,6 +563,41 @@ const DocSearchBase: FC<Props> = (props) => {
461
563
  ) : null}
462
564
  </Group>
463
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
+
464
601
  {error ? (
465
602
  <Alert color="red" title={I18n.get("Error")}>
466
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": "לסכם שוב",