import { supabase } from "@/lib/supabase"; import type { Conversation, Message, ModelId, Project } from "@/types"; const time = (value: string | number | null | undefined) => value ? new Date(value).getTime() : Date.now(); export async function loadCloudWorkspace(): Promise<{ projects: Project[]; conversations: Conversation[] }> { const [{ data: projects, error: projectError }, { data: conversations, error: conversationError }] = await Promise.all([ supabase.from("projects").select("*").order("is_pinned", { ascending: false }).order("updated_at", { ascending: false }), supabase.from("conversations").select("*").order("is_pinned", { ascending: false }).order("updated_at", { ascending: false }), ]); if (projectError || conversationError) throw projectError || conversationError; const ids = (conversations || []).map((conversation) => conversation.id); const { data: messages, error: messageError } = ids.length ? await supabase.from("messages").select("*,message_reactions(emoji,user_id)").in("conversation_id", ids).order("created_at") : { data: [], error: null }; if (messageError) throw messageError; const mappedMessages = new Map(); (messages || []).forEach((message: any) => { const reactions: Record = {}; (message.message_reactions || []).forEach((reaction: any) => { reactions[reaction.emoji] = [...(reactions[reaction.emoji] || []), reaction.user_id]; }); const item: Message = { id: message.id, role: message.role, content: message.content, thinking: message.thinking || undefined, thinkingTimeSeconds: message.thinking_seconds || undefined, tokens: message.token_count || undefined, ragSources: message.rag_sources || [], authorId: message.author_id || undefined, reactions, createdAt: time(message.created_at), }; mappedMessages.set(message.conversation_id, [...(mappedMessages.get(message.conversation_id) || []), item]); }); return { projects: (projects || []).map((project: any) => ({ id: project.id, name: project.name, description: project.description, isPinned: project.is_pinned, createdAt: time(project.created_at), updatedAt: time(project.updated_at), })), conversations: (conversations || []).map((conversation: any) => ({ id: conversation.id, title: conversation.title, modelId: conversation.model_id as ModelId, projectId: conversation.project_id, isPinned: conversation.is_pinned, isGroup: conversation.is_group, summary: conversation.compact_summary, messages: mappedMessages.get(conversation.id) || [], createdAt: time(conversation.created_at), updatedAt: time(conversation.updated_at), })), }; } export async function createCloudConversation(modelId: ModelId, projectId: string | null = null, isGroup = false) { const { data, error } = await supabase.rpc("create_conversation_with_owner", { title_input: "New Session", model_input: "purcar", project_input: projectId, group_input: isGroup, }); if (error) throw error; return data as string; } export async function saveCloudMessage(conversationId: string, message: Message) { const { data: { user } } = await supabase.auth.getUser(); if (!user) throw new Error("Sign in required"); const { error } = await supabase.from("messages").upsert({ id: message.id, conversation_id: conversationId, author_id: user.id, role: message.role, content: message.content, thinking: message.thinking || null, thinking_seconds: message.thinkingTimeSeconds || null, token_count: message.tokens || null, rag_sources: message.ragSources || [], created_at: new Date(message.createdAt).toISOString(), }); if (error) throw error; } export async function updateCloudConversation(id: string, patch: Record) { const { error } = await supabase.from("conversations").update({ ...patch, updated_at: new Date().toISOString() }).eq("id", id); if (error) throw error; } export async function deleteCloudConversation(id: string) { const { error } = await supabase.from("conversations").delete().eq("id", id); if (error) throw error; } export async function createCloudProject(name: string) { const { data: { user } } = await supabase.auth.getUser(); if (!user) throw new Error("Sign in required"); const { data, error } = await supabase.from("projects").insert({ owner_id: user.id, name: name.trim() }).select("*").single(); if (error) throw error; return { id: data.id, name: data.name, description: data.description, isPinned: data.is_pinned, createdAt: time(data.created_at), updatedAt: time(data.updated_at) } as Project; } export async function inviteCloudMember(conversationId: string, username: string) { const { error } = await supabase.rpc("invite_conversation_member", { conversation_input: conversationId, username_input: username.replace(/^@/, "").trim() }); if (error) throw error; } export type ConversationInvite = { id: string; conversationId: string; conversationTitle: string; inviterId: string; status: string; createdAt: string; }; export async function loadPendingConversationInvites(): Promise { const { data, error } = await supabase.rpc("get_pending_conversation_invites"); if (error) throw error; return (data || []).map((item: any) => ({ id: item.id, conversationId: item.conversation_id, conversationTitle: item.conversation_title, inviterId: item.inviter_id, status: item.status, createdAt: item.created_at })); } export async function respondToConversationInvite(id: string, accept: boolean): Promise { const { data, error } = await supabase.rpc("respond_to_conversation_invite", { invitation_input: id, accept_input: accept }); if (error) throw error; return data as string; } export async function toggleCloudReaction(messageId: string, emoji: string, userId: string, active: boolean) { const query = supabase.from("message_reactions"); const { error } = active ? await query.delete().eq("message_id", messageId).eq("user_id", userId).eq("emoji", emoji) : await query.insert({ message_id: messageId, user_id: userId, emoji }); if (error) throw error; }