mirror of
https://github.com/dataelement/bisheng.git
synced 2026-09-01 15:32:50 +08:00
feat(model): 日常模式默认模型可独立配置-通用需求
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user