|
|
import { createClient } from "@/lib/supabase/client"; |
|
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_BACKEND_URL; |
|
|
|
|
|
export type Thread = { |
|
|
thread_id: string; |
|
|
account_id: string | null; |
|
|
project_id?: string | null; |
|
|
is_public?: boolean; |
|
|
created_at: string; |
|
|
updated_at: string; |
|
|
metadata?: { |
|
|
workflow_id?: string; |
|
|
workflow_name?: string; |
|
|
workflow_run_name?: string; |
|
|
is_workflow_execution?: boolean; |
|
|
agent_id?: string; |
|
|
is_agent_builder?: boolean; |
|
|
[key: string]: any; |
|
|
}; |
|
|
[key: string]: any; |
|
|
}; |
|
|
|
|
|
export type Project = { |
|
|
id: string; |
|
|
name: string; |
|
|
description: string; |
|
|
account_id: string; |
|
|
created_at: string; |
|
|
updated_at?: string; |
|
|
sandbox: { |
|
|
vnc_preview?: string; |
|
|
sandbox_url?: string; |
|
|
id?: string; |
|
|
pass?: string; |
|
|
}; |
|
|
is_public?: boolean; |
|
|
[key: string]: any; |
|
|
}; |
|
|
|
|
|
|
|
|
export const getThread = async (threadId: string): Promise<Thread> => { |
|
|
const supabase = createClient(); |
|
|
const { data, error } = await supabase |
|
|
.from('threads') |
|
|
.select('*') |
|
|
.eq('thread_id', threadId) |
|
|
.single(); |
|
|
|
|
|
if (error) throw error; |
|
|
|
|
|
return data; |
|
|
}; |
|
|
|
|
|
export const updateThread = async ( |
|
|
threadId: string, |
|
|
data: Partial<Thread>, |
|
|
): Promise<Thread> => { |
|
|
const supabase = createClient(); |
|
|
|
|
|
const updateData = { ...data }; |
|
|
|
|
|
|
|
|
const { data: updatedThread, error } = await supabase |
|
|
.from('threads') |
|
|
.update(updateData) |
|
|
.eq('thread_id', threadId) |
|
|
.select() |
|
|
.single(); |
|
|
|
|
|
if (error) { |
|
|
console.error('Error updating thread:', error); |
|
|
throw new Error(`Error updating thread: ${error.message}`); |
|
|
} |
|
|
|
|
|
return updatedThread; |
|
|
}; |
|
|
|
|
|
export const toggleThreadPublicStatus = async ( |
|
|
threadId: string, |
|
|
isPublic: boolean, |
|
|
): Promise<Thread> => { |
|
|
return updateThread(threadId, { is_public: isPublic }); |
|
|
}; |
|
|
|
|
|
const deleteSandbox = async (sandboxId: string): Promise<void> => { |
|
|
try { |
|
|
const supabase = createClient(); |
|
|
const { |
|
|
data: { session }, |
|
|
} = await supabase.auth.getSession(); |
|
|
|
|
|
const headers: Record<string, string> = { |
|
|
'Content-Type': 'application/json', |
|
|
}; |
|
|
|
|
|
if (session?.access_token) { |
|
|
headers['Authorization'] = `Bearer ${session.access_token}`; |
|
|
} |
|
|
|
|
|
const response = await fetch(`${API_URL}/sandboxes/${sandboxId}`, { |
|
|
method: 'DELETE', |
|
|
headers, |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
console.warn('Failed to delete sandbox, continuing with thread deletion'); |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn('Error deleting sandbox, continuing with thread deletion:', error); |
|
|
} |
|
|
}; |
|
|
|
|
|
export const deleteThread = async (threadId: string, sandboxId?: string): Promise<void> => { |
|
|
try { |
|
|
const supabase = createClient(); |
|
|
|
|
|
|
|
|
if (sandboxId) { |
|
|
await deleteSandbox(sandboxId); |
|
|
} else { |
|
|
|
|
|
const { data: thread, error: threadError } = await supabase |
|
|
.from('threads') |
|
|
.select('project_id') |
|
|
.eq('thread_id', threadId) |
|
|
.single(); |
|
|
|
|
|
if (threadError) { |
|
|
console.error('Error fetching thread:', threadError); |
|
|
throw new Error(`Error fetching thread: ${threadError.message}`); |
|
|
} |
|
|
|
|
|
|
|
|
if (thread?.project_id) { |
|
|
const { data: project } = await supabase |
|
|
.from('projects') |
|
|
.select('sandbox') |
|
|
.eq('project_id', thread.project_id) |
|
|
.single(); |
|
|
|
|
|
if (project?.sandbox?.id) { |
|
|
await deleteSandbox(project.sandbox.id); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
console.log(`Deleting all agent runs for thread ${threadId}`); |
|
|
const { error: agentRunsError } = await supabase |
|
|
.from('agent_runs') |
|
|
.delete() |
|
|
.eq('thread_id', threadId); |
|
|
|
|
|
if (agentRunsError) { |
|
|
console.error('Error deleting agent runs:', agentRunsError); |
|
|
throw new Error(`Error deleting agent runs: ${agentRunsError.message}`); |
|
|
} |
|
|
|
|
|
console.log(`Deleting all messages for thread ${threadId}`); |
|
|
const { error: messagesError } = await supabase |
|
|
.from('messages') |
|
|
.delete() |
|
|
.eq('thread_id', threadId); |
|
|
|
|
|
if (messagesError) { |
|
|
console.error('Error deleting messages:', messagesError); |
|
|
throw new Error(`Error deleting messages: ${messagesError.message}`); |
|
|
} |
|
|
|
|
|
console.log(`Deleting thread ${threadId}`); |
|
|
const { error: threadError2 } = await supabase |
|
|
.from('threads') |
|
|
.delete() |
|
|
.eq('thread_id', threadId); |
|
|
|
|
|
if (threadError2) { |
|
|
console.error('Error deleting thread:', threadError2); |
|
|
throw new Error(`Error deleting thread: ${threadError2.message}`); |
|
|
} |
|
|
|
|
|
console.log( |
|
|
`Thread ${threadId} successfully deleted with all related items`, |
|
|
); |
|
|
} catch (error) { |
|
|
console.error('Error deleting thread and related items:', error); |
|
|
throw error; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const getPublicProjects = async (): Promise<Project[]> => { |
|
|
try { |
|
|
const supabase = createClient(); |
|
|
|
|
|
|
|
|
const { data: publicThreads, error: threadsError } = await supabase |
|
|
.from('threads') |
|
|
.select('project_id') |
|
|
.eq('is_public', true); |
|
|
|
|
|
if (threadsError) { |
|
|
console.error('Error fetching public threads:', threadsError); |
|
|
return []; |
|
|
} |
|
|
|
|
|
|
|
|
if (!publicThreads?.length) { |
|
|
return []; |
|
|
} |
|
|
|
|
|
|
|
|
const publicProjectIds = [ |
|
|
...new Set(publicThreads.map((thread) => thread.project_id)), |
|
|
].filter(Boolean); |
|
|
|
|
|
|
|
|
if (!publicProjectIds.length) { |
|
|
return []; |
|
|
} |
|
|
|
|
|
|
|
|
const { data: projects, error: projectsError } = await supabase |
|
|
.from('projects') |
|
|
.select('*') |
|
|
.in('project_id', publicProjectIds); |
|
|
|
|
|
if (projectsError) { |
|
|
console.error('Error fetching public projects:', projectsError); |
|
|
return []; |
|
|
} |
|
|
|
|
|
console.log( |
|
|
'[API] Raw public projects from DB:', |
|
|
projects?.length, |
|
|
projects, |
|
|
); |
|
|
|
|
|
|
|
|
const mappedProjects: Project[] = (projects || []).map((project) => ({ |
|
|
id: project.project_id, |
|
|
name: project.name || '', |
|
|
description: project.description || '', |
|
|
account_id: project.account_id, |
|
|
created_at: project.created_at, |
|
|
updated_at: project.updated_at, |
|
|
sandbox: project.sandbox || { |
|
|
id: '', |
|
|
pass: '', |
|
|
vnc_preview: '', |
|
|
sandbox_url: '', |
|
|
}, |
|
|
is_public: true, |
|
|
})); |
|
|
|
|
|
console.log( |
|
|
'[API] Mapped public projects for frontend:', |
|
|
mappedProjects.length, |
|
|
); |
|
|
|
|
|
return mappedProjects; |
|
|
} catch (err) { |
|
|
console.error('Error fetching public projects:', err); |
|
|
return []; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export const getProject = async (projectId: string): Promise<Project> => { |
|
|
const supabase = createClient(); |
|
|
|
|
|
try { |
|
|
const { data, error } = await supabase |
|
|
.from('projects') |
|
|
.select('*') |
|
|
.eq('project_id', projectId) |
|
|
.single(); |
|
|
|
|
|
if (error) { |
|
|
|
|
|
if (error.code === 'PGRST116') { |
|
|
throw new Error(`Project not found or not accessible: ${projectId}`); |
|
|
} |
|
|
throw error; |
|
|
} |
|
|
|
|
|
console.log('Raw project data from database:', data); |
|
|
|
|
|
|
|
|
if (data.sandbox?.id) { |
|
|
|
|
|
const ensureSandboxActive = async () => { |
|
|
try { |
|
|
const { |
|
|
data: { session }, |
|
|
} = await supabase.auth.getSession(); |
|
|
|
|
|
|
|
|
const headers: Record<string, string> = { |
|
|
'Content-Type': 'application/json', |
|
|
}; |
|
|
|
|
|
if (session?.access_token) { |
|
|
headers['Authorization'] = `Bearer ${session.access_token}`; |
|
|
} |
|
|
|
|
|
console.log(`Ensuring sandbox is active for project ${projectId}...`); |
|
|
const response = await fetch( |
|
|
`${API_URL}/project/${projectId}/sandbox/ensure-active`, |
|
|
{ |
|
|
method: 'POST', |
|
|
headers, |
|
|
}, |
|
|
); |
|
|
|
|
|
if (!response.ok) { |
|
|
const errorText = await response |
|
|
.text() |
|
|
.catch(() => 'No error details available'); |
|
|
console.warn( |
|
|
`Failed to ensure sandbox is active: ${response.status} ${response.statusText}`, |
|
|
errorText, |
|
|
); |
|
|
} else { |
|
|
console.log('Sandbox activation successful'); |
|
|
} |
|
|
} catch (sandboxError) { |
|
|
console.warn('Failed to ensure sandbox is active:', sandboxError); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
ensureSandboxActive(); |
|
|
} |
|
|
|
|
|
|
|
|
const mappedProject: Project = { |
|
|
id: data.project_id, |
|
|
name: data.name || '', |
|
|
description: data.description || '', |
|
|
account_id: data.account_id, |
|
|
is_public: data.is_public || false, |
|
|
created_at: data.created_at, |
|
|
sandbox: data.sandbox || { |
|
|
id: '', |
|
|
pass: '', |
|
|
vnc_preview: '', |
|
|
sandbox_url: '', |
|
|
}, |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return mappedProject; |
|
|
} catch (error) { |
|
|
console.error(`Error fetching project ${projectId}:`, error); |
|
|
throw error; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const updateProject = async ( |
|
|
projectId: string, |
|
|
data: Partial<Project>, |
|
|
): Promise<Project> => { |
|
|
const supabase = createClient(); |
|
|
|
|
|
console.log('Updating project with ID:', projectId); |
|
|
console.log('Update data:', data); |
|
|
|
|
|
|
|
|
if (!projectId || projectId === '') { |
|
|
console.error('Attempted to update project with invalid ID:', projectId); |
|
|
throw new Error('Cannot update project: Invalid project ID'); |
|
|
} |
|
|
|
|
|
const { data: updatedData, error } = await supabase |
|
|
.from('projects') |
|
|
.update(data) |
|
|
.eq('project_id', projectId) |
|
|
.select() |
|
|
.single(); |
|
|
|
|
|
if (error) { |
|
|
console.error('Error updating project:', error); |
|
|
throw error; |
|
|
} |
|
|
|
|
|
if (!updatedData) { |
|
|
throw new Error('No data returned from update'); |
|
|
} |
|
|
|
|
|
|
|
|
if (typeof window !== 'undefined') { |
|
|
window.dispatchEvent( |
|
|
new CustomEvent('project-updated', { |
|
|
detail: { |
|
|
projectId, |
|
|
updatedData: { |
|
|
id: updatedData.project_id, |
|
|
name: updatedData.name, |
|
|
description: updatedData.description, |
|
|
}, |
|
|
}, |
|
|
}), |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
id: updatedData.project_id, |
|
|
name: updatedData.name, |
|
|
description: updatedData.description || '', |
|
|
account_id: updatedData.account_id, |
|
|
created_at: updatedData.created_at, |
|
|
sandbox: updatedData.sandbox || { |
|
|
id: '', |
|
|
pass: '', |
|
|
vnc_preview: '', |
|
|
sandbox_url: '', |
|
|
}, |
|
|
}; |
|
|
}; |