2026-04-03 06:15:20 +00:00
|
|
|
import { toast } from "sonner"
|
|
|
|
|
|
2026-03-16 08:25:16 +00:00
|
|
|
import { normalizeUnixTimestamp } from "@/features/chat/state"
|
2026-04-21 08:30:02 +00:00
|
|
|
import {
|
|
|
|
|
type AssistantMessageKind,
|
|
|
|
|
type ContextUsage,
|
|
|
|
|
updateChatStore,
|
|
|
|
|
} from "@/store/chat"
|
2026-03-16 08:25:16 +00:00
|
|
|
|
|
|
|
|
export interface PicoMessage {
|
|
|
|
|
type: string
|
|
|
|
|
id?: string
|
|
|
|
|
session_id?: string
|
|
|
|
|
timestamp?: number | string
|
|
|
|
|
payload?: Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 12:23:12 +00:00
|
|
|
function parseAssistantMessageKind(
|
|
|
|
|
payload: Record<string, unknown>,
|
|
|
|
|
): AssistantMessageKind {
|
|
|
|
|
return payload.thought === true ? "thought" : "normal"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasAssistantKindPayload(payload: Record<string, unknown>): boolean {
|
|
|
|
|
return typeof payload.thought === "boolean"
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 08:30:02 +00:00
|
|
|
function parseContextUsage(
|
|
|
|
|
payload: Record<string, unknown>,
|
|
|
|
|
): ContextUsage | undefined {
|
|
|
|
|
const raw = payload.context_usage
|
|
|
|
|
if (!raw || typeof raw !== "object") return undefined
|
|
|
|
|
const obj = raw as Record<string, unknown>
|
|
|
|
|
const used = Number(obj.used_tokens)
|
|
|
|
|
const total = Number(obj.total_tokens)
|
|
|
|
|
if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0)
|
|
|
|
|
return undefined
|
|
|
|
|
return {
|
|
|
|
|
used_tokens: used,
|
|
|
|
|
total_tokens: total,
|
|
|
|
|
compress_at_tokens: Number(obj.compress_at_tokens) || 0,
|
|
|
|
|
used_percent: Number(obj.used_percent) || 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 08:25:16 +00:00
|
|
|
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()}`
|
2026-04-10 12:23:12 +00:00
|
|
|
const kind = parseAssistantMessageKind(payload)
|
2026-04-21 08:30:02 +00:00
|
|
|
const contextUsage = parseContextUsage(payload)
|
2026-03-16 08:25:16 +00:00
|
|
|
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,
|
2026-04-10 12:23:12 +00:00
|
|
|
kind,
|
2026-03-16 08:25:16 +00:00
|
|
|
timestamp,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
isTyping: false,
|
2026-04-21 08:30:02 +00:00
|
|
|
...(contextUsage ? { contextUsage } : {}),
|
2026-03-16 08:25:16 +00:00
|
|
|
}))
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "message.update": {
|
|
|
|
|
const content = (payload.content as string) || ""
|
|
|
|
|
const messageId = payload.message_id as string
|
2026-04-10 12:23:12 +00:00
|
|
|
const hasKind = hasAssistantKindPayload(payload)
|
|
|
|
|
const kind = parseAssistantMessageKind(payload)
|
2026-03-16 08:25:16 +00:00
|
|
|
if (!messageId) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateChatStore((prev) => ({
|
|
|
|
|
messages: prev.messages.map((msg) =>
|
2026-04-10 12:23:12 +00:00
|
|
|
msg.id === messageId
|
|
|
|
|
? {
|
|
|
|
|
...msg,
|
|
|
|
|
content,
|
|
|
|
|
...(hasKind ? { kind } : {}),
|
|
|
|
|
}
|
|
|
|
|
: msg,
|
2026-03-16 08:25:16 +00:00
|
|
|
),
|
|
|
|
|
}))
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "typing.start":
|
|
|
|
|
updateChatStore({ isTyping: true })
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
case "typing.stop":
|
|
|
|
|
updateChatStore({ isTyping: false })
|
|
|
|
|
break
|
|
|
|
|
|
2026-04-03 06:15:20 +00:00
|
|
|
case "error": {
|
|
|
|
|
const requestId =
|
|
|
|
|
typeof payload.request_id === "string" ? payload.request_id : ""
|
|
|
|
|
const errorMessage =
|
|
|
|
|
typeof payload.message === "string" ? payload.message : ""
|
|
|
|
|
|
2026-03-16 08:25:16 +00:00
|
|
|
console.error("Pico error:", payload)
|
2026-04-03 06:15:20 +00:00
|
|
|
if (errorMessage) {
|
|
|
|
|
toast.error(errorMessage)
|
|
|
|
|
}
|
|
|
|
|
updateChatStore((prev) => ({
|
|
|
|
|
messages: requestId
|
|
|
|
|
? prev.messages.filter((msg) => msg.id !== requestId)
|
|
|
|
|
: prev.messages,
|
|
|
|
|
isTyping: false,
|
|
|
|
|
}))
|
2026-03-16 08:25:16 +00:00
|
|
|
break
|
2026-04-03 06:15:20 +00:00
|
|
|
}
|
2026-03-16 08:25:16 +00:00
|
|
|
|
|
|
|
|
case "pong":
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
console.log("Unknown pico message type:", message.type)
|
|
|
|
|
}
|
|
|
|
|
}
|