feat: fetch APInex models from main thread, populate default-model dropdown (networkAccess added)

This commit is contained in:
Hermes Agent committed 2026-09-19 15:51:12 +02:00
1 parent 1468570d0f
commit ce5aa025aa
3 files changed
+64 -2

No files matched your search

+25
View File
@@ -502,6 +502,31 @@ export default function () {
})
})
// UI asks to list models for a provider. Network must run here (main thread) —
// the plugin UI iframe is CSP-sandboxed and cannot fetch external URLs.
on('FETCH_MODELS', async function (payload: { baseUrl: string, apiKey: string }) {
try {
const base = payload.baseUrl.replace(/\/+$/, '')
const res = await fetch(base + '/models', {
headers: { Authorization: 'Bearer ' + payload.apiKey }
})
if (!res.ok) {
emit('MODELS_RESULT', { ok: false, error: 'HTTP ' + res.status, models: [] })
return
}
const json: any = await res.json()
let models: string[] = []
if (Array.isArray(json.data)) {
models = json.data.map((m: any) => m.id).filter((x: any) => typeof x === 'string' && x.length > 0)
} else if (Array.isArray(json.models)) {
models = json.models.map((m: any) => m.id || m).filter((x: any) => typeof x === 'string')
}
emit('MODELS_RESULT', { ok: true, models })
} catch (e) {
emit('MODELS_RESULT', { ok: false, error: String(e), models: [] })
}
})
showUI({ width: 320, height: 200, themeColors: true, title: 'AI assistant (MVP)' })
// Restore persisted window placement (saved in clientStorage on the Figma file).
+3 -1
View File
@@ -12,6 +12,7 @@ export interface ProviderConfig {
baseUrl: string
apiKey: string
defaultModel: string
models: string[]
}
export interface Settings {
@@ -44,7 +45,8 @@ export function createApinexProvider (): ProviderConfig {
type: 'apinex',
baseUrl: APINEX_BASE_URL,
apiKey: '',
defaultModel: ''
defaultModel: '',
models: []
}
}
+36 -1
View File
@@ -60,6 +60,30 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
const [showKey, setShowKey] = useState(false)
const [savedNote, setSavedNote] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const [modelsError, setModelsError] = useState<string | null>(null)
const settingsRef = useRef<Settings>(settings)
settingsRef.current = settings
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
})
})
})
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
@@ -159,7 +183,18 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
</label>
<label style={fieldStyle}>
<span style={labelStyle}>Default model</span>
<Textbox value={active.defaultModel} placeholder="gpt-5-6-terra" onValueInput={function (v: string) { patchActive({ defaultModel: v }) }} />
<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>
)}