kya-validator 0.2.3

Rust core KYA (Know Your Agent) validator with Python bindings, TEE support, and blockchain integration
Documentation
/**
 * Main Demo Dashboard - Agent-based procurement workflow visualization.
 */
import { useState, useEffect } from 'react';
import { useDemoStore } from '../../store/demoStore';
import { AgentMode, ClientType, SessionStart } from '../../types/demoTypes';
import { demoApi } from '../../api/demoApi';
import AgentControlPanel from '../../components/demo/AgentControlPanel';
import TerminalPane from '../../components/demo/TerminalPane';
import PolicyValidationStatus from '../../components/demo/PolicyValidationStatus';
import NegotiationTimeline from '../../components/demo/NegotiationTimeline';
import AgentThinking from '../../components/demo/AgentThinking';

export default function DemoDashboard() {
  const {
    sessionId,
    agentMode,
    procurementMode,
    recipientMode,
    clientType,
    isSessionActive,
    isConnected,
    isConnecting,
    connectionError,
    exchangeStatus,
    visibleReason,
    setSessionId,
    setAgentMode,
    setProcurementMode,
    setRecipientMode,
    setClientType,
    setIsSessionActive,
    connectWebSocket,
    disconnectWebSocket,
    reset,
  } = useDemoStore();

  const [selectedTab, setSelectedTab] = useState<'dashboard' | 'flow' | 'negotiation'>('dashboard');
  
  // Check dark mode from document class (set by App.tsx)
  const darkMode = typeof window !== 'undefined' && document.documentElement.classList.contains('dark');

  useEffect(() => {
    return () => {
      reset();
    };
  }, []);

  const handleStartSession = async () => {
    const newSessionId = `session-${Date.now()}`;
    
    // Set session ID
    setSessionId(newSessionId);
    
    // Prepare session start data with dual-role mode support
    const sessionStart: SessionStart = {
      message_id: `${newSessionId}-start`,
      timestamp: new Date().toISOString(),
      message_type: 'session_start' as any,
      session_id: newSessionId,
      // Include both legacy and new role-specific modes for compatibility
      agent_mode: agentMode,
      procurement_mode: procurementMode,
      recipient_mode: recipientMode,
      client_type: clientType,
      scenario_config: {
        scenario_id: 'default',
      },
    };

    try {
      // Call API to start session
      await demoApi.startSession(sessionStart);
      
      // Set session as active immediately for better UX
      setIsSessionActive(true);
      
      // Try to connect to WebSocket (non-blocking)
      connectWebSocket(newSessionId).catch((error) => {
        console.warn('WebSocket connection failed, continuing without it:', error);
        // Don't block session start if WebSocket fails
      });
      
      console.log('Session started:', newSessionId);
    } catch (error) {
      console.error('Failed to start session:', error);
      // Set session active anyway for demo purposes
      setIsSessionActive(true);
    }
  };

  const handleEndSession = async () => {
    if (sessionId) {
      try {
        const sessionEnd = {
          message_id: `${sessionId}-end`,
          timestamp: new Date().toISOString(),
          message_type: 'session_end' as any,
          session_id: sessionId,
          reason: 'user_ended',
        };
        
        await demoApi.endSession(sessionId, sessionEnd);
      } catch (error) {
        console.error('Failed to end session:', error);
      }
    }
    
    // Disconnect WebSocket
    disconnectWebSocket();
    setIsSessionActive(false);
    console.log('Session ended');
  };

  return (
    <div className={`min-h-screen transition-colors duration-300 ${
      darkMode
        ? 'bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white'
        : 'bg-gradient-to-br from-slate-50 via-slate-100 to-slate-200 text-slate-900'
    }`}>
      {/* Header */}
      <header className={`border-b backdrop-blur-sm transition-colors duration-300 ${
        darkMode
          ? 'border-slate-700 bg-slate-800/50'
          : 'border-slate-200 bg-white/50'
      }`}>
        <div className="max-w-7xl mx-auto px-4 py-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-4">
              <h1 className={`text-2xl font-bold bg-clip-text text-transparent ${
                darkMode
                  ? 'bg-gradient-to-r from-cyan-400 to-blue-500'
                  : 'bg-gradient-to-r from-indigo-600 to-blue-600'
              }`}>
                KYA Validator Demo
              </h1>
              <span className={`text-sm ${darkMode ? 'text-slate-400' : 'text-slate-600'}`}>Agent-Based Procurement</span>
            </div>
            <div className="flex items-center gap-4">
              <div
                className={`flex items-center gap-2 px-3 py-1.5 rounded-full text-sm ${
                  isConnected
                    ? darkMode ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700'
                    : darkMode ? 'bg-red-500/20 text-red-400' : 'bg-red-100 text-red-700'
                }`}
              >
                <div
                  className={`w-2 h-2 rounded-full ${isConnecting ? 'animate-pulse' : ''} ${
                    isConnected ? 'bg-green-400' : 'bg-red-400'
                  }`}
                />
                <span>{isConnecting ? 'Connecting...' : isConnected ? 'Connected' : 'Disconnected'}</span>
              </div>
              {connectionError && (
                <div className={`text-sm ${darkMode ? 'text-red-400' : 'text-red-600'}`}>{connectionError}</div>
              )}
            </div>
          </div>
        </div>
      </header>

      {/* Main Content */}
      <main className="max-w-7xl mx-auto p-4">
        {/* Session Controls */}
        <div className={`mb-6 rounded-xl p-6 border transition-colors duration-300 ${
          darkMode
            ? 'bg-slate-800/50 border-slate-700'
            : 'bg-white/50 border-slate-200'
        }`}>
          <h2 className={`text-lg font-semibold mb-4 ${darkMode ? 'text-white' : 'text-slate-900'}`}>Session Configuration</h2>
          <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
            {/* Procurement Agent Mode */}
            <div>
              <label htmlFor="procurement-mode-select" className={`block text-sm font-medium mb-2 ${
                darkMode ? 'text-slate-300' : 'text-slate-700'
              }`}>
                Procurement Agent Mode
              </label>
              <select
                id="procurement-mode-select"
                value={procurementMode}
                onChange={(e) => setProcurementMode(e.target.value as AgentMode)}
                disabled={isSessionActive}
                className={`w-full rounded-lg px-4 py-2 focus:ring-2 focus:ring-cyan-500 focus:border-transparent disabled:opacity-50 transition-colors duration-200 ${
                  darkMode
                    ? 'bg-slate-700 border-slate-600 text-white'
                    : 'bg-slate-100 border-slate-300 text-slate-900'
                }`}
              >
                <option value={AgentMode.LLM}>Real LLM (Requires API Key)</option>
                <option value={AgentMode.SIMULATED}>Simulated (Demo Mode)</option>
              </select>
            </div>

            {/* Recipient Agent Mode */}
            <div>
              <label htmlFor="recipient-mode-select" className={`block text-sm font-medium mb-2 ${
                darkMode ? 'text-slate-300' : 'text-slate-700'
              }`}>
                Recipient Agent Mode
              </label>
              <select
                id="recipient-mode-select"
                value={recipientMode}
                onChange={(e) => setRecipientMode(e.target.value as AgentMode)}
                disabled={isSessionActive}
                className={`w-full rounded-lg px-4 py-2 focus:ring-2 focus:ring-cyan-500 focus:border-transparent disabled:opacity-50 transition-colors duration-200 ${
                  darkMode
                    ? 'bg-slate-700 border-slate-600 text-white'
                    : 'bg-slate-100 border-slate-300 text-slate-900'
                }`}
              >
                <option value={AgentMode.LLM}>Real LLM (Requires API Key)</option>
                <option value={AgentMode.SIMULATED}>Simulated (Demo Mode)</option>
              </select>
            </div>

            {/* Client Type */}
            <div>
              <label htmlFor="client-type-select" className={`block text-sm font-medium mb-2 ${
                darkMode ? 'text-slate-300' : 'text-slate-700'
              }`}>
                Client Type
              </label>
              <select
                id="client-type-select"
                value={clientType}
                onChange={(e) => setClientType(e.target.value as ClientType)}
                disabled={isSessionActive}
                className={`w-full rounded-lg px-4 py-2 focus:ring-2 focus:ring-cyan-500 focus:border-transparent disabled:opacity-50 transition-colors duration-200 ${
                  darkMode
                    ? 'bg-slate-700 border-slate-600 text-white'
                    : 'bg-slate-100 border-slate-300 text-slate-900'
                }`}
              >
                <option value={ClientType.FLOW_STOREFRONT}>
                  Type A: Flow Storefront
                </option>
                <option value={ClientType.AGENT_RECEIVER}>
                  Type B: Agent Receiver
                </option>
                <option value={ClientType.DOC_STOREFRONT}>
                  Type C: Documentation Storefront
                </option>
              </select>
            </div>

            {/* Action Button */}
            <div className="flex items-end">
              {!isSessionActive ? (
                <button
                  onClick={handleStartSession}
                  disabled={isConnecting}
                  className="w-full bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-600 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold py-2 px-6 rounded-lg transition-all duration-200 shadow-lg shadow-cyan-500/20"
                >
                  {isConnecting ? 'Starting...' : 'Start Session'}
                </button>
              ) : (
                <button
                  onClick={handleEndSession}
                  className="w-full bg-gradient-to-r from-red-500 to-pink-600 hover:from-red-600 hover:to-pink-700 text-white font-semibold py-2 px-6 rounded-lg transition-all duration-200 shadow-lg shadow-red-500/20"
                >
                  End Session
                </button>
              )}
            </div>
          </div>

          {/* Exchange Status Indicator */}
          {(exchangeStatus || visibleReason) && (
            <div className={`mt-4 p-3 rounded-lg border ${
              exchangeStatus === 'failed'
                ? darkMode ? 'bg-red-500/10 border-red-500/30 text-red-400' : 'bg-red-50 border-red-200 text-red-700'
                : exchangeStatus === 'degraded'
                ? darkMode ? 'bg-yellow-500/10 border-yellow-500/30 text-yellow-400' : 'bg-yellow-50 border-yellow-200 text-yellow-700'
                : darkMode ? 'bg-green-500/10 border-green-500/30 text-green-400' : 'bg-green-50 border-green-200 text-green-700'
            }`}>
              <div className="flex items-center gap-2">
                <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                  {exchangeStatus === 'failed' ? (
                    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
                  ) : exchangeStatus === 'degraded' ? (
                    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
                  ) : (
                    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
                  )}
                </svg>
                <span className="font-semibold">
                  Exchange Status: {exchangeStatus?.toUpperCase()}
                </span>
                {visibleReason && (
                  <span className="text-sm opacity-90">- {visibleReason}</span>
                )}
              </div>
            </div>
          )}
        </div>

        {/* Tab Navigation */}
        <div className="flex gap-2 mb-4">
          <button
            onClick={() => setSelectedTab('dashboard')}
            className={`px-4 py-2 rounded-lg font-medium transition-all ${
              selectedTab === 'dashboard'
                ? 'bg-cyan-500 text-white'
                : darkMode
                  ? 'bg-slate-700 text-slate-300 hover:bg-slate-600'
                  : 'bg-slate-200 text-slate-700 hover:bg-slate-300'
            }`}
          >
            Dashboard
          </button>
          <button
            onClick={() => setSelectedTab('flow')}
            className={`px-4 py-2 rounded-lg font-medium transition-all ${
              selectedTab === 'flow'
                ? 'bg-cyan-500 text-white'
                : darkMode
                  ? 'bg-slate-700 text-slate-300 hover:bg-slate-600'
                  : 'bg-slate-200 text-slate-700 hover:bg-slate-300'
            }`}
          >
            Workflow Flow
          </button>
          <button
            onClick={() => setSelectedTab('negotiation')}
            className={`px-4 py-2 rounded-lg font-medium transition-all ${
              selectedTab === 'negotiation'
                ? 'bg-cyan-500 text-white'
                : darkMode
                  ? 'bg-slate-700 text-slate-300 hover:bg-slate-600'
                  : 'bg-slate-200 text-slate-700 hover:bg-slate-300'
            }`}
          >
            Negotiation
          </button>
        </div>

        {/* Dashboard Content */}
        {selectedTab === 'dashboard' && (
          <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
            <div className="lg:col-span-2">
              <AgentControlPanel />
            </div>
            <div className="lg:col-span-1">
              <TerminalPane />
            </div>
            <div className="lg:col-span-1">
              <PolicyValidationStatus />
            </div>
          </div>
        )}

        {selectedTab === 'flow' && (
          <div className="bg-slate-800/50 rounded-xl p-6 border border-slate-700">
            <h2 className="text-lg font-semibold mb-4">Workflow Visualization</h2>
            <p className="text-slate-400">
              Workflow visualization will be displayed here using Cytoscape.js.
              This will show the flow-based storefront state machine.
            </p>
          </div>
        )}

        {selectedTab === 'negotiation' && (
          <div className="bg-slate-800/50 rounded-xl p-6 border border-slate-700">
            <h2 className="text-lg font-semibold mb-4">Negotiation History</h2>
            <NegotiationTimeline />
          </div>
        )}

        {/* Agent Thinking Panel */}
        {isSessionActive && <AgentThinking />}
      </main>
    </div>
  );
}