UNPKG

@graphteon/juricode

Version:

We are forging the future with lines of digital steel

788 lines 48.4 kB
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime"; import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { Box, Text, useInput, useApp, Spacer } from 'ink'; import { WSClient } from '../api/ws-client'; import { formatMessage } from '../utils/format-message'; import OpenHands from '../api/open-hands'; import { setupVSCodeTunnelFromAPI } from '../index'; const ConversationTUI = React.memo(({ taskId }) => { const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(''); const [isConnected, setIsConnected] = useState(false); const [agentState, setAgentState] = useState('connecting'); const [isInputMode, setIsInputMode] = useState(false); const [currentTaskId, setCurrentTaskId] = useState(taskId || ''); const wsRef = useRef(null); const inputValueRef = useRef(''); useEffect(() => { inputValueRef.current = inputValue; }, [inputValue]); const [currentMode, setCurrentMode] = useState(taskId ? 'conversation' : 'menu'); const [selectedIndex, setSelectedIndex] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [tasks, setTasks] = useState([]); const [suggestedTasks, setSuggestedTasks] = useState([]); const [repositories, setRepositories] = useState([]); const [branches, setBranches] = useState([]); const [filteredRepos, setFilteredRepos] = useState([]); const [selectedRepo, setSelectedRepo] = useState(null); const [createStep, setCreateStep] = useState(0); const [createForm, setCreateForm] = useState({ title: '', message: '', repository: '', gitProvider: '', branch: 'main' }); const [selectedRepoIndex, setSelectedRepoIndex] = useState(0); const [selectedBranchIndex, setSelectedBranchIndex] = useState(0); const [creating, setCreating] = useState(false); const [searchMode, setSearchMode] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const { exit } = useApp(); const forceExit = useCallback(() => { try { if (wsRef.current) { wsRef.current.disconnect(); } exit(); setTimeout(() => { process.exit(0); }, 100); } catch (error) { process.exit(0); } }, [exit]); const menuItems = [ { title: '📋 View All Tasks', value: 'tasks' }, { title: '💡 Suggested Tasks', value: 'suggested' }, { title: '📚 Browse Repositories', value: 'repositories' }, { title: '📝 Create New Task', value: 'create' }, { title: '🚪 Exit', value: 'exit' } ]; const createSteps = [ 'Enter Task Title', 'Enter Initial Message (Optional)', 'Select Repository', 'Select Branch', 'Review & Create' ]; useEffect(() => { if (currentTaskId && currentMode === 'conversation') { setMessages([]); const ws = new WSClient(); wsRef.current = ws; const processedMessageIds = new Set(); ws.onConnect(() => { setIsConnected(true); setAgentState('ready'); }); ws.onMessage((event) => { const messageId = `${event.id}-${event.timestamp}`; if (processedMessageIds.has(messageId)) { return; } processedMessageIds.add(messageId); if (event.source === 'agent' && event.action === 'system') { return; } const formatted = formatMessage(event); const newMessage = { id: messageId, source: event.source, content: formatted.text, timestamp: new Date(event.timestamp), status: 'success' }; setMessages((prev) => { const exists = prev.some(msg => msg.id === messageId); if (exists) { return prev; } return [...prev, newMessage]; }); }); ws.onError((error) => { const errorMessage = { id: `error-${Date.now()}`, source: 'system', content: `Error: ${error.message}`, timestamp: new Date(), status: 'error' }; setMessages((prev) => [...prev, errorMessage]); }); ws.onStateChange((state) => { setAgentState(state); if (state === 'awaiting_user_input' || state === 'finished') { setIsInputMode(true); } else { setIsInputMode(false); } }); ws.connect(currentTaskId).catch((error) => { const errorMessage = { id: `connect-error-${Date.now()}`, source: 'system', content: `Failed to connect: ${error.message}`, timestamp: new Date(), status: 'error' }; setMessages((prev) => [...prev, errorMessage]); }); return () => { ws.disconnect(); processedMessageIds.clear(); }; } }, [currentTaskId, currentMode]); useEffect(() => { switch (currentMode) { case 'tasks': fetchTasks(); break; case 'suggested': fetchSuggestedTasks(); break; case 'repositories': fetchRepositories(); break; case 'create': if (createStep === 2) fetchRepositories(); if (createStep === 3 && createForm.repository && createForm.repository.trim() !== '') fetchBranches(); break; } }, [currentMode, createStep, createForm.repository]); useEffect(() => { if (searchTerm) { const filtered = repositories.filter(repo => repo.full_name.toLowerCase().includes(searchTerm.toLowerCase())); setFilteredRepos(filtered); setSelectedIndex(0); } else { setFilteredRepos(repositories); } }, [searchTerm, repositories]); const fetchTasks = async () => { try { setLoading(true); const userTasks = await OpenHands.getUserConversations(); setTasks(userTasks); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch tasks'); } finally { setLoading(false); } }; const fetchSuggestedTasks = async () => { try { setLoading(true); const suggested = await OpenHands.getSuggestedTasks(); setSuggestedTasks(suggested); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch suggested tasks'); } finally { setLoading(false); } }; const fetchRepositories = async () => { try { setLoading(true); const repos = await OpenHands.retrieveUserGitRepositories(); setRepositories(repos); setFilteredRepos(repos); setSelectedRepoIndex(-1); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch repositories'); } finally { setLoading(false); } }; const fetchBranches = async () => { try { setLoading(true); const repoBranches = await OpenHands.getRepositoryBranches(createForm.repository); setBranches(repoBranches); setSelectedBranchIndex(0); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch branches'); } finally { setLoading(false); } }; const createTask = async () => { try { setCreating(true); const conversation = await OpenHands.createConversation(createForm.repository || undefined, createForm.gitProvider || undefined, createForm.message || undefined, [], undefined, createForm.repository ? createForm.branch : undefined); setCurrentTaskId(conversation.conversation_id); setCurrentMode('conversation'); setMessages([]); setCreateStep(0); setCreateForm({ title: '', message: '', repository: '', gitProvider: '', branch: 'main' }); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create task'); } finally { setCreating(false); } }; const selectTask = (task) => { setCurrentTaskId(task.conversation_id); setCurrentMode('conversation'); setMessages([]); }; const selectSuggestedTask = async (task) => { try { setLoading(true); const conversation = await OpenHands.createConversationFromSuggestedTask(task); setCurrentTaskId(conversation.conversation_id); setCurrentMode('conversation'); setMessages([]); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create conversation from suggested task'); } finally { setLoading(false); } }; const selectRepository = (repo) => { setSelectedRepo(repo); setCurrentMode('branches'); fetchRepositoryBranches(repo); }; const fetchRepositoryBranches = async (repo) => { try { setLoading(true); const repoBranches = await OpenHands.getRepositoryBranches(repo.full_name); setBranches(repoBranches); setSelectedIndex(0); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch branches'); } finally { setLoading(false); } }; const selectBranch = async (repo, branch) => { try { setLoading(true); const conversation = await OpenHands.createConversation(repo.full_name, repo.git_provider, undefined, [], undefined, branch.name); setCurrentTaskId(conversation.conversation_id); setCurrentMode('conversation'); setMessages([]); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create conversation from repository'); } finally { setLoading(false); } }; const goBack = () => { if (currentMode === 'conversation') { setCurrentMode('menu'); setCurrentTaskId(''); setMessages([]); if (wsRef.current) { wsRef.current.disconnect(); } } else if (currentMode === 'branches') { setCurrentMode('repositories'); } else if (currentMode === 'create' && createStep > 0) { setCreateStep(createStep - 1); } else { setCurrentMode('menu'); setSelectedIndex(0); } }; const handleEditorCommand = useCallback(async () => { try { setAgentState('processing'); const processingMessage = { id: Date.now().toString(), source: 'system', content: 'Setting up VSCode editor tunnel...', timestamp: new Date(), status: 'pending' }; setMessages((prev) => [...prev, processingMessage]); const vscodeUrl = await setupVSCodeTunnelFromAPI(currentTaskId); const successMessage = { id: Date.now().toString(), source: 'system', content: `VSCode editor is now available at: ${vscodeUrl}`, timestamp: new Date(), status: 'success' }; setMessages((prev) => [...prev, successMessage]); setAgentState('ready'); setIsInputMode(true); } catch (error) { const errorMessage = { id: Date.now().toString(), source: 'system', content: `Failed to setup VSCode editor: ${error instanceof Error ? error.message : 'Unknown error'}`, timestamp: new Date(), status: 'error' }; setMessages((prev) => [...prev, errorMessage]); setAgentState('ready'); setIsInputMode(true); } }, [currentTaskId]); const handleInput = useCallback((input, key) => { if (key.escape) { if (currentMode === 'menu') { forceExit(); return; } else { goBack(); return; } } if (input === 'q' || input === 'Q') { forceExit(); return; } if (loading || creating) return; if (currentMode === 'conversation') { if (!isInputMode) return; if (key.return) { if (inputValue.trim()) { const userMessage = { id: Date.now().toString(), source: 'user', content: inputValue, timestamp: new Date(), status: 'success' }; setMessages((prev) => [...prev, userMessage]); if (inputValue.trim() === '/editor') { handleEditorCommand(); } else { if (wsRef.current) { wsRef.current.send(inputValue); } } inputValueRef.current = ''; setInputValue(''); setIsInputMode(false); setAgentState('processing'); } } else if (key.backspace || key.delete) { const newValue = inputValueRef.current.slice(0, -1); inputValueRef.current = newValue; setImmediate(() => setInputValue(newValue)); } else if (input) { const newValue = inputValueRef.current + input; inputValueRef.current = newValue; setImmediate(() => setInputValue(newValue)); } return; } if (currentMode === 'menu') { if (key.upArrow && selectedIndex > 0) { setSelectedIndex(selectedIndex - 1); } if (key.downArrow && selectedIndex < menuItems.length - 1) { setSelectedIndex(selectedIndex + 1); } if (key.return) { const selectedItem = menuItems[selectedIndex]; if (selectedItem.value === 'exit') { forceExit(); } else { setCurrentMode(selectedItem.value); setSelectedIndex(0); } } return; } if (currentMode === 'tasks') { if (key.upArrow && selectedIndex > 0) { setSelectedIndex(selectedIndex - 1); } if (key.downArrow && selectedIndex < tasks.length - 1) { setSelectedIndex(selectedIndex + 1); } if (key.return && tasks.length > 0) { selectTask(tasks[selectedIndex]); } return; } if (currentMode === 'suggested') { if (key.upArrow && selectedIndex > 0) { setSelectedIndex(selectedIndex - 1); } if (key.downArrow && selectedIndex < suggestedTasks.length - 1) { setSelectedIndex(selectedIndex + 1); } if (key.return && suggestedTasks.length > 0) { selectSuggestedTask(suggestedTasks[selectedIndex]); } return; } if (currentMode === 'repositories') { if (searchMode) { if (key.return) { setSearchMode(false); return; } if (key.backspace || key.delete) { setSearchTerm(prev => prev.slice(0, -1)); return; } if (input && input.length === 1) { setSearchTerm(prev => prev + input); return; } return; } if (key.upArrow && selectedIndex > 0) { setSelectedIndex(selectedIndex - 1); } if (key.downArrow && selectedIndex < filteredRepos.length - 1) { setSelectedIndex(selectedIndex + 1); } if (key.return && filteredRepos.length > 0) { selectRepository(filteredRepos[selectedIndex]); } if (input === 's') { setSearchMode(true); } return; } if (currentMode === 'branches') { if (key.upArrow && selectedIndex > 0) { setSelectedIndex(selectedIndex - 1); } if (key.downArrow && selectedIndex < branches.length - 1) { setSelectedIndex(selectedIndex + 1); } if (key.return && branches.length > 0 && selectedRepo) { selectBranch(selectedRepo, branches[selectedIndex]); } return; } if (currentMode === 'create') { if (createStep === 0) { if (key.return && createForm.title.trim()) { setCreateStep(1); return; } if (key.backspace || key.delete) { setCreateForm(prev => ({ ...prev, title: prev.title.slice(0, -1) })); return; } if (input && input.length === 1) { setCreateForm(prev => ({ ...prev, title: prev.title + input })); return; } } if (createStep === 1) { if (key.return) { setCreateStep(2); return; } if (key.backspace || key.delete) { setCreateForm(prev => ({ ...prev, message: prev.message.slice(0, -1) })); return; } if (input && input.length === 1) { setCreateForm(prev => ({ ...prev, message: prev.message + input })); return; } } if (createStep === 2) { if (searchMode) { if (key.return) { if (selectedRepoIndex === -1) { setCreateForm(prev => ({ ...prev, repository: '', gitProvider: '', branch: 'main' })); setCreateStep(4); setSearchMode(false); setSearchTerm(''); } else { setSearchMode(false); setSelectedRepoIndex(0); } return; } if (key.backspace || key.delete) { setSearchTerm(prev => prev.slice(0, -1)); return; } if (input && input.length === 1) { setSearchTerm(prev => prev + input); return; } return; } const filteredRepos = searchTerm ? repositories.filter(repo => repo.full_name.toLowerCase().includes(searchTerm.toLowerCase())) : repositories; if (key.upArrow) { setSelectedRepoIndex(Math.max(-1, selectedRepoIndex - 1)); } if (key.downArrow) { setSelectedRepoIndex(Math.min(filteredRepos.length - 1, selectedRepoIndex + 1)); } if (key.return) { if (selectedRepoIndex === -1) { setCreateForm(prev => ({ ...prev, repository: '', gitProvider: '', branch: 'main' })); setCreateStep(4); } else if (filteredRepos.length > 0 && selectedRepoIndex < filteredRepos.length) { const selectedRepo = filteredRepos[selectedRepoIndex]; setCreateForm(prev => ({ ...prev, repository: selectedRepo.full_name, gitProvider: selectedRepo.git_provider })); setCreateStep(3); } return; } if (input === 'n' || input === 'N') { setCreateForm(prev => ({ ...prev, repository: '', gitProvider: '', branch: 'main' })); setCreateStep(4); return; } if (input === 's' || input === 'S') { setSearchMode(true); setSearchTerm(''); return; } } if (createStep === 3) { if (key.upArrow && selectedBranchIndex > 0) { setSelectedBranchIndex(selectedBranchIndex - 1); } if (key.downArrow && selectedBranchIndex < branches.length - 1) { setSelectedBranchIndex(selectedBranchIndex + 1); } if (key.return && branches.length > 0) { const selectedBranch = branches[selectedBranchIndex]; setCreateForm(prev => ({ ...prev, branch: selectedBranch.name })); setCreateStep(4); return; } } if (createStep === 4) { if (key.return) { createTask(); return; } } return; } }, [currentMode, loading, creating, isInputMode, inputValue, handleEditorCommand, goBack, forceExit, selectedIndex, menuItems, tasks, suggestedTasks, filteredRepos, branches, selectedRepo, createStep, createForm, selectedRepoIndex, repositories, selectedBranchIndex, searchMode, searchTerm]); useInput(handleInput); const getStatusIcon = useCallback((status) => { switch (status) { case 'success': return '✓'; case 'error': return '✗'; case 'pending': return '⏳'; default: return ''; } }, []); const getSourceIcon = useCallback((source) => { switch (source) { case 'user': return '🧔'; case 'agent': return '🤖'; case 'system': return '💻'; default: return ''; } }, []); const getAgentStateText = useCallback(() => { switch (agentState) { case 'connecting': return 'Connecting...'; case 'ready': return 'Ready for input'; case 'processing': return 'Agent is processing'; case 'awaiting_user_input': return 'Ready for input'; case 'finished': return 'Agent finished'; default: return agentState; } }, [agentState]); const getStatusColor = (status) => { switch (status.toLowerCase()) { case 'running': return 'green'; case 'stopped': return 'red'; case 'finished': return 'blue'; default: return 'yellow'; } }; const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString(); }; const getHeaderTitle = () => { switch (currentMode) { case 'menu': return '🏠 JuriCode Main Menu'; case 'conversation': return '💬 JuriCode Conversation'; case 'tasks': return '📋 All Tasks'; case 'suggested': return '💡 Suggested Tasks'; case 'repositories': return '📚 Browse Repositories'; case 'branches': return `🌿 Select Branch - ${selectedRepo?.full_name}`; case 'create': return `📝 Create New Task - Step ${createStep + 1} of ${createSteps.length}: ${createSteps[createStep]}`; default: return '🏠 JuriCode'; } }; const visibleMessages = useMemo(() => { const maxVisibleMessages = 10; return messages.slice(-maxVisibleMessages); }, [messages]); const messageComponents = useMemo(() => { return visibleMessages.map((message) => (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: message.source === 'user' ? 'blue' : message.source === 'agent' ? 'green' : 'yellow', children: [getSourceIcon(message.source), " ", message.source === 'user' ? 'You' : message.source === 'agent' ? 'Assistant' : 'System', " ", message.timestamp.toLocaleTimeString(), " ", getStatusIcon(message.status)] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { wrap: "wrap", children: message.content }) })] }, message.id))); }, [visibleMessages, getSourceIcon, getStatusIcon]); const inputDisplayText = useMemo(() => { return isInputMode ? inputValue : 'Waiting for agent...'; }, [isInputMode, inputValue]); const cursorComponent = useMemo(() => { return isInputMode ? _jsx(Text, { color: "gray", children: "\u2588" }) : null; }, [isInputMode]); const inputArea = useMemo(() => (_jsxs(Box, { borderStyle: "single", borderColor: isInputMode ? 'green' : 'gray', paddingX: 1, children: [_jsx(Text, { color: isInputMode ? 'green' : 'gray', children: isInputMode ? '> ' : '⏸ ' }), _jsxs(Text, { color: isInputMode ? 'white' : 'gray', children: [inputDisplayText, cursorComponent] })] })), [isInputMode, inputDisplayText, cursorComponent]); const renderContent = useCallback(() => { if (currentMode === 'conversation') { return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: messages.length === 0 ? (_jsx(Text, { color: "gray", children: agentState === 'connecting' ? 'Connecting to conversation...' : 'No messages yet. Start typing below!' })) : (_jsxs(Box, { flexDirection: "column", children: [messages.length > 10 ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["... ", messages.length - 10, " older messages"] })) : null, messageComponents] })) }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsxs(Text, { color: "yellow", children: ["Status: ", getAgentStateText()] }), _jsx(Spacer, {}), _jsxs(Text, { color: "gray", children: ["Task ID: ", currentTaskId] })] }), inputArea, _jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [isInputMode ? 'Type your message and press Enter to send' : 'Please wait for the agent to finish processing', ' • Press ESC to go back to menu'] }) })] })); } if (currentMode === 'menu') { return (_jsxs(_Fragment, { children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: menuItems.map((item, index) => (_jsx(Box, { children: _jsxs(Text, { color: index === selectedIndex ? 'green' : 'white', bold: true, children: [index === selectedIndex ? '▶ ' : ' ', item.title] }) }, item.value))) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 Navigate \u2022 Enter Select \u2022 Q Quit" }) })] })); } if (currentMode === 'tasks') { return (_jsxs(_Fragment, { children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: loading ? (_jsx(Box, { justifyContent: "center", alignItems: "center", height: "100%", children: _jsx(Text, { color: "yellow", children: "\u23F3 Loading tasks..." }) })) : error ? (_jsx(Box, { justifyContent: "center", alignItems: "center", height: "100%", children: _jsxs(Text, { color: "red", children: ["\u274C Error: ", error] }) })) : tasks.length === 0 ? (_jsx(Box, { justifyContent: "center", alignItems: "center", height: "100%", children: _jsx(Text, { color: "gray", children: "No tasks found. Create a new task to get started!" }) })) : (tasks.map((task, index) => (_jsxs(Box, { children: [_jsxs(Text, { color: index === selectedIndex ? 'green' : 'white', bold: true, children: [index === selectedIndex ? '▶ ' : ' ', task.title || 'Untitled Task'] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "ID: " }), _jsx(Text, { color: "cyan", children: task.conversation_id }), _jsx(Text, { color: "gray", children: " | Status: " }), _jsx(Text, { color: getStatusColor(task.status), children: task.status }), _jsx(Text, { color: "gray", children: " | Created: " }), _jsx(Text, { color: "blue", children: formatDate(task.created_at) })] })] }, task.conversation_id)))) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [tasks.length > 0 ? '↑↓ Navigate • Enter Select • ' : '', "ESC Back \u2022 Q Quit"] }) })] })); } if (currentMode === 'suggested') { return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: loading ? (_jsx(Text, { color: "yellow", children: "\u23F3 Loading suggested tasks..." })) : error ? (_jsxs(Text, { color: "red", children: ["\u274C Error: ", error] })) : suggestedTasks.length === 0 ? (_jsx(Text, { color: "gray", children: "No suggested tasks available at the moment." })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "gray", children: [suggestedTasks.length, " suggested tasks found \u2022 Showing ", selectedIndex + 1, "/", suggestedTasks.length] }) }), (() => { const maxVisible = 20; const startIndex = Math.max(0, selectedIndex - Math.floor(maxVisible / 2)); const endIndex = Math.min(suggestedTasks.length, startIndex + maxVisible); const adjustedStartIndex = Math.max(0, endIndex - maxVisible); const visibleTasks = suggestedTasks.slice(adjustedStartIndex, endIndex); return (_jsxs(_Fragment, { children: [adjustedStartIndex > 0 ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["\u2191 ", adjustedStartIndex, " more tasks above"] })) : null, visibleTasks.map((task, index) => { const actualIndex = adjustedStartIndex + index; return (_jsxs(Box, { children: [_jsxs(Text, { color: actualIndex === selectedIndex ? 'green' : 'white', bold: true, children: [actualIndex === selectedIndex ? '▶ ' : ' ', task.title] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "Repository: " }), _jsx(Text, { color: "cyan", children: task.repo }), _jsx(Text, { color: "gray", children: " | Provider: " }), _jsx(Text, { color: "blue", children: task.git_provider }), _jsx(Text, { color: "gray", children: " | Type: " }), _jsx(Text, { color: "yellow", children: task.task_type }), _jsx(Text, { color: "gray", children: " | Issue #" }), _jsx(Text, { color: "magenta", children: task.issue_number })] })] }, actualIndex)); }), endIndex < suggestedTasks.length ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["\u2193 ", suggestedTasks.length - endIndex, " more tasks below"] })) : null] })); })()] })) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [suggestedTasks.length > 0 ? `${selectedIndex + 1}/${suggestedTasks.length} • ↑↓ Navigate • Enter Select • ` : '', "ESC Back \u2022 Q Quit"] }) })] })); } if (currentMode === 'repositories') { return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [searchMode ? (_jsx(Box, { marginBottom: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, children: _jsxs(Text, { color: "yellow", children: ["Search: ", searchTerm, "\u2588"] }) })) : null, _jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: loading ? (_jsx(Text, { color: "yellow", children: "\u23F3 Loading repositories..." })) : error ? (_jsxs(Text, { color: "red", children: ["\u274C Error: ", error] })) : filteredRepos.length === 0 ? (_jsx(Text, { color: "gray", children: searchTerm ? `No repositories found matching "${searchTerm}"` : 'No repositories found.' })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "gray", children: [filteredRepos.length, " repositories found \u2022 Showing ", selectedIndex + 1, "/", filteredRepos.length] }) }), (() => { const maxVisible = 20; const startIndex = Math.max(0, selectedIndex - Math.floor(maxVisible / 2)); const endIndex = Math.min(filteredRepos.length, startIndex + maxVisible); const adjustedStartIndex = Math.max(0, endIndex - maxVisible); const visibleRepos = filteredRepos.slice(adjustedStartIndex, endIndex); return (_jsxs(_Fragment, { children: [adjustedStartIndex > 0 ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["\u2191 ", adjustedStartIndex, " more repositories above"] })) : null, visibleRepos.map((repo, index) => { const actualIndex = adjustedStartIndex + index; return (_jsxs(Box, { children: [_jsxs(Text, { color: actualIndex === selectedIndex ? 'green' : 'white', bold: true, children: [actualIndex === selectedIndex ? '▶ ' : ' ', repo.full_name, repo.stargazers_count && repo.stargazers_count > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u2605", repo.stargazers_count] })) : null] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "Provider: " }), _jsx(Text, { color: "blue", children: repo.git_provider }), _jsx(Text, { color: "gray", children: " | " }), _jsx(Text, { color: repo.is_public ? 'green' : 'yellow', children: repo.is_public ? 'Public' : 'Private' })] })] }, repo.full_name)); }), endIndex < filteredRepos.length ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["\u2193 ", filteredRepos.length - endIndex, " more repositories below"] })) : null] })); })()] })) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [filteredRepos.length > 0 ? `${selectedIndex + 1}/${filteredRepos.length} • ↑↓ Navigate • Enter Select • ` : '', "S Search \u2022 ESC Back \u2022 Q Quit"] }) })] })); } if (currentMode === 'branches') { return (_jsxs(_Fragment, { children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: loading ? (_jsx(Box, { justifyContent: "center", alignItems: "center", height: "100%", children: _jsx(Text, { color: "yellow", children: "\u23F3 Loading branches..." }) })) : branches.length === 0 ? (_jsx(Box, { justifyContent: "center", alignItems: "center", height: "100%", children: _jsx(Text, { color: "gray", children: "No branches found." }) })) : (branches.map((branch, index) => (_jsxs(Box, { children: [_jsxs(Text, { color: index === selectedIndex ? 'green' : 'white', bold: true, children: [index === selectedIndex ? '▶ ' : ' ', branch.name, branch.protected ? _jsx(Text, { color: "red", children: " \uD83D\uDD12" }) : null] }), branch.last_push_date ? (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "Last push: " }), _jsx(Text, { color: "blue", children: new Date(branch.last_push_date).toLocaleDateString() })] })) : null] }, branch.name)))) }), _jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [branches.length > 0 ? '↑↓ Navigate • Enter Select • ' : '', "ESC Back \u2022 Q Quit"] }) })] })); } if (currentMode === 'create') { const renderCreateStep = () => { switch (createStep) { case 0: return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Enter a title for your task:" }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "blue", paddingX: 1, children: _jsxs(Text, { children: [createForm.title, "\u2588"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Enter to continue" }) })] })); case 1: return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Enter an initial message (optional):" }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "blue", paddingX: 1, children: _jsxs(Text, { children: [createForm.message, "\u2588"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Enter to continue (can be empty)" }) })] })); case 2: return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Select a repository:" }), searchMode ? (_jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, children: _jsxs(Text, { color: "yellow", children: ["Search: ", searchTerm || '', "\u2588"] }) })) : null, loading ? (_jsx(Box, { marginTop: 1, justifyContent: "center", children: _jsx(Text, { color: "yellow", children: "\u23F3 Loading repositories..." }) })) : error ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u274C Error: ", error] }) })) : (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: selectedRepoIndex === -1 ? 'green' : 'white', bold: true, children: [selectedRepoIndex === -1 ? '▶ ' : ' ', "\u23ED\uFE0F Skip No Repository"] }) }), repositories.length === 0 ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "No repositories available." }) })) : (_jsx(_Fragment, { children: (() => { const maxVisible = 19; const filteredRepos = searchTerm ? repositories.filter(repo => repo.full_name.toLowerCase().includes(searchTerm.toLowerCase())) : repositories; if (filteredRepos.length === 0) { return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: searchTerm ? `No repositories found matching "${searchTerm}"` : 'No repositories available.' }) })); } const startIndex = Math.max(0, selectedRepoIndex - Math.floor(maxVisible / 2)); const endIndex = Math.min(filteredRepos.length, startIndex + maxVisible); const adjustedStartIndex = Math.max(0, endIndex - maxVisible); const visibleRepos = filteredRepos.slice(adjustedStartIndex, endIndex); return (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsxs(Text, { color: "gray", children: [filteredRepos.length, " repositories found \u2022 Showing ", selectedRepoIndex >= 0 ? `${selectedRepoIndex + 1}` : 'Skip', "/", filteredRepos.length + 1] }) }), adjustedStartIndex > 0 ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["\u2191 ", adjustedStartIndex, " more repositories above"] })) : null, visibleRepos.map((repo, index) => { const actualIndex = adjustedStartIndex + index; return (_jsxs(Box, { children: [_jsxs(Text, { color: actualIndex === selectedRepoIndex ? 'green' : 'white', bold: true, children: [actualIndex === selectedRepoIndex ? '▶ ' : ' ', repo.full_name || 'Unknown Repository'] }), repo.stargazers_count && repo.stargazers_count > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u2605", repo.stargazers_count] })) : null, _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "Provider: " }), _jsx(Text, { color: "blue", children: repo.git_provider || 'Unknown' }), _jsx(Text, { color: "gray", children: " | " }), _jsx(Text, { color: repo.is_public ? 'green' : 'yellow', children: repo.is_public ? 'Public' : 'Private' })] })] }, repo.full_name || `repo-${index}`)); }), endIndex < filteredRepos.length ? (_jsxs(Text, { color: "gray", dimColor: true, children: ["\u2193 ", filteredRepos.length - endIndex, " more repositories below"] })) : null] })); })() }))] }))] })); case 3: if (!createForm.repository || createForm.repository.trim() === '') { setCreateStep(4); return null; } return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["Select a branch for ", createForm.repository, ":"] }), loading ? (_jsx(Box, { marginTop: 1, justifyContent: "center", children: _jsx(Text, { color: "yellow", children: "\u23F3 Loading branches..." }) })) : error ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u274C Error: ", error] }) })) : branches.length === 0 ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "No branches found." }) })) : (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [branches.slice(0, 5).map((branch, index) => (_jsx(Box, { children: _jsxs(Text, { color: index === selectedBranchIndex ? 'green' : 'white', children: [index === selectedBranchIndex ? '▶ ' : ' ', branch.name, branch.protected ? _jsx(Text, { color: "red", children: " \uD83D\uDD12" }) : null] }) }, branch.name))), branches.length > 5 ? (_jsxs(Text, { color: "gray", children: ["... and ", branches.length - 5, " more"] })) : null] }))] })); case 4: return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Review your task:" }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "blue", paddingX: 1, paddingY: 1, children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "Title: " }), _jsx(Text, { color: "white", bold: true, children: createForm.title || 'Untitled' })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Message: " }), _jsx(Text, { color: "white", children: createForm.message || '(none)' })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Repository: " }), _jsx(Text, { color: "cyan", children: createForm.repository || '(none - standalone task)' })] }), createForm.repository && createForm.repository.trim() !== '' ? (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Provider: " }), _jsx(Text, { color: "blue", children: createForm.gitProvider || 'Unknown' })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Branch: " }), _jsx(Text, { color: "green", children: createForm.branch || 'main' })] })] })) : null] }) }), _jsx(Box, { marginTop: 1, children: creating ? (_jsx(Text, { color: "yellow", children: "\u23F3 Creating task..." })) : (_jsx(Text, { color: "green", children: "Press Enter to create task" })) })] })); default: return null; } }; return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, paddingY: 1, children: [error && createStep === 4 ? (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "red", children: ["\u274C Error: ", error] }) })) : null, renderCreateStep()] }), _jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", children: createStep === 2 && !searchMode ? '↑↓ Navigate • Enter Select • N Skip • S Search • ' + (createStep > 0 ? 'ESC Previous Step • ' : 'ESC Back • ') + 'Q Quit' : createStep === 2 && searchMode ? 'Type to search • Enter Finish Search • ' + (createStep > 0 ? 'ESC Previous Step • ' : 'ESC Back • ') + 'Q Quit' : createStep === 3 && branches.length > 0 ? '↑↓ Navigate • Enter Select • ' + (createStep > 0 ? 'ESC Previous Step • ' : 'ESC Back • ') + 'Q Quit' : (createStep > 0 ? 'ESC Previous Step • ' : 'ESC Back • ') + 'Q Quit' }) })] })); } return null; }, [currentMode, messages, agentState, messageComponents, isInputMode, inputArea, currentTaskId, getAgentStateText, loading, error, tasks, selectedIndex, menuItems, exit, suggestedTasks, searchMode, searchTerm, filteredRepos, branches, selectedRepo, createStep, createForm, selectedRepoIndex, repositories, selectedBranchIndex, creating]); return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsxs(Box, { borderStyle: "single", borderColor: "blue", paddingX: 1, children: [_jsx(Text, { color: "blue", bold: true, children: getHeaderTitle() }), currentMode === 'conversation' ? (_jsxs(_Fragment, { children: [_jsx(Spacer, {}), _jsx(Text, { color: isConnected ? 'green' : 'red', children: isConnected ? '● Connected' : '● Disconnected' })] })) : null] }), renderContent()] })); }); export default ConversationTUI; //# sourceMappingURL=ConversationTUI.js.map