server session initial
This commit is contained in:
parent
988426a5f5
commit
fbbefe0a63
24
src/models/token_model.py
Normal file
24
src/models/token_model.py
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -3,9 +3,11 @@ from src.routers.chat_router import chat
|
|||||||
from src.routers.data_router import data
|
from src.routers.data_router import data
|
||||||
from src.routers.base_router import base
|
from src.routers.base_router import base
|
||||||
from src.routers.tool_router import tool
|
from src.routers.tool_router import tool
|
||||||
|
from src.routers.admin_router import admin
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
router.include_router(base)
|
router.include_router(base)
|
||||||
router.include_router(chat)
|
router.include_router(chat)
|
||||||
router.include_router(data)
|
router.include_router(data)
|
||||||
router.include_router(tool)
|
router.include_router(tool)
|
||||||
|
router.include_router(admin)
|
||||||
|
|||||||
83
src/routers/admin_router.py
Normal file
83
src/routers/admin_router.py
Normal file
@ -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"}
|
||||||
@ -3,19 +3,49 @@ import json
|
|||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from fastapi import APIRouter, Body
|
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from langchain_core.messages import AIMessageChunk
|
from langchain_core.messages import AIMessageChunk
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from src import executor, config, retriever
|
from src import executor, config, retriever
|
||||||
from src.core import HistoryManager
|
from src.core import HistoryManager
|
||||||
from src.agents import agent_manager
|
from src.agents import agent_manager
|
||||||
from src.models import select_model
|
from src.models import select_model
|
||||||
from src.utils.logging_config import logger
|
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")
|
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("/")
|
@chat.get("/")
|
||||||
async def chat_get():
|
async def chat_get():
|
||||||
|
|||||||
40
src/utils/db_manager.py
Normal file
40
src/utils/db_manager.py
Normal file
@ -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()
|
||||||
253
web/src/components/TokenManagerComponent.vue
Normal file
253
web/src/components/TokenManagerComponent.vue
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
<template>
|
||||||
|
<div class="token-manager">
|
||||||
|
<h2 class="manager-title">访问令牌管理</h2>
|
||||||
|
<div class="token-tools">
|
||||||
|
<a-button type="primary" size="small" @click="showAddTokenModal">
|
||||||
|
<PlusOutlined /> 创建令牌
|
||||||
|
</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:visible="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';
|
||||||
|
|
||||||
|
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 response = await fetch(`/api/admin/tokens?agent_id=${props.agentId}`);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
tokens.value = data;
|
||||||
|
} else {
|
||||||
|
message.error('获取令牌列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取令牌列表出错:', error);
|
||||||
|
message.error('获取令牌列表出错');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建新令牌
|
||||||
|
const createToken = async () => {
|
||||||
|
if (!newToken.value.name.trim()) {
|
||||||
|
message.warning('请输入令牌名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/admin/tokens', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
agent_id: props.agentId,
|
||||||
|
name: newToken.value.name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
tokens.value.push(data);
|
||||||
|
message.success('令牌创建成功');
|
||||||
|
addTokenModalVisible.value = false;
|
||||||
|
newToken.value.name = '';
|
||||||
|
} else {
|
||||||
|
message.error('创建令牌失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建令牌出错:', error);
|
||||||
|
message.error('创建令牌出错');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除令牌
|
||||||
|
const deleteToken = async (tokenId) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/admin/tokens/${tokenId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
tokens.value = tokens.value.filter(token => token.id !== tokenId);
|
||||||
|
message.success('令牌已删除');
|
||||||
|
} else {
|
||||||
|
message.error('删除令牌失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除令牌出错:', error);
|
||||||
|
message.error('删除令牌出错');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 复制令牌到剪贴板
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
||||||
@ -1,16 +1,106 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="agent-single-view">
|
<div class="agent-single-view">
|
||||||
<AgentChatComponent :agent-id="agentId" />
|
<!-- Token验证弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="tokenModalVisible"
|
||||||
|
title="访问验证"
|
||||||
|
:closable="false"
|
||||||
|
:maskClosable="false"
|
||||||
|
:keyboard="false"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<div class="token-verify-form">
|
||||||
|
<p>需要输入访问令牌才能使用该智能体</p>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="tokenInput"
|
||||||
|
placeholder="请输入访问令牌"
|
||||||
|
@pressEnter="verifyToken"
|
||||||
|
/>
|
||||||
|
<div class="error-message" v-if="errorMessage">{{ errorMessage }}</div>
|
||||||
|
<div class="token-actions">
|
||||||
|
<a-button type="primary" :loading="verifying" @click="verifyToken">验证</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 智能体聊天界面 -->
|
||||||
|
<AgentChatComponent v-if="isVerified" :agent-id="agentId" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
const agentId = computed(() => route.params.agent_id);
|
const agentId = computed(() => route.params.agent_id);
|
||||||
|
|
||||||
|
// Token验证相关状态
|
||||||
|
const tokenModalVisible = ref(false);
|
||||||
|
const tokenInput = ref('');
|
||||||
|
const isVerified = ref(false);
|
||||||
|
const verifying = ref(false);
|
||||||
|
const errorMessage = ref('');
|
||||||
|
|
||||||
|
// 验证Token
|
||||||
|
const verifyToken = async () => {
|
||||||
|
if (!tokenInput.value) {
|
||||||
|
errorMessage.value = '请输入访问令牌';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
verifying.value = true;
|
||||||
|
errorMessage.value = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/chat/verify_token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
agent_id: agentId.value,
|
||||||
|
token: tokenInput.value
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// 验证成功
|
||||||
|
isVerified.value = true;
|
||||||
|
tokenModalVisible.value = false;
|
||||||
|
|
||||||
|
// 保存验证状态到localStorage
|
||||||
|
localStorage.setItem(`agent-token-verified-${agentId.value}`, 'true');
|
||||||
|
} else {
|
||||||
|
// 验证失败
|
||||||
|
const data = await response.json();
|
||||||
|
errorMessage.value = data.detail || '令牌验证失败';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证令牌出错:', error);
|
||||||
|
errorMessage.value = '验证令牌时发生错误';
|
||||||
|
} finally {
|
||||||
|
verifying.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否已经验证
|
||||||
|
const checkVerification = () => {
|
||||||
|
const verified = localStorage.getItem(`agent-token-verified-${agentId.value}`);
|
||||||
|
if (verified === 'true') {
|
||||||
|
isVerified.value = true;
|
||||||
|
} else {
|
||||||
|
tokenModalVisible.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件挂载时检查验证状态
|
||||||
|
onMounted(() => {
|
||||||
|
checkVerification();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@ -19,6 +109,23 @@ const agentId = computed(() => route.params.agent_id);
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.token-verify-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -79,10 +79,38 @@
|
|||||||
<CloseOutlined class="iconfont icon-20" />
|
<CloseOutlined class="iconfont icon-20" />
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="selectedAgentId && configSchema" class="config-form">
|
<div v-if="selectedAgentId" class="config-form">
|
||||||
|
<!-- 打开配置弹窗的按钮 -->
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
block
|
||||||
|
@click="openConfigModal"
|
||||||
|
>
|
||||||
|
打开智能体配置
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<!-- 添加Token管理组件 -->
|
||||||
|
<div class="token-section">
|
||||||
|
<TokenManagerComponent :agent-id="selectedAgentId" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-agent-selected">
|
||||||
|
请先选择一个智能体
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配置弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="state.configModalVisible"
|
||||||
|
title="智能体配置"
|
||||||
|
width="650px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="closeConfigModal"
|
||||||
|
>
|
||||||
|
<div v-if="selectedAgentId && configSchema" class="config-modal-content">
|
||||||
<!-- 配置表单 -->
|
<!-- 配置表单 -->
|
||||||
<a-form :model="agentConfig" layout="vertical">
|
<a-form :model="agentConfig" layout="vertical">
|
||||||
<!-- 系统提示词 -->
|
|
||||||
<div class="empty-config" v-if="state.isEmptyConfig">
|
<div class="empty-config" v-if="state.isEmptyConfig">
|
||||||
<a-alert type="warning" message="该智能体没有配置项" show-icon/>
|
<a-alert type="warning" message="该智能体没有配置项" show-icon/>
|
||||||
</div>
|
</div>
|
||||||
@ -93,8 +121,6 @@
|
|||||||
:name="key"
|
:name="key"
|
||||||
class="config-item"
|
class="config-item"
|
||||||
>
|
>
|
||||||
<!-- <p>{{ value }}</p> -->
|
|
||||||
<!-- 根据值的类型使用不同的输入控件 -->
|
|
||||||
<p v-if="value.description" class="description">{{ value.description }}</p>
|
<p v-if="value.description" class="description">{{ value.description }}</p>
|
||||||
<a-switch
|
<a-switch
|
||||||
v-if="typeof agentConfig[key] === 'boolean'"
|
v-if="typeof agentConfig[key] === 'boolean'"
|
||||||
@ -120,17 +146,15 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 保存和重置按钮 -->
|
<!-- 弹窗底部按钮 -->
|
||||||
<div class="form-actions" v-if="!state.isEmptyConfig">
|
<div class="form-actions" v-if="!state.isEmptyConfig">
|
||||||
<a-button type="primary" @click="saveConfig">保存配置</a-button>
|
<a-button type="primary" @click="saveConfig">保存配置</a-button>
|
||||||
<a-button @click="resetConfig">重置</a-button>
|
<a-button @click="resetConfig">重置</a-button>
|
||||||
|
<a-button @click="closeConfigModal">取消</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="no-agent-selected">
|
</a-modal>
|
||||||
请先选择一个智能体
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -145,6 +169,7 @@ import {
|
|||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||||
|
import TokenManagerComponent from '@/components/TokenManagerComponent.vue';
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const agents = ref({});
|
const agents = ref({});
|
||||||
@ -153,6 +178,7 @@ const state = reactive({
|
|||||||
debug_mode: false,
|
debug_mode: false,
|
||||||
isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'),
|
isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'),
|
||||||
isConfigSidebarOpen: false,
|
isConfigSidebarOpen: false,
|
||||||
|
configModalVisible: false,
|
||||||
isEmptyConfig: computed(() =>
|
isEmptyConfig: computed(() =>
|
||||||
!selectedAgentId.value ||
|
!selectedAgentId.value ||
|
||||||
Object.keys(configurableItems.value).length === 0
|
Object.keys(configurableItems.value).length === 0
|
||||||
@ -169,6 +195,16 @@ const toggleDebugMode = () => {
|
|||||||
state.debug_mode = !state.debug_mode;
|
state.debug_mode = !state.debug_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 打开配置弹窗
|
||||||
|
const openConfigModal = () => {
|
||||||
|
state.configModalVisible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭配置弹窗
|
||||||
|
const closeConfigModal = () => {
|
||||||
|
state.configModalVisible = false;
|
||||||
|
};
|
||||||
|
|
||||||
// 根据选中的智能体加载配置
|
// 根据选中的智能体加载配置
|
||||||
const loadAgentConfig = () => {
|
const loadAgentConfig = () => {
|
||||||
// BUG: 目前消息重置有问题,需要重置消息
|
// BUG: 目前消息重置有问题,需要重置消息
|
||||||
@ -222,6 +258,7 @@ const saveConfig = () => {
|
|||||||
|
|
||||||
// 提示保存成功
|
// 提示保存成功
|
||||||
message.success('配置已保存');
|
message.success('配置已保存');
|
||||||
|
closeConfigModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置配置
|
// 重置配置
|
||||||
@ -379,18 +416,13 @@ const getPlaceholder = (key, value) => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: calc(100vh - 100px);
|
max-height: calc(100vh - 100px);
|
||||||
|
|
||||||
.description {
|
.token-section {
|
||||||
font-size: 12px;
|
margin-top: 1.5rem;
|
||||||
color: var(--gray-700);
|
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 {
|
.no-agent-selected {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
color: var(--gray-500);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user