diff --git a/web/static/css/style.css b/web/static/css/style.css index 683e2345..25306192 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -4430,7 +4430,7 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { .ai-channel-manager { border: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 88%, transparent); - border-radius: 10px; + border-radius: 8px; background: var(--card-bg, #fff); overflow: hidden; } @@ -4446,8 +4446,8 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { } .ai-channel-manager-header h4 { - margin: 0 0 8px; - font-size: 16px; + margin: 0 0 6px; + font-size: 17px; line-height: 1.25; } @@ -4467,55 +4467,75 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { color: var(--error-color, #e53e3e); } +.ai-channel-header-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 10px; + flex-shrink: 0; +} + .ai-channel-save-btn { flex-shrink: 0; min-width: 92px; } .ai-channel-manager-body { - display: grid; - grid-template-columns: minmax(280px, 360px) minmax(0, 1fr); + display: block; min-height: 0; - align-items: stretch; background: var(--card-bg, #fff); overflow: visible; } -.ai-channel-sidebar { - position: relative; - display: flex; - flex-direction: column; - gap: 10px; - min-height: 0; - overflow: hidden; - border-right: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 76%, transparent); - background: color-mix(in srgb, var(--bg-secondary, #f8fafc) 86%, var(--card-bg, #fff)); - padding: 18px; -} - -.ai-channel-sidebar-head { +.ai-channel-switcher { display: flex; align-items: center; justify-content: space-between; - gap: 10px; - margin-bottom: 0; - color: var(--text-secondary, #4a5568); - font-size: 12px; - font-weight: 700; - letter-spacing: 0; + gap: 16px; + padding: 16px 22px; + border-bottom: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 72%, transparent); + background: color-mix(in srgb, var(--bg-secondary, #f8fafc) 70%, var(--card-bg, #fff)); } -.ai-channel-bulk-actions { +.ai-channel-switcher-field { + display: grid; + grid-template-columns: auto minmax(260px, 420px); + align-items: center; + gap: 10px; + min-width: 0; +} + +.ai-channel-switcher-field label { + color: var(--text-secondary, #4a5568); + font-size: 13px; + font-weight: 700; + white-space: nowrap; +} + +.ai-channel-switch-select { + width: 100%; + min-height: 38px; + padding: 0.45rem 2rem 0.45rem 0.7rem; + border: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 88%, transparent); + border-radius: 7px; + background: color-mix(in srgb, var(--input-bg, var(--card-bg, #fff)) 92%, transparent); + color: var(--text-primary, #2d3748); + font-size: 0.875rem; +} + +.ai-channel-switch-select:focus { + border-color: color-mix(in srgb, var(--accent-color, #3182ce) 70%, transparent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color, #3182ce) 12%, transparent); + outline: none; +} + +.ai-channel-switcher-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; - gap: 5px; - margin: 0; - min-width: 0; - padding: 0; - border: 0; - background: transparent; + gap: 8px; } .ai-channel-bulk-btn { @@ -4523,8 +4543,8 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { align-items: center; justify-content: center; min-width: 0; - min-height: 26px; - padding: 4px 8px; + min-height: 28px; + padding: 5px 9px; border: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 68%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--card-bg, #fff) 38%, transparent); @@ -4555,8 +4575,8 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { } .ai-channel-icon-btn { - width: 30px; - height: 30px; + width: 32px; + height: 32px; border: 1px solid var(--border-color, #e2e8f0); border-radius: 8px; background: var(--card-bg, #fff); @@ -4577,19 +4597,7 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { } .ai-channel-list { - position: absolute; - top: 64px; - right: 13px; - bottom: 18px; - left: 18px; - display: flex; - flex-direction: column; - gap: 10px; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - padding: 2px 5px 2px 1px; - scrollbar-gutter: stable; + display: none; } .ai-channel-list::-webkit-scrollbar { @@ -4616,10 +4624,10 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { align-items: start; gap: 10px; width: 100%; - min-height: 92px; - padding: 12px; + min-height: 86px; + padding: 11px; border: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 64%, transparent); - border-radius: 8px; + border-radius: 7px; background: color-mix(in srgb, var(--card-bg, #fff) 70%, transparent); color: var(--text-color, #2d3748); text-align: left; @@ -4640,7 +4648,7 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { height: 16px; margin: 3px 0 0; border: 1px solid color-mix(in srgb, var(--text-muted, #718096) 52%, transparent); - border-radius: 5px; + border-radius: 4px; appearance: none; -webkit-appearance: none; background-color: color-mix(in srgb, var(--card-bg, #fff) 84%, transparent); @@ -4671,7 +4679,6 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { .ai-channel-list-item:hover { background: var(--card-bg, #fff); border-color: color-mix(in srgb, var(--accent-color, #3182ce) 24%, transparent); - transform: translateY(-1px); } .ai-channel-list-item.active { @@ -4779,17 +4786,17 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { min-width: 0; min-height: 0; overflow-y: visible; - padding: 22px 28px 28px; + padding: 20px 28px 28px; background: var(--card-bg, #fff); } .ai-channel-editor-head { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 18px; - margin-bottom: 18px; - padding-bottom: 18px; + margin-bottom: 6px; + padding-bottom: 16px; border-bottom: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 72%, transparent); } @@ -4804,7 +4811,7 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { .ai-channel-editor-head h5 { margin: 0; color: var(--text-color, #2d3748); - font-size: 22px; + font-size: 21px; font-weight: 700; line-height: 1.2; } @@ -4827,8 +4834,9 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { color: var(--error-color, #e53e3e); } + .ai-channel-editor-form { - max-width: 980px; + max-width: none; } .ai-channel-editor-form .form-group { @@ -4837,7 +4845,7 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { .ai-channel-editor-form .form-group label { display: block; - margin-bottom: 0; + margin-bottom: 6px; color: var(--text-primary); font-size: 0.875rem; font-weight: 500; @@ -4848,10 +4856,14 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { .ai-channel-editor-form input[type="password"], .ai-channel-editor-form input[type="number"], .ai-channel-editor-form select { + width: 100%; min-height: 42px; - border-radius: 8px; + padding: 0.5rem 0.75rem; + border-radius: 7px; border-color: color-mix(in srgb, var(--border-color, #e2e8f0) 88%, transparent); background: color-mix(in srgb, var(--input-bg, var(--card-bg, #fff)) 92%, transparent); + color: var(--text-color, #2d3748); + font-size: 0.875rem; } .ai-channel-editor-form input:focus, @@ -4863,9 +4875,175 @@ html[data-theme="dark"] .new-chat-btn:focus-visible { .ai-channel-editor-form .form-hint, .ai-channel-editor-form small { + display: block; + margin-top: 5px; + color: var(--text-muted, #718096); + font-size: 0.75rem; line-height: 1.45; } +.ai-channel-form-section { + padding: 18px 0; + border-bottom: 1px solid color-mix(in srgb, var(--border-color, #e2e8f0) 64%, transparent); +} + +.ai-channel-form-section:last-of-type { + border-bottom: 0; +} + +.ai-channel-form-section-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 24px; + margin-bottom: 14px; +} + +.ai-channel-form-section-head > div:first-child { + max-width: 620px; +} + +.ai-channel-form-section-head h6 { + margin: 0; + color: var(--text-primary, #1f2937); + font-size: 14px; + font-weight: 700; + line-height: 1.35; +} + +.ai-channel-form-section-head p { + margin: 4px 0 0; + color: var(--text-muted, #718096); + font-size: 12px; + line-height: 1.45; +} + +.ai-channel-section-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; + min-width: 210px; +} + +.ai-channel-section-actions .form-inline-result { + margin-top: 0; +} + +.ai-channel-section-actions .connection-test-result { + display: none; + max-width: min(760px, 54vw); + padding: 8px 10px; + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + background: color-mix(in srgb, var(--surface-color, #ffffff) 86%, var(--bg-secondary, #f7fafc)); + color: var(--text-muted, #718096); + font-size: 12px; + line-height: 1.45; + text-align: left; + white-space: pre-wrap; + overflow-wrap: anywhere; + word-break: break-word; +} + +.ai-channel-section-actions .connection-test-result.is-visible { + display: inline-block; +} + +.ai-channel-section-actions .connection-test-result.is-error { + border-color: color-mix(in srgb, var(--danger-color, #e53e3e) 42%, var(--border-color, #e2e8f0)); + background: color-mix(in srgb, var(--danger-color, #e53e3e) 8%, var(--surface-color, #ffffff)); + color: var(--danger-color, #e53e3e); +} + +.ai-channel-section-actions .connection-test-result.is-success { + border-color: color-mix(in srgb, var(--success-color, #38a169) 38%, var(--border-color, #e2e8f0)); + background: color-mix(in srgb, var(--success-color, #38a169) 9%, var(--surface-color, #ffffff)); + color: var(--success-color, #38a169); +} + +.ai-channel-form-grid, +.ai-channel-reasoning-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); + column-gap: 16px; + row-gap: 14px; +} + +.ai-channel-reasoning-grid { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.ai-channel-editor-form .span-2 { + grid-column: 1 / -1; +} + +.required-mark { + color: var(--error-color, #e53e3e); +} + +.form-inline-result { + display: block; + margin-top: 6px; + color: var(--text-muted, #718096); + font-size: 0.75rem; + line-height: 1.45; +} + +.ai-channel-reasoning-toggle { + margin-top: 8px; +} + +.ai-channel-advanced-section { + padding: 10px 0 0; + border-bottom: 0; +} + +.ai-channel-advanced-section summary { + display: flex; + align-items: center; + gap: 10px; + min-height: 38px; + padding: 0; + color: var(--text-primary, #1f2937); + cursor: pointer; + list-style: none; +} + +.ai-channel-advanced-section summary::-webkit-details-marker { + display: none; +} + +.ai-channel-advanced-section summary::before { + content: ""; + width: 8px; + height: 8px; + flex: 0 0 auto; + border-right: 2px solid var(--text-muted, #718096); + border-bottom: 2px solid var(--text-muted, #718096); + transform: rotate(-45deg); + transition: transform 0.16s ease; +} + +.ai-channel-advanced-section[open] summary::before { + transform: rotate(45deg); +} + +.ai-channel-advanced-section summary strong { + display: block; + font-size: 14px; + line-height: 1.35; +} + +.ai-channel-advanced-section summary small { + margin-top: 4px; +} + +.ai-channel-advanced-section[open] .ai-channel-reasoning-grid { + margin-top: 14px; +} + html[data-theme="dark"] .ai-channel-manager { background: #111827; border-color: rgba(71, 85, 105, 0.42); @@ -4876,6 +5054,21 @@ html[data-theme="dark"] .ai-channel-manager-header { border-bottom-color: rgba(71, 85, 105, 0.34); } +html[data-theme="dark"] .ai-channel-switcher { + background: #101827; + border-bottom-color: rgba(71, 85, 105, 0.34); +} + +html[data-theme="dark"] .ai-channel-switcher-field label { + color: #cbd5e1; +} + +html[data-theme="dark"] .ai-channel-switch-select { + background: rgba(15, 23, 42, 0.72); + border-color: rgba(71, 85, 105, 0.44); + color: #e5e7eb; +} + html[data-theme="dark"] .ai-channel-sidebar { background: #101827; border-right-color: rgba(71, 85, 105, 0.34); @@ -4928,6 +5121,42 @@ html[data-theme="dark"] .ai-channel-editor-head { border-bottom-color: rgba(71, 85, 105, 0.34); } +html[data-theme="dark"] .ai-channel-form-section, +html[data-theme="dark"] .ai-channel-advanced-section { + border-color: rgba(71, 85, 105, 0.34); +} + +html[data-theme="dark"] .ai-channel-form-section-head h6 { + color: #e5e7eb; +} + +html[data-theme="dark"] .ai-channel-form-section-head p, +html[data-theme="dark"] .form-inline-result { + color: #94a3b8; +} + +html[data-theme="dark"] .ai-channel-section-actions .connection-test-result { + border-color: rgba(71, 85, 105, 0.52); + background: rgba(15, 23, 42, 0.74); + color: #94a3b8; +} + +html[data-theme="dark"] .ai-channel-section-actions .connection-test-result.is-error { + border-color: rgba(248, 113, 113, 0.42); + background: rgba(127, 29, 29, 0.22); + color: #f87171; +} + +html[data-theme="dark"] .ai-channel-section-actions .connection-test-result.is-success { + border-color: rgba(52, 211, 153, 0.38); + background: rgba(6, 78, 59, 0.22); + color: #34d399; +} + +html[data-theme="dark"] .ai-channel-advanced-section summary { + color: #e5e7eb; +} + html[data-theme="dark"] .ai-channel-icon-btn { background: rgba(15, 23, 42, 0.72); border-color: rgba(71, 85, 105, 0.44); @@ -5009,23 +5238,9 @@ html[data-theme="dark"] .ai-channel-editor-form select { align-items: stretch; } - .ai-channel-manager-body { - grid-template-columns: 1fr; - height: auto; - overflow: visible; - } - - .ai-channel-sidebar { - border-right: 0; - border-bottom: 1px solid var(--border-color, #e2e8f0); - max-height: 420px; - } - - .ai-channel-list { - position: static; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - max-height: 360px; + .ai-channel-switcher { + align-items: stretch; + flex-direction: column; } .ai-channel-editor { @@ -5035,19 +5250,40 @@ html[data-theme="dark"] .ai-channel-editor-form select { @media (max-width: 640px) { .ai-channel-manager-header, - .ai-channel-sidebar, + .ai-channel-switcher, .ai-channel-editor { padding-left: 14px; padding-right: 14px; } - .ai-channel-editor-actions { + .ai-channel-switcher-actions { justify-content: flex-start; } - .ai-channel-bulk-actions { + .ai-channel-header-actions { justify-content: flex-start; } + + .ai-channel-switcher-field { + grid-template-columns: 1fr; + } + + .ai-channel-form-section-head, + .ai-channel-form-grid, + .ai-channel-reasoning-grid { + grid-template-columns: 1fr; + } + + .ai-channel-form-section-head, + .ai-channel-section-actions { + align-items: stretch; + flex-direction: column; + } + + .ai-channel-section-actions { + justify-content: flex-start; + min-width: 0; + } } .chat-reasoning-field-label { @@ -9923,6 +10159,54 @@ html[data-theme="dark"] .robot-binding-service-hint-icon { white-space: nowrap; } +.settings-custom-select-option--probe .settings-custom-select-label { + flex: 1 1 auto; +} + +.settings-custom-select-status { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + gap: 5px; + max-width: 42%; + padding: 2px 6px; + border-radius: 999px; + background: var(--bg-secondary, #f5f6f8); + color: var(--text-secondary, #64748b); + font-size: 11px; + font-weight: 600; + line-height: 1.3; +} + +.settings-custom-select-status-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.settings-custom-select-status-dot { + flex: 0 0 auto; + width: 6px; + height: 6px; + border-radius: 999px; + background: currentColor; +} + +.settings-custom-select-status.ready { + background: color-mix(in srgb, var(--success-color, #38a169) 11%, transparent); + color: var(--success-color, #38a169); +} + +.settings-custom-select-status.failed { + background: color-mix(in srgb, var(--danger-color, #e53e3e) 10%, transparent); + color: var(--danger-color, #e53e3e); +} + +.settings-custom-select-status.testing { + background: color-mix(in srgb, var(--accent-color, #0066ff) 10%, transparent); + color: var(--accent-color, #0066ff); +} + .form-group input:not([type="checkbox"]):not([type="radio"]):focus, .form-group select:focus { outline: none; @@ -38401,6 +38685,26 @@ html[data-theme="dark"] #page-settings .audit-custom-select-option.is-selected { color: #60a5fa !important; } +html[data-theme="dark"] #page-settings .settings-custom-select-status { + background: rgba(148, 163, 184, 0.12); + color: #94a3b8; +} + +html[data-theme="dark"] #page-settings .settings-custom-select-status.ready { + background: rgba(52, 211, 153, 0.14); + color: #34d399; +} + +html[data-theme="dark"] #page-settings .settings-custom-select-status.failed { + background: rgba(248, 113, 113, 0.14); + color: #f87171; +} + +html[data-theme="dark"] #page-settings .settings-custom-select-status.testing { + background: rgba(96, 165, 250, 0.14); + color: #60a5fa; +} + /* Chat @ tool mention panel dark theme. */ html[data-theme="dark"] .mention-suggestions { background: #111827 !important; diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index cfc20d0f..d5a6dc73 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -2603,23 +2603,40 @@ "aiChannelNew": "New", "aiChannelCopy": "Copy", "aiChannelDelete": "Delete", - "aiChannelHint": "Saved channels appear on the left. Saving writes to ai.channels; the default channel is used by new chats and tasks without an explicit channel.", + "aiChannelHint": "Use the dropdown to switch saved channels. Saving writes to ai.channels; the default channel is used by new chats and tasks without an explicit channel.", "aiChannelSavedList": "Saved channels", "aiChannelListAria": "AI channel list", "aiChannelEditing": "Editing", + "aiChannelBulkProbe": "Probe all", + "aiChannelBulkProbeTitle": "Check whether all complete channels are available", + "aiChannelSelectAria": "Select {name}", "aiChannelDefaultBadge": "Default", "aiChannelReady": "Ready", + "aiChannelReadyWithLatency": "Ready{latency}", "aiChannelDraft": "Incomplete", "aiChannelDefaultMeta": "Default channel", "aiChannelCustomMeta": "Custom channel", "aiChannelOpenAICompat": "OpenAI compatible", "aiChannelModelMissing": "Model missing", "aiChannelName": "Channel name", + "aiChannelConnectionSection": "Connection", + "aiChannelConnectionHint": "Confirm provider, endpoint, key, and model first. Test connection uses these values.", + "aiChannelModelSection": "Model and limits", + "aiChannelModelHint": "The model name controls routing. Token limits control context and single-response output.", + "aiChannelLimitsSection": "Limits", + "aiChannelLimitsHint": "Token limits control the context window and single-response output.", "aiChannelUntitled": "New Channel", "aiChannelDeleteConfirm": "Delete AI channel \"{name}\"?", "aiChannelSaved": "Channel saved", "aiChannelDefaultSaved": "Default channel saved", "aiChannelCount": "{count} channel(s) saved", + "aiChannelSaving": "Saving channel...", + "aiChannelNewUnsaved": "New channel is not saved yet. Fill it in, then click Save changes.", + "aiChannelCopyUnsaved": "Copied channel is not saved yet. Review it, then click Save changes.", + "aiChannelDeleted": "Channel deleted", + "aiChannelProbeNoComplete": "No complete channel to probe. Fill in Base URL, API Key, and Model first.", + "aiChannelProbing": "Probing {count} channel(s)...", + "aiChannelProbeDone": "Probe complete: {ok}/{total} ready", "apiProvider": "API Provider", "providerOpenAI": "OpenAI / OpenAI-compatible API", "providerClaude": "Claude (Anthropic Messages API)", @@ -2799,7 +2816,12 @@ "testing": "Testing connection...", "testSuccess": "Connection successful", "testFailed": "Connection failed", - "testError": "Test error" + "testError": "Test error", + "testErrorInvalidApiKey": "API Key is invalid or unauthorized. Check that the key is correct.", + "testErrorUnauthorized": "Authentication failed. Check the API Key.", + "testErrorForbidden": "Request denied. Check account permissions or model access.", + "testErrorModelUnavailable": "Model is unavailable or the model name is incorrect.", + "testErrorBaseUrl": "Base URL is unavailable. Check that the endpoint is correct." }, "settingsTerminal": { "title": "Terminal", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 7db25db0..98bd41d8 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -2591,23 +2591,40 @@ "aiChannelNew": "新增", "aiChannelCopy": "复制", "aiChannelDelete": "删除", - "aiChannelHint": "已保存的通道会显示在左侧;保存后写入 ai.channels,默认通道用于新对话和未指定通道的任务。", + "aiChannelHint": "通过下拉切换已保存通道;保存后写入 ai.channels,默认通道用于新对话和未指定通道的任务。", "aiChannelSavedList": "已保存通道", "aiChannelListAria": "AI 通道列表", "aiChannelEditing": "正在编辑", + "aiChannelBulkProbe": "批量探活", + "aiChannelBulkProbeTitle": "检测全部完整通道是否可用", + "aiChannelSelectAria": "选择 {name}", "aiChannelDefaultBadge": "默认", "aiChannelReady": "可用", + "aiChannelReadyWithLatency": "可用{latency}", "aiChannelDraft": "待完善", "aiChannelDefaultMeta": "默认通道", "aiChannelCustomMeta": "自定义通道", "aiChannelOpenAICompat": "OpenAI 兼容", "aiChannelModelMissing": "未填写模型", "aiChannelName": "通道名称", + "aiChannelConnectionSection": "连接信息", + "aiChannelConnectionHint": "先确认服务商、地址、密钥和模型,测试连接会使用这些信息。", + "aiChannelModelSection": "模型与额度", + "aiChannelModelHint": "模型名决定请求路由,Token 上限控制上下文和单次输出。", + "aiChannelLimitsSection": "额度设置", + "aiChannelLimitsHint": "Token 上限控制上下文窗口和单次输出。", "aiChannelUntitled": "新通道", "aiChannelDeleteConfirm": "确定删除 AI 通道「{name}」吗?", "aiChannelSaved": "通道已保存", "aiChannelDefaultSaved": "已设为默认通道", "aiChannelCount": "已保存 {count} 个通道", + "aiChannelSaving": "正在保存通道...", + "aiChannelNewUnsaved": "新通道尚未保存,填写后点击「保存更改」。", + "aiChannelCopyUnsaved": "复制的通道尚未保存,确认后点击「保存更改」。", + "aiChannelDeleted": "通道已删除", + "aiChannelProbeNoComplete": "没有可探活的完整通道,请先填写 Base URL、API Key 和模型", + "aiChannelProbing": "正在探活 {count} 个通道...", + "aiChannelProbeDone": "探活完成:{ok}/{total} 可用", "apiProvider": "API 提供商", "providerOpenAI": "OpenAI / 兼容 OpenAI 协议", "providerClaude": "Claude (Anthropic Messages API)", @@ -2787,7 +2804,12 @@ "testing": "测试中...", "testSuccess": "连接成功", "testFailed": "连接失败", - "testError": "测试出错" + "testError": "测试出错", + "testErrorInvalidApiKey": "API Key 无效或无权限,请检查密钥是否填写正确", + "testErrorUnauthorized": "认证失败,请检查 API Key", + "testErrorForbidden": "请求被拒绝,请检查账号权限或模型访问权限", + "testErrorModelUnavailable": "模型不可用或模型名不正确,请检查模型名称", + "testErrorBaseUrl": "Base URL 不可用,请检查地址是否正确" }, "settingsTerminal": { "title": "终端", diff --git a/web/static/js/settings.js b/web/static/js/settings.js index 8187e287..7e575dde 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -142,6 +142,19 @@ function syncSettingsCustomSelect(select) { item.appendChild(check); item.appendChild(label); + + if (select.id === 'ai-channel-select') { + const probeStatus = option.dataset.probeStatus || ''; + const probeMessage = option.dataset.probeMessage || ''; + if (probeStatus) { + item.classList.add('settings-custom-select-option--probe', `probe-${probeStatus}`); + const status = document.createElement('span'); + status.className = `settings-custom-select-status ${probeStatus}`; + status.innerHTML = ``; + status.querySelector('.settings-custom-select-status-text').textContent = probeMessage || probeStatus; + item.appendChild(status); + } + } reg.menu.appendChild(item); }); } @@ -2557,6 +2570,90 @@ function writeAIChannelToMainForm(id) { const allowEl = document.getElementById('openai-reasoning-allow-client'); if (allowEl) allowEl.checked = r.allow_client_reasoning !== false; syncModelListFetchButtons(); + syncAIChannelEditorPreview(); +} + +function displayAIChannelName(id, ch) { + const name = String(ch?.name || '').trim(); + if ((name === '新通道' || name === 'New Channel') && !String(ch?.model || '').trim()) { + return settingsT('settingsBasic.aiChannelUntitled', name || id); + } + return name || id; +} + +function aiChannelSelectLabel(id, ch) { + const marker = id === currentConfig?.ai?.default_channel ? ' *' : ''; + return `${displayAIChannelName(id, ch)}${marker} · ${ch?.model || '-'}`; +} + +function aiChannelOptionProbeMeta(id) { + const probe = aiChannelProbeResults[id]; + if (!probe) return null; + const status = probe.status || ''; + if (!['testing', 'ready', 'failed'].includes(status)) return null; + return { + status, + message: probe.message || (status === 'ready' + ? settingsT('settingsBasic.aiChannelReady', '可用') + : status === 'testing' + ? settingsT('settingsBasic.testing', '测试中...') + : settingsT('settingsBasic.testFailed', '连接失败')) + }; +} + +function updateAIChannelSelectOption(id) { + const select = document.getElementById('ai-channel-select'); + if (!select || !currentConfig?.ai?.channels) return; + const channelId = normalizeAIChannelId(id || selectedAIChannelId || currentConfig.ai.default_channel || 'default'); + const ch = currentConfig.ai.channels[channelId]; + if (!ch) return; + const opt = Array.from(select.options).find((option) => option.value === channelId); + if (opt) { + opt.textContent = aiChannelSelectLabel(channelId, ch); + const probeMeta = aiChannelOptionProbeMeta(channelId); + if (probeMeta) { + opt.dataset.probeStatus = probeMeta.status; + opt.dataset.probeMessage = probeMeta.message; + } else { + delete opt.dataset.probeStatus; + delete opt.dataset.probeMessage; + } + select.value = channelId; + select.selectedIndex = opt.index; + } + if (typeof syncSettingsCustomSelect === 'function') { + syncSettingsCustomSelect(select); + } +} + +function syncSelectedAIChannelUI() { + updateAIChannelSelectOption(selectedAIChannelId); + updateAIChannelEditorChrome(selectedAIChannelId); + renderAIChannelList(); +} + +function syncAIChannelEditorPreview() { + if (!currentConfig?.ai?.channels || !selectedAIChannelId || !currentConfig.ai.channels[selectedAIChannelId]) return; + const id = normalizeAIChannelId(selectedAIChannelId); + const next = readAIChannelFromMainForm(id); + currentConfig.ai.channels[id] = next; + syncSelectedAIChannelUI(); +} + +function bindAIChannelEditorPreviewSync() { + const ids = [ + 'ai-channel-name', + 'openai-provider', + 'openai-base-url', + 'openai-model' + ]; + ids.forEach((fieldId) => { + const el = document.getElementById(fieldId); + if (!el || el.dataset.aiChannelPreviewBound === '1') return; + el.dataset.aiChannelPreviewBound = '1'; + const eventName = el.tagName === 'SELECT' ? 'change' : 'input'; + el.addEventListener(eventName, syncAIChannelEditorPreview); + }); } function renderAIChannelSelect() { @@ -2570,16 +2667,24 @@ function renderAIChannelSelect() { const ch = currentConfig.ai.channels[id] || {}; const opt = document.createElement('option'); opt.value = id; - const marker = id === currentConfig.ai.default_channel ? ' *' : ''; - opt.textContent = `${ch.name || id}${marker} · ${ch.model || '-'}`; + opt.textContent = aiChannelSelectLabel(id, ch); + const probeMeta = aiChannelOptionProbeMeta(id); + if (probeMeta) { + opt.dataset.probeStatus = probeMeta.status; + opt.dataset.probeMessage = probeMeta.message; + } select.appendChild(opt); }); selectedAIChannelId = selectedAIChannelId && currentConfig.ai.channels[selectedAIChannelId] ? selectedAIChannelId : currentConfig.ai.default_channel; select.value = selectedAIChannelId; - renderAIChannelList(ids); + updateAIChannelSelectOption(selectedAIChannelId); + if (typeof syncSettingsCustomSelect === 'function') { + syncSettingsCustomSelect(select); + } updateAIChannelEditorChrome(selectedAIChannelId); + renderAIChannelList(ids); const countLabel = typeof window.t === 'function' ? window.t('settingsBasic.aiChannelCount').replace('{count}', String(ids.length)) : `已保存 ${ids.length} 个通道`; @@ -2621,7 +2726,7 @@ function renderAIChannelList(ids) { checkbox.type = 'checkbox'; checkbox.className = 'ai-channel-bulk-check'; checkbox.checked = selectedAIChannelBulkIds.has(id); - checkbox.setAttribute('aria-label', `选择 ${ch.name || id}`); + checkbox.setAttribute('aria-label', settingsT('settingsBasic.aiChannelSelectAria', '选择 {name}').replace('{name}', displayAIChannelName(id, ch))); checkbox.onclick = (event) => { event.stopPropagation(); if (checkbox.checked) { @@ -2631,6 +2736,7 @@ function renderAIChannelList(ids) { } item.classList.toggle('checked', checkbox.checked); }; + const displayName = displayAIChannelName(id, ch); const defaultBadge = isDefault ? `${escapeAIChannelHtml(settingsT('settingsBasic.aiChannelDefaultBadge', '默认'))}` : ''; let statusText = isComplete ? settingsT('settingsBasic.aiChannelReady', '可用') @@ -2645,7 +2751,7 @@ function renderAIChannelList(ids) { body.innerHTML = `
已保存的通道会显示在左侧;保存后写入 ai.channels,默认通道用于新对话和未指定通道的任务。
+通过下拉切换已保存通道;保存后写入 ai.channels,默认通道用于新对话和未指定通道的任务。
+