@lastbrain/ai-ui-react 1.0.73 → 1.0.74

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.
Files changed (83) hide show
  1. package/dist/components/AiChipLabel.d.ts.map +1 -1
  2. package/dist/components/AiChipLabel.js +10 -7
  3. package/dist/components/AiContextButton.d.ts +1 -1
  4. package/dist/components/AiContextButton.d.ts.map +1 -1
  5. package/dist/components/AiContextButton.js +25 -12
  6. package/dist/components/AiImageButton.d.ts.map +1 -1
  7. package/dist/components/AiImageButton.js +32 -16
  8. package/dist/components/AiInput.d.ts.map +1 -1
  9. package/dist/components/AiInput.js +15 -5
  10. package/dist/components/AiModelSelect.d.ts.map +1 -1
  11. package/dist/components/AiModelSelect.js +3 -1
  12. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  13. package/dist/components/AiPromptPanel.js +72 -47
  14. package/dist/components/AiSelect.d.ts.map +1 -1
  15. package/dist/components/AiSelect.js +8 -3
  16. package/dist/components/AiStatusButton.d.ts.map +1 -1
  17. package/dist/components/AiStatusButton.js +23 -20
  18. package/dist/components/AiTextarea.d.ts.map +1 -1
  19. package/dist/components/AiTextarea.js +19 -6
  20. package/dist/components/ErrorToast.d.ts.map +1 -1
  21. package/dist/components/ErrorToast.js +4 -2
  22. package/dist/components/LBApiKeySelector.d.ts.map +1 -1
  23. package/dist/components/LBApiKeySelector.js +13 -5
  24. package/dist/components/LBConnectButton.d.ts.map +1 -1
  25. package/dist/components/LBConnectButton.js +8 -3
  26. package/dist/components/LBKeyPicker.d.ts.map +1 -1
  27. package/dist/components/LBKeyPicker.js +8 -4
  28. package/dist/components/LBSigninModal.d.ts.map +1 -1
  29. package/dist/components/LBSigninModal.js +13 -7
  30. package/dist/components/UsageToast.d.ts.map +1 -1
  31. package/dist/components/UsageToast.js +4 -2
  32. package/dist/context/I18nContext.d.ts +15 -0
  33. package/dist/context/I18nContext.d.ts.map +1 -0
  34. package/dist/context/I18nContext.js +44 -0
  35. package/dist/context/LBAuthProvider.d.ts +4 -1
  36. package/dist/context/LBAuthProvider.d.ts.map +1 -1
  37. package/dist/context/LBAuthProvider.js +3 -2
  38. package/dist/hooks/useAiCallImage.d.ts.map +1 -1
  39. package/dist/hooks/useAiCallImage.js +1 -107
  40. package/dist/hooks/useAiCallText.d.ts.map +1 -1
  41. package/dist/hooks/useAiCallText.js +1 -25
  42. package/dist/hooks/useLoadingTimer.d.ts +5 -0
  43. package/dist/hooks/useLoadingTimer.d.ts.map +1 -0
  44. package/dist/hooks/useLoadingTimer.js +27 -0
  45. package/dist/i18n/de.json +62 -0
  46. package/dist/i18n/en.json +128 -0
  47. package/dist/i18n/es.json +70 -0
  48. package/dist/i18n/fr.json +128 -0
  49. package/dist/i18n/it.json +62 -0
  50. package/dist/i18n/pt.json +62 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -0
  54. package/dist/styles.css +141 -1
  55. package/package.json +3 -3
  56. package/src/components/AiChipLabel.tsx +17 -8
  57. package/src/components/AiContextButton.tsx +44 -20
  58. package/src/components/AiImageButton.tsx +52 -25
  59. package/src/components/AiInput.tsx +20 -5
  60. package/src/components/AiModelSelect.tsx +3 -1
  61. package/src/components/AiPromptPanel.tsx +177 -59
  62. package/src/components/AiSelect.tsx +8 -3
  63. package/src/components/AiStatusButton.tsx +51 -40
  64. package/src/components/AiTextarea.tsx +24 -6
  65. package/src/components/ErrorToast.tsx +4 -2
  66. package/src/components/LBApiKeySelector.tsx +33 -13
  67. package/src/components/LBConnectButton.tsx +9 -3
  68. package/src/components/LBKeyPicker.tsx +10 -4
  69. package/src/components/LBSigninModal.tsx +31 -15
  70. package/src/components/UsageToast.tsx +4 -2
  71. package/src/context/I18nContext.tsx +71 -0
  72. package/src/context/LBAuthProvider.tsx +9 -1
  73. package/src/hooks/useAiCallImage.ts +1 -149
  74. package/src/hooks/useAiCallText.ts +1 -30
  75. package/src/hooks/useLoadingTimer.ts +32 -0
  76. package/src/i18n/de.json +62 -0
  77. package/src/i18n/en.json +128 -0
  78. package/src/i18n/es.json +70 -0
  79. package/src/i18n/fr.json +128 -0
  80. package/src/i18n/it.json +62 -0
  81. package/src/i18n/pt.json +62 -0
  82. package/src/index.ts +2 -0
  83. package/src/styles.css +141 -1
@@ -3,12 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import "../styles/register";
4
4
  import { useState, useEffect, useRef, useLayoutEffect, } from "react";
5
5
  import { createPortal } from "react-dom";
6
- import { BookOpen, Search, Sparkles, Star, Tag, Settings } from "lucide-react";
6
+ import { BookOpen, Search, Sparkles, Star, Tag, Settings, Loader2, } from "lucide-react";
7
7
  import { aiStyles } from "../styles/inline";
8
8
  import { handleAIError } from "../utils/errorHandler";
9
9
  import { usePrompts, } from "../hooks/usePrompts";
10
10
  import { useModelManagement } from "../hooks/useModelManagement";
11
11
  import { AiProvider, useAiContext } from "../context/AiProvider";
12
+ import { useI18n } from "../context/I18nContext";
13
+ import { useLoadingTimer } from "../hooks/useLoadingTimer";
12
14
  export function AiPromptPanel(props) {
13
15
  const { apiKey, baseUrl } = props;
14
16
  let hasContext = false;
@@ -31,6 +33,7 @@ export function AiPromptPanel(props) {
31
33
  return _jsx(AiPromptPanelInternal, { ...props });
32
34
  }
33
35
  function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "modal", models = [], sourceText, children, enableModelManagement = true, modelCategory = "text", availableModels = [], userModels = [], onModelToggle, apiKey, baseUrl, showOnlyUserModels = false, }) {
36
+ const { t } = useI18n();
34
37
  const [selectedModel, setSelectedModel] = useState("");
35
38
  const [prompt, setPrompt] = useState("");
36
39
  const [promptId, setPromptId] = useState(undefined);
@@ -50,6 +53,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
50
53
  const [isModelManagementOpen, setIsModelManagementOpen] = useState(false);
51
54
  const [loadingModels, setLoadingModels] = useState([]);
52
55
  const [modelSearchQuery, setModelSearchQuery] = useState("");
56
+ const { formatted: loadingElapsed } = useLoadingTimer(isGenerating);
53
57
  const { prompts, loading: promptsLoading, fetchPrompts, incrementStat, } = usePrompts();
54
58
  // Hook de gestion des modèles (automatique si enableModelManagement et pas de models/availableModels externes)
55
59
  const autoModelManagement = useModelManagement({
@@ -76,6 +80,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
76
80
  : autoModelManagement.availableModels;
77
81
  const effectiveUserModels = userModels.length > 0 ? userModels : autoModelManagement.userModels;
78
82
  const effectiveToggleModel = onModelToggle || autoModelManagement.toggleModel;
83
+ const isModelsLoading = autoModelManagement.loading && effectiveAvailableModels.length === 0;
79
84
  // Gestion des modèles
80
85
  const handleModelToggle = async (modelId, isActive) => {
81
86
  if (!effectiveToggleModel)
@@ -99,7 +104,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
99
104
  id: m.id,
100
105
  name: m.name,
101
106
  category: m.type === "image" ? "image" : "text",
102
- provider: "Unknown",
107
+ provider: t("common.unknown", "Unknown"),
103
108
  }));
104
109
  }
105
110
  const categoryModels = effectiveAvailableModels.filter((m) => m.category === modelCategory);
@@ -309,23 +314,27 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
309
314
  fontWeight: 600,
310
315
  color: "var(--ai-text-secondary)",
311
316
  letterSpacing: "0.02em",
312
- }, children: "G\u00E9n\u00E9ration en cours\u2026" })] })), _jsxs("div", { style: {
317
+ }, children: t("prompt.modal.generating", "Generating...") }), _jsx("div", { className: "ai-loading-meta", children: t("ai.loading.elapsed", "{seconds}", {
318
+ seconds: loadingElapsed,
319
+ }) })] })), _jsxs("div", { style: {
313
320
  ...aiStyles.modalHeader,
314
321
  position: "sticky",
315
322
  top: 0,
316
323
  zIndex: 5,
317
324
  backdropFilter: "blur(8px)",
318
- }, children: [_jsx("h2", { style: aiStyles.modalTitle, children: showPromptLibrary ? "Select a Prompt" : "AI Prompt Configuration" }), _jsx("button", { style: {
325
+ }, children: [_jsx("h2", { style: aiStyles.modalTitle, children: showPromptLibrary
326
+ ? t("prompt.modal.selectPrompt", "Select a Prompt")
327
+ : t("prompt.modal.title", "AI Prompt Configuration") }), _jsx("button", { style: {
319
328
  ...aiStyles.modalCloseButton,
320
329
  ...(isCloseHovered && aiStyles.modalCloseButtonHover),
321
- }, onClick: handleClose, onMouseEnter: () => setIsCloseHovered(true), onMouseLeave: () => setIsCloseHovered(false), "aria-label": "Close", children: "\u00D7" })] }), _jsx("div", { style: {
330
+ }, onClick: handleClose, onMouseEnter: () => setIsCloseHovered(true), onMouseLeave: () => setIsCloseHovered(false), "aria-label": t("common.closeLabel", "Close"), children: "\u00D7" })] }), _jsx("div", { style: {
322
331
  ...aiStyles.modalBody,
323
332
  flex: 1,
324
333
  overflow: "auto",
325
334
  }, children: !showPromptLibrary ? (_jsxs(_Fragment, { children: [sourceText && (_jsxs("div", { style: {
326
335
  ...aiStyles.modalInputGroup,
327
336
  marginBottom: "16px",
328
- }, children: [_jsx("label", { style: aiStyles.modalLabel, children: "Source Text" }), _jsx("div", { style: {
337
+ }, children: [_jsx("label", { style: aiStyles.modalLabel, children: t("prompt.modal.sourceText", "Source Text") }), _jsx("div", { style: {
329
338
  padding: "12px",
330
339
  background: aiStyles.textarea.background,
331
340
  border: `1px solid ${aiStyles.input.border}`,
@@ -343,7 +352,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
343
352
  justifyContent: "space-between",
344
353
  alignItems: "center",
345
354
  marginBottom: "8px",
346
- }, children: [_jsx("label", { htmlFor: "model-select", style: aiStyles.modalLabel, children: "AI Model" }), effectiveAvailableModels.length > 0 && (_jsxs("button", { onClick: () => setIsModelManagementOpen(true), className: "ai-inline-btn", title: "G\u00E9rer les mod\u00E8les", children: [_jsx(Settings, { size: 14 }), "G\u00E9rer les mod\u00E8les", effectiveAvailableModels.filter((m) => m.category === modelCategory &&
355
+ }, children: [_jsx("label", { htmlFor: "model-select", style: aiStyles.modalLabel, children: "AI Model" }), isModelsLoading ? (_jsx("span", { className: "ai-inline-skeleton", "aria-hidden": "true" })) : effectiveAvailableModels.length > 0 ? (_jsxs("button", { onClick: () => setIsModelManagementOpen(true), className: "ai-inline-btn", title: t("prompt.modal.manageModels", "Manage models"), children: [_jsx(Settings, { size: 14 }), t("prompt.modal.manageModels", "Manage models"), effectiveAvailableModels.filter((m) => m.category === modelCategory &&
347
356
  !effectiveUserModels.includes(m.id)).length > 0 && (_jsxs("span", { style: {
348
357
  marginLeft: "2px",
349
358
  padding: "2px 6px",
@@ -353,42 +362,44 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
353
362
  borderRadius: "10px",
354
363
  fontWeight: "600",
355
364
  }, children: ["+", effectiveAvailableModels.filter((m) => m.category === modelCategory &&
356
- !effectiveUserModels.includes(m.id)).length] }))] }))] }), _jsxs("select", { id: "model-select", value: activeModelId, onChange: (e) => setSelectedModel(e.target.value), onFocus: () => setModelFocused(true), onBlur: () => setModelFocused(false), style: {
365
+ !effectiveUserModels.includes(m.id)).length] }))] })) : null] }), _jsxs("select", { id: "model-select", value: activeModelId, onChange: (e) => setSelectedModel(e.target.value), onFocus: () => setModelFocused(true), onBlur: () => setModelFocused(false), style: {
357
366
  ...aiStyles.select,
358
367
  ...(modelFocused && aiStyles.selectFocus),
359
368
  }, children: [modelOptions.length === 0 && (_jsx("option", { value: "", children: showOnlyUserModels
360
- ? "No active models. Open 'Gérer les modèles'."
361
- : "Loading models..." })), modelOptions.map((model) => {
369
+ ? t("prompt.modal.noActiveModels", "No active models. Open 'Manage models'.")
370
+ : t("prompt.modal.loadingModels", "Loading models...") })), modelOptions.map((model) => {
362
371
  const isActive = effectiveUserModels.includes(model.id);
363
372
  return (_jsxs("option", { value: model.id, style: {
364
373
  opacity: showAllModels && !isActive ? 0.6 : 1,
365
- }, children: [model.name, showAllModels && isActive && " ✓", showAllModels && !isActive && " (Désactivé)"] }, model.id));
366
- })] }), showAllModels && onModelToggle && (_jsx("div", { style: {
374
+ }, children: [model.name, showAllModels && isActive && " ✓", showAllModels && !isActive
375
+ ? ` (${t("common.inactive", "Inactive")})`
376
+ : ""] }, model.id));
377
+ })] }), showAllModels && onModelToggle && (_jsxs("div", { style: {
367
378
  fontSize: "11px",
368
379
  color: "var(--ai-text-secondary)",
369
380
  marginTop: "4px",
370
- }, children: "\uD83D\uDCA1 Cliquez sur \"\u2699\uFE0F\" pour activer/d\u00E9sactiver les mod\u00E8les" }))] })) : (
381
+ }, children: ["\uD83D\uDCA1", " ", t("prompt.modal.clickSettingsHint", "Click ⚙️ to enable/disable models")] }))] })) : (
371
382
  // Version classique
372
383
  _jsxs(_Fragment, { children: [_jsx("label", { htmlFor: "model-select", style: aiStyles.modalLabel, children: "AI Model" }), _jsxs("select", { id: "model-select", value: activeModelId, onChange: (e) => setSelectedModel(e.target.value), onFocus: () => setModelFocused(true), onBlur: () => setModelFocused(false), style: {
373
384
  ...aiStyles.select,
374
385
  ...(modelFocused && aiStyles.selectFocus),
375
- }, children: [models.length === 0 && (_jsx("option", { value: "", children: "Loading models..." })), models.map((model) => (_jsx("option", { value: model.id, children: model.name }, model.id)))] })] })) }), _jsxs("div", { style: aiStyles.modalInputGroup, children: [_jsxs("div", { style: {
386
+ }, children: [models.length === 0 && (_jsx("option", { value: "", children: t("prompt.modal.loadingModels", "Loading models...") })), models.map((model) => (_jsx("option", { value: model.id, children: model.name }, model.id)))] })] })) }), _jsxs("div", { style: aiStyles.modalInputGroup, children: [_jsxs("div", { style: {
376
387
  display: "flex",
377
388
  justifyContent: "space-between",
378
389
  alignItems: "center",
379
390
  marginBottom: "8px",
380
- }, children: [_jsxs("label", { htmlFor: "prompt-input", style: aiStyles.modalLabel, children: ["Prompt", _jsx("span", { style: {
391
+ }, children: [_jsxs("label", { htmlFor: "prompt-input", style: aiStyles.modalLabel, children: [t("prompt.modal.prompt", "Prompt"), _jsx("span", { style: {
381
392
  color: "var(--ai-text-secondary)",
382
393
  marginLeft: "4px",
383
394
  fontSize: "12px",
384
395
  fontWeight: 400,
385
- }, children: "(Cmd/Ctrl + Enter to submit)" })] }), filteredPrompts.length > 0 && (_jsxs("button", { onClick: () => setShowPromptLibrary(true), className: "ai-inline-btn", children: [_jsx(BookOpen, { size: 14 }), "Browse Prompts (", filteredPrompts.length, ")"] }))] }), _jsx("textarea", { id: "prompt-input", ref: promptRef, value: prompt, onChange: (e) => setPrompt(e.target.value), onFocus: () => setPromptFocused(true), onBlur: () => setPromptFocused(false), placeholder: sourceText
386
- ? "Enter your AI prompt... e.g., 'Correct spelling and grammar', 'Make it more professional', 'Translate to English'"
387
- : "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'", rows: 6, style: {
396
+ }, children: t("prompt.modal.promptHint", "(Cmd/Ctrl + Enter to submit)") })] }), promptsLoading ? (_jsx("span", { className: "ai-inline-skeleton", "aria-hidden": "true" })) : filteredPrompts.length > 0 ? (_jsxs("button", { onClick: () => setShowPromptLibrary(true), className: "ai-inline-btn", children: [_jsx(BookOpen, { size: 14 }), t("prompt.modal.browsePrompts", "Browse Prompts"), " (", filteredPrompts.length, ")"] })) : null] }), _jsx("textarea", { id: "prompt-input", ref: promptRef, value: prompt, onChange: (e) => setPrompt(e.target.value), onFocus: () => setPromptFocused(true), onBlur: () => setPromptFocused(false), placeholder: sourceText
397
+ ? t("prompt.modal.promptPlaceholderWithSource", "Enter your AI prompt... e.g., 'Fix grammar', 'Make it more professional', 'Translate to English'")
398
+ : t("prompt.modal.promptPlaceholderNoSource", "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'"), rows: 6, style: {
388
399
  ...aiStyles.textarea,
389
400
  padding: "12px 16px",
390
401
  ...(promptFocused && aiStyles.textareaFocus),
391
- } })] })] })) : (_jsxs("div", { children: [_jsx("button", { onClick: () => setShowPromptLibrary(false), style: {
402
+ } })] })] })) : (_jsxs("div", { children: [_jsxs("button", { onClick: () => setShowPromptLibrary(false), style: {
392
403
  padding: "8px 0",
393
404
  fontSize: "14px",
394
405
  color: "var(--ai-primary)",
@@ -399,7 +410,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
399
410
  display: "flex",
400
411
  alignItems: "center",
401
412
  gap: "4px",
402
- }, children: "\u2190 Back to form" }), _jsxs("div", { style: { marginBottom: "12px" }, children: [_jsxs("div", { style: {
413
+ }, children: ["\u2190 ", t("prompt.modal.backToFormNoArrow", "Back to form")] }), _jsxs("div", { style: { marginBottom: "12px" }, children: [_jsxs("div", { style: {
403
414
  ...aiStyles.inputWrapper,
404
415
  marginBottom: "10px",
405
416
  }, children: [_jsx(Search, { size: 16, style: {
@@ -408,7 +419,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
408
419
  top: "50%",
409
420
  transform: "translateY(-50%)",
410
421
  color: aiStyles.tooltipLabel.color,
411
- } }), _jsx("input", { value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search prompts...", style: {
422
+ } }), _jsx("input", { value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: t("prompt.modal.searchPrompts", "Search prompts..."), style: {
412
423
  ...aiStyles.input,
413
424
  padding: "10px 12px 10px 36px",
414
425
  } })] }), availableTags.length > 0 && (_jsxs("div", { style: {
@@ -422,7 +433,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
422
433
  gap: "6px",
423
434
  fontSize: "12px",
424
435
  color: aiStyles.tooltipLabel.color,
425
- }, children: [_jsx(Tag, { size: 14 }), "Tags"] }), _jsx("button", { onClick: () => setSelectedTag("all"), style: {
436
+ }, children: [_jsx(Tag, { size: 14 }), t("prompt.modal.tags", "Tags")] }), _jsx("button", { onClick: () => setSelectedTag("all"), style: {
426
437
  ...aiStyles.chip,
427
438
  borderColor: selectedTag === "all"
428
439
  ? "var(--ai-primary)"
@@ -430,7 +441,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
430
441
  background: selectedTag === "all"
431
442
  ? "color-mix(in srgb, var(--ai-primary) 20%, transparent)"
432
443
  : aiStyles.chip.background,
433
- }, children: "All" }), availableTags.map((tag) => (_jsx("button", { onClick: () => setSelectedTag(tag), style: {
444
+ }, children: t("common.all", "All") }), availableTags.map((tag) => (_jsx("button", { onClick: () => setSelectedTag(tag), style: {
434
445
  ...aiStyles.chip,
435
446
  borderColor: selectedTag === tag
436
447
  ? "var(--ai-primary)"
@@ -438,11 +449,17 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
438
449
  background: selectedTag === tag
439
450
  ? "color-mix(in srgb, var(--ai-primary) 20%, transparent)"
440
451
  : aiStyles.chip.background,
441
- }, children: tag }, tag)))] }))] }), promptsLoading ? (_jsx("div", { style: { textAlign: "center", padding: "40px 0" }, children: "Loading prompts..." })) : visiblePrompts.length === 0 ? (_jsx("div", { style: {
452
+ }, children: tag }, tag)))] }))] }), promptsLoading ? (_jsx("div", { style: {
453
+ display: "flex",
454
+ flexDirection: "column",
455
+ gap: "12px",
456
+ maxHeight: "400px",
457
+ overflow: "auto",
458
+ }, children: Array.from({ length: 4 }).map((_, idx) => (_jsxs("div", { className: "ai-list-skeleton", children: [_jsx("div", { className: "ai-list-skeleton__line ai-list-skeleton__line--lg" }), _jsx("div", { className: "ai-list-skeleton__line ai-list-skeleton__line--md" })] }, `prompt-skeleton-${idx}`))) })) : visiblePrompts.length === 0 ? (_jsx("div", { style: {
442
459
  textAlign: "center",
443
460
  padding: "40px 0",
444
461
  color: "var(--ai-text-secondary)",
445
- }, children: "No prompts available for this model type" })) : (_jsxs("div", { style: {
462
+ }, children: t("prompt.modal.noPrompts", "No prompts available for this model type") })) : (_jsxs("div", { style: {
446
463
  display: "flex",
447
464
  flexDirection: "column",
448
465
  gap: "12px",
@@ -454,7 +471,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
454
471
  fontWeight: 600,
455
472
  textTransform: "uppercase",
456
473
  marginBottom: "8px",
457
- }, children: "Favorites" }), _jsx("div", { style: {
474
+ }, children: t("prompt.modal.favorites", "Favorites") }), _jsx("div", { style: {
458
475
  display: "flex",
459
476
  flexDirection: "column",
460
477
  gap: "12px",
@@ -499,7 +516,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
499
516
  textTransform: "uppercase",
500
517
  marginTop: favoritePrompts.length > 0 ? "12px" : undefined,
501
518
  marginBottom: "8px",
502
- }, children: "All prompts" }), _jsx("div", { style: {
519
+ }, children: t("prompt.modal.allPrompts", "All prompts") }), _jsx("div", { style: {
503
520
  display: "flex",
504
521
  flexDirection: "column",
505
522
  gap: "12px",
@@ -540,7 +557,11 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
540
557
  bottom: 0,
541
558
  zIndex: 5,
542
559
  backdropFilter: "blur(8px)",
543
- }, children: [_jsx("button", { onClick: handleClose, className: "ai-btn ai-btn--ghost", children: "Cancel" }), _jsx("button", { onClick: handleSubmit, disabled: !selectedModel || !prompt.trim(), className: "ai-btn ai-btn--primary", children: sourceText ? "Transform with AI" : "Generate with AI" })] })] }), enableModelManagement && isModelManagementOpen && (_jsxs("div", { style: {
560
+ }, children: [_jsx("button", { onClick: handleClose, className: "ai-btn ai-btn--ghost", children: t("prompt.modal.cancel", "Cancel") }), _jsx("button", { onClick: handleSubmit, disabled: isGenerating || !selectedModel || !prompt.trim(), className: "ai-btn ai-btn--primary", children: isGenerating ? (_jsxs("span", { className: "ai-loading-stack", children: [_jsxs("span", { className: "ai-loading-row", children: [_jsx(Loader2, { size: 16, className: "ai-spinner" }), _jsx("span", { children: sourceText
561
+ ? t("prompt.modal.transforming", "Transforming...")
562
+ : t("prompt.modal.generating", "Generating...") })] }), _jsx("span", { className: "ai-loading-meta", children: t("ai.loading.elapsed", "{seconds}", {
563
+ seconds: loadingElapsed,
564
+ }) })] })) : sourceText ? (t("prompt.modal.transform", "Transform with AI")) : (t("prompt.modal.generate", "Generate with AI")) })] })] }), enableModelManagement && isModelManagementOpen && (_jsxs("div", { style: {
544
565
  ...aiStyles.modal,
545
566
  zIndex: 2147483646, // Au-dessus du modal principal
546
567
  }, children: [_jsx("div", { style: {
@@ -557,10 +578,10 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
557
578
  display: "flex",
558
579
  flexDirection: "column",
559
580
  boxShadow: "var(--ai-shadow-lg)",
560
- }, children: [_jsxs("div", { style: aiStyles.modalHeader, children: [_jsx("h2", { style: aiStyles.modalTitle, children: "Gestion des mod\u00E8les IA" }), _jsx("button", { style: aiStyles.modalCloseButton, onClick: () => {
581
+ }, children: [_jsxs("div", { style: aiStyles.modalHeader, children: [_jsx("h2", { style: aiStyles.modalTitle, children: t("prompt.modal.modelMgmtTitle", "AI Model Management") }), _jsx("button", { style: aiStyles.modalCloseButton, onClick: () => {
561
582
  setIsModelManagementOpen(false);
562
583
  setModelSearchQuery("");
563
- }, "aria-label": "Close", children: "\u00D7" })] }), _jsxs("div", { style: {
584
+ }, "aria-label": t("common.closeLabel", "Close"), children: "\u00D7" })] }), _jsxs("div", { style: {
564
585
  ...aiStyles.modalBody,
565
586
  flex: 1,
566
587
  overflow: "auto",
@@ -568,11 +589,14 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
568
589
  fontSize: "14px",
569
590
  color: "var(--ai-muted)",
570
591
  margin: "0 0 8px 0",
571
- }, children: "Activez ou d\u00E9sactivez les mod\u00E8les selon vos besoins" }), _jsxs("p", { style: {
592
+ }, children: t("prompt.modal.modelMgmtSubtitle", "Enable or disable models based on your needs") }), _jsx("p", { style: {
572
593
  fontSize: "12px",
573
594
  color: "var(--ai-text-tertiary)",
574
595
  margin: "0 0 16px 0",
575
- }, children: [effectiveAvailableModels.filter((m) => m.category === modelCategory).length, " ", "mod\u00E8les disponibles \u2022", " ", effectiveUserModels.filter((id) => effectiveAvailableModels.some((m) => m.id === id && m.category === modelCategory)).length, " ", "activ\u00E9s"] }), _jsxs("div", { style: {
596
+ }, children: t("prompt.modal.modelAvailableCount", "{available} models available {active} enabled", {
597
+ available: effectiveAvailableModels.filter((m) => m.category === modelCategory).length,
598
+ active: effectiveUserModels.filter((id) => effectiveAvailableModels.some((m) => m.id === id && m.category === modelCategory)).length,
599
+ }) }), _jsxs("div", { style: {
576
600
  ...aiStyles.inputWrapper,
577
601
  marginBottom: "16px",
578
602
  }, children: [_jsx(Search, { size: 16, style: {
@@ -581,11 +605,11 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
581
605
  top: "50%",
582
606
  transform: "translateY(-50%)",
583
607
  color: "var(--ai-text-tertiary)",
584
- } }), _jsx("input", { value: modelSearchQuery, onChange: (e) => setModelSearchQuery(e.target.value), placeholder: "Rechercher un mod\u00E8le...", style: {
608
+ } }), _jsx("input", { value: modelSearchQuery, onChange: (e) => setModelSearchQuery(e.target.value), placeholder: t("prompt.modal.searchModel", "Search a model..."), style: {
585
609
  ...aiStyles.input,
586
610
  padding: "10px 12px 10px 36px",
587
611
  background: "var(--ai-bg)",
588
- } })] })] }), _jsxs("div", { className: "ai-model-mgmt-list", children: [effectiveAvailableModels
612
+ } })] })] }), _jsxs("div", { className: "ai-model-mgmt-list", children: [isModelsLoading ? (_jsx(_Fragment, { children: Array.from({ length: 4 }).map((_, idx) => (_jsxs("div", { className: "ai-list-skeleton", children: [_jsx("div", { className: "ai-list-skeleton__line ai-list-skeleton__line--lg" }), _jsx("div", { className: "ai-list-skeleton__line ai-list-skeleton__line--md" })] }, `model-skeleton-${idx}`))) })) : null, effectiveAvailableModels
589
613
  .filter((model) => {
590
614
  if (model.category !== modelCategory)
591
615
  return false;
@@ -624,24 +648,25 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
624
648
  height: 0,
625
649
  position: "absolute",
626
650
  } }), _jsx("span", { className: `ai-toggle ${isActive ? "ai-toggle--active" : ""}` })] })] }, modelData.id));
627
- }), effectiveAvailableModels.filter((model) => {
628
- if (model.category !== modelCategory)
629
- return false;
630
- if (!modelSearchQuery.trim())
631
- return true;
632
- const query = modelSearchQuery.toLowerCase();
633
- return (model.name.toLowerCase().includes(query) ||
634
- model.provider.toLowerCase().includes(query) ||
635
- model.description?.toLowerCase().includes(query));
636
- }).length === 0 && (_jsx("div", { style: {
651
+ }), !isModelsLoading &&
652
+ effectiveAvailableModels.filter((model) => {
653
+ if (model.category !== modelCategory)
654
+ return false;
655
+ if (!modelSearchQuery.trim())
656
+ return true;
657
+ const query = modelSearchQuery.toLowerCase();
658
+ return (model.name.toLowerCase().includes(query) ||
659
+ model.provider.toLowerCase().includes(query) ||
660
+ model.description?.toLowerCase().includes(query));
661
+ }).length === 0 && (_jsx("div", { style: {
637
662
  textAlign: "center",
638
663
  padding: "32px 16px",
639
664
  color: "var(--ai-text-tertiary)",
640
665
  fontSize: "14px",
641
666
  }, children: modelSearchQuery.trim()
642
- ? "Aucun modèle ne correspond à votre recherche"
643
- : "Aucun modèle disponible" }))] })] }), _jsx("div", { style: aiStyles.modalFooter, children: _jsx("button", { onClick: () => {
667
+ ? t("prompt.modal.noModelMatch", "No model matches your search")
668
+ : t("prompt.modal.noModelAvailable", "No models available") }))] })] }), _jsx("div", { style: aiStyles.modalFooter, children: _jsx("button", { onClick: () => {
644
669
  setIsModelManagementOpen(false);
645
670
  setModelSearchQuery("");
646
- }, className: "ai-btn ai-btn--ghost", children: "Fermer" }) })] })] }))] }), portalRoot);
671
+ }, className: "ai-btn ai-btn--ghost", children: t("prompt.modal.closeModelMgmt", "Close") }) })] })] }))] }), portalRoot);
647
672
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAU9D,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACnE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAe,EACf,OAAO,EACP,KAAK,EACL,MAAM,EACN,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CA4If"}
1
+ {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAW9D,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACnE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAe,EACf,OAAO,EACP,KAAK,EACL,MAAM,EACN,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CAgJf"}
@@ -11,7 +11,9 @@ import { LBSigninModal } from "./LBSigninModal";
11
11
  import { handleAIError } from "../utils/errorHandler";
12
12
  import { useLB } from "../context/LBAuthProvider";
13
13
  import { useAiContext } from "../context/AiProvider";
14
+ import { useI18n } from "../context/I18nContext";
14
15
  export function AiSelect({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode = "modal", size = "md", radius = "full", context, model, prompt, storeOutputs, artifactTitle, onValue, onToast, disabled, className, children, ...selectProps }) {
16
+ const { t } = useI18n();
15
17
  const [isOpen, setIsOpen] = useState(false);
16
18
  const [showAuthModal, setShowAuthModal] = useState(false);
17
19
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
@@ -68,7 +70,10 @@ export function AiSelect({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode
68
70
  });
69
71
  if (result.text) {
70
72
  onValue?.(result.text);
71
- onToast?.({ type: "success", message: "AI suggestion ready" });
73
+ onToast?.({
74
+ type: "success",
75
+ message: t("ai.select.suggestionReady", "AI suggestion ready"),
76
+ });
72
77
  showUsageToast(result);
73
78
  }
74
79
  }
@@ -82,6 +87,6 @@ export function AiSelect({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode
82
87
  const sizeClass = `ai-size-${size}`;
83
88
  const radiusClass = `ai-radius-${radius}`;
84
89
  return (_jsxs("div", { className: `ai-control-group ai-glow ${className || ""}`, children: [_jsxs("div", { className: `ai-shell ${sizeClass} ${radiusClass}`, children: [_jsx("select", { ...selectProps, className: `ai-control ai-control-input ai-control-select ${sizeClass} ${radiusClass}`, disabled: disabled || loading, children: children }), _jsx("span", { className: "ai-control-select-chevron", "aria-hidden": "true", children: _jsx(ChevronDown, { size: 14 }) }), _jsx("button", { className: `ai-control-action ai-spark ${sizeClass} ${radiusClass}`, onClick: handleOpenPanel, title: shouldShowSparkles
85
- ? "Générer avec l'IA"
86
- : "Se connecter pour utiliser l'IA", disabled: disabled || loading, type: "button", children: loading ? (_jsx(Loader2, { size: 16, className: "ai-spinner" })) : shouldShowSparkles ? (_jsx(Sparkles, { size: 16 })) : (_jsx(Lock, { size: 16 })) })] }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey)), _jsx(LBSigninModal, { isOpen: showAuthModal, onClose: () => setShowAuthModal(false) })] }));
90
+ ? t("ai.generate", "Generate with AI")
91
+ : t("auth.connectToUseAi", "Sign in to use AI"), disabled: disabled || loading, type: "button", children: loading ? (_jsx(Loader2, { size: 16, className: "ai-spinner" })) : shouldShowSparkles ? (_jsx(Sparkles, { size: 16 })) : (_jsx(Lock, { size: 16 })) })] }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey)), _jsx(LBSigninModal, { isOpen: showAuthModal, onClose: () => setShowAuthModal(false) })] }));
87
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,uBAAuB,CAAC;AAuB9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAmHD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,EACd,IAAW,EACX,MAAe,GAChB,EAAE,mBAAmB,2CA+gBrB"}
1
+ {"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,uBAAuB,CAAC;AAuB9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAmHD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,EACd,IAAW,EACX,MAAe,GAChB,EAAE,mBAAmB,2CAyhBrB"}
@@ -8,30 +8,31 @@ import { LBContext, } from "../context/LBAuthProvider";
8
8
  import { AiContext } from "../context/AiProvider";
9
9
  import { LBApiKeySelector } from "./LBApiKeySelector";
10
10
  import { LBSigninModal } from "./LBSigninModal";
11
+ import { useI18n } from "../context/I18nContext";
11
12
  const QUICK_LINKS = [
12
13
  {
13
14
  href: "https://prompt.lastbrain.io/auth/ai/tokens",
14
- title: "Dashboard",
15
+ titleKey: "status.dashboard",
15
16
  icon: BarChart3,
16
17
  },
17
18
  {
18
19
  href: "https://prompt.lastbrain.io/auth/ai/history",
19
- title: "Historique",
20
+ titleKey: "status.history",
20
21
  icon: History,
21
22
  },
22
23
  {
23
24
  href: "https://prompt.lastbrain.io/auth/ai/settings",
24
- title: "Settings",
25
+ titleKey: "status.settings",
25
26
  icon: Settings,
26
27
  },
27
28
  {
28
29
  href: "https://prompt.lastbrain.io/auth/ai/prompts",
29
- title: "Prompts",
30
+ titleKey: "status.prompts",
30
31
  icon: FileText,
31
32
  },
32
33
  {
33
34
  href: "https://prompt.lastbrain.io/auth/folder",
34
- title: "Dossiers",
35
+ titleKey: "status.folders",
35
36
  icon: Folder,
36
37
  },
37
38
  ];
@@ -64,6 +65,7 @@ function UsageCircle({ percentage }) {
64
65
  : "ai-usage-circle-text--low", fontWeight: "700", children: [safe.toFixed(0), "%"] })] }));
65
66
  }
66
67
  export function AiStatusButton({ status, loading = false, className = "", size = "md", radius = "full", }) {
68
+ const { t } = useI18n();
67
69
  let lbStatus;
68
70
  let user = null;
69
71
  let logout;
@@ -272,28 +274,29 @@ export function AiStatusButton({ status, loading = false, className = "", size =
272
274
  .filter(Boolean)
273
275
  .join(" ");
274
276
  const tooltipNode = showTooltip && canPortal
275
- ? createPortal(_jsx("div", { ref: tooltipRef, className: "ai-popover ai-tooltip ai-status-tooltip", style: tooltipStyle, onMouseEnter: () => setShowTooltip(true), onMouseLeave: closeTooltip, children: lbStatus === "ready" && user ? (_jsx(_Fragment, { children: _jsxs("div", { className: "ai-popover-body", children: [_jsx("div", { className: "ai-popover-header", children: "API Status" }), _jsx("div", { className: "ai-popover-section ai-popover-section--first", children: _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "User" }), _jsx("span", { className: "ai-popover-value ai-truncate max-w-[200px]", children: user.email })] }) }), _jsxs("div", { className: "ai-popover-section", children: [_jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "API Key" }), _jsxs("div", { className: "ai-row", children: [lbIsLoadingStatus || isImplicitStatusLoading ? (_jsx("div", { className: "ai-kv-skeleton w-[110px]" })) : (_jsx("span", { className: "ai-popover-value", children: effectiveStatus?.apiKey?.name ||
277
+ ? createPortal(_jsx("div", { ref: tooltipRef, className: "ai-popover ai-tooltip ai-status-tooltip", style: tooltipStyle, onMouseEnter: () => setShowTooltip(true), onMouseLeave: closeTooltip, children: lbStatus === "ready" && user ? (_jsx(_Fragment, { children: _jsxs("div", { className: "ai-popover-body", children: [_jsx("div", { className: "ai-popover-header", children: t("status.title", "API Status") }), _jsx("div", { className: "ai-popover-section ai-popover-section--first", children: _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.user", "User") }), _jsx("span", { className: "ai-popover-value ai-truncate", style: { maxWidth: 200 }, children: user.email })] }) }), _jsxs("div", { className: "ai-popover-section", children: [_jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.apiKey", "API Key") }), _jsxs("div", { className: "ai-row", children: [lbIsLoadingStatus || isImplicitStatusLoading ? (_jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--110" })) : (_jsx("span", { className: "ai-popover-value", children: effectiveStatus?.apiKey?.name ||
276
278
  effectiveStatus?.api_key?.name ||
277
- "Unknown" })), switchApiKey &&
279
+ t("common.unknown", "Unknown") })), switchApiKey &&
278
280
  !isApiKeyAuthMode &&
279
281
  !isImplicitStatusLoading ? (_jsx("button", { type: "button", className: "ai-icon-btn", onClick: (e) => {
280
282
  e.stopPropagation();
281
283
  setShowTooltip(false);
282
284
  setShowApiKeySelector(true);
283
- }, title: "Changer de cl\u00E9 API", children: _jsx(ArrowRightLeft, { size: 12 }) })) : null] })] }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "Env" }), (lbIsLoadingStatus || isImplicitStatusLoading) &&
284
- !effectiveStatus?.apiKey?.env ? (_jsx("div", { className: "ai-kv-skeleton w-12" })) : (_jsx("span", { className: "ai-popover-value", children: effectiveStatus?.apiKey?.env ||
285
+ }, title: t("status.changeApiKey", "Switch API key"), children: _jsx(ArrowRightLeft, { size: 12 }) })) : null] })] }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.env", "Env") }), (lbIsLoadingStatus || isImplicitStatusLoading) &&
286
+ !effectiveStatus?.apiKey?.env ? (_jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--48" })) : (_jsx("span", { className: "ai-popover-value", children: effectiveStatus?.apiKey?.env ||
285
287
  effectiveStatus?.api_key?.env ||
286
- "N/A" }))] }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "Rate Limit" }), (lbIsLoadingStatus || isImplicitStatusLoading) &&
287
- !effectiveStatus?.apiKey?.rate_limit_rpm ? (_jsx("div", { className: "ai-kv-skeleton w-[92px]" })) : (_jsxs("span", { className: "ai-popover-value", children: [effectiveStatus?.apiKey?.rate_limit_rpm ||
288
+ "N/A" }))] }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.rateLimit", "Rate Limit") }), (lbIsLoadingStatus || isImplicitStatusLoading) &&
289
+ !effectiveStatus?.apiKey?.rate_limit_rpm ? (_jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--92" })) : (_jsxs("span", { className: "ai-popover-value", children: [effectiveStatus?.apiKey?.rate_limit_rpm ||
288
290
  effectiveStatus?.api_key?.rate_limit_rpm ||
289
- 0, " ", "req/min"] }))] }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "Auth" }), _jsx("span", { className: "ai-popover-value", children: lbIsLoadingStatus || isImplicitStatusLoading
291
+ 0, " ", "req/min"] }))] }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.auth", "Auth") }), _jsx("span", { className: "ai-popover-value", children: lbIsLoadingStatus || isImplicitStatusLoading
290
292
  ? "..."
291
293
  : authTypeValue ||
292
294
  lbStatus ||
293
- "unknown" })] })] }), _jsxs("div", { className: "ai-popover-section", children: [_jsx("div", { className: "ai-popover-header text-xs mb-2", children: "Wallet" }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "Total" }), (lbIsLoadingStatus || isImplicitStatusLoading) &&
294
- !effectiveStatus?.balance ? (_jsxs("div", { className: "ai-row", children: [_jsx("div", { className: "ai-kv-skeleton w-[120px]" }), _jsx("div", { className: "ai-kv-skeleton w-7 h-7 rounded-full" })] })) : (_jsxs("div", { className: "ai-row", children: [_jsxs("span", { className: "ai-popover-value", children: ["$", fixed(balanceUsed, 2), " / $", fixed(balanceTotal, 2)] }), _jsx(UsageCircle, { percentage: balancePct })] }))] })] }), _jsxs("div", { className: "ai-popover-section", children: [_jsx("div", { className: "ai-popover-header text-xs mb-2", children: "Storage" }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: "Total" }), lbIsLoadingStorage || isImplicitStorageLoading ? (_jsxs("div", { className: "ai-row", children: [_jsx("div", { className: "ai-kv-skeleton w-[120px]" }), _jsx("div", { className: "ai-kv-skeleton w-7 h-7 rounded-full" })] })) : (_jsxs("div", { className: "ai-row", children: [_jsxs("span", { className: "ai-popover-value", children: [formatStorage(storageUsed), " /", " ", formatStorage(storageTotal)] }), _jsx(UsageCircle, { percentage: storagePct })] }))] })] }), _jsxs("div", { className: "ai-status-actions", children: [QUICK_LINKS.map((item) => {
295
+ t("status.unknown", "unknown") })] })] }), _jsxs("div", { className: "ai-popover-section", children: [_jsx("div", { className: "ai-popover-header ai-popover-header--sub", children: t("status.wallet", "Wallet") }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.total", "Total") }), (lbIsLoadingStatus || isImplicitStatusLoading) &&
296
+ !effectiveStatus?.balance ? (_jsxs("div", { className: "ai-row", children: [_jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--120" }), _jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--circle" })] })) : (_jsxs("div", { className: "ai-row", children: [_jsxs("span", { className: "ai-popover-value", children: ["$", fixed(balanceUsed, 2), " / $", fixed(balanceTotal, 2)] }), _jsx(UsageCircle, { percentage: balancePct })] }))] })] }), _jsxs("div", { className: "ai-popover-section", children: [_jsx("div", { className: "ai-popover-header ai-popover-header--sub", children: t("status.storage", "Storage") }), _jsxs("div", { className: "ai-popover-row", children: [_jsx("span", { className: "ai-popover-label", children: t("status.total", "Total") }), lbIsLoadingStorage || isImplicitStorageLoading ? (_jsxs("div", { className: "ai-row", children: [_jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--120" }), _jsx("div", { className: "ai-kv-skeleton ai-kv-skeleton--circle" })] })) : (_jsxs("div", { className: "ai-row", children: [_jsxs("span", { className: "ai-popover-value", children: [formatStorage(storageUsed), " /", " ", formatStorage(storageTotal)] }), _jsx(UsageCircle, { percentage: storagePct })] }))] })] }), _jsxs("div", { className: "ai-status-actions", children: [QUICK_LINKS.map((item) => {
295
297
  const Icon = item.icon;
296
- return (_jsx("button", { type: "button", className: "ai-status-action-btn", onClick: () => window.open(item.href, "_blank"), title: item.title, children: _jsx(Icon, { size: 17 }) }, item.href));
298
+ const label = t(item.titleKey, item.titleKey);
299
+ return (_jsx("button", { type: "button", className: "ai-status-action-btn", onClick: () => window.open(item.href, "_blank"), title: label, "data-ai-tip": label, children: _jsx(Icon, { size: 17 }) }, item.href));
297
300
  }), logout && !isApiKeyAuthMode ? (_jsx("button", { type: "button", className: "ai-status-action-btn ai-status-action-btn--danger", onClick: async () => {
298
301
  try {
299
302
  await logout();
@@ -305,12 +308,12 @@ export function AiStatusButton({ status, loading = false, className = "", size =
305
308
  catch (error) {
306
309
  console.error("Logout failed:", error);
307
310
  }
308
- }, title: "Logout", children: _jsx(LogOut, { size: 17 }) })) : null] })] }) })) : (_jsxs("div", { className: "ai-popover-body", children: [_jsx("div", { className: "ai-popover-header", children: "LastBrain Authentication" }), _jsx("p", { className: "ai-signin-subtitle mt-0", children: "Connectez-vous pour acc\u00E9der aux fonctionnalit\u00E9s IA." }), _jsx("button", { type: "button", className: "ai-btn ai-btn--auth w-full mt-2", onClick: () => {
311
+ }, title: t("status.logout", "Logout"), "data-ai-tip": t("status.logout", "Logout"), children: _jsx(LogOut, { size: 17 }) })) : null] })] }) })) : (_jsxs("div", { className: "ai-popover-body", children: [_jsx("div", { className: "ai-popover-header", children: t("status.lastbrainAuth", "LastBrain Authentication") }), _jsx("p", { className: "ai-signin-subtitle", style: { marginTop: 0 }, children: t("status.connectToAccess", "Sign in to access AI features.") }), _jsx("button", { type: "button", className: "ai-btn ai-btn--auth", style: { width: "100%", marginTop: 8 }, onClick: () => {
309
312
  setShowSigninModal(true);
310
313
  setShowTooltip(false);
311
- }, children: "Se connecter" })] })) }), document.body)
314
+ }, children: t("auth.signIn", "Sign in") })] })) }), document.body)
312
315
  : null;
313
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative inline-block", children: [_jsx("button", { ref: buttonRef, className: triggerClass, onMouseEnter: openTooltip, onMouseLeave: closeTooltip, onClick: () => {
316
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ref: buttonRef, className: triggerClass, onMouseEnter: openTooltip, onMouseLeave: closeTooltip, onClick: () => {
314
317
  if (requiresApiKeySelection) {
315
318
  setShowApiKeySelector(true);
316
319
  return;
@@ -319,8 +322,8 @@ export function AiStatusButton({ status, loading = false, className = "", size =
319
322
  setShowSigninModal(true);
320
323
  }
321
324
  }, disabled: loading || isSelectingApiKey, title: requiresApiKeySelection
322
- ? "Sélectionnez une clé API"
323
- : "Voir le status", "aria-label": "AI status", children: renderTriggerIcon() }), showCornerLoading ? (_jsx("span", { className: "ai-status-loading-dot", "aria-hidden": "true", children: _jsx(Loader2, { size: 7, className: "ai-spinner text-[var(--ai-primary)]" }) })) : null] }), tooltipNode, showApiKeySelector && apiKeys.length > 0 ? (_jsx(LBApiKeySelector, { isOpen: showApiKeySelector, apiKeys: apiKeys, onSelect: async (keyId) => {
325
+ ? t("status.selectApiKey", "Select an API key")
326
+ : t("status.view", "View status"), "aria-label": t("status.aiStatusAria", "AI status"), children: renderTriggerIcon() }), showCornerLoading ? (_jsx("span", { className: "ai-status-loading-dot", "aria-hidden": "true", children: _jsx(Loader2, { size: 7, className: "ai-spinner" }) })) : null] }), tooltipNode, showApiKeySelector && apiKeys.length > 0 ? (_jsx(LBApiKeySelector, { isOpen: showApiKeySelector, apiKeys: apiKeys, onSelect: async (keyId) => {
324
327
  setIsSelectingApiKey(true);
325
328
  try {
326
329
  if (!switchApiKey) {
@@ -1 +1 @@
1
- {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAU9D,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAa,EACb,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CA+NjB"}
1
+ {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAY9D,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAa,EACb,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CA+OjB"}
@@ -11,7 +11,10 @@ import { handleAIError } from "../utils/errorHandler";
11
11
  import { useLB } from "../context/LBAuthProvider";
12
12
  import { LBSigninModal } from "./LBSigninModal";
13
13
  import { useAiContext } from "../context/AiProvider";
14
+ import { useI18n } from "../context/I18nContext";
15
+ import { useLoadingTimer } from "../hooks/useLoadingTimer";
14
16
  export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode = "modal", size = "md", radius = "lg", context, model, prompt, editMode = false, enableModelManagement, storeOutputs, artifactTitle, onValue, onToast, disabled, className, ...textareaProps }) {
17
+ const { t } = useI18n();
15
18
  const [isOpen, setIsOpen] = useState(false);
16
19
  const [showAuthModal, setShowAuthModal] = useState(false);
17
20
  const [textareaValue, setTextareaValue] = useState(textareaProps.value?.toString() ||
@@ -48,6 +51,7 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
48
51
  modelType: "text-or-language",
49
52
  });
50
53
  const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
54
+ const { formatted: loadingElapsed } = useLoadingTimer(loading);
51
55
  const hasConfiguration = Boolean(model && prompt);
52
56
  const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
53
57
  const shouldShowSparkles = isAuthReady && !disabled;
@@ -82,12 +86,18 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
82
86
  textareaRef.current.value = result.text;
83
87
  }
84
88
  onValue?.(result.text);
85
- onToast?.({ type: "success", message: "AI generation successful" });
89
+ onToast?.({
90
+ type: "success",
91
+ message: t("ai.generationSuccess", "AI generation successful"),
92
+ });
86
93
  showUsageToast(result);
87
94
  }
88
95
  }
89
96
  catch (error) {
90
- onToast?.({ type: "error", message: "Failed to generate text" });
97
+ onToast?.({
98
+ type: "error",
99
+ message: t("ai.generationError", "Failed to generate text"),
100
+ });
91
101
  }
92
102
  finally {
93
103
  setIsOpen(false);
@@ -114,7 +124,10 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
114
124
  textareaRef.current.value = result.text;
115
125
  }
116
126
  onValue?.(result.text);
117
- onToast?.({ type: "success", message: "AI generation successful" });
127
+ onToast?.({
128
+ type: "success",
129
+ message: t("ai.generationSuccess", "AI generation successful"),
130
+ });
118
131
  showUsageToast(result);
119
132
  }
120
133
  }
@@ -146,8 +159,8 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
146
159
  }, onBlur: (e) => {
147
160
  textareaProps.onBlur?.(e);
148
161
  }, "aria-invalid": Boolean(textareaProps["aria-invalid"]), disabled: disabled || loading }), _jsx("button", { className: `ai-control-action ai-spark ${sizeClass} ${radiusClass}`, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, disabled: disabled || loading || !isAuthReady, type: "button", title: !isAuthReady
149
- ? "Authentication required"
162
+ ? t("auth.required", "Authentication required")
150
163
  : hasConfiguration
151
- ? "Generate with AI"
152
- : "Setup AI", children: loading ? (_jsx(Loader2, { size: 16, className: "ai-spinner" })) : shouldShowSparkles ? (_jsx(Sparkles, { size: 16 })) : (_jsx(Lock, { size: 16 })) })] }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: [], modelCategory: "text", sourceText: textareaValue || undefined, baseUrl: baseUrl, apiKey: apiKeyId, enableModelManagement: enableModelManagement, showOnlyUserModels: true })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey)), _jsx(LBSigninModal, { isOpen: showAuthModal, onClose: () => setShowAuthModal(false) })] }));
164
+ ? t("ai.generate", "Generate with AI")
165
+ : t("ai.setup", "Setup AI"), children: loading ? (_jsx(Loader2, { size: 16, className: "ai-spinner" })) : shouldShowSparkles ? (_jsx(Sparkles, { size: 16 })) : (_jsx(Lock, { size: 16 })) })] }), loading ? (_jsx("span", { className: "ai-control-timer", children: t("ai.loading.elapsed", "{seconds}", { seconds: loadingElapsed }) })) : null, isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: [], modelCategory: "text", sourceText: textareaValue || undefined, baseUrl: baseUrl, apiKey: apiKeyId, enableModelManagement: enableModelManagement, showOnlyUserModels: true })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey)), _jsx(LBSigninModal, { isOpen: showAuthModal, onClose: () => setShowAuthModal(false) })] }));
153
166
  }