mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
|
|
import type { ThreadMessageLike } from "@assistant-ui/react";
|
||
|
|
import { z } from "zod";
|
||
|
|
import type { MessageRecord } from "./thread-persistence";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Zod schema for persisted attachment info
|
||
|
|
*/
|
||
|
|
const PersistedAttachmentSchema = z.object({
|
||
|
|
id: z.string(),
|
||
|
|
name: z.string(),
|
||
|
|
type: z.string(),
|
||
|
|
contentType: z.string().optional(),
|
||
|
|
imageDataUrl: z.string().optional(),
|
||
|
|
extractedContent: z.string().optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
const AttachmentsPartSchema = z.object({
|
||
|
|
type: z.literal("attachments"),
|
||
|
|
items: z.array(PersistedAttachmentSchema),
|
||
|
|
});
|
||
|
|
|
||
|
|
type PersistedAttachment = z.infer<typeof PersistedAttachmentSchema>;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Extract persisted attachments from message content (type-safe with Zod)
|
||
|
|
*/
|
||
|
|
function extractPersistedAttachments(content: unknown): PersistedAttachment[] {
|
||
|
|
if (!Array.isArray(content)) return [];
|
||
|
|
|
||
|
|
for (const part of content) {
|
||
|
|
const result = AttachmentsPartSchema.safeParse(part);
|
||
|
|
if (result.success) {
|
||
|
|
return result.data.items;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert backend message to assistant-ui ThreadMessageLike format
|
||
|
|
* Filters out 'thinking-steps' part as it's handled separately via messageThinkingSteps
|
||
|
|
* Restores attachments for user messages from persisted data
|
||
|
|
*/
|
||
|
|
export function convertToThreadMessage(msg: MessageRecord): ThreadMessageLike {
|
||
|
|
let content: ThreadMessageLike["content"];
|
||
|
|
|
||
|
|
if (typeof msg.content === "string") {
|
||
|
|
content = [{ type: "text", text: msg.content }];
|
||
|
|
} else if (Array.isArray(msg.content)) {
|
||
|
|
// Filter out custom metadata parts - they're handled separately
|
||
|
|
const filteredContent = msg.content.filter((part: unknown) => {
|
||
|
|
if (typeof part !== "object" || part === null || !("type" in part)) return true;
|
||
|
|
const partType = (part as { type: string }).type;
|
||
|
|
// Filter out thinking-steps, mentioned-documents, and attachments
|
||
|
|
return (
|
||
|
|
partType !== "thinking-steps" &&
|
||
|
|
partType !== "mentioned-documents" &&
|
||
|
|
partType !== "attachments"
|
||
|
|
);
|
||
|
|
});
|
||
|
|
content =
|
||
|
|
filteredContent.length > 0
|
||
|
|
? (filteredContent as ThreadMessageLike["content"])
|
||
|
|
: [{ type: "text", text: "" }];
|
||
|
|
} else {
|
||
|
|
content = [{ type: "text", text: String(msg.content) }];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Restore attachments for user messages
|
||
|
|
let attachments: ThreadMessageLike["attachments"];
|
||
|
|
if (msg.role === "user") {
|
||
|
|
const persistedAttachments = extractPersistedAttachments(msg.content);
|
||
|
|
if (persistedAttachments.length > 0) {
|
||
|
|
attachments = persistedAttachments.map((att) => ({
|
||
|
|
id: att.id,
|
||
|
|
name: att.name,
|
||
|
|
type: att.type as "document" | "image" | "file",
|
||
|
|
contentType: att.contentType || "application/octet-stream",
|
||
|
|
status: { type: "complete" as const },
|
||
|
|
content: [],
|
||
|
|
// Custom fields for our ChatAttachment interface
|
||
|
|
imageDataUrl: att.imageDataUrl,
|
||
|
|
extractedContent: att.extractedContent,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build metadata.custom for author display in shared chats
|
||
|
|
const metadata = msg.author_id
|
||
|
|
? {
|
||
|
|
custom: {
|
||
|
|
author: {
|
||
|
|
displayName: msg.author_display_name ?? null,
|
||
|
|
avatarUrl: msg.author_avatar_url ?? null,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
: undefined;
|
||
|
|
|
||
|
|
return {
|
||
|
|
id: `msg-${msg.id}`,
|
||
|
|
role: msg.role,
|
||
|
|
content,
|
||
|
|
createdAt: new Date(msg.created_at),
|
||
|
|
attachments,
|
||
|
|
metadata,
|
||
|
|
};
|
||
|
|
}
|