继续优化接口,移除无关模块:(1)移除原本的 Token 管理模块;(2)所有 API 请求使用 /api 前缀;(3)移除重复的中间件验证;(4)前后端都移除工具模块;(5)移除原本的 safe_config 模块;(6)其他小细节优化
This commit is contained in:
parent
50ce0b3da1
commit
a454389f6e
@ -1,12 +1,10 @@
|
||||
import os
|
||||
import sqlite3
|
||||
import pathlib
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from server.models.token_model import Base, AgentToken
|
||||
from server.models.user_model import User, OperationLog
|
||||
from server.models import Base
|
||||
from server.models.user_model import User
|
||||
|
||||
class DBManager:
|
||||
"""数据库管理器"""
|
||||
|
||||
@ -6,13 +6,12 @@ from fastapi.responses import JSONResponse
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from server.routers import router
|
||||
from server.utils.auth_middleware import get_current_user, is_public_path, is_admin_path
|
||||
from server.models.user_model import User
|
||||
from server.utils.auth_middleware import is_public_path
|
||||
from src.utils.logging_config import logger
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.include_router(router, prefix="/api")
|
||||
|
||||
# CORS 设置
|
||||
app.add_middleware(
|
||||
@ -33,85 +32,24 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
||||
if is_public_path(path):
|
||||
return await call_next(request)
|
||||
|
||||
# 注意:前端代理已经去掉了 /api 前缀,例如 /api/chat 变成了 /chat
|
||||
# 判断是否需要验证的API请求,包括聊天、数据、工具等
|
||||
is_api_path = (
|
||||
path.startswith("/chat") or
|
||||
path.startswith("/data") or
|
||||
path.startswith("/admin") or
|
||||
path.startswith("/auth") and not is_public_path(path)
|
||||
)
|
||||
|
||||
if not is_api_path:
|
||||
if not path.startswith("/api"):
|
||||
# 非API路径,可能是前端路由或静态资源
|
||||
return await call_next(request)
|
||||
|
||||
# 提取Authorization头
|
||||
auth_header = request.headers.get("Authorization")
|
||||
if not auth_header or not auth_header.startswith("Bearer "):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
content={"detail": "请先登录"},
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
# # 提取Authorization头
|
||||
# auth_header = request.headers.get("Authorization")
|
||||
# if not auth_header or not auth_header.startswith("Bearer "):
|
||||
# return JSONResponse(
|
||||
# status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
# content={"detail": f"请先登录。Path: {path}"},
|
||||
# headers={"WWW-Authenticate": "Bearer"}
|
||||
# )
|
||||
|
||||
# 获取token
|
||||
token = auth_header.split("Bearer ")[1]
|
||||
# # 获取token
|
||||
# token = auth_header.split("Bearer ")[1]
|
||||
|
||||
# 添加token到请求状态,后续路由可以直接使用
|
||||
request.state.token = token
|
||||
|
||||
# 检查是否需要管理员权限
|
||||
if is_admin_path(path):
|
||||
# 尝试获取数据库会话
|
||||
try:
|
||||
from server.db_manager import db_manager
|
||||
from server.utils.auth_utils import AuthUtils
|
||||
|
||||
db = db_manager.get_session()
|
||||
try:
|
||||
# 验证token并获取用户信息
|
||||
payload = AuthUtils.verify_access_token(token)
|
||||
user_id = payload.get("sub")
|
||||
|
||||
if not user_id:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
content={"detail": "无效的用户标识"},
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
|
||||
# 查询用户信息
|
||||
from server.models.user_model import User
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
content={"detail": "用户不存在"},
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
|
||||
# 检查管理员权限
|
||||
if user.role not in ["admin", "superadmin"]:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
content={"detail": "需要管理员权限"}
|
||||
)
|
||||
|
||||
# 将用户信息添加到请求状态
|
||||
request.state.user = user
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"验证管理员权限出错: {e}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
content={"detail": "验证用户权限出错"},
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
# # 添加token到请求状态,后续路由可以直接使用
|
||||
# request.state.token = token
|
||||
|
||||
# 继续处理请求
|
||||
return await call_next(request)
|
||||
|
||||
3
server/models/__init__.py
Normal file
3
server/models/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
@ -1,24 +0,0 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class AgentToken(Base):
|
||||
"""智能体访问令牌模型"""
|
||||
__tablename__ = 'agent_tokens'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
agent_id = Column(String, nullable=False, index=True) # 智能体ID
|
||||
name = Column(String, nullable=False) # 令牌名称
|
||||
token = Column(String, nullable=False, unique=True) # 令牌值
|
||||
created_at = Column(DateTime, default=func.now()) # 创建时间
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"agent_id": self.agent_id,
|
||||
"name": self.name,
|
||||
"token": self.token,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
@ -1,9 +1,8 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from server.models.token_model import Base
|
||||
from server.models import Base
|
||||
|
||||
class User(Base):
|
||||
"""用户模型"""
|
||||
|
||||
@ -2,12 +2,10 @@ from fastapi import APIRouter
|
||||
from server.routers.chat_router import chat
|
||||
from server.routers.data_router import data
|
||||
from server.routers.base_router import base
|
||||
from server.routers.admin_router import admin
|
||||
from server.routers.auth_router import auth
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(base)
|
||||
router.include_router(chat)
|
||||
router.include_router(data)
|
||||
router.include_router(admin)
|
||||
router.include_router(auth)
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
import secrets
|
||||
import string
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from server.db_manager import db_manager
|
||||
from server.models.token_model import AgentToken
|
||||
from server.models.user_model import User, OperationLog
|
||||
from server.utils.auth_middleware import get_db, get_current_user, get_admin_user, oauth2_scheme
|
||||
from server.routers.auth_router import log_operation
|
||||
|
||||
admin = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
# 请求和响应模型
|
||||
class TokenCreate(BaseModel):
|
||||
agent_id: str
|
||||
name: str
|
||||
|
||||
class TokenVerify(BaseModel):
|
||||
agent_id: str
|
||||
token: str
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
id: int
|
||||
agent_id: str
|
||||
name: str
|
||||
token: str
|
||||
created_at: str
|
||||
|
||||
# 生成随机token
|
||||
def generate_token(length=32):
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
||||
|
||||
@admin.get("/tokens", response_model=List[TokenResponse])
|
||||
async def get_agent_tokens(
|
||||
agent_id: Optional[str] = Query(None),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取智能体的token列表(需要管理员权限)"""
|
||||
query = db.query(AgentToken)
|
||||
if agent_id:
|
||||
query = query.filter(AgentToken.agent_id == agent_id)
|
||||
tokens = query.all()
|
||||
return [token.to_dict() for token in tokens]
|
||||
|
||||
@admin.post("/tokens", response_model=TokenResponse)
|
||||
async def create_token(
|
||||
token_data: TokenCreate,
|
||||
request: Request,
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建新的token(需要管理员权限)"""
|
||||
# 生成随机token
|
||||
token_value = generate_token()
|
||||
|
||||
# 创建token记录
|
||||
new_token = AgentToken(
|
||||
agent_id=token_data.agent_id,
|
||||
name=token_data.name,
|
||||
token=token_value
|
||||
)
|
||||
|
||||
# 保存到数据库
|
||||
db.add(new_token)
|
||||
db.commit()
|
||||
db.refresh(new_token)
|
||||
|
||||
# 记录操作
|
||||
log_operation(
|
||||
db,
|
||||
current_user.id,
|
||||
"创建令牌",
|
||||
f"为智能体 {token_data.agent_id} 创建访问令牌: {token_data.name}",
|
||||
request
|
||||
)
|
||||
|
||||
return new_token.to_dict()
|
||||
|
||||
@admin.delete("/tokens/{token_id}", response_model=dict)
|
||||
async def delete_token(
|
||||
token_id: int,
|
||||
request: Request,
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除token(需要管理员权限)"""
|
||||
token = db.query(AgentToken).filter(AgentToken.id == token_id).first()
|
||||
if not token:
|
||||
raise HTTPException(status_code=404, detail="Token not found")
|
||||
|
||||
# 记录操作信息
|
||||
log_operation(
|
||||
db,
|
||||
current_user.id,
|
||||
"删除令牌",
|
||||
f"删除令牌ID: {token_id}, 智能体: {token.agent_id}, 名称: {token.name}",
|
||||
request
|
||||
)
|
||||
|
||||
db.delete(token)
|
||||
db.commit()
|
||||
|
||||
return {"success": True, "message": "Token deleted"}
|
||||
|
||||
@admin.post("/verify_token")
|
||||
async def verify_agent_token(
|
||||
token_data: TokenVerify,
|
||||
token: Optional[str] = Depends(oauth2_scheme),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""验证智能体访问令牌(所有用户都可访问)"""
|
||||
# 查找令牌
|
||||
agent_token = db.query(AgentToken).filter(
|
||||
AgentToken.agent_id == token_data.agent_id,
|
||||
AgentToken.token == token_data.token
|
||||
).first()
|
||||
|
||||
if not agent_token:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
return {"success": True, "message": "Token verified"}
|
||||
@ -1,4 +1,4 @@
|
||||
from fastapi import Request, Body
|
||||
from fastapi import Request, Body, Depends
|
||||
from fastapi import APIRouter
|
||||
from fastapi import Request, Body
|
||||
|
||||
@ -6,6 +6,8 @@ base = APIRouter()
|
||||
|
||||
from src import config, retriever, knowledge_base, graph_base
|
||||
from src.utils import logger
|
||||
from server.utils.auth_middleware import get_admin_user, get_superadmin_user
|
||||
from server.models.user_model import User
|
||||
|
||||
|
||||
@base.get("/")
|
||||
@ -13,27 +15,28 @@ async def route_index():
|
||||
return {"message": "You Got It!"}
|
||||
|
||||
@base.get("/config")
|
||||
def get_config():
|
||||
return config.get_safe_config()
|
||||
def get_config(current_user: User = Depends(get_admin_user)):
|
||||
return config.dump_config()
|
||||
|
||||
@base.post("/config")
|
||||
async def update_config(key = Body(...), value = Body(...)):
|
||||
if key == "custom_models":
|
||||
value = config.compare_custom_models(value)
|
||||
|
||||
async def update_config(
|
||||
key = Body(...),
|
||||
value = Body(...),
|
||||
current_user: User = Depends(get_admin_user)
|
||||
) -> dict:
|
||||
config[key] = value
|
||||
config.save()
|
||||
return config.get_safe_config()
|
||||
return config.dump_config()
|
||||
|
||||
@base.post("/restart")
|
||||
async def restart():
|
||||
async def restart(current_user: User = Depends(get_superadmin_user)):
|
||||
knowledge_base.restart()
|
||||
graph_base.start()
|
||||
retriever.restart()
|
||||
return {"message": "Restarted!"}
|
||||
|
||||
@base.get("/log")
|
||||
def get_log():
|
||||
def get_log(current_user: User = Depends(get_admin_user)):
|
||||
from src.utils.logging_config import LOG_FILE
|
||||
from collections import deque
|
||||
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
import os
|
||||
from fastapi import APIRouter, Body
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from src.agents import agent_manager
|
||||
|
||||
tool = APIRouter(prefix="/tool")
|
||||
|
||||
|
||||
class Tool(BaseModel):
|
||||
name: str
|
||||
title: str
|
||||
description: str
|
||||
url: str
|
||||
method: Optional[str] = "POST"
|
||||
params: Optional[Dict[str, Any]] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
@tool.get("/", response_model=List[Tool])
|
||||
async def route_index():
|
||||
tools = [
|
||||
Tool(
|
||||
name="text-chunking",
|
||||
title="文本分块",
|
||||
description="将文本分块以更好地理解。可以输入文本或者上传文件。",
|
||||
url="/tools/text-chunking",
|
||||
method="POST",
|
||||
),
|
||||
Tool(
|
||||
name="pdf2txt",
|
||||
title="PDF转文本",
|
||||
description="将PDF文件转换为文本文件。",
|
||||
url="/tools/pdf2txt",
|
||||
method="POST",
|
||||
),
|
||||
Tool(
|
||||
name="agent",
|
||||
title="智能体(Dev)",
|
||||
description="智能体演练平台,现在还处于开发预览状态,欢迎提 Issue,但先不要用于正式场景。",
|
||||
url="/tools/agent",
|
||||
)
|
||||
]
|
||||
|
||||
for agent in agent_manager.agents.values():
|
||||
tools.append(
|
||||
Tool(
|
||||
name=agent.name,
|
||||
title=agent.name,
|
||||
description=agent.description,
|
||||
url=f"/agent/{agent.name}",
|
||||
method="POST",
|
||||
metadata=agent.config_schema.to_dict(),
|
||||
)
|
||||
)
|
||||
|
||||
return tools
|
||||
|
||||
@tool.post("/text-chunking")
|
||||
async def text_chunking(text: str = Body(...), params: Dict[str, Any] = Body(...)):
|
||||
from src.core.indexing import chunk
|
||||
nodes = chunk(text, params=params)
|
||||
return {"nodes": [node.to_dict() for node in nodes]}
|
||||
|
||||
@tool.post("/pdf2txt")
|
||||
async def handle_pdf2txt(file: str = Body(...)):
|
||||
from src.plugins import ocr
|
||||
text = ocr.process_pdf(file)
|
||||
return {"text": text}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from typing import Optional, List
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from typing import Optional, List, Callable
|
||||
from fastapi import Depends, HTTPException, status, APIRouter
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.orm import Session
|
||||
from jose import JWTError, jwt
|
||||
@ -14,21 +14,11 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token", auto_error=Fals
|
||||
|
||||
# 公开路径列表,无需登录即可访问
|
||||
PUBLIC_PATHS = [
|
||||
r"^/auth/token$", # 登录
|
||||
r"^/auth/check-first-run$", # 检查是否首次运行
|
||||
r"^/auth/initialize$", # 初始化系统
|
||||
r"^/docs$", r"^/redoc$", r"^/openapi.json$", # API文档
|
||||
r"^/static/.*$", # 静态资源
|
||||
r"^/assets/.*$", # 前端资源文件
|
||||
r"^/$", # 根路径(登录页)
|
||||
r"^/login$", # 登录页面
|
||||
r"^/home$", # 首页
|
||||
r"^/home/.*$", # 首页下的所有路径
|
||||
r"^/favicon\.ico$", # 网站图标
|
||||
r"^/_nuxt/.*$", # Nuxt.js生成的资源文件
|
||||
r"^/js/.*$", # JavaScript文件
|
||||
r"^/css/.*$", # CSS文件
|
||||
r"^/img/.*$" # 图片文件
|
||||
r"^/api/auth/token$", # 登录
|
||||
r"^/api/auth/check-first-run$", # 检查是否首次运行
|
||||
r"^/api/auth/initialize$", # 初始化系统
|
||||
r"^/api$", # Health Check
|
||||
r"^/api/login$", # 登录页面
|
||||
]
|
||||
|
||||
# 获取数据库会话
|
||||
@ -110,35 +100,3 @@ def is_public_path(path: str) -> bool:
|
||||
if re.match(pattern, path):
|
||||
return True
|
||||
return False
|
||||
|
||||
# 路径是否需要管理员权限
|
||||
ADMIN_PATHS = [
|
||||
r"^/admin/.*$", # 管理员接口
|
||||
r"^/data/.*$", # 数据操作接口,所有数据操作都需要管理员权限
|
||||
r"^/chat/set_default_agent$", # 设置默认智能体
|
||||
r"^/chat/models/update$", # 更新模型列表
|
||||
r"^/auth/users.*$", # 用户管理相关API
|
||||
r"^/config$" # 系统配置API
|
||||
]
|
||||
|
||||
# 路径是否需要超级管理员权限
|
||||
SUPERADMIN_PATHS = [
|
||||
r"^/restart$", # 系统重启
|
||||
r"^/auth/users/\d+/role$" # 修改用户角色(仅超级管理员可操作)
|
||||
]
|
||||
|
||||
# 检查路径是否需要管理员权限
|
||||
def is_admin_path(path: str) -> bool:
|
||||
path = path.rstrip('/') # 去除尾部斜杠以便于匹配
|
||||
for pattern in ADMIN_PATHS:
|
||||
if re.match(pattern, path):
|
||||
return True
|
||||
return False
|
||||
|
||||
# 检查路径是否需要超级管理员权限
|
||||
def is_superadmin_path(path: str) -> bool:
|
||||
path = path.rstrip('/') # 去除尾部斜杠以便于匹配
|
||||
for pattern in SUPERADMIN_PATHS:
|
||||
if re.match(pattern, path):
|
||||
return True
|
||||
return False
|
||||
@ -212,18 +212,8 @@ class Config(SimpleConfig):
|
||||
|
||||
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 dump_config(self):
|
||||
return json.loads(str(self))
|
||||
|
||||
def compare_custom_models(self, value):
|
||||
"""
|
||||
|
||||
@ -70,40 +70,6 @@ export const userManagementApi = {
|
||||
},
|
||||
}
|
||||
|
||||
// 令牌管理API
|
||||
export const tokenApi = {
|
||||
/**
|
||||
* 获取令牌列表
|
||||
* @param {string} agentId - 智能体ID
|
||||
* @returns {Promise} - 令牌列表
|
||||
*/
|
||||
getTokens: async (agentId) => {
|
||||
checkAdminPermission()
|
||||
return apiGet(`/api/admin/tokens?agent_id=${agentId}`, {}, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建新令牌
|
||||
* @param {string} agentId - 智能体ID
|
||||
* @param {string} name - 令牌名称
|
||||
* @returns {Promise} - 创建结果
|
||||
*/
|
||||
createToken: async (agentId, name) => {
|
||||
checkAdminPermission()
|
||||
return apiPost('/api/admin/tokens', { agent_id: agentId, name }, {}, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除令牌
|
||||
* @param {string} tokenId - 令牌ID
|
||||
* @returns {Promise} - 删除结果
|
||||
*/
|
||||
deleteToken: async (tokenId) => {
|
||||
checkAdminPermission()
|
||||
return apiDelete(`/api/admin/tokens/${tokenId}`, {}, true)
|
||||
},
|
||||
}
|
||||
|
||||
// 知识库管理API
|
||||
export const knowledgeBaseApi = {
|
||||
/**
|
||||
|
||||
@ -1,232 +0,0 @@
|
||||
<template>
|
||||
<div class="token-manager">
|
||||
<div class="token-tools">
|
||||
<a-button type="primary" size="small" @click="showAddTokenModal">
|
||||
<PlusOutlined /> 创建 Token
|
||||
</a-button>
|
||||
</div>
|
||||
<!-- 令牌列表 -->
|
||||
<div class="token-list" v-if="tokens.length > 0">
|
||||
<a-spin :spinning="loading">
|
||||
<a-list size="small">
|
||||
<a-list-item v-for="token in tokens" :key="token.id">
|
||||
<div class="token-item">
|
||||
<div class="token-info">
|
||||
<div class="token-name">{{ token.name }}</div>
|
||||
<div class="token-value">
|
||||
<code>{{ token.token }}</code>
|
||||
<a-button type="link" size="small" @click="copyToken(token.token)">
|
||||
<CopyOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="token-time">创建时间: {{ formatDate(token.created_at) }}</div>
|
||||
</div>
|
||||
<div class="token-actions">
|
||||
<a-popconfirm
|
||||
title="确定要删除这个令牌吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="deleteToken(token.id)"
|
||||
>
|
||||
<a-button type="text" danger size="small">
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-spin>
|
||||
</div>
|
||||
<a-empty v-else description="暂无访问令牌" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||
|
||||
<!-- 添加令牌弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="addTokenModalVisible"
|
||||
title="添加访问令牌"
|
||||
ok-text="创建"
|
||||
cancel-text="取消"
|
||||
@ok="createToken"
|
||||
>
|
||||
<a-form :model="newToken" layout="vertical">
|
||||
<a-form-item label="令牌名称" name="name">
|
||||
<a-input v-model:value="newToken.name" placeholder="请输入令牌名称" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { message, Empty } from 'ant-design-vue';
|
||||
import { PlusOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons-vue';
|
||||
import { tokenApi } from '@/apis/admin_api';
|
||||
|
||||
const props = defineProps({
|
||||
agentId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 状态
|
||||
const tokens = ref([]);
|
||||
const loading = ref(false);
|
||||
const addTokenModalVisible = ref(false);
|
||||
const newToken = ref({
|
||||
name: ''
|
||||
});
|
||||
|
||||
// 获取令牌列表
|
||||
const fetchTokens = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await tokenApi.getTokens(props.agentId);
|
||||
tokens.value = data;
|
||||
} catch (error) {
|
||||
console.error('获取令牌列表出错:', error);
|
||||
message.error(error.message || '获取令牌列表出错');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 创建新令牌
|
||||
const createToken = async () => {
|
||||
if (!newToken.value.name.trim()) {
|
||||
message.warning('请输入令牌名称');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await tokenApi.createToken(props.agentId, newToken.value.name);
|
||||
tokens.value.push(data);
|
||||
message.success('令牌创建成功');
|
||||
addTokenModalVisible.value = false;
|
||||
newToken.value.name = '';
|
||||
} catch (error) {
|
||||
console.error('创建令牌出错:', error);
|
||||
message.error(error.message || '创建令牌出错');
|
||||
}
|
||||
};
|
||||
|
||||
// 删除令牌
|
||||
const deleteToken = async (tokenId) => {
|
||||
try {
|
||||
await tokenApi.deleteToken(tokenId);
|
||||
tokens.value = tokens.value.filter(token => token.id !== tokenId);
|
||||
message.success('令牌已删除');
|
||||
} catch (error) {
|
||||
console.error('删除令牌出错:', error);
|
||||
message.error(error.message || '删除令牌出错');
|
||||
}
|
||||
};
|
||||
|
||||
// 复制令牌到剪贴板
|
||||
const copyToken = (token) => {
|
||||
navigator.clipboard.writeText(token).then(() => {
|
||||
message.success('令牌已复制到剪贴板');
|
||||
});
|
||||
};
|
||||
|
||||
// 显示添加令牌弹窗
|
||||
const showAddTokenModal = () => {
|
||||
newToken.value.name = '';
|
||||
addTokenModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
// 监听agentId变化
|
||||
watch(() => props.agentId, (newAgentId) => {
|
||||
if (newAgentId) {
|
||||
fetchTokens();
|
||||
} else {
|
||||
tokens.value = [];
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 组件挂载时获取令牌列表
|
||||
onMounted(() => {
|
||||
if (props.agentId) {
|
||||
fetchTokens();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.token-manager {
|
||||
margin-top: 1rem;
|
||||
// padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.manager-title {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.token-tools {
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.token-list {
|
||||
max-height: calc(100vh - 400px);
|
||||
overflow-y: auto;
|
||||
li.ant-list-item {
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.token-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.token-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.token-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.token-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
background-color: var(--main-light-4);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.25rem;
|
||||
overflow-x: auto;
|
||||
user-select: all;
|
||||
|
||||
code {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.token-time {
|
||||
font-size: 0.75rem;
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
.token-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="user-info-dropdown" :data-align="showRole ? 'left' : 'center'">
|
||||
<div class="user-avatar">
|
||||
<UserOutlined />
|
||||
<div class="user-role-badge" :class="userRoleClass"></div>
|
||||
<!-- <div class="user-role-badge" :class="userRoleClass"></div> -->
|
||||
</div>
|
||||
<div v-if="showRole">{{ userStore.username }}</div>
|
||||
</div>
|
||||
@ -116,8 +116,6 @@ const goToLogin = () => {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--main-color);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@ -30,7 +30,6 @@ import { useConfigStore } from '@/stores/config'
|
||||
import { useDatabaseStore } from '@/stores/database'
|
||||
import DebugComponent from '@/components/DebugComponent.vue'
|
||||
import UserInfoComponent from '@/components/UserInfoComponent.vue'
|
||||
import { configApi } from '@/apis/public_api'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const databaseStore = useDatabaseStore()
|
||||
@ -80,12 +79,6 @@ onMounted(() => {
|
||||
const route = useRoute()
|
||||
console.log(route)
|
||||
|
||||
const apiDocsUrl = computed(() => {
|
||||
// return `${import.meta.env.VITE_API_URL || `http://${window.location.hostname}:${window.location.port}`}/docs`
|
||||
return `http://localhost:5050/docs`
|
||||
})
|
||||
|
||||
|
||||
// 下面是导航菜单部分,添加智能体项
|
||||
const mainList = [{
|
||||
name: '对话',
|
||||
@ -167,10 +160,6 @@ const mainList = [{
|
||||
</div>
|
||||
<div class="fill" style="flex-grow: 1;"></div>
|
||||
|
||||
<!-- 用户信息组件 -->
|
||||
<div class="nav-item user-info">
|
||||
<UserInfoComponent />
|
||||
</div>
|
||||
|
||||
<div class="github nav-item">
|
||||
<a-tooltip placement="right">
|
||||
@ -191,6 +180,15 @@ const mainList = [{
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</div> -->
|
||||
|
||||
<!-- 用户信息组件 -->
|
||||
<div class="nav-item user-info">
|
||||
<a-tooltip placement="right">
|
||||
<template #title>用户信息</template>
|
||||
<UserInfoComponent />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<RouterLink class="nav-item setting" to="/setting" active-class="active">
|
||||
<a-tooltip placement="right">
|
||||
<template #title>设置</template>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { systemConfigApi } from '@/apis/admin_api'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
@ -20,14 +21,7 @@ export const useConfigStore = defineStore('config', () => {
|
||||
|
||||
function setConfigValue(key, value) {
|
||||
config.value[key] = value
|
||||
fetch('/api/config', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ key, value }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
systemConfigApi.updateSystemConfig({ key, value })
|
||||
.then(data => {
|
||||
console.debug('Success:', data)
|
||||
setConfig(data)
|
||||
@ -35,8 +29,7 @@ export const useConfigStore = defineStore('config', () => {
|
||||
}
|
||||
|
||||
function refreshConfig() {
|
||||
fetch('/api/config')
|
||||
.then(response => response.json())
|
||||
systemConfigApi.getSystemConfig()
|
||||
.then(data => {
|
||||
console.log("config", data)
|
||||
setConfig(data)
|
||||
|
||||
@ -40,14 +40,6 @@
|
||||
智能体配置
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
class="action-button"
|
||||
@click="openTokenModal"
|
||||
>
|
||||
<template #icon><KeyOutlined /></template>
|
||||
访问令牌
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
class="action-button"
|
||||
@click="goToAgentPage"
|
||||
@ -204,16 +196,6 @@
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 令牌管理弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="state.tokenModalVisible"
|
||||
title="访问令牌管理"
|
||||
width="650px"
|
||||
:footer="null"
|
||||
@cancel="closeTokenModal"
|
||||
>
|
||||
<TokenManagerComponent v-if="selectedAgentId" :agent-id="selectedAgentId" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -233,8 +215,6 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||
import TokenManagerComponent from '@/components/TokenManagerComponent.vue';
|
||||
import UserInfoComponent from '@/components/UserInfoComponent.vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { chatApi } from '@/apis/auth_api';
|
||||
import { systemConfigApi } from '@/apis/admin_api';
|
||||
@ -253,7 +233,6 @@ const state = reactive({
|
||||
isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'),
|
||||
isConfigSidebarOpen: false,
|
||||
configModalVisible: false,
|
||||
tokenModalVisible: false,
|
||||
isEmptyConfig: computed(() =>
|
||||
!selectedAgentId.value ||
|
||||
Object.keys(configurableItems.value).length === 0
|
||||
@ -521,16 +500,6 @@ const openConfigModal = () => {
|
||||
const closeConfigModal = () => {
|
||||
state.configModalVisible = false;
|
||||
};
|
||||
|
||||
// 打开令牌管理弹窗
|
||||
const openTokenModal = () => {
|
||||
state.tokenModalVisible = true;
|
||||
};
|
||||
|
||||
// 关闭令牌管理弹窗
|
||||
const closeTokenModal = () => {
|
||||
state.tokenModalVisible = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@ -580,12 +549,6 @@ const closeTokenModal = () => {
|
||||
min-width: calc(var(--config-sidebar-width) - 16px);
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 100px);
|
||||
|
||||
.token-section {
|
||||
margin-top: 1.5rem;
|
||||
border-top: 1px solid var(--main-light-3);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.no-agent-selected {
|
||||
|
||||
@ -286,7 +286,6 @@ import { onMounted, reactive, ref, watch, toRaw, onUnmounted, computed } from 'v
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { knowledgeBaseApi } from '@/apis/admin_api'
|
||||
import HeaderComponent from '@/components/HeaderComponent.vue';
|
||||
import {
|
||||
@ -300,7 +299,6 @@ import {
|
||||
CloudUploadOutlined,
|
||||
SearchOutlined,
|
||||
LoadingOutlined,
|
||||
CaretUpOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ export default defineConfig(({ mode }) => {
|
||||
'^/api': {
|
||||
target: env.VITE_API_URL || 'http://127.0.0.1:5050',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user