Преглед изворни кода

fix: 修复模型选择器不显示的问题,savedModels为空时回退到model字段,放宽内置预设enabled判断

Mochocyang пре 2 месеци
родитељ
комит
2b687eb526

+ 6 - 3
src/components/settings/sections/maintenance-section.tsx

@@ -20,7 +20,7 @@ import { Button } from "@/components/ui/button"
 import { Label } from "@/components/ui/label"
 import { ChatModelSelector } from "@/components/chat/chat-model-selector"
 import { useWikiStore } from "@/stores/wiki-store"
-import { getFirstAvailableModelKey } from "@/lib/llm-model-keys"
+import { getFirstAvailableModelKey, getEffectiveSavedModels } from "@/lib/llm-model-keys"
 import { hasUsableLlm } from "@/lib/has-usable-llm"
 import { normalizePath } from "@/lib/path-utils"
 import { resolveDefaultModel, resolveModelConfig } from "@/lib/novel/model-resolver"
@@ -240,9 +240,12 @@ export function MaintenanceSection() {
       if (key.startsWith("custom-")) {
         if (config.enabled === false) continue
       } else {
-        if (config.enabled !== true) continue
+        // 内置预设:已启用,或有有效配置(apiKey + model/savedModels)
+        const hasConfig = config.enabled === true
+          || Boolean((config.apiKey || config.savedModels?.length) && (config.model || config.savedModels?.length))
+        if (!hasConfig) continue
       }
-      if (config.savedModels && config.savedModels.length > 0) {
+      if (getEffectiveSavedModels(config).length > 0) {
         return true
       }
     }

+ 6 - 2
src/components/sources/outline-chat-panel.tsx

@@ -115,6 +115,7 @@ import {
   resolveNovelModel,
   resolveUsableModelKey,
 } from "@/lib/novel/model-resolver";
+import { getEffectiveSavedModels } from "@/lib/llm-model-keys";
 import { ChatModelSelector } from "@/components/chat/chat-model-selector";
 import { useStreamingText } from "@/hooks/use-streaming-text";
 import { highlightCode } from "@/lib/streaming-code-highlight";
@@ -1207,9 +1208,12 @@ export function OutlineChatPanel({ onClose }: { onClose: () => void }) {
       if (key.startsWith("custom-")) {
         if (config.enabled === false) continue;
       } else {
-        if (config.enabled !== true) continue;
+        // 内置预设:已启用,或有有效配置(apiKey + model/savedModels)
+        const hasConfig = config.enabled === true
+          || Boolean((config.apiKey || config.savedModels?.length) && (config.model || config.savedModels?.length));
+        if (!hasConfig) continue;
       }
-      if (config.savedModels && config.savedModels.length > 0) {
+      if (getEffectiveSavedModels(config).length > 0) {
         return true;
       }
     }

+ 31 - 8
src/lib/llm-model-keys.ts

@@ -1,9 +1,12 @@
-import type { ProviderConfigs, ProviderOverride } from "@/stores/wiki-store"
+import type { ProviderConfigs, ProviderOverride, SavedModel } from "@/stores/wiki-store"
 
 function isProviderAvailable(providerId: string, config: ProviderOverride): boolean {
-  return providerId.startsWith("custom-")
-    ? config.enabled !== false
-    : config.enabled === true
+  if (providerId.startsWith("custom-")) {
+    return config.enabled !== false
+  }
+  // 内置预设:已启用,或有有效配置(apiKey + model/savedModels)
+  return config.enabled === true
+    || Boolean((config.apiKey || config.savedModels?.length) && (config.model || config.savedModels?.length))
 }
 
 function getAvailableProviderEntries(
@@ -18,14 +21,34 @@ function getAvailableProviderEntries(
   ]
 }
 
+/**
+ * 获取 provider 的有效模型列表。
+ * 优先使用 savedModels;为空时回退到 model 字段(兼容旧数据/未拉取模型的场景)。
+ */
+export function getEffectiveSavedModels(config: ProviderOverride): SavedModel[] {
+  if (config.savedModels && config.savedModels.length > 0) {
+    return config.savedModels
+  }
+  const model = config.model?.trim()
+  if (model) {
+    return [{
+      id: `fallback-${model}`,
+      name: model,
+      model,
+      createdAt: 0,
+    }]
+  }
+  return []
+}
+
 export function hasAvailableModels(providerConfigs: ProviderConfigs): boolean {
   return getAvailableProviderEntries(providerConfigs)
-    .some(([, config]) => (config.savedModels?.length ?? 0) > 0)
+    .some(([, config]) => getEffectiveSavedModels(config).length > 0)
 }
 
 export function getFirstAvailableModelKey(providerConfigs: ProviderConfigs): string {
   for (const [providerId, config] of getAvailableProviderEntries(providerConfigs)) {
-    const first = config.savedModels?.[0]
+    const first = getEffectiveSavedModels(config)[0]
     if (first) return `${providerId}/${first.model}`
   }
   return ""
@@ -48,7 +71,7 @@ export function getStableAvailableModelKey(
       const config = providerConfigs[providerId]
       if (
         isProviderAvailable(providerId, config)
-        && config.savedModels?.some((model) => model.model === modelId)
+        && getEffectiveSavedModels(config).some((model) => model.model === modelId)
       ) {
         return `${providerId}/${modelId}`
       }
@@ -57,7 +80,7 @@ export function getStableAvailableModelKey(
 
   for (const [providerId, config] of getAvailableProviderEntries(providerConfigs)) {
     if (providerId === exactProviderId) continue
-    if (config.savedModels?.some((model) => model.model === trimmed)) {
+    if (getEffectiveSavedModels(config).some((model) => model.model === trimmed)) {
       return `${providerId}/${trimmed}`
     }
   }

+ 6 - 6
src/lib/novel/model-resolver.ts

@@ -2,7 +2,7 @@ import { useWikiStore, type LlmConfig, type NovelConfig, type ProviderOverride }
 import { LLM_PRESETS } from "@/components/settings/llm-presets"
 import { resolveConfig } from "@/components/settings/preset-resolver"
 import { hasUsableLlm } from "@/lib/has-usable-llm"
-import { getStableAvailableModelKey } from "@/lib/llm-model-keys"
+import { getStableAvailableModelKey, getEffectiveSavedModels } from "@/lib/llm-model-keys"
 
 export type NovelTaskType = "writing" | "review" | "summary" | "extract" | "lint" | "deAi"
 
@@ -31,11 +31,11 @@ export function isModelKeyRegistered(
   if (slashIdx > 0) {
     const providerId = trimmed.slice(0, slashIdx)
     const modelId = trimmed.slice(slashIdx + 1)
-    return !!providerConfigs[providerId]?.savedModels?.some((m) => m.model === modelId)
+    return !!getEffectiveSavedModels(providerConfigs[providerId] ?? {}).some((m) => m.model === modelId)
   }
 
   for (const override of Object.values(providerConfigs)) {
-    if (override.savedModels?.some((m) => m.model === trimmed)) {
+    if (getEffectiveSavedModels(override).some((m) => m.model === trimmed)) {
       return true
     }
   }
@@ -66,7 +66,7 @@ export function resolveModelConfig(
     const providerId = targetModel.slice(0, slashIdx)
     const modelId = targetModel.slice(slashIdx + 1)
     const override = providerConfigs[providerId]
-    if (override?.savedModels?.some((m) => m.model === modelId)) {
+    if (override && getEffectiveSavedModels(override).some((m) => m.model === modelId)) {
       const template = LLM_PRESETS.find((p) => p.id === providerId) ?? LLM_PRESETS.find((p) => p.id === "custom")
       if (template) {
         return { ...resolveConfig(template, override, baseConfig), model: modelId }
@@ -76,7 +76,7 @@ export function resolveModelConfig(
   }
   // 回退:按纯模型名匹配(兼容旧数据)
   for (const [providerId, override] of Object.entries(providerConfigs)) {
-    if (override.savedModels?.some((m) => m.model === targetModel)) {
+    if (getEffectiveSavedModels(override).some((m) => m.model === targetModel)) {
       const template = LLM_PRESETS.find((p) => p.id === providerId) ?? LLM_PRESETS.find((p) => p.id === "custom")
       if (template) {
         return { ...resolveConfig(template, override, baseConfig), model: targetModel }
@@ -187,7 +187,7 @@ export function formatResolvedModelLabel(
   if (!model) return "未知模型"
 
   for (const override of Object.values(providerConfigs)) {
-    const found = override.savedModels?.find((saved) => saved.model === model)
+    const found = getEffectiveSavedModels(override).find((saved) => saved.model === model)
     if (found?.name?.trim()) return found.name.trim()
   }