feat(agent): 添加智能体元数据支持和示例问题展示

- 新增智能体元数据配置文件及图标资源
- 在聊天界面中显示智能体图标和示例问题
- 更新 changelog 中已完成的任务项
This commit is contained in:
Wenjie Zhang 2025-09-09 20:25:48 +08:00
parent 2acbd4b93a
commit 04f49f48d5
6 changed files with 113 additions and 7 deletions

View File

@ -1,7 +1,7 @@
目前已有的开发计划包括:
💭 **Features Todo**
- [ ] LangGraph 升级到 0.6+ 版本,并适配新特性,如 context 等,添加 MCP 工具的支持。
- [x] LangGraph 升级到 0.6+ 版本,并适配新特性,如 context 等,添加 MCP 工具的支持。
- [x] 支持动态工具配置的同时,将 Configuration替换为 Context 后能够正常使用
- [ ] 使用更好的知识图谱检索方法
- [ ] 使用其他的聊天记录管理方法,解决两个问题,一个是上下文长度过长,一个是上下文的类型变得更加丰富,比如多模态等等。(现在是基于 LangGraph 的 Memory 实现的v0.2.3 版本实现),暂定使用 [mem0](github.com/mem0ai/mem0) 来实现。但是目前了解下来,还不是我想要的那种方案。可能会基于这个实现一个 ThreadConvManager 这个类。
@ -24,7 +24,7 @@
下面的功能**可能**会放在后续版本实现,暂时未定
- [ ] 支持数据库查询功能
- [ ] 消息内容支持图片显示
- [x] 消息内容支持图片显示
- [ ] 添加 SQL 读取工具
- [ ] 添加绘图工具(这里的绘图是指绘制固定格式的图和表等,暂时没想好如何支持自定义绘图)
- [ ] 添加测试脚本,覆盖最常见的功能

View File

@ -2,6 +2,8 @@ import asyncio
import json
import traceback
import uuid
import yaml
from pathlib import Path
from fastapi import APIRouter, Body, Depends, HTTPException
from fastapi.responses import StreamingResponse
@ -97,7 +99,11 @@ async def get_agent(current_user: User = Depends(get_required_user)):
"""获取所有可用智能体(需要登录)"""
agents = await agent_manager.get_agents_info()
# logger.debug(f"agents: {agents}")
return {"agents": agents}
metadata = {}
if Path("src/static/agents_meta.yaml").exists():
with open("src/static/agents_meta.yaml") as f:
metadata = yaml.safe_load(f)
return {"agents": agents, "metadata": metadata}
@chat.post("/agent/{agent_id}")

View File

@ -0,0 +1,9 @@
ChatbotAgent:
name: 食品安全知识助手
icon: /agents-icon/chatbot.png
examples:
- 冰箱里不同食材应该怎么存放?
- 有机食品和普通食品有什么区别?
- 外卖食品安全需要注意什么?
- 如何判断食品是否已经变质?
- 食品添加剂对健康有害吗?

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

View File

@ -60,8 +60,12 @@
</div>
<div v-else-if="!conversations.length" class="chat-examples">
<h1>{{ currentAgent ? currentAgent.name : '请选择一个智能体开始对话' }}</h1>
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p>
<img v-if="currentAgentMetadata.icon" class="agent-icons" :src="currentAgentMetadata.icon" alt="智能体图标" />
<div v-else style="margin-bottom: 150px"></div>
<h1>您好我是{{ currentAgentName }}有什么可以帮您</h1>
<!-- <h1>{{ currentAgent ? currentAgent.name : '请选择一个智能体开始对话' }}</h1>
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p> -->
<div class="inputer-init">
<MessageInputComponent
v-model="userInput"
@ -72,6 +76,21 @@
@send="handleSendMessage"
@keydown="handleKeyDown"
/>
<!-- 示例问题 -->
<div class="example-questions" v-if="exampleQuestions.length > 0">
<div class="example-title">或试试这些问题</div>
<div class="example-chips">
<div
v-for="question in exampleQuestions"
:key="question.id"
class="example-chip"
@click="handleExampleClick(question.text)"
>
{{ question.text }}
</div>
</div>
</div>
</div>
</div>
<div class="chat-box" ref="messagesContainer">
@ -164,6 +183,15 @@ const {
// ==================== LOCAL CHAT & UI STATE ====================
const userInput = ref('');
//
const exampleQuestions = computed(() => {
const examples = currentAgentMetadata.value?.examples || [];
return examples.map((text, index) => ({
id: index + 1,
text: text
}));
});
const chatState = reactive({
currentThreadId: null,
isLoadingThreads: false,
@ -195,6 +223,9 @@ const currentAgentId = computed(() => {
}
});
const currentAgentMetadata = computed(() => agentStore.metadata[currentAgentId.value] || {});
const currentAgentName = computed(() => currentAgentMetadata?.value.name || currentAgent.name || '智能体');
const currentAgent = computed(() => agents.value[currentAgentId.value] || null);
const chatsList = computed(() => threads.value || []);
const currentChatId = computed(() => chatState.currentThreadId);
@ -674,6 +705,14 @@ const handleKeyDown = (e) => {
}
};
//
const handleExampleClick = (questionText) => {
userInput.value = questionText;
nextTick(() => {
handleSendMessage();
});
};
const shareChat = async () => {
if (!AgentValidator.validateShareOperation(currentChatId.value, currentAgent.value, handleValidationError)) return;
try {
@ -926,7 +965,7 @@ watch(conversations, () => {
padding: 0 50px;
text-align: center;
position: absolute;
top: 20%;
top: 15%;
width: 100%;
z-index: 9;
animation: slideInUp 0.5s ease-out;
@ -942,8 +981,58 @@ watch(conversations, () => {
color: var(--gray-700);
}
.agent-icons {
height: 180px;
}
.example-questions {
margin-top: 16px;
text-align: center;
.example-title {
font-size: 0.85rem;
color: var(--gray-600);
margin-bottom: 12px;
}
.example-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.example-chip {
padding: 6px 12px;
background: var(--gray-100);
border: 1px solid var(--gray-200);
border-radius: 16px;
cursor: pointer;
font-size: 0.8rem;
color: var(--gray-700);
transition: all 0.15s ease;
white-space: nowrap;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background: var(--main-50);
border-color: var(--main-200);
color: var(--main-700);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
}
}
.inputer-init {
margin: 40px auto;
margin: 20px auto;
width: 90%;
max-width: 800px;
}

View File

@ -7,6 +7,7 @@ export const useAgentStore = defineStore('agent', {
state: () => ({
// 智能体相关状态
agents: {}, // 以ID为键的智能体对象
metadata: {}, // 智能体元数据
selectedAgentId: null, // 当前选中的智能体ID
defaultAgentId: null, // 默认智能体ID
@ -102,6 +103,7 @@ export const useAgentStore = defineStore('agent', {
acc[agent.id] = agent;
return acc;
}, {});
this.metadata = response.metadata;
} catch (error) {
console.error('Failed to fetch agents:', error);
handleChatError(error, 'fetch');