From 6830183cd0f6c99aea2c2adae8cd80fba33cb1cf Mon Sep 17 00:00:00 2001 From: lintsinghua <1930438860@qq.com> Date: Wed, 22 Oct 2025 21:53:16 +0800 Subject: [PATCH] feat(instant-analysis): Enhance code analysis UI and user experience - Add parseAIExplanation function to handle complex AI explanation parsing - Implement automatic scrolling to loading card during analysis - Improve issue rendering with more detailed and visually appealing components - Add useEffect hook to manage scrolling behavior during analysis - Refactor issue rendering with enhanced styling and information display - Include additional context and visual indicators for code issues - Optimize user interaction and feedback during code analysis process --- src/pages/InstantAnalysis.tsx | 442 ++++++++++++++++++++-------------- src/pages/TaskDetail.tsx | 193 +++++++++++---- 2 files changed, 402 insertions(+), 233 deletions(-) diff --git a/src/pages/InstantAnalysis.tsx b/src/pages/InstantAnalysis.tsx index 1e03ecd..d9e3c25 100644 --- a/src/pages/InstantAnalysis.tsx +++ b/src/pages/InstantAnalysis.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -26,6 +26,26 @@ import { api } from "@/shared/config/database"; import type { CodeAnalysisResult } from "@/shared/types"; import { toast } from "sonner"; +// AI解释解析函数 +function parseAIExplanation(aiExplanation: string) { + try { + const parsed = JSON.parse(aiExplanation); + // 检查是否有xai字段 + if (parsed.xai) { + return parsed.xai; + } + // 检查是否直接包含what, why, how字段 + if (parsed.what || parsed.why || parsed.how) { + return parsed; + } + // 如果都没有,返回null表示无法解析 + return null; + } catch (error) { + // JSON解析失败,返回null + return null; + } +} + export default function InstantAnalysis() { const user = null as any; const [code, setCode] = useState(""); @@ -34,9 +54,27 @@ export default function InstantAnalysis() { const [result, setResult] = useState(null); const [analysisTime, setAnalysisTime] = useState(0); const fileInputRef = useRef(null); + const loadingCardRef = useRef(null); const supportedLanguages = CodeAnalysisEngine.getSupportedLanguages(); + // 监听analyzing状态变化,自动滚动到加载卡片 + useEffect(() => { + if (analyzing && loadingCardRef.current) { + // 使用requestAnimationFrame确保DOM更新完成后再滚动 + requestAnimationFrame(() => { + setTimeout(() => { + if (loadingCardRef.current) { + loadingCardRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + }, 50); + }); + } + }, [analyzing]); + // 示例代码 const exampleCodes = { javascript: `// 示例JavaScript代码 - 包含多种问题 @@ -120,6 +158,15 @@ public class Example { try { setAnalyzing(true); + + // 立即滚动到页面底部(加载卡片会出现的位置) + setTimeout(() => { + window.scrollTo({ + top: document.body.scrollHeight, + behavior: 'smooth' + }); + }, 100); + const startTime = Date.now(); const analysisResult = await CodeAnalysisEngine.analyzeCode(code, language); @@ -225,6 +272,146 @@ public class Example { setAnalysisTime(0); }; + // 渲染问题的函数,使用紧凑样式 + const renderIssue = (issue: any, index: number) => ( +
+
+
+
+ {getTypeIcon(issue.type)} +
+
+

{issue.title}

+
+ 📍 + 第 {issue.line} 行 + {issue.column && ,第 {issue.column} 列} +
+
+
+ + {issue.severity === 'critical' ? '严重' : + issue.severity === 'high' ? '高' : + issue.severity === 'medium' ? '中等' : '低'} + +
+ + {issue.description && ( +
+
+ + 问题详情 +
+

+ {issue.description} +

+
+ )} + + {issue.code_snippet && ( +
+
+
+
+ +
+ 问题代码 +
+ 第 {issue.line} 行 +
+
+
+              {issue.code_snippet}
+            
+
+
+ )} + +
+ {issue.suggestion && ( +
+
+
+ +
+ 修复建议 +
+

{issue.suggestion}

+
+ )} + + {issue.ai_explanation && (() => { + const parsedExplanation = parseAIExplanation(issue.ai_explanation); + + if (parsedExplanation) { + return ( +
+
+
+ +
+ AI 解释 +
+ +
+ {parsedExplanation.what && ( +
+ 问题: + {parsedExplanation.what} +
+ )} + + {parsedExplanation.why && ( +
+ 原因: + {parsedExplanation.why} +
+ )} + + {parsedExplanation.how && ( +
+ 方案: + {parsedExplanation.how} +
+ )} + + {parsedExplanation.learn_more && ( + + )} +
+
+ ); + } else { + // 如果无法解析JSON,回退到原始显示方式 + return ( +
+
+ + AI 解释 +
+

{issue.ai_explanation}

+
+ ); + } + })()} +
+
+ ); + return (
{/* 页面标题 */} @@ -235,9 +422,9 @@ public class Example { {/* 代码输入区域 */} - +
- 代码分析 + 代码分析 {result && (
- + {/* 工具栏 */} -
+
{/* 快速示例 */} -
- 示例: +
+ 示例: @@ -297,6 +485,7 @@ public class Example { size="sm" onClick={() => loadExampleCode('python')} disabled={analyzing} + className="h-7 px-2 text-xs" > Python @@ -305,6 +494,7 @@ public class Example { size="sm" onClick={() => loadExampleCode('java')} disabled={analyzing} + className="h-7 px-2 text-xs" > Java @@ -316,10 +506,10 @@ public class Example { placeholder="粘贴代码或上传文件..." value={code} onChange={(e) => setCode(e.target.value)} - className="min-h-[300px] font-mono text-sm" + className="min-h-[250px] font-mono text-sm" disabled={analyzing} /> -
+
{code.length} 字符,{code.split('\n').length} 行
@@ -350,18 +540,18 @@ public class Example {
{/* 结果概览 */} - +
- - + + 分析结果 -
- - +
+ + {analysisTime.toFixed(2)}s - + {language.charAt(0).toUpperCase() + language.slice(1)}
@@ -369,78 +559,78 @@ public class Example { {/* 核心指标 */} -
-
-
- +
+
+
+
-
+
{result.quality_score.toFixed(1)}
-

质量评分

- +

质量评分

+
-
-
- +
+
+
-
+
{result.summary.critical_issues + result.summary.high_issues}
-

严重问题

+

严重问题

需要立即处理
-
-
- +
+
+
-
+
{result.summary.medium_issues + result.summary.low_issues}
-

一般问题

+

一般问题

建议优化
-
-
- +
+
+
-
+
{result.issues.length}
-

总问题数

+

总问题数

已全部识别
{/* 详细指标 */} -
-

- +
+

+ 详细指标

-
+
-
{result.metrics.complexity}
-

复杂度

- +
{result.metrics.complexity}
+

复杂度

+
-
{result.metrics.maintainability}
-

可维护性

- +
{result.metrics.maintainability}
+

可维护性

+
-
{result.metrics.security}
-

安全性

- +
{result.metrics.security}
+

安全性

+
-
{result.metrics.performance}
-

性能

- +
{result.metrics.performance}
+

性能

+
@@ -449,152 +639,38 @@ public class Example { {/* 问题详情 */} - - - + + + 发现的问题 ({result.issues.length}) {result.issues.length > 0 ? ( - - + + 全部 ({result.issues.length}) - + 严重 ({result.issues.filter(i => i.severity === 'critical').length}) - + 高 ({result.issues.filter(i => i.severity === 'high').length}) - + 中等 ({result.issues.filter(i => i.severity === 'medium').length}) - - {result.issues.map((issue, index) => ( -
-
-
-
- {getTypeIcon(issue.type)} -
-
-

{issue.title}

-

第 {issue.line} 行

-
-
- - {issue.severity === 'critical' ? '严重' : - issue.severity === 'high' ? '高' : - issue.severity === 'medium' ? '中等' : '低'} - -
- -

- {issue.description} -

- -
-
- 问题代码 - 第 {issue.line} 行 -
-
-                            {issue.code_snippet}
-                          
-
- -
-
-
- - 修复建议 -
-

{issue.suggestion}

-
- - {issue.ai_explanation && ( -
-
- - AI 解释 -
-

{issue.ai_explanation}

-
- )} -
-
- ))} + + {result.issues.map((issue, index) => renderIssue(issue, index))} {['critical', 'high', 'medium'].map(severity => ( - + {result.issues.filter(issue => issue.severity === severity).length > 0 ? ( - result.issues.filter(issue => issue.severity === severity).map((issue, index) => ( -
-
-
-
- {getTypeIcon(issue.type)} -
-
-

{issue.title}

-

第 {issue.line} 行

-
-
- - {issue.severity === 'critical' ? '严重' : - issue.severity === 'high' ? '高' : - issue.severity === 'medium' ? '中等' : '低'} - -
- -

- {issue.description} -

- -
-
- 问题代码 - 第 {issue.line} 行 -
-
-                                {issue.code_snippet}
-                              
-
- -
-
-
- - 修复建议 -
-

{issue.suggestion}

-
- - {issue.ai_explanation && ( -
-
- - AI 解释 -
-

{issue.ai_explanation}

-
- )} -
-
- )) + result.issues.filter(issue => issue.severity === severity).map((issue, index) => renderIssue(issue, index)) ) : (
@@ -632,7 +708,7 @@ public class Example { {/* 分析进行中状态 */} {analyzing && ( - +
diff --git a/src/pages/TaskDetail.tsx b/src/pages/TaskDetail.tsx index 5d0cd3d..13df1a1 100644 --- a/src/pages/TaskDetail.tsx +++ b/src/pages/TaskDetail.tsx @@ -5,12 +5,12 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { +import { ArrowLeft, - Activity, - AlertTriangle, - CheckCircle, - Clock, + Activity, + AlertTriangle, + CheckCircle, + Clock, FileText, Calendar, GitBranch, @@ -27,6 +27,26 @@ import { api } from "@/shared/config/database"; import type { AuditTask, AuditIssue } from "@/shared/types"; import { toast } from "sonner"; +// AI解释解析函数 +function parseAIExplanation(aiExplanation: string) { + try { + const parsed = JSON.parse(aiExplanation); + // 检查是否有xai字段 + if (parsed.xai) { + return parsed.xai; + } + // 检查是否直接包含what, why, how字段 + if (parsed.what || parsed.why || parsed.how) { + return parsed; + } + // 如果都没有,返回null表示无法解析 + return null; + } catch (error) { + // JSON解析失败,返回null + return null; + } +} + // 问题列表组件 function IssuesList({ issues }: { issues: AuditIssue[] }) { const getSeverityColor = (severity: string) => { @@ -56,75 +76,148 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) { const lowIssues = issues.filter(issue => issue.severity === 'low'); const renderIssue = (issue: AuditIssue, index: number) => ( -
-
+
+
-
+ issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' : + 'bg-blue-100 text-blue-600' + }`}> {getTypeIcon(issue.issue_type)}
-
-

{issue.title}

-

{issue.file_path}

+
+

{issue.title}

+
+ + {issue.file_path} +
{issue.line_number && ( -

- 第 {issue.line_number} 行 - {issue.column_number && `, 第 ${issue.column_number} 列`} -

+
+ 📍 + 第 {issue.line_number} 行 + {issue.column_number && ,第 {issue.column_number} 列} +
)}
- + {issue.severity === 'critical' ? '严重' : - issue.severity === 'high' ? '高' : - issue.severity === 'medium' ? '中等' : '低'} + issue.severity === 'high' ? '高' : + issue.severity === 'medium' ? '中等' : '低'}
{issue.description && ( -

- {issue.description} -

+
+
+ + 问题详情 +
+

+ {issue.description} +

+
)} {issue.code_snippet && ( -
+
- 问题代码 +
+
+ +
+ 问题代码 +
{issue.line_number && ( 第 {issue.line_number} 行 )}
-
-            {issue.code_snippet}
-          
+
+
+              {issue.code_snippet}
+            
+
)} -
+
{issue.suggestion && ( -
+
- - 修复建议 +
+ +
+ 修复建议
-

{issue.suggestion}

+

{issue.suggestion}

)} - {issue.ai_explanation && ( -
-
- - AI 解释 -
-

{issue.ai_explanation}

-
- )} + {issue.ai_explanation && (() => { + const parsedExplanation = parseAIExplanation(issue.ai_explanation); + + if (parsedExplanation) { + return ( +
+
+
+ +
+ AI 解释 +
+ +
+ {parsedExplanation.what && ( +
+ 问题: + {parsedExplanation.what} +
+ )} + + {parsedExplanation.why && ( +
+ 原因: + {parsedExplanation.why} +
+ )} + + {parsedExplanation.how && ( +
+ 方案: + {parsedExplanation.how} +
+ )} + + {parsedExplanation.learn_more && ( + + )} +
+
+ ); + } else { + // 如果无法解析JSON,回退到原始显示方式 + return ( +
+
+ + AI 解释 +
+

{issue.ai_explanation}

+
+ ); + } + })()}
); @@ -235,14 +328,14 @@ export default function TaskDetail() { const loadTaskDetail = async () => { if (!id) return; - + try { setLoading(true); const [taskData, issuesData] = await Promise.all([ api.getAuditTaskById(id), api.getAuditIssues(id) ]); - + setTask(taskData); setIssues(issuesData); } catch (error) { @@ -333,14 +426,14 @@ export default function TaskDetail() {

{task.project?.name || '未知项目'} - 审计任务

- +
{getStatusIcon(task.status)} - {task.status === 'completed' ? '已完成' : - task.status === 'running' ? '运行中' : - task.status === 'failed' ? '失败' : '等待中'} + {task.status === 'completed' ? '已完成' : + task.status === 'running' ? '运行中' : + task.status === 'failed' ? '失败' : '等待中'} {task.status === 'completed' && (