@lastbrain/ai-ui-react 1.0.9 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACxD,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,UAAU,EACd,EAAE,YAAY,2CAiKd"}
1
+ {"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACxD,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,UAAU,EACd,EAAE,YAAY,2CAmKd"}
@@ -20,7 +20,7 @@ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, p
20
20
  const handleClosePanel = () => {
21
21
  setIsOpen(false);
22
22
  };
23
- const handleSubmit = async (selectedModel, selectedPrompt) => {
23
+ const handleSubmit = async (selectedModel, selectedPrompt, promptId) => {
24
24
  try {
25
25
  const result = await generateText({
26
26
  model: selectedModel,
@@ -87,5 +87,5 @@ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, p
87
87
  ...(disabled || loading
88
88
  ? { opacity: 0.5, cursor: "not-allowed" }
89
89
  : {}),
90
- }, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("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" }) })) : hasConfiguration ? (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) : (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("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" }) })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] }))] }));
90
+ }, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("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" }) })) : hasConfiguration ? (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) : (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("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" }) })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [], sourceText: inputValue || undefined }))] }));
91
91
  }
@@ -4,9 +4,10 @@ import type { UiMode } from "../types";
4
4
  export interface AiPromptPanelProps {
5
5
  isOpen: boolean;
6
6
  onClose: () => void;
7
- onSubmit: (model: string, prompt: string) => void;
7
+ onSubmit: (model: string, prompt: string, promptId?: string) => void;
8
8
  uiMode?: UiMode;
9
9
  models?: ModelRef[];
10
+ sourceText?: string;
10
11
  children?: (props: AiPromptPanelRenderProps) => ReactNode;
11
12
  }
12
13
  export interface AiPromptPanelRenderProps {
@@ -15,8 +16,9 @@ export interface AiPromptPanelRenderProps {
15
16
  setSelectedModel: (model: string) => void;
16
17
  prompt: string;
17
18
  setPrompt: (prompt: string) => void;
19
+ sourceText?: string;
18
20
  handleSubmit: () => void;
19
21
  handleClose: () => void;
20
22
  }
21
- export declare function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode, models, children, }: AiPromptPanelProps): import("react/jsx-runtime").JSX.Element | null;
23
+ export declare function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode, models, sourceText, children, }: AiPromptPanelProps): import("react/jsx-runtime").JSX.Element | null;
22
24
  //# sourceMappingURL=AiPromptPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,SAAS,CAAC;CAC3D;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,MAAW,EACX,QAAQ,GACT,EAAE,kBAAkB,kDA4JpB"}
1
+ {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIvC,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,SAAS,CAAC;CAC3D;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,MAAW,EACX,UAAU,EACV,QAAQ,GACT,EAAE,kBAAkB,kDA0VpB"}
@@ -1,31 +1,48 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
4
  import { aiStyles } from "../styles/inline";
5
- export function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode = "modal", models = [], children, }) {
6
- const [selectedModel, setSelectedModel] = useState(models[0]?.id || "");
5
+ import { usePrompts } from "../hooks/usePrompts";
6
+ export function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode = "modal", models = [], sourceText, children, }) {
7
+ const [selectedModel, setSelectedModel] = useState("");
7
8
  const [prompt, setPrompt] = useState("");
9
+ const [promptId, setPromptId] = useState(undefined);
8
10
  const [isCloseHovered, setIsCloseHovered] = useState(false);
9
11
  const [isCancelHovered, setIsCancelHovered] = useState(false);
10
12
  const [isSubmitHovered, setIsSubmitHovered] = useState(false);
11
13
  const [promptFocused, setPromptFocused] = useState(false);
12
14
  const [modelFocused, setModelFocused] = useState(false);
15
+ const [showPromptLibrary, setShowPromptLibrary] = useState(false);
16
+ const { prompts, loading: promptsLoading, fetchPrompts, incrementStat } = usePrompts();
17
+ // Set initial model when models change
13
18
  useEffect(() => {
14
19
  if (models.length > 0 && !selectedModel) {
15
20
  setSelectedModel(models[0].id);
16
21
  }
17
22
  }, [models, selectedModel]);
23
+ // Fetch prompts when modal opens
24
+ useEffect(() => {
25
+ if (isOpen && models.length > 0) {
26
+ const modelType = models.find((m) => m.id === selectedModel)?.type;
27
+ fetchPrompts({
28
+ type: modelType === "image" ? "image" : "text",
29
+ });
30
+ }
31
+ }, [isOpen, selectedModel, models, fetchPrompts]);
18
32
  if (!isOpen)
19
33
  return null;
20
34
  const handleSubmit = () => {
21
35
  if (!selectedModel || !prompt.trim())
22
36
  return;
23
- onSubmit(selectedModel, prompt);
37
+ onSubmit(selectedModel, prompt, promptId);
24
38
  setPrompt("");
39
+ setPromptId(undefined);
25
40
  };
26
41
  const handleClose = () => {
27
42
  onClose();
28
43
  setPrompt("");
44
+ setPromptId(undefined);
45
+ setShowPromptLibrary(false);
29
46
  };
30
47
  const handleKeyDown = (e) => {
31
48
  if (e.key === "Escape") {
@@ -35,29 +52,125 @@ export function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode = "modal", mod
35
52
  handleSubmit();
36
53
  }
37
54
  };
55
+ const handleSelectPrompt = (promptData) => {
56
+ setPrompt(promptData.content);
57
+ setPromptId(promptData.id);
58
+ incrementStat(promptData.id, "picked");
59
+ setShowPromptLibrary(false);
60
+ };
61
+ const currentModelType = models.find((m) => m.id === selectedModel)?.type;
62
+ const filteredPrompts = prompts.filter((p) => {
63
+ const matchesType = currentModelType === "image" ? p.type === "image" : p.type !== "image";
64
+ return matchesType;
65
+ });
38
66
  const renderProps = {
39
67
  models,
40
68
  selectedModel,
41
69
  setSelectedModel,
42
70
  prompt,
43
71
  setPrompt,
72
+ sourceText,
44
73
  handleSubmit,
45
74
  handleClose,
46
75
  };
47
76
  if (children) {
48
77
  return (_jsx("div", { style: aiStyles.modal, onKeyDown: handleKeyDown, children: children(renderProps) }));
49
78
  }
50
- return (_jsxs("div", { style: aiStyles.modal, onKeyDown: handleKeyDown, children: [_jsx("div", { style: aiStyles.modalOverlay, onClick: handleClose }), _jsxs("div", { style: aiStyles.modalContent, children: [_jsxs("div", { style: aiStyles.modalHeader, children: [_jsx("h2", { style: aiStyles.modalTitle, children: "AI Prompt Configuration" }), _jsx("button", { style: {
79
+ return (_jsxs("div", { style: aiStyles.modal, onKeyDown: handleKeyDown, children: [_jsx("div", { style: aiStyles.modalOverlay, onClick: handleClose }), _jsxs("div", { style: aiStyles.modalContent, children: [_jsxs("div", { style: aiStyles.modalHeader, children: [_jsx("h2", { style: aiStyles.modalTitle, children: showPromptLibrary ? "Select a Prompt" : "AI Prompt Configuration" }), _jsx("button", { style: {
51
80
  ...aiStyles.modalCloseButton,
52
81
  ...(isCloseHovered && aiStyles.modalCloseButtonHover),
53
- }, onClick: handleClose, onMouseEnter: () => setIsCloseHovered(true), onMouseLeave: () => setIsCloseHovered(false), "aria-label": "Close", children: "\u00D7" })] }), _jsxs("div", { style: aiStyles.modalBody, children: [_jsxs("div", { style: aiStyles.modalInputGroup, children: [_jsx("label", { htmlFor: "model-select", style: aiStyles.modalLabel, children: "AI Model" }), _jsxs("select", { id: "model-select", value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), onFocus: () => setModelFocused(true), onBlur: () => setModelFocused(false), style: {
54
- ...aiStyles.select,
55
- ...(modelFocused && aiStyles.selectFocus),
56
- }, children: [models.length === 0 && (_jsx("option", { value: "", children: "No models available" })), models.map((model) => (_jsx("option", { value: model.id, children: model.name }, model.id)))] })] }), _jsxs("div", { style: aiStyles.modalInputGroup, children: [_jsxs("label", { htmlFor: "prompt-input", style: aiStyles.modalLabel, children: ["Prompt", _jsx("span", { style: { color: aiStyles.textareaFocus.borderColor, marginLeft: "4px" }, children: "(Cmd/Ctrl + Enter to submit)" })] }), _jsx("textarea", { id: "prompt-input", value: prompt, onChange: (e) => setPrompt(e.target.value), onFocus: () => setPromptFocused(true), onBlur: () => setPromptFocused(false), placeholder: "Enter your AI prompt... e.g., 'Correct spelling and grammar'", rows: 6, style: {
57
- ...aiStyles.textarea,
58
- padding: "12px 16px",
59
- ...(promptFocused && aiStyles.textareaFocus),
60
- } })] })] }), _jsxs("div", { style: aiStyles.modalFooter, children: [_jsx("button", { onClick: handleClose, onMouseEnter: () => setIsCancelHovered(true), onMouseLeave: () => setIsCancelHovered(false), style: {
82
+ }, onClick: handleClose, onMouseEnter: () => setIsCloseHovered(true), onMouseLeave: () => setIsCloseHovered(false), "aria-label": "Close", children: "\u00D7" })] }), _jsx("div", { style: aiStyles.modalBody, children: !showPromptLibrary ? (_jsxs(_Fragment, { children: [sourceText && (_jsxs("div", { style: {
83
+ ...aiStyles.modalInputGroup,
84
+ marginBottom: "16px",
85
+ }, children: [_jsx("label", { style: aiStyles.modalLabel, children: "Source Text" }), _jsx("div", { style: {
86
+ padding: "12px",
87
+ background: aiStyles.textarea.background,
88
+ border: `1px solid ${aiStyles.input.border}`,
89
+ borderRadius: "8px",
90
+ fontSize: "13px",
91
+ color: aiStyles.textarea.color,
92
+ maxHeight: "120px",
93
+ overflow: "auto",
94
+ whiteSpace: "pre-wrap",
95
+ wordBreak: "break-word",
96
+ }, children: sourceText })] })), _jsxs("div", { style: aiStyles.modalInputGroup, children: [_jsx("label", { htmlFor: "model-select", style: aiStyles.modalLabel, children: "AI Model" }), _jsxs("select", { id: "model-select", value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), onFocus: () => setModelFocused(true), onBlur: () => setModelFocused(false), style: {
97
+ ...aiStyles.select,
98
+ ...(modelFocused && aiStyles.selectFocus),
99
+ }, 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: {
100
+ display: "flex",
101
+ justifyContent: "space-between",
102
+ alignItems: "center",
103
+ marginBottom: "8px",
104
+ }, children: [_jsxs("label", { htmlFor: "prompt-input", style: aiStyles.modalLabel, children: ["Prompt", _jsx("span", { style: {
105
+ color: "#6b7280",
106
+ marginLeft: "4px",
107
+ fontSize: "12px",
108
+ fontWeight: 400,
109
+ }, children: "(Cmd/Ctrl + Enter to submit)" })] }), filteredPrompts.length > 0 && (_jsxs("button", { onClick: () => setShowPromptLibrary(true), style: {
110
+ padding: "4px 12px",
111
+ fontSize: "12px",
112
+ color: "#ffffff",
113
+ background: "#8b5cf620",
114
+ border: "none",
115
+ borderRadius: "6px",
116
+ cursor: "pointer",
117
+ transition: "all 0.2s",
118
+ }, onMouseEnter: (e) => {
119
+ e.currentTarget.style.background = "#8b5cf630";
120
+ }, onMouseLeave: (e) => {
121
+ e.currentTarget.style.background = "#8b5cf620";
122
+ }, children: ["\uD83D\uDCDA Browse Prompts (", filteredPrompts.length, ")"] }))] }), _jsx("textarea", { id: "prompt-input", value: prompt, onChange: (e) => setPrompt(e.target.value), onFocus: () => setPromptFocused(true), onBlur: () => setPromptFocused(false), placeholder: sourceText
123
+ ? "Enter your AI prompt... e.g., 'Correct spelling and grammar', 'Make it more professional', 'Translate to English'"
124
+ : "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'", rows: 6, style: {
125
+ ...aiStyles.textarea,
126
+ padding: "12px 16px",
127
+ ...(promptFocused && aiStyles.textareaFocus),
128
+ } })] })] })) : (_jsxs("div", { children: [_jsx("button", { onClick: () => setShowPromptLibrary(false), style: {
129
+ padding: "8px 0",
130
+ fontSize: "14px",
131
+ color: "#8b5cf6",
132
+ background: "transparent",
133
+ border: "none",
134
+ cursor: "pointer",
135
+ marginBottom: "16px",
136
+ display: "flex",
137
+ alignItems: "center",
138
+ gap: "4px",
139
+ }, children: "\u2190 Back to form" }), promptsLoading ? (_jsx("div", { style: { textAlign: "center", padding: "40px 0" }, children: "Loading prompts..." })) : filteredPrompts.length === 0 ? (_jsx("div", { style: { textAlign: "center", padding: "40px 0", color: "#6b7280" }, children: "No prompts available for this model type" })) : (_jsx("div", { style: {
140
+ display: "flex",
141
+ flexDirection: "column",
142
+ gap: "12px",
143
+ maxHeight: "400px",
144
+ overflow: "auto",
145
+ }, children: filteredPrompts.map((promptData) => (_jsxs("div", { onClick: () => handleSelectPrompt(promptData), style: {
146
+ padding: "16px",
147
+ border: `1px solid #e5e7eb`,
148
+ borderRadius: "8px",
149
+ cursor: "pointer",
150
+ transition: "all 0.2s",
151
+ }, onMouseEnter: (e) => {
152
+ e.currentTarget.style.background = "#8b5cf610";
153
+ e.currentTarget.style.borderColor = "#8b5cf6";
154
+ }, onMouseLeave: (e) => {
155
+ e.currentTarget.style.background = "transparent";
156
+ e.currentTarget.style.borderColor = "#e5e7eb";
157
+ }, children: [_jsx("div", { style: {
158
+ fontWeight: 600,
159
+ marginBottom: "4px",
160
+ color: "#111827",
161
+ }, children: promptData.title }), _jsx("div", { style: {
162
+ fontSize: "13px",
163
+ color: "#6b7280",
164
+ overflow: "hidden",
165
+ textOverflow: "ellipsis",
166
+ display: "-webkit-box",
167
+ WebkitLineClamp: 2,
168
+ WebkitBoxOrient: "vertical",
169
+ }, children: promptData.content }), ("category" in promptData && promptData.category) ? (_jsx("div", { style: {
170
+ marginTop: "8px",
171
+ fontSize: "11px",
172
+ color: "#8b5cf6",
173
+ }, children: String(promptData.category) })) : null] }, promptData.id))) }))] })) }), _jsxs("div", { style: aiStyles.modalFooter, children: [_jsx("button", { onClick: handleClose, onMouseEnter: () => setIsCancelHovered(true), onMouseLeave: () => setIsCancelHovered(false), style: {
61
174
  ...aiStyles.button,
62
175
  ...aiStyles.buttonSecondary,
63
176
  ...(isCancelHovered && aiStyles.buttonSecondaryHover),
@@ -65,5 +178,5 @@ export function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode = "modal", mod
65
178
  ...aiStyles.button,
66
179
  ...(isSubmitHovered && !(!selectedModel || !prompt.trim()) && aiStyles.buttonHover),
67
180
  ...(!selectedModel || !prompt.trim() ? aiStyles.buttonDisabled : {}),
68
- }, children: "Generate with AI" })] })] })] }));
181
+ }, children: sourceText ? "Transform with AI" : "Generate with AI" })] })] })] }));
69
182
  }
@@ -63,15 +63,15 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
63
63
  }, children: status.quota.is_active ? "Active" : "Inactive" })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Storage" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Database:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.storage.db_mb.toFixed(2), " MB"] })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Files:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.storage.files_mb.toFixed(2), " MB"] })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: {
64
64
  ...aiStyles.tooltipValue,
65
65
  ...aiStyles.tooltipValueBold,
66
- }, children: [(status.storage.db_mb + status.storage.files_mb).toFixed(2), " MB"] })] })] }), _jsxs("div", { style: aiStyles.tooltipActions, children: [_jsx("a", { href: "https://ai.lastbrain.io/fr/auth/dashboard", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
66
+ }, children: [(status.storage.db_mb + status.storage.files_mb).toFixed(2), " MB"] })] })] }), _jsxs("div", { style: aiStyles.tooltipActions, children: [_jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/dashboard", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
67
67
  Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
68
68
  }, onMouseLeave: (e) => {
69
69
  Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
70
- }, children: "Dashboard" }), _jsx("a", { href: "https://ai.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
70
+ }, children: "Dashboard" }), _jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
71
71
  Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
72
72
  }, onMouseLeave: (e) => {
73
73
  Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
74
- }, children: "History" }), _jsx("a", { href: "https://ai.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
74
+ }, children: "History" }), _jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
75
75
  Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
76
76
  }, onMouseLeave: (e) => {
77
77
  Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
@@ -1 +1 @@
1
- {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,sBAAsB,EAAE,MAAM,OAAO,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,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;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAmKjB"}
1
+ {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,sBAAsB,EAAE,MAAM,OAAO,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,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;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAqKjB"}
@@ -22,7 +22,7 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
22
22
  const handleClosePanel = () => {
23
23
  setIsOpen(false);
24
24
  };
25
- const handleSubmit = async (selectedModel, selectedPrompt) => {
25
+ const handleSubmit = async (selectedModel, selectedPrompt, promptId) => {
26
26
  try {
27
27
  const result = await generateText({
28
28
  model: selectedModel,
@@ -89,5 +89,5 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
89
89
  ...(disabled || loading
90
90
  ? { opacity: 0.5, cursor: "not-allowed" }
91
91
  : {}),
92
- }, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("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" }) })) : hasConfiguration ? (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) : (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("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" }) })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] }))] }));
92
+ }, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("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" }) })) : hasConfiguration ? (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) : (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("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" }) })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [], sourceText: textareaValue || undefined }))] }));
93
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Headless React components for LastBrain AI UI Kit",
5
5
  "private": false,
6
6
  "type": "module",
@@ -51,7 +51,8 @@ export function AiInput({
51
51
 
52
52
  const handleSubmit = async (
53
53
  selectedModel: string,
54
- selectedPrompt: string
54
+ selectedPrompt: string,
55
+ promptId?: string
55
56
  ) => {
56
57
  try {
57
58
  const result = await generateText({
@@ -184,6 +185,7 @@ export function AiInput({
184
185
  onSubmit={handleSubmit}
185
186
  uiMode={uiMode}
186
187
  models={models || []}
188
+ sourceText={inputValue || undefined}
187
189
  />
188
190
  )}
189
191
  </div>
@@ -4,13 +4,15 @@ import { useState, useEffect, type ReactNode } from "react";
4
4
  import type { ModelRef } from "@lastbrain/ai-ui-core";
5
5
  import type { UiMode } from "../types";
6
6
  import { aiStyles } from "../styles/inline";
7
+ import { usePrompts, type Prompt, type PublicPrompt } from "../hooks/usePrompts";
7
8
 
8
9
  export interface AiPromptPanelProps {
9
10
  isOpen: boolean;
10
11
  onClose: () => void;
11
- onSubmit: (model: string, prompt: string) => void;
12
+ onSubmit: (model: string, prompt: string, promptId?: string) => void;
12
13
  uiMode?: UiMode;
13
14
  models?: ModelRef[];
15
+ sourceText?: string; // Current text from input/textarea
14
16
  children?: (props: AiPromptPanelRenderProps) => ReactNode;
15
17
  }
16
18
 
@@ -20,6 +22,7 @@ export interface AiPromptPanelRenderProps {
20
22
  setSelectedModel: (model: string) => void;
21
23
  prompt: string;
22
24
  setPrompt: (prompt: string) => void;
25
+ sourceText?: string;
23
26
  handleSubmit: () => void;
24
27
  handleClose: () => void;
25
28
  }
@@ -30,33 +33,52 @@ export function AiPromptPanel({
30
33
  onSubmit,
31
34
  uiMode = "modal",
32
35
  models = [],
36
+ sourceText,
33
37
  children,
34
38
  }: AiPromptPanelProps) {
35
- const [selectedModel, setSelectedModel] = useState(models[0]?.id || "");
39
+ const [selectedModel, setSelectedModel] = useState("");
36
40
  const [prompt, setPrompt] = useState("");
41
+ const [promptId, setPromptId] = useState<string | undefined>(undefined);
37
42
  const [isCloseHovered, setIsCloseHovered] = useState(false);
38
43
  const [isCancelHovered, setIsCancelHovered] = useState(false);
39
44
  const [isSubmitHovered, setIsSubmitHovered] = useState(false);
40
45
  const [promptFocused, setPromptFocused] = useState(false);
41
46
  const [modelFocused, setModelFocused] = useState(false);
47
+ const [showPromptLibrary, setShowPromptLibrary] = useState(false);
42
48
 
49
+ const { prompts, loading: promptsLoading, fetchPrompts, incrementStat } = usePrompts();
50
+
51
+ // Set initial model when models change
43
52
  useEffect(() => {
44
53
  if (models.length > 0 && !selectedModel) {
45
54
  setSelectedModel(models[0].id);
46
55
  }
47
56
  }, [models, selectedModel]);
48
57
 
58
+ // Fetch prompts when modal opens
59
+ useEffect(() => {
60
+ if (isOpen && models.length > 0) {
61
+ const modelType = models.find((m) => m.id === selectedModel)?.type;
62
+ fetchPrompts({
63
+ type: modelType === "image" ? "image" : "text",
64
+ });
65
+ }
66
+ }, [isOpen, selectedModel, models, fetchPrompts]);
67
+
49
68
  if (!isOpen) return null;
50
69
 
51
70
  const handleSubmit = () => {
52
71
  if (!selectedModel || !prompt.trim()) return;
53
- onSubmit(selectedModel, prompt);
72
+ onSubmit(selectedModel, prompt, promptId);
54
73
  setPrompt("");
74
+ setPromptId(undefined);
55
75
  };
56
76
 
57
77
  const handleClose = () => {
58
78
  onClose();
59
79
  setPrompt("");
80
+ setPromptId(undefined);
81
+ setShowPromptLibrary(false);
60
82
  };
61
83
 
62
84
  const handleKeyDown = (e: React.KeyboardEvent) => {
@@ -68,12 +90,27 @@ export function AiPromptPanel({
68
90
  }
69
91
  };
70
92
 
93
+ const handleSelectPrompt = (promptData: Prompt | PublicPrompt) => {
94
+ setPrompt(promptData.content);
95
+ setPromptId(promptData.id);
96
+ incrementStat(promptData.id, "picked");
97
+ setShowPromptLibrary(false);
98
+ };
99
+
100
+ const currentModelType = models.find((m) => m.id === selectedModel)?.type;
101
+ const filteredPrompts = prompts.filter((p: Prompt | PublicPrompt) => {
102
+ const matchesType =
103
+ currentModelType === "image" ? p.type === "image" : p.type !== "image";
104
+ return matchesType;
105
+ });
106
+
71
107
  const renderProps: AiPromptPanelRenderProps = {
72
108
  models,
73
109
  selectedModel,
74
110
  setSelectedModel,
75
111
  prompt,
76
112
  setPrompt,
113
+ sourceText,
77
114
  handleSubmit,
78
115
  handleClose,
79
116
  };
@@ -91,7 +128,9 @@ export function AiPromptPanel({
91
128
  <div style={aiStyles.modalOverlay} onClick={handleClose} />
92
129
  <div style={aiStyles.modalContent}>
93
130
  <div style={aiStyles.modalHeader}>
94
- <h2 style={aiStyles.modalTitle}>AI Prompt Configuration</h2>
131
+ <h2 style={aiStyles.modalTitle}>
132
+ {showPromptLibrary ? "Select a Prompt" : "AI Prompt Configuration"}
133
+ </h2>
95
134
  <button
96
135
  style={{
97
136
  ...aiStyles.modalCloseButton,
@@ -107,54 +146,209 @@ export function AiPromptPanel({
107
146
  </div>
108
147
 
109
148
  <div style={aiStyles.modalBody}>
110
- <div style={aiStyles.modalInputGroup}>
111
- <label htmlFor="model-select" style={aiStyles.modalLabel}>
112
- AI Model
113
- </label>
114
- <select
115
- id="model-select"
116
- value={selectedModel}
117
- onChange={(e) => setSelectedModel(e.target.value)}
118
- onFocus={() => setModelFocused(true)}
119
- onBlur={() => setModelFocused(false)}
120
- style={{
121
- ...aiStyles.select,
122
- ...(modelFocused && aiStyles.selectFocus),
123
- }}
124
- >
125
- {models.length === 0 && (
126
- <option value="">No models available</option>
149
+ {!showPromptLibrary ? (
150
+ <>
151
+ {sourceText && (
152
+ <div style={{
153
+ ...aiStyles.modalInputGroup,
154
+ marginBottom: "16px",
155
+ }}>
156
+ <label style={aiStyles.modalLabel}>
157
+ Source Text
158
+ </label>
159
+ <div style={{
160
+ padding: "12px",
161
+ background: aiStyles.textarea.background,
162
+ border: `1px solid ${aiStyles.input.border}`,
163
+ borderRadius: "8px",
164
+ fontSize: "13px",
165
+ color: aiStyles.textarea.color,
166
+ maxHeight: "120px",
167
+ overflow: "auto",
168
+ whiteSpace: "pre-wrap",
169
+ wordBreak: "break-word",
170
+ }}>
171
+ {sourceText}
172
+ </div>
173
+ </div>
174
+ )}
175
+
176
+ <div style={aiStyles.modalInputGroup}>
177
+ <label htmlFor="model-select" style={aiStyles.modalLabel}>
178
+ AI Model
179
+ </label>
180
+ <select
181
+ id="model-select"
182
+ value={selectedModel}
183
+ onChange={(e) => setSelectedModel(e.target.value)}
184
+ onFocus={() => setModelFocused(true)}
185
+ onBlur={() => setModelFocused(false)}
186
+ style={{
187
+ ...aiStyles.select,
188
+ ...(modelFocused && aiStyles.selectFocus),
189
+ }}
190
+ >
191
+ {models.length === 0 && (
192
+ <option value="">Loading models...</option>
193
+ )}
194
+ {models.map((model) => (
195
+ <option key={model.id} value={model.id}>
196
+ {model.name}
197
+ </option>
198
+ ))}
199
+ </select>
200
+ </div>
201
+
202
+ <div style={aiStyles.modalInputGroup}>
203
+ <div style={{
204
+ display: "flex",
205
+ justifyContent: "space-between",
206
+ alignItems: "center",
207
+ marginBottom: "8px",
208
+ }}>
209
+ <label htmlFor="prompt-input" style={aiStyles.modalLabel}>
210
+ Prompt
211
+ <span style={{
212
+ color: "#6b7280",
213
+ marginLeft: "4px",
214
+ fontSize: "12px",
215
+ fontWeight: 400,
216
+ }}>
217
+ (Cmd/Ctrl + Enter to submit)
218
+ </span>
219
+ </label>
220
+ {filteredPrompts.length > 0 && (
221
+ <button
222
+ onClick={() => setShowPromptLibrary(true)}
223
+ style={{
224
+ padding: "4px 12px",
225
+ fontSize: "12px",
226
+ color: "#ffffff",
227
+ background: "#8b5cf620",
228
+ border: "none",
229
+ borderRadius: "6px",
230
+ cursor: "pointer",
231
+ transition: "all 0.2s",
232
+ }}
233
+ onMouseEnter={(e) => {
234
+ e.currentTarget.style.background = "#8b5cf630";
235
+ }}
236
+ onMouseLeave={(e) => {
237
+ e.currentTarget.style.background = "#8b5cf620";
238
+ }}
239
+ >
240
+ 📚 Browse Prompts ({filteredPrompts.length})
241
+ </button>
242
+ )}
243
+ </div>
244
+ <textarea
245
+ id="prompt-input"
246
+ value={prompt}
247
+ onChange={(e) => setPrompt(e.target.value)}
248
+ onFocus={() => setPromptFocused(true)}
249
+ onBlur={() => setPromptFocused(false)}
250
+ placeholder={sourceText
251
+ ? "Enter your AI prompt... e.g., 'Correct spelling and grammar', 'Make it more professional', 'Translate to English'"
252
+ : "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'"
253
+ }
254
+ rows={6}
255
+ style={{
256
+ ...aiStyles.textarea,
257
+ padding: "12px 16px",
258
+ ...(promptFocused && aiStyles.textareaFocus),
259
+ }}
260
+ />
261
+ </div>
262
+ </>
263
+ ) : (
264
+ <div>
265
+ <button
266
+ onClick={() => setShowPromptLibrary(false)}
267
+ style={{
268
+ padding: "8px 0",
269
+ fontSize: "14px",
270
+ color: "#8b5cf6",
271
+ background: "transparent",
272
+ border: "none",
273
+ cursor: "pointer",
274
+ marginBottom: "16px",
275
+ display: "flex",
276
+ alignItems: "center",
277
+ gap: "4px",
278
+ }}
279
+ >
280
+ ← Back to form
281
+ </button>
282
+
283
+ {promptsLoading ? (
284
+ <div style={{ textAlign: "center", padding: "40px 0" }}>
285
+ Loading prompts...
286
+ </div>
287
+ ) : filteredPrompts.length === 0 ? (
288
+ <div style={{ textAlign: "center", padding: "40px 0", color: "#6b7280" }}>
289
+ No prompts available for this model type
290
+ </div>
291
+ ) : (
292
+ <div style={{
293
+ display: "flex",
294
+ flexDirection: "column" as const,
295
+ gap: "12px",
296
+ maxHeight: "400px",
297
+ overflow: "auto",
298
+ }}>
299
+ {filteredPrompts.map((promptData: Prompt | PublicPrompt) => (
300
+ <div
301
+ key={promptData.id}
302
+ onClick={() => handleSelectPrompt(promptData)}
303
+ style={{
304
+ padding: "16px",
305
+ border: `1px solid #e5e7eb`,
306
+ borderRadius: "8px",
307
+ cursor: "pointer",
308
+ transition: "all 0.2s",
309
+ }}
310
+ onMouseEnter={(e) => {
311
+ e.currentTarget.style.background = "#8b5cf610";
312
+ e.currentTarget.style.borderColor = "#8b5cf6";
313
+ }}
314
+ onMouseLeave={(e) => {
315
+ e.currentTarget.style.background = "transparent";
316
+ e.currentTarget.style.borderColor = "#e5e7eb";
317
+ }}
318
+ >
319
+ <div style={{
320
+ fontWeight: 600,
321
+ marginBottom: "4px",
322
+ color: "#111827",
323
+ }}>
324
+ {promptData.title}
325
+ </div>
326
+ <div style={{
327
+ fontSize: "13px",
328
+ color: "#6b7280",
329
+ overflow: "hidden",
330
+ textOverflow: "ellipsis",
331
+ display: "-webkit-box",
332
+ WebkitLineClamp: 2,
333
+ WebkitBoxOrient: "vertical" as any,
334
+ }}>
335
+ {promptData.content}
336
+ </div>
337
+ {("category" in promptData && promptData.category) ? (
338
+ <div style={{
339
+ marginTop: "8px",
340
+ fontSize: "11px",
341
+ color: "#8b5cf6",
342
+ }}>
343
+ {String(promptData.category)}
344
+ </div>
345
+ ) : null}
346
+ </div>
347
+ ))}
348
+ </div>
127
349
  )}
128
- {models.map((model) => (
129
- <option key={model.id} value={model.id}>
130
- {model.name}
131
- </option>
132
- ))}
133
- </select>
134
- </div>
135
-
136
- <div style={aiStyles.modalInputGroup}>
137
- <label htmlFor="prompt-input" style={aiStyles.modalLabel}>
138
- Prompt
139
- <span style={{ color: aiStyles.textareaFocus.borderColor, marginLeft: "4px" }}>
140
- (Cmd/Ctrl + Enter to submit)
141
- </span>
142
- </label>
143
- <textarea
144
- id="prompt-input"
145
- value={prompt}
146
- onChange={(e) => setPrompt(e.target.value)}
147
- onFocus={() => setPromptFocused(true)}
148
- onBlur={() => setPromptFocused(false)}
149
- placeholder="Enter your AI prompt... e.g., 'Correct spelling and grammar'"
150
- rows={6}
151
- style={{
152
- ...aiStyles.textarea,
153
- padding: "12px 16px",
154
- ...(promptFocused && aiStyles.textareaFocus),
155
- }}
156
- />
157
- </div>
350
+ </div>
351
+ )}
158
352
  </div>
159
353
 
160
354
  <div style={aiStyles.modalFooter}>
@@ -181,7 +375,7 @@ export function AiPromptPanel({
181
375
  ...(!selectedModel || !prompt.trim() ? aiStyles.buttonDisabled : {}),
182
376
  }}
183
377
  >
184
- Generate with AI
378
+ {sourceText ? "Transform with AI" : "Generate with AI"}
185
379
  </button>
186
380
  </div>
187
381
  </div>
@@ -256,7 +256,7 @@ export function AiStatusButton({
256
256
 
257
257
  <div style={aiStyles.tooltipActions}>
258
258
  <a
259
- href="https://ai.lastbrain.io/fr/auth/dashboard"
259
+ href="https://prompt.lastbrain.io/fr/auth/dashboard"
260
260
  target="_blank"
261
261
  rel="noopener noreferrer"
262
262
  style={aiStyles.tooltipLink}
@@ -270,7 +270,7 @@ export function AiStatusButton({
270
270
  Dashboard
271
271
  </a>
272
272
  <a
273
- href="https://ai.lastbrain.io/fr/auth/billing"
273
+ href="https://prompt.lastbrain.io/fr/auth/billing"
274
274
  target="_blank"
275
275
  rel="noopener noreferrer"
276
276
  style={aiStyles.tooltipLink}
@@ -284,7 +284,7 @@ export function AiStatusButton({
284
284
  History
285
285
  </a>
286
286
  <a
287
- href="https://ai.lastbrain.io/fr/auth/billing"
287
+ href="https://prompt.lastbrain.io/fr/auth/billing"
288
288
  target="_blank"
289
289
  rel="noopener noreferrer"
290
290
  style={aiStyles.tooltipLink}
@@ -53,7 +53,8 @@ export function AiTextarea({
53
53
 
54
54
  const handleSubmit = async (
55
55
  selectedModel: string,
56
- selectedPrompt: string
56
+ selectedPrompt: string,
57
+ promptId?: string
57
58
  ) => {
58
59
  try {
59
60
  const result = await generateText({
@@ -186,6 +187,7 @@ export function AiTextarea({
186
187
  onSubmit={handleSubmit}
187
188
  uiMode={uiMode}
188
189
  models={models || []}
190
+ sourceText={textareaValue || undefined}
189
191
  />
190
192
  )}
191
193
  </div>