import React, { useState, useEffect } from 'react'; import useFlowStore from '../store/flowStore'; import type { NodeData } from '../store/flowStore'; import ReactMarkdown from 'react-markdown'; import { Play, Settings, Info, Save } from 'lucide-react'; const Sidebar = () => { const { nodes, selectedNodeId, updateNodeData, getActiveContext } = useFlowStore(); const [activeTab, setActiveTab] = useState<'interact' | 'settings' | 'debug'>('interact'); const [streamBuffer, setStreamBuffer] = useState(''); const selectedNode = nodes.find((n) => n.id === selectedNodeId); // Reset stream buffer when node changes useEffect(() => { setStreamBuffer(''); }, [selectedNodeId]); if (!selectedNode) { return (

Select a node to edit

); } const handleRun = async () => { if (!selectedNode) return; updateNodeData(selectedNode.id, { status: 'loading', response: '' }); setStreamBuffer(''); // Use getActiveContext which respects the user's selected traces const context = getActiveContext(selectedNode.id); try { const response = await fetch('http://localhost:8000/api/run_node_stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ node_id: selectedNode.id, incoming_contexts: [{ messages: context }], // Simple list wrap for now user_prompt: selectedNode.data.userPrompt, merge_strategy: selectedNode.data.mergeStrategy || 'smart', config: { provider: selectedNode.data.model.includes('gpt') ? 'openai' : 'google', model_name: selectedNode.data.model, temperature: selectedNode.data.temperature, system_prompt: selectedNode.data.systemPrompt, api_key: selectedNode.data.apiKey, } }) }); if (!response.body) return; const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullResponse = ''; while (true) { const { value, done } = await reader.read(); if (done) break; const chunk = decoder.decode(value); fullResponse += chunk; setStreamBuffer(prev => prev + chunk); // We update the store less frequently or at the end to avoid too many re-renders // But for "live" feel we might want to update local state `streamBuffer` and sync to store at end } // Update final state // Append the new interaction to the node's output messages const newUserMsg = { id: `msg_${Date.now()}_u`, role: 'user', content: selectedNode.data.userPrompt }; const newAssistantMsg = { id: `msg_${Date.now()}_a`, role: 'assistant', content: fullResponse }; updateNodeData(selectedNode.id, { status: 'success', response: fullResponse, messages: [...context, newUserMsg, newAssistantMsg] as any }); } catch (error) { console.error(error); updateNodeData(selectedNode.id, { status: 'error' }); } }; const handleChange = (field: keyof NodeData, value: any) => { updateNodeData(selectedNode.id, { [field]: value }); }; return (
{/* Header */}
handleChange('label', e.target.value)} className="font-bold text-lg bg-transparent border-none focus:ring-0 focus:outline-none w-full" />
{selectedNode.data.status}
ID: {selectedNode.id}
{/* Tabs */}
{/* Content */}
{activeTab === 'interact' && (
{/* Trace Selector */} {selectedNode.data.traces && selectedNode.data.traces.length > 0 && (
{selectedNode.data.traces.map((trace) => { const isActive = selectedNode.data.activeTraceIds?.includes(trace.id); return (
{ const current = selectedNode.data.activeTraceIds || []; const next = [trace.id]; // Single select mode handleChange('activeTraceIds', next); }} >
#{trace.id.slice(-4)}
From Node: {trace.sourceNodeId}
{trace.messages.length} msgs
); })}
)}