feat(models): 添加嵌入和聊天模型状态检查接口 #282
- 在知识路由中新增获取指定嵌入模型和聊天模型状态的接口 - 实现获取所有嵌入模型和聊天模型状态的功能 - 更新前端API以支持模型状态检查,增强用户体验 - 优化日志记录格式,提升调试信息的可读性
This commit is contained in:
parent
a337cba3f7
commit
f1fe10f22c
25
AGENTS.md
25
AGENTS.md
@ -11,13 +11,15 @@ Yuxi-Know/
|
||||
├── server/ # 服务端代码(部分)
|
||||
├── src/ # 主要源代码目录
|
||||
│ ├── agents/ # 智能体应用
|
||||
│ ├── config/ # 配置文件
|
||||
│ ├── config/ # 配置文件
|
||||
│ ├── knowledge/ # 知识库相关
|
||||
│ ├── models/ # 数据模型
|
||||
│ ├── plugins/ # 插件(存放OCR)
|
||||
│ ├── static/ # 静态资源(配置文件)
|
||||
│ └── utils/ # 工具函数
|
||||
├── web/ # 前端代码
|
||||
├── web/src/ # 前端代码
|
||||
│ ├── apis/ # 前端的接口定义
|
||||
│ └── views/ # 主要 vue 文件
|
||||
└── 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).
|
||||
|
||||
## 风格说明
|
||||
前端开发规范:
|
||||
|
||||
- UI风格要简洁,同时要保持一致性,颜色要尽量参考 [base.css](web/src/assets/css/base.css) 中的颜色。不要悬停位移,不要过度使用阴影以及渐变色。
|
||||
- Python 代码要符合 Python 的规范,尽量使用较新的语法,避免使用旧版本的语法(版本兼容到 3.12+),使用 uvx ruff check 检查 lint。
|
||||
- API 接口规范:所有的 API 接口都应该定义在 web/src/apis 下面,并继承自 apiGet/apiPost/apiRequest
|
||||
- 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 src import config, knowledge_base
|
||||
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
|
||||
|
||||
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:
|
||||
logger.error(f"获取知识库统计失败 {e}, {traceback.format_exc()}")
|
||||
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.utils.auth_middleware import get_admin_user, get_superadmin_user
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
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
|
||||
|
||||
|
||||
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__":
|
||||
pass
|
||||
|
||||
@ -168,12 +168,89 @@ class OtherEmbedding(BaseEmbeddingModel):
|
||||
logger.error(f"Other Embedding async request failed: {e}, {payload}")
|
||||
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):
|
||||
provider, model_name = model_id.split("/", 1) if model_id else ("", "")
|
||||
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}"
|
||||
logger.debug(f"Loading embedding model {model_id}")
|
||||
logger.info(f"Loading embedding model {model_id}")
|
||||
if provider == "local":
|
||||
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(
|
||||
LOG_FILE,
|
||||
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",
|
||||
rotation="10 MB", # 文件大小达到 10MB 时轮转
|
||||
retention="30 days", # 保留30天的日志
|
||||
@ -35,7 +35,7 @@ def setup_logger(name, level="DEBUG", console=True):
|
||||
format=(
|
||||
"<green>{time:MM-DD HH:mm:ss}</green> "
|
||||
"<level>{level}</level> "
|
||||
"<cyan>{name}:{line}</cyan>: "
|
||||
"<cyan>{file}:{line}</cyan>: "
|
||||
"<level>{message}</level>"
|
||||
),
|
||||
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')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// === 聊天模型状态检查分组 ===
|
||||
// =============================================================================
|
||||
|
||||
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>
|
||||
<a-dropdown trigger="click">
|
||||
<a class="model-select" @click.prevent>
|
||||
<!-- <BulbOutlined /> -->
|
||||
<a-tooltip :title="model_name" placement="right">
|
||||
<span class="model-text text"> {{ model_name }} </span>
|
||||
</a-tooltip>
|
||||
<span class="text" style="color: #aaa;">{{ model_provider }} </span>
|
||||
</a>
|
||||
<div class="model-select" @click.prevent>
|
||||
<div class="model-select-content">
|
||||
<div class="model-info">
|
||||
<a-tooltip :title="model_name" placement="right">
|
||||
<span class="model-text text"> {{ model_name }} </span>
|
||||
</a-tooltip>
|
||||
<span class="model-provider">{{ model_provider }}</span>
|
||||
</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>
|
||||
<a-menu class="scrollable-menu">
|
||||
<a-menu-item-group v-for="(item, key) in modelKeys" :key="key" :title="modelNames[item]?.name">
|
||||
@ -25,9 +48,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { BulbOutlined } from '@ant-design/icons-vue'
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { chatModelApi } from '@/apis/system_api'
|
||||
|
||||
const props = defineProps({
|
||||
model_name: {
|
||||
@ -43,6 +66,12 @@ const props = defineProps({
|
||||
const configStore = useConfigStore()
|
||||
const emit = defineEmits(['select-model'])
|
||||
|
||||
// 状态管理
|
||||
const state = reactive({
|
||||
currentModelStatus: null, // 当前模型状态
|
||||
checkingStatus: false // 是否正在检查状态
|
||||
})
|
||||
|
||||
// 从configStore中获取所需数据
|
||||
const modelNames = computed(() => configStore.config?.model_names)
|
||||
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])
|
||||
})
|
||||
|
||||
// 当前模型状态
|
||||
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 })
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<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 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -67,12 +161,14 @@ const handleSelectModel = (provider, name) => {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--gray-200);
|
||||
border-radius: 8px;
|
||||
border-radius: @border-radius;
|
||||
background-color: white;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background-color: white;
|
||||
|
||||
// 修饰符类
|
||||
&.borderless {
|
||||
border: none;
|
||||
}
|
||||
@ -81,21 +177,79 @@ const handleSelectModel = (provider, name) => {
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.model-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #000;
|
||||
// 内容区域
|
||||
.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 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
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 {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
// 自定义滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
width: @scrollbar-width;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
@ -106,10 +260,10 @@ const handleSelectModel = (provider, name) => {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--gray-400);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--gray-500);
|
||||
&:hover {
|
||||
background: var(--gray-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -26,20 +26,50 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="card card-select">
|
||||
<span class="label">{{ items?.embed_model.des }}</span>
|
||||
<a-select style="width: 300px"
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span class="label">{{ items?.embed_model.des }}</span>
|
||||
<!-- <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"
|
||||
@change="handleChange('embed_model', $event)"
|
||||
@dropdownVisibleChange="checkAllModelStatus"
|
||||
>
|
||||
<a-select-option
|
||||
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>
|
||||
</div>
|
||||
<div class="card card-select">
|
||||
<span class="label">{{ items?.reranker.des }}</span>
|
||||
<a-select style="width: 300px"
|
||||
<a-select style="width: 320px"
|
||||
:value="configStore.config?.reranker"
|
||||
@change="handleChange('reranker', $event)"
|
||||
>
|
||||
@ -156,6 +186,7 @@ import ModelProvidersComponent from '@/components/ModelProvidersComponent.vue';
|
||||
import UserManagementComponent from '@/components/UserManagementComponent.vue';
|
||||
import { notification, Button } from 'ant-design-vue';
|
||||
import { configApi } from '@/apis/system_api'
|
||||
import { embeddingApi } from '@/apis/knowledge_api'
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
|
||||
const configStore = useConfigStore()
|
||||
@ -165,7 +196,9 @@ const isNeedRestart = ref(false)
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
section: 'base',
|
||||
windowWidth: window?.innerWidth || 0
|
||||
windowWidth: window?.innerWidth || 0,
|
||||
modelStatuses: {}, // 存储embedding模型状态
|
||||
checkingStatus: false // 是否正在检查状态
|
||||
})
|
||||
|
||||
const handleModelLocalPathsUpdate = (config) => {
|
||||
@ -225,6 +258,8 @@ onMounted(() => {
|
||||
updateWindowWidth()
|
||||
window.addEventListener('resize', updateWindowWidth)
|
||||
state.section = userStore.isSuperAdmin ? 'base' : 'user'
|
||||
|
||||
checkAllModelStatus()
|
||||
})
|
||||
|
||||
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) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user