refactor: static single-provider settings form (no Add/Delete, form visible immediately); defaultSettings seeds one provider

This commit is contained in:
Hermes Agent committed 2026-09-19 16:19:34 +02:00
1 parent a26a1fd81e
commit bfd3638093
3 files changed
+53 -85

No files matched your search

+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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>}