fix: 修复token验证问题,租户管理API存在401未授权问题需要进一步排查
This commit is contained in:
parent
802c57003c
commit
b5fa1b0d63
198
api/app.py
198
api/app.py
@ -1,9 +1,10 @@
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Request, Body, Response
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Request, Body, Response, UploadFile, File
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from models import AccountCreate, AccountResponse, PasswordChange, TenantCreate, TenantResponse
|
||||
from models import AccountCreate, AccountResponse, PasswordChange, TenantCreate, TenantResponse, ModelConfig
|
||||
from model_manager import ModelManager
|
||||
from account_manager import AccountManager as DifyAccountManager
|
||||
from backend_account_manager import BackendAccountManager
|
||||
|
||||
@ -48,11 +49,12 @@ except Exception as e:
|
||||
# 添加CORS中间件
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:3001", "http://127.0.0.1:3001"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
expose_headers=["*"]
|
||||
expose_headers=["*"],
|
||||
max_age=600
|
||||
)
|
||||
api_auth = APIAuthManager()
|
||||
op_logger = OperationLogger()
|
||||
@ -111,6 +113,9 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
# 打印接收到的token用于调试
|
||||
logger.info(f"Received token: {token[:10]}...{token[-10:]}")
|
||||
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
@ -122,11 +127,18 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
if not user:
|
||||
raise credentials_exception
|
||||
|
||||
logger.info(f"Authenticated user: {username}")
|
||||
return {
|
||||
"id": str(user.get("id")),
|
||||
"username": user.get("username"),
|
||||
"email": user.get("email", "")
|
||||
}
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token已过期",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
@ -176,16 +188,27 @@ async def auth_register(request: Request):
|
||||
)
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def auth_login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
async def auth_login(request: Request):
|
||||
"""用户登录(auth)"""
|
||||
try:
|
||||
data = await request.json()
|
||||
username = data.get("username")
|
||||
password = data.get("password")
|
||||
|
||||
if not username or not password:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="缺少用户名或密码"
|
||||
)
|
||||
|
||||
client_ip = request.client.host if request.client else "unknown"
|
||||
user = backend_account_manager.get_user_by_username(form_data.username)
|
||||
if not user or not backend_account_manager.verify_password(form_data.password, user["password"], user["password_salt"]):
|
||||
user = backend_account_manager.get_user_by_username(username)
|
||||
if not user or not backend_account_manager.verify_password(password, user["password"], user["password_salt"]):
|
||||
op_logger.log_operation(
|
||||
user_id=0,
|
||||
operation_type="LOGIN_ATTEMPT",
|
||||
endpoint="/api/auth/login",
|
||||
parameters=f"username={form_data.username}, ip={client_ip}",
|
||||
parameters=f"username={username}, ip={client_ip}",
|
||||
status="FAILED"
|
||||
)
|
||||
raise HTTPException(
|
||||
@ -193,6 +216,7 @@ async def auth_login(request: Request, form_data: OAuth2PasswordRequestForm = De
|
||||
detail="用户名或密码错误",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user["username"]},
|
||||
@ -206,11 +230,17 @@ async def auth_login(request: Request, form_data: OAuth2PasswordRequestForm = De
|
||||
status="SUCCESS"
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
except Exception as e:
|
||||
logger.error(f"登录失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="登录失败"
|
||||
)
|
||||
|
||||
@app.post("/api/user/login")
|
||||
async def user_login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
async def user_login(request: Request):
|
||||
"""用户登录(user)"""
|
||||
return await auth_login(request, form_data)
|
||||
return await auth_login(request)
|
||||
|
||||
@app.post("/api/auth/refresh")
|
||||
async def refresh_token(current_user: dict = Depends(get_current_user)):
|
||||
@ -438,6 +468,154 @@ async def get_tenant(name: str, current_user: dict = Depends(get_current_user)):
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="查询租户失败")
|
||||
|
||||
# 模型管理路由组
|
||||
@app.post("/api/models/upload")
|
||||
async def upload_model_config(
|
||||
file: UploadFile = File(...),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""上传模型配置文件"""
|
||||
try:
|
||||
# 检查是否为admin用户
|
||||
if current_user["username"] != "admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要admin用户权限"
|
||||
)
|
||||
|
||||
# 读取上传的文件内容
|
||||
contents = await file.read()
|
||||
config = json.loads(contents)
|
||||
|
||||
# 验证模型配置
|
||||
required_fields = ["model_name", "provider_name", "model_type", "api_key"]
|
||||
for field in required_fields:
|
||||
if field not in config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=f"模型配置缺少必要字段: {field}"
|
||||
)
|
||||
|
||||
return {"message": "模型配置上传成功", "config": config}
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="无效的JSON格式"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"上传模型配置失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="上传模型配置失败"
|
||||
)
|
||||
|
||||
@app.post("/api/models/assign")
|
||||
async def assign_model_to_tenant(
|
||||
model_config: ModelConfig,
|
||||
tenant_id: Optional[str] = None,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""分配模型给租户"""
|
||||
try:
|
||||
# 检查是否为admin用户
|
||||
if current_user["username"] != "admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要admin用户权限"
|
||||
)
|
||||
|
||||
# 如果未指定租户ID,则为所有租户添加模型
|
||||
if not tenant_id:
|
||||
total_added = ModelManager.add_models_for_all_tenants()
|
||||
return {"message": f"模型已成功分配给所有租户,共{total_added}个"}
|
||||
|
||||
# 为指定租户添加模型
|
||||
tenant = TenantManager.get_tenant_by_id(tenant_id)
|
||||
if not tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="租户不存在"
|
||||
)
|
||||
|
||||
ModelManager.add_model_for_tenant(tenant_id, tenant["encrypt_public_key"], model_config.dict())
|
||||
return {"message": "模型已成功分配给租户"}
|
||||
except Exception as e:
|
||||
logger.error(f"分配模型失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="分配模型失败"
|
||||
)
|
||||
|
||||
@app.get("/api/models/{tenant_id}")
|
||||
async def get_tenant_models(
|
||||
tenant_id: str,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取租户的模型列表"""
|
||||
try:
|
||||
models = ModelManager.get_tenant_models(tenant_id)
|
||||
return {
|
||||
"tenant_id": tenant_id,
|
||||
"models": models
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取租户模型失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="获取租户模型失败"
|
||||
)
|
||||
|
||||
@app.delete("/api/models/{model_id}")
|
||||
async def delete_model(
|
||||
model_id: str,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""删除特定模型"""
|
||||
try:
|
||||
# 检查是否为admin用户
|
||||
if current_user["username"] != "admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要admin用户权限"
|
||||
)
|
||||
|
||||
rows_affected = ModelManager.delete_model(model_id)
|
||||
if rows_affected == 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="模型不存在"
|
||||
)
|
||||
return {"message": "模型删除成功"}
|
||||
except Exception as e:
|
||||
logger.error(f"删除模型失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="删除模型失败"
|
||||
)
|
||||
|
||||
@app.delete("/api/models/all/{model_name}")
|
||||
async def delete_model_for_all_tenants(
|
||||
model_name: str,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""删除所有租户的特定模型"""
|
||||
try:
|
||||
# 检查管理员权限
|
||||
if current_user["username"] != "admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限"
|
||||
)
|
||||
|
||||
total_deleted = ModelManager.delete_specific_model_for_all_tenants(model_name)
|
||||
return {"message": f"模型已从所有租户中删除,共{total_deleted}个"}
|
||||
except Exception as e:
|
||||
logger.error(f"批量删除模型失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="批量删除模型失败"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8001)
|
||||
|
||||
@ -14,31 +14,67 @@ class ModelManager:
|
||||
"""模型管理类"""
|
||||
|
||||
@staticmethod
|
||||
def load_config(config_path):
|
||||
"""加载模型配置文件"""
|
||||
def add_volc_models_for_tenant(tenant_name, config_path=CONFIG_PATHS['volc_model_config']):
|
||||
"""为指定租户添加火山模型"""
|
||||
try:
|
||||
if not os.path.exists(config_path):
|
||||
raise FileNotFoundError(f"配置文件 {config_path} 不存在!")
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config
|
||||
# 加载模型配置
|
||||
config = ModelManager.load_config(config_path)
|
||||
models = config.get("models", [])
|
||||
|
||||
# 获取租户信息
|
||||
tenant = TenantManager.get_tenant_by_name(tenant_name)
|
||||
if not tenant:
|
||||
logger.error(f"未找到租户: {tenant_name}")
|
||||
return 0
|
||||
|
||||
# 为租户添加模型
|
||||
total_added = 0
|
||||
for model_config in models:
|
||||
if ModelManager.add_volc_model_for_tenant(tenant['id'], tenant['encrypt_public_key'], model_config):
|
||||
total_added += 1
|
||||
|
||||
logger.info(f"为租户 {tenant_name} 添加火山模型完成,共添加 {total_added} 条记录。")
|
||||
return total_added
|
||||
except Exception as e:
|
||||
logger.error(f"加载配置文件失败: {e}")
|
||||
logger.error(f"为租户 {tenant_name} 添加火山模型失败: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def check_model_exists(tenant_id, model_name):
|
||||
"""检查指定租户是否已存在指定的模型"""
|
||||
def get_tenant_models(tenant_id: str):
|
||||
"""获取租户的模型列表"""
|
||||
try:
|
||||
query = """
|
||||
SELECT COUNT(*) FROM provider_models
|
||||
WHERE tenant_id = %s AND model_name = %s;
|
||||
SELECT
|
||||
id,
|
||||
provider_name,
|
||||
model_name,
|
||||
model_type,
|
||||
encrypted_config,
|
||||
is_valid,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM provider_models
|
||||
WHERE tenant_id = %s
|
||||
ORDER BY created_at DESC;
|
||||
"""
|
||||
count = execute_query(query, (tenant_id, model_name), fetch_one=True)[0]
|
||||
return count > 0
|
||||
with get_db_cursor() as cursor:
|
||||
cursor.execute(query, (tenant_id,))
|
||||
models = cursor.fetchall()
|
||||
|
||||
return [{
|
||||
"id": str(model[0]),
|
||||
"provider_name": model[1],
|
||||
"model_name": model[2],
|
||||
"model_type": model[3],
|
||||
"encrypted_config": json.loads(model[4]),
|
||||
"is_valid": model[5],
|
||||
"created_at": model[6],
|
||||
"updated_at": model[7]
|
||||
} for model in models]
|
||||
except Exception as e:
|
||||
logger.error(f"检查模型是否存在时发生错误: {e}")
|
||||
return False
|
||||
logger.error(f"获取租户模型失败: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@staticmethod
|
||||
def add_model_for_tenant(tenant_id, public_key_pem, model_config):
|
||||
|
||||
@ -1,7 +1,32 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
class ModelConfig(BaseModel):
|
||||
"""模型配置上传模型"""
|
||||
model_name: str
|
||||
provider_name: str
|
||||
model_type: str
|
||||
api_key: str
|
||||
endpoint_url: Optional[str] = None
|
||||
display_name: Optional[str] = None
|
||||
context_size: Optional[int] = None
|
||||
max_tokens_to_sample: Optional[int] = None
|
||||
|
||||
class ModelResponse(BaseModel):
|
||||
"""模型响应模型"""
|
||||
id: str
|
||||
model_name: str
|
||||
provider_name: str
|
||||
model_type: str
|
||||
created_at: datetime
|
||||
|
||||
class TenantModelResponse(BaseModel):
|
||||
"""租户模型响应模型"""
|
||||
tenant_id: str
|
||||
tenant_name: str
|
||||
models: List[ModelResponse]
|
||||
|
||||
class AccountCreate(BaseModel):
|
||||
"""创建账户请求模型"""
|
||||
username: str
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { request } from '../../axios/service'
|
||||
import type { LoginParams, RegisterParams, LoginForm } from '@/api/auth/types'
|
||||
|
||||
export const login = (formData: FormData | LoginForm) =>
|
||||
export const login = (data: { username: string; password: string }) =>
|
||||
request<{ access_token: string }>({
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
data: formData,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
50
web/src/api/model/index.ts
Normal file
50
web/src/api/model/index.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { request } from '@/axios/service'
|
||||
import type { ApiResponse, ModelConfig, ModelResponse, TenantModelResponse } from './types.ts'
|
||||
|
||||
export function uploadModelConfig(file: File): Promise<ApiResponse<{config: any}>> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request({
|
||||
url: '/api/models/upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function assignModelToTenant(
|
||||
modelConfig: ModelConfig,
|
||||
tenantId?: string
|
||||
): Promise<ApiResponse<{message: string}>> {
|
||||
return request({
|
||||
url: '/api/models/assign',
|
||||
method: 'post',
|
||||
data: {
|
||||
model_config: modelConfig,
|
||||
tenant_id: tenantId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getTenantModels(tenantId: string): Promise<ApiResponse<TenantModelResponse>> {
|
||||
return request({
|
||||
url: `/api/models/${tenantId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteModel(modelId: string): Promise<ApiResponse<{message: string}>> {
|
||||
return request({
|
||||
url: `/api/models/${modelId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteModelForAllTenants(modelName: string): Promise<ApiResponse<{message: string}>> {
|
||||
return request({
|
||||
url: `/api/models/all/${modelName}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
30
web/src/api/model/types.ts
Normal file
30
web/src/api/model/types.ts
Normal file
@ -0,0 +1,30 @@
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number
|
||||
message: string
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface ModelConfig {
|
||||
model_name: string
|
||||
provider_name: string
|
||||
model_type: string
|
||||
api_key: string
|
||||
endpoint_url?: string
|
||||
display_name?: string
|
||||
context_size?: number
|
||||
max_tokens_to_sample?: number
|
||||
}
|
||||
|
||||
export interface ModelResponse {
|
||||
id: string
|
||||
model_name: string
|
||||
provider_name: string
|
||||
model_type: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface TenantModelResponse {
|
||||
tenant_id: string
|
||||
tenant_name: string
|
||||
models: ModelResponse[]
|
||||
}
|
||||
@ -10,8 +10,15 @@ export interface TenantForm {
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface TenantListParams {
|
||||
search?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export interface TenantListResponse {
|
||||
tenants: TenantItem[]
|
||||
total?: number
|
||||
}
|
||||
|
||||
export interface TenantDetailResponse {
|
||||
|
||||
@ -7,10 +7,12 @@ const service = createAxios()
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.token) {
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`
|
||||
const token = localStorage.getItem('access_token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
config.headers['Content-Type'] = 'application/json'
|
||||
config.withCredentials = true
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
|
||||
@ -32,7 +32,8 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: 'model',
|
||||
component: () => import('../views/Model/index.vue')
|
||||
component: () => import('../views/Model/index.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -77,15 +77,15 @@ const handleLogin = async () => {
|
||||
loading.value = true
|
||||
await loginFormRef.value.validate()
|
||||
const encryptedPassword = await encryptPassword(loginForm.value.password)
|
||||
const formData = new FormData()
|
||||
formData.append('username', loginForm.value.username)
|
||||
formData.append('password', encryptedPassword)
|
||||
|
||||
const res = await login(formData as LoginFormData)
|
||||
const res = await login({
|
||||
username: loginForm.value.username,
|
||||
password: encryptedPassword
|
||||
})
|
||||
console.log('登录成功:', res)
|
||||
|
||||
// 确保token存储完成后再跳转
|
||||
const token = res.access_token || res.token
|
||||
const token = res.access_token
|
||||
setToken(token)
|
||||
localStorage.setItem('token', token)
|
||||
setupTokenRefresh()
|
||||
|
||||
@ -1,16 +1,249 @@
|
||||
<template>
|
||||
<div class="model-container">
|
||||
<h1>模型管理</h1>
|
||||
<!-- 模型列表和配置表单将在这里实现 -->
|
||||
|
||||
<el-card class="upload-card">
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
accept=".json"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽模型配置文件到此处或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
请上传JSON格式的模型配置文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="uploadModel"
|
||||
:disabled="!selectedFile"
|
||||
>
|
||||
上传模型
|
||||
</el-button>
|
||||
</el-card>
|
||||
|
||||
<el-card class="tenant-search-card">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="租户查询">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="输入租户名称或ID"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="searchTenants">
|
||||
查询
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="tenant-list-card" v-if="searchedTenants.length > 0">
|
||||
<el-table :data="searchedTenants" style="width: 100%">
|
||||
<el-table-column prop="name" label="租户名称" />
|
||||
<el-table-column prop="id" label="租户ID" />
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="showTenantModels(scope.row.id)"
|
||||
>
|
||||
查询模型
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="modelDialogVisible" title="租户模型信息">
|
||||
<el-table :data="models" style="width: 100%">
|
||||
<el-table-column prop="model_name" label="模型名称" />
|
||||
<el-table-column prop="provider_name" label="提供商" />
|
||||
<el-table-column prop="model_type" label="模型类型" />
|
||||
<el-table-column prop="created_at" label="创建时间" />
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="assignDialogVisible" title="分配模型">
|
||||
<el-form :model="assignForm">
|
||||
<el-form-item label="选择租户">
|
||||
<el-select v-model="assignForm.tenantId" placeholder="请选择租户">
|
||||
<el-option
|
||||
v-for="tenant in tenants"
|
||||
:key="tenant.id"
|
||||
:label="tenant.name"
|
||||
:value="tenant.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="assignDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmAssign">
|
||||
确认分配
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// 模型管理逻辑将在这里实现
|
||||
import { ref } from 'vue'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
uploadModelConfig,
|
||||
assignModelToTenant,
|
||||
getTenantModels,
|
||||
deleteModel as deleteModelApi
|
||||
} from '@/api/model'
|
||||
import { fetchTenants } from '@/api/tenant'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { ModelResponse } from '@/api/model/types'
|
||||
|
||||
interface Tenant {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
const selectedFile = ref<File | null>(null)
|
||||
const models = ref<ModelResponse[]>([])
|
||||
const tenants = ref<Tenant[]>([])
|
||||
const searchedTenants = ref<Tenant[]>([])
|
||||
const searchQuery = ref('')
|
||||
const modelDialogVisible = ref(false)
|
||||
const assignDialogVisible = ref(false)
|
||||
const currentModel = ref<ModelResponse | null>(null)
|
||||
const assignForm = ref({
|
||||
tenantId: ''
|
||||
})
|
||||
|
||||
const searchTenants = async () => {
|
||||
try {
|
||||
const res = await fetchTenants({ search: searchQuery.value })
|
||||
searchedTenants.value = res.tenants
|
||||
} catch (error) {
|
||||
ElMessage.error('查询租户失败')
|
||||
}
|
||||
}
|
||||
|
||||
const showTenantModels = async (tenantId: string) => {
|
||||
try {
|
||||
const res = await getTenantModels(tenantId)
|
||||
models.value = res.data.models
|
||||
modelDialogVisible.value = true
|
||||
} catch (error) {
|
||||
ElMessage.error('加载模型列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleTenantChange = async (tenantId: string) => {
|
||||
try {
|
||||
const res = await getTenantModels(tenantId)
|
||||
models.value = res.data.models
|
||||
} catch (error) {
|
||||
ElMessage.error('加载模型列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = (file: File) => {
|
||||
selectedFile.value = file
|
||||
}
|
||||
|
||||
const uploadModel = async () => {
|
||||
if (!selectedFile.value) return
|
||||
|
||||
try {
|
||||
const res = await uploadModelConfig(selectedFile.value)
|
||||
ElMessage.success(res.message)
|
||||
loadModels()
|
||||
selectedFile.value = null
|
||||
} catch (error) {
|
||||
ElMessage.error('上传失败')
|
||||
}
|
||||
}
|
||||
|
||||
const loadModels = async () => {
|
||||
try {
|
||||
// 从store获取当前租户ID
|
||||
const currentTenantId = localStorage.getItem('currentTenantId')
|
||||
if (!currentTenantId) {
|
||||
ElMessage.warning('请先选择租户')
|
||||
return
|
||||
}
|
||||
const res = await getTenantModels(currentTenantId)
|
||||
models.value = res.data.models
|
||||
} catch (error) {
|
||||
ElMessage.error('加载模型列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
const loadTenants = async () => {
|
||||
try {
|
||||
const res = await fetchTenants({})
|
||||
tenants.value = res.tenants
|
||||
} catch (error) {
|
||||
ElMessage.error('加载租户列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
const assignModel = (model: ModelResponse) => {
|
||||
currentModel.value = model
|
||||
assignDialogVisible.value = true
|
||||
}
|
||||
|
||||
const confirmAssign = async () => {
|
||||
if (!currentModel.value) return
|
||||
|
||||
try {
|
||||
await assignModelToTenant({
|
||||
model_name: currentModel.value.model_name,
|
||||
provider_name: currentModel.value.provider_name,
|
||||
model_type: currentModel.value.model_type,
|
||||
api_key: '' // 实际应用中应从安全存储获取
|
||||
}, assignForm.value.tenantId)
|
||||
ElMessage.success('分配成功')
|
||||
assignDialogVisible.value = false
|
||||
} catch (error) {
|
||||
ElMessage.error('分配失败')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteModel = async (modelId: string) => {
|
||||
try {
|
||||
await deleteModelApi(modelId)
|
||||
ElMessage.success('删除成功')
|
||||
loadModels()
|
||||
} catch (error) {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
loadModels()
|
||||
loadTenants()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.model-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.model-list-card {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user