File size: 6,164 Bytes
7e92e00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f71d0a0
7e92e00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f71d0a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e92e00
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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;
}