This commit is contained in:
xiongxiao
2026-03-16 03:23:40 +08:00
committed by cnb
parent 8ad1254341
commit 4b1d1072b8
11 changed files with 872 additions and 118 deletions

View File

@@ -0,0 +1,126 @@
import { useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { FileIcon, FolderIcon, RefreshCwIcon, TrashIcon } from 'lucide-react';
import { useChatDevStore } from '../store';
import { useCodeGraphStore } from '@/pages/code-graph/store';
export const OpencodeChat = () => {
const {
question, projectInfo,
sessionId, isLoading,
saveSessionInfo, loadSessionInfo, fetchSession, fetchMessages, clearSession,
setData,
} = useChatDevStore(
useShallow((s) => ({
question: s.question,
projectInfo: s.projectInfo,
setData: s.setData,
sessionId: s.sessionId,
isLoading: s.isLoading,
saveSessionInfo: s.saveSessionInfo,
loadSessionInfo: s.loadSessionInfo,
fetchSession: s.fetchSession,
fetchMessages: s.fetchMessages,
clearSession: s.clearSession,
})),
);
const codeGraphStore = useCodeGraphStore(useShallow((s) => ({
createQuestion: s.createQuestion,
url: s.url,
})));
// 初始化后尝试从 sessionStorage 恢复 opencode session 并加载历史消息
useEffect(() => {
if (!codeGraphStore.url) return;
const info = loadSessionInfo();
if (info) {
fetchSession(info.sessionId);
fetchMessages(info.sessionId);
}
}, [codeGraphStore.url]);
const relativePath = projectInfo
? (projectInfo.filepath || '').replace((projectInfo.projectPath || '') + '/', '') || '/'
: null;
const onSend = async () => {
const res = await codeGraphStore.createQuestion({
question,
projectPath: projectInfo?.projectPath,
engine: 'opencode',
sessionId: sessionId || undefined,
});
console.log(res);
if (res?.code === 200 && res?.data) {
const { sessionId: newSessionId, messageId: newMessageId } = res.data as any;
if (newSessionId) {
saveSessionInfo(newSessionId, newMessageId || '');
fetchMessages(newSessionId);
}
}
};
return (
<div className='w-full max-w-2xl rounded-xl border border-white/10 bg-slate-900 shadow-2xl flex flex-col'>
{/* Session 信息栏 */}
{sessionId && (
<div className='flex items-center gap-2 px-4 py-2 border-b border-white/10'>
<span className='flex items-center gap-1 text-xs text-slate-400 ml-auto'>
<span className='truncate max-w-[160px]' title={sessionId}>
Session: {sessionId.slice(0, 8)}...
</span>
<button
title='刷新消息'
onClick={() => fetchMessages(sessionId)}
className='p-1 rounded hover:text-emerald-400 transition-colors'>
<RefreshCwIcon className='size-3' />
</button>
<button
title='清除 Session'
onClick={clearSession}
className='p-1 rounded hover:text-red-400 transition-colors'>
<TrashIcon className='size-3' />
</button>
</span>
</div>
)}
{/* 内容区 */}
<div className='px-4 py-4 flex flex-col gap-4'>
{/* 节点信息 */}
{relativePath && (
<div className='flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800/60 border border-white/5 min-w-0'>
<FileIcon className='size-4 shrink-0 text-slate-400' />
<span className='text-xs text-slate-300 font-mono truncate' title={relativePath}>
{relativePath}
</span>
</div>
)}
{projectInfo?.projectPath && !relativePath && (
<div className='flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-800/60 border border-white/5 min-w-0'>
<FolderIcon className='size-4 shrink-0 text-slate-400' />
<span className='text-xs text-slate-300 font-mono truncate'>
{projectInfo.projectPath}
</span>
</div>
)}
{/* 问题输入区 */}
<textarea
className='w-full rounded-lg border border-white/10 bg-slate-800 px-3 py-2 text-sm text-slate-200 placeholder:text-slate-500 focus:outline-none focus:ring-1 focus:ring-emerald-500 resize-none'
rows={6}
placeholder='请输入内容...'
value={question}
onChange={(e) => setData({ question: e.target.value })}
/>
<button
disabled={isLoading}
className='w-full rounded-lg bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-medium py-2 transition-colors'
onClick={onSend}>
{isLoading ? '处理中...' : '发送'}
</button>
</div>
</div>
);
};