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