feat(models): 添加嵌入和聊天模型状态检查接口 #282
- 在知识路由中新增获取指定嵌入模型和聊天模型状态的接口 - 实现获取所有嵌入模型和聊天模型状态的功能 - 更新前端API以支持模型状态检查,增强用户体验 - 优化日志记录格式,提升调试信息的可读性
This commit is contained in:
parent
a337cba3f7
commit
f1fe10f22c
23
AGENTS.md
23
AGENTS.md
@ -17,7 +17,9 @@ Yuxi-Know/
|
|||||||
│ ├── plugins/ # 插件(存放OCR)
|
│ ├── plugins/ # 插件(存放OCR)
|
||||||
│ ├── static/ # 静态资源(配置文件)
|
│ ├── static/ # 静态资源(配置文件)
|
||||||
│ └── utils/ # 工具函数
|
│ └── utils/ # 工具函数
|
||||||
├── web/ # 前端代码
|
├── web/src/ # 前端代码
|
||||||
|
│ ├── apis/ # 前端的接口定义
|
||||||
|
│ └── views/ # 主要 vue 文件
|
||||||
└── docker-compose.yml # Docker Compose 配置
|
└── docker-compose.yml # Docker Compose 配置
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -37,7 +39,20 @@ Yuxi-Know/
|
|||||||
|
|
||||||
核心原则: 由于 api-dev 和 web-dev 服务均配置了热重载 (hot-reloading),本地修改代码后无需重启容器,服务会自动更新。应该先检查项目是否已经在后台启动(`docker ps`),具体的可以阅读 [docker-compose.yml](docker-compose.yml).
|
核心原则: 由于 api-dev 和 web-dev 服务均配置了热重载 (hot-reloading),本地修改代码后无需重启容器,服务会自动更新。应该先检查项目是否已经在后台启动(`docker ps`),具体的可以阅读 [docker-compose.yml](docker-compose.yml).
|
||||||
|
|
||||||
## 风格说明
|
前端开发规范:
|
||||||
|
|
||||||
- UI风格要简洁,同时要保持一致性,颜色要尽量参考 [base.css](web/src/assets/css/base.css) 中的颜色。不要悬停位移,不要过度使用阴影以及渐变色。
|
- API 接口规范:所有的 API 接口都应该定义在 web/src/apis 下面,并继承自 apiGet/apiPost/apiRequest
|
||||||
- Python 代码要符合 Python 的规范,尽量使用较新的语法,避免使用旧版本的语法(版本兼容到 3.12+),使用 uvx ruff check 检查 lint。
|
- Icon 应该从 @ant-design/icons-vue 或者 lucide-vue-next
|
||||||
|
- Vue 中的样式使用 less,并尽量使用[base.css](web/src/assets/css/base.css) 中的颜色。
|
||||||
|
- UI风格要简洁,同时要保持一致性,颜色要尽量参考 不要悬停位移,不要过度使用阴影以及渐变色。
|
||||||
|
|
||||||
|
|
||||||
|
后端开发规范:
|
||||||
|
|
||||||
|
- 项目使用 uv 来管理依赖
|
||||||
|
- Python 代码要符合 Python 的规范,尽量使用较新的语法,避免使用旧版本的语法(版本兼容到 3.12+),使用 make lint 检查 lint。使用 make format 来格式化代码。
|
||||||
|
|
||||||
|
其他:
|
||||||
|
|
||||||
|
- 如果需要新建说明文档,则保存在 docs/vibe 文件夹下面
|
||||||
|
- 测试脚本可以放在 test 文件夹下面,可以从 docker 中启动测试
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from server.models.user_model import User
|
|||||||
from server.utils.auth_middleware import get_admin_user
|
from server.utils.auth_middleware import get_admin_user
|
||||||
from src import config, knowledge_base
|
from src import config, knowledge_base
|
||||||
from src.knowledge.indexing import process_file_to_markdown
|
from src.knowledge.indexing import process_file_to_markdown
|
||||||
|
from src.models.embed import test_embedding_model_status, test_all_embedding_models_status
|
||||||
from src.utils import hashstr, logger
|
from src.utils import hashstr, logger
|
||||||
|
|
||||||
knowledge = APIRouter(prefix="/knowledge", tags=["knowledge"])
|
knowledge = APIRouter(prefix="/knowledge", tags=["knowledge"])
|
||||||
@ -554,3 +555,35 @@ async def get_knowledge_base_statistics(current_user: User = Depends(get_admin_u
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取知识库统计失败 {e}, {traceback.format_exc()}")
|
logger.error(f"获取知识库统计失败 {e}, {traceback.format_exc()}")
|
||||||
return {"message": f"获取知识库统计失败 {e}", "stats": {}}
|
return {"message": f"获取知识库统计失败 {e}", "stats": {}}
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# === Embedding模型状态检查分组 ===
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@knowledge.get("/embedding-models/{model_id}/status")
|
||||||
|
async def get_embedding_model_status(model_id: str, current_user: User = Depends(get_admin_user)):
|
||||||
|
"""获取指定embedding模型的状态"""
|
||||||
|
logger.debug(f"Checking embedding model status: {model_id}")
|
||||||
|
try:
|
||||||
|
status = await test_embedding_model_status(model_id)
|
||||||
|
return {"status": status, "message": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取embedding模型状态失败 {model_id}: {e}, {traceback.format_exc()}")
|
||||||
|
return {
|
||||||
|
"message": f"获取embedding模型状态失败: {e}",
|
||||||
|
"status": {"model_id": model_id, "status": "error", "message": str(e)},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@knowledge.get("/embedding-models/status")
|
||||||
|
async def get_all_embedding_models_status(current_user: User = Depends(get_admin_user)):
|
||||||
|
"""获取所有embedding模型的状态"""
|
||||||
|
logger.debug("Checking all embedding models status")
|
||||||
|
try:
|
||||||
|
status = await test_all_embedding_models_status()
|
||||||
|
return {"status": status, "message": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取所有embedding模型状态失败: {e}, {traceback.format_exc()}")
|
||||||
|
return {"message": f"获取所有embedding模型状态失败: {e}", "status": {"models": {}, "total": 0, "available": 0}}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException
|
|||||||
from server.models.user_model import User
|
from server.models.user_model import User
|
||||||
from server.utils.auth_middleware import get_admin_user, get_superadmin_user
|
from server.utils.auth_middleware import get_admin_user, get_superadmin_user
|
||||||
from src import config, graph_base
|
from src import config, graph_base
|
||||||
|
from src.models.chat import test_chat_model_status, test_all_chat_models_status
|
||||||
from src.utils.logging_config import logger
|
from src.utils.logging_config import logger
|
||||||
|
|
||||||
system = APIRouter(prefix="/system", tags=["system"])
|
system = APIRouter(prefix="/system", tags=["system"])
|
||||||
@ -244,3 +245,35 @@ async def check_ocr_services_health(current_user: User = Depends(get_admin_user)
|
|||||||
overall_status = "healthy" if any(svc["status"] == "healthy" for svc in health_status.values()) else "unhealthy"
|
overall_status = "healthy" if any(svc["status"] == "healthy" for svc in health_status.values()) else "unhealthy"
|
||||||
|
|
||||||
return {"overall_status": overall_status, "services": health_status, "message": "OCR服务健康检查完成"}
|
return {"overall_status": overall_status, "services": health_status, "message": "OCR服务健康检查完成"}
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# === 聊天模型状态检查分组 ===
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@system.get("/chat-models/status")
|
||||||
|
async def get_chat_model_status(provider: str, model_name: str, current_user: User = Depends(get_admin_user)):
|
||||||
|
"""获取指定聊天模型的状态"""
|
||||||
|
logger.debug(f"Checking chat model status: {provider}/{model_name}")
|
||||||
|
try:
|
||||||
|
status = await test_chat_model_status(provider, model_name)
|
||||||
|
return {"status": status, "message": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取聊天模型状态失败 {provider}/{model_name}: {e}")
|
||||||
|
return {
|
||||||
|
"message": f"获取聊天模型状态失败: {e}",
|
||||||
|
"status": {"provider": provider, "model_name": model_name, "status": "error", "message": str(e)},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@system.get("/chat-models/all/status")
|
||||||
|
async def get_all_chat_models_status(current_user: User = Depends(get_admin_user)):
|
||||||
|
"""获取所有聊天模型的状态"""
|
||||||
|
logger.debug("Checking all chat models status")
|
||||||
|
try:
|
||||||
|
status = await test_all_chat_models_status()
|
||||||
|
return {"status": status, "message": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取所有聊天模型状态失败: {e}")
|
||||||
|
return {"message": f"获取所有聊天模型状态失败: {e}", "status": {"models": {}, "total": 0, "available": 0}}
|
||||||
|
|||||||
@ -131,5 +131,70 @@ def get_custom_model(model_id):
|
|||||||
return modle_info
|
return modle_info
|
||||||
|
|
||||||
|
|
||||||
|
async def test_chat_model_status(provider: str, model_name: str) -> dict:
|
||||||
|
"""
|
||||||
|
测试指定聊天模型的状态
|
||||||
|
|
||||||
|
Args:
|
||||||
|
provider: 模型提供商
|
||||||
|
model_name: 模型名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含状态信息的字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 加载模型
|
||||||
|
logger.debug(f"Selecting chat model {provider}/{model_name}")
|
||||||
|
model = select_model(provider, model_name)
|
||||||
|
|
||||||
|
# 使用简单的测试消息
|
||||||
|
test_messages = [{"role": "user", "content": "Say 1"}]
|
||||||
|
|
||||||
|
# 发送测试请求
|
||||||
|
response = model.call(test_messages, stream=False)
|
||||||
|
logger.debug(f"Test chat model status response: {response}")
|
||||||
|
|
||||||
|
# 检查响应是否有效
|
||||||
|
if response and response.content:
|
||||||
|
return {"provider": provider, "model_name": model_name, "status": "available", "message": "连接正常"}
|
||||||
|
else:
|
||||||
|
return {"provider": provider, "model_name": model_name, "status": "unavailable", "message": "响应无效"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"测试聊天模型状态失败 {provider}/{model_name}: {e}")
|
||||||
|
return {"provider": provider, "model_name": model_name, "status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_all_chat_models_status() -> dict:
|
||||||
|
"""
|
||||||
|
测试所有支持的聊天模型状态
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含所有模型状态的字典
|
||||||
|
"""
|
||||||
|
from src import config
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
# 获取所有可用的模型
|
||||||
|
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
|
||||||
|
|
||||||
|
available_count = len([m for m in results.values() if m["status"] == "available"])
|
||||||
|
|
||||||
|
return {"models": results, "total": len(results), "available": available_count}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -168,12 +168,89 @@ class OtherEmbedding(BaseEmbeddingModel):
|
|||||||
logger.error(f"Other Embedding async request failed: {e}, {payload}")
|
logger.error(f"Other Embedding async request failed: {e}, {payload}")
|
||||||
raise ValueError(f"Other Embedding async request failed: {e}")
|
raise ValueError(f"Other Embedding async request failed: {e}")
|
||||||
|
|
||||||
|
async def test_connection(self) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
测试embedding模型的连接性
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (success: bool, message: str)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 使用简单的测试文本
|
||||||
|
test_text = ["Hello world"]
|
||||||
|
await self.aencode(test_text)
|
||||||
|
return True, "连接正常"
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
|
||||||
|
async def test_embedding_model_status(model_id: str) -> dict:
|
||||||
|
"""
|
||||||
|
测试指定embedding模型的状态
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_id: 模型ID,格式为 "provider/model_name"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含状态信息的字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
support_embed_models = config.embed_model_names.keys()
|
||||||
|
if model_id not in support_embed_models:
|
||||||
|
return {"model_id": model_id, "status": "unsupported", "message": f"不支持的模型: {model_id}"}
|
||||||
|
|
||||||
|
# 选择并创建模型实例
|
||||||
|
model = select_embedding_model(model_id)
|
||||||
|
|
||||||
|
# 测试连接
|
||||||
|
success, message = await model.test_connection()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"model_id": model_id,
|
||||||
|
"status": "available" if success else "unavailable",
|
||||||
|
"message": message if not success else "连接正常",
|
||||||
|
"dimension": model.dimension,
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"测试embedding模型状态失败 {model_id}: {e}")
|
||||||
|
return {"model_id": model_id, "status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_all_embedding_models_status() -> dict:
|
||||||
|
"""
|
||||||
|
测试所有支持的embedding模型状态
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含所有模型状态的字典
|
||||||
|
"""
|
||||||
|
support_embed_models = list(config.embed_model_names.keys())
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
# 并发测试所有模型
|
||||||
|
tasks = [test_embedding_model_status(model_id) for model_id in support_embed_models]
|
||||||
|
model_statuses = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
|
for i, status in enumerate(model_statuses):
|
||||||
|
if isinstance(status, Exception):
|
||||||
|
model_id = support_embed_models[i]
|
||||||
|
results[model_id] = {"model_id": model_id, "status": "error", "message": str(status)}
|
||||||
|
else:
|
||||||
|
results[status["model_id"]] = status
|
||||||
|
|
||||||
|
return {
|
||||||
|
"models": results,
|
||||||
|
"total": len(support_embed_models),
|
||||||
|
"available": len([m for m in results.values() if m["status"] == "available"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def select_embedding_model(model_id):
|
def select_embedding_model(model_id):
|
||||||
provider, model_name = model_id.split("/", 1) if model_id else ("", "")
|
provider, model_name = model_id.split("/", 1) if model_id else ("", "")
|
||||||
support_embed_models = config.embed_model_names.keys()
|
support_embed_models = config.embed_model_names.keys()
|
||||||
assert model_id in support_embed_models, f"Unsupported embed model: {model_id}, only support {support_embed_models}"
|
assert model_id in support_embed_models, f"Unsupported embed model: {model_id}, only support {support_embed_models}"
|
||||||
logger.debug(f"Loading embedding model {model_id}")
|
logger.info(f"Loading embedding model {model_id}")
|
||||||
if provider == "local":
|
if provider == "local":
|
||||||
raise ValueError("Local embedding model is not supported, please use other embedding models")
|
raise ValueError("Local embedding model is not supported, please use other embedding models")
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ def setup_logger(name, level="DEBUG", console=True):
|
|||||||
loguru_logger.add(
|
loguru_logger.add(
|
||||||
LOG_FILE,
|
LOG_FILE,
|
||||||
level=level,
|
level=level,
|
||||||
format="{time:YYYY-MM-DD HH:mm:ss} - {level} - {name}:{line} - {message}",
|
format="{time:YYYY-MM-DD HH:mm:ss} - {level} - {file}:{line} - {message}",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
rotation="10 MB", # 文件大小达到 10MB 时轮转
|
rotation="10 MB", # 文件大小达到 10MB 时轮转
|
||||||
retention="30 days", # 保留30天的日志
|
retention="30 days", # 保留30天的日志
|
||||||
@ -35,7 +35,7 @@ def setup_logger(name, level="DEBUG", console=True):
|
|||||||
format=(
|
format=(
|
||||||
"<green>{time:MM-DD HH:mm:ss}</green> "
|
"<green>{time:MM-DD HH:mm:ss}</green> "
|
||||||
"<level>{level}</level> "
|
"<level>{level}</level> "
|
||||||
"<cyan>{name}:{line}</cyan>: "
|
"<cyan>{file}:{line}</cyan>: "
|
||||||
"<level>{message}</level>"
|
"<level>{message}</level>"
|
||||||
),
|
),
|
||||||
colorize=True,
|
colorize=True,
|
||||||
|
|||||||
@ -198,3 +198,26 @@ export const typeApi = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// === Embedding模型状态检查分组 ===
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const embeddingApi = {
|
||||||
|
/**
|
||||||
|
* 获取指定embedding模型的状态
|
||||||
|
* @param {string} modelId - 模型ID
|
||||||
|
* @returns {Promise} - 模型状态
|
||||||
|
*/
|
||||||
|
getModelStatus: async (modelId) => {
|
||||||
|
return apiAdminGet(`/api/knowledge/embedding-models/${modelId}/status`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有embedding模型的状态
|
||||||
|
* @returns {Promise} - 所有模型状态
|
||||||
|
*/
|
||||||
|
getAllModelsStatus: async () => {
|
||||||
|
return apiAdminGet('/api/knowledge/embedding-models/status')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -100,5 +100,29 @@ export const ocrApi = {
|
|||||||
getHealth: async () => apiAdminGet('/api/system/ocr/health')
|
getHealth: async () => apiAdminGet('/api/system/ocr/health')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// === 聊天模型状态检查分组 ===
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const chatModelApi = {
|
||||||
|
/**
|
||||||
|
* 获取指定聊天模型的状态
|
||||||
|
* @param {string} provider - 模型提供商
|
||||||
|
* @param {string} modelName - 模型名称
|
||||||
|
* @returns {Promise} - 模型状态
|
||||||
|
*/
|
||||||
|
getModelStatus: async (provider, modelName) => {
|
||||||
|
return apiAdminGet(`/api/system/chat-models/status?provider=${encodeURIComponent(provider)}&model_name=${encodeURIComponent(modelName)}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有聊天模型的状态
|
||||||
|
* @returns {Promise} - 所有模型状态
|
||||||
|
*/
|
||||||
|
getAllModelsStatus: async () => {
|
||||||
|
return apiAdminGet('/api/system/chat-models/all/status')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-dropdown trigger="click">
|
<a-dropdown trigger="click">
|
||||||
<a class="model-select" @click.prevent>
|
<div class="model-select" @click.prevent>
|
||||||
<!-- <BulbOutlined /> -->
|
<div class="model-select-content">
|
||||||
|
<div class="model-info">
|
||||||
<a-tooltip :title="model_name" placement="right">
|
<a-tooltip :title="model_name" placement="right">
|
||||||
<span class="model-text text"> {{ model_name }} </span>
|
<span class="model-text text"> {{ model_name }} </span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<span class="text" style="color: #aaa;">{{ model_provider }} </span>
|
<span class="model-provider">{{ model_provider }}</span>
|
||||||
</a>
|
</div>
|
||||||
|
<div class="model-status-controls">
|
||||||
|
<span
|
||||||
|
v-if="currentModelStatus"
|
||||||
|
class="model-status-indicator"
|
||||||
|
:class="currentModelStatus.status"
|
||||||
|
:title="getCurrentModelStatusTooltip()"
|
||||||
|
>
|
||||||
|
{{ modelStatusIcon }}
|
||||||
|
</span>
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
:loading="state.checkingStatus"
|
||||||
|
@click.stop="checkCurrentModelStatus"
|
||||||
|
:disabled="state.checkingStatus"
|
||||||
|
class="status-check-button"
|
||||||
|
>
|
||||||
|
{{ state.checkingStatus ? '检查中...' : '检查' }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu class="scrollable-menu">
|
<a-menu class="scrollable-menu">
|
||||||
<a-menu-item-group v-for="(item, key) in modelKeys" :key="key" :title="modelNames[item]?.name">
|
<a-menu-item-group v-for="(item, key) in modelKeys" :key="key" :title="modelNames[item]?.name">
|
||||||
@ -25,9 +48,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
import { BulbOutlined } from '@ant-design/icons-vue'
|
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { chatModelApi } from '@/apis/system_api'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
model_name: {
|
model_name: {
|
||||||
@ -43,6 +66,12 @@ const props = defineProps({
|
|||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const emit = defineEmits(['select-model'])
|
const emit = defineEmits(['select-model'])
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const state = reactive({
|
||||||
|
currentModelStatus: null, // 当前模型状态
|
||||||
|
checkingStatus: false // 是否正在检查状态
|
||||||
|
})
|
||||||
|
|
||||||
// 从configStore中获取所需数据
|
// 从configStore中获取所需数据
|
||||||
const modelNames = computed(() => configStore.config?.model_names)
|
const modelNames = computed(() => configStore.config?.model_names)
|
||||||
const modelStatus = computed(() => configStore.config?.model_provider_status)
|
const modelStatus = computed(() => configStore.config?.model_provider_status)
|
||||||
@ -53,13 +82,78 @@ const modelKeys = computed(() => {
|
|||||||
return Object.keys(modelStatus.value || {}).filter(key => modelStatus.value?.[key])
|
return Object.keys(modelStatus.value || {}).filter(key => modelStatus.value?.[key])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 当前模型状态
|
||||||
|
const currentModelStatus = computed(() => {
|
||||||
|
return state.currentModelStatus
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查当前模型状态
|
||||||
|
const checkCurrentModelStatus = async () => {
|
||||||
|
if (!props.model_provider || !props.model_name) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
state.checkingStatus = true
|
||||||
|
const response = await chatModelApi.getModelStatus(props.model_provider, props.model_name)
|
||||||
|
if (response.status) {
|
||||||
|
state.currentModelStatus = response.status
|
||||||
|
} else {
|
||||||
|
state.currentModelStatus = null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`检查当前模型 ${props.model_provider}/${props.model_name} 状态失败:`, error)
|
||||||
|
state.currentModelStatus = { status: 'error', message: error.message }
|
||||||
|
} finally {
|
||||||
|
state.checkingStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelStatusIcon = computed(() => {
|
||||||
|
const status = currentModelStatus.value
|
||||||
|
if (!status) return '○'
|
||||||
|
if (status.status === 'available') return '✓'
|
||||||
|
if (status.status === 'unavailable') return '✗'
|
||||||
|
if (status.status === 'error') return '⚠'
|
||||||
|
return '○'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 获取当前模型状态提示文本
|
||||||
|
const getCurrentModelStatusTooltip = () => {
|
||||||
|
const status = currentModelStatus.value
|
||||||
|
if (!status) return '状态未知'
|
||||||
|
|
||||||
|
let statusText = ''
|
||||||
|
if (status.status === 'available') statusText = '可用'
|
||||||
|
else if (status.status === 'unavailable') statusText = '不可用'
|
||||||
|
else if (status.status === 'error') statusText = '错误'
|
||||||
|
|
||||||
|
const message = status.message || '无详细信息'
|
||||||
|
return `${statusText}: ${message}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 选择模型的方法
|
// 选择模型的方法
|
||||||
const handleSelectModel = (provider, name) => {
|
const handleSelectModel = async (provider, name) => {
|
||||||
emit('select-model', { provider, name })
|
emit('select-model', { provider, name })
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 变量定义
|
||||||
|
@status-success: #52c41a;
|
||||||
|
@status-error: #ff4d4f;
|
||||||
|
@status-warning: #faad14;
|
||||||
|
@status-default: #999;
|
||||||
|
@border-radius: 8px;
|
||||||
|
@scrollbar-width: 6px;
|
||||||
|
@status-indicator-padding: 2px 4px;
|
||||||
|
@status-check-button-padding: 0 4px;
|
||||||
|
@status-check-button-font-size: 12px;
|
||||||
|
@status-indicator-font-size: 11px;
|
||||||
|
@model-provider-color: #aaa;
|
||||||
|
|
||||||
|
// 主选择器样式
|
||||||
.model-select {
|
.model-select {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -67,12 +161,14 @@ const handleSelectModel = (provider, name) => {
|
|||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid var(--gray-200);
|
border: 1px solid var(--gray-200);
|
||||||
border-radius: 8px;
|
border-radius: @border-radius;
|
||||||
|
background-color: white;
|
||||||
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
background-color: white;
|
|
||||||
|
|
||||||
|
// 修饰符类
|
||||||
&.borderless {
|
&.borderless {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@ -81,21 +177,79 @@ const handleSelectModel = (provider, name) => {
|
|||||||
max-width: 380px;
|
max-width: 380px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
.model-select-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// 模型信息区域
|
||||||
|
.model-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.model-text {
|
.model-text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: #000;
|
color: #000;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.model-provider {
|
||||||
|
color: @model-provider-color;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 状态控制区域
|
||||||
|
.model-status-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
// 状态指示器
|
||||||
|
.model-status-indicator {
|
||||||
|
font-size: @status-indicator-font-size;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: @status-indicator-padding;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
// 状态样式修饰符
|
||||||
|
&.available {
|
||||||
|
color: @status-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unavailable {
|
||||||
|
color: @status-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: @status-warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查按钮
|
||||||
|
.status-check-button {
|
||||||
|
font-size: @status-check-button-font-size;
|
||||||
|
padding: @status-check-button-padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动菜单样式
|
||||||
.scrollable-menu {
|
.scrollable-menu {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
// 自定义滚动条样式
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: @scrollbar-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
@ -106,12 +260,12 @@ const handleSelectModel = (provider, name) => {
|
|||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: var(--gray-400);
|
background: var(--gray-400);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
&:hover {
|
||||||
background: var(--gray-500);
|
background: var(--gray-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@ -26,20 +26,50 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="card card-select">
|
<div class="card card-select">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
<span class="label">{{ items?.embed_model.des }}</span>
|
<span class="label">{{ items?.embed_model.des }}</span>
|
||||||
<a-select style="width: 300px"
|
<!-- <a-button
|
||||||
|
size="small"
|
||||||
|
:loading="state.checkingStatus"
|
||||||
|
@click="checkAllModelStatus"
|
||||||
|
:disabled="state.checkingStatus"
|
||||||
|
>
|
||||||
|
检查状态
|
||||||
|
</a-button> -->
|
||||||
|
</div>
|
||||||
|
<a-select style="width: 320px"
|
||||||
:value="configStore.config?.embed_model"
|
:value="configStore.config?.embed_model"
|
||||||
@change="handleChange('embed_model', $event)"
|
@change="handleChange('embed_model', $event)"
|
||||||
|
@dropdownVisibleChange="checkAllModelStatus"
|
||||||
>
|
>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="(name, idx) in items?.embed_model.choices" :key="idx"
|
v-for="(name, idx) in items?.embed_model.choices" :key="idx"
|
||||||
:value="name">{{ name }}
|
:value="name"
|
||||||
|
>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px; min-width: 0;">
|
||||||
|
<span style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
:style="{
|
||||||
|
color: getModelStatusColor(name),
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
flexShrink: 0,
|
||||||
|
padding: '2px 4px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
}"
|
||||||
|
:title="getModelStatusTooltip(name)"
|
||||||
|
>
|
||||||
|
{{ getModelStatusIcon(name) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="card card-select">
|
<div class="card card-select">
|
||||||
<span class="label">{{ items?.reranker.des }}</span>
|
<span class="label">{{ items?.reranker.des }}</span>
|
||||||
<a-select style="width: 300px"
|
<a-select style="width: 320px"
|
||||||
:value="configStore.config?.reranker"
|
:value="configStore.config?.reranker"
|
||||||
@change="handleChange('reranker', $event)"
|
@change="handleChange('reranker', $event)"
|
||||||
>
|
>
|
||||||
@ -156,6 +186,7 @@ import ModelProvidersComponent from '@/components/ModelProvidersComponent.vue';
|
|||||||
import UserManagementComponent from '@/components/UserManagementComponent.vue';
|
import UserManagementComponent from '@/components/UserManagementComponent.vue';
|
||||||
import { notification, Button } from 'ant-design-vue';
|
import { notification, Button } from 'ant-design-vue';
|
||||||
import { configApi } from '@/apis/system_api'
|
import { configApi } from '@/apis/system_api'
|
||||||
|
import { embeddingApi } from '@/apis/knowledge_api'
|
||||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||||
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
@ -165,7 +196,9 @@ const isNeedRestart = ref(false)
|
|||||||
const state = reactive({
|
const state = reactive({
|
||||||
loading: false,
|
loading: false,
|
||||||
section: 'base',
|
section: 'base',
|
||||||
windowWidth: window?.innerWidth || 0
|
windowWidth: window?.innerWidth || 0,
|
||||||
|
modelStatuses: {}, // 存储embedding模型状态
|
||||||
|
checkingStatus: false // 是否正在检查状态
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleModelLocalPathsUpdate = (config) => {
|
const handleModelLocalPathsUpdate = (config) => {
|
||||||
@ -225,6 +258,8 @@ onMounted(() => {
|
|||||||
updateWindowWidth()
|
updateWindowWidth()
|
||||||
window.addEventListener('resize', updateWindowWidth)
|
window.addEventListener('resize', updateWindowWidth)
|
||||||
state.section = userStore.isSuperAdmin ? 'base' : 'user'
|
state.section = userStore.isSuperAdmin ? 'base' : 'user'
|
||||||
|
|
||||||
|
checkAllModelStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -249,6 +284,56 @@ const sendRestart = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 检查所有embedding模型状态
|
||||||
|
const checkAllModelStatus = async () => {
|
||||||
|
try {
|
||||||
|
state.checkingStatus = true
|
||||||
|
const response = await embeddingApi.getAllModelsStatus()
|
||||||
|
if (response.status.models) {
|
||||||
|
state.modelStatuses = response.status.models
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('检查所有模型状态失败:', error)
|
||||||
|
message.error('获取模型状态失败')
|
||||||
|
} finally {
|
||||||
|
state.checkingStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取模型状态图标
|
||||||
|
const getModelStatusIcon = (modelId) => {
|
||||||
|
const status = state.modelStatuses[modelId]
|
||||||
|
if (!status) return '○'
|
||||||
|
if (status.status === 'available') return '✓'
|
||||||
|
if (status.status === 'unavailable') return '✗'
|
||||||
|
if (status.status === 'error') return '⚠'
|
||||||
|
return '○'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取模型状态颜色
|
||||||
|
const getModelStatusColor = (modelId) => {
|
||||||
|
const status = state.modelStatuses[modelId]
|
||||||
|
if (!status) return '#999'
|
||||||
|
if (status.status === 'available') return '#52c41a'
|
||||||
|
if (status.status === 'unavailable') return '#ff4d4f'
|
||||||
|
if (status.status === 'error') return '#faad14'
|
||||||
|
return '#999'
|
||||||
|
}
|
||||||
|
// 获取模型状态提示文本
|
||||||
|
const getModelStatusTooltip = (modelId) => {
|
||||||
|
const status = state.modelStatuses[modelId]
|
||||||
|
if (!status) return '状态未知'
|
||||||
|
|
||||||
|
let statusText = ''
|
||||||
|
if (status.status === 'available') statusText = '可用'
|
||||||
|
else if (status.status === 'unavailable') statusText = '不可用'
|
||||||
|
else if (status.status === 'error') statusText = '错误'
|
||||||
|
|
||||||
|
const message = status.message || '无详细信息'
|
||||||
|
return `${statusText}: ${message}`
|
||||||
|
}
|
||||||
|
|
||||||
const openLink = (url) => {
|
const openLink = (url) => {
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user