From 005109221609f24d8ffa24431edf67ae1ba05e8f Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:48:21 +0800 Subject: [PATCH] fix: improve model list layout in ProviderCard (#311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --------- Co-authored-by: idle888 <546806917@qq.com> Co-authored-by: Claude Sonnet 4.6 --- bin/hermes-web-ui.mjs | 0 .../components/hermes/models/ProviderCard.vue | 55 +++++++++++++++++++ packages/client/src/i18n/locales/en.ts | 13 +++++ packages/client/src/i18n/locales/zh.ts | 13 +++++ .../server/src/controllers/hermes/models.ts | 11 ++++ .../server/src/services/config-helpers.ts | 7 ++- packages/server/src/shared/providers.ts | 7 +++ scripts/setup.sh | 0 8 files changed, 104 insertions(+), 2 deletions(-) mode change 100755 => 100644 bin/hermes-web-ui.mjs mode change 100755 => 100644 scripts/setup.sh diff --git a/bin/hermes-web-ui.mjs b/bin/hermes-web-ui.mjs old mode 100755 new mode 100644 diff --git a/packages/client/src/components/hermes/models/ProviderCard.vue b/packages/client/src/components/hermes/models/ProviderCard.vue index 14f1b79..b59f889 100644 --- a/packages/client/src/components/hermes/models/ProviderCard.vue +++ b/packages/client/src/components/hermes/models/ProviderCard.vue @@ -91,6 +91,20 @@ async function handleDelete() { {{ t('models.baseUrl') }} {{ provider.base_url }} +
+ {{ t('models.models') }} + {{ provider.models.length }} {{ t('models.count') }} +
+
+ {{ model }} + + +{{ provider.models.length - 20 }} {{ t('models.more') }} + +
@@ -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; diff --git a/packages/client/src/i18n/locales/en.ts b/packages/client/src/i18n/locales/en.ts index 2f58bd3..5bfd001 100644 --- a/packages/client/src/i18n/locales/en.ts +++ b/packages/client/src/i18n/locales/en.ts @@ -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 diff --git a/packages/client/src/i18n/locales/zh.ts b/packages/client/src/i18n/locales/zh.ts index 359f8fb..a6b7712 100644 --- a/packages/client/src/i18n/locales/zh.ts +++ b/packages/client/src/i18n/locales/zh.ts @@ -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 扫描上方二维码,或在手机上打开链接完成绑定', }, // 网关 diff --git a/packages/server/src/controllers/hermes/models.ts b/packages/server/src/controllers/hermes/models.ts index b80363d..d3e5d01 100644 --- a/packages/server/src/controllers/hermes/models.ts +++ b/packages/server/src/controllers/hermes/models.ts @@ -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) : '' diff --git a/packages/server/src/services/config-helpers.ts b/packages/server/src/services/config-helpers.ts index 28532a8..9685f8d 100644 --- a/packages/server/src/services/config-helpers.ts +++ b/packages/server/src/services/config-helpers.ts @@ -32,6 +32,7 @@ export const PROVIDER_ENV_MAP: Record { +export async function fetchProviderModels(baseUrl: string, apiKey: string, freeOnly = false): Promise { 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 [] diff --git a/packages/server/src/shared/providers.ts b/packages/server/src/shared/providers.ts index ebf10bc..0c69684 100644 --- a/packages/server/src/shared/providers.ts +++ b/packages/server/src/shared/providers.ts @@ -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', diff --git a/scripts/setup.sh b/scripts/setup.sh old mode 100755 new mode 100644