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]) => (
))
)}
);
}
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' && (
)}
);
}