feat: bundle apinex model catalog statically (Figma sandbox has no network egress); dropdown uses bundled list, Fetch falls back gracefully

This commit is contained in:
Hermes Agent committed 2026-09-19 18:06:44 +02:00
1 parent 0f7005e4b1
commit 15d5f0408f
4 files changed
+44 -6

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "figma-ai-plugin", "name": "figma-ai-plugin",
"version": "0.1.11", "version": "0.1.12",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@create-figma-plugin/ui": "^4.0.3", "@create-figma-plugin/ui": "^4.0.3",
+33
View File
@@ -0,0 +1,33 @@
// Auto-generated from apinex /v1/models (bundled; Figma plugin sandbox has no network egress).
export const APINEX_MODELS: string[] = [
"claude-fable-5.1",
"claude-opus-5",
"claude-sonnet-5",
"deepseek-v4-flash",
"deepseek-v4-pro",
"deepseek-v4.1-flash",
"free/claude-opus-4.6",
"free/claude-sonnet-4.6",
"free/deepseek-v4-flash-0731",
"free/deepseek-v4-pro-0813",
"free/deepseek-v4.1-flash",
"free/glm-5.3-flash",
"free/gemini-3.1-pro",
"free/gemini-3.8-flash",
"free/gpt-5.6-luna",
"free/hy4",
"free/kimi-k3",
"free/mimo-v2.5",
"free/muse-spark-1.3",
"free/qwen-3.8-max",
"gemini-3.1-pro",
"gemini-3.8-flash",
"kimi-k3",
"gpt-5.6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-6-astra",
"glm-5.3",
"glm-5.3-flash",
"grok-4.6"
]
+3 -1
View File
@@ -3,6 +3,8 @@
// No server auth yet — everything lives on the Figma file locally. Backup/restore // No server auth yet — everything lives on the Figma file locally. Backup/restore
// is just a JSON export/import of this same document. (Server auth comes later.) // is just a JSON export/import of this same document. (Server auth comes later.)
import { APINEX_MODELS } from './apinex-models'
export type ProviderType = 'apinex' export type ProviderType = 'apinex'
export interface ProviderConfig { export interface ProviderConfig {
@@ -47,7 +49,7 @@ export function createApinexProvider (): ProviderConfig {
baseUrl: APINEX_BASE_URL, baseUrl: APINEX_BASE_URL,
apiKey: '', apiKey: '',
defaultModel: '', defaultModel: '',
models: [] models: APINEX_MODELS
} }
} }
+7 -4
View File
@@ -22,6 +22,7 @@ import { h, Fragment, Component } from 'preact'
import { useState, useEffect, useRef } from 'preact/hooks' import { useState, useEffect, useRef } from 'preact/hooks'
import MenuItem from './MenuItem' import MenuItem from './MenuItem'
import { type Settings, type ProviderConfig, defaultSettings, loadSettings, saveSettings, settingsToJSON, settingsFromJSON, APINEX_BASE_URL } from './settings' import { type Settings, type ProviderConfig, defaultSettings, loadSettings, saveSettings, settingsToJSON, settingsFromJSON, APINEX_BASE_URL } from './settings'
import { APINEX_MODELS } from './apinex-models'
const MIN_WINDOW_SIZE = { width: 320, height: 200 } const MIN_WINDOW_SIZE = { width: 320, height: 200 }
// No static max: "maximize" targets the whole viewport (maxViewport from main), // No static max: "maximize" targets the whole viewport (maxViewport from main),
@@ -29,7 +30,7 @@ const MIN_WINDOW_SIZE = { width: 320, height: 200 }
// very high — actual clamp is done by main via clampWindowSize (bounds*zoom). // 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". // Full plugin version shown in the footer status bar. Keep in sync with package.json "version".
const PLUGIN_VERSION = '0.1.11' const PLUGIN_VERSION = '0.1.12'
const fieldStyle = { const fieldStyle = {
display: 'flex', display: 'flex',
@@ -121,7 +122,7 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
: 'Failed to load models' : 'Failed to load models'
logEvent(' error: ' + msg) logEvent(' error: ' + msg)
if (payload.raw != null) logEvent(' RAW: ' + String(payload.raw).slice(0, 1000)) if (payload.raw != null) logEvent(' RAW: ' + String(payload.raw).slice(0, 1000))
setModelsError(msg) setModelsError('Live fetch unavailable (sandbox has no internet) — using bundled list')
return return
} }
logEvent(' models parsed=' + (payload.models ? payload.models.length : 0)) logEvent(' models parsed=' + (payload.models ? payload.models.length : 0))
@@ -197,6 +198,8 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
return <Muted>No provider configured.</Muted> return <Muted>No provider configured.</Muted>
} }
const modelOptions = (provider.models && provider.models.length > 0) ? provider.models : APINEX_MODELS
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-medium)' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-medium)' }}>
<section style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-8)' }}> <section style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-8)' }}>
@@ -218,10 +221,10 @@ function SettingsPanel (props: { settings: Settings, setSettings: (s: Settings)
<span style={labelStyle}>Default model</span> <span style={labelStyle}>Default model</span>
<div style={{ display: 'flex', gap: 'var(--space-4)', alignItems: 'center' }}> <div style={{ display: 'flex', gap: 'var(--space-4)', alignItems: 'center' }}>
<Dropdown <Dropdown
placeholder={provider.models.length > 0 ? 'Select model' : 'Fetch models first'} placeholder='Select model'
value={provider.defaultModel === '' ? null : provider.defaultModel} value={provider.defaultModel === '' ? null : provider.defaultModel}
onValueChange={function (id: string) { patch({ defaultModel: id }) }} onValueChange={function (id: string) { patch({ defaultModel: id }) }}
options={(provider.models || []).map(function (m) { return { value: m, text: m } })} options={modelOptions.map(function (m) { return { value: m, text: m } })}
/> />
<Button onClick={fetchModels} disabled={provider.apiKey.trim() === ''}>Fetch</Button> <Button onClick={fetchModels} disabled={provider.apiKey.trim() === ''}>Fetch</Button>
</div> </div>