@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
@@ -0,0 +1,150 @@
1
+ "use client";
2
+
3
+ import React, { useState, useRef, type InputHTMLAttributes } from "react";
4
+ import type { BaseAiProps } from "../types";
5
+ import { useAiCallText } from "../hooks/useAiCallText";
6
+ import { useAiModels } from "../hooks/useAiModels";
7
+ import { AiPromptPanel } from "./AiPromptPanel";
8
+
9
+ export interface AiInputProps
10
+ extends
11
+ Omit<BaseAiProps, "type">,
12
+ Omit<InputHTMLAttributes<HTMLInputElement>, "onValue"> {
13
+ uiMode?: "modal" | "drawer";
14
+ }
15
+
16
+ export function AiInput({
17
+ baseUrl,
18
+ apiKeyId,
19
+ uiMode = "modal",
20
+ context,
21
+ model,
22
+ prompt,
23
+ editMode = false,
24
+ onValue,
25
+ onToast,
26
+ disabled,
27
+ className,
28
+ ...inputProps
29
+ }: AiInputProps) {
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const [inputValue, setInputValue] = useState(
32
+ inputProps.value?.toString() || inputProps.defaultValue?.toString() || ""
33
+ );
34
+ const inputRef = useRef<HTMLInputElement>(null);
35
+
36
+ const { models } = useAiModels({ baseUrl, apiKeyId });
37
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
38
+
39
+ const hasConfiguration = Boolean(model && prompt);
40
+
41
+ const handleOpenPanel = () => {
42
+ setIsOpen(true);
43
+ };
44
+
45
+ const handleClosePanel = () => {
46
+ setIsOpen(false);
47
+ };
48
+
49
+ const handleSubmit = async (
50
+ selectedModel: string,
51
+ selectedPrompt: string
52
+ ) => {
53
+ try {
54
+ const result = await generateText({
55
+ model: selectedModel,
56
+ prompt: selectedPrompt,
57
+ context: context || inputValue || undefined,
58
+ actionType: "autocomplete",
59
+ });
60
+
61
+ if (result.text) {
62
+ if (editMode) {
63
+ setInputValue(result.text);
64
+ if (inputRef.current) {
65
+ inputRef.current.value = result.text;
66
+ }
67
+ }
68
+ onValue?.(result.text);
69
+ onToast?.({ type: "success", message: "AI generation successful" });
70
+ }
71
+ } catch (error) {
72
+ onToast?.({ type: "error", message: "Failed to generate text" });
73
+ } finally {
74
+ setIsOpen(false);
75
+ }
76
+ };
77
+
78
+ const handleQuickGenerate = async () => {
79
+ if (!model || !prompt) return;
80
+
81
+ try {
82
+ const result = await generateText({
83
+ model,
84
+ prompt,
85
+ context: context || inputValue || undefined,
86
+ actionType: "autocomplete",
87
+ });
88
+
89
+ if (result.text) {
90
+ if (editMode) {
91
+ setInputValue(result.text);
92
+ if (inputRef.current) {
93
+ inputRef.current.value = result.text;
94
+ }
95
+ }
96
+ onValue?.(result.text);
97
+ onToast?.({ type: "success", message: "AI generation successful" });
98
+ }
99
+ } catch (error) {
100
+ onToast?.({ type: "error", message: "Failed to generate text" });
101
+ }
102
+ };
103
+
104
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
105
+ const newValue = e.target.value;
106
+ setInputValue(newValue);
107
+ inputProps.onChange?.(e);
108
+ };
109
+
110
+ return (
111
+ <div data-ai-input-wrapper className={className}>
112
+ <input
113
+ ref={inputRef}
114
+ {...inputProps}
115
+ value={inputValue}
116
+ onChange={handleInputChange}
117
+ disabled={disabled || loading}
118
+ data-ai-input
119
+ />
120
+ {hasConfiguration ? (
121
+ <button
122
+ onClick={handleQuickGenerate}
123
+ disabled={disabled || loading}
124
+ data-ai-generate-button
125
+ type="button"
126
+ >
127
+ {loading ? "Generating..." : "AI"}
128
+ </button>
129
+ ) : (
130
+ <button
131
+ onClick={handleOpenPanel}
132
+ disabled={disabled || loading}
133
+ data-ai-setup-button
134
+ type="button"
135
+ >
136
+ Setup AI
137
+ </button>
138
+ )}
139
+ {isOpen && (
140
+ <AiPromptPanel
141
+ isOpen={isOpen}
142
+ onClose={handleClosePanel}
143
+ onSubmit={handleSubmit}
144
+ uiMode={uiMode}
145
+ models={models || []}
146
+ />
147
+ )}
148
+ </div>
149
+ );
150
+ }
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import type { ModelRef } from "@lastbrain/ai-ui-core";
5
+
6
+ export interface AiModelSelectProps {
7
+ models: ModelRef[];
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ className?: string;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export function AiModelSelect({
15
+ models,
16
+ value,
17
+ onChange,
18
+ className,
19
+ disabled,
20
+ }: AiModelSelectProps) {
21
+ return (
22
+ <select
23
+ value={value}
24
+ onChange={(e) => onChange(e.target.value)}
25
+ className={className}
26
+ disabled={disabled}
27
+ data-ai-model-select
28
+ >
29
+ <option value="">Select a model</option>
30
+ {models.map((model) => (
31
+ <option key={model.id} value={model.id}>
32
+ {model.name}
33
+ </option>
34
+ ))}
35
+ </select>
36
+ );
37
+ }
@@ -0,0 +1,120 @@
1
+ "use client";
2
+
3
+ import { useState, type ReactNode } from "react";
4
+ import type { ModelRef } from "@lastbrain/ai-ui-core";
5
+ import type { UiMode } from "../types";
6
+
7
+ export interface AiPromptPanelProps {
8
+ isOpen: boolean;
9
+ onClose: () => void;
10
+ onSubmit: (model: string, prompt: string) => void;
11
+ uiMode?: UiMode;
12
+ models?: ModelRef[];
13
+ children?: (props: AiPromptPanelRenderProps) => ReactNode;
14
+ }
15
+
16
+ export interface AiPromptPanelRenderProps {
17
+ models?: ModelRef[];
18
+ selectedModel: string;
19
+ setSelectedModel: (model: string) => void;
20
+ prompt: string;
21
+ setPrompt: (prompt: string) => void;
22
+ handleSubmit: () => void;
23
+ handleClose: () => void;
24
+ }
25
+
26
+ export function AiPromptPanel({
27
+ isOpen,
28
+ onClose,
29
+ onSubmit,
30
+ uiMode = "modal",
31
+ models = [],
32
+ children,
33
+ }: AiPromptPanelProps) {
34
+ const [selectedModel, setSelectedModel] = useState(models[0]?.id || "");
35
+ const [prompt, setPrompt] = useState("");
36
+
37
+ if (!isOpen) return null;
38
+
39
+ const handleSubmit = () => {
40
+ onSubmit(selectedModel, prompt);
41
+ setPrompt("");
42
+ };
43
+
44
+ const handleClose = () => {
45
+ onClose();
46
+ setPrompt("");
47
+ };
48
+
49
+ const renderProps: AiPromptPanelRenderProps = {
50
+ models,
51
+ selectedModel,
52
+ setSelectedModel,
53
+ prompt,
54
+ setPrompt,
55
+ handleSubmit,
56
+ handleClose,
57
+ };
58
+
59
+ if (children) {
60
+ return (
61
+ <div data-ai-prompt-panel data-type={uiMode}>
62
+ {children(renderProps)}
63
+ </div>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <div data-ai-prompt-panel data-type={uiMode}>
69
+ <div data-ai-prompt-panel-overlay onClick={handleClose} />
70
+ <div data-ai-prompt-panel-content>
71
+ <div data-ai-prompt-panel-header>
72
+ <h2>AI Prompt</h2>
73
+ <button onClick={handleClose} data-ai-close-button>
74
+ ×
75
+ </button>
76
+ </div>
77
+ <div data-ai-prompt-panel-body>
78
+ <div data-ai-model-select-wrapper>
79
+ <label htmlFor="model-select">Model</label>
80
+ <select
81
+ id="model-select"
82
+ value={selectedModel}
83
+ onChange={(e) => setSelectedModel(e.target.value)}
84
+ data-ai-model-select
85
+ >
86
+ {models.map((model) => (
87
+ <option key={model.id} value={model.id}>
88
+ {model.name}
89
+ </option>
90
+ ))}
91
+ </select>
92
+ </div>
93
+ <div data-ai-prompt-wrapper>
94
+ <label htmlFor="prompt-input">Prompt</label>
95
+ <textarea
96
+ id="prompt-input"
97
+ value={prompt}
98
+ onChange={(e) => setPrompt(e.target.value)}
99
+ placeholder="Enter your prompt..."
100
+ rows={5}
101
+ data-ai-prompt-input
102
+ />
103
+ </div>
104
+ </div>
105
+ <div data-ai-prompt-panel-footer>
106
+ <button onClick={handleClose} data-ai-cancel-button>
107
+ Cancel
108
+ </button>
109
+ <button
110
+ onClick={handleSubmit}
111
+ disabled={!selectedModel || !prompt}
112
+ data-ai-submit-button
113
+ >
114
+ Generate
115
+ </button>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ import React, { useState, type SelectHTMLAttributes } from "react";
4
+ import type { BaseAiProps } from "../types";
5
+ import { useAiCallText } from "../hooks/useAiCallText";
6
+ import { useAiModels } from "../hooks/useAiModels";
7
+ import { AiPromptPanel } from "./AiPromptPanel";
8
+
9
+ export interface AiSelectProps
10
+ extends
11
+ Omit<BaseAiProps, "type">,
12
+ Omit<SelectHTMLAttributes<HTMLSelectElement>, "onValue"> {
13
+ children: React.ReactNode;
14
+ uiMode?: "modal" | "drawer";
15
+ }
16
+
17
+ export function AiSelect({
18
+ baseUrl,
19
+ apiKeyId,
20
+ uiMode = "modal",
21
+ context,
22
+ model,
23
+ prompt,
24
+ onValue,
25
+ onToast,
26
+ disabled,
27
+ className,
28
+ children,
29
+ ...selectProps
30
+ }: AiSelectProps) {
31
+ const [isOpen, setIsOpen] = useState(false);
32
+
33
+ const { models } = useAiModels({ baseUrl, apiKeyId });
34
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
35
+
36
+ const handleOpenPanel = () => {
37
+ setIsOpen(true);
38
+ };
39
+
40
+ const handleClosePanel = () => {
41
+ setIsOpen(false);
42
+ };
43
+
44
+ const handleSubmit = async (
45
+ selectedModel: string,
46
+ selectedPrompt: string
47
+ ) => {
48
+ try {
49
+ const result = await generateText({
50
+ model: selectedModel,
51
+ prompt: selectedPrompt,
52
+ context: context || undefined,
53
+ actionType: "autocomplete",
54
+ });
55
+
56
+ if (result.text) {
57
+ onValue?.(result.text);
58
+ onToast?.({ type: "success", message: "AI suggestion ready" });
59
+ }
60
+ } catch (error) {
61
+ onToast?.({ type: "error", message: "Failed to generate suggestion" });
62
+ } finally {
63
+ setIsOpen(false);
64
+ }
65
+ };
66
+
67
+ return (
68
+ <div data-ai-select-wrapper className={className}>
69
+ <select {...selectProps} disabled={disabled || loading} data-ai-select>
70
+ {children}
71
+ </select>
72
+ <button
73
+ onClick={handleOpenPanel}
74
+ disabled={disabled || loading}
75
+ data-ai-assist-button
76
+ type="button"
77
+ >
78
+ AI Assist
79
+ </button>
80
+ {isOpen && (
81
+ <AiPromptPanel
82
+ isOpen={isOpen}
83
+ onClose={handleClosePanel}
84
+ onSubmit={handleSubmit}
85
+ uiMode={uiMode}
86
+ models={models || []}
87
+ />
88
+ )}
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import { useState, type ButtonHTMLAttributes } from "react";
4
+ import { useAiStatus } from "../hooks/useAiStatus";
5
+
6
+ export interface AiSettingsButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
+ baseUrl?: string;
8
+ apiKeyId?: string;
9
+ onAddTokens?: () => void;
10
+ onAddStorage?: () => void;
11
+ onDashboard?: () => void;
12
+ onDocumentation?: () => void;
13
+ }
14
+
15
+ export function AiSettingsButton({
16
+ baseUrl,
17
+ apiKeyId,
18
+ onAddTokens,
19
+ onAddStorage,
20
+ onDashboard,
21
+ onDocumentation,
22
+ className,
23
+ children,
24
+ ...buttonProps
25
+ }: AiSettingsButtonProps) {
26
+ const [isOpen, setIsOpen] = useState(false);
27
+ const { status, loading } = useAiStatus({ baseUrl, apiKeyId });
28
+
29
+ const handleToggle = () => {
30
+ setIsOpen(!isOpen);
31
+ };
32
+
33
+ const handleClose = () => {
34
+ setIsOpen(false);
35
+ };
36
+
37
+ const hasApiKey = Boolean(apiKeyId);
38
+
39
+ return (
40
+ <div data-ai-settings-wrapper>
41
+ <button
42
+ {...buttonProps}
43
+ onClick={handleToggle}
44
+ className={className}
45
+ data-ai-settings-button
46
+ disabled={loading}
47
+ >
48
+ {children || "AI Settings"}
49
+ </button>
50
+ {isOpen && (
51
+ <div data-ai-settings-panel>
52
+ <div data-ai-settings-overlay onClick={handleClose} />
53
+ <div data-ai-settings-content>
54
+ <div data-ai-settings-header>
55
+ <h3>AI Settings</h3>
56
+ <button onClick={handleClose} data-ai-close-button>
57
+ ×
58
+ </button>
59
+ </div>
60
+ <div data-ai-settings-body>
61
+ {hasApiKey ? (
62
+ <>
63
+ {status && (
64
+ <div data-ai-status-section>
65
+ <div data-ai-status-item>
66
+ <span>Tokens:</span>
67
+ <span>{status.balance?.total || 0}</span>
68
+ </div>
69
+ {status.storage?.total_mb !== undefined && (
70
+ <div data-ai-status-item>
71
+ <span>Storage:</span>
72
+ <span>{status.storage.total_mb.toFixed(2)} MB</span>
73
+ </div>
74
+ )}
75
+ </div>
76
+ )}
77
+ <div data-ai-actions-section>
78
+ {onDashboard && (
79
+ <button onClick={onDashboard} data-ai-action-button>
80
+ Dashboard
81
+ </button>
82
+ )}
83
+ {onAddTokens && (
84
+ <button onClick={onAddTokens} data-ai-action-button>
85
+ Add Tokens
86
+ </button>
87
+ )}
88
+ {onAddStorage && (
89
+ <button onClick={onAddStorage} data-ai-action-button>
90
+ Add Storage
91
+ </button>
92
+ )}
93
+ </div>
94
+ </>
95
+ ) : (
96
+ <div data-ai-no-key-section>
97
+ <p>No API key configured</p>
98
+ {onDocumentation && (
99
+ <button onClick={onDocumentation} data-ai-action-button>
100
+ View Documentation
101
+ </button>
102
+ )}
103
+ </div>
104
+ )}
105
+ </div>
106
+ </div>
107
+ </div>
108
+ )}
109
+ </div>
110
+ );
111
+ }
@@ -0,0 +1,152 @@
1
+ "use client";
2
+
3
+ import React, { useState, useRef, type TextareaHTMLAttributes } from "react";
4
+ import type { BaseAiProps } from "../types";
5
+ import { useAiCallText } from "../hooks/useAiCallText";
6
+ import { useAiModels } from "../hooks/useAiModels";
7
+ import { AiPromptPanel } from "./AiPromptPanel";
8
+
9
+ export interface AiTextareaProps
10
+ extends
11
+ Omit<BaseAiProps, "type">,
12
+ Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "onValue"> {
13
+ uiMode?: "modal" | "drawer";
14
+ }
15
+
16
+ export function AiTextarea({
17
+ baseUrl,
18
+ apiKeyId,
19
+ uiMode = "modal",
20
+ context,
21
+ model,
22
+ prompt,
23
+ editMode = false,
24
+ onValue,
25
+ onToast,
26
+ disabled,
27
+ className,
28
+ ...textareaProps
29
+ }: AiTextareaProps) {
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const [textValue, setTextValue] = useState(
32
+ textareaProps.value?.toString() ||
33
+ textareaProps.defaultValue?.toString() ||
34
+ ""
35
+ );
36
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
37
+
38
+ const { models } = useAiModels({ baseUrl, apiKeyId });
39
+ const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
40
+
41
+ const hasConfiguration = Boolean(model && prompt);
42
+
43
+ const handleOpenPanel = () => {
44
+ setIsOpen(true);
45
+ };
46
+
47
+ const handleClosePanel = () => {
48
+ setIsOpen(false);
49
+ };
50
+
51
+ const handleSubmit = async (
52
+ selectedModel: string,
53
+ selectedPrompt: string
54
+ ) => {
55
+ try {
56
+ const result = await generateText({
57
+ model: selectedModel,
58
+ prompt: selectedPrompt,
59
+ context: context || textValue || undefined,
60
+ actionType: "autocomplete",
61
+ });
62
+
63
+ if (result.text) {
64
+ if (editMode) {
65
+ setTextValue(result.text);
66
+ if (textareaRef.current) {
67
+ textareaRef.current.value = result.text;
68
+ }
69
+ }
70
+ onValue?.(result.text);
71
+ onToast?.({ type: "success", message: "AI generation successful" });
72
+ }
73
+ } catch (error) {
74
+ onToast?.({ type: "error", message: "Failed to generate text" });
75
+ } finally {
76
+ setIsOpen(false);
77
+ }
78
+ };
79
+
80
+ const handleQuickGenerate = async () => {
81
+ if (!model || !prompt) return;
82
+
83
+ try {
84
+ const result = await generateText({
85
+ model,
86
+ prompt,
87
+ context: context || textValue || undefined,
88
+ actionType: "autocomplete",
89
+ });
90
+
91
+ if (result.text) {
92
+ if (editMode) {
93
+ setTextValue(result.text);
94
+ if (textareaRef.current) {
95
+ textareaRef.current.value = result.text;
96
+ }
97
+ }
98
+ onValue?.(result.text);
99
+ onToast?.({ type: "success", message: "AI generation successful" });
100
+ }
101
+ } catch (error) {
102
+ onToast?.({ type: "error", message: "Failed to generate text" });
103
+ }
104
+ };
105
+
106
+ const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
107
+ const newValue = e.target.value;
108
+ setTextValue(newValue);
109
+ textareaProps.onChange?.(e);
110
+ };
111
+
112
+ return (
113
+ <div data-ai-textarea-wrapper className={className}>
114
+ <textarea
115
+ ref={textareaRef}
116
+ {...textareaProps}
117
+ value={textValue}
118
+ onChange={handleTextareaChange}
119
+ disabled={disabled || loading}
120
+ data-ai-textarea
121
+ />
122
+ {hasConfiguration ? (
123
+ <button
124
+ onClick={handleQuickGenerate}
125
+ disabled={disabled || loading}
126
+ data-ai-generate-button
127
+ type="button"
128
+ >
129
+ {loading ? "Generating..." : "AI"}
130
+ </button>
131
+ ) : (
132
+ <button
133
+ onClick={handleOpenPanel}
134
+ disabled={disabled || loading}
135
+ data-ai-setup-button
136
+ type="button"
137
+ >
138
+ Setup AI
139
+ </button>
140
+ )}
141
+ {isOpen && (
142
+ <AiPromptPanel
143
+ isOpen={isOpen}
144
+ onClose={handleClosePanel}
145
+ onSubmit={handleSubmit}
146
+ uiMode={uiMode}
147
+ models={models || []}
148
+ />
149
+ )}
150
+ </div>
151
+ );
152
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, type ReactNode } from "react";
4
+ import type { UiMode } from "../types";
5
+
6
+ export interface AiContextValue {
7
+ baseUrl: string;
8
+ apiKeyId: string;
9
+ uiMode: UiMode;
10
+ }
11
+
12
+ const AiContext = createContext<AiContextValue | undefined>(undefined);
13
+
14
+ export interface AiProviderProps {
15
+ baseUrl: string;
16
+ apiKeyId: string;
17
+ uiMode?: UiMode;
18
+ children: ReactNode;
19
+ }
20
+
21
+ export function AiProvider({
22
+ baseUrl,
23
+ apiKeyId,
24
+ uiMode = "modal",
25
+ children,
26
+ }: AiProviderProps) {
27
+ const value: AiContextValue = {
28
+ baseUrl,
29
+ apiKeyId,
30
+ uiMode,
31
+ };
32
+
33
+ return <AiContext.Provider value={value}>{children}</AiContext.Provider>;
34
+ }
35
+
36
+ export function useAiContext(): AiContextValue {
37
+ const context = useContext(AiContext);
38
+ if (!context) {
39
+ throw new Error("useAiContext must be used within AiProvider");
40
+ }
41
+ return context;
42
+ }
43
+
44
+ export { AiContext };