Newer
Older
cortex-hub / frontend / src / features / agents / components / drilldown / EvaluationPanel.js
import React from 'react';
import ReactMarkdown from 'react-markdown';

const EvaluationPanel = ({
    chatHistory,
    selectedAuditId,
    setSelectedAuditId,
    feedbackContent,
    rubricContent,
    historyLog,
    coworkerContent,
    setCoworkerContent,
    handleSaveGroundTruth,
    savingGroundTruth,
    editConfig,
    fetchData,
    agent
}) => {
    const messagesWithAudit = chatHistory.filter(m => m.message_metadata?.evaluation && !m.isUser);
    const selectedMessage = selectedAuditId ? chatHistory.find(m => m.id === selectedAuditId) : null;

    const displayFeedback = selectedMessage ? selectedMessage.message_metadata.evaluation.feedback : feedbackContent;
    const displayRubric = selectedMessage ? selectedMessage.message_metadata.evaluation.rubric : rubricContent;
    const displayHistory = selectedMessage ? selectedMessage.message_metadata.evaluation.history : historyLog;

    return (
        <div className="p-6 flex flex-col gap-6 h-full overflow-y-auto scrollbar-hide">
            {/* Inspection Controls */}
            <div className="flex flex-col md:flex-row justify-between items-start md:items-center p-4 bg-white/5 dark:bg-gray-800/30 backdrop-blur-md border border-gray-200 dark:border-white/5 rounded-2xl shadow-sm gap-4 shrink-0 transition-all hover:border-indigo-500/30">
                <div className="flex flex-col">
                    <span className="text-[10px] font-black uppercase tracking-widest text-indigo-500 mb-1">Audit Mode</span>
                    <div className="flex bg-gray-100 dark:bg-gray-900/50 p-1 rounded-xl border border-gray-200 dark:border-gray-800">
                        <button
                            onClick={() => setSelectedAuditId(null)}
                            className={`px-4 py-1.5 text-xs font-bold rounded-lg transition-all ${!selectedAuditId ? 'bg-white dark:bg-gray-800 text-indigo-500 shadow-md' : 'text-gray-500 hover:text-gray-400'}`}
                        >
                            Live (Latest)
                        </button>
                        <button
                            onClick={() => {
                                if (messagesWithAudit.length > 0) setSelectedAuditId(messagesWithAudit[messagesWithAudit.length - 1].id);
                            }}
                            className={`px-4 py-1.5 text-xs font-bold rounded-lg transition-all ${selectedAuditId ? 'bg-white dark:bg-gray-800 text-indigo-500 shadow-md' : 'text-gray-500 hover:text-gray-400'}`}
                        >
                            Historical Audit
                        </button>
                    </div>
                </div>

                {selectedAuditId && (
                    <div className="flex flex-col flex-1 w-full max-w-md">
                        <span className="text-[10px] font-black uppercase tracking-widest text-indigo-500 mb-1">Select Historical Message</span>
                        <select
                            value={selectedAuditId || ""}
                            onChange={(e) => setSelectedAuditId(e.target.value)}
                            className="w-full bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800 text-xs font-bold py-2.5 px-4 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all cursor-pointer"
                        >
                            {messagesWithAudit.map((m, i) => (
                                <option key={m.id} value={m.id}>
                                    Audit {i + 1}: {(m.text || "Tool/System Message").substring(0, 50)}... ({new Date(m.timestamp).toLocaleTimeString()})
                                </option>
                            ))}
                        </select>
                    </div>
                )}

                <div className="flex flex-col items-end shrink-0">
                    <span className="text-[10px] font-black uppercase tracking-widest text-gray-400 dark:text-gray-500 mb-1">Terminal Inspector</span>
                    <div className="flex items-center gap-2 px-3 py-1.5 bg-gray-50 dark:bg-black/40 rounded-xl text-xs font-mono font-bold text-gray-400 border border-gray-200 dark:border-white/5 italic shadow-inner">
                        {selectedAuditId ? `SNAPSHOT: MSID-${selectedAuditId}` : "LIVE: .CORTEX/"}
                    </div>
                </div>
            </div>

            {/* Auditor Guidelines Editor (Oversight Policy) */}
            <div className="flex flex-col bg-white/5 dark:bg-black/40 backdrop-blur-xl border border-gray-200 dark:border-white/10 rounded-2xl overflow-hidden shadow-2xl shadow-indigo-500/5 shrink-0 transition-all hover:border-indigo-500/20">
                <div className="px-5 py-3 bg-indigo-600/5 dark:bg-indigo-600/10 border-b border-gray-200 dark:border-white/10 flex justify-between items-center">
                    <div className="flex flex-col">
                        <span className="text-[10px] uppercase tracking-[0.3em] font-black text-indigo-500 dark:text-indigo-400 flex items-center gap-2">
                            <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor font-bold"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
                            Auditor Guidelines
                        </span>
                        <span className="text-[9px] text-gray-400 dark:text-indigo-400/40 font-mono mt-0.5">Ground Truth Policy Layer</span>
                    </div>
                    <div className="flex items-center gap-4">
                        <span className="text-[10px] font-mono text-gray-400 dark:text-indigo-400/50 bg-gray-100 dark:bg-white/5 px-2 py-0.5 rounded">.coworker.md</span>
                        <button
                            onClick={handleSaveGroundTruth}
                            disabled={savingGroundTruth || editConfig?.is_locked}
                            className={`px-5 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all shadow-lg ${savingGroundTruth
                                ? "bg-gray-100 dark:bg-white/5 text-gray-400"
                                : "bg-gradient-to-r from-indigo-600 to-blue-600 text-white hover:scale-105 active:scale-95 shadow-indigo-500/20"
                                }`}
                        >
                            {savingGroundTruth ? "Guidelines Syncing..." : "Sync Guidelines"}
                        </button>
                    </div>
                </div>
                <div className="p-5">
                    <textarea
                        value={coworkerContent}
                        onChange={(e) => setCoworkerContent(e.target.value)}
                        placeholder="# Auditor Guidelines... Define architectural constraints and judging rules here."
                        className="w-full h-52 bg-transparent text-gray-800 dark:text-emerald-400 font-mono text-sm leading-relaxed resize-none focus:outline-none scrollbar-hide border-none placeholder:text-gray-400 dark:placeholder:text-gray-800"
                        spellCheck="false"
                        disabled={editConfig?.is_locked}
                    ></textarea>
                </div>
            </div>

            <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 min-h-[400px]">
                {/* Evaluation Strategy (Rubric) */}
                <div className="flex flex-col min-h-[300px] bg-white dark:bg-gray-800/20 border border-gray-200 dark:border-white/5 rounded-2xl overflow-hidden shadow-sm transition-all hover:border-gray-300 dark:hover:border-white/10">
                    <div className="px-5 py-3 border-b border-gray-100 dark:border-white/5 bg-gray-50/50 dark:bg-black/20 flex justify-between items-center shrink-0">
                        <span className="text-[10px] uppercase tracking-widest font-black text-gray-500 flex items-center gap-2">
                            <svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
                            Quality Rubric
                        </span>
                        <span className="text-[10px] font-mono text-gray-400 bg-white dark:bg-black/20 px-1.5 py-0.5 rounded">rubric.md</span>
                    </div>
                    <div className="flex-1 p-5 overflow-auto font-mono text-xs whitespace-pre-wrap leading-relaxed text-gray-700 dark:text-gray-400 scrollbar-hide">
                        {displayRubric || <span className="italic text-gray-400 opacity-50 text-[10px]">No rubric configuration found for this agent instance.</span>}
                    </div>
                </div>

                {/* Live feedback loop (Feedback) */}
                <div className="flex flex-col min-h-[300px] bg-white dark:bg-indigo-500/5 border border-indigo-200 dark:border-indigo-500/20 rounded-2xl overflow-hidden shadow-indigo-500/5 shadow-2xl transition-all hover:border-indigo-500/40">
                    <div className="px-5 py-3 border-b border-indigo-100 dark:border-indigo-500/20 bg-indigo-50/50 dark:bg-indigo-500/10 flex justify-between items-center shrink-0">
                        <span className="text-[10px] uppercase tracking-[0.2em] font-black text-indigo-600 dark:text-indigo-400 flex items-center gap-2">
                            <div className="w-2 h-2 bg-indigo-500 rounded-full animate-pulse shadow-[0_0_10px_rgba(99,102,241,0.8)]"></div>
                            {selectedAuditId ? "Audit Snapshot" : "Audit Stream"}
                        </span>
                        <span className="text-[10px] font-mono text-indigo-400 bg-white/50 dark:bg-black/20 px-1.5 py-0.5 rounded">{selectedAuditId ? `MSID-${selectedAuditId}` : "feedback.md"}</span>
                    </div>
                    <div className="flex-1 p-5 overflow-auto font-sans text-xs leading-relaxed text-indigo-600/90 dark:text-indigo-300 markdown-content scrollbar-hide">
                        {displayFeedback ? (
                            <ReactMarkdown className="prose prose-sm dark:prose-invert max-w-none">
                                {displayFeedback}
                            </ReactMarkdown>
                        ) : (
                            <div className="flex flex-col items-center justify-center h-full gap-3 opacity-50 grayscale">
                                <div className="w-8 h-8 border-2 border-indigo-500/30 border-t-indigo-500 rounded-full animate-spin"></div>
                                <span className="italic text-center max-w-[200px]">
                                    {agent?.status === 'active' || agent?.status === 'starting'
                                        ? "Consulting Co-Worker rubric and auditing results..."
                                        : "Waiting for next evaluation cycle to populate feedback..."}
                                </span>
                            </div>
                        )}
                    </div>
                </div>
            </div>

            {/* Rework History Viewer */}
            <div className="flex flex-col bg-white dark:bg-gray-800/20 border border-gray-200 dark:border-white/5 rounded-2xl overflow-hidden shadow-sm shrink-0 transition-all hover:border-emerald-500/20">
                <div className="px-5 py-3 border-b border-gray-100 dark:border-white/5 bg-gray-50/50 dark:bg-black/20 flex justify-between items-center shrink-0">
                    <span className="text-[10px] uppercase tracking-widest font-black text-emerald-600 dark:text-emerald-500 flex items-center gap-2">
                        <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
                        History Timeline
                    </span>
                    <div className="flex items-center gap-4">
                        <button
                            onClick={fetchData}
                            className="text-[10px] font-black uppercase tracking-wider px-3 py-1 rounded-lg border border-indigo-500/30 text-indigo-500 hover:bg-indigo-500/10 transition-all active:scale-95"
                        >
                            Refresh Context
                        </button>
                        <span className="text-[10px] font-mono text-gray-400 bg-white dark:bg-black/20 px-1.5 py-0.5 rounded">history.log</span>
                    </div>
                </div>
                <div className="flex-1 overflow-y-auto px-5 py-5 scrollbar-hide max-h-[400px]">
                    <div className="flex flex-col gap-4">
                        {(displayHistory && displayHistory.length > 0) ? [...displayHistory].reverse().map((entry, idx) => (
                            <div key={idx} className={`group relative flex flex-col gap-3 p-4 rounded-xl border transition-all ${entry.type === 'attempt' ? 'border-gray-200 dark:border-white/5 bg-gray-50/5 dark:bg-white/5 hover:bg-white/10' : 'bg-transparent border-dashed border-gray-100 dark:border-white/5'}`}>
                                <div className="flex justify-between items-center">
                                    <div className="flex items-center gap-3">
                                        <div className={`w-2.5 h-2.5 rounded-full ${entry.type === 'attempt' ? (entry.score >= (editConfig?.rework_threshold || 80) ? 'bg-emerald-500 shadow-[0_0_10px_rgba(16,185,129,0.6)]' : 'bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.6)]') : 'bg-indigo-500/50'}`}></div>
                                        <span className="text-[11px] font-black uppercase tracking-tight text-gray-800 dark:text-gray-100">
                                            {entry.type === 'attempt' ? `Attempt Round ${entry.round}` : entry.name || 'Event'}
                                            {entry.duration && <span className="ml-3 font-mono text-gray-400 dark:text-gray-600 normal-case opacity-60">took {entry.duration}s</span>}
                                        </span>
                                    </div>
                                    <div className="flex items-center gap-4">
                                        <span className="text-[10px] font-mono text-gray-400 dark:text-gray-600">
                                            {entry.timestamp ? new Date(entry.timestamp * (entry.timestamp < 2000000000 ? 1000 : 1)).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : 'Pending'}
                                        </span>
                                        {entry.score !== undefined && (
                                            <div className={`text-[10px] font-black px-3 py-1 rounded-lg shadow-sm ${entry.score >= (editConfig?.rework_threshold || 80) ? 'bg-emerald-500/10 text-emerald-500 border border-emerald-500/20' : 'bg-amber-500/10 text-amber-500 border border-amber-500/20'}`}>
                                                {entry.score}% PRECISION
                                            </div>
                                        )}
                                    </div>
                                </div>

                                {entry.type === 'attempt' ? (
                                    <div className="space-y-3">
                                        <p className="text-[12px] text-gray-600 dark:text-gray-400 font-mono leading-relaxed border-l-4 border-gray-100 dark:border-white/10 pl-5 py-1 italic bg-gray-50/50 dark:bg-black/20 rounded-r-lg">
                                            {entry.reason || entry.message || entry.text || "No details provided"}
                                        </p>

                                        {entry.sub_events?.length > 0 && (
                                            <div className="flex flex-wrap gap-2.5 pl-6">
                                                {entry.sub_events.map((sub, sidx) => (
                                                    <div key={sidx} className="px-3 py-1 bg-white dark:bg-black/40 border border-gray-100 dark:border-white/5 rounded-lg text-[9px] font-black text-gray-500 dark:text-gray-400 flex items-center gap-3 transition-all hover:border-indigo-500/30">
                                                        <div className="w-1.5 h-1.5 bg-indigo-500 rounded-full animate-pulse"></div>
                                                        <span className="uppercase tracking-widest">{sub.name}</span>
                                                        <span className="font-mono text-indigo-500/60 dark:text-indigo-400/50">{sub.duration}ₛ</span>
                                                    </div>
                                                ))}
                                            </div>
                                        )}
                                    </div>
                                ) : (
                                    <p className="text-[11px] text-gray-400 italic ml-6 bg-gray-50/30 dark:bg-white/5 p-2 rounded-lg">
                                        {entry.details}
                                    </p>
                                )}
                            </div>
                        )) : (
                            <div className="flex flex-col items-center py-12 text-gray-400 gap-4 opacity-40">
                                <svg className="w-12 h-12" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
                                <span className="text-xs uppercase tracking-[0.2em] font-black">No activity recorded</span>
                            </div>
                        )}
                    </div>
                </div>
                {displayHistory && displayHistory.length > 0 && (
                    <div className="px-5 py-3 bg-gray-50/30 dark:bg-black/20 border-t border-gray-100 dark:border-white/5 flex justify-between items-center text-[10px] uppercase font-black tracking-widest text-gray-400">
                        <span>Aggregate Time Cost</span>
                        <span className="font-mono text-indigo-500">
                            {displayHistory.reduce((acc, entry) => acc + (entry.duration || 0), 0).toFixed(2)}s
                        </span>
                    </div>
                )}
            </div>
        </div>
    );
};

export default EvaluationPanel;