fix: improve model list layout in ProviderCard (#311)

* fix: add LongCat provider, OpenRouter free models, model list in cards

- Add longcat to PROVIDER_ENV_MAP and PROVIDER_PRESETS
- Add freeOnly param to fetchProviderModels, use for OpenRouter
- Show model list in ProviderCard with count
- Fix qq.ts import.meta.url → __dirname for CJS compat
- Add zh/en i18n keys for model count display

* fix: improve model list layout in ProviderCard

- Change models-list from max-height to fixed height (100px)
- Add align-content: flex-start to prevent vertical spacing
- Optimize gap to 4px vertical, 6px horizontal
- Fix model-tag height to 20px to prevent background stretching
- Use inline-flex for better tag alignment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: idle888 <546806917@qq.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-29 20:48:21 +08:00
committed by GitHub
parent 2ae7e7ad1b
commit 0051092216
8 changed files with 104 additions and 2 deletions
Executable → Regular
View File
@@ -91,6 +91,20 @@ async function handleDelete() {
<span class="info-label">{{ t('models.baseUrl') }}</span>
<code class="info-value mono">{{ provider.base_url }}</code>
</div>
<div class="info-row models-row">
<span class="info-label">{{ t('models.models') }}</span>
<span class="info-value models-count">{{ provider.models.length }} {{ t('models.count') }}</span>
</div>
<div class="models-list">
<span
v-for="model in provider.models.slice(0, 20)"
:key="model"
class="model-tag"
>{{ model }}</span>
<span v-if="provider.models.length > 20" class="model-tag model-tag-more">
+{{ provider.models.length - 20 }} {{ t('models.more') }}
</span>
</div>
</div>
<div class="card-actions">
@@ -176,6 +190,47 @@ async function handleDelete() {
font-size: 12px;
}
.models-row {
margin-top: 4px;
}
.models-count {
color: $text-muted;
font-size: 12px;
}
.models-list {
display: flex;
flex-wrap: wrap;
gap: 4px 6px;
margin-top: 6px;
height: 100px;
overflow-y: auto;
align-content: flex-start;
}
.model-tag {
display: inline-flex;
align-items: center;
height: 20px;
font-size: 10px;
font-family: $font-code;
padding: 2px 6px;
border-radius: 3px;
background: rgba(var(--accent-primary-rgb), 0.08);
color: $text-secondary;
white-space: nowrap;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
&-more {
background: rgba(var(--accent-primary-rgb), 0.15);
color: $accent-primary;
font-weight: 500;
}
}
.card-actions {
display: flex;
gap: 8px;
+13
View File
@@ -313,6 +313,9 @@ export default {
customModelPlaceholder: 'Custom model name',
customModelHint: 'Enter to load',
noProviders: 'No providers found. Add a custom provider to get started.',
models: 'Models',
count: 'models',
more: 'more',
builtIn: 'Built-in',
customType: 'Custom',
provider: 'Provider',
@@ -554,6 +557,16 @@ export default {
qrFetching: 'Fetching QR code...',
qrScanHint: 'Scan with WeChat to login',
qrScanedHint: 'Scaned, please confirm on phone...',
// QQ
qqAppId: 'App ID',
qqAppIdHint: 'QQ Open Platform Bot App ID',
qqAppSecret: 'App Secret',
qqAppSecretHint: 'QQ Open Platform Bot App Secret',
qqMarkdown: 'Markdown Support',
qqMarkdownHint: 'Enable Markdown formatted messages (some clients may not support)',
qqSandbox: 'Sandbox Mode',
qqSandboxHint: 'Enable sandbox environment (for testing)',
qqQrScanHint: 'Scan the QR code with QQ, or open the link on your phone to complete binding',
},
// Language
+13
View File
@@ -313,6 +313,9 @@ export default {
customModelPlaceholder: '自定义模型名称',
customModelHint: '按回车加载',
noProviders: '暂无 Provider,添加一个开始吧。',
models: '模型列表',
count: '个模型',
more: '个更多',
builtIn: '内置',
customType: '自定义',
provider: 'Provider',
@@ -546,6 +549,16 @@ export default {
qrFetching: '正在获取二维码...',
qrScanHint: '使用微信扫描二维码登录',
qrScanedHint: '已扫描,请在手机上确认...',
// QQ
qqAppId: 'App ID',
qqAppIdHint: 'QQ 开放平台机器人 App ID',
qqAppSecret: 'App Secret',
qqAppSecretHint: 'QQ 开放平台机器人 App Secret',
qqMarkdown: 'Markdown 支持',
qqMarkdownHint: '启用 Markdown 格式消息(部分客户端可能不支持)',
qqSandbox: '沙箱模式',
qqSandboxHint: '启用沙箱环境(测试用)',
qqQrScanHint: '使用 QQ 扫描上方二维码,或在手机上打开链接完成绑定',
},
// 网关
@@ -134,6 +134,17 @@ export async function getAvailable(ctx: any) {
}
if (Object.keys(modelMeta).length === 0) modelMeta = undefined
}
} else if (providerKey === 'openrouter') {
// OpenRouter has 200+ models — fetch dynamically like Copilot
if (envMapping.api_key_env) {
const orKey = envGetValue(envMapping.api_key_env)
if (orKey) {
try {
const fetched = await fetchProviderModels(baseUrl, orKey, true)
if (fetched.length > 0) modelsList = fetched
} catch { /* ignore — leave empty, won't show */ }
}
}
}
if (modelsList.length > 0) {
const apiKey = envMapping.api_key_env ? envGetValue(envMapping.api_key_env) : ''
@@ -32,6 +32,7 @@ export const PROVIDER_ENV_MAP: Record<string, { api_key_env: string; base_url_en
nous: { api_key_env: '', base_url_env: '' },
'openai-codex': { api_key_env: '', base_url_env: '' },
copilot: { api_key_env: '', base_url_env: '' },
longcat: { api_key_env: 'LONGCAT_API_KEY', base_url_env: 'LONGCAT_BASE_URL' },
}
// --- Types ---
@@ -182,7 +183,7 @@ export async function listFilesRecursive(dir: string, prefix: string): Promise<{
// --- Provider model helpers ---
export async function fetchProviderModels(baseUrl: string, apiKey: string): Promise<string[]> {
export async function fetchProviderModels(baseUrl: string, apiKey: string, freeOnly = false): Promise<string[]> {
const base = baseUrl.replace(/\/+$/, '')
const modelsUrl = /\/v\d+\/?$/.test(base) ? `${base}/models` : `${base}/v1/models`
try {
@@ -199,7 +200,9 @@ export async function fetchProviderModels(baseUrl: string, apiKey: string): Prom
logger.warn('available-models %s returned unexpected format', modelsUrl)
return []
}
return data.data.map(m => m.id).sort()
let models = data.data.map(m => m.id)
if (freeOnly) models = models.filter(m => m.endsWith(':free'))
return models.sort()
} catch (err: any) {
logger.error(err, 'available-models %s failed', modelsUrl)
return []
+7
View File
@@ -256,6 +256,13 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
base_url: 'https://opencode.ai/zen/go/v1',
models: ['glm-5.1', 'glm-5', 'kimi-k2.5', 'mimo-v2-pro', 'mimo-v2-omni', 'minimax-m2.7', 'minimax-m2.5'],
},
{
label: 'LongCat',
value: 'longcat',
builtin: true,
base_url: 'https://api.longcat.chat/openai',
models: ['LongCat-Flash-Lite', 'LongCat-2.0-Preview'],
},
{
label: 'OpenAI Codex',
value: 'openai-codex',
Executable → Regular
View File