Files
picoclaw/web/frontend/src/features/chat/protocol.ts
T

98 lines
2.3 KiB
TypeScript

import { toast } from "sonner"
import { normalizeUnixTimestamp } from "@/features/chat/state"
import { updateChatStore } from "@/store/chat"
export interface PicoMessage {
type: string
id?: string
session_id?: string
timestamp?: number | string
payload?: Record<string, unknown>
}
export function handlePicoMessage(
message: PicoMessage,
expectedSessionId: string,
) {
if (message.session_id && message.session_id !== expectedSessionId) {
return
}
const payload = message.payload || {}
switch (message.type) {
case "message.create": {
const content = (payload.content as string) || ""
const messageId = (payload.message_id as string) || `pico-${Date.now()}`
const timestamp =
message.timestamp !== undefined &&
Number.isFinite(Number(message.timestamp))
? normalizeUnixTimestamp(Number(message.timestamp))
: Date.now()
updateChatStore((prev) => ({
messages: [
...prev.messages,
{
id: messageId,
role: "assistant",
content,
timestamp,
},
],
isTyping: false,
}))
break
}
case "message.update": {
const content = (payload.content as string) || ""
const messageId = payload.message_id as string
if (!messageId) {
break
}
updateChatStore((prev) => ({
messages: prev.messages.map((msg) =>
msg.id === messageId ? { ...msg, content } : msg,
),
}))
break
}
case "typing.start":
updateChatStore({ isTyping: true })
break
case "typing.stop":
updateChatStore({ isTyping: false })
break
case "error": {
const requestId =
typeof payload.request_id === "string" ? payload.request_id : ""
const errorMessage =
typeof payload.message === "string" ? payload.message : ""
console.error("Pico error:", payload)
if (errorMessage) {
toast.error(errorMessage)
}
updateChatStore((prev) => ({
messages: requestId
? prev.messages.filter((msg) => msg.id !== requestId)
: prev.messages,
isTyping: false,
}))
break
}
case "pong":
break
default:
console.log("Unknown pico message type:", message.type)
}
}