Spaces:
Paused
Paused
| import React, { useState } from "react"; | |
| interface RecognitionMetadata { | |
| recognizer_name: string; | |
| recognizer_identifier: string; | |
| } | |
| interface GuardrailEntity { | |
| end: number; | |
| score: number; | |
| start: number; | |
| entity_type: string; | |
| analysis_explanation: string | null; | |
| recognition_metadata: RecognitionMetadata; | |
| } | |
| interface MaskedEntityCount { | |
| [key: string]: number; | |
| } | |
| interface GuardrailInformation { | |
| duration: number; | |
| end_time: number; | |
| start_time: number; | |
| guardrail_mode: string; | |
| guardrail_name: string; | |
| guardrail_status: string; | |
| guardrail_response: GuardrailEntity[]; | |
| masked_entity_count: MaskedEntityCount; | |
| } | |
| interface GuardrailViewerProps { | |
| data: GuardrailInformation; | |
| } | |
| export function GuardrailViewer({ data }: GuardrailViewerProps) { | |
| const [sectionExpanded, setSectionExpanded] = useState(true); | |
| const [entityListExpanded, setEntityListExpanded] = useState(true); | |
| const [expandedEntities, setExpandedEntities] = useState<Record<number, boolean>>({}); | |
| if (!data) { | |
| return null; | |
| } | |
| // Calculate total masked entities | |
| const totalMaskedEntities = data.masked_entity_count ? | |
| Object.values(data.masked_entity_count).reduce((sum, count) => sum + count, 0) : 0; | |
| const formatTime = (timestamp: number): string => { | |
| const date = new Date(timestamp * 1000); | |
| return date.toLocaleString(); | |
| }; | |
| const toggleEntity = (index: number) => { | |
| setExpandedEntities(prev => ({ | |
| ...prev, | |
| [index]: !prev[index] | |
| })); | |
| }; | |
| const getScoreColor = (score: number): string => { | |
| if (score >= 0.8) return "text-green-600"; | |
| return "text-yellow-600"; | |
| }; | |
| return ( | |
| <div className="bg-white rounded-lg shadow mb-6"> | |
| <div | |
| className="flex justify-between items-center p-4 border-b cursor-pointer hover:bg-gray-50" | |
| onClick={() => setSectionExpanded(!sectionExpanded)} | |
| > | |
| <div className="flex items-center"> | |
| <svg | |
| className={`w-5 h-5 mr-2 text-gray-600 transition-transform ${sectionExpanded ? 'transform rotate-90' : ''}`} | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> | |
| </svg> | |
| <h3 className="text-lg font-medium">Guardrail Information</h3> | |
| <span className={`ml-3 px-2 py-1 rounded-md text-xs font-medium inline-block ${ | |
| data.guardrail_status === "success" | |
| ? 'bg-green-100 text-green-800' | |
| : 'bg-red-100 text-red-800' | |
| }`}> | |
| {data.guardrail_status} | |
| </span> | |
| {totalMaskedEntities > 0 && ( | |
| <span className="ml-3 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium"> | |
| {totalMaskedEntities} masked {totalMaskedEntities === 1 ? 'entity' : 'entities'} | |
| </span> | |
| )} | |
| </div> | |
| <span className="text-sm text-gray-500">{sectionExpanded ? 'Click to collapse' : 'Click to expand'}</span> | |
| </div> | |
| {sectionExpanded && ( | |
| <div className="p-4"> | |
| <div className="bg-white rounded-lg border p-4 mb-4"> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="space-y-2"> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Guardrail Name:</span> | |
| <span className="font-mono">{data.guardrail_name}</span> | |
| </div> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Mode:</span> | |
| <span className="font-mono">{data.guardrail_mode}</span> | |
| </div> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Status:</span> | |
| <span className={`px-2 py-1 rounded-md text-xs font-medium inline-block ${ | |
| data.guardrail_status === "success" | |
| ? 'bg-green-100 text-green-800' | |
| : 'bg-red-100 text-red-800' | |
| }`}> | |
| {data.guardrail_status} | |
| </span> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Start Time:</span> | |
| <span>{formatTime(data.start_time)}</span> | |
| </div> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">End Time:</span> | |
| <span>{formatTime(data.end_time)}</span> | |
| </div> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Duration:</span> | |
| <span>{data.duration.toFixed(4)}s</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Masked Entity Summary */} | |
| {data.masked_entity_count && Object.keys(data.masked_entity_count).length > 0 && ( | |
| <div className="mt-4 pt-4 border-t"> | |
| <h4 className="font-medium mb-2">Masked Entity Summary</h4> | |
| <div className="flex flex-wrap gap-2"> | |
| {Object.entries(data.masked_entity_count).map(([entityType, count]) => ( | |
| <span key={entityType} className="px-3 py-1.5 bg-blue-50 text-blue-700 rounded-md text-xs font-medium"> | |
| {entityType}: {count} | |
| </span> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Detected Entities Section */} | |
| {data.guardrail_response && data.guardrail_response.length > 0 && ( | |
| <div className="mt-4"> | |
| <div | |
| className="flex items-center mb-2 cursor-pointer" | |
| onClick={() => setEntityListExpanded(!entityListExpanded)} | |
| > | |
| <svg | |
| className={`w-5 h-5 mr-2 transition-transform ${entityListExpanded ? 'transform rotate-90' : ''}`} | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> | |
| </svg> | |
| <h4 className="font-medium">Detected Entities ({data.guardrail_response.length})</h4> | |
| </div> | |
| {entityListExpanded && ( | |
| <div className="space-y-2"> | |
| {data.guardrail_response.map((entity, index) => { | |
| const isExpanded = expandedEntities[index] || false; | |
| return ( | |
| <div key={index} className="border rounded-lg overflow-hidden"> | |
| <div | |
| className="flex items-center justify-between p-3 bg-gray-50 cursor-pointer hover:bg-gray-100" | |
| onClick={() => toggleEntity(index)} | |
| > | |
| <div className="flex items-center"> | |
| <svg | |
| className={`w-5 h-5 mr-2 transition-transform ${isExpanded ? 'transform rotate-90' : ''}`} | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> | |
| </svg> | |
| <span className="font-medium mr-2">{entity.entity_type}</span> | |
| <span className={`font-mono ${getScoreColor(entity.score)}`}> | |
| Score: {entity.score.toFixed(2)} | |
| </span> | |
| </div> | |
| <span className="text-xs text-gray-500"> | |
| Position: {entity.start}-{entity.end} | |
| </span> | |
| </div> | |
| {isExpanded && ( | |
| <div className="p-3 border-t bg-white"> | |
| <div className="grid grid-cols-2 gap-4 mb-2"> | |
| <div className="space-y-2"> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Entity Type:</span> | |
| <span>{entity.entity_type}</span> | |
| </div> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Position:</span> | |
| <span>Characters {entity.start}-{entity.end}</span> | |
| </div> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Confidence:</span> | |
| <span className={getScoreColor(entity.score)}> | |
| {entity.score.toFixed(2)} | |
| </span> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| {entity.recognition_metadata && ( | |
| <> | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Recognizer:</span> | |
| <span>{entity.recognition_metadata.recognizer_name}</span> | |
| </div> | |
| <div className="flex overflow-hidden"> | |
| <span className="font-medium w-1/3">Identifier:</span> | |
| <span className="truncate text-xs font-mono"> | |
| {entity.recognition_metadata.recognizer_identifier} | |
| </span> | |
| </div> | |
| </> | |
| )} | |
| {entity.analysis_explanation && ( | |
| <div className="flex"> | |
| <span className="font-medium w-1/3">Explanation:</span> | |
| <span>{entity.analysis_explanation}</span> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |