📌 本章目标:了解工作台的更多业务模块,学习模块化设计思路
⭐⭐⭐ 难度:进阶 | ⏱️ 预计时间:2 小时
10.1 工作台不止聊天
前面我们重点讲了聊天、执行面板和审批,但一个完整的 Agent 工作台远不止这些。
一个企业级的 Agent 工作台通常还包括:
| 模块 | 功能 | 重要程度 |
|---|---|---|
| 定时任务 | 让 Agent 定时自动干活 | ⭐⭐⭐⭐⭐ |
| 记忆管理 | Agent 的知识和记忆 | ⭐⭐⭐⭐ |
| 设置面板 | Agent 配置、个人设置 | ⭐⭐⭐⭐⭐ |
| 技能市场 | 给 Agent 安装新技能 | ⭐⭐⭐ |
| 文件管理 | Agent 可访问的文件 | ⭐⭐⭐ |
| 嵌入模式 | 把对话嵌到其他系统里 | ⭐⭐⭐ |
本章我们挑几个重要的模块讲一讲,了解它们的设计思路。不用每个都实现得很完整,重点是理解「为什么这么设计」。
10.2 定时任务(Cron)
什么是定时任务?
让 Agent 按照设定的时间自动执行任务,比如:
- 每天早上 9 点巡检服务器
- 每周一早上生成周报
- 每小时检查一次数据备份
Cron 表达式基础
Cron 表达式是用来描述定时规则的标准格式,由 6-7 个字段组成:
bash
秒 分 时 日 月 周 (年)
例:
0 0 9 * * * → 每天早上 9 点
0 30 8 * * 1-5 → 周一到周五早上 8:30
0 0 * / 2 * * → 每 2 小时
💡 小贴士:Cron 表达式对新手不友好,所以我们通常用可视化的 Cron 编辑器,让用户通过点选来设置,自动生成表达式。
定时任务的数据结构
typescript
interface CronTask {
id: string;
name: string; // 任务名称
description: string; // 任务描述
agentId: number; // 属于哪个 Agent
cronExpression: string; // Cron 表达式
prompt: string; // 执行时发送给 Agent 的 prompt
enabled: boolean; // 是否启用
createdAt: string;
updatedAt: string;
lastRunAt?: string; // 上次执行时间
nextRunAt?: string; // 下次执行时间
lastRunStatus?: 'success' | 'failed' | 'running';
}
功能模块划分
bash
定时任务模块
├── 任务列表(侧边栏展示)
├── 创建/编辑弹窗
│ ├── Cron 可视化编辑器
│ └── Prompt 输入
├── 任务详情页
│ ├── 任务信息
│ └── 执行历史
└── 手动触发(立即执行一次)
Cron 编辑器的实现
推荐用现成的库,不用自己写:
| 库 | 说明 |
|---|---|
react-js-cron |
可视化 Cron 编辑器,轻量好用 |
cronstrue |
把 Cron 表达式转成人类可读的文字(如「每天上午 9 点」) |
bash
npm install react-js-cron cronstrue
基本用法:
tsx
import { Cron } from 'react-js-cron';
import cronstrue from 'cronstrue/i18n/zh_CN';
import 'react-js-cron/dist/styles.css';
function CronEditor() {
const [value, setValue] = useState('0 0 9 * * *');
return (
<div>
<Cron value={value} onChange={setValue} />
<p>执行频率:{cronstrue.toString(value)}</p>
</div>
);
}
CronStore 设计
typescript
class CronStore {
tasks: CronTask[] = [];
loading = false;
keyword = '';
pendingEditTask: CronTask | null = null;
constructor() {
makeAutoObservable(this);
}
get filteredTasks(): CronTask[] {
if (!this.keyword) return this.tasks;
const kw = this.keyword.toLowerCase();
return this.tasks.filter(
t => t.name.toLowerCase().includes(kw) || t.description.toLowerCase().includes(kw)
);
}
async fetchTasks(agentId: number) { /* ... */ }
async createTask(agentId: number, data: CreateCronInput) { /* ... */ }
async updateTask(taskId: string, data: UpdateCronInput) { /* ... */ }
async deleteTask(taskId: string) { /* ... */ }
async toggleTask(taskId: string, enabled: boolean) { /* ... */ }
async triggerTask(taskId: string) { /* 立即执行一次 */ }
}
10.3 记忆管理(Memory)
什么是 Agent 记忆?
Agent 的记忆就是它「知道的事情」。有了记忆,Agent 就能:
- 记住你的偏好(比如「我喜欢用 Markdown 格式输出」)
- 记住你是谁、做什么工作
- 从过去的对话中学习和积累知识
记忆的分类
| 类型 | 说明 | 存储位置 | 生命周期 |
|---|---|---|---|
| 短期记忆 | 当前对话的上下文 | 会话内 | 对话结束就没了 |
| 用户画像 | 关于用户的长期认知 | 用户级 | 长期保存 |
| Agent 记忆 | Agent 自身的知识和经验 | Agent 级 | 长期保存 |
| 全局知识库 | 所有 Agent 共享的知识 | 全局 | 长期保存 |
记忆的可视化展示
记忆不只是列个表,可以做得很「科幻」:
| 视图 | 效果 | 适用场景 |
|---|---|---|
| 表格视图 | 普通的列表/表格 | 查看全部、管理操作 |
| 时间线视图 | 按时间顺序展示记忆的形成 | 了解记忆的演变 |
| 星座图视图 | 2D Canvas 上的节点和连线 | 展示记忆之间的关联,很有科技感 |
🎯 设计理念:记忆模块不仅是功能,也是产品的「科技感」来源。用户看到密密麻麻的记忆节点和连线,会觉得「这个 Agent 真的在学习和积累」。
记忆 Store 设计
typescript
interface MemoryItem {
id: string;
content: string; // 记忆内容
source: string; // 来源:user_input / agent_generated / imported
category?: string; // 分类
tags: string[]; // 标签
importance: number; // 重要程度 0-1
createdAt: string;
updatedAt: string;
}
class MemoryStore {
memories: MemoryItem[] = [];
loading = false;
viewMode: 'table' | 'timeline' | 'constellation' = 'table';
selectedCategory: string | null = null;
constructor() {
makeAutoObservable(this);
}
async fetchMemories(agentId: number) { /* ... */ }
async addMemory(agentId: number, content: string) { /* ... */ }
async updateMemory(id: string, content: string) { /* ... */ }
async deleteMemory(id: string) { /* ... */ }
}
10.4 设置面板
设置面板有什么?
设置面板是所有配置的入口,通常用 Tab 切换不同的设置分类:
bash
设置面板
├── 个人资料
│ ├── 用户信息
│ ├── 显示设置(主题、语言)
│ └── 通知设置
├── Agent 设置
│ ├── 基本信息(名称、描述、头像)
│ ├── 忙时策略(忙的时候怎么处理新请求)
│ ├── 安全模式(权限等级)
│ ├── 技能管理(安装/卸载/启用/停用)
│ ├── 工具集配置
│ └── 文件权限
├── Token 管理
│ ├── Token 列表
│ ├── 创建新 Token
│ └── 删除 Token
└── 关于
├── 版本信息
└── 更新日志
Token 管理是什么?
Token 是其他系统调用你的 Agent 时的「钥匙」。
bash
其他系统 ──Token──▶ 你的 Agent
(比如工单系统) (验证身份)
Token 管理的典型功能:
- 生成新 Token(给它起个名字,知道是给谁用的)
- 查看 Token 列表
- 删除 Token(吊销权限)
- Token 显示用掩码,只在创建时显示一次明文
忙时策略
当 Agent 正在处理一个任务,又来新任务了怎么办?
| 策略 | 说明 |
|---|---|
| 排队 | 新任务排队等,一个一个来 |
| 并行 | 同时处理多个任务(消耗更多资源) |
| 拒绝 | 忙的时候直接拒绝新任务 |
| 转交 | 转交给另一个空闲的 Agent |
10.5 技能(Skill)
什么是技能?
技能是 Agent 能力的扩展包。就像手机的 App 一样,安装不同的技能,Agent 就能干不同的事。
比如:
- 「数据库查询」技能:让 Agent 能查数据库
- 「邮件发送」技能:让 Agent 能发邮件
- 「Excel 处理」技能:让 Agent 能操作 Excel 文件
技能和工具的区别
| 概念 | 说明 | 粒度 |
|---|---|---|
| 工具(Tool) | 单个可调用的能力,如 exec 执行命令 |
小 |
| 技能(Skill) | 一组相关工具 + 使用说明 + 配置,如「数据库技能」 | 大 |
一个技能可以包含多个工具和一组使用说明(告诉 Agent 什么时候用、怎么用)。
技能市场
用户可以从「技能市场」浏览和安装新技能:
bash
技能市场
├── 分类浏览(办公/运维/开发/...)
├── 搜索
├── 技能详情页
│ ├── 介绍
│ ├── 功能说明
│ └── 安装按钮
└── 已安装管理
├── 启用/停用
└── 卸载
10.6 嵌入模式(Chat Embed)
什么是嵌入模式?
把 Agent 对话能力作为一个组件,嵌入到其他系统里。比如:
- 工单系统里,每个工单旁边有个 AI 助手
- 运维平台里,嵌一个 AI 运维助手
- 办公系统里,嵌一个 AI 办公助手
和主工作台的区别
| 对比项 | 主工作台 | 嵌入模式 |
|---|---|---|
| 布局 | 三栏完整布局 | 只有聊天窗口 |
| Agent 列表 | 有 | 没有(通常固定一个 Agent) |
| 侧边栏 | 有 | 没有 |
| 执行面板 | 有 | 可选 |
| 数据来源 | 普通 WS 通道 | 工单/业务 WS 通道 |
| 通信方式 | 直接和后端通信 | 通过宿主页面 postMessage |
嵌入模式的通信
嵌入页面和宿主页面通过 postMessage 通信:
typescript
// 嵌入页面发给宿主
window.parent.postMessage({
type: 'action_result',
data: { actionId: 'xxx', result: 'approved' }
}, '*');
// 接收宿主的消息
window.addEventListener('message', (event) => {
if (event.data.type === 'init_work_order') {
// 初始化工单上下文
}
});
10.7 模块化设计的心得
学了这么多模块,你可能会问:怎么保证这么多模块不混乱?
分享几个关键原则:
原则一:每个模块自己管自己
bash
好的结构: 不好的结构:
workbench/ workbench/
├── components/ ├── components/
│ ├── Sidebar/ │ ├── Sidebar.tsx
│ ├── Conversation/ │ ├── Conversation.tsx
│ ├── ExecPanel/ │ ├── ExecPanel.tsx
│ └── Settings/ │ └── Settings.tsx
├── store/ ├── store/
│ ├── AgentStore.ts │ └── index.ts (什么都往里塞)
│ ├── ConversationStore.ts
│ └── ...
└── services/
├── gateway/
└── ...
每个模块有自己的组件、自己的样式、自己的逻辑,想删就删、想改就改,不会牵连其他模块。
原则二:跨模块的事放根 Store
模块之间需要协调怎么办?比如「切换 Agent」会影响:
- AgentStore(选中状态)
- ConversationStore(会话列表)
- SessionStore(消息列表)
- ExecutionStore(执行日志)
这种跨模块的逻辑,放在根 Store 里统一处理。
原则三:数据流从上往下
bash
根 Store → 子 Store → 组件 → 子组件
数据和方法的调用方向永远是从上往下。子模块不要直接调用根 Store 的方法,根 Store 来协调子模块。
💡 类比:就像公司管理,CEO 协调各部门经理,部门经理管自己部门的人。部门经理不直接指挥别的部门的人,有事找 CEO 协调。
10.8 本章小结
你学到了什么?
- ✅ 了解了定时任务模块的设计(Cron 表达式、编辑器、任务管理)
- ✅ 了解了记忆管理模块(记忆分类、多种可视化视图)
- ✅ 了解了设置面板的结构(个人设置、Agent 设置、Token 管理)
- ✅ 了解了技能的概念(Agent 能力的扩展包)
- ✅ 了解了嵌入模式(把对话能力嵌入其他系统)
- ✅ 学到了模块化设计的三个核心原则
模块全景图
bash
AI Agent 工作台
├── 聊天会话(核心)
│ ├── 消息列表
│ ├── 输入框
│ └── 审批
├── 执行面板
│ ├── 输出
│ ├── 文件
│ └── 变更
├── 定时任务
├── 记忆管理
├── 设置面板
├── 技能管理
└── 嵌入模式
✏️ 动手练习
- 选一个你最感兴趣的模块(定时任务 / 记忆管理 / 设置面板),把它的 Store 和基础 UI 实现一下
- 给你的项目加一个设置页面,用 Tabs 切换不同的设置分类
- 思考一下:如果要加一个「消息搜索」功能,你会怎么设计?放在哪个模块?数据从哪来?
- 挑战:实现一个简单的 Cron 编辑器(可以用 react-js-cron)