123 lines
4.7 KiB
TypeScript
123 lines
4.7 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { useSearch } from '@tanstack/react-router';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
import { BotIcon, FileIcon, FolderIcon } from 'lucide-react';
|
|
import { useChatDevStore } from './store';
|
|
import { BOT_KEYS, BotKey } from '@/pages/code-graph/store/bot-helper';
|
|
import openclawSvg from '@/pages/code-graph/assets/openclaw.svg';
|
|
import opencodePng from '@/pages/code-graph/assets/opencode.png';
|
|
import { useCodeGraphStore } from '../code-graph/store';
|
|
import { useLayoutStore } from '../auth/store';
|
|
|
|
const BOT_ICONS: Record<BotKey, string> = {
|
|
openclaw: openclawSvg,
|
|
opencode: opencodePng,
|
|
};
|
|
|
|
export const App = () => {
|
|
const { timestamp } = useSearch({ from: '/chat-dev' });
|
|
const { question, engine, projectInfo, initFromTimestamp, setData } = useChatDevStore(
|
|
useShallow((s) => ({
|
|
question: s.question,
|
|
engine: s.engine,
|
|
projectInfo: s.projectInfo,
|
|
initFromTimestamp: s.initFromTimestamp,
|
|
setData: s.setData,
|
|
})),
|
|
);
|
|
const layoutStore = useLayoutStore(useShallow((s) => ({
|
|
me: s.me,
|
|
})));
|
|
const codeGraphStore = useCodeGraphStore(useShallow((s) => ({
|
|
createQuestion: s.createQuestion,
|
|
init: s.init,
|
|
})));
|
|
|
|
useEffect(() => {
|
|
if (!layoutStore.me?.username) return;
|
|
codeGraphStore.init(layoutStore.me, { load: false });
|
|
}, [layoutStore.me]);
|
|
useEffect(() => {
|
|
if (timestamp) {
|
|
initFromTimestamp(timestamp);
|
|
}
|
|
}, [timestamp]);
|
|
|
|
const relativePath = projectInfo
|
|
? (projectInfo.filepath || '').replace((projectInfo.projectPath || '') + '/', '') || '/'
|
|
: null;
|
|
const onSend = async () => {
|
|
if (projectInfo) {
|
|
const res = await codeGraphStore.createQuestion({
|
|
question,
|
|
projectPath: projectInfo.projectPath,
|
|
engine,
|
|
});
|
|
console.log(res);
|
|
}
|
|
}
|
|
return (
|
|
<div className='h-full bg-slate-950 text-slate-100 flex flex-col items-center py-10 px-4'>
|
|
<div className='w-full max-w-2xl rounded-xl border border-white/10 bg-slate-900 shadow-2xl flex flex-col'>
|
|
{/* 标题栏 */}
|
|
<div className='flex items-center gap-2 px-4 py-3 border-b border-white/10'>
|
|
<BotIcon className='size-4 text-emerald-400' />
|
|
<span className='text-sm font-medium text-slate-100'>AI 助手</span>
|
|
</div>
|
|
|
|
{/* 内容区 */}
|
|
<div className='px-4 py-4 flex flex-col gap-4'>
|
|
{/* 节点信息 + Bot 切换 */}
|
|
<div className='flex items-center gap-2'>
|
|
{relativePath && (
|
|
<div className='flex-1 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-1 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>
|
|
)}
|
|
{/* Bot 切换按钮组 */}
|
|
<div className='flex items-center gap-1 shrink-0 ml-auto'>
|
|
{BOT_KEYS.map((key) => (
|
|
<button
|
|
key={key}
|
|
title={key}
|
|
onClick={() => setData({ engine: key })}
|
|
className={`p-1.5 rounded-lg border transition-colors ${engine === key
|
|
? 'border-emerald-500/60 bg-emerald-500/10'
|
|
: 'border-white/5 bg-slate-800/60 opacity-40 hover:opacity-70'
|
|
}`}>
|
|
<img src={BOT_ICONS[key]} alt={key} className='size-5 object-contain' />
|
|
</button>
|
|
))}
|
|
</div>
|
|
</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 className='w-full rounded-lg bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white text-sm font-medium py-2 transition-colors' onClick={onSend}>
|
|
发送
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App; |