@graphteon/juricode
Version:
We are forging the future with lines of digital steel
788 lines • 48.4 kB
JavaScript
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