Skip to content

Commit 5427c8e

Browse files
committed
feat(chatengine): doc refine
1 parent 8b4ddb4 commit 5427c8e

File tree

15 files changed

+626
-1524
lines changed

15 files changed

+626
-1524
lines changed

packages/pro-components/chat/chat-engine/_example/agui-comprehensive.tsx

Lines changed: 152 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import {
99
ToolCallRenderer,
1010
useAgentToolcall,
1111
useChat,
12-
AgentStateProvider,
12+
useAgentState,
1313
} from '@tdesign-react/chat';
14-
import { CheckCircleFilledIcon, TimeFilledIcon, ErrorCircleFilledIcon } from 'tdesign-icons-react';
14+
import { CheckCircleFilledIcon, TimeFilledIcon, ErrorCircleFilledIcon, LoadingIcon } from 'tdesign-icons-react';
1515
import type {
1616
TdChatMessageConfig,
1717
TdChatSenderParams,
@@ -89,10 +89,9 @@ const PlanningSteps: React.FC<ToolcallComponentProps<PlanningArgs>> = ({
8989
respond,
9090
agentState,
9191
}) => {
92-
// 直接使用注入的 agentState,无需额外 Hook
93-
const planningState = agentState?.[args?.taskId] || {};
94-
const items = planningState?.items || [];
95-
92+
// 因为配置了 subscribeKey,agentState 已经是 taskId 对应的状态对象
93+
const planningState = agentState || {};
94+
9695
const isComplete = status === 'complete';
9796

9897
React.useEffect(() => {
@@ -101,49 +100,21 @@ const PlanningSteps: React.FC<ToolcallComponentProps<PlanningArgs>> = ({
101100
}
102101
}, [isComplete, respond]);
103102

104-
const getStatusIcon = (itemStatus: string) => {
105-
switch (itemStatus) {
106-
case 'completed':
107-
return <CheckCircleFilledIcon style={{ color: '#00a870' }} />;
108-
case 'running':
109-
return <TimeFilledIcon style={{ color: '#0052d9' }} />;
110-
case 'failed':
111-
return <ErrorCircleFilledIcon style={{ color: '#e34d59' }} />;
112-
default:
113-
return <TimeFilledIcon style={{ color: '#bbbbbb' }} />;
114-
}
115-
};
116-
117103
return (
118104
<Card bordered style={{ marginTop: 8 }}>
119105
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 12 }}>
120106
正在为您规划 {args?.destination} {args?.days}日游
121107
</div>
122108

123-
{/* 进度条 */}
109+
{/* 只保留进度条 */}
124110
{planningState?.progress !== undefined && (
125-
<div style={{ marginBottom: 16 }}>
111+
<div>
126112
<Progress percentage={planningState.progress} />
127113
<div style={{ fontSize: 12, color: '#888', marginTop: 4 }}>
128114
{planningState.message || '规划中...'}
129115
</div>
130116
</div>
131117
)}
132-
133-
{/* 步骤列表 */}
134-
{items.length > 0 && (
135-
<Space direction="vertical" size="small" style={{ width: '100%' }}>
136-
{items.map((item: any, index: number) => (
137-
<div key={index} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
138-
{getStatusIcon(item.status)}
139-
<span style={{ flex: 1 }}>{item.label}</span>
140-
<Tag theme={item.status === 'completed' ? 'success' : 'default'} size="small">
141-
{item.status}
142-
</Tag>
143-
</div>
144-
))}
145-
</Space>
146-
)}
147118
</Card>
148119
);
149120
};
@@ -152,6 +123,7 @@ const PlanningSteps: React.FC<ToolcallComponentProps<PlanningArgs>> = ({
152123
const UserPreferencesForm: React.FC<ToolcallComponentProps<UserPreferencesArgs, any, UserPreferencesResponse>> = ({
153124
status,
154125
respond,
126+
result,
155127
}) => {
156128
const [budget, setBudget] = useState(5000);
157129
const [interests, setInterests] = useState<string[]>(['美食', '文化']);
@@ -165,10 +137,23 @@ const UserPreferencesForm: React.FC<ToolcallComponentProps<UserPreferencesArgs,
165137
});
166138
};
167139

168-
if (status === 'complete') {
140+
if (status === 'complete' && result) {
169141
return (
170142
<Card bordered style={{ marginTop: 8 }}>
171-
<div style={{ color: '#00a870' }}>✓ 已收到您的偏好设置</div>
143+
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: '#00a870' }}>
144+
✓ 已收到您的偏好设置
145+
</div>
146+
<Space direction="vertical" size="small">
147+
<div style={{ fontSize: 12, color: '#666' }}>
148+
预算:¥{result.budget}
149+
</div>
150+
<div style={{ fontSize: 12, color: '#666' }}>
151+
兴趣:{result.interests.join('、')}
152+
</div>
153+
<div style={{ fontSize: 12, color: '#666' }}>
154+
住宿:{result.accommodation}
155+
</div>
156+
</Space>
172157
</Card>
173158
);
174159
}
@@ -222,14 +207,118 @@ const UserPreferencesForm: React.FC<ToolcallComponentProps<UserPreferencesArgs,
222207
);
223208
};
224209

210+
// ==================== 外部进度面板组件 ====================
211+
212+
/**
213+
* 右侧进度面板组件
214+
* 演示如何在对话组件外部使用 useAgentState 获取状态
215+
*
216+
* 💡 使用场景:展示规划行程的详细子步骤(从后端 STATE_DELTA 事件推送)
217+
*
218+
* 实现方式:
219+
* 1. 使用 useAgentState 订阅状态更新
220+
* 2. 从 stateMap 中获取规划步骤的详细进度
221+
*/
222+
const ProgressPanel: React.FC = () => {
223+
// 使用 useAgentState 订阅状态更新
224+
const { stateMap, currentStateKey } = useAgentState();
225+
226+
// 获取规划状态
227+
const planningState = useMemo(() => {
228+
if (!currentStateKey || !stateMap[currentStateKey]) {
229+
return null;
230+
}
231+
return stateMap[currentStateKey];
232+
}, [stateMap, currentStateKey]);
233+
234+
// 如果没有规划状态,不显示面板
235+
if (!planningState || !planningState.items || planningState.items.length === 0) {
236+
return null;
237+
}
238+
239+
const items = planningState.items || [];
240+
const completedCount = items.filter((item: any) => item.status === 'completed').length;
241+
const totalCount = items.length;
242+
243+
// 如果所有步骤都完成了,隐藏面板
244+
if (completedCount === totalCount && totalCount > 0) {
245+
return null;
246+
}
247+
248+
const getStatusIcon = (itemStatus: string) => {
249+
switch (itemStatus) {
250+
case 'completed':
251+
return <CheckCircleFilledIcon style={{ color: '#00a870', fontSize: '14px' }} />;
252+
case 'running':
253+
return <LoadingIcon style={{ color: '#0052d9', fontSize: '14px' }} />;
254+
case 'failed':
255+
return <ErrorCircleFilledIcon style={{ color: '#e34d59', fontSize: '14px' }} />;
256+
default:
257+
return <TimeFilledIcon style={{ color: '#bbbbbb', fontSize: '14px' }} />;
258+
}
259+
};
260+
261+
return (
262+
<div style={{
263+
position: 'fixed',
264+
right: '200px',
265+
top: '50%',
266+
transform: 'translateY(-50%)',
267+
width: '200px',
268+
background: '#fff',
269+
padding: '16px',
270+
borderRadius: '8px',
271+
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
272+
border: '1px solid #e7e7e7',
273+
zIndex: 1000,
274+
}}>
275+
<div style={{
276+
marginBottom: '12px',
277+
paddingBottom: '8px',
278+
borderBottom: '1px solid #e7e7e7'
279+
}}>
280+
<div style={{ fontSize: '14px', fontWeight: 600, color: '#000', marginBottom: '4px' }}>
281+
规划进度
282+
</div>
283+
<Tag theme="primary" variant="light" size="small">
284+
{completedCount}/{totalCount}
285+
</Tag>
286+
</div>
287+
288+
{/* 步骤列表 */}
289+
<Space direction="vertical" size="small" style={{ width: '100%' }}>
290+
{items.map((item: any, index: number) => (
291+
<div key={index} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
292+
{getStatusIcon(item.status)}
293+
<span style={{
294+
flex: 1,
295+
fontSize: '12px',
296+
color: item.status === 'completed' ? '#00a870' : (item.status === 'running' ? '#0052d9' : '#666'),
297+
fontWeight: item.status === 'running' ? 600 : 400,
298+
}}>
299+
{item.label}
300+
</span>
301+
</div>
302+
))}
303+
</Space>
304+
</div>
305+
);
306+
};
307+
225308
// ==================== 主组件 ====================
226309
const TravelPlannerContent: React.FC = () => {
227-
const listRef = useRef<TdChatListApi>(null);
228-
const inputRef = useRef<TdChatSenderApi>(null);
310+
const listRef = useRef<TdChatListApi & HTMLElement>(null);
311+
const inputRef = useRef<TdChatSenderApi & HTMLElement>(null);
229312
const [inputValue, setInputValue] = useState<string>('请为我规划一个北京3日游行程');
230313

231-
// 注册工具配置(利用 agentState 注入)
314+
// 注册工具配置
232315
useAgentToolcall([
316+
{
317+
name: 'collect_user_preferences',
318+
description: '收集用户偏好',
319+
parameters: [{ name: 'destination', type: 'string', required: true }],
320+
component: UserPreferencesForm as any,
321+
},
233322
{
234323
name: 'query_weather',
235324
description: '查询目的地天气',
@@ -245,12 +334,8 @@ const TravelPlannerContent: React.FC = () => {
245334
{ name: 'taskId', type: 'string', required: true },
246335
],
247336
component: PlanningSteps as any,
248-
},
249-
{
250-
name: 'collect_user_preferences',
251-
description: '收集用户偏好',
252-
parameters: [{ name: 'destination', type: 'string', required: true }],
253-
component: UserPreferencesForm as any,
337+
// 配置 subscribeKey,让组件订阅对应 taskId 的状态
338+
subscribeKey: (props) => props.args?.taskId,
254339
},
255340
]);
256341

@@ -290,17 +375,20 @@ const TravelPlannerContent: React.FC = () => {
290375
// 处理工具调用响应
291376
const handleToolCallRespond = useCallback(
292377
async (toolcall: ToolCall, response: any) => {
293-
const tools = chatEngine.getToolcallByName(toolcall.toolCallName) || {};
294-
await chatEngine.sendAIMessage({
295-
params: {
296-
toolCallMessage: {
297-
...tools,
298-
result: JSON.stringify(response),
378+
// 判断如果是手机用户偏好的响应,则使用 toolcall 中的信息来构建新的请求
379+
if (toolcall.toolCallName === 'collect_user_preferences') {
380+
await chatEngine.sendAIMessage({
381+
params: {
382+
toolCallMessage: {
383+
toolCallId: toolcall.toolCallId,
384+
toolCallName: toolcall.toolCallName,
385+
result: JSON.stringify(response),
386+
},
299387
},
300-
},
301-
sendRequest: true,
302-
});
303-
listRef.current?.scrollList({ to: 'bottom' });
388+
sendRequest: true,
389+
});
390+
listRef.current?.scrollList({ to: 'bottom' });
391+
}
304392
},
305393
[chatEngine],
306394
);
@@ -320,17 +408,6 @@ const TravelPlannerContent: React.FC = () => {
320408
[handleToolCallRespond],
321409
);
322410

323-
// 操作栏
324-
const actionHandler = (name: string) => {
325-
switch (name) {
326-
case 'replay':
327-
chatEngine.regenerateAIMessage();
328-
break;
329-
default:
330-
console.log('触发操作', name);
331-
}
332-
};
333-
334411
const renderMsgContents = (message: ChatMessagesData) => (
335412
<>
336413
{message.content?.map((item: any, index: number) => renderMessageContent(item, index))}
@@ -344,10 +421,13 @@ const TravelPlannerContent: React.FC = () => {
344421
};
345422

346423
return (
347-
<div style={{ height: '400px', display: 'flex', flexDirection: 'column' }}>
424+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', position: 'relative' }}>
425+
{/* 右侧进度面板:使用 useAgentState 订阅状态 */}
426+
<ProgressPanel />
427+
348428
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
349429
<ChatList ref={listRef}>
350-
{messages.map((message, idx) => (
430+
{messages.map((message) => (
351431
<ChatMessage key={message.id} {...messageProps[message.role]} message={message}>
352432
{renderMsgContents(message)}
353433
</ChatMessage>
@@ -367,11 +447,5 @@ const TravelPlannerContent: React.FC = () => {
367447
);
368448
};
369449

370-
// 使用 Provider 包裹
371-
export default function TravelPlannerChat() {
372-
return (
373-
<AgentStateProvider initialState={{}}>
374-
<TravelPlannerContent />
375-
</AgentStateProvider>
376-
);
377-
}
450+
// 导出主组件(不需要 Provider,因为 useAgentState 内部已处理)
451+
export default TravelPlannerContent;

0 commit comments

Comments
 (0)