Adding features work

This commit is contained in:
Auto
2025-12-30 16:11:08 +02:00
parent 5ffb6a4c5e
commit cb65cfe151
15 changed files with 562 additions and 126 deletions

View File

@@ -0,0 +1,177 @@
/**
* Debug Log Viewer Component
*
* Collapsible panel at the bottom of the screen showing real-time
* agent output (tool calls, results, steps). Similar to browser DevTools.
*/
import { useEffect, useRef, useState } from 'react'
import { ChevronUp, ChevronDown, Trash2, Terminal } from 'lucide-react'
interface DebugLogViewerProps {
logs: Array<{ line: string; timestamp: string }>
isOpen: boolean
onToggle: () => void
onClear: () => void
}
type LogLevel = 'error' | 'warn' | 'debug' | 'info'
export function DebugLogViewer({
logs,
isOpen,
onToggle,
onClear,
}: DebugLogViewerProps) {
const scrollRef = useRef<HTMLDivElement>(null)
const [autoScroll, setAutoScroll] = useState(true)
// Auto-scroll to bottom when new logs arrive (if user hasn't scrolled up)
useEffect(() => {
if (autoScroll && scrollRef.current && isOpen) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight
}
}, [logs, autoScroll, isOpen])
// Detect if user scrolled up
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const el = e.currentTarget
const isAtBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 50
setAutoScroll(isAtBottom)
}
// Parse log level from line content
const getLogLevel = (line: string): LogLevel => {
const lowerLine = line.toLowerCase()
if (lowerLine.includes('error') || lowerLine.includes('exception') || lowerLine.includes('traceback')) {
return 'error'
}
if (lowerLine.includes('warn') || lowerLine.includes('warning')) {
return 'warn'
}
if (lowerLine.includes('debug')) {
return 'debug'
}
return 'info'
}
// Get color class for log level
const getLogColor = (level: LogLevel): string => {
switch (level) {
case 'error':
return 'text-red-400'
case 'warn':
return 'text-yellow-400'
case 'debug':
return 'text-gray-400'
case 'info':
default:
return 'text-green-400'
}
}
// Format timestamp to HH:MM:SS
const formatTimestamp = (timestamp: string): string => {
try {
const date = new Date(timestamp)
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
} catch {
return ''
}
}
return (
<div
className={`fixed bottom-0 left-0 right-0 z-40 transition-all duration-200 ${
isOpen ? 'h-72' : 'h-10'
}`}
>
{/* Header bar */}
<div
className="flex items-center justify-between h-10 px-4 bg-[#1a1a1a] border-t-3 border-black cursor-pointer"
onClick={onToggle}
>
<div className="flex items-center gap-2">
<Terminal size={16} className="text-green-400" />
<span className="font-mono text-sm text-white font-bold">
Debug
</span>
{logs.length > 0 && (
<span className="px-2 py-0.5 text-xs font-mono bg-[#333] text-gray-300 rounded">
{logs.length}
</span>
)}
{!autoScroll && isOpen && (
<span className="px-2 py-0.5 text-xs font-mono bg-yellow-600 text-white rounded">
Paused
</span>
)}
</div>
<div className="flex items-center gap-2">
{isOpen && (
<button
onClick={(e) => {
e.stopPropagation()
onClear()
}}
className="p-1.5 hover:bg-[#333] rounded transition-colors"
title="Clear logs"
>
<Trash2 size={14} className="text-gray-400" />
</button>
)}
<div className="p-1">
{isOpen ? (
<ChevronDown size={16} className="text-gray-400" />
) : (
<ChevronUp size={16} className="text-gray-400" />
)}
</div>
</div>
</div>
{/* Log content area */}
{isOpen && (
<div
ref={scrollRef}
onScroll={handleScroll}
className="h-[calc(100%-2.5rem)] overflow-y-auto bg-[#1a1a1a] p-2 font-mono text-sm"
>
{logs.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-500">
No logs yet. Start the agent to see output.
</div>
) : (
<div className="space-y-0.5">
{logs.map((log, index) => {
const level = getLogLevel(log.line)
const colorClass = getLogColor(level)
const timestamp = formatTimestamp(log.timestamp)
return (
<div
key={`${log.timestamp}-${index}`}
className="flex gap-2 hover:bg-[#2a2a2a] px-1 py-0.5 rounded"
>
<span className="text-gray-500 select-none shrink-0">
{timestamp}
</span>
<span className={`${colorClass} whitespace-pre-wrap break-all`}>
{log.line}
</span>
</div>
)
})}
</div>
)}
</div>
)}
</div>
)
}

View File

@@ -12,6 +12,9 @@ import { useState } from 'react'
import { X, Bot, FileEdit, ArrowRight, ArrowLeft, Loader2, CheckCircle2 } from 'lucide-react'
import { useCreateProject } from '../hooks/useProjects'
import { SpecCreationChat } from './SpecCreationChat'
import { startAgent } from '../lib/api'
type InitializerStatus = 'idle' | 'starting' | 'error'
type Step = 'name' | 'method' | 'chat' | 'complete'
type SpecMethod = 'claude' | 'manual'
@@ -31,6 +34,8 @@ export function NewProjectModal({
const [projectName, setProjectName] = useState('')
const [_specMethod, setSpecMethod] = useState<SpecMethod | null>(null)
const [error, setError] = useState<string | null>(null)
const [initializerStatus, setInitializerStatus] = useState<InitializerStatus>('idle')
const [initializerError, setInitializerError] = useState<string | null>(null)
// Suppress unused variable warning - specMethod may be used in future
void _specMethod
@@ -89,12 +94,27 @@ export function NewProjectModal({
}
}
const handleSpecComplete = () => {
setStep('complete')
setTimeout(() => {
onProjectCreated(projectName.trim())
handleClose()
}, 1500)
const handleSpecComplete = async () => {
// Auto-start the initializer agent
setInitializerStatus('starting')
try {
await startAgent(projectName.trim())
// Success - navigate to project
setStep('complete')
setTimeout(() => {
onProjectCreated(projectName.trim())
handleClose()
}, 1500)
} catch (err) {
setInitializerStatus('error')
setInitializerError(err instanceof Error ? err.message : 'Failed to start agent')
}
}
const handleRetryInitializer = () => {
setInitializerError(null)
setInitializerStatus('idle')
handleSpecComplete()
}
const handleChatCancel = () => {
@@ -108,6 +128,8 @@ export function NewProjectModal({
setProjectName('')
setSpecMethod(null)
setError(null)
setInitializerStatus('idle')
setInitializerError(null)
onClose()
}
@@ -126,6 +148,9 @@ export function NewProjectModal({
projectName={projectName.trim()}
onComplete={handleSpecComplete}
onCancel={handleChatCancel}
initializerStatus={initializerStatus}
initializerError={initializerError}
onRetryInitializer={handleRetryInitializer}
/>
</div>
)

View File

@@ -6,22 +6,30 @@
*/
import { useEffect, useRef, useState } from 'react'
import { Send, X, CheckCircle2, AlertCircle, Wifi, WifiOff, RotateCcw } from 'lucide-react'
import { Send, X, CheckCircle2, AlertCircle, Wifi, WifiOff, RotateCcw, Loader2, ArrowRight } from 'lucide-react'
import { useSpecChat } from '../hooks/useSpecChat'
import { ChatMessage } from './ChatMessage'
import { QuestionOptions } from './QuestionOptions'
import { TypingIndicator } from './TypingIndicator'
type InitializerStatus = 'idle' | 'starting' | 'error'
interface SpecCreationChatProps {
projectName: string
onComplete: (specPath: string) => void
onCancel: () => void
initializerStatus?: InitializerStatus
initializerError?: string | null
onRetryInitializer?: () => void
}
export function SpecCreationChat({
projectName,
onComplete,
onCancel,
initializerStatus = 'idle',
initializerError = null,
onRetryInitializer,
}: SpecCreationChatProps) {
const [input, setInput] = useState('')
const [error, setError] = useState<string | null>(null)
@@ -241,18 +249,50 @@ export function SpecCreationChat({
{/* Completion footer */}
{isComplete && (
<div className="p-4 border-t-3 border-[var(--color-neo-border)] bg-[var(--color-neo-done)]">
<div className={`p-4 border-t-3 border-[var(--color-neo-border)] ${
initializerStatus === 'error' ? 'bg-[var(--color-neo-danger)]' : 'bg-[var(--color-neo-done)]'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<CheckCircle2 size={20} />
<span className="font-bold">Specification created successfully!</span>
{initializerStatus === 'starting' ? (
<>
<Loader2 size={20} className="animate-spin" />
<span className="font-bold">Starting agent...</span>
</>
) : initializerStatus === 'error' ? (
<>
<AlertCircle size={20} className="text-white" />
<span className="font-bold text-white">
{initializerError || 'Failed to start agent'}
</span>
</>
) : (
<>
<CheckCircle2 size={20} />
<span className="font-bold">Specification created successfully!</span>
</>
)}
</div>
<div className="flex items-center gap-2">
{initializerStatus === 'error' && onRetryInitializer && (
<button
onClick={onRetryInitializer}
className="neo-btn bg-white"
>
<RotateCcw size={14} />
Retry
</button>
)}
{initializerStatus === 'idle' && (
<button
onClick={() => onComplete('')}
className="neo-btn neo-btn-primary"
>
Continue to Project
<ArrowRight size={16} />
</button>
)}
</div>
<button
onClick={() => onComplete('')}
className="neo-btn bg-white"
>
Continue to Project
</button>
</div>
</div>
)}