import { React, h, Box, Text, useInput, useApp, ChatSession } from './plugins.js'; import type { LanguageModelV3, ToolSet, IChatUsage } from './plugins.js'; import { MessageList } from './components.messagelist.js'; import { InputArea } from './components.inputarea.js'; import { StatusBar } from './components.statusbar.js'; import type { IChatMessage } from './components.message.js'; export interface IChatAppProps { model: LanguageModelV3; system?: string; tools?: ToolSet; maxSteps?: number; modelName?: string; } const emptyUsage: IChatUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, turns: 0 }; export function ChatApp({ model, system, tools, maxSteps, modelName }: IChatAppProps): React.ReactElement { const { exit } = useApp(); const [messages, setMessages] = React.useState([]); const [streamingText, setStreamingText] = React.useState(''); const [busy, setBusy] = React.useState(false); const [usage, setUsage] = React.useState({ ...emptyUsage }); const sessionRef = React.useRef(null); if (!sessionRef.current) { sessionRef.current = new ChatSession( { model, system, tools, maxSteps: maxSteps ?? 20 }, { onToken: (delta) => { setStreamingText((prev) => prev + delta); }, onToolCall: (name, input) => { setMessages((prev) => [ ...prev, { role: 'tool' as const, content: '', toolName: name, toolInput: typeof input === 'string' ? input : JSON.stringify(input), }, ]); }, }, ); } useInput((input, key) => { if (input === 'c' && key.ctrl) { exit(); } if (input === 'l' && key.ctrl) { setMessages([]); setStreamingText(''); sessionRef.current?.clear(); setUsage({ ...emptyUsage }); } }); const handleSubmit = async (text: string) => { if (busy) return; if (text === '/clear') { setMessages([]); setStreamingText(''); sessionRef.current?.clear(); setUsage({ ...emptyUsage }); return; } if (text === '/quit' || text === '/exit') { exit(); return; } setMessages((prev) => [...prev, { role: 'user', content: text }]); setBusy(true); setStreamingText(''); try { const result = await sessionRef.current!.send(text); setStreamingText(''); setMessages((prev) => [...prev, { role: 'assistant', content: result.text }]); setUsage(sessionRef.current!.getUsage()); } catch (error) { const errMsg = error instanceof Error ? error.message : String(error); setMessages((prev) => [...prev, { role: 'assistant', content: `Error: ${errMsg}` }]); } finally { setBusy(false); } }; return h(Box, { flexDirection: 'column' as const }, h(Box, { paddingX: 1 }, h(Text, { bold: true, color: 'cyan' }, 'smartchat'), modelName ? h(Text, { dimColor: true }, ` \u2014 ${modelName}`) : null, ), h(MessageList, { messages, streamingText, busy }), h(InputArea, { disabled: busy, onSubmit: handleSubmit }), h(StatusBar, { modelName, usage, busy }), ); }