diff --git a/archon-ui-main/src/App.tsx b/archon-ui-main/src/App.tsx index 2a0cdc22..f81dbec2 100644 --- a/archon-ui-main/src/App.tsx +++ b/archon-ui-main/src/App.tsx @@ -6,6 +6,7 @@ import { KnowledgeBasePage } from './pages/KnowledgeBasePage'; import { SettingsPage } from './pages/SettingsPage'; import { MCPPage } from './pages/MCPPage'; import { OnboardingPage } from './pages/OnboardingPage'; +import CodingAgents from './pages/CodingAgents'; import { MainLayout } from './components/layout/MainLayout'; import { ThemeProvider } from './contexts/ThemeContext'; import { ToastProvider } from './contexts/ToastContext'; @@ -49,6 +50,7 @@ const AppRoutes = () => { } /> } /> } /> + } /> } /> {projectsEnabled ? ( <> diff --git a/archon-ui-main/src/components/layout/Navigation.tsx b/archon-ui-main/src/components/layout/Navigation.tsx index e2f1e806..27b14e37 100644 --- a/archon-ui-main/src/components/layout/Navigation.tsx +++ b/archon-ui-main/src/components/layout/Navigation.tsx @@ -1,4 +1,4 @@ -import { BookOpen, Settings } from "lucide-react"; +import { BookOpen, Settings, Code2 } from "lucide-react"; import type React from "react"; import { Link, useLocation } from "react-router-dom"; // TEMPORARY: Use old SettingsContext until settings are migrated @@ -34,6 +34,12 @@ export function Navigation({ className }: NavigationProps) { label: "Knowledge Base", enabled: true, }, + { + path: "/coding-agents", + icon: , + label: "Coding Agents", + enabled: true, + }, { path: "/mcp", icon: ( diff --git a/archon-ui-main/src/features/coding-agents/components/ChatInterface.tsx b/archon-ui-main/src/features/coding-agents/components/ChatInterface.tsx new file mode 100644 index 00000000..e21e99de --- /dev/null +++ b/archon-ui-main/src/features/coding-agents/components/ChatInterface.tsx @@ -0,0 +1,95 @@ +import { useEffect } from "react"; +import { useCodingAgentChat, useCodingAgentSession } from "../hooks"; +import { MessageList } from "./MessageList"; +import { MessageInput } from "./MessageInput"; +import { SessionStatus } from "./SessionStatus"; + +interface ChatInterfaceProps { + workingDirectory: string; +} + +export function ChatInterface({ workingDirectory }: ChatInterfaceProps) { + const { + currentSessionId, + session, + isSessionLoading, + createSession, + cancelSession, + isCreatingSession, + isCancellingSession, + } = useCodingAgentSession(); + + const { + messages, + isProcessing, + sendMessage, + clearMessages, + } = useCodingAgentChat(currentSessionId); + + // Create session when working directory is set + useEffect(() => { + if (workingDirectory && !currentSessionId && !isCreatingSession) { + createSession({ + workingDirectory, + mcpServers: [] // Explicitly pass empty array for now + }); + } + }, [workingDirectory, currentSessionId, isCreatingSession, createSession]); + + // Clear messages when session changes + useEffect(() => { + if (currentSessionId) { + clearMessages(); + } + }, [currentSessionId, clearMessages]); + + const handleCancel = () => { + if (currentSessionId && !isCancellingSession) { + cancelSession(); + } + }; + + const isDisabled = !currentSessionId || + session?.status === "error" || + session?.status === "cancelled" || + isSessionLoading || + isCreatingSession; + + return ( +
+ {/* Header with session status */} +
+

Coding Agent Chat

+ +
+ + {/* Messages area */} + + + {/* Input area */} + +
+ ); +} \ No newline at end of file diff --git a/archon-ui-main/src/features/coding-agents/components/FolderSelector.tsx b/archon-ui-main/src/features/coding-agents/components/FolderSelector.tsx new file mode 100644 index 00000000..eb87dc32 --- /dev/null +++ b/archon-ui-main/src/features/coding-agents/components/FolderSelector.tsx @@ -0,0 +1,105 @@ +import { Folder, FolderOpen } from "lucide-react"; +import { useState, useRef } from "react"; +import { Button } from "../../ui/primitives/button"; +import { Input } from "../../ui/primitives/input"; + +interface FolderSelectorProps { + value: string; + onChange: (path: string) => void; + disabled?: boolean; + placeholder?: string; + label?: string; +} + +export function FolderSelector({ + value, + onChange, + disabled = false, + placeholder = "Enter full absolute path (e.g., /Users/name/Projects/myproject)", + label = "Working Directory", +}: FolderSelectorProps) { + const [isSelecting, setIsSelecting] = useState(false); + const inputRef = useRef(null); + + // Handle folder selection via file input + const handleFolderSelect = () => { + // Create a hidden input element for folder selection + const input = document.createElement("input"); + input.type = "file"; + input.webkitdirectory = true; + input.directory = true; + + input.onchange = (e: any) => { + const files = e.target.files; + if (files && files.length > 0) { + // Extract the folder path from the first file + const path = files[0].webkitRelativePath || files[0].path || ""; + const folderPath = path.split("/")[0]; + + // Browser security prevents getting the full path + // Only the folder name is available, which isn't enough + // Show a message to the user + const folderName = folderPath || "selected folder"; + alert(`Browser security prevents accessing the full path.\n\nPlease manually enter the complete path to "${folderName}" in the input field.\n\nExample: /Users/username/Projects/${folderName}`); + // Don't update with just the folder name as it won't work + // onChange(folderPath || value); + } + setIsSelecting(false); + }; + + input.oncancel = () => { + setIsSelecting(false); + }; + + setIsSelecting(true); + input.click(); + }; + + return ( +
+ {label && ( + + )} +
+
+ + onChange(e.target.value)} + disabled={disabled || isSelecting} + placeholder={placeholder} + className="pl-10 bg-gray-900/50 border-gray-700 text-gray-100 placeholder:text-gray-500" + /> +
+ +
+ {value && ( +

+ Selected: {value} +

+ )} +
+ ); +} + +// Add type declarations for webkitdirectory +declare module "react" { + interface InputHTMLAttributes extends AriaAttributes, DOMAttributes { + webkitdirectory?: boolean; + directory?: boolean; + } +} \ No newline at end of file diff --git a/archon-ui-main/src/features/coding-agents/components/MessageInput.tsx b/archon-ui-main/src/features/coding-agents/components/MessageInput.tsx new file mode 100644 index 00000000..ff325cdc --- /dev/null +++ b/archon-ui-main/src/features/coding-agents/components/MessageInput.tsx @@ -0,0 +1,96 @@ +import { Send, Square } from "lucide-react"; +import { useState, useRef, useEffect, KeyboardEvent } from "react"; +import { Button } from "../../ui/primitives/button"; + +interface MessageInputProps { + onSend: (message: string) => void; + onCancel?: () => void; + disabled?: boolean; + isProcessing?: boolean; + placeholder?: string; +} + +export function MessageInput({ + onSend, + onCancel, + disabled = false, + isProcessing = false, + placeholder = "Type a message...", +}: MessageInputProps) { + const [message, setMessage] = useState(""); + const textareaRef = useRef(null); + + // Auto-resize textarea + useEffect(() => { + const textarea = textareaRef.current; + if (textarea) { + textarea.style.height = "auto"; + textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`; + } + }, [message]); + + const handleSend = () => { + const trimmedMessage = message.trim(); + if (trimmedMessage && !disabled && !isProcessing) { + onSend(trimmedMessage); + setMessage(""); + } + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + return ( +
+
+
+