import { useCallback, useMemo, useState } from "react"; import { Badge, Tabs, Typography, Alert } from "antd"; import RegionList from "./RegionList"; import RelationList from "./RelationList"; import RelationEditor from "./RelationEditor"; import type { LSResultItem, LSRegionInfo, PanelRegion, PanelRelation, } from "../annotation-result.types"; interface AnnotationResultPanelProps { annotationResult: LSResultItem[]; lsRegions: LSRegionInfo[]; selectedRegionId: string | null; onSelectRegion: (regionId: string) => void; availableRelationLabels: string[]; panelRelations: PanelRelation[]; onPanelRelationsChange: (relations: PanelRelation[]) => void; collapsed: boolean; } let relationIdCounter = 0; const genRelationId = () => `panel_rel_${Date.now()}_${++relationIdCounter}`; /** Convert LSRegionInfo from iframe to PanelRegion for display. */ const toPanelRegion = (info: LSRegionInfo): PanelRegion => ({ id: info.id, type: info.type, fromName: info.from_name, toName: info.to_name, displayText: info.displayText, displayLabel: info.displayLabel, value: info.value, }); /** Extract LS-native relations from result array (excluding panel-managed ones). */ const extractLsRelations = (result: LSResultItem[], panelRelationIds: Set): PanelRelation[] => result .filter((item) => item.type === "relation" && item.from_id && item.to_id && item._source !== "panel" && !panelRelationIds.has(item.id), ) .map((item) => ({ id: item.id, fromRegionId: item.from_id!, toRegionId: item.to_id!, direction: item.direction || "right", labels: item.labels || [], source: "ls" as const, })); export default function AnnotationResultPanel({ annotationResult, lsRegions, selectedRegionId, onSelectRegion, availableRelationLabels, panelRelations, onPanelRelationsChange, collapsed, }: AnnotationResultPanelProps) { const [relationPickMode, setRelationPickMode] = useState(false); const [pickedRegionIds, setPickedRegionIds] = useState([]); const [editorOpen, setEditorOpen] = useState(false); const [editingRelation, setEditingRelation] = useState(null); const [activeTabKey, setActiveTabKey] = useState("regions"); const regions = useMemo( () => lsRegions.map(toPanelRegion), [lsRegions], ); const panelRelationIds = useMemo( () => new Set(panelRelations.map((r) => r.id)), [panelRelations], ); const lsRelations = useMemo( () => extractLsRelations(annotationResult, panelRelationIds), [annotationResult, panelRelationIds], ); const allRelations = useMemo( () => [...lsRelations, ...panelRelations], [lsRelations, panelRelations], ); // --- Relation pick mode --- const handleStartAddRelation = useCallback(() => { if (regions.length < 2) { // Not enough regions, open editor directly setEditingRelation(null); setEditorOpen(true); return; } setRelationPickMode(true); setPickedRegionIds([]); // 自动切到区域 Tab,让用户看到拾取提示 setActiveTabKey("regions"); }, [regions.length]); const handlePickRegion = useCallback( (regionId: string) => { setPickedRegionIds((prev) => { if (prev.includes(regionId)) { return prev.filter((id) => id !== regionId); } const next = [...prev, regionId]; if (next.length >= 2) { // Two regions picked, open editor setTimeout(() => { setRelationPickMode(false); setEditingRelation(null); setEditorOpen(true); }, 0); } return next.slice(0, 2); }); }, [], ); const handleCancelPick = useCallback(() => { setRelationPickMode(false); setPickedRegionIds([]); }, []); // --- Relation CRUD --- const handleConfirmRelation = useCallback( (data: { fromRegionId: string; toRegionId: string; direction: "right" | "left" | "bi"; labels: string[] }) => { if (editingRelation) { onPanelRelationsChange( panelRelations.map((r) => r.id === editingRelation.id ? { ...r, ...data } : r, ), ); } else { onPanelRelationsChange([ ...panelRelations, { id: genRelationId(), ...data, source: "panel", }, ]); } setEditorOpen(false); setEditingRelation(null); setPickedRegionIds([]); }, [editingRelation, onPanelRelationsChange, panelRelations], ); const handleDeleteRelation = useCallback( (relationId: string) => { onPanelRelationsChange(panelRelations.filter((r) => r.id !== relationId)); }, [onPanelRelationsChange, panelRelations], ); const handleEditRelation = useCallback((relation: PanelRelation) => { setEditingRelation(relation); setEditorOpen(true); }, []); const handleEditorCancel = useCallback(() => { setEditorOpen(false); setEditingRelation(null); }, []); if (collapsed) return null; const tabItems = [ { key: "regions", label: ( 区域 ), children: (
{relationPickMode && ( )}
), }, { key: "relations", label: ( 关系 ), children: (
), }, ]; return (
标注结果 {regions.length} 区域 / {allRelations.length} 关系
); }