openclaw-matrix-multiaccounts/ui/src/ui/controllers/chat.ts
2026-01-01 21:09:28 +01:00

109 lines
3 KiB
TypeScript

import type { GatewayBrowserClient } from "../gateway";
export type ChatState = {
client: GatewayBrowserClient | null;
connected: boolean;
sessionKey: string;
chatLoading: boolean;
chatMessages: unknown[];
chatThinkingLevel: string | null;
chatSending: boolean;
chatMessage: string;
chatRunId: string | null;
chatStream: string | null;
lastError: string | null;
};
type ChatEventPayload = {
runId: string;
sessionKey: string;
state: "delta" | "final" | "aborted" | "error";
message?: unknown;
errorMessage?: string;
};
export async function loadChatHistory(state: ChatState) {
if (!state.client || !state.connected) return;
state.chatLoading = true;
state.lastError = null;
try {
const res = (await state.client.request("chat.history", {
sessionKey: state.sessionKey,
limit: 200,
})) as { messages?: unknown[]; thinkingLevel?: string | null };
state.chatMessages = Array.isArray(res.messages) ? res.messages : [];
state.chatThinkingLevel = res.thinkingLevel ?? null;
} catch (err) {
state.lastError = String(err);
} finally {
state.chatLoading = false;
}
}
export async function sendChat(state: ChatState) {
if (!state.client || !state.connected) return;
const msg = state.chatMessage.trim();
if (!msg) return;
state.chatSending = true;
state.chatMessage = "";
state.lastError = null;
const runId = crypto.randomUUID();
state.chatRunId = runId;
state.chatStream = "";
try {
await state.client.request("chat.send", {
sessionKey: state.sessionKey,
message: msg,
deliver: false,
idempotencyKey: runId,
});
} catch (err) {
state.chatRunId = null;
state.chatStream = null;
state.chatMessage = msg;
state.lastError = String(err);
} finally {
state.chatSending = false;
}
}
export function handleChatEvent(
state: ChatState,
payload?: ChatEventPayload,
) {
if (!payload) return null;
if (payload.sessionKey !== state.sessionKey) return null;
if (payload.runId && state.chatRunId && payload.runId !== state.chatRunId)
return null;
if (payload.state === "delta") {
state.chatStream = extractText(payload.message) ?? state.chatStream;
} else if (payload.state === "final") {
state.chatStream = null;
state.chatRunId = null;
} else if (payload.state === "error") {
state.chatStream = null;
state.chatRunId = null;
state.lastError = payload.errorMessage ?? "chat error";
}
return payload.state;
}
function extractText(message: unknown): string | null {
const m = message as Record<string, unknown>;
const content = m.content;
if (typeof content === "string") return content;
if (Array.isArray(content)) {
const parts = content
.map((p) => {
const item = p as Record<string, unknown>;
if (item.type === "text" && typeof item.text === "string") return item.text;
return null;
})
.filter((v): v is string => typeof v === "string");
if (parts.length > 0) return parts.join("\n");
}
if (typeof m.text === "string") return m.text;
return null;
}