临时保存
This commit is contained in:
parent
04ee276331
commit
9451b01ae9
@ -1,8 +1,15 @@
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
from src.agents.registry import Configuration
|
||||
from src.agents.tools_factory import multiply, add, subtract, divide
|
||||
|
||||
|
||||
|
||||
def get_default_tools():
|
||||
return ["TavilySearchResults", "multiply", "add", "subtract", "divide"]
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ChatbotConfiguration(Configuration):
|
||||
@ -24,3 +31,32 @@ class ChatbotConfiguration(Configuration):
|
||||
},
|
||||
)
|
||||
|
||||
tools: list = field(
|
||||
default_factory=get_default_tools,
|
||||
metadata={
|
||||
"description": "The tools to use for the agent's interactions. "
|
||||
"Should be in the form: provider/model-name."
|
||||
},
|
||||
)
|
||||
|
||||
temperature: float = field(
|
||||
default=0.7,
|
||||
metadata={
|
||||
"description": "控制模型生成结果的随机性,值越大随机性越高,建议范围 0.0-1.0"
|
||||
},
|
||||
)
|
||||
|
||||
use_tools: bool = field(
|
||||
default=True,
|
||||
metadata={
|
||||
"description": "是否启用工具调用功能"
|
||||
},
|
||||
)
|
||||
|
||||
max_iterations: int = field(
|
||||
default=10,
|
||||
metadata={
|
||||
"description": "智能体最大执行步数,防止无限循环"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -6,14 +6,13 @@ from datetime import datetime
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langgraph.checkpoint.memory import MemorySaver # 实际上没有起作用
|
||||
|
||||
|
||||
from src.agents.registry import State, BaseAgent
|
||||
from src.agents.utils import load_chat_model
|
||||
from src.agents.tools_factory import multiply, add, subtract, divide
|
||||
from src.agents.chatbot.configuration import ChatbotConfiguration
|
||||
from src.agents.tools_factory import _TOOLS_REGISTRY
|
||||
|
||||
class ChatbotAgent(BaseAgent):
|
||||
name = "chatbot"
|
||||
@ -27,14 +26,15 @@ class ChatbotAgent(BaseAgent):
|
||||
|
||||
def _get_tools(self, config_schema: RunnableConfig):
|
||||
"""根据配置获取工具"""
|
||||
tools = [multiply, add, subtract, divide, TavilySearchResults(max_results=10)]
|
||||
return tools
|
||||
default_tools_names = config_schema.get("tools", [])
|
||||
default_tools = [_TOOLS_REGISTRY[tool] for tool in default_tools_names]
|
||||
return default_tools
|
||||
|
||||
def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]:
|
||||
"""调用 llm 模型"""
|
||||
config_schema = config or {}
|
||||
conf = self.config_schema.from_runnable_config(config_schema)
|
||||
model = load_chat_model(conf.model)
|
||||
model = load_chat_model(conf.model, temperature=conf.temperature)
|
||||
model_with_tools = model.bind_tools(self._get_tools(config_schema))
|
||||
|
||||
res = model_with_tools.invoke(
|
||||
|
||||
@ -42,7 +42,9 @@ class Configuration(SimpleConfig):
|
||||
|
||||
@classmethod
|
||||
def to_dict(cls):
|
||||
return {f.name: getattr(cls, f.name) for f in fields(cls) if f.init}
|
||||
# 创建一个实例来处理 default_factory
|
||||
instance = cls()
|
||||
return {f.name: getattr(instance, f.name) for f in fields(cls) if f.init}
|
||||
|
||||
|
||||
|
||||
@ -84,12 +86,13 @@ class BaseAgent():
|
||||
def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs):
|
||||
graph = self.get_graph(config_schema=config_schema, **kwargs)
|
||||
conf = self.config_schema.from_runnable_config(config_schema)
|
||||
for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema):
|
||||
msg_type = msg.type
|
||||
|
||||
return_keys =conf.return_keys
|
||||
if not return_keys or msg_type in return_keys:
|
||||
yield msg, metadata
|
||||
for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema):
|
||||
# msg_type = msg.type
|
||||
# return_keys = conf.get("return_keys", [])
|
||||
# if not return_keys or msg_type in return_keys:
|
||||
# yield msg, metadata
|
||||
yield msg, metadata
|
||||
|
||||
@abstractmethod
|
||||
def get_graph(self, **kwargs) -> CompiledStateGraph:
|
||||
|
||||
@ -6,9 +6,7 @@ from typing import Any, Callable, Optional, Type, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from langchain_core.tools import tool, BaseTool
|
||||
|
||||
|
||||
_TOOLS_REGISTRY = {}
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
# refs https://github.com/chatchat-space/LangGraph-Chatchat chatchat-server/chatchat/server/agent/tools_factory/tools_registry.py
|
||||
def regist_tool(
|
||||
@ -115,3 +113,12 @@ def subtract(first_int: int, second_int: int) -> int:
|
||||
def divide(first_int: int, second_int: int) -> int:
|
||||
"""Divide two integers."""
|
||||
return first_int / second_int
|
||||
|
||||
|
||||
_TOOLS_REGISTRY = {
|
||||
"multiply": multiply,
|
||||
"add": add,
|
||||
"subtract": subtract,
|
||||
"divide": divide,
|
||||
"TavilySearchResults": TavilySearchResults(max_results=10),
|
||||
}
|
||||
|
||||
@ -7,14 +7,23 @@ from langchain_core.messages import AIMessageChunk, ToolMessage
|
||||
|
||||
|
||||
|
||||
def load_chat_model(fully_specified_name: str) -> BaseChatModel:
|
||||
def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel:
|
||||
"""Load a chat model from a fully specified name.
|
||||
|
||||
Args:
|
||||
fully_specified_name (str): String in the format 'provider/model'.
|
||||
**kwargs: Additional parameters to pass to the model.
|
||||
"""
|
||||
provider, model = fully_specified_name.split("/", maxsplit=1)
|
||||
return select_model(model_name=model, model_provider=provider).chat_open_ai
|
||||
model_instance = select_model(model_name=model, model_provider=provider)
|
||||
|
||||
# 配置额外参数,如temperature
|
||||
if kwargs and hasattr(model_instance, 'chat_open_ai'):
|
||||
for key, value in kwargs.items():
|
||||
if value is not None:
|
||||
setattr(model_instance.chat_open_ai, key, value)
|
||||
|
||||
return model_instance.chat_open_ai
|
||||
|
||||
|
||||
def agent_cli(agent: BaseAgent, config: RunnableConfig = None):
|
||||
|
||||
@ -11,7 +11,8 @@ class OpenAIBase():
|
||||
self.model_name = model_name
|
||||
self.chat_open_ai = ChatOpenAI(model=model_name,
|
||||
api_key=api_key,
|
||||
base_url=base_url)
|
||||
base_url=base_url,
|
||||
temperature=0.7)
|
||||
|
||||
def predict(self, message, stream=False):
|
||||
if isinstance(message, str):
|
||||
|
||||
@ -130,17 +130,21 @@ async def get_agent():
|
||||
@chat.post("/agent/{agent_name}")
|
||||
def chat_agent(agent_name: str,
|
||||
query: str = Body(...),
|
||||
meta: dict = Body({}),
|
||||
history: list = Body(...),
|
||||
thread_id: str | None = Body(None)):
|
||||
|
||||
meta["server_model_name"] = agent_name
|
||||
config: dict = Body({})):
|
||||
|
||||
# 将meta和thread_id整合到config中
|
||||
def make_chunk(content=None, **kwargs):
|
||||
chat_metadata = {
|
||||
"agent_name": agent_name,
|
||||
"thread_id": config.get("thread_id"),
|
||||
}
|
||||
if update_metadata := kwargs.get("chat_metadata"):
|
||||
chat_metadata.update(update_metadata)
|
||||
|
||||
return json.dumps({
|
||||
"response": content,
|
||||
"model_name": agent_name,
|
||||
"meta": meta,
|
||||
"chat_metadata": chat_metadata,
|
||||
**kwargs
|
||||
}, ensure_ascii=False).encode('utf-8') + b"\n"
|
||||
|
||||
@ -150,22 +154,27 @@ def chat_agent(agent_name: str,
|
||||
logger.error(f"Error getting agent {agent_name}: {e}")
|
||||
return StreamingResponse(make_chunk(message=f"Error getting agent {agent_name}: {e}", status="error"), media_type='application/json')
|
||||
|
||||
# 从config中获取history_round
|
||||
history_round = config.get("history_round")
|
||||
history_manager = HistoryManager(history)
|
||||
messages = history_manager.get_history_with_msg(query, max_rounds=meta.get('history_round'))
|
||||
history_manager.add_user(query) # 注意这里使用原始查询
|
||||
messages = history_manager.get_history_with_msg(query, max_rounds=history_round)
|
||||
history_manager.add_user(query)
|
||||
|
||||
# 如果没有thread_id则生成一个
|
||||
if "thread_id" not in config or not config["thread_id"]:
|
||||
config["thread_id"] = str(uuid.uuid4())
|
||||
|
||||
# 构造运行时配置
|
||||
runnable_config = {
|
||||
"configurable": {
|
||||
"thread_id": thread_id or str(uuid.uuid4()),
|
||||
"return_keys": []
|
||||
**config
|
||||
}
|
||||
}
|
||||
|
||||
def stream_messages():
|
||||
content = ""
|
||||
yield make_chunk(status="waiting")
|
||||
for msg, metadata in agent.stream_messages(messages, runnable_config):
|
||||
# logger.debug(f">>>>> msg: {msg.model_dump()}, >>>>>>> {metadata=}")
|
||||
yield make_chunk(status="init")
|
||||
for msg, metadata in agent.stream_messages(messages, config_schema=runnable_config):
|
||||
if isinstance(msg, AIMessageChunk) and msg.content != "<tool_call>":
|
||||
content += msg.content
|
||||
yield make_chunk(content=msg.content,
|
||||
|
||||
@ -8,28 +8,16 @@
|
||||
<PlusCircleOutlined /> <span class="text">新对话</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header__center">
|
||||
<slot name="header-center"></slot>
|
||||
</div>
|
||||
<div class="header__right">
|
||||
<div v-if="!props.agentId" class="current-agent nav-btn">
|
||||
<a-dropdown>
|
||||
<div class="current-agent nav-btn">
|
||||
<RobotOutlined />
|
||||
<span v-if="currentAgent">{{ currentAgent.name }}</span>
|
||||
<span v-else>请选择智能体</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="({key}) => selectAgent(key)">
|
||||
<a-menu-item v-for="(agent, name) in agents" :key="name">
|
||||
<RobotOutlined /> {{ agent.name }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div v-else class="current-agent nav-btn">
|
||||
<div class="current-agent nav-btn" @click="sayHi">
|
||||
<RobotOutlined />
|
||||
<span v-if="currentAgent">{{ currentAgent.name }}</span>
|
||||
<span v-else>加载中...</span>
|
||||
</div>
|
||||
<slot name="header-right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -38,7 +26,7 @@
|
||||
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="chat-box" ref="messagesContainer" :class="{ 'is-debug': options.debug_mode }">
|
||||
<div class="chat-box" ref="messagesContainer" :class="{ 'is-debug': config.debug_mode }">
|
||||
<MessageComponent
|
||||
v-for="(message, index) in messages"
|
||||
:message="message"
|
||||
@ -46,7 +34,7 @@
|
||||
:is-processing="isProcessing"
|
||||
@retry="retryMessage(index)"
|
||||
>
|
||||
<div v-if="options.debug_mode" class="status-info">{{ message }}</div>
|
||||
<div v-if="config.debug_mode" class="status-info">{{ message }}</div>
|
||||
|
||||
<!-- 工具调用 -->
|
||||
<template #tool-calls>
|
||||
@ -112,12 +100,12 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch, nextTick, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import {
|
||||
RobotOutlined, SendOutlined, LoadingOutlined,
|
||||
ThunderboltOutlined, ReloadOutlined, CheckCircleOutlined,
|
||||
PlusCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import MessageInputComponent from '@/components/MessageInputComponent.vue'
|
||||
import MessageComponent from '@/components/MessageComponent.vue'
|
||||
|
||||
@ -126,23 +114,18 @@ const props = defineProps({
|
||||
agentId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
// 移除路由相关逻辑,使用props
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// ==================== 状态管理 ====================
|
||||
|
||||
// UI状态
|
||||
const state = reactive({});
|
||||
|
||||
// 应用选项
|
||||
const options = reactive({
|
||||
use_web: true,
|
||||
debug_mode: false,
|
||||
});
|
||||
|
||||
// DOM引用
|
||||
const messagesContainer = ref(null);
|
||||
@ -153,7 +136,6 @@ const currentAgent = ref(null); // 当前选中的智能体
|
||||
const userInput = ref(''); // 用户输入
|
||||
const messages = ref([]); // 消息列表
|
||||
const isProcessing = ref(false); // 是否正在处理请求
|
||||
const threadId = ref(null); // 会话线程ID
|
||||
|
||||
// ==================== 工具调用相关 ====================
|
||||
|
||||
@ -200,7 +182,6 @@ const resetStatusSteps = () => {
|
||||
|
||||
// 重置线程
|
||||
const resetThread = () => {
|
||||
threadId.value = null;
|
||||
messages.value = [];
|
||||
resetStatusSteps();
|
||||
saveState();
|
||||
@ -274,7 +255,6 @@ const prepareMessageHistory = (msgs) => {
|
||||
const selectAgent = (agentName) => {
|
||||
currentAgent.value = agents.value[agentName];
|
||||
messages.value = [];
|
||||
threadId.value = null;
|
||||
resetStatusSteps();
|
||||
saveState();
|
||||
};
|
||||
@ -352,11 +332,10 @@ const sendMessageWithText = async (text) => {
|
||||
// 设置请求参数
|
||||
const requestData = {
|
||||
query: userMessage,
|
||||
meta: {
|
||||
use_web: options.use_web
|
||||
},
|
||||
history: history.slice(0, -1), // 去掉最后一条刚添加的用户消息
|
||||
thread_id: threadId.value
|
||||
config: {
|
||||
...props.config
|
||||
}
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
@ -663,11 +642,6 @@ onMounted(async () => {
|
||||
|
||||
// 处理元数据
|
||||
const handleMetadata = (data) => {
|
||||
// 更新线程ID
|
||||
if (data.metadata?.thread_id && !threadId.value) {
|
||||
threadId.value = data.metadata.thread_id;
|
||||
}
|
||||
|
||||
// 检查并更新运行ID
|
||||
if (data.metadata?.run_id && !currentRunId.value) {
|
||||
currentRunId.value = data.metadata.run_id;
|
||||
@ -707,16 +681,6 @@ const loadState = () => {
|
||||
|
||||
console.log("loadState with prefix:", storagePrefix);
|
||||
|
||||
// 加载设置选项
|
||||
const savedOptions = localStorage.getItem(`${storagePrefix}-options`);
|
||||
if (savedOptions) {
|
||||
try {
|
||||
Object.assign(options, JSON.parse(savedOptions));
|
||||
} catch (e) {
|
||||
console.error('解析选项数据出错:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载消息历史
|
||||
const savedMessages = localStorage.getItem(`${storagePrefix}-messages`);
|
||||
if (savedMessages) {
|
||||
@ -737,8 +701,8 @@ const loadState = () => {
|
||||
// 加载线程ID
|
||||
const savedThreadId = localStorage.getItem(`${storagePrefix}-thread-id`);
|
||||
if (savedThreadId) {
|
||||
threadId.value = savedThreadId;
|
||||
console.log(`加载线程ID (${storagePrefix}):`, threadId.value);
|
||||
currentRunId.value = savedThreadId;
|
||||
console.log(`加载线程ID (${storagePrefix}):`, currentRunId.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从localStorage加载状态出错:', error);
|
||||
@ -754,7 +718,7 @@ watch(() => props.agentId, async (newAgentId, oldAgentId) => {
|
||||
if (newAgentId !== oldAgentId) {
|
||||
// 重置会话
|
||||
messages.value = [];
|
||||
threadId.value = null;
|
||||
currentRunId.value = null;
|
||||
resetStatusSteps();
|
||||
|
||||
// 加载新的智能体数据
|
||||
@ -819,9 +783,6 @@ const saveState = () => {
|
||||
localStorage.setItem(`${prefix}-current-agent`, currentAgent.value.name);
|
||||
}
|
||||
|
||||
// 保存设置选项
|
||||
localStorage.setItem(`${prefix}-options`, JSON.stringify(options));
|
||||
|
||||
// 保存消息历史
|
||||
if (messages.value && messages.value.length > 0) {
|
||||
console.log(`保存消息历史 (${prefix}):`, messages.value.length);
|
||||
@ -831,9 +792,9 @@ const saveState = () => {
|
||||
}
|
||||
|
||||
// 保存线程ID
|
||||
if (threadId.value) {
|
||||
localStorage.setItem(`${prefix}-thread-id`, threadId.value);
|
||||
console.log(`保存线程ID (${prefix}):`, threadId.value);
|
||||
if (currentRunId.value) {
|
||||
localStorage.setItem(`${prefix}-thread-id`, currentRunId.value);
|
||||
console.log(`保存线程ID (${prefix}):`, currentRunId.value);
|
||||
} else {
|
||||
localStorage.removeItem(`${prefix}-thread-id`);
|
||||
}
|
||||
@ -842,8 +803,12 @@ const saveState = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const sayHi = () => {
|
||||
message.success(`Hi, I am ${currentAgent.value.name}, ${currentAgent.value.description}`);
|
||||
}
|
||||
|
||||
// 监听状态变化并保存
|
||||
watch([currentAgent, options, messages, threadId], () => {
|
||||
watch([currentAgent, messages, currentRunId], () => {
|
||||
try {
|
||||
saveState();
|
||||
} catch (error) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="agent-view">
|
||||
<!-- 左侧智能体列表侧边栏 -->
|
||||
<div class="sidebar" :class="{ 'is-open': state.isSidebarOpen }">
|
||||
<h2 class="sidebar-title">
|
||||
智能体列表
|
||||
@ -37,21 +38,113 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间内容区域 -->
|
||||
<div class="content">
|
||||
<AgentChatComponent :agent-id="selectedAgentId">
|
||||
<AgentChatComponent
|
||||
:agent-id="selectedAgentId"
|
||||
:config="agentConfig"
|
||||
@open-config="toggleConfigSidebar(true)"
|
||||
>
|
||||
<template #header-left>
|
||||
<div class="toggle-sidebar nav-btn" @click="toggleSidebar" v-if="!state.isSidebarOpen">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
</div>
|
||||
</template>
|
||||
<template #header-right>
|
||||
<div class="toggle-sidebar nav-btn" @click="toggleConfigSidebar()">
|
||||
<SettingOutlined class="iconfont icon-20" />
|
||||
<span class="text">配置</span>
|
||||
</div>
|
||||
</template>
|
||||
</AgentChatComponent>
|
||||
</div>
|
||||
|
||||
<!-- 右侧配置侧边栏 -->
|
||||
<div class="config-sidebar" :class="{ 'is-open': state.isConfigSidebarOpen }">
|
||||
<h2 class="sidebar-title">
|
||||
智能体配置
|
||||
<div class="toggle-sidebar" @click="toggleConfigSidebar(false)">
|
||||
<CloseOutlined class="iconfont icon-20" />
|
||||
</div>
|
||||
</h2>
|
||||
<div v-if="selectedAgentId && configSchema" class="config-form">
|
||||
<!-- 配置表单 -->
|
||||
<a-form :model="agentConfig" layout="vertical">
|
||||
<!-- 系统提示词 -->
|
||||
|
||||
<div class="empty-config" v-if="state.isEmptyConfig">
|
||||
<a-alert type="warning" message="该智能体没有配置项" show-icon/>
|
||||
</div>
|
||||
<a-form-item v-if="configSchema.system_prompt" label="系统提示词" name="system_prompt">
|
||||
<a-textarea
|
||||
v-model:value="agentConfig.system_prompt"
|
||||
:rows="4"
|
||||
placeholder="设置智能体的系统提示词"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<a-form-item v-if="configSchema.model" :label="`模型 (默认: ${configSchema.model})`" name="model">
|
||||
<a-input
|
||||
v-model:value="agentConfig.model"
|
||||
placeholder="provider/model-name"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 工具选择: 多选,默认全选 -->
|
||||
<a-form-item v-if="configSchema.tools" :label="`工具`" name="tools">
|
||||
<a-select
|
||||
v-model:value="agentConfig.tools"
|
||||
placeholder="选择工具"
|
||||
mode="multiple"
|
||||
>
|
||||
<a-select-option v-for="tool in configSchema.tools" :key="tool">
|
||||
{{ tool }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 其他配置项,可按需扩展 -->
|
||||
<div v-if="Object.keys(additionalConfig).length > 0">
|
||||
<a-divider>其他配置</a-divider>
|
||||
<a-form-item
|
||||
v-for="(value, key) in additionalConfig"
|
||||
:key="key"
|
||||
:label="key"
|
||||
:name="key"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="additionalConfig[key]"
|
||||
:placeholder="`设置${key}`"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 保存和重置按钮 -->
|
||||
<div class="form-actions" v-if="!state.isEmptyConfig">
|
||||
<a-button type="primary" @click="saveConfig">保存配置</a-button>
|
||||
<a-button @click="resetConfig">重置</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else class="no-agent-selected">
|
||||
请先选择一个智能体
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, reactive, watch } from 'vue';
|
||||
import { RobotOutlined, MenuFoldOutlined, MenuUnfoldOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { ref, onMounted, reactive, watch, computed, h } from 'vue';
|
||||
import {
|
||||
RobotOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
CloseOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||
|
||||
// 状态
|
||||
@ -59,7 +152,80 @@ const agents = ref({});
|
||||
const selectedAgentId = ref(null);
|
||||
const state = reactive({
|
||||
isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'),
|
||||
isConfigSidebarOpen: false,
|
||||
isEmptyConfig: computed(() => Object.keys(agents.value[selectedAgentId.value]?.config_schema || {}).length === 0),
|
||||
});
|
||||
const configSchema = computed(() => agents.value[selectedAgentId.value]?.config_schema || {});
|
||||
|
||||
// 配置状态
|
||||
const agentConfig = ref({
|
||||
system_prompt: '',
|
||||
model: '',
|
||||
debug_mode: false,
|
||||
});
|
||||
|
||||
// 存储额外配置项
|
||||
const additionalConfig = ref({});
|
||||
|
||||
// 根据选中的智能体加载配置
|
||||
const loadAgentConfig = () => {
|
||||
if (!selectedAgentId.value || !agents.value[selectedAgentId.value]) return;
|
||||
|
||||
const agent = agents.value[selectedAgentId.value];
|
||||
const configSchema = agent.config_schema || {};
|
||||
|
||||
// 重置配置
|
||||
agentConfig.value = {
|
||||
system_prompt: configSchema.system_prompt || '',
|
||||
model: configSchema.model || '',
|
||||
// 默认全选所有工具
|
||||
tools: configSchema.tools || [],
|
||||
};
|
||||
|
||||
// 清空额外配置
|
||||
additionalConfig.value = {};
|
||||
|
||||
// 加载存储的配置
|
||||
const savedConfig = JSON.parse(localStorage.getItem(`agent-config-${selectedAgentId.value}`) || '{}');
|
||||
|
||||
// 合并已保存的配置
|
||||
if (savedConfig) {
|
||||
Object.assign(agentConfig.value, savedConfig);
|
||||
}
|
||||
|
||||
// 如果没有保存过tools配置,则默认全选
|
||||
if (!savedConfig.tools && configSchema.tools) {
|
||||
agentConfig.value.tools = [...configSchema.tools];
|
||||
}
|
||||
|
||||
// 加载额外配置项
|
||||
Object.keys(configSchema).forEach(key => {
|
||||
if (!['system_prompt', 'model', 'tools'].includes(key)) {
|
||||
additionalConfig.value[key] = savedConfig[key] || configSchema[key] || '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = () => {
|
||||
// 合并所有配置
|
||||
const fullConfig = {
|
||||
...agentConfig.value,
|
||||
...additionalConfig.value
|
||||
};
|
||||
|
||||
// 保存配置到本地存储
|
||||
localStorage.setItem(`agent-config-${selectedAgentId.value}`, JSON.stringify(fullConfig));
|
||||
|
||||
// 提示保存成功
|
||||
message.success('配置已保存');
|
||||
};
|
||||
|
||||
// 重置配置
|
||||
const resetConfig = () => {
|
||||
loadAgentConfig();
|
||||
message.info('配置已重置');
|
||||
};
|
||||
|
||||
// 监听侧边栏状态变化并保存到localStorage
|
||||
watch(
|
||||
@ -69,6 +235,14 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
// 监听智能体选择变化
|
||||
watch(
|
||||
() => selectedAgentId.value,
|
||||
() => {
|
||||
loadAgentConfig();
|
||||
}
|
||||
);
|
||||
|
||||
// 获取智能体列表
|
||||
const fetchAgents = async () => {
|
||||
try {
|
||||
@ -81,6 +255,11 @@ const fetchAgents = async () => {
|
||||
return acc;
|
||||
}, {});
|
||||
console.log("agents", agents.value);
|
||||
|
||||
// 加载当前选中智能体的配置
|
||||
if (selectedAgentId.value) {
|
||||
loadAgentConfig();
|
||||
}
|
||||
} else {
|
||||
console.error('获取智能体失败');
|
||||
}
|
||||
@ -89,25 +268,27 @@ const fetchAgents = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 切换侧边栏
|
||||
// 切换左侧侧边栏
|
||||
const toggleSidebar = () => {
|
||||
state.isSidebarOpen = !state.isSidebarOpen;
|
||||
};
|
||||
|
||||
// 切换配置侧边栏
|
||||
const toggleConfigSidebar = (forceOpen) => {
|
||||
if (forceOpen !== undefined) {
|
||||
state.isConfigSidebarOpen = forceOpen;
|
||||
} else {
|
||||
state.isConfigSidebarOpen = !state.isConfigSidebarOpen;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择智能体
|
||||
const selectAgent = (agentId) => {
|
||||
selectedAgentId.value = agentId;
|
||||
// 保存选择到本地存储
|
||||
localStorage.setItem('last-selected-agent', agentId);
|
||||
};
|
||||
|
||||
// 生成智能体头像颜色
|
||||
const getAgentColor = (name) => {
|
||||
// 简单的哈希函数生成颜色
|
||||
const hash = name.split('').reduce((acc, char) => {
|
||||
return char.charCodeAt(0) + ((acc << 5) - acc);
|
||||
}, 0);
|
||||
return `hsl(${Math.abs(hash) % 360}, 70%, 60%)`;
|
||||
// 加载该智能体的配置
|
||||
loadAgentConfig();
|
||||
};
|
||||
|
||||
// 初始化
|
||||
@ -123,6 +304,9 @@ onMounted(async () => {
|
||||
// 默认选择第一个智能体
|
||||
selectedAgentId.value = Object.keys(agents.value)[0];
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
loadAgentConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -133,6 +317,7 @@ onMounted(async () => {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
--agent-sidebar-width: 230px;
|
||||
--config-sidebar-width: 350px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@ -150,6 +335,44 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 配置侧边栏样式
|
||||
.config-sidebar {
|
||||
width: 0;
|
||||
max-width: var(--config-sidebar-width);
|
||||
border-left: 1px solid var(--main-light-3);
|
||||
background-color: var(--bg-sider);
|
||||
box-sizing: content-box;
|
||||
overflow-y: auto;
|
||||
transition: width 0.3s ease;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
|
||||
&.is-open {
|
||||
width: var(--config-sidebar-width);
|
||||
}
|
||||
|
||||
.config-form {
|
||||
padding: 16px;
|
||||
min-width: calc(var(--config-sidebar-width) - 16px);
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.no-agent-selected {
|
||||
padding: 16px;
|
||||
color: var(--gray-500);
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
@ -283,6 +506,21 @@ onMounted(async () => {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-sidebar {
|
||||
position: absolute;
|
||||
z-index: 101;
|
||||
right: 0;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border-radius: 16px 0 0 16px;
|
||||
box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&.is-open {
|
||||
width: 90%;
|
||||
max-width: var(--config-sidebar-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user