174 lines
No EOL
6 KiB
TypeScript
174 lines
No EOL
6 KiB
TypeScript
import { app, ipcMain } from "electron";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { getUserConfig } from "../utils";
|
|
|
|
export function setupApiHandlers() {
|
|
// Handler for can-change-keys API
|
|
ipcMain.handle("api:can-change-keys", async () => {
|
|
const canChangeKeys = process.env.CAN_CHANGE_KEYS !== "false";
|
|
return { canChange: canChangeKeys };
|
|
});
|
|
|
|
// Handler for has-required-key API
|
|
ipcMain.handle("api:has-required-key", async () => {
|
|
const userConfigPath = process.env.USER_CONFIG_PATH;
|
|
|
|
let keyFromFile = "";
|
|
if (userConfigPath && fs.existsSync(userConfigPath)) {
|
|
try {
|
|
const raw = fs.readFileSync(userConfigPath, "utf-8");
|
|
const cfg = JSON.parse(raw || "{}");
|
|
keyFromFile = cfg?.OPENAI_API_KEY || "";
|
|
} catch {
|
|
// Silent error handling
|
|
}
|
|
}
|
|
|
|
const keyFromEnv = process.env.OPENAI_API_KEY || "";
|
|
const hasKey = Boolean((keyFromFile || keyFromEnv).trim());
|
|
|
|
return { hasKey };
|
|
});
|
|
|
|
// Reads persisted user config so runtime toggles from the settings page
|
|
// are picked up immediately without requiring an app restart.
|
|
ipcMain.handle("api:telemetry-status", async () => {
|
|
const cfg = getUserConfig();
|
|
const fromConfig = cfg.DISABLE_ANONYMOUS_TRACKING;
|
|
const fromEnv = process.env.DISABLE_ANONYMOUS_TRACKING;
|
|
const raw = fromConfig ?? fromEnv ?? "";
|
|
const isDisabled = raw === "true" || raw === "True";
|
|
return { telemetryEnabled: !isDisabled };
|
|
});
|
|
|
|
// Handler for save-layout API
|
|
ipcMain.handle("api:save-layout", async (event, { layout_name, components }) => {
|
|
try {
|
|
if (!layout_name || !components || !Array.isArray(components)) {
|
|
throw new Error("Invalid request body. Expected layout_name and components array.");
|
|
}
|
|
|
|
// Define the layouts directory path
|
|
const layoutsDir = path.join(process.cwd(), "app_data", "layouts", layout_name);
|
|
|
|
// Create the directory if it doesn't exist
|
|
if (!fs.existsSync(layoutsDir)) {
|
|
fs.mkdirSync(layoutsDir, { recursive: true });
|
|
}
|
|
|
|
// Save each component as a separate file
|
|
const savedFiles = [];
|
|
|
|
for (const component of components) {
|
|
const { slide_number, component_code, component_name } = component;
|
|
|
|
if (!component_code || !component_name) {
|
|
console.warn(
|
|
`Skipping component for slide ${slide_number}: missing code or name`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const fileName = `${component_name}.tsx`;
|
|
const filePath = path.join(layoutsDir, fileName);
|
|
const cleanComponentCode = component_code
|
|
.replace(/```tsx/g, "")
|
|
.replace(/```/g, "");
|
|
|
|
fs.writeFileSync(filePath, cleanComponentCode, "utf8");
|
|
savedFiles.push({
|
|
slide_number,
|
|
component_name,
|
|
file_path: filePath,
|
|
file_name: fileName,
|
|
});
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
layout_name,
|
|
path: layoutsDir,
|
|
saved_files: savedFiles.length,
|
|
components: savedFiles,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error saving layout:", error);
|
|
throw new Error("Failed to save layout components");
|
|
}
|
|
});
|
|
|
|
// Handler for templates API (static list)
|
|
ipcMain.handle("api:templates", async () => {
|
|
try {
|
|
// In development, use servers/nextjs/presentation-templates
|
|
// In production, use resources/nextjs/presentation-templates
|
|
const baseDir = app.getAppPath();
|
|
const isDev = !app.isPackaged;
|
|
const templatesPath = isDev
|
|
? path.join(baseDir, "servers", "nextjs", "presentation-templates")
|
|
: path.join(baseDir, "resources", "nextjs", "presentation-templates");
|
|
|
|
if (!fs.existsSync(templatesPath)) {
|
|
return [];
|
|
}
|
|
|
|
const items = fs.readdirSync(templatesPath, { withFileTypes: true });
|
|
const templateDirectories = items
|
|
.filter(item => item.isDirectory())
|
|
.map(dir => dir.name);
|
|
|
|
const allLayouts: Array<{ templateName: string; templateID: string; files: string[]; settings: any }> = [];
|
|
|
|
// Scan each template directory for layout files and settings
|
|
for (const templateName of templateDirectories) {
|
|
try {
|
|
const templatePath = path.join(templatesPath, templateName);
|
|
const templateFiles = fs.readdirSync(templatePath);
|
|
|
|
// Filter for .tsx files and exclude any non-layout files
|
|
const layoutFiles = templateFiles.filter(file =>
|
|
file.endsWith('.tsx') &&
|
|
!file.startsWith('.') &&
|
|
!file.includes('.test.') &&
|
|
!file.includes('.spec.') &&
|
|
file !== 'settings.json'
|
|
);
|
|
|
|
// Read settings.json if it exists
|
|
let settings: any = null;
|
|
const settingsPath = path.join(templatePath, 'settings.json');
|
|
try {
|
|
const settingsContent = fs.readFileSync(settingsPath, 'utf-8');
|
|
settings = JSON.parse(settingsContent);
|
|
} catch (settingsError) {
|
|
console.warn(`No settings.json found for template ${templateName} or invalid JSON`);
|
|
// Provide default settings if settings.json is missing or invalid
|
|
settings = {
|
|
description: `${templateName} presentation layouts`,
|
|
ordered: false,
|
|
default: false
|
|
};
|
|
}
|
|
|
|
if (layoutFiles.length > 0) {
|
|
allLayouts.push({
|
|
templateName: templateName,
|
|
templateID: templateName,
|
|
files: layoutFiles,
|
|
settings: settings
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error reading template directory ${templateName}:`, error);
|
|
// Continue with other templates even if one fails
|
|
}
|
|
}
|
|
|
|
return allLayouts;
|
|
} catch (error) {
|
|
console.error("Error reading templates:", error);
|
|
return [];
|
|
}
|
|
});
|
|
} |