import { useEffect, useRef, type KeyboardEvent } from 'react'; import type { AgentMessage, AssistantMessage, ToolCall, ToolResultMessage } from '../shared/types'; import { PiLogo } from './TopBar'; function ToolCallView({ call }: { call: ToolCall }) { const entries = Object.entries(call.arguments); return (
{call.name}
{call.name === 'bash' && typeof call.arguments.command === 'string' ? (
$ {call.arguments.command}
) : ( entries.map(([k, v]) => (
{k}
{String(v)}
)) )}
); } function ResultView({ m }: { m: ToolResultMessage }) { const text = m.content.map((c) => c.text).join('\n'); return (
{m.toolName} result{m.details.exitCode !== undefined ? ` · exit ${m.details.exitCode}` : ''} {m.isError ? ' · error' : ''}
{text}
); } function AssistantView({ m, live }: { m: AssistantMessage; live?: boolean }) { return (
{m.content.map((c, i) => c.type === 'thinking' ? (
Thinking
{c.thinking}
) : c.type === 'text' ? (

{c.text}

) : ( ), )} {live && m.content.length === 0 && } {m.stopReason === 'error' &&

{m.errorMessage}

} {m.stopReason === 'aborted' &&

Stopped.

} {m.stopReason === 'length' &&

Stopped at the output limit.

}
); } interface Props { messages: AgentMessage[]; streaming: AssistantMessage | null; busy: string | null; draft: string; contextTokens: number; onDraft: (v: string) => void; onSend: () => void; onStop: () => void; onNewChat: () => void; } export function ChatPanel({ messages, streaming, busy, draft, contextTokens, onDraft, onSend, onStop, onNewChat }: Props) { const logRef = useRef(null); const working = busy === 'prompt'; const last = messages[messages.length - 1]; useEffect(() => { const el = logRef.current; if (el) el.scrollTop = el.scrollHeight; }, [messages, streaming]); const onKey = (e: KeyboardEvent) => { // Không gửi khi đang gõ dấu bằng bộ gõ (tiếng Việt, tiếng Trung…). if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); onSend(); } }; return (
{messages.length === 0 && !working && (

Ask Pi to work in your workspace

Pi can read, write and edit files and run shell commands. Everything stays in this browser.

)} {messages.map((m, i) => m.role === 'user' ? (
{m.content}
) : m.role === 'assistant' ? ( ) : ( ), )} {working && streaming && } {working && !streaming && last && last.role !== 'assistant' && (
)}