import { useEffect, useMemo, useRef } from 'react' import useAppStore from '../store/appStore' const KIND_COLOR = { frontend: '#60a5fa', backend: '#34d399', install: '#f59e0b', } // Heuristic: lines that look like real errors / stack traces. Intentionally // broad — false positives just mean the user gets a "Fix" affordance on a // harmless line, which they can ignore. False negatives are worse. const ERROR_RE = /\b(error|exception|traceback|enoent|cannot find module|syntaxerror|typeerror|referenceerror|fatal)\b|^✗|^\s*at\s+\S+\s*\(/i // Common false positives we'd rather not light up: log lines that explicitly // report success/no-error, and the literal word "error" in non-error context. const NOT_ERROR_RE = /\b(no errors?|0 errors?|error[-_]free)\b/i function isErrorLine(line) { if (!line) return false if (NOT_ERROR_RE.test(line)) return false return ERROR_RE.test(line) } // Bundle a window of context around an error line — a few lines before, the // hit, a few after. The agent has `read_logs` if it needs more, but this // gives it enough to start without an extra tool roundtrip. function buildContext(logs, idx, before = 6, after = 4) { const start = Math.max(0, idx - before) const end = Math.min(logs.length, idx + after + 1) return logs.slice(start, end) .map((e) => `[${e.kind}] ${e.line}`) .join('\n') } function FixPill({ onClick, disabled }) { return ( ) } export default function LogsPanel() { const logs = useAppStore((s) => s.runtime.logs) const mode = useAppStore((s) => s.mode) const setMode = useAppStore((s) => s.setMode) const setComposerDraft = useAppStore((s) => s.setComposerDraft) const activeFile = useAppStore((s) => s.project.activeFile) const showToast = useAppStore((s) => s.showToast) const isStreaming = useAppStore((s) => s.isStreaming) const bottomRef = useRef(null) useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'instant' }) }, [logs.length]) // Memoize the error flags so we don't re-regex on every render of every row. const errorMask = useMemo( () => logs.map((e) => isErrorLine(e.line)), [logs], ) function fixThis(idx) { const ctx = buildContext(logs, idx) const entry = logs[idx] const kindLabel = entry?.kind === 'install' ? 'install step' : entry?.kind === 'backend' ? 'backend dev server' : entry?.kind === 'frontend' ? 'frontend dev server' : 'dev server' const text = `The ${kindLabel} just emitted an error. Here's the log window around it: \`\`\` ${ctx} \`\`\` Find the file(s) involved, read them, and fix the root cause. Use \`read_logs\` if you need broader context.` // Auto-pin the active file if there is one — it's usually what the user // was just editing and likely the source of the regression. const pins = activeFile ? [activeFile] : [] setComposerDraft({ text, pinnedFiles: pins }) // If we're in CODE mode, flip to VIBE so the composer is visible and the // draft actually lands somewhere the user can see. if (mode !== 'vibe') setMode('vibe') showToast('Sent to chat — review and press Enter') } return (
{logs.length === 0 ? (
No logs yet. Click Run to start the dev servers.
) : ( logs.map((entry, i) => { const isError = errorMask[i] return (
[{entry.kind}] {entry.line} {isError && ( fixThis(i)} disabled={isStreaming} /> )}
) }) )}
) }