refactor: static single-provider settings form (no Add/Delete, form visible immediately); defaultSettings seeds one provider
This commit is contained in:
1 parent
a26a1fd81e
commit
bfd3638093
3 files changed
+53
-85
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "figma-ai-plugin",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@create-figma-plugin/ui": "^4.0.3",
|
||||
|
||||
+3
-2
@@ -26,11 +26,12 @@ export const APINEX_BASE_URL = 'https://api.apinex.bond/v1'
|
||||
const SETTINGS_KEY = 'settings'
|
||||
|
||||
export function defaultSettings (): Settings {
|
||||
const p = createApinexProvider()
|
||||
return {
|
||||
version: 1,
|
||||
auth: { mode: 'local' },
|
||||
providers: [],
|
||||
activeProviderId: null
|
||||
providers: [p],
|
||||
activeProviderId: p.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
-82
@@ -21,7 +21,7 @@ import { emit, on } from '@create-figma-plugin/utilities'
|
||||
import { h, Fragment } from 'preact'
|
||||
import { useState, useEffect, useRef } from 'preact/hooks'
|
||||
import MenuItem from './MenuItem'
|
||||
import { type Settings, type ProviderConfig, defaultSettings, loadSettings, saveSettings, createApinexProvider, settingsToJSON, settingsFromJSON, APINEX_BASE_URL } from './settings'
|
||||
import { type Settings, type ProviderConfig, defaultSettings, loadSettings, saveSettings, settingsToJSON, settingsFromJSON, APINEX_BASE_URL } from './settings'
|
||||
|
||||
const MIN_WINDOW_SIZE = { width: 320, height: 200 }
|
||||
// No static max: "maximize" targets the whole viewport (maxViewport from main),
|
||||
@@ -29,7 +29,7 @@ const MIN_WINDOW_SIZE = { width: 320, height: 200 }
|
||||
// very high — actual clamp is done by main via clampWindowSize (bounds*zoom).
|
||||
|
||||
// Full plugin version shown in the footer status bar. Keep in sync with package.json "version".
|
||||
const PLUGIN_VERSION = '0.1.2'
|
||||
const PLUGIN_VERSION = '0.1.3'
|
||||
|
||||
const fieldStyle = {
|
||||
display: 'flex',
|
||||
@@ -64,50 +64,33 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
|
||||
const settingsRef = useRef<Settings>(settings)
|
||||
settingsRef.current = settings
|
||||
|
||||
// Static single provider (APInex). Always edit providers[0].
|
||||
const provider = settings.providers[0] != null ? settings.providers[0] : null
|
||||
|
||||
useEffect(function () {
|
||||
const dispose = on('MODELS_RESULT', function (payload: { ok: boolean, models: string[], error?: string }) {
|
||||
if (!payload.ok) { setModelsError(payload.error != null ? payload.error : 'Failed to load models'); return }
|
||||
setModelsError(null)
|
||||
const prev = settingsRef.current
|
||||
setSettings({
|
||||
...prev,
|
||||
providers: prev.providers.map(function (p) {
|
||||
return p.id === prev.activeProviderId ? { ...p, models: payload.models } : p
|
||||
})
|
||||
})
|
||||
if (prev.providers[0] == null) return
|
||||
const updated = { ...prev.providers[0], models: payload.models }
|
||||
setSettings({ ...prev, providers: [updated] })
|
||||
})
|
||||
return dispose
|
||||
}, [])
|
||||
|
||||
function fetchModels () {
|
||||
if (active == null) return
|
||||
setModelsError(null)
|
||||
emit('FETCH_MODELS', { baseUrl: active.baseUrl, apiKey: active.apiKey })
|
||||
}
|
||||
|
||||
const active = settings.providers.find(function (p) { return p.id === settings.activeProviderId }) || null
|
||||
|
||||
function patchActive (patch: Partial<ProviderConfig>) {
|
||||
if (active == null) return
|
||||
function patch (p: Partial<ProviderConfig>) {
|
||||
if (provider == null) return
|
||||
setSettings({
|
||||
...settings,
|
||||
providers: settings.providers.map(function (p) {
|
||||
return p.id === active.id ? { ...p, ...patch } : p
|
||||
})
|
||||
providers: [{ ...provider, ...p }]
|
||||
})
|
||||
}
|
||||
|
||||
function addProvider () {
|
||||
const p = createApinexProvider()
|
||||
const prev = settingsRef.current
|
||||
setSettings({ ...prev, providers: [...prev.providers, p], activeProviderId: p.id })
|
||||
}
|
||||
|
||||
function deleteActive () {
|
||||
if (active == null) return
|
||||
const providers = settings.providers.filter(function (p) { return p.id !== active.id })
|
||||
const activeProviderId = providers.length > 0 ? providers[0].id : null
|
||||
setSettings({ ...settings, providers, activeProviderId })
|
||||
function fetchModels () {
|
||||
if (provider == null) return
|
||||
setModelsError(null)
|
||||
emit('FETCH_MODELS', { baseUrl: provider.baseUrl, apiKey: provider.apiKey })
|
||||
}
|
||||
|
||||
function save () {
|
||||
@@ -146,60 +129,44 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
const providerOptions = settings.providers.map(function (p) {
|
||||
return { value: p.id, text: p.name }
|
||||
})
|
||||
if (provider == null) {
|
||||
return <Muted>No provider configured.</Muted>
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-16)' }}>
|
||||
<section>
|
||||
<Text style={{ fontWeight: 600, fontSize: '12px' }}>Providers</Text>
|
||||
<div style={{ display: 'flex', gap: 'var(--space-8)', marginTop: 'var(--space-8)' }}>
|
||||
<Dropdown
|
||||
placeholder="Select provider"
|
||||
value={settings.activeProviderId}
|
||||
onValueChange={function (id: string) { setSettings({ ...settings, activeProviderId: id }) }}
|
||||
options={providerOptions}
|
||||
/>
|
||||
<Button onClick={addProvider}>Add APInex</Button>
|
||||
<Button danger onClick={deleteActive} disabled={active == null}>Delete</Button>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-medium)' }}>
|
||||
<section style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-8)' }}>
|
||||
<Text style={{ fontWeight: 600, fontSize: '12px' }}>Provider · {provider.type}</Text>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>Name</span>
|
||||
<Textbox value={provider.name} placeholder="APInex" onValueInput={function (v: string) { patch({ name: v }) }} />
|
||||
</label>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>API key</span>
|
||||
<Textbox password={!showKey} value={provider.apiKey} placeholder="sk-apx…" onValueInput={function (v: string) { patch({ apiKey: v }) }} />
|
||||
</label>
|
||||
<Checkbox value={showKey} onValueChange={setShowKey}>Show key</Checkbox>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>Base URL</span>
|
||||
<Textbox value={provider.baseUrl} placeholder={APINEX_BASE_URL} onValueInput={function (v: string) { patch({ baseUrl: v }) }} />
|
||||
</label>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>Default model</span>
|
||||
<div style={{ display: 'flex', gap: 'var(--space-4)', alignItems: 'center' }}>
|
||||
<Dropdown
|
||||
placeholder={provider.models.length > 0 ? 'Select model' : 'Fetch models first'}
|
||||
value={provider.defaultModel}
|
||||
onValueChange={function (id: string) { patch({ defaultModel: id }) }}
|
||||
options={(provider.models || []).map(function (m) { return { value: m, text: m } })}
|
||||
/>
|
||||
<Button onClick={fetchModels} disabled={provider.apiKey.trim() === ''}>Fetch</Button>
|
||||
</div>
|
||||
{modelsError != null && (
|
||||
<Muted style={{ color: 'var(--figma-color-text-danger)' }}>{modelsError}</Muted>
|
||||
)}
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{active != null && (
|
||||
<section style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-8)' }}>
|
||||
<Text style={{ fontWeight: 600, fontSize: '12px' }}>{active.name} · {active.type}</Text>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>Name</span>
|
||||
<Textbox value={active.name} placeholder="My APInex" onValueInput={function (v: string) { patchActive({ name: v }) }} />
|
||||
</label>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>API key</span>
|
||||
<Textbox password={!showKey} value={active.apiKey} placeholder="sk-apx…" onValueInput={function (v: string) { patchActive({ apiKey: v }) }} />
|
||||
</label>
|
||||
<Checkbox value={showKey} onValueChange={setShowKey}>Show key</Checkbox>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>Base URL</span>
|
||||
<Textbox value={active.baseUrl} placeholder={APINEX_BASE_URL} onValueInput={function (v: string) { patchActive({ baseUrl: v }) }} />
|
||||
</label>
|
||||
<label style={fieldStyle}>
|
||||
<span style={labelStyle}>Default model</span>
|
||||
<div style={{ display: 'flex', gap: 'var(--space-4)', alignItems: 'center' }}>
|
||||
<Dropdown
|
||||
placeholder={active.models.length > 0 ? 'Select model' : 'Fetch models first'}
|
||||
value={active.defaultModel}
|
||||
onValueChange={function (id: string) { patchActive({ defaultModel: id }) }}
|
||||
options={(active.models || []).map(function (m) { return { value: m, text: m } })}
|
||||
/>
|
||||
<Button onClick={fetchModels} disabled={active.apiKey.trim() === ''}>Fetch</Button>
|
||||
</div>
|
||||
{modelsError != null && (
|
||||
<Muted style={{ color: 'var(--figma-color-text-danger)' }}>{modelsError}</Muted>
|
||||
)}
|
||||
</label>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: 'var(--space-8)', alignItems: 'center' }}>
|
||||
<Button onClick={save}>Save</Button>
|
||||
{savedNote && <Muted>Saved</Muted>}
|
||||
|
||||
Reference in new issue
Block a user