109 lines
3 KiB
TypeScript
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;
|
|
}
|