127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import { app } from '@/agent/index.ts'
|
||
import { useStudioStore } from '../studio/store';
|
||
import { useShallow } from 'zustand/shallow';
|
||
import { useState } from 'react';
|
||
import { query } from '@/modules/query.ts'
|
||
import { QueryViewMessages } from '../query-view';
|
||
import { toast } from 'sonner';
|
||
export const Chat = () => {
|
||
const studioStore = useStudioStore(useShallow((state) => ({
|
||
routes: state.routes,
|
||
showRightPanel: state.showRightPanel,
|
||
setShowRightPanel: state.setShowRightPanel,
|
||
addMessage: state.addMessage,
|
||
})));
|
||
const [text, setText] = useState('');
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const onSend = async () => {
|
||
if (!text.trim() || isLoading) return;
|
||
setIsLoading(true);
|
||
const { routes } = studioStore;
|
||
let callPrompts = '';
|
||
const toolsList = routes.map((r, index) =>
|
||
`${index + 1}. 工具名称: ${r.id}\n 描述: ${r.description}`
|
||
).join('\n\n');
|
||
|
||
callPrompts = `你是一个 AI 助手,你可以使用以下工具来帮助用户完成任务:
|
||
|
||
${toolsList}
|
||
|
||
## 回复规则
|
||
1. 如果用户的请求可以使用上述工具完成,请返回 JSON 格式数据
|
||
2. 如果没有合适的工具,请直接分析并回答用户问题
|
||
|
||
## JSON 数据格式
|
||
\`\`\`json
|
||
{
|
||
"id": "工具的id",
|
||
"payload": {
|
||
// 工具所需的参数(如果需要)
|
||
// 例如: "id": "xxx", "name": "xxx"
|
||
}
|
||
}
|
||
\`\`\`
|
||
|
||
注意:
|
||
- payload 中包含工具执行所需的所有参数
|
||
- 如果工具不需要参数,payload 可以为空对象 {}
|
||
- 确保返回的 id 与上述工具列表中的工具名称完全匹配`
|
||
|
||
const res = await query.post({
|
||
path: 'ai',
|
||
payload: {
|
||
messages: [
|
||
{
|
||
role: 'system',
|
||
content: callPrompts
|
||
},
|
||
{
|
||
role: 'user',
|
||
content: text
|
||
}
|
||
],
|
||
isJson: true
|
||
}
|
||
})
|
||
setText('');
|
||
console.log('发送消息', text, res);
|
||
if (res.code === 200) {
|
||
// 处理返回结果
|
||
const payload = res.data?.action;
|
||
if (payload) {
|
||
const route = routes.find(r => r.id === payload.id);
|
||
const { path, key } = route || {};
|
||
const { id, ...otherParams } = payload.payload || {};
|
||
const action = { path, key, ...otherParams }
|
||
let response;
|
||
if (route) {
|
||
response = await app.run(action);
|
||
// toast.success('工具调用成功');
|
||
} else {
|
||
console.error('未找到对应工具', payload.id);
|
||
toast.error('未找到对应工具');
|
||
return
|
||
}
|
||
if (route?.metadata?.viewItem) {
|
||
// 自动打开右侧面板
|
||
if (!studioStore.showRightPanel) {
|
||
studioStore.setShowRightPanel(true);
|
||
}
|
||
const viewItem = route.metadata.viewItem;
|
||
viewItem.response = response;
|
||
viewItem.action = action;
|
||
viewItem.description = route.description || viewItem.description;
|
||
// @ts-ignore
|
||
viewItem._id = Date.now();
|
||
studioStore.addMessage(viewItem);
|
||
}
|
||
}
|
||
}
|
||
setIsLoading(false);
|
||
}
|
||
return <div className="h-full flex flex-col border-l border-gray-300 bg-white">
|
||
<div style={{ height: '3rem' }} className="flex items-center justify-between px-4 border-b border-gray-300 bg-gray-50">
|
||
<div className="text-sm text-gray-600">智能体</div>
|
||
</div>
|
||
<div style={{ height: 'calc(100% - 3rem)' }} className="overflow-auto">
|
||
<QueryViewMessages type="component" />
|
||
</div>
|
||
<div className="flex items-center gap-2 px-4 py-3 border-t border-gray-300 bg-white">
|
||
<input
|
||
type="text"
|
||
placeholder="输入消息..."
|
||
value={text}
|
||
onChange={(e) => setText(e.target.value)}
|
||
onKeyDown={(e) => e.key === 'Enter' && onSend()}
|
||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md bg-white text-black placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-800 transition-all"
|
||
/>
|
||
<button
|
||
onClick={onSend}
|
||
disabled={isLoading}
|
||
className="px-4 py-2 bg-black hover:bg-gray-900 disabled:bg-gray-400 text-white font-medium rounded-md transition-colors duration-200 flex-shrink-0"
|
||
>
|
||
{isLoading ? '发送中...' : '发送'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
} |