@lastbrain/ai-ui-react 1.0.74 → 1.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiContextButton.d.ts +1 -1
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +3 -2
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +6 -3
- package/dist/components/AiInput.d.ts +1 -1
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +4 -4
- package/dist/components/AiModelSelect.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +30 -6
- package/dist/components/AiSelect.d.ts +1 -1
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +1 -1
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +2 -2
- package/dist/components/AiTextarea.d.ts +2 -1
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +16 -6
- package/dist/components/ErrorToast.js +3 -3
- package/dist/components/LBConnectButton.d.ts.map +1 -1
- package/dist/components/LBConnectButton.js +1 -3
- package/dist/components/LBKeyPicker.js +9 -9
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/UsageToast.d.ts +3 -3
- package/dist/components/UsageToast.d.ts.map +1 -1
- package/dist/context/I18nContext.d.ts +1 -1
- package/dist/context/I18nContext.d.ts.map +1 -1
- package/dist/context/I18nContext.js +1 -1
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +12 -5
- package/dist/examples/AiImageGenerator.js +1 -1
- package/dist/hooks/useAiStatus.d.ts.map +1 -1
- package/dist/hooks/useAiStatus.js +43 -5
- package/dist/hooks/useLoadingTimer.d.ts.map +1 -1
- package/dist/hooks/useLoadingTimer.js +11 -7
- package/dist/styles.css +3 -3
- package/dist/utils/errorHandler.d.ts +2 -2
- package/dist/utils/errorHandler.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/AiChipLabel.tsx +1 -4
- package/src/components/AiContextButton.tsx +15 -6
- package/src/components/AiImageButton.tsx +10 -4
- package/src/components/AiInput.tsx +9 -4
- package/src/components/AiModelSelect.tsx +3 -1
- package/src/components/AiPromptPanel.tsx +83 -49
- package/src/components/AiSelect.tsx +2 -2
- package/src/components/AiStatusButton.tsx +48 -27
- package/src/components/AiTextarea.tsx +25 -9
- package/src/components/ErrorToast.tsx +3 -3
- package/src/components/LBApiKeySelector.tsx +5 -5
- package/src/components/LBConnectButton.tsx +1 -3
- package/src/components/LBKeyPicker.tsx +10 -10
- package/src/components/LBSigninModal.tsx +12 -9
- package/src/components/UsageToast.tsx +4 -4
- package/src/context/I18nContext.tsx +6 -2
- package/src/context/LBAuthProvider.tsx +12 -5
- package/src/examples/AiImageGenerator.tsx +1 -1
- package/src/hooks/useAiStatus.ts +47 -5
- package/src/hooks/useLoadingTimer.ts +14 -8
- package/src/styles.css +3 -3
- package/src/utils/errorHandler.ts +3 -3
- package/src/utils/modelManagement.ts +3 -3
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { useState, useEffect, useCallback, useContext, useMemo } from "react";
|
|
3
3
|
import { useAiClient } from "./useAiClient";
|
|
4
|
+
import { LBContext } from "../context/LBAuthProvider";
|
|
4
5
|
export function useAiStatus(options) {
|
|
5
6
|
const client = useAiClient(options);
|
|
7
|
+
const lbContext = useContext(LBContext);
|
|
6
8
|
const [status, setStatus] = useState(null);
|
|
7
9
|
const [loading, setLoading] = useState(false);
|
|
8
10
|
const [error, setError] = useState(null);
|
|
11
|
+
const statusFromLB = useMemo(() => {
|
|
12
|
+
if (!lbContext || lbContext.status !== "ready") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (lbContext.apiStatus) {
|
|
16
|
+
return lbContext.apiStatus;
|
|
17
|
+
}
|
|
18
|
+
if (lbContext.basicStatus) {
|
|
19
|
+
return {
|
|
20
|
+
...lbContext.basicStatus,
|
|
21
|
+
storage: lbContext.storageStatus?.storage ||
|
|
22
|
+
lbContext.basicStatus?.storage,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}, [lbContext]);
|
|
9
27
|
const fetchStatus = useCallback(async () => {
|
|
28
|
+
// If LBProvider is available, it already handles fast status + storage split.
|
|
29
|
+
if (lbContext && lbContext.status === "ready") {
|
|
30
|
+
try {
|
|
31
|
+
await lbContext.refreshBasicStatus();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Ignore: provider already tracks errors/status
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
10
38
|
console.log("[useAiStatus] Starting status fetch");
|
|
11
39
|
setLoading(true);
|
|
12
40
|
setError(null);
|
|
@@ -22,13 +50,23 @@ export function useAiStatus(options) {
|
|
|
22
50
|
finally {
|
|
23
51
|
setLoading(false);
|
|
24
52
|
}
|
|
25
|
-
}, [client]);
|
|
53
|
+
}, [client, lbContext]);
|
|
26
54
|
useEffect(() => {
|
|
55
|
+
if (statusFromLB) {
|
|
56
|
+
setStatus(statusFromLB);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
setError(null);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
27
61
|
fetchStatus();
|
|
28
|
-
}, [fetchStatus]);
|
|
62
|
+
}, [fetchStatus, statusFromLB]);
|
|
63
|
+
const isLoadingFromLB = !!lbContext &&
|
|
64
|
+
lbContext.status === "ready" &&
|
|
65
|
+
!statusFromLB &&
|
|
66
|
+
(lbContext.isLoadingStatus || lbContext.isLoadingStorage);
|
|
29
67
|
return {
|
|
30
|
-
status,
|
|
31
|
-
loading,
|
|
68
|
+
status: statusFromLB || status,
|
|
69
|
+
loading: isLoadingFromLB || loading,
|
|
32
70
|
error,
|
|
33
71
|
refetch: fetchStatus,
|
|
34
72
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLoadingTimer.d.ts","sourceRoot":"","sources":["../../src/hooks/useLoadingTimer.ts"],"names":[],"mappings":"AAWA,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO;;;
|
|
1
|
+
{"version":3,"file":"useLoadingTimer.d.ts","sourceRoot":"","sources":["../../src/hooks/useLoadingTimer.ts"],"names":[],"mappings":"AAWA,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO;;;EA0B9C"}
|
|
@@ -11,17 +11,21 @@ export function useLoadingTimer(active) {
|
|
|
11
11
|
const [seconds, setSeconds] = useState(0);
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
if (!active) {
|
|
14
|
-
setSeconds(0);
|
|
15
|
-
return;
|
|
14
|
+
const timeoutId = window.setTimeout(() => setSeconds(0), 0);
|
|
15
|
+
return () => window.clearTimeout(timeoutId);
|
|
16
16
|
}
|
|
17
|
-
setSeconds(0);
|
|
18
|
-
const
|
|
17
|
+
const resetId = window.setTimeout(() => setSeconds(0), 0);
|
|
18
|
+
const intervalId = window.setInterval(() => {
|
|
19
19
|
setSeconds((prev) => prev + 1);
|
|
20
20
|
}, 1000);
|
|
21
|
-
return () =>
|
|
21
|
+
return () => {
|
|
22
|
+
window.clearTimeout(resetId);
|
|
23
|
+
window.clearInterval(intervalId);
|
|
24
|
+
};
|
|
22
25
|
}, [active]);
|
|
26
|
+
const displaySeconds = active ? seconds : 0;
|
|
23
27
|
return {
|
|
24
|
-
seconds,
|
|
25
|
-
formatted: formatElapsed(
|
|
28
|
+
seconds: displaySeconds,
|
|
29
|
+
formatted: formatElapsed(displaySeconds),
|
|
26
30
|
};
|
|
27
31
|
}
|
package/dist/styles.css
CHANGED
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
align-items: center;
|
|
196
196
|
gap: 10px;
|
|
197
197
|
min-height: var(--ai-control-h, var(--ai-size-md-h));
|
|
198
|
-
padding: 0
|
|
198
|
+
padding: 0 8px;
|
|
199
199
|
border-radius: var(--ai-radius-current, var(--ai-radius-full));
|
|
200
200
|
border: 1px solid var(--ai-border);
|
|
201
201
|
background:
|
|
@@ -362,8 +362,8 @@
|
|
|
362
362
|
.ai-shell--textarea .ai-control-action,
|
|
363
363
|
.ai-shell--textarea .ai-spark {
|
|
364
364
|
position: absolute;
|
|
365
|
-
right:
|
|
366
|
-
bottom:
|
|
365
|
+
right: 8px;
|
|
366
|
+
bottom: 8px;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
/* Generic buttons */
|
|
@@ -6,7 +6,7 @@ export interface ParsedError {
|
|
|
6
6
|
/**
|
|
7
7
|
* Parse et uniformise la gestion des erreurs des composants AI
|
|
8
8
|
*/
|
|
9
|
-
export declare function parseAIError(error:
|
|
9
|
+
export declare function parseAIError(error: any): ParsedError;
|
|
10
10
|
/**
|
|
11
11
|
* Interface pour la callback de gestion d'erreur uniforme
|
|
12
12
|
*/
|
|
@@ -23,7 +23,7 @@ export interface ErrorToastCallback {
|
|
|
23
23
|
* @param onToast Callback externe optionnelle fournie par le parent
|
|
24
24
|
* @param showInternalToast Callback interne optionnelle pour afficher un toast dans le composant
|
|
25
25
|
*/
|
|
26
|
-
export declare function handleAIError(error:
|
|
26
|
+
export declare function handleAIError(error: any, onToast?: ErrorToastCallback, showInternalToast?: (error: {
|
|
27
27
|
message: string;
|
|
28
28
|
code?: string;
|
|
29
29
|
}) => void): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/errorHandler.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;CACzB;AAeD;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/errorHandler.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;CACzB;AAeD;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,WAAW,CA8CpD;AAyFD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAClE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,GAAG,EACV,OAAO,CAAC,EAAE,kBAAkB,EAC5B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,GACtE,IAAI,CAuBN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/ai-ui-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.76",
|
|
4
4
|
"description": "Headless React components for LastBrain AI UI Kit",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"lucide-react": "^0.257.0",
|
|
54
|
-
"@lastbrain/ai-ui-core": "1.0.
|
|
54
|
+
"@lastbrain/ai-ui-core": "1.0.55"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/react": "^19.2.0",
|
|
@@ -212,10 +212,7 @@ Exemple de réponse attendue: javascript, react, frontend, api, development`;
|
|
|
212
212
|
onKeyDown={handleKeyDown}
|
|
213
213
|
placeholder={
|
|
214
214
|
placeholder ||
|
|
215
|
-
t(
|
|
216
|
-
"ai.chips.placeholder",
|
|
217
|
-
"Type and press Enter to add tags..."
|
|
218
|
-
)
|
|
215
|
+
t("ai.chips.placeholder", "Type and press Enter to add tags...")
|
|
219
216
|
}
|
|
220
217
|
className={`ai-control ai-control-input ai-control-input--with-action ${sizeClass} ${radiusClass}`}
|
|
221
218
|
/>
|
|
@@ -22,7 +22,7 @@ type ContextData =
|
|
|
22
22
|
| boolean
|
|
23
23
|
| object
|
|
24
24
|
| unknown[]
|
|
25
|
-
| { [key: string]:
|
|
25
|
+
| { [key: string]: any };
|
|
26
26
|
|
|
27
27
|
export interface AiContextButtonProps
|
|
28
28
|
extends
|
|
@@ -208,7 +208,9 @@ export function AiContextButton({
|
|
|
208
208
|
)
|
|
209
209
|
? 0
|
|
210
210
|
: analysisResult.cost
|
|
211
|
-
).toFixed(
|
|
211
|
+
).toFixed(
|
|
212
|
+
6
|
|
213
|
+
)}\n${t("ai.context.requestId", "Request ID")}: ${analysisResult.requestId || "N/A"}`;
|
|
212
214
|
|
|
213
215
|
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
|
214
216
|
const url = URL.createObjectURL(blob);
|
|
@@ -254,7 +256,9 @@ export function AiContextButton({
|
|
|
254
256
|
) : (
|
|
255
257
|
<>
|
|
256
258
|
<Sparkles size={16} />
|
|
257
|
-
{children ||
|
|
259
|
+
{children || (
|
|
260
|
+
<span>{t("ai.context.buttonAnalyze", "Analyze")}</span>
|
|
261
|
+
)}
|
|
258
262
|
</>
|
|
259
263
|
)}
|
|
260
264
|
</button>
|
|
@@ -292,7 +296,8 @@ export function AiContextButton({
|
|
|
292
296
|
<div className="ai-row">
|
|
293
297
|
<FileText size={18} />
|
|
294
298
|
<h2 className="ai-result-title">
|
|
295
|
-
{resultModalTitle ||
|
|
299
|
+
{resultModalTitle ||
|
|
300
|
+
t("ai.context.resultTitle", "Analysis result")}
|
|
296
301
|
</h2>
|
|
297
302
|
</div>
|
|
298
303
|
<div className="ai-row">
|
|
@@ -320,12 +325,16 @@ export function AiContextButton({
|
|
|
320
325
|
|
|
321
326
|
<div className="ai-result-body">
|
|
322
327
|
<div className="ai-result-block">
|
|
323
|
-
<h3 className="ai-result-subtitle">
|
|
328
|
+
<h3 className="ai-result-subtitle">
|
|
329
|
+
{t("common.promptUsed", "Prompt used")}
|
|
330
|
+
</h3>
|
|
324
331
|
<pre className="ai-result-code">{analysisResult.prompt}</pre>
|
|
325
332
|
</div>
|
|
326
333
|
|
|
327
334
|
<div className="ai-result-block">
|
|
328
|
-
<h3 className="ai-result-subtitle">
|
|
335
|
+
<h3 className="ai-result-subtitle">
|
|
336
|
+
{t("common.result", "Result")}
|
|
337
|
+
</h3>
|
|
329
338
|
<div className="ai-result-content">
|
|
330
339
|
{analysisResult.content}
|
|
331
340
|
</div>
|
|
@@ -74,7 +74,7 @@ export function AiImageButton({
|
|
|
74
74
|
requestId: string;
|
|
75
75
|
tokens: number;
|
|
76
76
|
} | null>(null);
|
|
77
|
-
const { showUsageToast
|
|
77
|
+
const { showUsageToast } = useUsageToast();
|
|
78
78
|
const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
|
|
79
79
|
|
|
80
80
|
// Rendre l'authentification optionnelle
|
|
@@ -122,8 +122,11 @@ export function AiImageButton({
|
|
|
122
122
|
if (!generatedImage || !onImageSave) return;
|
|
123
123
|
try {
|
|
124
124
|
await onImageSave(generatedImage.url);
|
|
125
|
-
onToast?.({
|
|
126
|
-
|
|
125
|
+
onToast?.({
|
|
126
|
+
type: "success",
|
|
127
|
+
message: t("ai.image.savedSuccess", "Image saved"),
|
|
128
|
+
});
|
|
129
|
+
} catch {
|
|
127
130
|
onToast?.({
|
|
128
131
|
type: "error",
|
|
129
132
|
message: t("ai.image.saveError", "Error while saving"),
|
|
@@ -174,7 +177,10 @@ export function AiImageButton({
|
|
|
174
177
|
});
|
|
175
178
|
onToast?.({
|
|
176
179
|
type: "success",
|
|
177
|
-
message: t(
|
|
180
|
+
message: t(
|
|
181
|
+
"ai.image.generatedSuccess",
|
|
182
|
+
"Image generated successfully"
|
|
183
|
+
),
|
|
178
184
|
});
|
|
179
185
|
|
|
180
186
|
// Afficher le toast de coût même en mode dev
|
|
@@ -34,7 +34,7 @@ export function AiInput({
|
|
|
34
34
|
context,
|
|
35
35
|
model,
|
|
36
36
|
prompt,
|
|
37
|
-
editMode = false,
|
|
37
|
+
editMode: _editMode = false,
|
|
38
38
|
enableModelManagement = true,
|
|
39
39
|
storeOutputs,
|
|
40
40
|
artifactTitle,
|
|
@@ -51,7 +51,12 @@ export function AiInput({
|
|
|
51
51
|
inputProps.value?.toString() || inputProps.defaultValue?.toString() || ""
|
|
52
52
|
);
|
|
53
53
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
54
|
-
const {
|
|
54
|
+
const {
|
|
55
|
+
showUsageToast: _showUsageToast,
|
|
56
|
+
toastData,
|
|
57
|
+
toastKey,
|
|
58
|
+
clearToast,
|
|
59
|
+
} = useUsageToast();
|
|
55
60
|
|
|
56
61
|
// Rendre l'authentification optionnelle
|
|
57
62
|
let lbStatus: string | undefined;
|
|
@@ -77,7 +82,7 @@ export function AiInput({
|
|
|
77
82
|
const baseUrl = propBaseUrl ?? ctxBaseUrl;
|
|
78
83
|
const apiKeyId = propApiKeyId ?? ctxApiKeyId;
|
|
79
84
|
|
|
80
|
-
const { models } = useAiModels({
|
|
85
|
+
const { models: _models } = useAiModels({
|
|
81
86
|
baseUrl,
|
|
82
87
|
apiKeyId,
|
|
83
88
|
modelType: "text-or-language",
|
|
@@ -104,7 +109,7 @@ export function AiInput({
|
|
|
104
109
|
const handleSubmit = async (
|
|
105
110
|
selectedModel: string,
|
|
106
111
|
selectedPrompt: string,
|
|
107
|
-
|
|
112
|
+
_promptId?: string
|
|
108
113
|
) => {
|
|
109
114
|
try {
|
|
110
115
|
const resolvedContext = inputValue || context || undefined;
|
|
@@ -29,7 +29,9 @@ export function AiModelSelect({
|
|
|
29
29
|
disabled={disabled}
|
|
30
30
|
data-ai-model-select
|
|
31
31
|
>
|
|
32
|
-
<option value="">
|
|
32
|
+
<option value="">
|
|
33
|
+
{t("ai.select.modelPlaceholder", "Select a model")}
|
|
34
|
+
</option>
|
|
33
35
|
{models.map((model) => (
|
|
34
36
|
<option key={model.id} value={model.id}>
|
|
35
37
|
{model.name}
|
|
@@ -102,7 +102,7 @@ function AiPromptPanelInternal({
|
|
|
102
102
|
isOpen,
|
|
103
103
|
onClose,
|
|
104
104
|
onSubmit,
|
|
105
|
-
uiMode
|
|
105
|
+
uiMode = "modal",
|
|
106
106
|
models = [],
|
|
107
107
|
sourceText,
|
|
108
108
|
children,
|
|
@@ -405,9 +405,29 @@ function AiPromptPanelInternal({
|
|
|
405
405
|
return null;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
const isDrawer = uiMode === "drawer";
|
|
409
|
+
const panelContainerStyle = isDrawer
|
|
410
|
+
? {
|
|
411
|
+
...aiStyles.modal,
|
|
412
|
+
alignItems: "stretch",
|
|
413
|
+
justifyContent: "flex-end",
|
|
414
|
+
padding: "0",
|
|
415
|
+
}
|
|
416
|
+
: aiStyles.modal;
|
|
417
|
+
const panelContentStyle = isDrawer
|
|
418
|
+
? {
|
|
419
|
+
...aiStyles.modalContent,
|
|
420
|
+
width: "min(92vw, 640px)",
|
|
421
|
+
maxWidth: "640px",
|
|
422
|
+
maxHeight: "100vh",
|
|
423
|
+
height: "100vh",
|
|
424
|
+
borderRadius: "16px 0 0 16px",
|
|
425
|
+
}
|
|
426
|
+
: aiStyles.modalContent;
|
|
427
|
+
|
|
408
428
|
if (children) {
|
|
409
429
|
return createPortal(
|
|
410
|
-
<div style={
|
|
430
|
+
<div style={panelContainerStyle} onKeyDown={handleKeyDown}>
|
|
411
431
|
{children(renderProps)}
|
|
412
432
|
</div>,
|
|
413
433
|
portalRoot
|
|
@@ -415,7 +435,7 @@ function AiPromptPanelInternal({
|
|
|
415
435
|
}
|
|
416
436
|
|
|
417
437
|
return createPortal(
|
|
418
|
-
<div style={
|
|
438
|
+
<div style={panelContainerStyle} onKeyDown={handleKeyDown}>
|
|
419
439
|
<div
|
|
420
440
|
style={{
|
|
421
441
|
...aiStyles.modalOverlay,
|
|
@@ -426,13 +446,18 @@ function AiPromptPanelInternal({
|
|
|
426
446
|
/>
|
|
427
447
|
<div
|
|
428
448
|
style={{
|
|
429
|
-
...
|
|
449
|
+
...panelContentStyle,
|
|
430
450
|
opacity: isClosing ? 0 : 1,
|
|
431
|
-
transform: isClosing
|
|
451
|
+
transform: isClosing
|
|
452
|
+
? isDrawer
|
|
453
|
+
? "translateX(16px)"
|
|
454
|
+
: "translateY(12px)"
|
|
455
|
+
: isDrawer
|
|
456
|
+
? "translateX(0)"
|
|
457
|
+
: "translateY(0)",
|
|
432
458
|
transition: "opacity 200ms ease, transform 200ms ease",
|
|
433
459
|
display: "flex",
|
|
434
460
|
flexDirection: "column",
|
|
435
|
-
maxHeight: "85vh",
|
|
436
461
|
overflow: "hidden",
|
|
437
462
|
}}
|
|
438
463
|
>
|
|
@@ -570,7 +595,10 @@ function AiPromptPanelInternal({
|
|
|
570
595
|
AI Model
|
|
571
596
|
</label>
|
|
572
597
|
{isModelsLoading ? (
|
|
573
|
-
<span
|
|
598
|
+
<span
|
|
599
|
+
className="ai-inline-skeleton"
|
|
600
|
+
aria-hidden="true"
|
|
601
|
+
/>
|
|
574
602
|
) : effectiveAvailableModels.length > 0 ? (
|
|
575
603
|
<button
|
|
576
604
|
onClick={() => setIsModelManagementOpen(true)}
|
|
@@ -747,17 +775,17 @@ function AiPromptPanelInternal({
|
|
|
747
775
|
onChange={(e) => setPrompt(e.target.value)}
|
|
748
776
|
onFocus={() => setPromptFocused(true)}
|
|
749
777
|
onBlur={() => setPromptFocused(false)}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
778
|
+
placeholder={
|
|
779
|
+
sourceText
|
|
780
|
+
? t(
|
|
781
|
+
"prompt.modal.promptPlaceholderWithSource",
|
|
782
|
+
"Enter your AI prompt... e.g., 'Fix grammar', 'Make it more professional', 'Translate to English'"
|
|
783
|
+
)
|
|
784
|
+
: t(
|
|
785
|
+
"prompt.modal.promptPlaceholderNoSource",
|
|
786
|
+
"Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'"
|
|
787
|
+
)
|
|
788
|
+
}
|
|
761
789
|
rows={6}
|
|
762
790
|
style={{
|
|
763
791
|
...aiStyles.textarea,
|
|
@@ -889,7 +917,10 @@ function AiPromptPanelInternal({
|
|
|
889
917
|
}}
|
|
890
918
|
>
|
|
891
919
|
{Array.from({ length: 4 }).map((_, idx) => (
|
|
892
|
-
<div
|
|
920
|
+
<div
|
|
921
|
+
key={`prompt-skeleton-${idx}`}
|
|
922
|
+
className="ai-list-skeleton"
|
|
923
|
+
>
|
|
893
924
|
<div className="ai-list-skeleton__line ai-list-skeleton__line--lg" />
|
|
894
925
|
<div className="ai-list-skeleton__line ai-list-skeleton__line--md" />
|
|
895
926
|
</div>
|
|
@@ -1260,7 +1291,10 @@ function AiPromptPanelInternal({
|
|
|
1260
1291
|
{isModelsLoading ? (
|
|
1261
1292
|
<>
|
|
1262
1293
|
{Array.from({ length: 4 }).map((_, idx) => (
|
|
1263
|
-
<div
|
|
1294
|
+
<div
|
|
1295
|
+
key={`model-skeleton-${idx}`}
|
|
1296
|
+
className="ai-list-skeleton"
|
|
1297
|
+
>
|
|
1264
1298
|
<div className="ai-list-skeleton__line ai-list-skeleton__line--lg" />
|
|
1265
1299
|
<div className="ai-list-skeleton__line ai-list-skeleton__line--md" />
|
|
1266
1300
|
</div>
|
|
@@ -1365,35 +1399,35 @@ function AiPromptPanelInternal({
|
|
|
1365
1399
|
);
|
|
1366
1400
|
})}
|
|
1367
1401
|
{!isModelsLoading &&
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1402
|
+
effectiveAvailableModels.filter((model) => {
|
|
1403
|
+
if (model.category !== modelCategory) return false;
|
|
1404
|
+
if (!modelSearchQuery.trim()) return true;
|
|
1405
|
+
const query = modelSearchQuery.toLowerCase();
|
|
1406
|
+
return (
|
|
1407
|
+
model.name.toLowerCase().includes(query) ||
|
|
1408
|
+
model.provider.toLowerCase().includes(query) ||
|
|
1409
|
+
model.description?.toLowerCase().includes(query)
|
|
1410
|
+
);
|
|
1411
|
+
}).length === 0 && (
|
|
1412
|
+
<div
|
|
1413
|
+
style={{
|
|
1414
|
+
textAlign: "center",
|
|
1415
|
+
padding: "32px 16px",
|
|
1416
|
+
color: "var(--ai-text-tertiary)",
|
|
1417
|
+
fontSize: "14px",
|
|
1418
|
+
}}
|
|
1419
|
+
>
|
|
1420
|
+
{modelSearchQuery.trim()
|
|
1421
|
+
? t(
|
|
1422
|
+
"prompt.modal.noModelMatch",
|
|
1423
|
+
"No model matches your search"
|
|
1424
|
+
)
|
|
1425
|
+
: t(
|
|
1426
|
+
"prompt.modal.noModelAvailable",
|
|
1427
|
+
"No models available"
|
|
1428
|
+
)}
|
|
1429
|
+
</div>
|
|
1430
|
+
)}
|
|
1397
1431
|
</div>
|
|
1398
1432
|
</div>
|
|
1399
1433
|
|