diff --git a/src/config/__init__.py b/src/config/__init__.py
index d0b516c5..0682393d 100644
--- a/src/config/__init__.py
+++ b/src/config/__init__.py
@@ -11,6 +11,7 @@ MODEL_NAMES = _models["MODEL_NAMES"]
EMBED_MODEL_INFO = _models["EMBED_MODEL_INFO"]
RERANKER_LIST = _models["RERANKER_LIST"]
+DEFAULT_MOCK_API = 'this_is_mock_api_key_in_frontend'
class SimpleConfig(dict):
@@ -174,3 +175,34 @@ class Config(SimpleConfig):
json.dump(self, f, indent=4)
logger.info(f"Config file {self.filename} saved")
+
+ def get_safe_config(self):
+ """
+ 获取安全的配置,即过滤掉 api_key
+ """
+
+ config = json.loads(str(self))
+
+ # 过滤掉 api_key
+ for model in config.get("custom_models", []):
+ model["api_key"] = DEFAULT_MOCK_API if model.get("api_key") else ""
+
+ return config
+
+ 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.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
diff --git a/src/models/chat_model.py b/src/models/chat_model.py
index d62da34c..fe5c9c82 100644
--- a/src/models/chat_model.py
+++ b/src/models/chat_model.py
@@ -58,7 +58,7 @@ class OpenModel(OpenAIBase):
class CustomModel(OpenAIBase):
def __init__(self, model_info):
model_name = model_info["name"]
- api_key = model_info["api_key"] or "custom_model"
+ api_key = model_info.get("api_key", "custom_model")
base_url = get_docker_safe_url(model_info["api_base"])
logger.info(f"> Custom model: {model_name}, base_url: {base_url}")
diff --git a/src/routers/base_router.py b/src/routers/base_router.py
index 0dcbbde7..16986210 100644
--- a/src/routers/base_router.py
+++ b/src/routers/base_router.py
@@ -1,14 +1,10 @@
+from fastapi import Request, Body
from fastapi import APIRouter
base = APIRouter()
-from fastapi import FastAPI, HTTPException
-from fastapi.responses import JSONResponse
-from fastapi import Request, Body
-
-from src.core import HistoryManager
from src.core.startup import startup
-from src.utils import logger
+from src.utils import hashstr, logger
@base.get("/")
@@ -17,13 +13,16 @@ async def route_index():
@base.get("/config")
def get_config():
- return startup.config
+ return startup.config.get_safe_config()
@base.post("/config")
async def update_config(key = Body(...), value = Body(...)):
+ if key == "custom_models":
+ value = startup.config.compare_custom_models(value)
+
startup.config[key] = value
startup.config.save()
- return startup.config
+ return startup.config.get_safe_config()
@base.post("/restart")
async def restart():
diff --git a/web/src/assets/providers/siliconflow.png b/web/src/assets/providers/siliconflow.png
index f32fc6eb..bc8db4aa 100644
Binary files a/web/src/assets/providers/siliconflow.png and b/web/src/assets/providers/siliconflow.png differ
diff --git a/web/src/views/SettingView.vue b/web/src/views/SettingView.vue
index 522770ca..57171bf7 100644
--- a/web/src/views/SettingView.vue
+++ b/web/src/views/SettingView.vue
@@ -5,11 +5,8 @@
配置文件也可以在 saves/config/base.yaml 中修改
-
- 需要重启
-
-
- 重启服务
+
+ {{ isNeedRestart ? '需要重启' : '重启服务' }}
@@ -121,12 +118,12 @@
>
-
+
{{ item.api_base }}
-
+
@@ -135,7 +132,7 @@
class="custom-model-modal"
v-model:open="customModel.visible"
:title="customModel.modelTitle"
- @ok="handleAddCustomModel"
+ @ok="handleAddOrEditCustomModel"
@cancel="handleCancelCustomModel"
:okText="'确认'"
:cancelText="'取消'"
@@ -145,13 +142,13 @@
添加的模型是兼容 OpenAI 的模型,比如 vllm,Ollama。
-
+
-
+
-
+
@@ -164,25 +161,47 @@
-
{{ modelNames[item].name }}
-
-
-
-
+
{{ modelNames[item].name }}
+
+
+
+
+
- {{ model }}
+
+
+
+
+
+
@@ -212,6 +231,8 @@ import {
DeleteOutlined,
EditOutlined,
InfoCircleOutlined,
+ DownOutlined,
+ UpOutlined,
} from '@ant-design/icons-vue';
import HeaderComponent from '@/components/HeaderComponent.vue';
import TableConfigComponent from '@/components/TableConfigComponent.vue';
@@ -228,7 +249,7 @@ const isNeedRestart = ref(false)
const customModel = reactive({
modelTitle: '添加自定义模型',
visible: false,
- custom_id: '', // Add this line
+ custom_id: '',
name: '',
api_key: '',
api_base: '',
@@ -249,6 +270,12 @@ const notModelKeys = computed(() => {
return Object.keys(modelStatus.value || {}).filter(key => !modelStatus.value?.[key])
})
+// 模型展开状态管理,默认展开
+const expandedModels = reactive(modelKeys.value.reduce((acc, key) => {
+ acc[key] = true
+ return acc
+}, {}))
+
const generateRandomHash = (length) => {
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let hash = '';
@@ -298,49 +325,35 @@ const handleChange = (key, e) => {
configStore.setConfigValue(key, e)
}
-const handleAddCustomModel = async () => {
+const handleAddOrEditCustomModel = async () => {
if (!customModel.name || !customModel.api_base) {
- message.error('请填写完整模型信息')
+ message.error('请填写完整的模型名称和API Base信息。')
return
}
- if (!configStore.config.custom_models) {
- configStore.config.custom_models = []
- }
-
+ let custom_models = configStore.config.custom_models || [];
const model_info = {
- custom_id: customModel.custom_id,
+ 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 (configStore.config.custom_models.find(item => item.custom_id == customModel.custom_id)) {
- message.error('模型ID已存在')
+ if (customModel.edit_type === 'add') {
+ if (custom_models.find(item => item.custom_id === customModel.custom_id)) {
+ message.error('模型ID已存在')
return
}
- const modelHash = generateRandomHash(4)
- model_info.custom_id = `${customModel.name}-${modelHash}`
- configStore.config.custom_models.push(model_info)
+ custom_models.push(model_info)
} else {
- configStore.config.custom_models = configStore.config.custom_models.map(item => {
- if (item.custom_id == customModel.custom_id) {
- if (item.name != customModel.name) {
- const modelHash = generateRandomHash(4)
- model_info.custom_id = `${customModel.name}-${modelHash}`
- configStore.setConfigValue('model_name', model_info.custom_id)
- }
- return model_info
- }
- return item
- })
+ // 如果 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', configStore.config.custom_models)
- message.success('添加自定义模型成功')
+ customModel.visible = false;
+ await configStore.setConfigValue('custom_models', custom_models);
+ message.success(customModel.edit_type === 'add' ? '模型添加成功' : '模型修改成功');
}
const handleDeleteCustomModel = (custom_id) => {
@@ -348,21 +361,32 @@ const handleDeleteCustomModel = (custom_id) => {
configStore.setConfigValue('custom_models', updatedModels);
}
-const handleEditCustomModel = (item) => {
+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
- customModel.visible = true
- customModel.edit_type = 'edit'
}
-const handleCancelCustomModel = () => {
+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
}
@@ -499,14 +523,31 @@ const sendRestart = () => {
align-items: center;
gap: 10px;
+ .model-title-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 10px;
+ flex: 1;
+ }
+
+ .model-url {
+ font-size: 12px;
+ width: fit-content;
+ color: var(--gray-500);
+ }
+
.model-icon {
- width: 24px;
- height: 24px;
+ width: 28px;
+ height: 28px;
+
// 灰度
filter: grayscale(100%);
img {
width: 100%;
height: 100%;
+ border-radius: 4px;
+ border: 1px solid var(--gray-300);
}
&.available {
@@ -514,10 +555,9 @@ const sendRestart = () => {
}
}
-
h3 {
margin: 0;
- font-size: 1rem;
+ font-size: 0.9rem;
font-weight: bold;
}
@@ -546,12 +586,53 @@ const sendRestart = () => {
}
.missing-keys {
+ margin-top: 4px;
color: var(--gray-600);
+ font-size: 12px;
& > span {
- margin-left: 10px;
+ margin-left: 6px;
user-select: all;
+ background-color: var(--gray-100);
+ padding: 2px 6px;
+ border-radius: 4px;
+ color: var(--gray-800);
}
}
+
+ .expand-button {
+ margin-left: auto;
+ height: 32px;
+ width: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ cursor: pointer;
+ color: var(--gray-500);
+
+ &:hover {
+ background-color: var(--gray-100);
+ }
+
+ .icon-wrapper {
+ display: inline-flex;
+ transition: transform 0.2s ease;
+
+ &.rotated {
+ transform: rotate(180deg);
+ }
+ }
+ }
+ }
+
+ .card-body-wrapper {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.2s ease-out; // 先快后慢
+
+ &.expanded {
+ max-height: 700px; /* 设置一个足够大的值 */
+ }
}
.card-body {
@@ -606,7 +687,7 @@ const sendRestart = () => {
gap: 10px;
.card-models__header {
width: 100%;
- height: 32px;
+ height: 24px;
display: flex;
justify-content: flex-start;
align-items: center;
@@ -631,6 +712,10 @@ const sendRestart = () => {
.api_base {
font-size: 12px;
color: var(--gray-600);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
}
&:hover {