purcar-chat-api / src /lib /cloudWorkspace.ts
ihatebaselines's picture
Deploy Purcar gateway and local model engine
f71d0a0 verified
Raw History Blame Contribute Delete
6.16 kB
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<string, Message[]>();
(messages || []).forEach((message: any) => {
const reactions: Record<string, string[]> = {};
(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<string, unknown>) {
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<ConversationInvite[]> {
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<string> {
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;
}