From fbbefe0a63af75427447e846e2b31cbca7515cc2 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Thu, 3 Apr 2025 16:02:14 +0800 Subject: [PATCH] server session initial --- src/models/token_model.py | 24 ++ src/routers/__init__.py | 2 + src/routers/admin_router.py | 83 ++++++ src/routers/chat_router.py | 32 ++- src/utils/db_manager.py | 40 +++ web/src/components/TokenManagerComponent.vue | 253 +++++++++++++++++++ web/src/views/AgentSingleView.vue | 113 ++++++++- web/src/views/AgentView.vue | 85 +++++-- 8 files changed, 610 insertions(+), 22 deletions(-) create mode 100644 src/models/token_model.py create mode 100644 src/routers/admin_router.py create mode 100644 src/utils/db_manager.py create mode 100644 web/src/components/TokenManagerComponent.vue diff --git a/src/models/token_model.py b/src/models/token_model.py new file mode 100644 index 00000000..74ee1d59 --- /dev/null +++ b/src/models/token_model.py @@ -0,0 +1,24 @@ +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 + } \ No newline at end of file diff --git a/src/routers/__init__.py b/src/routers/__init__.py index c39810cd..73f84125 100644 --- a/src/routers/__init__.py +++ b/src/routers/__init__.py @@ -3,9 +3,11 @@ from src.routers.chat_router import chat from src.routers.data_router import data from src.routers.base_router import base from src.routers.tool_router import tool +from src.routers.admin_router import admin router = APIRouter() router.include_router(base) router.include_router(chat) router.include_router(data) router.include_router(tool) +router.include_router(admin) diff --git a/src/routers/admin_router.py b/src/routers/admin_router.py new file mode 100644 index 00000000..3f06d46b --- /dev/null +++ b/src/routers/admin_router.py @@ -0,0 +1,83 @@ +import secrets +import string +from fastapi import APIRouter, Depends, HTTPException, Query +from pydantic import BaseModel +from typing import List, Optional +from sqlalchemy.orm import Session + +from src.utils.db_manager import db_manager +from src.models.token_model import AgentToken + +admin = APIRouter(prefix="/admin", tags=["admin"]) + +# 依赖项:获取数据库会话 +def get_db(): + db = db_manager.get_session() + try: + yield db + finally: + db.close() + +# 请求和响应模型 +class TokenCreate(BaseModel): + agent_id: str + name: 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), + 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, + 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) + + return new_token.to_dict() + +@admin.delete("/tokens/{token_id}", response_model=dict) +async def delete_token(token_id: int, 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") + + db.delete(token) + db.commit() + + return {"success": True, "message": "Token deleted"} \ No newline at end of file diff --git a/src/routers/chat_router.py b/src/routers/chat_router.py index ea687c02..8f8bda39 100644 --- a/src/routers/chat_router.py +++ b/src/routers/chat_router.py @@ -3,19 +3,49 @@ import json import asyncio import traceback import uuid -from fastapi import APIRouter, Body +from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.responses import StreamingResponse from langchain_core.messages import AIMessageChunk +from pydantic import BaseModel +from sqlalchemy.orm import Session from src import executor, config, retriever from src.core import HistoryManager from src.agents import agent_manager from src.models import select_model from src.utils.logging_config import logger +from src.utils.db_manager import db_manager +from src.models.token_model import AgentToken chat = APIRouter(prefix="/chat") +# 依赖项:获取数据库会话 +def get_db(): + db = db_manager.get_session() + try: + yield db + finally: + db.close() +class TokenVerify(BaseModel): + agent_id: str + token: str + +@chat.post("/verify_token") +async def verify_agent_token( + token_data: TokenVerify, + db: Session = Depends(get_db) +): + """验证智能体访问令牌""" + token = db.query(AgentToken).filter( + AgentToken.agent_id == token_data.agent_id, + AgentToken.token == token_data.token + ).first() + + if not token: + raise HTTPException(status_code=401, detail="Invalid token") + + return {"success": True, "message": "Token verified"} @chat.get("/") async def chat_get(): diff --git a/src/utils/db_manager.py b/src/utils/db_manager.py new file mode 100644 index 00000000..d42e042a --- /dev/null +++ b/src/utils/db_manager.py @@ -0,0 +1,40 @@ +import os +import sqlite3 +import pathlib +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.declarative import declarative_base + +from src.models.token_model import Base, AgentToken + +class DBManager: + """数据库管理器""" + + def __init__(self): + self.db_path = os.path.join("saves", "data", "server.db") + self.ensure_db_dir() + + # 创建SQLAlchemy引擎 + self.engine = create_engine(f"sqlite:///{self.db_path}") + + # 创建会话工厂 + self.Session = sessionmaker(bind=self.engine) + + # 确保表存在 + self.create_tables() + + def ensure_db_dir(self): + """确保数据库目录存在""" + db_dir = os.path.dirname(self.db_path) + pathlib.Path(db_dir).mkdir(parents=True, exist_ok=True) + + def create_tables(self): + """创建数据库表""" + Base.metadata.create_all(self.engine) + + def get_session(self): + """获取数据库会话""" + return self.Session() + +# 创建全局数据库管理器实例 +db_manager = DBManager() \ No newline at end of file diff --git a/web/src/components/TokenManagerComponent.vue b/web/src/components/TokenManagerComponent.vue new file mode 100644 index 00000000..cb84d2e6 --- /dev/null +++ b/web/src/components/TokenManagerComponent.vue @@ -0,0 +1,253 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/AgentSingleView.vue b/web/src/views/AgentSingleView.vue index e9fbc690..382e2ea8 100644 --- a/web/src/views/AgentSingleView.vue +++ b/web/src/views/AgentSingleView.vue @@ -1,16 +1,106 @@ diff --git a/web/src/views/AgentView.vue b/web/src/views/AgentView.vue index d3805fbe..e25e9a8f 100644 --- a/web/src/views/AgentView.vue +++ b/web/src/views/AgentView.vue @@ -79,10 +79,38 @@ -
+
+ + + 打开智能体配置 + + + +
+ +
+ +
+
+ 请先选择一个智能体 +
+
+ + + +
-
@@ -93,8 +121,6 @@ :name="key" class="config-item" > - -

{{ value.description }}

- +
保存配置 重置 + 取消
-
- 请先选择一个智能体 -
- +
@@ -145,6 +169,7 @@ import { } from '@ant-design/icons-vue'; import { message } from 'ant-design-vue'; import AgentChatComponent from '@/components/AgentChatComponent.vue'; +import TokenManagerComponent from '@/components/TokenManagerComponent.vue'; // 状态 const agents = ref({}); @@ -153,6 +178,7 @@ const state = reactive({ debug_mode: false, isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'), isConfigSidebarOpen: false, + configModalVisible: false, isEmptyConfig: computed(() => !selectedAgentId.value || Object.keys(configurableItems.value).length === 0 @@ -169,6 +195,16 @@ const toggleDebugMode = () => { state.debug_mode = !state.debug_mode; }; +// 打开配置弹窗 +const openConfigModal = () => { + state.configModalVisible = true; +}; + +// 关闭配置弹窗 +const closeConfigModal = () => { + state.configModalVisible = false; +}; + // 根据选中的智能体加载配置 const loadAgentConfig = () => { // BUG: 目前消息重置有问题,需要重置消息 @@ -222,6 +258,7 @@ const saveConfig = () => { // 提示保存成功 message.success('配置已保存'); + closeConfigModal(); }; // 重置配置 @@ -379,18 +416,13 @@ const getPlaceholder = (key, value) => { overflow-y: auto; max-height: calc(100vh - 100px); - .description { - font-size: 12px; - color: var(--gray-700); + .token-section { + margin-top: 1.5rem; + border-top: 1px solid var(--main-light-3); + padding-top: 1rem; } } - .form-actions { - display: flex; - justify-content: space-between; - margin-top: 20px; - } - .no-agent-selected { padding: 16px; color: var(--gray-500); @@ -548,6 +580,23 @@ const getPlaceholder = (key, value) => { } } } + +.config-modal-content { + max-height: 70vh; + overflow-y: auto; + + .description { + font-size: 12px; + color: var(--gray-700); + } + + .form-actions { + display: flex; + justify-content: space-between; + margin-top: 20px; + gap: 10px; + } +}