/** * Copyright 2023 Kapeta Inc. * SPDX-License-Identifier: BUSL-1.1 */ import { StormEventFileDone, StormEventPage, StormImage } from './events'; import { filesystemManager } from '../filesystemManager'; import { Response } from 'express'; import os from 'node:os'; import Path from 'path'; import FS from 'fs-extra'; import FSExtra from 'fs-extra'; import { ConversationItem } from './stream'; import exp from 'node:constants'; import { ImagePrompt } from './PageGenerator'; import clusterConfiguration from '@kapeta/local-cluster-config'; export const SystemIdHeader = 'System-Id'; export function normalizePath(path: string) { return path .replace(/#.*$/g, '') // Remove hash .replace(/\?.*$/gi, '') // Remove query params .replace(/:[a-z][a-z_]*\b/gi, '*') // Replace all params with * .replace(/\{[a-z-.]+}/gi, '*'); // Replace all variables with * } export async function writePageToDisk(systemId: string, event: StormEventPage) { const baseDir = getSystemBaseDir(systemId); const filePath = getFilePath(event.payload.method); const path = Path.join(baseDir, normalizePath(event.payload.path), filePath); await FS.ensureDir(Path.dirname(path)); await FS.writeFile(path, event.payload.content); console.log(`Page written to disk: ${event.payload.title} > ${path}`); return { path, }; } export async function writeAssetToDisk(systemId: string, event: StormEventFileDone) { const baseDir = getSystemBaseDir(systemId); const path = Path.join(baseDir, 'public', event.payload.filename); await FS.ensureDir(Path.dirname(path)); await FS.writeFile(path, event.payload.content); return { path, }; } export async function writeImageToDisk(systemId: string, event: StormImage, prompt: ImagePrompt) { const baseDir = getSystemBaseDir(systemId); const path = Path.join(baseDir, normalizePath(prompt.url)); const response = await fetch(event.payload.href); if (!response.ok || !response.body) { throw new Error(`Failed to fetch image: ${event.payload.href}`); } await FS.ensureDir(Path.dirname(path)); const buffer = await response.arrayBuffer(); await FS.writeFile(path, Buffer.from(buffer)); console.log(`Image written to disk: ${event.payload.href} > ${path}`); return { path, }; } export function hasPageOnDisk(systemId: string, path: string, method: string) { const fullPath = resolveReadPath(systemId, path, method); console.log('Checking for page on disk:', fullPath); return !!fullPath && FS.existsSync(fullPath); } export function getSystemBaseDir(systemId: string) { return Path.join(filesystemManager.getProjectRootFolder() || os.tmpdir(), 'ai-systems', systemId); } export function getSystemBaseImplDir(systemId: string) { return Path.join(os.tmpdir(), 'ai-systems-impl', systemId); } function getFilePath(method: string) { // For HEAD requests, we assume we're serving looking for a GET resource return Path.join(method === 'HEAD' ? 'get' : method.toLowerCase(), 'index.html'); } export function resolveReadPath(systemId: string, path: string, method: string) { const baseDir = getSystemBaseDir(systemId); path = normalizePath(path); let fullPath = Path.join(baseDir, path); //First check if there is a file at the exact path try { const stat = FS.statSync(fullPath); if (stat && stat.isFile()) { return fullPath; } } catch (e) { // Ignore } const filePath = getFilePath(method); fullPath = Path.join(baseDir, path, filePath); if (FS.existsSync(fullPath)) { return fullPath; } const parts = path.split('/'); let currentPath = ''; for (let part of parts) { const thisPath = Path.join(currentPath, part); const starPath = Path.join(currentPath, '*'); const thisPathDir = Path.join(baseDir, thisPath); const starPathDir = Path.join(baseDir, starPath); if (FS.existsSync(thisPathDir)) { currentPath = thisPath; continue; } if (FS.existsSync(starPathDir)) { currentPath = starPath; continue; } // Path not found return null; } return Path.join(baseDir, currentPath, filePath); } export function readPageFromDiskAsString(systemId: string, path: string, method: string) { const filePath = resolveReadPath(systemId, path, method); if (!filePath || !FS.existsSync(filePath)) { return null; } return FS.readFileSync(filePath, 'utf8'); } export function readPageFromDisk(systemId: string, path: string, method: string, res: Response) { const filePath = resolveReadPath(systemId, path, method); if (!filePath || !FS.existsSync(filePath)) { if (method === 'HEAD') { // For HEAD requests, only return the status and headers res.status(202).set('Retry-After', '3').end(); } else { // For GET requests, return the fallback HTML with status 202 res.status(202).set('Retry-After', '3').send(getFallbackHtml(path, method)); } return; } res.type(filePath.split('.').pop() as string); const content = FS.readFileSync(filePath); if (method === 'HEAD') { // For HEAD requests, just end the response after setting headers res.status(200).end(); } else { // For GET requests, return the full content res.write(content); res.end(); } } function getFallbackHtml(path: string, method: string): string { return `
Henrik is still working on this page. Please wait...