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 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 09:42:30 +08:00
parent 11c00d34bc
commit 306cb24c70
7 changed files with 351 additions and 19 deletions
@@ -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 (
<div className="field-result-list">
{items.map((item) => (
<div
key={item.field_path}
className={`field-result-item field-result-item--${item.status}`}
>
<div className="field-result-item__header">
<span className="field-result-item__name">
{item.status === 'filled' ? '✅' : '⚠️'} {item.evaluation_as}
</span>
<span className="field-result-item__score">
{item.scored}/{item.weight}
</span>
</div>
<div className="field-result-item__value">
{item.value}
</div>
{item.ai_feedback && (
<div className="field-result-item__feedback">
💬 {item.ai_feedback}
</div>
)}
</div>
))}
</div>
);
}
+32
View File
@@ -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 (
<div className="score-bar" data-passed={passed}>
<div className="score-bar__header">
<span className="score-bar__percentage">{pct}%</span>
<span className={`score-bar__status ${passed ? 'passed' : 'failed'}`}>
{passed ? '✅ 通过' : '❌ 未通过'}
</span>
</div>
<div className="score-bar__track">
<div
className={`score-bar__fill ${passed ? 'passed' : 'failed'}`}
style={{ width: `${pct}%` }}
/>
</div>
<div className="score-bar__footer">
{score.toFixed(1)} / {fullScore.toFixed(1)}
</div>
</div>
);
}
@@ -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 (
<div className="scored-result-card" data-passed={result.passed}>
<div className="scored-result-card__header">
<span className="scored-result-card__code">{result.code}</span>
<span className="scored-result-card__name">{result.name}</span>
</div>
<ScoreBar
score={result.machine_score}
fullScore={result.score}
percentage={result.percentage}
passed={result.passed}
/>
<FieldResultList items={result.field_results} />
{result.ai_suggestion && (
<div className="scored-result-card__suggestion">
<strong>💡 </strong>
<span>{result.ai_suggestion}</span>
</div>
)}
</div>
);
}
+3
View File
@@ -0,0 +1,3 @@
export { ScoreBar } from './ScoreBar';
export { FieldResultList } from './FieldResultList';
export { ScoredResultCard } from './ScoredResultCard';
+56 -4
View File
@@ -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<string | null>(null); // 当前正在编辑的评查点ID
@@ -2475,13 +2522,18 @@ export function ReviewPointsList({
{/* 评查点列表 */}
<div className="review-points-list">
{filteredReviewPoints.length > 0 ? (
{/* GraphRAG Scored 模式渲染 */}
{flowType === 'graphrag' && scoredResults && scoredResults.length > 0 ? (
scoredResults.map(result => (
<ScoredResultCard key={result.evaluation_point_id} result={result} />
))
) : filteredReviewPoints.length > 0 ? (
filteredReviewPoints.map(reviewPoint => (
<div
key={reviewPoint.id}
className={`rounded-md review-point-item ${activeReviewPointResultId === reviewPoint.id ? 'active border-l-4 !border-l-[rgba(0,104,74,1)] shadow-md' : 'border-l-4 border-l-transparent'}
className={`rounded-md review-point-item ${activeReviewPointResultId === reviewPoint.id ? 'active border-l-4 !border-l-[rgba(0,104,74,1)] shadow-md' : 'border-l-4 border-l-transparent'}
transition-all duration-300 ease-in-out shadow-sm
hover:shadow-lg hover:border-l-4 hover:border-l-[rgba(0,104,74,0.3)]
hover:shadow-lg hover:border-l-4 hover:border-l-[rgba(0,104,74,0.3)]
hover:bg-[rgba(0,0,0,0.02)] my-2`}
role="button"
tabIndex={0}