@lastbrain/ai-ui-react 1.0.3

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 (70) hide show
  1. package/README.md +84 -0
  2. package/dist/components/AiChipLabel.d.ts +8 -0
  3. package/dist/components/AiChipLabel.d.ts.map +1 -0
  4. package/dist/components/AiChipLabel.js +39 -0
  5. package/dist/components/AiImageButton.d.ts +8 -0
  6. package/dist/components/AiImageButton.d.ts.map +1 -0
  7. package/dist/components/AiImageButton.js +36 -0
  8. package/dist/components/AiInput.d.ts +7 -0
  9. package/dist/components/AiInput.d.ts.map +1 -0
  10. package/dist/components/AiInput.js +77 -0
  11. package/dist/components/AiModelSelect.d.ts +10 -0
  12. package/dist/components/AiModelSelect.d.ts.map +1 -0
  13. package/dist/components/AiModelSelect.js +5 -0
  14. package/dist/components/AiPromptPanel.d.ts +22 -0
  15. package/dist/components/AiPromptPanel.d.ts.map +1 -0
  16. package/dist/components/AiPromptPanel.js +30 -0
  17. package/dist/components/AiSelect.d.ts +8 -0
  18. package/dist/components/AiSelect.d.ts.map +1 -0
  19. package/dist/components/AiSelect.js +38 -0
  20. package/dist/components/AiSettingsButton.d.ts +11 -0
  21. package/dist/components/AiSettingsButton.d.ts.map +1 -0
  22. package/dist/components/AiSettingsButton.js +16 -0
  23. package/dist/components/AiTextarea.d.ts +7 -0
  24. package/dist/components/AiTextarea.d.ts.map +1 -0
  25. package/dist/components/AiTextarea.js +79 -0
  26. package/dist/context/AiProvider.d.ts +18 -0
  27. package/dist/context/AiProvider.d.ts.map +1 -0
  28. package/dist/context/AiProvider.js +20 -0
  29. package/dist/hooks/useAiCallImage.d.ts +12 -0
  30. package/dist/hooks/useAiCallImage.d.ts.map +1 -0
  31. package/dist/hooks/useAiCallImage.js +29 -0
  32. package/dist/hooks/useAiCallText.d.ts +12 -0
  33. package/dist/hooks/useAiCallText.d.ts.map +1 -0
  34. package/dist/hooks/useAiCallText.js +29 -0
  35. package/dist/hooks/useAiClient.d.ts +12 -0
  36. package/dist/hooks/useAiClient.d.ts.map +1 -0
  37. package/dist/hooks/useAiClient.js +13 -0
  38. package/dist/hooks/useAiModels.d.ts +13 -0
  39. package/dist/hooks/useAiModels.d.ts.map +1 -0
  40. package/dist/hooks/useAiModels.js +32 -0
  41. package/dist/hooks/useAiStatus.d.ts +13 -0
  42. package/dist/hooks/useAiStatus.d.ts.map +1 -0
  43. package/dist/hooks/useAiStatus.js +32 -0
  44. package/dist/hooks/usePrompts.d.ts +35 -0
  45. package/dist/hooks/usePrompts.d.ts.map +1 -0
  46. package/dist/hooks/usePrompts.js +125 -0
  47. package/dist/index.d.ts +17 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +20 -0
  50. package/dist/types.d.ts +20 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +1 -0
  53. package/package.json +60 -0
  54. package/src/components/AiChipLabel.tsx +88 -0
  55. package/src/components/AiImageButton.tsx +87 -0
  56. package/src/components/AiInput.tsx +150 -0
  57. package/src/components/AiModelSelect.tsx +37 -0
  58. package/src/components/AiPromptPanel.tsx +120 -0
  59. package/src/components/AiSelect.tsx +91 -0
  60. package/src/components/AiSettingsButton.tsx +111 -0
  61. package/src/components/AiTextarea.tsx +152 -0
  62. package/src/context/AiProvider.tsx +44 -0
  63. package/src/hooks/useAiCallImage.ts +49 -0
  64. package/src/hooks/useAiCallText.ts +49 -0
  65. package/src/hooks/useAiClient.ts +23 -0
  66. package/src/hooks/useAiModels.ts +50 -0
  67. package/src/hooks/useAiStatus.ts +50 -0
  68. package/src/hooks/usePrompts.ts +187 -0
  69. package/src/index.ts +23 -0
  70. package/src/types.ts +20 -0
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @lastbrain/ai-ui-react
2
+
3
+ Headless React components for LastBrain AI UI Kit.
4
+
5
+ ## Features
6
+
7
+ - Headless components (no UI library dependency)
8
+ - React hooks for AI operations
9
+ - Context provider for configuration
10
+ - TypeScript support
11
+ - SSR safe
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @lastbrain/ai-ui-react @lastbrain/ai-ui-core
17
+ # or
18
+ pnpm add @lastbrain/ai-ui-react @lastbrain/ai-ui-core
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### With Provider (Recommended)
24
+
25
+ ```tsx
26
+ import { AiProvider, AiInput, AiSettingsButton } from "@lastbrain/ai-ui-react";
27
+
28
+ function App() {
29
+ return (
30
+ <AiProvider baseUrl="https://api.lastbrain.com" apiKeyId="your-key">
31
+ <div>
32
+ <AiInput
33
+ model="openai/gpt-4o-mini"
34
+ prompt="Improve this text"
35
+ onValue={(text) => console.log(text)}
36
+ onToast={(toast) => alert(toast.message)}
37
+ />
38
+ <AiSettingsButton />
39
+ </div>
40
+ </AiProvider>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ### Without Provider
46
+
47
+ ```tsx
48
+ import { AiInput } from "@lastbrain/ai-ui-react";
49
+
50
+ function MyComponent() {
51
+ return (
52
+ <AiInput
53
+ baseUrl="https://api.lastbrain.com"
54
+ apiKeyId="your-key"
55
+ model="openai/gpt-4o-mini"
56
+ prompt="Improve this text"
57
+ onValue={(text) => console.log(text)}
58
+ />
59
+ );
60
+ }
61
+ ```
62
+
63
+ ## Components
64
+
65
+ - `AiInput` - Text input with AI assistance
66
+ - `AiTextarea` - Textarea with AI assistance
67
+ - `AiSelect` - Select dropdown with AI assistance
68
+ - `AiChipLabel` - Chip/label with AI assistance
69
+ - `AiImageButton` - Button for AI image generation
70
+ - `AiSettingsButton` - Settings button with status display
71
+ - `AiPromptPanel` - Modal/drawer for model and prompt selection
72
+ - `AiModelSelect` - Model selection dropdown
73
+
74
+ ## Hooks
75
+
76
+ - `useAiClient` - Access AI client instance
77
+ - `useAiModels` - Fetch available models
78
+ - `useAiStatus` - Fetch user status (tokens, wallet, storage)
79
+ - `useAiCallText` - Generate text
80
+ - `useAiCallImage` - Generate images
81
+
82
+ ## API Reference
83
+
84
+ See the [full documentation](https://docs.lastbrain.com/ai-ui-kit/react) for detailed API reference.
@@ -0,0 +1,8 @@
1
+ import { type HTMLAttributes } from "react";
2
+ import type { BaseAiProps } from "../types";
3
+ export interface AiChipLabelProps extends Omit<BaseAiProps, "type">, HTMLAttributes<HTMLDivElement> {
4
+ label?: string;
5
+ uiMode?: "modal" | "drawer";
6
+ }
7
+ export declare function AiChipLabel({ baseUrl, apiKeyId, uiMode, context, model, prompt, onValue, onToast, disabled, className, label, ...divProps }: AiChipLabelProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=AiChipLabel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiChipLabel.d.ts","sourceRoot":"","sources":["../../src/components/AiChipLabel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAK5C,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,QAAQ,EACZ,EAAE,gBAAgB,2CA4DlB"}
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useAiCallText } from "../hooks/useAiCallText";
5
+ import { useAiModels } from "../hooks/useAiModels";
6
+ import { AiPromptPanel } from "./AiPromptPanel";
7
+ export function AiChipLabel({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, onValue, onToast, disabled, className, label, ...divProps }) {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const [chipLabel, setChipLabel] = useState(label || "");
10
+ const { models } = useAiModels({ baseUrl, apiKeyId });
11
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
12
+ const handleOpenPanel = () => {
13
+ setIsOpen(true);
14
+ };
15
+ const handleClosePanel = () => {
16
+ setIsOpen(false);
17
+ };
18
+ const handleSubmit = async (selectedModel, selectedPrompt) => {
19
+ try {
20
+ const result = await generateText({
21
+ model: selectedModel,
22
+ prompt: selectedPrompt,
23
+ context: context || chipLabel || undefined,
24
+ });
25
+ if (result.text) {
26
+ setChipLabel(result.text);
27
+ onValue?.(result.text);
28
+ onToast?.({ type: "success", message: "Label generated successfully" });
29
+ }
30
+ }
31
+ catch (error) {
32
+ onToast?.({ type: "error", message: "Failed to generate label" });
33
+ }
34
+ finally {
35
+ setIsOpen(false);
36
+ }
37
+ };
38
+ return (_jsxs("div", { "data-ai-chip-wrapper": true, className: className, ...divProps, children: [_jsx("div", { "data-ai-chip-label": true, children: chipLabel || "Generate label" }), _jsx("button", { onClick: handleOpenPanel, disabled: disabled || loading, "data-ai-chip-button": true, type: "button", children: loading ? "..." : "✨" }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] }))] }));
39
+ }
@@ -0,0 +1,8 @@
1
+ import { type ButtonHTMLAttributes } from "react";
2
+ import type { BaseAiProps } from "../types";
3
+ export interface AiImageButtonProps extends Omit<BaseAiProps, "onValue" | "type">, ButtonHTMLAttributes<HTMLButtonElement> {
4
+ onImage?: (imageUrl: string) => void;
5
+ uiMode?: "modal" | "drawer";
6
+ }
7
+ export declare function AiImageButton({ baseUrl, apiKeyId, uiMode, context, model, prompt, onImage, onToast, disabled, className, children, ...buttonProps }: AiImageButtonProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=AiImageButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAK5C,MAAM,WAAW,kBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,oBAAoB,CAAC,iBAAiB,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,kBAAkB,2CAyDpB"}
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useAiCallImage } from "../hooks/useAiCallImage";
5
+ import { useAiModels } from "../hooks/useAiModels";
6
+ import { AiPromptPanel } from "./AiPromptPanel";
7
+ export function AiImageButton({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, onImage, onToast, disabled, className, children, ...buttonProps }) {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const { models } = useAiModels({ baseUrl, apiKeyId });
10
+ const { generateImage, loading } = useAiCallImage({ baseUrl, apiKeyId });
11
+ const handleOpenPanel = () => {
12
+ setIsOpen(true);
13
+ };
14
+ const handleClosePanel = () => {
15
+ setIsOpen(false);
16
+ };
17
+ const handleSubmit = async (selectedModel, selectedPrompt) => {
18
+ try {
19
+ const result = await generateImage({
20
+ model: selectedModel,
21
+ prompt: selectedPrompt,
22
+ });
23
+ if (result.url) {
24
+ onImage?.(result.url);
25
+ onToast?.({ type: "success", message: "Image generated successfully" });
26
+ }
27
+ }
28
+ catch (error) {
29
+ onToast?.({ type: "error", message: "Failed to generate image" });
30
+ }
31
+ finally {
32
+ setIsOpen(false);
33
+ }
34
+ };
35
+ return (_jsxs(_Fragment, { children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading, className: className, "data-ai-image-button": true, children: loading ? "Generating..." : children || "Generate Image" }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models?.filter((m) => m.type === "image") || [] }))] }));
36
+ }
@@ -0,0 +1,7 @@
1
+ import { type InputHTMLAttributes } from "react";
2
+ import type { BaseAiProps } from "../types";
3
+ export interface AiInputProps extends Omit<BaseAiProps, "type">, Omit<InputHTMLAttributes<HTMLInputElement>, "onValue"> {
4
+ uiMode?: "modal" | "drawer";
5
+ }
6
+ export declare function AiInput({ baseUrl, apiKeyId, uiMode, context, model, prompt, editMode, onValue, onToast, disabled, className, ...inputProps }: AiInputProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=AiInput.d.ts.map
@@ -0,0 +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;AAK5C,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,2CAyHd"}
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef } from "react";
4
+ import { useAiCallText } from "../hooks/useAiCallText";
5
+ import { useAiModels } from "../hooks/useAiModels";
6
+ import { AiPromptPanel } from "./AiPromptPanel";
7
+ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, onValue, onToast, disabled, className, ...inputProps }) {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const [inputValue, setInputValue] = useState(inputProps.value?.toString() || inputProps.defaultValue?.toString() || "");
10
+ const inputRef = useRef(null);
11
+ const { models } = useAiModels({ baseUrl, apiKeyId });
12
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
13
+ const hasConfiguration = Boolean(model && prompt);
14
+ const handleOpenPanel = () => {
15
+ setIsOpen(true);
16
+ };
17
+ const handleClosePanel = () => {
18
+ setIsOpen(false);
19
+ };
20
+ const handleSubmit = async (selectedModel, selectedPrompt) => {
21
+ try {
22
+ const result = await generateText({
23
+ model: selectedModel,
24
+ prompt: selectedPrompt,
25
+ context: context || inputValue || undefined,
26
+ actionType: "autocomplete",
27
+ });
28
+ if (result.text) {
29
+ if (editMode) {
30
+ setInputValue(result.text);
31
+ if (inputRef.current) {
32
+ inputRef.current.value = result.text;
33
+ }
34
+ }
35
+ onValue?.(result.text);
36
+ onToast?.({ type: "success", message: "AI generation successful" });
37
+ }
38
+ }
39
+ catch (error) {
40
+ onToast?.({ type: "error", message: "Failed to generate text" });
41
+ }
42
+ finally {
43
+ setIsOpen(false);
44
+ }
45
+ };
46
+ const handleQuickGenerate = async () => {
47
+ if (!model || !prompt)
48
+ return;
49
+ try {
50
+ const result = await generateText({
51
+ model,
52
+ prompt,
53
+ context: context || inputValue || undefined,
54
+ actionType: "autocomplete",
55
+ });
56
+ if (result.text) {
57
+ if (editMode) {
58
+ setInputValue(result.text);
59
+ if (inputRef.current) {
60
+ inputRef.current.value = result.text;
61
+ }
62
+ }
63
+ onValue?.(result.text);
64
+ onToast?.({ type: "success", message: "AI generation successful" });
65
+ }
66
+ }
67
+ catch (error) {
68
+ onToast?.({ type: "error", message: "Failed to generate text" });
69
+ }
70
+ };
71
+ const handleInputChange = (e) => {
72
+ const newValue = e.target.value;
73
+ setInputValue(newValue);
74
+ inputProps.onChange?.(e);
75
+ };
76
+ return (_jsxs("div", { "data-ai-input-wrapper": true, className: className, children: [_jsx("input", { ref: inputRef, ...inputProps, value: inputValue, onChange: handleInputChange, disabled: disabled || loading, "data-ai-input": true }), hasConfiguration ? (_jsx("button", { onClick: handleQuickGenerate, disabled: disabled || loading, "data-ai-generate-button": true, type: "button", children: loading ? "Generating..." : "AI" })) : (_jsx("button", { onClick: handleOpenPanel, disabled: disabled || loading, "data-ai-setup-button": true, type: "button", children: "Setup AI" })), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] }))] }));
77
+ }
@@ -0,0 +1,10 @@
1
+ import type { ModelRef } from "@lastbrain/ai-ui-core";
2
+ export interface AiModelSelectProps {
3
+ models: ModelRef[];
4
+ value: string;
5
+ onChange: (value: string) => void;
6
+ className?: string;
7
+ disabled?: boolean;
8
+ }
9
+ export declare function AiModelSelect({ models, value, onChange, className, disabled, }: AiModelSelectProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=AiModelSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiModelSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiModelSelect.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE,kBAAkB,2CAiBpB"}
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function AiModelSelect({ models, value, onChange, className, disabled, }) {
4
+ return (_jsxs("select", { value: value, onChange: (e) => onChange(e.target.value), className: className, disabled: disabled, "data-ai-model-select": true, children: [_jsx("option", { value: "", children: "Select a model" }), models.map((model) => (_jsx("option", { value: model.id, children: model.name }, model.id)))] }));
5
+ }
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ModelRef } from "@lastbrain/ai-ui-core";
3
+ import type { UiMode } from "../types";
4
+ export interface AiPromptPanelProps {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ onSubmit: (model: string, prompt: string) => void;
8
+ uiMode?: UiMode;
9
+ models?: ModelRef[];
10
+ children?: (props: AiPromptPanelRenderProps) => ReactNode;
11
+ }
12
+ export interface AiPromptPanelRenderProps {
13
+ models?: ModelRef[];
14
+ selectedModel: string;
15
+ setSelectedModel: (model: string) => void;
16
+ prompt: string;
17
+ setPrompt: (prompt: string) => void;
18
+ handleSubmit: () => void;
19
+ handleClose: () => void;
20
+ }
21
+ export declare function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode, models, children, }: AiPromptPanelProps): import("react/jsx-runtime").JSX.Element | null;
22
+ //# sourceMappingURL=AiPromptPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,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,kDAuFpB"}
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ export function AiPromptPanel({ isOpen, onClose, onSubmit, uiMode = "modal", models = [], children, }) {
5
+ const [selectedModel, setSelectedModel] = useState(models[0]?.id || "");
6
+ const [prompt, setPrompt] = useState("");
7
+ if (!isOpen)
8
+ return null;
9
+ const handleSubmit = () => {
10
+ onSubmit(selectedModel, prompt);
11
+ setPrompt("");
12
+ };
13
+ const handleClose = () => {
14
+ onClose();
15
+ setPrompt("");
16
+ };
17
+ const renderProps = {
18
+ models,
19
+ selectedModel,
20
+ setSelectedModel,
21
+ prompt,
22
+ setPrompt,
23
+ handleSubmit,
24
+ handleClose,
25
+ };
26
+ if (children) {
27
+ return (_jsx("div", { "data-ai-prompt-panel": true, "data-type": uiMode, children: children(renderProps) }));
28
+ }
29
+ return (_jsxs("div", { "data-ai-prompt-panel": true, "data-type": uiMode, children: [_jsx("div", { "data-ai-prompt-panel-overlay": true, onClick: handleClose }), _jsxs("div", { "data-ai-prompt-panel-content": true, children: [_jsxs("div", { "data-ai-prompt-panel-header": true, children: [_jsx("h2", { children: "AI Prompt" }), _jsx("button", { onClick: handleClose, "data-ai-close-button": true, children: "\u00D7" })] }), _jsxs("div", { "data-ai-prompt-panel-body": true, children: [_jsxs("div", { "data-ai-model-select-wrapper": true, children: [_jsx("label", { htmlFor: "model-select", children: "Model" }), _jsx("select", { id: "model-select", value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), "data-ai-model-select": true, children: models.map((model) => (_jsx("option", { value: model.id, children: model.name }, model.id))) })] }), _jsxs("div", { "data-ai-prompt-wrapper": true, children: [_jsx("label", { htmlFor: "prompt-input", children: "Prompt" }), _jsx("textarea", { id: "prompt-input", value: prompt, onChange: (e) => setPrompt(e.target.value), placeholder: "Enter your prompt...", rows: 5, "data-ai-prompt-input": true })] })] }), _jsxs("div", { "data-ai-prompt-panel-footer": true, children: [_jsx("button", { onClick: handleClose, "data-ai-cancel-button": true, children: "Cancel" }), _jsx("button", { onClick: handleSubmit, disabled: !selectedModel || !prompt, "data-ai-submit-button": true, children: "Generate" })] })] })] }));
30
+ }
@@ -0,0 +1,8 @@
1
+ import React, { type SelectHTMLAttributes } from "react";
2
+ import type { BaseAiProps } from "../types";
3
+ export interface AiSelectProps extends Omit<BaseAiProps, "type">, Omit<SelectHTMLAttributes<HTMLSelectElement>, "onValue"> {
4
+ children: React.ReactNode;
5
+ uiMode?: "modal" | "drawer";
6
+ }
7
+ export declare function AiSelect({ baseUrl, apiKeyId, uiMode, context, model, prompt, onValue, onToast, disabled, className, children, ...selectProps }: AiSelectProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=AiSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAK5C,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC1D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CA6Df"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useAiCallText } from "../hooks/useAiCallText";
5
+ import { useAiModels } from "../hooks/useAiModels";
6
+ import { AiPromptPanel } from "./AiPromptPanel";
7
+ export function AiSelect({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, onValue, onToast, disabled, className, children, ...selectProps }) {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const { models } = useAiModels({ baseUrl, apiKeyId });
10
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
11
+ const handleOpenPanel = () => {
12
+ setIsOpen(true);
13
+ };
14
+ const handleClosePanel = () => {
15
+ setIsOpen(false);
16
+ };
17
+ const handleSubmit = async (selectedModel, selectedPrompt) => {
18
+ try {
19
+ const result = await generateText({
20
+ model: selectedModel,
21
+ prompt: selectedPrompt,
22
+ context: context || undefined,
23
+ actionType: "autocomplete",
24
+ });
25
+ if (result.text) {
26
+ onValue?.(result.text);
27
+ onToast?.({ type: "success", message: "AI suggestion ready" });
28
+ }
29
+ }
30
+ catch (error) {
31
+ onToast?.({ type: "error", message: "Failed to generate suggestion" });
32
+ }
33
+ finally {
34
+ setIsOpen(false);
35
+ }
36
+ };
37
+ return (_jsxs("div", { "data-ai-select-wrapper": true, className: className, children: [_jsx("select", { ...selectProps, disabled: disabled || loading, "data-ai-select": true, children: children }), _jsx("button", { onClick: handleOpenPanel, disabled: disabled || loading, "data-ai-assist-button": true, type: "button", children: "AI Assist" }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] }))] }));
38
+ }
@@ -0,0 +1,11 @@
1
+ import { type ButtonHTMLAttributes } from "react";
2
+ export interface AiSettingsButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
3
+ baseUrl?: string;
4
+ apiKeyId?: string;
5
+ onAddTokens?: () => void;
6
+ onAddStorage?: () => void;
7
+ onDashboard?: () => void;
8
+ onDocumentation?: () => void;
9
+ }
10
+ export declare function AiSettingsButton({ baseUrl, apiKeyId, onAddTokens, onAddStorage, onDashboard, onDocumentation, className, children, ...buttonProps }: AiSettingsButtonProps): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=AiSettingsButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiSettingsButton.d.ts","sourceRoot":"","sources":["../../src/components/AiSettingsButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAG5D,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,WAAW,EACX,eAAe,EACf,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,qBAAqB,2CAsFvB"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useAiStatus } from "../hooks/useAiStatus";
5
+ export function AiSettingsButton({ baseUrl, apiKeyId, onAddTokens, onAddStorage, onDashboard, onDocumentation, className, children, ...buttonProps }) {
6
+ const [isOpen, setIsOpen] = useState(false);
7
+ const { status, loading } = useAiStatus({ baseUrl, apiKeyId });
8
+ const handleToggle = () => {
9
+ setIsOpen(!isOpen);
10
+ };
11
+ const handleClose = () => {
12
+ setIsOpen(false);
13
+ };
14
+ const hasApiKey = Boolean(apiKeyId);
15
+ return (_jsxs("div", { "data-ai-settings-wrapper": true, children: [_jsx("button", { ...buttonProps, onClick: handleToggle, className: className, "data-ai-settings-button": true, disabled: loading, children: children || "AI Settings" }), isOpen && (_jsxs("div", { "data-ai-settings-panel": true, children: [_jsx("div", { "data-ai-settings-overlay": true, onClick: handleClose }), _jsxs("div", { "data-ai-settings-content": true, children: [_jsxs("div", { "data-ai-settings-header": true, children: [_jsx("h3", { children: "AI Settings" }), _jsx("button", { onClick: handleClose, "data-ai-close-button": true, children: "\u00D7" })] }), _jsx("div", { "data-ai-settings-body": true, children: hasApiKey ? (_jsxs(_Fragment, { children: [status && (_jsxs("div", { "data-ai-status-section": true, children: [_jsxs("div", { "data-ai-status-item": true, children: [_jsx("span", { children: "Tokens:" }), _jsx("span", { children: status.balance?.total || 0 })] }), status.storage?.total_mb !== undefined && (_jsxs("div", { "data-ai-status-item": true, children: [_jsx("span", { children: "Storage:" }), _jsxs("span", { children: [status.storage.total_mb.toFixed(2), " MB"] })] }))] })), _jsxs("div", { "data-ai-actions-section": true, children: [onDashboard && (_jsx("button", { onClick: onDashboard, "data-ai-action-button": true, children: "Dashboard" })), onAddTokens && (_jsx("button", { onClick: onAddTokens, "data-ai-action-button": true, children: "Add Tokens" })), onAddStorage && (_jsx("button", { onClick: onAddStorage, "data-ai-action-button": true, children: "Add Storage" }))] })] })) : (_jsxs("div", { "data-ai-no-key-section": true, children: [_jsx("p", { children: "No API key configured" }), onDocumentation && (_jsx("button", { onClick: onDocumentation, "data-ai-action-button": true, children: "View Documentation" }))] })) })] })] }))] }));
16
+ }
@@ -0,0 +1,7 @@
1
+ import { type TextareaHTMLAttributes } from "react";
2
+ import type { BaseAiProps } from "../types";
3
+ export interface AiTextareaProps extends Omit<BaseAiProps, "type">, Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "onValue"> {
4
+ uiMode?: "modal" | "drawer";
5
+ }
6
+ export declare function AiTextarea({ baseUrl, apiKeyId, uiMode, context, model, prompt, editMode, onValue, onToast, disabled, className, ...textareaProps }: AiTextareaProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=AiTextarea.d.ts.map
@@ -0,0 +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;AAK5C,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,2CA2HjB"}
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef } from "react";
4
+ import { useAiCallText } from "../hooks/useAiCallText";
5
+ import { useAiModels } from "../hooks/useAiModels";
6
+ import { AiPromptPanel } from "./AiPromptPanel";
7
+ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, onValue, onToast, disabled, className, ...textareaProps }) {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const [textValue, setTextValue] = useState(textareaProps.value?.toString() ||
10
+ textareaProps.defaultValue?.toString() ||
11
+ "");
12
+ const textareaRef = useRef(null);
13
+ const { models } = useAiModels({ baseUrl, apiKeyId });
14
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
15
+ const hasConfiguration = Boolean(model && prompt);
16
+ const handleOpenPanel = () => {
17
+ setIsOpen(true);
18
+ };
19
+ const handleClosePanel = () => {
20
+ setIsOpen(false);
21
+ };
22
+ const handleSubmit = async (selectedModel, selectedPrompt) => {
23
+ try {
24
+ const result = await generateText({
25
+ model: selectedModel,
26
+ prompt: selectedPrompt,
27
+ context: context || textValue || undefined,
28
+ actionType: "autocomplete",
29
+ });
30
+ if (result.text) {
31
+ if (editMode) {
32
+ setTextValue(result.text);
33
+ if (textareaRef.current) {
34
+ textareaRef.current.value = result.text;
35
+ }
36
+ }
37
+ onValue?.(result.text);
38
+ onToast?.({ type: "success", message: "AI generation successful" });
39
+ }
40
+ }
41
+ catch (error) {
42
+ onToast?.({ type: "error", message: "Failed to generate text" });
43
+ }
44
+ finally {
45
+ setIsOpen(false);
46
+ }
47
+ };
48
+ const handleQuickGenerate = async () => {
49
+ if (!model || !prompt)
50
+ return;
51
+ try {
52
+ const result = await generateText({
53
+ model,
54
+ prompt,
55
+ context: context || textValue || undefined,
56
+ actionType: "autocomplete",
57
+ });
58
+ if (result.text) {
59
+ if (editMode) {
60
+ setTextValue(result.text);
61
+ if (textareaRef.current) {
62
+ textareaRef.current.value = result.text;
63
+ }
64
+ }
65
+ onValue?.(result.text);
66
+ onToast?.({ type: "success", message: "AI generation successful" });
67
+ }
68
+ }
69
+ catch (error) {
70
+ onToast?.({ type: "error", message: "Failed to generate text" });
71
+ }
72
+ };
73
+ const handleTextareaChange = (e) => {
74
+ const newValue = e.target.value;
75
+ setTextValue(newValue);
76
+ textareaProps.onChange?.(e);
77
+ };
78
+ return (_jsxs("div", { "data-ai-textarea-wrapper": true, className: className, children: [_jsx("textarea", { ref: textareaRef, ...textareaProps, value: textValue, onChange: handleTextareaChange, disabled: disabled || loading, "data-ai-textarea": true }), hasConfiguration ? (_jsx("button", { onClick: handleQuickGenerate, disabled: disabled || loading, "data-ai-generate-button": true, type: "button", children: loading ? "Generating..." : "AI" })) : (_jsx("button", { onClick: handleOpenPanel, disabled: disabled || loading, "data-ai-setup-button": true, type: "button", children: "Setup AI" })), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [] }))] }));
79
+ }
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ import type { UiMode } from "../types";
3
+ export interface AiContextValue {
4
+ baseUrl: string;
5
+ apiKeyId: string;
6
+ uiMode: UiMode;
7
+ }
8
+ declare const AiContext: import("react").Context<AiContextValue | undefined>;
9
+ export interface AiProviderProps {
10
+ baseUrl: string;
11
+ apiKeyId: string;
12
+ uiMode?: UiMode;
13
+ children: ReactNode;
14
+ }
15
+ export declare function AiProvider({ baseUrl, apiKeyId, uiMode, children, }: AiProviderProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function useAiContext(): AiContextValue;
17
+ export { AiContext };
18
+ //# sourceMappingURL=AiProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiProvider.d.ts","sourceRoot":"","sources":["../../src/context/AiProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,QAAA,MAAM,SAAS,qDAAuD,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,QAAQ,GACT,EAAE,eAAe,2CAQjB;AAED,wBAAgB,YAAY,IAAI,cAAc,CAM7C;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ const AiContext = createContext(undefined);
5
+ export function AiProvider({ baseUrl, apiKeyId, uiMode = "modal", children, }) {
6
+ const value = {
7
+ baseUrl,
8
+ apiKeyId,
9
+ uiMode,
10
+ };
11
+ return _jsx(AiContext.Provider, { value: value, children: children });
12
+ }
13
+ export function useAiContext() {
14
+ const context = useContext(AiContext);
15
+ if (!context) {
16
+ throw new Error("useAiContext must be used within AiProvider");
17
+ }
18
+ return context;
19
+ }
20
+ export { AiContext };
@@ -0,0 +1,12 @@
1
+ import type { AiImageRequest, AiImageResponse } from "@lastbrain/ai-ui-core";
2
+ export interface UseAiCallImageOptions {
3
+ baseUrl?: string;
4
+ apiKeyId?: string;
5
+ }
6
+ export interface UseAiCallImageResult {
7
+ generateImage: (request: AiImageRequest) => Promise<AiImageResponse>;
8
+ loading: boolean;
9
+ error: Error | null;
10
+ }
11
+ export declare function useAiCallImage(options?: UseAiCallImageOptions): UseAiCallImageResult;
12
+ //# sourceMappingURL=useAiCallImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiCallImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallImage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7E,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,oBAAoB,CA6BtB"}
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { useState, useCallback } from "react";
3
+ import { useAiClient } from "./useAiClient";
4
+ export function useAiCallImage(options) {
5
+ const client = useAiClient(options);
6
+ const [loading, setLoading] = useState(false);
7
+ const [error, setError] = useState(null);
8
+ const generateImage = useCallback(async (request) => {
9
+ setLoading(true);
10
+ setError(null);
11
+ try {
12
+ const result = await client.generateImage(request);
13
+ return result;
14
+ }
15
+ catch (err) {
16
+ const error = err instanceof Error ? err : new Error("Failed to generate image");
17
+ setError(error);
18
+ throw error;
19
+ }
20
+ finally {
21
+ setLoading(false);
22
+ }
23
+ }, [client]);
24
+ return {
25
+ generateImage,
26
+ loading,
27
+ error,
28
+ };
29
+ }