feat(ui): 添加文件 Diff 查看功能

当 AI 执行 write_file 或 edit_file 工具时,在工具结果中显示 View Diff 按钮,
点击后在 IDE 面板中显示文件修改前后的对比视图。

主要改动:
- core: edit_file/write_file 工具返回 fileDiff 元数据
- ui: 新增 DiffEditor 组件用于显示文件差异
- ui: ChatMessage 添加 View Diff 按钮
- ui: IDE 组件支持 Diff 视图切换
- ui: useChat hook 处理 fileDiff 回调
This commit is contained in:
2025-12-17 21:11:44 +08:00
parent 3320a2a5ba
commit fea5442d53
13 changed files with 470 additions and 22 deletions
+21 -1
View File
@@ -24,6 +24,7 @@ import {
createSession,
type Session,
type ActiveFileInfo,
type FileDiffInfo,
} from '@ai-assistant/ui';
import { ChatPage } from './pages/Chat';
@@ -53,6 +54,9 @@ export function App() {
return saved !== 'false'; // 默认开启
});
// Diff 显示状态(当 AI 编辑/写入文件时触发)
const [pendingDiff, setPendingDiff] = useState<FileDiffInfo | null>(null);
// 持久化自动附加开关状态
useEffect(() => {
localStorage.setItem('ai-assistant-auto-attach-file', String(autoAttachActiveFile));
@@ -121,6 +125,16 @@ export function App() {
setCurrentSessionId(newSessionId);
}, []);
// 文件 diff 回调(当 AI 编辑/写入文件时触发)
const handleFileDiff = useCallback((diff: FileDiffInfo) => {
setPendingDiff(diff);
}, []);
// Diff 关闭回调
const handleDiffClose = useCallback(() => {
setPendingDiff(null);
}, []);
// 处理面板宽度调整
const handleResize = useCallback((delta: number) => {
setIdePanelWidth((prev) => {
@@ -159,7 +173,11 @@ export function App() {
className="hidden md:flex flex-col"
style={{ width: `${idePanelWidth}%` }}
>
<IDE onActiveFileChange={setActiveFile} />
<IDE
onActiveFileChange={setActiveFile}
pendingDiff={pendingDiff}
onDiffClose={handleDiffClose}
/>
</div>
{/* 可拖拽分割线 */}
@@ -191,6 +209,8 @@ export function App() {
activeFile={activeFile}
autoAttachActiveFile={autoAttachActiveFile}
onAutoAttachActiveFileToggle={setAutoAttachActiveFile}
onFileDiff={handleFileDiff}
onViewDiff={handleFileDiff}
/>
) : (
<div className="flex-1 flex items-center justify-center h-full">
+10 -2
View File
@@ -17,6 +17,7 @@ import {
DiagnosticsIndicator,
ToolbarOverflowMenu,
type ActiveFileInfo,
type FileDiffInfo,
} from '@ai-assistant/ui';
interface ChatPageProps {
@@ -43,6 +44,10 @@ interface ChatPageProps {
autoAttachActiveFile?: boolean;
/** 自动附加开关变更回调 */
onAutoAttachActiveFileToggle?: (enabled: boolean) => void;
/** 文件 diff 回调(当 AI 写入/编辑文件时触发) */
onFileDiff?: (diff: FileDiffInfo) => void;
/** 查看文件 diff 回调(点击 View Diff 按钮) */
onViewDiff?: (diff: FileDiffInfo) => void;
}
export function ChatPage({
@@ -63,6 +68,8 @@ export function ChatPage({
activeFile,
autoAttachActiveFile,
onAutoAttachActiveFileToggle,
onFileDiff,
onViewDiff,
}: ChatPageProps) {
const {
messages,
@@ -100,6 +107,7 @@ export function ChatPage({
: undefined,
});
},
onFileDiff,
});
const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -208,13 +216,13 @@ export function ChatPage({
<AnimatePresence mode="popLayout">
{messages.map((message) => (
<ChatMessage key={message.id} message={message} onAnswerQuestion={answerQuestion} />
<ChatMessage key={message.id} message={message} onAnswerQuestion={answerQuestion} onViewDiff={onViewDiff ?? onFileDiff} />
))}
</AnimatePresence>
{/* 流式消息 - 复用 ChatMessage 组件 */}
{streamingMessage && (
<ChatMessage message={streamingMessage} isStreaming onAnswerQuestion={answerQuestion} />
<ChatMessage message={streamingMessage} isStreaming onAnswerQuestion={answerQuestion} onViewDiff={onViewDiff ?? onFileDiff} />
)}
{/* 子 Agent 进度显示 */}