From 306cb24c707bc31c9010d870df5a71c56fb6688e Mon Sep 17 00:00:00 2001 From: wren Date: Fri, 20 Mar 2026 09:42:30 +0800 Subject: [PATCH] feat(frontend): integrate GraphRAG scored evaluation results - Add getUnifiedEvaluationResults API function - Extend ReviewPointsListProps with flowType, scoredResults, scoredSummary - Add ScoredResultCard rendering for graphrag flow_type - Modify reviews.tsx loader to call unified API - Add scored evaluation component imports Co-Authored-By: Claude Opus 4.6 --- app/api/evaluation_points/reviews.ts | 93 +++++++++++++++++++ app/components/evaluation/FieldResultList.tsx | 46 +++++++++ app/components/evaluation/ScoreBar.tsx | 32 +++++++ .../evaluation/ScoredResultCard.tsx | 56 +++++++++++ app/components/evaluation/index.ts | 3 + app/components/reviews/ReviewPointsList.tsx | 60 +++++++++++- app/routes/reviews.tsx | 80 +++++++++++++--- 7 files changed, 351 insertions(+), 19 deletions(-) create mode 100644 app/components/evaluation/FieldResultList.tsx create mode 100644 app/components/evaluation/ScoreBar.tsx create mode 100644 app/components/evaluation/ScoredResultCard.tsx create mode 100644 app/components/evaluation/index.ts diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts index cfcbe9f..2c6c0e3 100644 --- a/app/api/evaluation_points/reviews.ts +++ b/app/api/evaluation_points/reviews.ts @@ -94,6 +94,53 @@ interface StatsData { score: number; } +// GraphRAG Scored 评查结果类型 +interface FieldScore { + field_path: string; + evaluation_as: string; + weight: number; + scored: number; + max_score: number; + status: string; // 'filled' | 'placeholder' + value: string; + page?: string; + ai_feedback?: string; +} + +interface ScoredEvaluationResult { + evaluation_point_id: number; + code: string; + name: string; + passed: boolean; + machine_score: number; + score: number; + percentage: number; + total_score: number; + total_weight: number; + pass_threshold: number; + result_type: 'scored'; + field_results: FieldScore[]; + missing_fields?: string[]; + ai_suggestion?: string; +} + +interface EvaluationSummary { + total_points: number; + passed_count: number; + failed_count: number; + total_score: number; + total_full_score: number; + average_percentage: number; +} + +interface UnifiedEvaluationResponse { + document_id: number; + flow_type: 'graphrag' | 'legacy'; + results: ScoredEvaluationResult[]; + summary: EvaluationSummary; + evaluated_at: string; +} + // 在文件顶部添加的类型定义,在interface区块前添加 interface OcrDataResult { pages?: number[]; @@ -1126,3 +1173,49 @@ export async function getReviewPoints_fromApi(fileId: string, request: Request) }; } } + +/** + * 获取统一评查结果(GraphRAG Scored 模式) + * + * @param fileId 文档ID + * @param request Remix请求对象 + * @returns 统一评查结果 + */ +export async function getUnifiedEvaluationResults(fileId: string, request: Request) { + try { + const { userInfo, frontendJWT } = await getUserSession(request); + if (!userInfo?.user_id) { + return { error: '用户身份验证失败', status: 401 }; + } + + const response = await apiRequest( + `/api/v2/evaluation/results-unified/${fileId}`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${frontendJWT}`, + 'Content-Type': 'application/json' + } + } + ); + + if (response.error) { + console.error('❌ [getUnifiedEvaluationResults] API调用失败:', response.error); + return { error: response.error, status: response.status || 500 }; + } + + if (response.data) { + return response.data; + } + + console.error('❌ [getUnifiedEvaluationResults] API响应数据为空'); + return { error: 'API响应数据为空', status: 500 }; + + } catch (error) { + console.error('❌ [getUnifiedEvaluationResults] 调用失败:', error); + return { + error: error instanceof Error ? error.message : '获取评查结果失败', + status: 500 + }; + } +} diff --git a/app/components/evaluation/FieldResultList.tsx b/app/components/evaluation/FieldResultList.tsx new file mode 100644 index 0000000..232d052 --- /dev/null +++ b/app/components/evaluation/FieldResultList.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +interface FieldResultItem { + field_path: string; + evaluation_as: string; + weight: number; + scored: number; + status: string; // 'filled' | 'placeholder' + value: string; + page?: string; + ai_feedback?: string; +} + +interface FieldResultListProps { + items: FieldResultItem[]; +} + +export function FieldResultList({ items }: FieldResultListProps) { + return ( +
+ {items.map((item) => ( +
+
+ + {item.status === 'filled' ? '✅' : '⚠️'} {item.evaluation_as} + + + {item.scored}/{item.weight} + +
+
+ {item.value} +
+ {item.ai_feedback && ( +
+ 💬 {item.ai_feedback} +
+ )} +
+ ))} +
+ ); +} diff --git a/app/components/evaluation/ScoreBar.tsx b/app/components/evaluation/ScoreBar.tsx new file mode 100644 index 0000000..05e2e0a --- /dev/null +++ b/app/components/evaluation/ScoreBar.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +interface ScoreBarProps { + score: number; // Actual score (e.g., 3 for 3/5) + fullScore: number; // Max score (e.g., 5) + percentage: number; // 0.0-1.0 + passed: boolean; +} + +export function ScoreBar({ score, fullScore, percentage, passed }: ScoreBarProps) { + const pct = Math.round(percentage * 100); + + return ( +
+
+ {pct}% + + {passed ? '✅ 通过' : '❌ 未通过'} + +
+
+
+
+
+ 得分 {score.toFixed(1)} / 满分 {fullScore.toFixed(1)} +
+
+ ); +} diff --git a/app/components/evaluation/ScoredResultCard.tsx b/app/components/evaluation/ScoredResultCard.tsx new file mode 100644 index 0000000..40712ee --- /dev/null +++ b/app/components/evaluation/ScoredResultCard.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { ScoreBar } from './ScoreBar'; +import { FieldResultList } from './FieldResultList'; + +interface ScoredResultCardProps { + result: { + evaluation_point_id: number; + code: string; + name: string; + passed: boolean; + machine_score: number; // e.g., 3 + score: number; // e.g., 5 (full score from evaluation_points) + percentage: number; + total_score: number; // e.g., 60 + total_weight: number; // e.g., 100 + field_results: Array<{ + field_path: string; + evaluation_as: string; + weight: number; + scored: number; + status: string; + value: string; + page?: string; + ai_feedback?: string; + }>; + missing_fields?: string[]; + ai_suggestion?: string; + }; +} + +export function ScoredResultCard({ result }: ScoredResultCardProps) { + return ( +
+
+ {result.code} + {result.name} +
+ + + + + + {result.ai_suggestion && ( +
+ 💡 建议: + {result.ai_suggestion} +
+ )} +
+ ); +} diff --git a/app/components/evaluation/index.ts b/app/components/evaluation/index.ts new file mode 100644 index 0000000..38a2db8 --- /dev/null +++ b/app/components/evaluation/index.ts @@ -0,0 +1,3 @@ +export { ScoreBar } from './ScoreBar'; +export { FieldResultList } from './FieldResultList'; +export { ScoredResultCard } from './ScoredResultCard'; diff --git a/app/components/reviews/ReviewPointsList.tsx b/app/components/reviews/ReviewPointsList.tsx index 39a87fd..c8df8e1 100644 --- a/app/components/reviews/ReviewPointsList.tsx +++ b/app/components/reviews/ReviewPointsList.tsx @@ -24,6 +24,7 @@ import { Tooltip } from '../ui/Tooltip'; import { CorporateInfoModal } from '../corporate-information'; import type { BusinessInfoResult, DishonestyResult } from '../corporate-information'; import { queryCompanyInfo } from '~/api/corporate-information/qichacha'; +import { ScoredResultCard } from '~/components/evaluation'; // import '../../styles/components/TooltipStyles.css'; /** @@ -145,6 +146,45 @@ interface Statistics { score: number; } +// GraphRAG Scored 评查结果类型 +interface FieldScore { + field_path: string; + evaluation_as: string; + weight: number; + scored: number; + max_score: number; + status: string; + value: string; + page?: string; + ai_feedback?: string; +} + +interface ScoredEvaluationResult { + evaluation_point_id: number; + code: string; + name: string; + passed: boolean; + machine_score: number; + score: number; + percentage: number; + total_score: number; + total_weight: number; + pass_threshold: number; + result_type: 'scored'; + field_results: FieldScore[]; + missing_fields?: string[]; + ai_suggestion?: string; +} + +interface EvaluationSummary { + total_points: number; + passed_count: number; + failed_count: number; + total_score: number; + total_full_score: number; + average_percentage: number; +} + interface ReviewPointsListProps { reviewPoints: ReviewPoint[]; statistics: Statistics; @@ -153,6 +193,10 @@ interface ReviewPointsListProps { onStatusChange: (id: string, editAuditStatusId: string | number, status: string, message: string) => void; fileFormat?: string; // 文档格式类型(PDF、DOCX等) onAiSuggestionReplace?: (searchText: string, replaceText: string, pageNumber: number) => void; // AI建议替换回调 + // GraphRAG Scored 模式支持 + flowType?: 'graphrag' | 'legacy'; + scoredResults?: ScoredEvaluationResult[]; + scoredSummary?: EvaluationSummary; } /** @@ -411,7 +455,10 @@ export function ReviewPointsList({ onReviewPointSelect, onStatusChange, fileFormat, - onAiSuggestionReplace + onAiSuggestionReplace, + flowType, + scoredResults, + scoredSummary }: ReviewPointsListProps) { // 状态管理 const [editingReviewPoint, setEditingReviewPoint] = useState(null); // 当前正在编辑的评查点ID @@ -2475,13 +2522,18 @@ export function ReviewPointsList({ {/* 评查点列表 */}
- {filteredReviewPoints.length > 0 ? ( + {/* GraphRAG Scored 模式渲染 */} + {flowType === 'graphrag' && scoredResults && scoredResults.length > 0 ? ( + scoredResults.map(result => ( + + )) + ) : filteredReviewPoints.length > 0 ? ( filteredReviewPoints.map(reviewPoint => (