@mdguggenbichler/slugbase-core 0.0.19 → 0.0.20

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,10 +1,9 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
1
+ import { useState, useEffect, useCallback } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import api from '../../api/client';
4
4
  import { useToast } from '../ui/Toast';
5
5
  import { useAppConfig } from '../../contexts/AppConfigContext';
6
- import { Save, Sparkles } from 'lucide-react';
7
- import Button from '../ui/Button';
6
+ import { Sparkles } from 'lucide-react';
8
7
  import { PageLoadingSkeleton } from '../ui/PageLoadingSkeleton';
9
8
  import { Switch } from '../ui/switch';
10
9
  import { Label } from '../ui/label';
@@ -20,7 +19,6 @@ export default function AdminAI() {
20
19
  const { showToast } = useToast();
21
20
  const { adminAiOnlyToggle } = useAppConfig();
22
21
  const [loading, setLoading] = useState(true);
23
- const [saving, setSaving] = useState(false);
24
22
  const [settings, setSettings] = useState({
25
23
  ai_enabled: false,
26
24
  ai_provider: 'openai',
@@ -107,28 +105,29 @@ export default function AdminAI() {
107
105
  ? modelOptions
108
106
  : [{ value: settings.ai_model, label: settings.ai_model }, ...modelOptions];
109
107
 
110
- const handleSave = async (e: React.FormEvent) => {
111
- e.preventDefault();
112
- setSaving(true);
113
- try {
114
- const payload = adminAiOnlyToggle
115
- ? { ai_enabled: settings.ai_enabled }
116
- : {
117
- ai_enabled: settings.ai_enabled,
118
- ai_provider: settings.ai_provider,
119
- ai_model: settings.ai_model,
120
- ai_api_key: settings.ai_api_key || undefined,
121
- };
122
- await api.post('/admin/settings/ai', payload);
123
- showToast(t('common.success'), 'success');
124
- await loadSettings();
125
- } catch (err: unknown) {
126
- const e = err as { response?: { data?: { error?: string } } };
127
- showToast(e?.response?.data?.error || t('common.error'), 'error');
128
- } finally {
129
- setSaving(false);
130
- }
131
- };
108
+ const saveSettings = useCallback(
109
+ async (payload: { ai_enabled?: boolean; ai_provider?: string; ai_model?: string; ai_api_key?: string }) => {
110
+ try {
111
+ const body = adminAiOnlyToggle
112
+ ? { ai_enabled: payload.ai_enabled ?? settings.ai_enabled }
113
+ : {
114
+ ...(payload.ai_enabled !== undefined && { ai_enabled: payload.ai_enabled }),
115
+ ...(payload.ai_provider !== undefined && { ai_provider: payload.ai_provider }),
116
+ ...(payload.ai_model !== undefined && { ai_model: payload.ai_model }),
117
+ ...(payload.ai_api_key !== undefined && payload.ai_api_key !== '' && { ai_api_key: payload.ai_api_key }),
118
+ };
119
+ if (adminAiOnlyToggle && payload.ai_enabled === undefined) return;
120
+ if (!adminAiOnlyToggle && Object.keys(body).length === 0) return;
121
+ await api.post('/admin/settings/ai', body);
122
+ showToast(t('common.success'), 'success');
123
+ await loadSettings();
124
+ } catch (err: unknown) {
125
+ const e = err as { response?: { data?: { error?: string } } };
126
+ showToast(e?.response?.data?.error || t('common.error'), 'error');
127
+ }
128
+ },
129
+ [adminAiOnlyToggle, settings.ai_enabled, showToast, t, loadSettings]
130
+ );
132
131
 
133
132
  if (loading) {
134
133
  return <PageLoadingSkeleton lines={8} />;
@@ -149,14 +148,15 @@ export default function AdminAI() {
149
148
  </div>
150
149
  </div>
151
150
 
152
- <form onSubmit={handleSave} className="space-y-4">
151
+ <div className="space-y-4">
153
152
  <div className="flex items-center gap-3">
154
153
  <Switch
155
154
  id="ai-enabled"
156
155
  checked={settings.ai_enabled}
157
- onCheckedChange={(checked) =>
158
- setSettings({ ...settings, ai_enabled: checked })
159
- }
156
+ onCheckedChange={(checked) => {
157
+ setSettings((s) => ({ ...s, ai_enabled: checked }));
158
+ saveSettings({ ai_enabled: checked });
159
+ }}
160
160
  />
161
161
  <Label htmlFor="ai-enabled" className="text-sm font-medium text-gray-900 dark:text-white cursor-pointer">
162
162
  {t('admin.ai.enabled')}
@@ -171,7 +171,10 @@ export default function AdminAI() {
171
171
  </Label>
172
172
  <Select
173
173
  value={settings.ai_provider}
174
- onChange={(value) => setSettings({ ...settings, ai_provider: value })}
174
+ onChange={(value) => {
175
+ setSettings((s) => ({ ...s, ai_provider: value }));
176
+ saveSettings({ ai_provider: value });
177
+ }}
175
178
  options={providerOptions}
176
179
  className="max-w-xs"
177
180
  />
@@ -184,7 +187,12 @@ export default function AdminAI() {
184
187
  <Input
185
188
  type="password"
186
189
  value={settings.ai_api_key}
187
- onChange={(e) => setSettings({ ...settings, ai_api_key: e.target.value })}
190
+ onChange={(e) => setSettings((s) => ({ ...s, ai_api_key: e.target.value }))}
191
+ onBlur={() => {
192
+ if (settings.ai_api_key.trim() !== '') {
193
+ saveSettings({ ai_api_key: settings.ai_api_key });
194
+ }
195
+ }}
188
196
  placeholder={settings.ai_api_key_set ? t('admin.ai.apiKeyPlaceholder') : 'sk-...'}
189
197
  className="max-w-md font-mono text-sm"
190
198
  />
@@ -199,7 +207,10 @@ export default function AdminAI() {
199
207
  </Label>
200
208
  <Select
201
209
  value={settings.ai_model}
202
- onChange={(value) => setSettings({ ...settings, ai_model: value })}
210
+ onChange={(value) => {
211
+ setSettings((s) => ({ ...s, ai_model: value }));
212
+ saveSettings({ ai_model: value });
213
+ }}
203
214
  options={modelOptionsWithCurrent}
204
215
  placeholder={
205
216
  !settings.ai_api_key_set
@@ -214,18 +225,7 @@ export default function AdminAI() {
214
225
  </div>
215
226
  </>
216
227
  )}
217
-
218
- <div className="pt-2">
219
- <Button type="submit" variant="primary" disabled={saving}>
220
- {saving ? t('common.loading') : (
221
- <>
222
- <Save className="h-4 w-4 mr-2 inline" />
223
- {t('common.save')}
224
- </>
225
- )}
226
- </Button>
227
- </div>
228
- </form>
228
+ </div>
229
229
  </div>
230
230
  </div>
231
231
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdguggenbichler/slugbase-core",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "SlugBase core: backend and frontend entrypoints for self-hosted and cloud apps",
5
5
  "type": "module",
6
6
  "exports": {