继续优化接口,移除无关模块:(1)移除原本的 Token 管理模块;(2)所有 API 请求使用 /api 前缀;(3)移除重复的中间件验证;(4)前后端都移除工具模块;(5)移除原本的 safe_config 模块;(6)其他小细节优化

This commit is contained in:
Wenjie Zhang 2025-05-05 16:31:35 +08:00
parent 50ce0b3da1
commit a454389f6e
20 changed files with 72 additions and 721 deletions

View File

@ -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:
"""数据库管理器"""

View File

@ -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)

View File

@ -0,0 +1,3 @@
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

View File

@ -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
}

View File

@ -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):
"""用户模型"""

View File

@ -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)

View File

@ -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"}

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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):
"""

View File

@ -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 = {
/**

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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)

View File

@ -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 {

View File

@ -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'

View File

@ -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: {