Files
router-studio/web/src/app/chat/index.tsx
2026-02-18 00:51:39 +08:00

127 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
}