feat(model): 日常模式默认模型可独立配置-通用需求

This commit is contained in:
highway
2026-08-25 10:23:55 +08:00
parent 755e67058b
commit 9f2188d7bc
22 changed files with 200 additions and 15 deletions
@@ -25,3 +25,8 @@ class ServerAddError(BaseErrorCode):
class WorkbenchEmbeddingError(BaseErrorCode):
Code: int = 10810
Msg: str = 'Please configure the workbench embedding model.'
class WorkbenchChatDefaultModelError(BaseErrorCode):
Code: int = 10811
Msg: str = 'The daily-chat default model must be one of the configured workbench chat models.'
@@ -86,6 +86,12 @@ class WorkbenchModelConfig(BaseModel):
default=None,
description="Linsight default execution model id (single-select from models)",
)
# Daily-chat (日常模式) default model — sibling of linsight_default_model_id,
# must be one of the configured ``models`` entries.
chat_default_model_id: str | None = Field(
default=None,
description="Daily-chat default model id (single-select from models)",
)
# RetrieveembeddingModels
embedding_model: WSModel | None = Field(default=None, description="embeddingModels")
# Speech-to-text model
@@ -19,6 +19,7 @@ from bisheng.common.errcode.llm import (
ServerAddAllError,
ServerAddError,
ServerExistError,
WorkbenchChatDefaultModelError,
WorkbenchEmbeddingError,
)
from bisheng.common.errcode.llm_tenant import (
@@ -115,6 +116,7 @@ def _workbench_model_ref_values(config: WorkbenchModelConfig) -> list[Any]:
return [
*(one.id for one in (config.models or [])),
config.linsight_default_model_id,
config.chat_default_model_id,
getattr(config.embedding_model, "id", None),
getattr(config.asr_model, "id", None),
getattr(config.tts_model, "id", None),
@@ -253,6 +255,7 @@ class LLMService:
[
*(one.id for one in (workbench_cfg.models or [])),
workbench_cfg.linsight_default_model_id,
workbench_cfg.chat_default_model_id,
getattr(workbench_cfg.embedding_model, "id", None),
getattr(workbench_cfg.asr_model, "id", None),
getattr(workbench_cfg.tts_model, "id", None),
@@ -373,6 +376,8 @@ class LLMService:
config.models = [one for one in config.models if is_allowed(one.id)]
if not is_allowed(config.linsight_default_model_id):
config.linsight_default_model_id = None
if not is_allowed(config.chat_default_model_id):
config.chat_default_model_id = None
for field_name in ("embedding_model", "asr_model", "tts_model", "chat_title_llm"):
ws_model = getattr(config, field_name)
if ws_model is not None and not is_allowed(ws_model.id):
@@ -1361,6 +1366,7 @@ class LLMService:
# linsight_default_model_id is already a model-id string (F035),
# not a WSModel; pass it directly alongside the WSModel ids.
config_obj.linsight_default_model_id,
config_obj.chat_default_model_id,
*(
(ws.id if ws else None)
for ws in (
@@ -1391,6 +1397,18 @@ class LLMService:
if config_obj.models is None:
config_obj.models = config_old_obj.models
# chat_default_model_id must come from the configured (and, per the
# avalidate call above, accessible) daily-chat model list.
if config_obj.chat_default_model_id is not None:
chat_default_id = _coerce_model_id(config_obj.chat_default_model_id)
selectable_ids = {
model_id
for model_id in (_coerce_model_id(one.id) for one in (config_obj.models or []))
if model_id is not None
}
if chat_default_id is None or chat_default_id not in selectable_ids:
raise WorkbenchChatDefaultModelError()
if config_obj.embedding_model:
# Determine consistency
if (
@@ -370,6 +370,7 @@ async def test_workbench_getter_sanitizes_inherited_stale_model_refs():
WSModel(id="30", name="deleted-model").model_dump(),
],
"linsight_default_model_id": "20",
"chat_default_model_id": "30",
"embedding_model": WSModel(id="10", name="root-embedding").model_dump(),
"asr_model": WSModel(id="30", name="deleted-asr").model_dump(),
"tts_model": WSModel(id="20", name="child-tts").model_dump(),
@@ -390,6 +391,7 @@ async def test_workbench_getter_sanitizes_inherited_stale_model_refs():
assert blocked is False
assert [one.id for one in (config.models or [])] == ["10"]
assert config.linsight_default_model_id is None
assert config.chat_default_model_id is None
assert config.embedding_model and config.embedding_model.id == "10"
assert config.asr_model is None
assert config.tts_model is None
@@ -17,7 +17,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from bisheng.common.errcode.llm import WorkbenchEmbeddingError
from bisheng.common.errcode.llm import WorkbenchChatDefaultModelError, WorkbenchEmbeddingError
from bisheng.llm.domain.schemas import WorkbenchModelConfig, WSModel
from bisheng.llm.domain.services.llm import LLMService
@@ -93,3 +93,93 @@ async def test_invalid_embedding_model_id_returns_business_error(invalid_model_i
assert exc_info.value.code == 10810
mock_validate.assert_not_awaited()
@pytest.mark.asyncio
async def test_chat_default_model_id_persists_alongside_linsight_default():
"""chat_default_model_id is a sibling of linsight_default_model_id — both
land in the same stored JSON without overwriting each other."""
incoming = WorkbenchModelConfig(
models=[WSModel(id="840", name="qwen3.7-max"), WSModel(id="841", name="deepseek-v4")],
linsight_default_model_id="840",
chat_default_model_id="841",
embedding_model=None,
)
persisted = await _run_and_capture(incoming)
assert persisted["linsight_default_model_id"] == "840"
assert persisted["chat_default_model_id"] == "841"
@pytest.mark.asyncio
async def test_chat_default_model_id_none_is_allowed():
"""No daily-chat default configured is a valid state."""
incoming = WorkbenchModelConfig(
models=[WSModel(id="840", name="qwen3.7-max")],
chat_default_model_id=None,
embedding_model=None,
)
persisted = await _run_and_capture(incoming)
assert persisted["chat_default_model_id"] is None
@pytest.mark.asyncio
async def test_chat_default_model_id_must_come_from_models_list():
"""A default outside the configured workbench chat model list is rejected."""
incoming = WorkbenchModelConfig(
models=[WSModel(id="840", name="qwen3.7-max")],
chat_default_model_id="999",
embedding_model=None,
)
with patch(
"bisheng.llm.domain.services.llm.avalidate_system_model_refs",
new=AsyncMock(),
), patch(
"bisheng.llm.domain.services.llm.TenantSystemModelConfigDao.aresolve",
new=AsyncMock(return_value=(json.dumps(_OLD.model_dump()), False, False)),
):
with pytest.raises(WorkbenchChatDefaultModelError) as exc_info:
await LLMService.update_workbench_llm(1, incoming, MagicMock(), tenant_id=1)
assert exc_info.value.code == 10811
@pytest.mark.parametrize("invalid_model_id", ["", "null", "undefined", "0", "-1", "abc"])
@pytest.mark.asyncio
async def test_chat_default_model_id_rejects_non_model_values(invalid_model_id: str):
incoming = WorkbenchModelConfig(
models=[WSModel(id="840", name="qwen3.7-max")],
chat_default_model_id=invalid_model_id,
embedding_model=None,
)
with patch(
"bisheng.llm.domain.services.llm.avalidate_system_model_refs",
new=AsyncMock(),
), patch(
"bisheng.llm.domain.services.llm.TenantSystemModelConfigDao.aresolve",
new=AsyncMock(return_value=(json.dumps(_OLD.model_dump()), False, False)),
):
with pytest.raises(WorkbenchChatDefaultModelError):
await LLMService.update_workbench_llm(1, incoming, MagicMock(), tenant_id=1)
@pytest.mark.asyncio
async def test_chat_default_model_id_validated_against_merged_old_models():
"""When the body omits ``models`` (merge-guard keeps the stored list), the
default is checked against that effective list — old id passes, new id not
in the stored list fails."""
# "727" exists only in the stored (_OLD) list.
incoming = WorkbenchModelConfig(chat_default_model_id="727", embedding_model=None)
persisted = await _run_and_capture(incoming)
assert persisted["chat_default_model_id"] == "727"
assert [m["id"] for m in persisted["models"]] == ["727", "774"]
incoming_bad = WorkbenchModelConfig(chat_default_model_id="840", embedding_model=None)
with patch(
"bisheng.llm.domain.services.llm.avalidate_system_model_refs",
new=AsyncMock(),
), patch(
"bisheng.llm.domain.services.llm.TenantSystemModelConfigDao.aresolve",
new=AsyncMock(return_value=(json.dumps(_OLD.model_dump()), False, False)),
):
with pytest.raises(WorkbenchChatDefaultModelError):
await LLMService.update_workbench_llm(1, incoming_bad, MagicMock(), tenant_id=1)
+1 -1
View File
@@ -3054,7 +3054,7 @@
},
"src/pages/appChat/useChatHelpers.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 12
"count": 11
},
"@typescript-eslint/no-unused-vars": {
"count": 2
+2
View File
@@ -98,6 +98,8 @@ export function getSelectableSkills(): Promise<SelectableSkill[]> {
export interface LinsightModelConfig {
models?: { id: string | number; name?: string; displayName?: string }[];
linsight_default_model_id?: string | null;
/** Admin-configured default model for new daily-mode conversations. */
chat_default_model_id?: string | null;
}
export function getLinsightModelConfig(): Promise<LinsightModelConfig> {
@@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react';
import { useRecoilState } from 'recoil';
import { useGetWorkbenchModelsQuery } from '~/hooks/queries/queries';
import store from '~/store';
interface UserLike {
@@ -18,11 +19,13 @@ interface BsConfigLike {
/**
* Hydrate / persist the chatModel atom under the user-scoped
* `bs:{uid}:chatModel` localStorage key. Falls back to the latest configured
* model when nothing is saved or the saved id no longer exists. Used by every
* chat surface that lets the user pick a model so the selection survives
* page refresh and new tabs (and gets wiped on re-login alongside the rest
* of `bs:*`).
* `bs:{uid}:chatModel` localStorage key. Resolution order for a fresh
* session: the user's last valid manual selection → the admin-configured
* daily-mode default (`chat_default_model_id` from /api/v1/llm/workbench) →
* the last configured model (compat for backends that predate the field).
* Used by every chat surface that lets the user pick a model so the selection
* survives page refresh and new tabs (and gets wiped on re-login alongside
* the rest of `bs:*`).
*/
export default function useChatModelMemo(
user: UserLike | null | undefined,
@@ -30,6 +33,7 @@ export default function useChatModelMemo(
) {
const [chatModel, setChatModel] = useRecoilState(store.chatModel);
const hydratedRef = useRef(false);
const { data: workbenchConfig, isLoading: workbenchLoading } = useGetWorkbenchModelsQuery();
useEffect(() => {
if (!bsConfig || !user?.id) return;
@@ -40,7 +44,18 @@ export default function useChatModelMemo(
let target: ModelLike | undefined | null = savedModelId
? models.find((m) => String(m.id) === savedModelId)
: null;
if (!target && models.length) target = models[models.length - 1];
if (!target) {
// No valid manual selection — the admin default decides. Wait for the
// workbench config before settling so the default isn't skipped.
if (workbenchLoading) return;
const rawDefault = (workbenchConfig as Record<string, unknown> | undefined)?.chat_default_model_id;
const adminDefaultId =
typeof rawDefault === 'string' || typeof rawDefault === 'number' ? String(rawDefault) : null;
if (adminDefaultId) {
target = models.find((m) => String(m.id) === adminDefaultId) ?? null;
}
if (!target && models.length) target = models[models.length - 1];
}
if (target) {
setChatModel({
id: Number(target.id),
@@ -49,7 +64,7 @@ export default function useChatModelMemo(
}
} catch { /* ignore */ }
hydratedRef.current = true;
}, [bsConfig, user?.id, setChatModel]);
}, [bsConfig, user?.id, setChatModel, workbenchConfig, workbenchLoading]);
useEffect(() => {
if (!hydratedRef.current || !user?.id || !chatModel.id) return;
@@ -103,6 +103,7 @@
"10802": "Failed to add provider. All models failed to initialize",
"10803": "Failed to add provider. Some models failed to initialize",
"10810": "Please configure the workbench embedding model.",
"10811": "The daily mode default model must be one of the configured workbench chat models. Please select again.",
"10900": "Knowledge base name already exists",
"10901": "Embedding model required",
"10902": "Summary model invalid. Reconfigure in system model settings: {{exception}}",
@@ -103,6 +103,7 @@
"10802": "提供元追加に失敗(全モデルの初期化失敗)",
"10803": "提供元追加に失敗(一部モデルの初期化失敗)",
"10810": "ワークベンチの embedding モデルを設定してください。",
"10811": "日常モードのデフォルトモデルは、設定済みのワークベンチチャットモデルから選択してください。",
"10900": "ナレッジベース名が重複しています",
"10901": "Embeddingモデルを選択してください",
"10902": "要約モデルが無効です:{{exception}}",
@@ -103,6 +103,7 @@
"10802": "添加服务提供方失败,模型全部初始化失败",
"10803": "添加服务提供方失败,部分模型初始化失败",
"10810": "请配置工作台向量模型。",
"10811": "日常模式默认模型必须是已配置的工作台对话模型,请重新选择。",
"10900": "知识库名称重复",
"10901": "知识库必须选择一个embedding模型",
"10902": "文档知识库总结模型已失效,请前往模型管理-系统模型设置中进行配置。{{exception}}",
@@ -103,6 +103,7 @@
"10802": "Failed to add provider. All models failed to initialize",
"10803": "Failed to add provider. Some models failed to initialize",
"10810": "Please configure the workbench embedding model.",
"10811": "The daily mode default model must be one of the configured workbench chat models. Please select again.",
"10900": "Knowledge base name already exists",
"10901": "Embedding model required",
"10902": "Summary model invalid. Reconfigure in system model settings: {{exception}}",
@@ -103,6 +103,7 @@
"10802": "提供元追加に失敗(全モデルの初期化失敗)",
"10803": "提供元追加に失敗(一部モデルの初期化失敗)",
"10810": "ワークベンチの embedding モデルを設定してください。",
"10811": "日常モードのデフォルトモデルは、設定済みのワークベンチチャットモデルから選択してください。",
"10900": "ナレッジベース名が重複しています",
"10901": "Embeddingモデルを選択してください",
"10902": "要約モデルが無効です:{{exception}}",
@@ -103,6 +103,7 @@
"10802": "添加服务提供方失败,模型全部初始化失败",
"10803": "添加服务提供方失败,部分模型初始化失败",
"10810": "请配置工作台向量模型。",
"10811": "日常模式默认模型必须是已配置的工作台对话模型,请重新选择。",
"10900": "知识库名称重复",
"10901": "知识库必须选择一个embedding模型",
"10902": "文档知识库总结模型已失效,请前往模型管理-系统模型设置中进行配置。{{exception}}",
@@ -103,6 +103,7 @@
"10802": "Failed to add provider. All models failed to initialize",
"10803": "Failed to add provider. Some models failed to initialize",
"10810": "Please configure the workbench embedding model.",
"10811": "The daily mode default model must be one of the configured workbench chat models. Please select again.",
"10900": "Knowledge base name already exists",
"10901": "Embedding model required",
"10902": "Summary model invalid. Reconfigure in system model settings: {{exception}}",
@@ -10,6 +10,7 @@
"workVectorModel": "Workbench Vector Model",
"workVectorModelTooltip": "Used for $t(bs:linsight) task execution scenarios",
"linsightDefaultModel": "$t(bs:linsight) Default Model",
"dailyDefaultModel": "Daily Mode Default Model",
"linsightDefaultModelTooltip": "This model is the default executor for $t(bs:linsight) tasks; users can switch to another model when starting a task",
"workbenchVoiceModel": "Workbench Voice Model",
"sessionTitleGenerationModel": "Session Title Generation Model",
@@ -103,6 +103,7 @@
"10802": "提供元追加に失敗(全モデルの初期化失敗)",
"10803": "提供元追加に失敗(一部モデルの初期化失敗)",
"10810": "ワークベンチの embedding モデルを設定してください。",
"10811": "日常モードのデフォルトモデルは、設定済みのワークベンチチャットモデルから選択してください。",
"10900": "ナレッジベース名が重複しています",
"10901": "Embeddingモデルを選択してください",
"10902": "要約モデルが無効です:{{exception}}",
@@ -8,6 +8,7 @@
"confirmCancelEdit": "変更はまだ保存されていません。本当に終了しますか?",
"promptSaved": "プロンプトが保存されました",
"linsightDefaultModel": "$t(bs:linsight)デフォルトモデル",
"dailyDefaultModel": "日常モードのデフォルトモデル",
"linsightDefaultModelTooltip": "このモデルは$t(bs:linsight)タスクのデフォルト実行モデルです。ユーザーはタスク開始時に切り替えできます",
"workbenchVoiceModel": "ワークベンチ音声モデル",
"sessionTitleGenerationModel": "セッションタイトル生成モデル",
@@ -103,6 +103,7 @@
"10802": "添加服务提供方失败,模型全部初始化失败",
"10803": "添加服务提供方失败,部分模型初始化失败",
"10810": "请配置工作台向量模型。",
"10811": "日常模式默认模型必须是已配置的工作台对话模型,请重新选择。",
"10900": "知识库名称重复",
"10901": "知识库必须选择一个embedding模型",
"10902": "文档知识库总结模型已失效,请前往模型管理-系统模型设置中进行配置。{{exception}}",
@@ -8,6 +8,7 @@
"confirmCancelEdit": "您的修改尚未保存,确定要退出吗?",
"promptSaved": "提示词已保存",
"linsightDefaultModel": "$t(bs:linsight)默认模型",
"dailyDefaultModel": "日常模式默认模型",
"linsightDefaultModelTooltip": "该模型为$t(bs:linsight)任务的默认执行模型,用户发起任务时可切换",
"workbenchVoiceModel": "工作台及知识空间语音模型",
"sessionTitleGenerationModel": "应用会话标题生成模型",
@@ -36,12 +36,15 @@ interface ModelManagementProps {
onNameChange: (index: number, name: string) => void;
onDescriptionChange?: (index: number, description: string) => void;
onVisualToggle?: (index: number, enabled: boolean) => void;
/** Daily-mode default model: used for new daily conversations when the user has no valid manual selection. */
chatDefaultModelId?: string | null;
onChatDefaultChange?: (id: string) => void;
/** Linsight default model: the model id used as the default executor for Linsight tasks. */
linsightDefaultModelId?: string | null;
onLinsightDefaultChange?: (id: string) => void;
}
export const ModelManagement = forwardRef<HTMLDivElement[], ModelManagementProps>(
({ models, errors, error, onAdd, onRemove, onModelChange, onNameChange, onDescriptionChange, onVisualToggle, linsightDefaultModelId, onLinsightDefaultChange }, ref) => {
({ models, errors, error, onAdd, onRemove, onModelChange, onNameChange, onDescriptionChange, onVisualToggle, chatDefaultModelId, onChatDefaultChange, linsightDefaultModelId, onLinsightDefaultChange }, ref) => {
// `assistant` mode hits /api/v1/llm/assistant/llm_list which is already
// filtered to the admin-configured assistant allowlist (default model
// and its server are placed first). Avoids the fetch-all + client-side
@@ -90,8 +93,9 @@ export const ModelManagement = forwardRef<HTMLDivElement[], ModelManagementProps
// }, [models, llmOptions])
return (
<div className="mt-2 border p-4 rounded-md bg-background">
<div className="grid mb-4 items-center" style={{ gridTemplateColumns: "1.2fr 0.85fr 1.3fr 72px 116px 36px" }}>
<div className="mt-2 border p-4 rounded-md bg-background overflow-x-auto">
<div className="min-w-[780px]">
<div className="grid mb-4 items-center" style={{ gridTemplateColumns: "1.2fr 0.85fr 1.3fr 72px 116px 116px 36px" }}>
<div className="">
<Label className="bisheng-label">{t('bench.model')}</Label>
</div>
@@ -105,6 +109,9 @@ export const ModelManagement = forwardRef<HTMLDivElement[], ModelManagementProps
<Label className="bisheng-label whitespace-nowrap mr-0.5">{t('bench.vision')}</Label>
<QuestionTooltip className="text-[#999999]" content={t('bench.visionText')} />
</div>
<div className="flex items-center justify-center">
<Label className="bisheng-label whitespace-nowrap">{t('model:model.dailyDefaultModel')}</Label>
</div>
<div className="flex items-center justify-center">
<Label className="bisheng-label whitespace-nowrap mr-0.5">{t('model:model.linsightDefaultModel')}</Label>
<QuestionTooltip className="text-[#999999]" content={t('model:model.linsightDefaultModelTooltip')} />
@@ -118,7 +125,7 @@ export const ModelManagement = forwardRef<HTMLDivElement[], ModelManagementProps
key={model.key}
ref={(el) => setItemRef(el, index)}
className="grid items-center mb-4"
style={{ gridTemplateColumns: "1.2fr 0.85fr 1.3fr 72px 116px 36px" }}
style={{ gridTemplateColumns: "1.2fr 0.85fr 1.3fr 72px 116px 116px 36px" }}
>
<div className="pr-2" id={model.id}>
{assistantLlmOptions.length > 0 ? (
@@ -170,6 +177,18 @@ export const ModelManagement = forwardRef<HTMLDivElement[], ModelManagementProps
/>
</div>
{/* Daily-mode default model: single-select radio across the whole column */}
<div className="flex items-center justify-center">
<input
type="radio"
name="chat-default-model"
className="size-4 cursor-pointer accent-primary disabled:cursor-not-allowed"
checked={!!model.id && String(model.id) === String(chatDefaultModelId ?? '')}
disabled={!model.id}
onChange={() => model.id && onChatDefaultChange?.(model.id)}
/>
</div>
{/* Linsight default model: single-select radio across the whole column */}
<div className="flex items-center justify-center">
<input
@@ -192,6 +211,7 @@ export const ModelManagement = forwardRef<HTMLDivElement[], ModelManagementProps
</div>
))}
</div>
<Button
variant="outline"
className="border-none size-7 bg-gray-200 hover:bg-gray-300 transition-colors mt-2"
@@ -64,6 +64,7 @@ export default function WorkbenchModel({ onBack }) {
const [form, setForm] = useState<any>({
sourceModelId: null,
linsightDefaultModelId: null,
chatDefaultModelId: null,
asrModelId: null,
ttsModelId: null,
knowledgeSpaceLlmId: null,
@@ -90,7 +91,7 @@ export default function WorkbenchModel({ onBack }) {
};
const submitConfig = async () => {
const { linsightDefaultModelId, sourceModelId, asrModelId, ttsModelId, chatTitleLlmId, models } = form;
const { linsightDefaultModelId, chatDefaultModelId, sourceModelId, asrModelId, ttsModelId, chatTitleLlmId, models } = form;
setSaveLoad(true);
try {
const data = {
@@ -100,6 +101,8 @@ export default function WorkbenchModel({ onBack }) {
embedding_model: { id: String(sourceModelId) },
// Linsight default executor model: one of the workbench chat models' id.
linsight_default_model_id: linsightDefaultModelId ? String(linsightDefaultModelId) : null,
// Daily-mode default model: independent sibling of the Linsight default.
chat_default_model_id: chatDefaultModelId ? String(chatDefaultModelId) : null,
asr_model: asrModelId ? { id: String(asrModelId) } : null, // 支持空值
tts_model: ttsModelId ? { id: String(ttsModelId) } : null, // 支持空值
// “应用会话标题生成模型”
@@ -115,6 +118,7 @@ export default function WorkbenchModel({ onBack }) {
setForm({
sourceModelId: newConfig?.embedding_model?.id || null,
linsightDefaultModelId: newConfig?.linsight_default_model_id || null,
chatDefaultModelId: newConfig?.chat_default_model_id || null,
asrModelId: newConfig?.asr_model?.id || null,
ttsModelId: newConfig?.tts_model?.id || null,
chatTitleLlmId: newConfig?.chat_title_llm?.id || null,
@@ -125,6 +129,7 @@ export default function WorkbenchModel({ onBack }) {
lastSaveFormDataRef.current = {
embedding_model: { id: newConfig?.embedding_model?.id },
linsight_default_model_id: newConfig?.linsight_default_model_id || null,
chat_default_model_id: newConfig?.chat_default_model_id || null,
abstract_prompt: newConfig?.abstract_prompt || defalutPrompt,
asr_model: { id: newConfig?.asr_model?.id },
tts_model: { id: newConfig?.tts_model?.id },
@@ -197,6 +202,7 @@ export default function WorkbenchModel({ onBack }) {
setForm({
sourceModelId: linsightConfig.embedding_model?.id || null,
linsightDefaultModelId: linsightConfig.linsight_default_model_id || null,
chatDefaultModelId: linsightConfig.chat_default_model_id || null,
asrModelId: linsightConfig.asr_model?.id || null,
ttsModelId: linsightConfig.tts_model?.id || null,
chatTitleLlmId: linsightConfig.chat_title_llm?.id || null,
@@ -207,6 +213,7 @@ export default function WorkbenchModel({ onBack }) {
lastSaveFormDataRef.current = {
embedding_model: { id: linsightConfig.embedding_model?.id },
linsight_default_model_id: linsightConfig.linsight_default_model_id || null,
chat_default_model_id: linsightConfig.chat_default_model_id || null,
abstract_prompt: linsightConfig.abstract_prompt || defalutPrompt,
asr_model: { id: linsightConfig.asr_model?.id },
tts_model: { id: linsightConfig.tts_model?.id },
@@ -227,7 +234,7 @@ export default function WorkbenchModel({ onBack }) {
const inheritedFromRoot = !!linsightConfig?.inherited_from_root;
const fallbackBlocked = !!linsightConfig?.fallback_blocked;
return (
<div className="max-w-[720px] mx-auto gap-y-4 flex flex-col mt-16 relative">
<div className="max-w-[880px] mx-auto gap-y-4 flex flex-col mt-16 relative">
<FallbackBlockedBanner visible={fallbackBlocked} />
{inheritedFromRoot && (
<div className="-mb-2 text-xs text-muted-foreground flex items-center">
@@ -243,6 +250,8 @@ export default function WorkbenchModel({ onBack }) {
error={''}
linsightDefaultModelId={form.linsightDefaultModelId}
onLinsightDefaultChange={(id) => setForm((prev) => ({ ...prev, linsightDefaultModelId: id }))}
chatDefaultModelId={form.chatDefaultModelId}
onChatDefaultChange={(id) => setForm((prev) => ({ ...prev, chatDefaultModelId: id }))}
onAdd={() => setForm((prev) => ({
...prev,
models: [...prev.models, { key: generateUUID(4), id: '', name: '', displayName: '', description: '', visual: false }],
@@ -253,10 +262,15 @@ export default function WorkbenchModel({ onBack }) {
const linsightDefaultModelId = removed?.id && removed.id === prev.linsightDefaultModelId
? null
: prev.linsightDefaultModelId;
// Same for the daily-mode default.
const chatDefaultModelId = removed?.id && String(removed.id) === String(prev.chatDefaultModelId ?? '')
? null
: prev.chatDefaultModelId;
return {
...prev,
models: prev.models.filter((_, i) => i !== index),
linsightDefaultModelId,
chatDefaultModelId,
};
})}
onModelChange={(index, id) => setForm((prev) => ({
@@ -340,6 +354,7 @@ export function useLinsightConfig() {
embedding_model: null,
abstract_prompt: defalutPrompt,
linsight_default_model_id: null,
chat_default_model_id: null,
asr_model: null,
tts_model: null,
knowledge_space_llm: null,