From d62c453ff54c1e724531cd8be59a3dcfcb8168f6 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 19 Sep 2026 17:06:36 +0200 Subject: [PATCH] fix: Fetch error shown as string (not [object Object]); extract API error from response body; guard fetch availability --- package.json | 2 +- src/main.ts | 18 +++++++++++++++--- src/ui.tsx | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 57a3cf9..bfdcb3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "figma-ai-plugin", - "version": "0.1.5", + "version": "0.1.6", "private": true, "dependencies": { "@create-figma-plugin/ui": "^4.0.3", diff --git a/src/main.ts b/src/main.ts index 4c5cd5c..a1d7649 100644 --- a/src/main.ts +++ b/src/main.ts @@ -504,14 +504,25 @@ 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 }) { + on('FETCH_MODELS', async function (payload: any) { try { + if (typeof fetch !== 'function') { + emit('MODELS_RESULT', { ok: false, error: 'fetch is not available in the plugin main thread', models: [] }) + return + } 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: [] }) + let msg = 'HTTP ' + res.status + try { + const body = JSON.parse(await res.text()) + if (body != null && body.error != null) { + msg = typeof body.error === 'string' ? body.error : (body.error.message || JSON.stringify(body.error)) + } + } catch (e) { /* keep HTTP status message */ } + emit('MODELS_RESULT', { ok: false, error: msg, models: [] }) return } const json: any = await res.json() @@ -523,7 +534,8 @@ export default function () { } emit('MODELS_RESULT', { ok: true, models }) } catch (e) { - emit('MODELS_RESULT', { ok: false, error: String(e), models: [] }) + const msg = e instanceof Error ? e.message : (e != null ? String(e) : 'Unknown error') + emit('MODELS_RESULT', { ok: false, error: msg, models: [] }) } }) diff --git a/src/ui.tsx b/src/ui.tsx index 5431c59..ad8d665 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -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.5' +const PLUGIN_VERSION = '0.1.6' const fieldStyle = { display: 'flex', @@ -90,7 +90,13 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: 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 } + if (!payload.ok) { + const msg = payload.error != null + ? (typeof payload.error === 'string' ? payload.error : JSON.stringify(payload.error)) + : 'Failed to load models' + setModelsError(msg) + return + } setModelsError(null) const prev = settingsRef.current if (prev.providers[0] == null) return