@lastbrain/ai-ui-react 1.0.8 → 1.0.10
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/README.md +16 -2
- package/dist/components/AiChipLabel.d.ts +7 -6
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +25 -36
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +28 -14
- package/dist/components/AiPromptPanel.d.ts +4 -2
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +159 -7
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +12 -1
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +41 -37
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +30 -16
- package/dist/styles/inline.d.ts +57 -115
- package/dist/styles/inline.d.ts.map +1 -1
- package/dist/styles/inline.js +410 -31
- package/package.json +1 -1
- package/src/components/AiChipLabel.tsx +38 -76
- package/src/components/AiInput.tsx +77 -34
- package/src/components/AiPromptPanel.tsx +307 -43
- package/src/components/AiSelect.tsx +19 -10
- package/src/components/AiStatusButton.tsx +64 -38
- package/src/components/AiTextarea.tsx +80 -37
- package/src/styles/inline.ts +481 -49
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { AiStatus } from "@lastbrain/ai-ui-core";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { aiStyles } from "../styles/inline";
|
|
4
|
+
import { useState, useRef, useEffect } from "react";
|
|
5
|
+
import { aiStyles, calculateTooltipPosition } from "../styles/inline";
|
|
6
6
|
|
|
7
7
|
export interface AiStatusButtonProps {
|
|
8
8
|
status: AiStatus | null;
|
|
@@ -17,10 +17,40 @@ export function AiStatusButton({
|
|
|
17
17
|
}: AiStatusButtonProps) {
|
|
18
18
|
const [showTooltip, setShowTooltip] = useState(false);
|
|
19
19
|
const [isHovered, setIsHovered] = useState(false);
|
|
20
|
+
const [tooltipPosition, setTooltipPosition] = useState<any>({});
|
|
21
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
22
|
+
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (showTooltip && buttonRef.current) {
|
|
26
|
+
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
27
|
+
const position = calculateTooltipPosition(buttonRect);
|
|
28
|
+
setTooltipPosition(position);
|
|
29
|
+
}
|
|
30
|
+
}, [showTooltip]);
|
|
31
|
+
|
|
32
|
+
const handleMouseEnter = () => {
|
|
33
|
+
setShowTooltip(true);
|
|
34
|
+
setIsHovered(true);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleMouseLeave = () => {
|
|
38
|
+
// Keep tooltip visible if hovering over it
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
if (
|
|
41
|
+
!tooltipRef.current?.matches(":hover") &&
|
|
42
|
+
!buttonRef.current?.matches(":hover")
|
|
43
|
+
) {
|
|
44
|
+
setShowTooltip(false);
|
|
45
|
+
setIsHovered(false);
|
|
46
|
+
}
|
|
47
|
+
}, 100);
|
|
48
|
+
};
|
|
20
49
|
|
|
21
50
|
if (loading) {
|
|
22
51
|
return (
|
|
23
52
|
<button
|
|
53
|
+
ref={buttonRef}
|
|
24
54
|
style={{
|
|
25
55
|
...aiStyles.statusButton,
|
|
26
56
|
...aiStyles.statusButtonDisabled,
|
|
@@ -29,9 +59,7 @@ export function AiStatusButton({
|
|
|
29
59
|
disabled
|
|
30
60
|
>
|
|
31
61
|
<svg
|
|
32
|
-
style={
|
|
33
|
-
animation: "ai-spin 1s linear infinite",
|
|
34
|
-
}}
|
|
62
|
+
style={aiStyles.spinner}
|
|
35
63
|
width="16"
|
|
36
64
|
height="16"
|
|
37
65
|
viewBox="0 0 24 24"
|
|
@@ -48,20 +76,15 @@ export function AiStatusButton({
|
|
|
48
76
|
return (
|
|
49
77
|
<div style={{ position: "relative", display: "inline-block" }}>
|
|
50
78
|
<button
|
|
79
|
+
ref={buttonRef}
|
|
51
80
|
style={{
|
|
52
81
|
...aiStyles.statusButton,
|
|
53
82
|
color: "#ef4444",
|
|
54
83
|
...(isHovered && aiStyles.statusButtonHover),
|
|
55
84
|
}}
|
|
56
85
|
className={className}
|
|
57
|
-
onMouseEnter={
|
|
58
|
-
|
|
59
|
-
setIsHovered(true);
|
|
60
|
-
}}
|
|
61
|
-
onMouseLeave={() => {
|
|
62
|
-
setShowTooltip(false);
|
|
63
|
-
setIsHovered(false);
|
|
64
|
-
}}
|
|
86
|
+
onMouseEnter={handleMouseEnter}
|
|
87
|
+
onMouseLeave={handleMouseLeave}
|
|
65
88
|
>
|
|
66
89
|
<svg
|
|
67
90
|
width="16"
|
|
@@ -73,7 +96,16 @@ export function AiStatusButton({
|
|
|
73
96
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
|
74
97
|
</svg>
|
|
75
98
|
</button>
|
|
76
|
-
{showTooltip &&
|
|
99
|
+
{showTooltip && (
|
|
100
|
+
<div
|
|
101
|
+
ref={tooltipRef}
|
|
102
|
+
style={{ ...aiStyles.tooltip, ...tooltipPosition }}
|
|
103
|
+
onMouseEnter={() => setShowTooltip(true)}
|
|
104
|
+
onMouseLeave={handleMouseLeave}
|
|
105
|
+
>
|
|
106
|
+
No status available
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
77
109
|
</div>
|
|
78
110
|
);
|
|
79
111
|
}
|
|
@@ -81,20 +113,15 @@ export function AiStatusButton({
|
|
|
81
113
|
return (
|
|
82
114
|
<div style={{ position: "relative", display: "inline-block" }}>
|
|
83
115
|
<button
|
|
116
|
+
ref={buttonRef}
|
|
84
117
|
style={{
|
|
85
118
|
...aiStyles.statusButton,
|
|
86
119
|
color: "#10b981",
|
|
87
120
|
...(isHovered && aiStyles.statusButtonHover),
|
|
88
121
|
}}
|
|
89
122
|
className={className}
|
|
90
|
-
onMouseEnter={
|
|
91
|
-
|
|
92
|
-
setIsHovered(true);
|
|
93
|
-
}}
|
|
94
|
-
onMouseLeave={() => {
|
|
95
|
-
setShowTooltip(false);
|
|
96
|
-
setIsHovered(false);
|
|
97
|
-
}}
|
|
123
|
+
onMouseEnter={handleMouseEnter}
|
|
124
|
+
onMouseLeave={handleMouseLeave}
|
|
98
125
|
>
|
|
99
126
|
<svg
|
|
100
127
|
width="16"
|
|
@@ -108,7 +135,12 @@ export function AiStatusButton({
|
|
|
108
135
|
</button>
|
|
109
136
|
|
|
110
137
|
{showTooltip && (
|
|
111
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
ref={tooltipRef}
|
|
140
|
+
style={{ ...aiStyles.tooltip, ...tooltipPosition }}
|
|
141
|
+
onMouseEnter={() => setShowTooltip(true)}
|
|
142
|
+
onMouseLeave={handleMouseLeave}
|
|
143
|
+
>
|
|
112
144
|
<div style={aiStyles.tooltipHeader}>API Status</div>
|
|
113
145
|
|
|
114
146
|
<div
|
|
@@ -224,49 +256,43 @@ export function AiStatusButton({
|
|
|
224
256
|
|
|
225
257
|
<div style={aiStyles.tooltipActions}>
|
|
226
258
|
<a
|
|
227
|
-
href="https://
|
|
259
|
+
href="https://prompt.lastbrain.io/fr/auth/dashboard"
|
|
228
260
|
target="_blank"
|
|
229
261
|
rel="noopener noreferrer"
|
|
230
262
|
style={aiStyles.tooltipLink}
|
|
231
263
|
onMouseEnter={(e) => {
|
|
232
|
-
e.currentTarget.style.
|
|
233
|
-
e.currentTarget.style.borderColor = "#3b82f6";
|
|
264
|
+
Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
|
|
234
265
|
}}
|
|
235
266
|
onMouseLeave={(e) => {
|
|
236
|
-
e.currentTarget.style.
|
|
237
|
-
e.currentTarget.style.borderColor = "#dbeafe";
|
|
267
|
+
Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
|
|
238
268
|
}}
|
|
239
269
|
>
|
|
240
270
|
Dashboard
|
|
241
271
|
</a>
|
|
242
272
|
<a
|
|
243
|
-
href="https://
|
|
273
|
+
href="https://prompt.lastbrain.io/fr/auth/billing"
|
|
244
274
|
target="_blank"
|
|
245
275
|
rel="noopener noreferrer"
|
|
246
276
|
style={aiStyles.tooltipLink}
|
|
247
277
|
onMouseEnter={(e) => {
|
|
248
|
-
e.currentTarget.style.
|
|
249
|
-
e.currentTarget.style.borderColor = "#3b82f6";
|
|
278
|
+
Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
|
|
250
279
|
}}
|
|
251
280
|
onMouseLeave={(e) => {
|
|
252
|
-
e.currentTarget.style.
|
|
253
|
-
e.currentTarget.style.borderColor = "#dbeafe";
|
|
281
|
+
Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
|
|
254
282
|
}}
|
|
255
283
|
>
|
|
256
284
|
History
|
|
257
285
|
</a>
|
|
258
286
|
<a
|
|
259
|
-
href="https://
|
|
287
|
+
href="https://prompt.lastbrain.io/fr/auth/billing"
|
|
260
288
|
target="_blank"
|
|
261
289
|
rel="noopener noreferrer"
|
|
262
290
|
style={aiStyles.tooltipLink}
|
|
263
291
|
onMouseEnter={(e) => {
|
|
264
|
-
e.currentTarget.style.
|
|
265
|
-
e.currentTarget.style.borderColor = "#3b82f6";
|
|
292
|
+
Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
|
|
266
293
|
}}
|
|
267
294
|
onMouseLeave={(e) => {
|
|
268
|
-
e.currentTarget.style.
|
|
269
|
-
e.currentTarget.style.borderColor = "#dbeafe";
|
|
295
|
+
Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
|
|
270
296
|
}}
|
|
271
297
|
>
|
|
272
298
|
Settings
|
|
@@ -5,6 +5,7 @@ import type { BaseAiProps } from "../types";
|
|
|
5
5
|
import { useAiCallText } from "../hooks/useAiCallText";
|
|
6
6
|
import { useAiModels } from "../hooks/useAiModels";
|
|
7
7
|
import { AiPromptPanel } from "./AiPromptPanel";
|
|
8
|
+
import { aiStyles } from "../styles/inline";
|
|
8
9
|
|
|
9
10
|
export interface AiTextareaProps
|
|
10
11
|
extends
|
|
@@ -28,11 +29,13 @@ export function AiTextarea({
|
|
|
28
29
|
...textareaProps
|
|
29
30
|
}: AiTextareaProps) {
|
|
30
31
|
const [isOpen, setIsOpen] = useState(false);
|
|
31
|
-
const [
|
|
32
|
+
const [textareaValue, setTextareaValue] = useState(
|
|
32
33
|
textareaProps.value?.toString() ||
|
|
33
34
|
textareaProps.defaultValue?.toString() ||
|
|
34
35
|
""
|
|
35
36
|
);
|
|
37
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
38
|
+
const [isButtonHovered, setIsButtonHovered] = useState(false);
|
|
36
39
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
37
40
|
|
|
38
41
|
const { models } = useAiModels({ baseUrl, apiKeyId });
|
|
@@ -50,22 +53,21 @@ export function AiTextarea({
|
|
|
50
53
|
|
|
51
54
|
const handleSubmit = async (
|
|
52
55
|
selectedModel: string,
|
|
53
|
-
selectedPrompt: string
|
|
56
|
+
selectedPrompt: string,
|
|
57
|
+
promptId?: string
|
|
54
58
|
) => {
|
|
55
59
|
try {
|
|
56
60
|
const result = await generateText({
|
|
57
61
|
model: selectedModel,
|
|
58
62
|
prompt: selectedPrompt,
|
|
59
|
-
context:
|
|
63
|
+
context: textareaValue || context || undefined,
|
|
60
64
|
actionType: "autocomplete",
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
if (result.text) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
textareaRef.current.value = result.text;
|
|
68
|
-
}
|
|
68
|
+
setTextareaValue(result.text);
|
|
69
|
+
if (textareaRef.current) {
|
|
70
|
+
textareaRef.current.value = result.text;
|
|
69
71
|
}
|
|
70
72
|
onValue?.(result.text);
|
|
71
73
|
onToast?.({ type: "success", message: "AI generation successful" });
|
|
@@ -84,16 +86,14 @@ export function AiTextarea({
|
|
|
84
86
|
const result = await generateText({
|
|
85
87
|
model,
|
|
86
88
|
prompt,
|
|
87
|
-
context:
|
|
89
|
+
context: textareaValue || context || undefined,
|
|
88
90
|
actionType: "autocomplete",
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
if (result.text) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
textareaRef.current.value = result.text;
|
|
96
|
-
}
|
|
94
|
+
setTextareaValue(result.text);
|
|
95
|
+
if (textareaRef.current) {
|
|
96
|
+
textareaRef.current.value = result.text;
|
|
97
97
|
}
|
|
98
98
|
onValue?.(result.text);
|
|
99
99
|
onToast?.({ type: "success", message: "AI generation successful" });
|
|
@@ -105,39 +105,81 @@ export function AiTextarea({
|
|
|
105
105
|
|
|
106
106
|
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
107
107
|
const newValue = e.target.value;
|
|
108
|
-
|
|
108
|
+
setTextareaValue(newValue);
|
|
109
109
|
textareaProps.onChange?.(e);
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
return (
|
|
113
|
-
<div
|
|
113
|
+
<div style={aiStyles.textareaWrapper} className={className}>
|
|
114
114
|
<textarea
|
|
115
115
|
ref={textareaRef}
|
|
116
116
|
{...textareaProps}
|
|
117
|
-
|
|
117
|
+
style={{
|
|
118
|
+
...aiStyles.textarea,
|
|
119
|
+
...(isFocused && aiStyles.textareaFocus),
|
|
120
|
+
}}
|
|
121
|
+
value={textareaValue}
|
|
118
122
|
onChange={handleTextareaChange}
|
|
123
|
+
onFocus={(e) => {
|
|
124
|
+
setIsFocused(true);
|
|
125
|
+
textareaProps.onFocus?.(e);
|
|
126
|
+
}}
|
|
127
|
+
onBlur={(e) => {
|
|
128
|
+
setIsFocused(false);
|
|
129
|
+
textareaProps.onBlur?.(e);
|
|
130
|
+
}}
|
|
119
131
|
disabled={disabled || loading}
|
|
120
|
-
data-ai-textarea
|
|
121
132
|
/>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
<button
|
|
134
|
+
style={{
|
|
135
|
+
...aiStyles.textareaAiButton,
|
|
136
|
+
...(isButtonHovered && aiStyles.textareaAiButtonHover),
|
|
137
|
+
...(disabled || loading
|
|
138
|
+
? { opacity: 0.5, cursor: "not-allowed" }
|
|
139
|
+
: {}),
|
|
140
|
+
}}
|
|
141
|
+
onClick={hasConfiguration ? handleQuickGenerate : handleOpenPanel}
|
|
142
|
+
onMouseEnter={() => setIsButtonHovered(true)}
|
|
143
|
+
onMouseLeave={() => setIsButtonHovered(false)}
|
|
144
|
+
disabled={disabled || loading}
|
|
145
|
+
type="button"
|
|
146
|
+
title={hasConfiguration ? "Generate with AI" : "Setup AI"}
|
|
147
|
+
>
|
|
148
|
+
{loading ? (
|
|
149
|
+
<svg
|
|
150
|
+
style={aiStyles.spinner}
|
|
151
|
+
width="16"
|
|
152
|
+
height="16"
|
|
153
|
+
viewBox="0 0 24 24"
|
|
154
|
+
fill="none"
|
|
155
|
+
stroke="currentColor"
|
|
156
|
+
>
|
|
157
|
+
<path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" />
|
|
158
|
+
</svg>
|
|
159
|
+
) : hasConfiguration ? (
|
|
160
|
+
<svg
|
|
161
|
+
width="16"
|
|
162
|
+
height="16"
|
|
163
|
+
viewBox="0 0 24 24"
|
|
164
|
+
fill="none"
|
|
165
|
+
stroke="currentColor"
|
|
166
|
+
strokeWidth="2"
|
|
167
|
+
>
|
|
168
|
+
<path d="M12 5v14M5 12h14" />
|
|
169
|
+
</svg>
|
|
170
|
+
) : (
|
|
171
|
+
<svg
|
|
172
|
+
width="16"
|
|
173
|
+
height="16"
|
|
174
|
+
viewBox="0 0 24 24"
|
|
175
|
+
fill="none"
|
|
176
|
+
stroke="currentColor"
|
|
177
|
+
strokeWidth="2"
|
|
178
|
+
>
|
|
179
|
+
<path d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
|
180
|
+
</svg>
|
|
181
|
+
)}
|
|
182
|
+
</button>
|
|
141
183
|
{isOpen && (
|
|
142
184
|
<AiPromptPanel
|
|
143
185
|
isOpen={isOpen}
|
|
@@ -145,6 +187,7 @@ export function AiTextarea({
|
|
|
145
187
|
onSubmit={handleSubmit}
|
|
146
188
|
uiMode={uiMode}
|
|
147
189
|
models={models || []}
|
|
190
|
+
sourceText={textareaValue || undefined}
|
|
148
191
|
/>
|
|
149
192
|
)}
|
|
150
193
|
</div>
|