refactor!: 移除自定义模型支持

This commit is contained in:
Wenjie Zhang 2025-10-10 14:59:12 +08:00
parent b866ddfec2
commit a31f60b0a6
13 changed files with 83 additions and 320 deletions

1
.gitignore vendored
View File

@ -49,6 +49,7 @@ saves_dev
notebooks
graphrag
docker/volumes
docs/vibe
.cursorrules
/models

View File

@ -3,25 +3,8 @@
Yuxi-Know 是一个基于知识图谱和向量数据库的智能知识库系统。它通过 FastAPI 提供后端 API使用 Vue.js 构建前端界面,并利用 Docker Compose 进行整体服务的编排和管理。
文档中心在 `docs` 文件夹下面。
```
Yuxi-Know/
├── docker/ # Docker 配置文件
├── scripts/ # 脚本文件,如批量上传等
├── server/ # 服务端代码(部分)
├── src/ # 主要源代码目录
│ ├── agents/ # 智能体应用
│ ├── config/ # 配置文件
│ ├── knowledge/ # 知识库相关
│ ├── models/ # 数据模型
│ ├── plugins/ # 插件存放OCR
│ ├── static/ # 静态资源(配置文件)
│ └── utils/ # 工具函数
├── web/src/ # 前端代码
│ ├── apis/ # 前端的接口定义
│ └── views/ # 主要 vue 文件
└── docker-compose.yml # Docker Compose 配置
```
# 核心服务与容器 (Core Services & Containers)
@ -54,5 +37,6 @@ Yuxi-Know/
其他:
- 如果需要新建说明文档,则保存在 docs/vibe 文件夹下面
- 如果需要新建说明文档(仅开发者可见,非必要不创建),则保存在 `docs/vibe` 文件夹下面
- 测试脚本可以放在 test 文件夹下面,可以从 docker 中启动测试
- 代码更新后要检查文档部分是否有需要更新的地方,文档的目录定义在 `docs/.vitepress/config.mts`

View File

@ -141,7 +141,7 @@ export HTTPS_PROXY=http://IP:PORT
<details>
<summary>自定义模型供应商</summary>
在 [src/config/static/models.yaml](src/config/static/models.yaml) 中添加新的模型配置,然后在 `.env` 文件中添加对应的环境变量,最后重新部署项目即可。
在 [src/config/static/models.yaml](src/config/static/models.yaml) 中添加新的模型配置,然后在 `.env` 文件中添加对应的环境变量,最后重新部署项目即可。如需使用自定义配置文件,可设置环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 指向该文件。
**示例**
@ -150,8 +150,7 @@ custom-provider-name-here:
name: custom-provider-name-here
default: custom-model-name-here
base_url: "https://api.your-provider.com/v1"
env:
- CUSTOM_API_KEY_ENV_NAME # e.g. OPENAI_API_KEY
env: CUSTOM_API_KEY_ENV_NAME # e.g. OPENAI_API_KEY
models:
- supported-model-name-here
```
@ -159,7 +158,7 @@ custom-provider-name-here:
### 自定义嵌入模型和重排序模型
需要注意的是,从 v0.2 版本开始,项目将模型部署和项目本身做了完全解耦因此无论是 Embedding 还是 Reranker如果需要使用本地模型都需要使用 vllm /ollama 转换为 api 服务后,在 src/config/static/models.yaml 或者 src/config/static/models.private.yaml (优先)中 添加类似下面的信息:
需要注意的是,从 v0.2 版本开始,项目将模型部署和项目本身做了完全解耦因此无论是 Embedding 还是 Reranker如果需要使用本地模型都需要使用 vllm /ollama 转换为 API 服务后,在 [src/config/static/models.yaml](src/config/static/models.yaml)(或环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 指向的文件)中添加类似下面的信息:
```yaml
EMBED_MODEL_INFO:

View File

@ -22,7 +22,7 @@
- [x] 添加统计信息
- [ ] 支持 MinerU 2.5 的解析方法
- [ ] Options 中添加网络搜索和绘制图片的选项,分别是用来调用工具
- [ ] 移除自定义模型,同时将模型的 env 修改为单环境变量的形式,移除 models.yaml 的私有共有双配置模式,除非配置了环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 然后指向一个变量。
- [x] 移除自定义模型,同时将模型的 env 修改为单环境变量的形式,移除 models.yaml 的私有共有双配置模式,除非配置了环境变量 `OVERRIDE_DEFAULT_MODELS_CONFIG_WITH` 然后指向一个变量。
📝 **Base**

View File

@ -42,13 +42,17 @@
### 1. 编辑模型配置文件
**方式一:修改公共配置**
**方式一:修改默认配置**
编辑 `src/config/static/models.yaml` 文件
**方式二:创建私有配置(推荐)**
复制模板并创建私有配置
**方式二:使用覆盖配置**
创建自定义配置文件并通过环境变量指定
```bash
cp src/config/static/models.yaml src/config/static/models.private.yaml
# 创建自定义配置文件
cp src/config/static/models.yaml /path/to/your/custom-models.yaml
# 设置环境变量
export OVERRIDE_DEFAULT_MODELS_CONFIG_WITH=/path/to/your/custom-models.yaml
```
### 2. 添加模型配置
@ -60,20 +64,17 @@ custom-provider-name:
name: custom-provider-name
default: custom-model-name
base_url: "https://api.your-provider.com/v1"
env:
- CUSTOM_API_KEY_ENV_NAME
env: CUSTOM_API_KEY_ENV_NAME # 注意:现在是单个环境变量
models:
- supported-model-name
- another-model-name
# 本地 Ollama 服务
local-ollama:
name: Local Ollama
base_url: "http://localhost:11434/v1"
default: llama3.2
env:
- NO_API_KEY
env: NO_API_KEY # 对于不需要API Key的服务使用NO_API_KEY
models:
- llama3.2
- qwen2.5
@ -83,8 +84,7 @@ local-vllm:
name: Local vLLM
base_url: "http://localhost:8000/v1"
default: Qwen/Qwen2.5-7B-Instruct
env:
- NO_API_KEY
env: NO_API_KEY
models:
- Qwen/Qwen2.5-7B-Instruct
- Qwen/Qwen2.5-14B-Instruct
@ -113,7 +113,7 @@ docker compose restart api-dev
#### 1. 配置模型信息
`src/config/static/models.yaml` `src/config/static/models.private.yaml`添加配置:
`src/config/static/models.yaml` 中或通过覆盖配置文件添加配置:
```yaml
EMBED_MODEL_INFO:

View File

@ -665,10 +665,7 @@ async def get_all_feedbacks(
db.query(MessageFeedback, Message, Conversation, User)
.join(Message, MessageFeedback.message_id == Message.id)
.join(Conversation, Message.conversation_id == Conversation.id)
.outerjoin(User,
(MessageFeedback.user_id == User.id) |
(MessageFeedback.user_id == User.user_id)
)
.outerjoin(User, (MessageFeedback.user_id == User.id) | (MessageFeedback.user_id == User.user_id))
)
# Apply filters

View File

@ -5,7 +5,6 @@ from langchain_core.language_models import BaseChatModel
from pydantic import SecretStr
from src import config
from src.models import get_custom_model
from src.utils import get_docker_safe_url
@ -15,22 +14,13 @@ def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel:
"""
provider, model = fully_specified_name.split("/", maxsplit=1)
if provider == "custom":
from langchain_openai import ChatOpenAI
model_info = get_custom_model(model)
api_key = model_info.get("api_key") or "custom_model"
base_url = get_docker_safe_url(model_info["api_base"])
model_name = model_info.get("name") or "custom_model"
return ChatOpenAI(
model=model_name,
api_key=SecretStr(api_key),
base_url=base_url,
stream_usage=True,
)
assert provider != "custom", "[弃用] 自定义模型已移除,请在 src/config/static/models.yaml 中配置"
model_info = config.model_names.get(provider, {})
api_key = os.getenv(model_info["env"][0], model_info["env"][0])
env_var = model_info["env"]
api_key = os.getenv(env_var, env_var)
base_url = get_docker_safe_url(model_info["base_url"])
if provider in ["deepseek", "dashscope"]:

View File

@ -6,8 +6,6 @@ import yaml
from src.utils.logging_config import logger
DEFAULT_MOCK_API = "this_is_mock_api_key_in_frontend"
class SimpleConfig(dict):
def __key(self, key):
@ -44,6 +42,7 @@ class Config(SimpleConfig):
self.filename = str(Path(f"{self.save_dir}/config/base.yaml"))
os.makedirs(os.path.dirname(self.filename), exist_ok=True)
self._models_config_path: Path | None = None
self._update_models_from_file()
### >>> 默认配置
@ -100,40 +99,42 @@ class Config(SimpleConfig):
def _update_models_from_file(self):
"""
models.yaml models.private.yaml 中更新 MODEL_NAMES
models.yaml 或覆盖配置文件中更新 MODEL_NAMES
"""
# 检查是否设置了覆盖配置文件的环境变量
override_config_path = os.getenv("OVERRIDE_DEFAULT_MODELS_CONFIG_WITH")
with open(Path("src/config/static/models.yaml"), encoding="utf-8") as f:
if override_config_path and os.path.exists(override_config_path):
config_file = Path(override_config_path)
logger.info(f"Using override models config from: {override_config_path}")
else:
config_file = Path("src/config/static/models.yaml")
logger.info("Using default models config")
self._models_config_path = config_file
with open(self._models_config_path, encoding="utf-8") as f:
_models = yaml.safe_load(f)
# 尝试打开一个 models.private.yaml 文件,用来覆盖 models.yaml 中的配置
# 兼容性测试(历史遗留问题)
exist_yml = Path("src/config/static/models.private.yml").exists()
exist_yaml = Path("src/config/static/models.private.yaml").exists()
if exist_yml and not exist_yaml:
os.rename("src/config/static/models.private.yml", "src/config/static/models.private.yaml")
try:
with open(Path("src/config/static/models.private.yaml"), encoding="utf-8") as f:
_models_private = yaml.safe_load(f)
except FileNotFoundError:
_models_private = {}
# 修改为按照子元素合并
# _models = {**_models, **_models_private}
self.model_names = {**_models["MODEL_NAMES"], **_models_private.get("MODEL_NAMES", {})}
self.embed_model_names = {**_models["EMBED_MODEL_INFO"], **_models_private.get("EMBED_MODEL_INFO", {})}
self.reranker_names = {**_models["RERANKER_LIST"], **_models_private.get("RERANKER_LIST", {})}
self.model_names = _models["MODEL_NAMES"]
self.embed_model_names = _models["EMBED_MODEL_INFO"]
self.reranker_names = _models["RERANKER_LIST"]
def _save_models_to_file(self):
_models = {
"""
将当前模型配置写回模型配置文件
"""
if self._models_config_path is None:
self._models_config_path = Path("src/config/static/models.yaml")
models_payload = {
"MODEL_NAMES": self.model_names,
"EMBED_MODEL_INFO": self.embed_model_names,
"RERANKER_LIST": self.reranker_names,
}
with open(Path("src/config/static/models.private.yaml"), "w", encoding="utf-8") as f:
yaml.dump(_models, f, indent=2, allow_unicode=True)
with open(self._models_config_path, "w", encoding="utf-8") as f:
yaml.safe_dump(models_payload, f, indent=2, allow_unicode=True, sort_keys=False)
def handle_self(self):
"""
@ -156,19 +157,21 @@ class Config(SimpleConfig):
)
# 检查模型提供商的环境变量
conds = {}
self.model_provider_status = {}
for provider in self.model_names:
conds[provider] = self.model_names[provider]["env"]
conds_bool = [bool(os.getenv(_k)) for _k in conds[provider]]
self.model_provider_status[provider] = all(conds_bool)
env_var = self.model_names[provider]["env"]
# 如果环境变量名为 NO_API_KEY则认为总是可用
if env_var == "NO_API_KEY":
self.model_provider_status[provider] = True
else:
self.model_provider_status[provider] = bool(os.getenv(env_var))
if os.getenv("TAVILY_API_KEY"):
self.enable_web_search = True
self.valuable_model_provider = [k for k, v in self.model_provider_status.items() if v]
assert len(self.valuable_model_provider) > 0, (
f"No model provider available, please check your `.env` file. API_KEY_LIST: {conds}"
"No model provider available, please check your `.env` file."
)
def load(self):
@ -218,23 +221,6 @@ class Config(SimpleConfig):
def dump_config(self):
return json.loads(str(self))
def compare_custom_models(self, value):
"""
比较 custom_models 中的 api_key如果输入的 api_key 与当前的 api_key 相同则不修改
如果输入的 api_key DEFAULT_MOCK_API则使用当前的 api_key
"""
current_models_dict = {model["custom_id"]: model.get("api_key") for model in self.get("custom_models", [])}
for i, model in enumerate(value):
input_custom_id = model.get("custom_id")
input_api_key = model.get("api_key")
if input_custom_id in current_models_dict:
current_api_key = current_models_dict[input_custom_id]
if input_api_key == DEFAULT_MOCK_API or input_api_key == current_api_key:
value[i]["api_key"] = current_api_key
return value
config = Config()

View File

@ -1,8 +1,8 @@
####################################################
#
# 不要直接修改这个里面的文件,可能会有被覆盖的风险,
# 建议 复制一份 在 models.private.yaml 中修改,
# 会自动加载
# 默认模型配置文件。可以直接修改该文件更新模型。
# 如需自定义路径,可通过环境变量
# OVERRIDE_DEFAULT_MODELS_CONFIG_WITH 指向新的 YAML。
#
#####################################################
@ -12,8 +12,7 @@ MODEL_NAMES:
url: https://platform.openai.com/docs/models
base_url: https://api.openai.com/v1
default: gpt-4o-mini
env:
- OPENAI_API_KEY
env: OPENAI_API_KEY
models:
- gpt-4
- gpt-4o
@ -24,8 +23,7 @@ MODEL_NAMES:
url: https://platform.deepseek.com/api-docs/zh-cn/pricing
base_url: https://api.deepseek.com/v1
default: deepseek-chat
env:
- DEEPSEEK_API_KEY
env: DEEPSEEK_API_KEY
models:
- deepseek-chat
- deepseek-reasoner
@ -35,8 +33,7 @@ MODEL_NAMES:
url: https://open.bigmodel.cn/dev/api
base_url: https://open.bigmodel.cn/api/paas/v4/
default: glm-4.5-flash
env:
- ZHIPUAI_API_KEY
env: ZHIPUAI_API_KEY
models:
- glm-4.5
- glm-4.5-air
@ -47,8 +44,7 @@ MODEL_NAMES:
url: https://cloud.siliconflow.cn/models
base_url: https://api.siliconflow.cn/v1
default: zai-org/GLM-4.5
env:
- SILICONFLOW_API_KEY
env: SILICONFLOW_API_KEY
models:
- Qwen/Qwen3-235B-A22B-Thinking-2507
- Qwen/Qwen3-235B-A22B-Instruct-2507
@ -60,8 +56,7 @@ MODEL_NAMES:
url: https://api.together.ai/models
base_url: https://api.together.xyz/v1/
default: meta-llama/Llama-3.3-70B-Instruct-Turbo-Free
env:
- TOGETHER_API_KEY
env: TOGETHER_API_KEY
models:
- meta-llama/Llama-3.3-70B-Instruct-Turbo-Free
@ -70,8 +65,7 @@ MODEL_NAMES:
url: https://bailian.console.aliyun.com/?switchAgent=10226727&productCode=p_efm#/model-market
base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
default: qwen-max-latest
env:
- DASHSCOPE_API_KEY
env: DASHSCOPE_API_KEY
models:
- qwen-max-latest
- qwen-plus-latest
@ -84,8 +78,7 @@ MODEL_NAMES:
url: https://console.volcengine.com/ark/region:ark+cn-beijing/model
base_url: https://ark.cn-beijing.volces.com/api/v3
default: doubao-seed-1-6-250615
env:
- ARK_API_KEY
env: ARK_API_KEY
models:
- doubao-seed-1-6-250615
- doubao-seed-1-6-thinking-250715
@ -96,8 +89,7 @@ MODEL_NAMES:
url: https://openrouter.ai/models
base_url: https://openrouter.ai/api/v1
default: openai/gpt-4o
env:
- OPENROUTER_API_KEY
env: OPENROUTER_API_KEY
models:
- openai/gpt-4o
- x-ai/grok-4

View File

@ -1,4 +1,4 @@
from src.models.chat import get_custom_model, select_model
from src.models.chat import select_model
from src.models.embed import select_embedding_model
__all__ = ["select_model", "select_embedding_model", "get_custom_model"]
__all__ = ["select_model", "select_embedding_model"]

View File

@ -79,14 +79,6 @@ class OpenModel(OpenAIBase):
super().__init__(api_key=api_key, base_url=base_url, model_name=model_name)
class CustomModel(OpenAIBase):
def __init__(self, model_info):
model_name = model_info["name"]
api_key = model_info.get("api_key") or "custom_model"
base_url = get_docker_safe_url(model_info["api_base"])
logger.info(f"> Custom model: {model_name}, base_url: {base_url}")
super().__init__(api_key=api_key, base_url=base_url, model_name=model_name)
class GeneralResponse:
@ -106,14 +98,10 @@ def select_model(model_provider, model_name=None):
if model_provider == "openai":
return OpenModel(model_name)
if model_provider == "custom":
model_info = get_custom_model(model_name)
return CustomModel(model_info)
# 其他模型默认使用OpenAIBase
try:
model = OpenAIBase(
api_key=os.getenv(model_info["env"][0]),
api_key=os.getenv(model_info["env"]),
base_url=model_info["base_url"],
model_name=model_name,
)
@ -122,13 +110,6 @@ def select_model(model_provider, model_name=None):
raise ValueError(f"Model provider {model_provider} load failed, {e} \n {traceback.format_exc()}")
def get_custom_model(model_id):
"""return model_info"""
assert config.custom_models is not None, "custom_models is not set"
modle_info = next((x for x in config.custom_models if x["custom_id"] == model_id), None)
if modle_info is None:
raise ValueError(f"Model {model_id} not found in custom models")
return modle_info
async def test_chat_model_status(provider: str, model_name: str) -> dict:
@ -178,18 +159,11 @@ async def test_all_chat_models_status() -> dict:
# 获取所有可用的模型
for provider, provider_info in config.model_names.items():
if provider == "custom":
# 处理自定义模型
for custom_model in config.custom_models:
model_id = f"custom/{custom_model['custom_id']}"
status = await test_chat_model_status("custom", custom_model["custom_id"])
results[model_id] = status
else:
# 处理普通模型
for model_name in provider_info.models:
model_id = f"{provider}/{model_name}"
status = await test_chat_model_status(provider, model_name)
results[model_id] = status
# 处理普通模型
for model_name in provider_info.models:
model_id = f"{provider}/{model_name}"
status = await test_chat_model_status(provider, model_name)
results[model_id] = status
available_count = len([m for m in results.values() if m["status"] == "available"])

View File

@ -1,38 +1,7 @@
<template>
<div>
<a-alert message="自定义模型在 0.3的稳定版中移除,届时只能通过修改 models.yaml 来添加模型和供应商。" type="warning" />
<a-alert message="自定义模型在 0.3.x 的稳定版中移除,只能通过修改 models.yaml 来添加模型和供应商。" type="warning" />
<br>
<div class="model-provider-card custom-models-card">
<div class="card-header">
<h3>自定义模型</h3>
</div>
<div class="card-body">
<div class="custom-model" v-for="(item, key) in configStore.config.custom_models" :key="item.custom_id">
<div class="card-models__header">
<div class="name" :title="item.name">{{ item.name }}</div>
<div class="action">
<a-popconfirm
title="确认删除该模型?"
@confirm="handleDeleteCustomModel(item.custom_id)"
okText="确认删除"
cancelText="取消"
ok-type="danger"
>
<a-button type="text" @click.stop><DeleteOutlined /></a-button>
</a-popconfirm>
<a-button type="text" @click.stop="prepareToEditCustomModel(item)"><EditOutlined /></a-button>
</div>
</div>
<div class="api_base">{{ item.api_base }}</div>
</div>
<div class="card-models custom-model add-model" @click="prepareToAddCustomModel">
<div class="card-models__header">
<div class="name"> + 添加模型</div>
</div>
<div class="api_base">添加兼容 OpenAI 的模型</div>
</div>
</div>
</div>
<div class="model-provider-card configured-provider" v-for="(item, key) in modelKeys" :key="key">
<div class="card-header" @click="toggleExpand(item)">
<div :class="{'model-icon': true, 'available': modelStatus[item]}">
@ -87,42 +56,11 @@
<SettingOutlined />
</a-button>
<div class="missing-keys">
需配置<span v-for="(key, idx) in modelNames[item].env" :key="idx">{{ key }}</span>
需配置<span>{{ modelNames[item].env }}</span>
</div>
</div>
</div>
<!-- 添加和编辑自定义模型的弹窗 -->
<a-modal
class="custom-model-modal"
v-model:open="customModel.visible"
:title="customModel.modelTitle"
@ok="handleAddOrEditCustomModel"
@cancel="handleCancelCustomModel"
:okText="'确认'"
:cancelText="'取消'"
:okButtonProps="{disabled: !customModel.name || !customModel.api_base}"
:ok-type="'primary'"
>
<p>添加的模型是兼容 OpenAI 的模型比如 vllmOllama</p>
<a-form :model="customModel" layout="vertical">
<a-form-item label="模型ID" v-if="customModel.edit_type == 'edit'" name="custom_id">
<p class="form-item-description">调用的模型的ID</p>
<a-input v-model:value="customModel.custom_id" disabled/>
</a-form-item>
<a-form-item label="模型名称" name="name" :rules="[{ required: true, message: '请输入模型名称' }]">
<p class="form-item-description">调用的模型的名称</p>
<a-input v-model:value="customModel.name" :disabled="customModel.edit_type == 'edit'"/>
</a-form-item>
<a-form-item label="API Base" name="api_base" :rules="[{ required: true, message: '请输入API Base' }]">
<p class="form-item-description">比如 <code>http://localhost:11434/v1</code></p>
<a-input v-model:value="customModel.api_base" />
</a-form-item>
<a-form-item label="API KEY" name="api_key">
<a-input-password v-model:value="customModel.api_key" :visibilityToggle="true" autocomplete="new-password"/>
</a-form-item>
</a-form>
</a-modal>
<!-- 模型提供商配置弹窗 -->
<a-modal
@ -180,7 +118,7 @@
<div v-if="providerConfig.allModels.length === 0" class="modal-no-models">
<a-alert v-if="!modelStatus[providerConfig.provider]" type="warning" message="请在 src/.env 中配置对应的 APIKEY并重新启动服务" />
<div v-else>
<a-alert type="warning" message="该提供商暂未适配获取模型列表的方法,如果需要添加模型,请在 src/config/static/models.private.yaml 中添加。(如没有此文件,则需要手动创建一个)" />
<a-alert type="warning" message="该提供商暂未适配获取模型列表的方法,如需添加模型,请编辑 src/config/static/models.yaml 或通过环境变量 OVERRIDE_DEFAULT_MODELS_CONFIG_WITH 指向的文件。" />
<img src="@/assets/pics/guides/how-to-add-models.png" alt="添加模型指引" style="width: 100%; height: 100%; margin-top: 16px;">
</div>
</div>
@ -194,8 +132,6 @@
import { computed, reactive, watch, h } from 'vue'
import { message } from 'ant-design-vue';
import {
DeleteOutlined,
EditOutlined,
InfoCircleOutlined,
SettingOutlined,
DownCircleOutlined,
@ -212,16 +148,6 @@ const configStore = useConfigStore();
const modelNames = computed(() => configStore.config?.model_names);
const modelStatus = computed(() => configStore.config?.model_provider_status);
//
const customModel = reactive({
modelTitle: '添加自定义模型',
visible: false,
custom_id: '',
name: '',
api_key: '',
api_base: '',
edit_type: 'add',
});
//
const providerConfig = reactive({
@ -257,86 +183,6 @@ watch(modelKeys, (newKeys) => {
});
}, { immediate: true });
//
const generateRandomHash = (length) => {
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let hash = '';
for (let i = 0; i < length; i++) {
hash += chars.charAt(Math.floor(Math.random() * chars.length));
}
return hash;
};
//
const handleDeleteCustomModel = (customId) => {
const updatedModels = configStore.config.custom_models.filter(item => item.custom_id !== customId);
configStore.setConfigValue('custom_models', updatedModels);
};
//
const prepareToEditCustomModel = (item) => {
customModel.modelTitle = '编辑自定义模型';
customModel.custom_id = item.custom_id;
customModel.visible = true;
customModel.edit_type = 'edit';
customModel.name = item.name;
customModel.api_key = item.api_key;
customModel.api_base = item.api_base;
};
//
const prepareToAddCustomModel = () => {
customModel.modelTitle = '添加自定义模型';
customModel.edit_type = 'add';
customModel.visible = true;
clearCustomModel();
};
//
const clearCustomModel = () => {
customModel.custom_id = '';
customModel.name = '';
customModel.api_key = '';
customModel.api_base = '';
};
// /
const handleCancelCustomModel = () => {
clearCustomModel();
customModel.visible = false;
};
//
const handleAddOrEditCustomModel = async () => {
if (!customModel.name || !customModel.api_base) {
message.error('请填写完整的模型名称和API Base信息。');
return;
}
let custom_models = configStore.config.custom_models || [];
const model_info = {
custom_id: customModel.custom_id || `${customModel.name}-${generateRandomHash(4)}`,
name: customModel.name,
api_key: customModel.api_key,
api_base: customModel.api_base,
};
if (customModel.edit_type === 'add') {
if (custom_models.find(item => item.custom_id === customModel.custom_id)) {
message.error('模型ID已存在');
return;
}
custom_models.push(model_info);
} else {
// custom_id
custom_models = custom_models.map(item => item.custom_id === customModel.custom_id ? model_info : item);
}
customModel.visible = false;
await configStore.setConfigValue('custom_models', custom_models);
message.success(customModel.edit_type === 'add' ? '模型添加成功' : '模型修改成功');
};
//
const toggleExpand = (item) => {

View File

@ -37,11 +37,6 @@
{{ model }}
</a-menu-item>
</a-menu-item-group>
<a-menu-item-group v-if="customModels.length > 0" title="自定义模型">
<a-menu-item v-for="(model, idx) in customModels" :key="`custom-${idx}`" @click="handleSelectModel('custom', model.custom_id)">
custom/{{ model.custom_id }}
</a-menu-item>
</a-menu-item-group>
</a-menu>
</template>
</a-dropdown>
@ -75,7 +70,6 @@ const state = reactive({
// configStore
const modelNames = computed(() => configStore.config?.model_names)
const modelStatus = computed(() => configStore.config?.model_provider_status)
const customModels = computed(() => configStore.config?.custom_models || [])
// modelStatus key
const modelKeys = computed(() => {