mirror of https://github.com/openclaw/openclaw.git
151 lines
4.0 KiB
TypeScript
151 lines
4.0 KiB
TypeScript
import type { Component } from "@mariozechner/pi-tui";
|
|
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
import { theme } from "../theme/theme.js";
|
|
import { AssistantMessageComponent } from "./assistant-message.js";
|
|
import { ToolExecutionComponent } from "./tool-execution.js";
|
|
import { UserMessageComponent } from "./user-message.js";
|
|
|
|
export class ChatLog extends Container {
|
|
private readonly maxComponents: number;
|
|
private toolById = new Map<string, ToolExecutionComponent>();
|
|
private streamingRuns = new Map<string, AssistantMessageComponent>();
|
|
private toolsExpanded = false;
|
|
|
|
constructor(maxComponents = 180) {
|
|
super();
|
|
this.maxComponents = Math.max(20, Math.floor(maxComponents));
|
|
}
|
|
|
|
private dropComponentReferences(component: Component) {
|
|
for (const [toolId, tool] of this.toolById.entries()) {
|
|
if (tool === component) {
|
|
this.toolById.delete(toolId);
|
|
}
|
|
}
|
|
for (const [runId, message] of this.streamingRuns.entries()) {
|
|
if (message === component) {
|
|
this.streamingRuns.delete(runId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private pruneOverflow() {
|
|
while (this.children.length > this.maxComponents) {
|
|
const oldest = this.children[0];
|
|
if (!oldest) {
|
|
return;
|
|
}
|
|
this.removeChild(oldest);
|
|
this.dropComponentReferences(oldest);
|
|
}
|
|
}
|
|
|
|
private append(component: Component) {
|
|
this.addChild(component);
|
|
this.pruneOverflow();
|
|
}
|
|
|
|
clearAll() {
|
|
this.clear();
|
|
this.toolById.clear();
|
|
this.streamingRuns.clear();
|
|
}
|
|
|
|
addSystem(text: string) {
|
|
this.append(new Spacer(1));
|
|
this.append(new Text(theme.system(text), 1, 0));
|
|
}
|
|
|
|
addUser(text: string) {
|
|
this.append(new UserMessageComponent(text));
|
|
}
|
|
|
|
private resolveRunId(runId?: string) {
|
|
return runId ?? "default";
|
|
}
|
|
|
|
startAssistant(text: string, runId?: string) {
|
|
const component = new AssistantMessageComponent(text);
|
|
this.streamingRuns.set(this.resolveRunId(runId), component);
|
|
this.append(component);
|
|
return component;
|
|
}
|
|
|
|
updateAssistant(text: string, runId?: string) {
|
|
const effectiveRunId = this.resolveRunId(runId);
|
|
const existing = this.streamingRuns.get(effectiveRunId);
|
|
if (!existing) {
|
|
this.startAssistant(text, runId);
|
|
return;
|
|
}
|
|
existing.setText(text);
|
|
}
|
|
|
|
finalizeAssistant(text: string, runId?: string) {
|
|
const effectiveRunId = this.resolveRunId(runId);
|
|
const existing = this.streamingRuns.get(effectiveRunId);
|
|
if (existing) {
|
|
existing.setText(text);
|
|
this.streamingRuns.delete(effectiveRunId);
|
|
return;
|
|
}
|
|
this.append(new AssistantMessageComponent(text));
|
|
}
|
|
|
|
dropAssistant(runId?: string) {
|
|
const effectiveRunId = this.resolveRunId(runId);
|
|
const existing = this.streamingRuns.get(effectiveRunId);
|
|
if (!existing) {
|
|
return;
|
|
}
|
|
this.removeChild(existing);
|
|
this.streamingRuns.delete(effectiveRunId);
|
|
}
|
|
|
|
startTool(toolCallId: string, toolName: string, args: unknown) {
|
|
const existing = this.toolById.get(toolCallId);
|
|
if (existing) {
|
|
existing.setArgs(args);
|
|
return existing;
|
|
}
|
|
const component = new ToolExecutionComponent(toolName, args);
|
|
component.setExpanded(this.toolsExpanded);
|
|
this.toolById.set(toolCallId, component);
|
|
this.append(component);
|
|
return component;
|
|
}
|
|
|
|
updateToolArgs(toolCallId: string, args: unknown) {
|
|
const existing = this.toolById.get(toolCallId);
|
|
if (!existing) {
|
|
return;
|
|
}
|
|
existing.setArgs(args);
|
|
}
|
|
|
|
updateToolResult(
|
|
toolCallId: string,
|
|
result: unknown,
|
|
opts?: { isError?: boolean; partial?: boolean },
|
|
) {
|
|
const existing = this.toolById.get(toolCallId);
|
|
if (!existing) {
|
|
return;
|
|
}
|
|
if (opts?.partial) {
|
|
existing.setPartialResult(result as Record<string, unknown>);
|
|
return;
|
|
}
|
|
existing.setResult(result as Record<string, unknown>, {
|
|
isError: opts?.isError,
|
|
});
|
|
}
|
|
|
|
setToolsExpanded(expanded: boolean) {
|
|
this.toolsExpanded = expanded;
|
|
for (const tool of this.toolById.values()) {
|
|
tool.setExpanded(expanded);
|
|
}
|
|
}
|
|
}
|