export type FileEntry = { name: string; type: 'file' | 'directory'; size?: number; modified?: number; }; export type ListeningPort = { port: number; pid: number | null; process: string | null; }; export type TerminalFeatures = { terminal?: boolean; }; import { REXPRO_API_BASE_URL } from '$lib/constants'; export type TerminalServer = { id: string; url: string; name: string; }; export const getTerminalServers = async (token: string): Promise => { const res = await fetch(`${REXPRO_API_BASE_URL}/terminals/`, { headers: { Authorization: `Bearer ${token}` } }).catch(() => null); if (!res || !res.ok) return []; return res.json().catch(() => []); }; export const getTerminalConfig = async ( baseUrl: string, apiKey: string ): Promise<{ features: TerminalFeatures } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/api/config`; const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } }).catch(() => null); if (!res || !res.ok) return null; return res.json().catch(() => null); }; export const getCwd = async ( baseUrl: string, apiKey: string, sessionId?: string ): Promise => { const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`; const headers: Record = { Authorization: `Bearer ${apiKey}` }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { headers }).catch(() => null); if (!res || !res.ok) return null; const json = await res.json().catch(() => null); return json?.cwd ?? null; }; export const listFiles = async ( baseUrl: string, apiKey: string, path: string = '/', sessionId?: string ): Promise => { // The endpoint uses `directory` as the query param name const url = `${baseUrl.replace(/\/$/, '')}/files/list?directory=${encodeURIComponent(path)}`; const headers: Record = { Authorization: `Bearer ${apiKey}` }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { headers }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); }) .catch((err) => { console.error('open-terminal listFiles error:', err); return null; }); return res?.entries ?? null; }; export const readFile = async ( baseUrl: string, apiKey: string, path: string, sessionId?: string ): Promise => { const url = `${baseUrl.replace(/\/$/, '')}/files/read?path=${encodeURIComponent(path)}`; const headers: Record = { Authorization: `Bearer ${apiKey}` }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { headers }).catch((err) => { console.error('open-terminal readFile error:', err); return null; }); if (!res || !res.ok) return null; const contentType = res.headers.get('content-type') ?? ''; if (contentType.startsWith('image/') || contentType.startsWith('application/octet')) { // Binary — return a placeholder return `[Binary file: ${contentType}]`; } // Text files: endpoint returns JSON { path, total_lines, content } // Binary image files: endpoint returns raw bytes (handled above) const json = await res.json().catch(() => null); return json?.content ?? null; }; export const downloadFileBlob = async ( baseUrl: string, apiKey: string, path: string, sessionId?: string ): Promise<{ blob: Blob; filename: string } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/files/view?path=${encodeURIComponent(path)}`; const headers: Record = { Authorization: `Bearer ${apiKey}` }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { headers }).catch(() => null); if (!res || !res.ok) return null; const filename = path.split('/').pop() ?? 'file'; const blob = await res.blob(); return { blob, filename }; }; export const archiveFromTerminal = async ( baseUrl: string, apiKey: string, paths: string[], sessionId?: string ): Promise<{ blob: Blob; filename: string } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/files/archive`; const headers: Record = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ paths }) }).catch(() => null); if (!res || !res.ok) return null; const disposition = res.headers.get('content-disposition') ?? ''; const match = disposition.match(/filename="?([^"]+)"?/); const filename = match?.[1] ?? 'download.zip'; const blob = await res.blob(); return { blob, filename }; }; export const uploadToTerminal = async ( baseUrl: string, apiKey: string, directory: string, file: File, sessionId?: string ): Promise<{ path: string; size: number } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/files/upload?directory=${encodeURIComponent(directory)}`; const body = new FormData(); body.append('file', file); const headers: Record = { Authorization: `Bearer ${apiKey}` }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { method: 'POST', headers, body }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); }) .catch((err) => { console.error('open-terminal uploadToTerminal error:', err); return null; }); return res; }; export const createDirectory = async ( baseUrl: string, apiKey: string, path: string, sessionId?: string ): Promise<{ path: string } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/files/mkdir`; const headers: Record = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ path }) }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); }) .catch((err) => { console.error('open-terminal createDirectory error:', err); return null; }); return res; }; export const deleteEntry = async ( baseUrl: string, apiKey: string, path: string, sessionId?: string ): Promise<{ path: string; type: string } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/files/delete?path=${encodeURIComponent(path)}`; const headers: Record = { Authorization: `Bearer ${apiKey}` }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { method: 'DELETE', headers }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); }) .catch((err) => { console.error('open-terminal deleteEntry error:', err); return null; }); return res; }; export const setCwd = async ( baseUrl: string, apiKey: string, path: string, sessionId?: string ): Promise<{ cwd: string } | null> => { const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`; const headers: Record = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ path }) }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); }) .catch((err) => { console.error('open-terminal setCwd error:', err); return null; }); return res; }; export const moveEntry = async ( baseUrl: string, apiKey: string, source: string, destination: string, sessionId?: string ): Promise<{ source: string; destination: string } | { error: string }> => { const url = `${baseUrl.replace(/\/$/, '')}/files/move`; const headers: Record = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; if (sessionId) headers['X-Session-Id'] = sessionId; const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ source, destination }) }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); }) .catch((err) => { console.error('open-terminal moveEntry error:', err); return { error: err?.detail ?? 'Move failed' }; }); return res; }; export const getListeningPorts = async ( baseUrl: string, apiKey: string ): Promise => { const url = `${baseUrl.replace(/\/$/, '')}/ports`; const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } }).catch(() => null); if (!res || !res.ok) return []; const json = await res.json().catch(() => null); return json?.ports ?? []; }; export const getPortProxyUrl = (baseUrl: string, port: number, path: string = ''): string => { return `${baseUrl.replace(/\/$/, '')}/proxy/${port}/${path}`; }; // --------------------------------------------------------------------------- // Notebook execution // --------------------------------------------------------------------------- export const createNotebookSession = async ( baseUrl: string, apiKey: string, path: string ): Promise<{ id: string; kernel: string; status: string } | { error: string }> => { const url = `${baseUrl.replace(/\/$/, '')}/notebooks`; const res = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ path }) }) .then(async (res) => { if (!res.ok) { const body = await res.json().catch(() => ({})); return { error: body?.detail ?? `HTTP ${res.status}` }; } return res.json(); }) .catch((err) => { console.error('open-terminal createNotebookSession error:', err); return { error: 'Connection failed' }; }); return res; }; export const executeNotebookCell = async ( baseUrl: string, apiKey: string, sessionId: string, cellIndex: number, source?: string ): Promise<{ status: string; execution_count?: number; outputs: any[] } | { error: string }> => { const url = `${baseUrl.replace(/\/$/, '')}/notebooks/${sessionId}/execute`; const body: Record = { cell_index: cellIndex }; if (source !== undefined) body.source = source; const res = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) .then(async (res) => { if (!res.ok) { const body = await res.json().catch(() => ({})); return { error: body?.detail ?? `HTTP ${res.status}` }; } return res.json(); }) .catch((err) => { console.error('open-terminal executeNotebookCell error:', err); return { error: 'Connection failed' }; }); return res; }; export const stopNotebookSession = async ( baseUrl: string, apiKey: string, sessionId: string ): Promise => { const url = `${baseUrl.replace(/\/$/, '')}/notebooks/${sessionId}`; const res = await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${apiKey}` } }).catch(() => null); return res?.ok ?? false; };