From ce5aa025aa2133e850f6a8373258949d36e26dad Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 19 Sep 2026 15:51:12 +0200 Subject: [PATCH] feat: fetch APInex models from main thread, populate default-model dropdown (networkAccess added) --- src/main.ts | 25 +++++++++++++++++++++++++ src/settings.ts | 4 +++- src/ui.tsx | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index 17e9e6c..557b65e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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). diff --git a/src/settings.ts b/src/settings.ts index 4dd2e74..7da80dd 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -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: [] } } diff --git a/src/ui.tsx b/src/ui.tsx index 894ef8e..91517e5 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -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(null) + const [modelsError, setModelsError] = useState(null) + const settingsRef = useRef(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) )}