Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function Error({
error,
reset,
}: {
error: Error;
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion contexts/script.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ const ScriptContextProvider: React.FC<ScriptContextProps> = ({
};

const restartScript = useCallback(
// This is debonced as allowing the user to spam the restart button can cause race
// This is debounced as allowing the user to spam the restart button can cause race
// conditions. In particular, the restart may not be processed correctly and can
// get the user into a state where no run has been sent to the server.
debounce(async () => {
Expand Down
82 changes: 82 additions & 0 deletions electron/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { app } from 'electron';
import { getPort } from 'get-port-please';
import { join, dirname, parse } from 'path';
import log from 'electron-log/main.js';
import util from 'util';
import { renameSync } from 'fs';

// App config
const dev = !app.isPackaged;
const appName = 'Acorn';
const appDir = app.getAppPath();
const logsDir = app.getPath('logs');
const resourcesDir = dirname(appDir);
const dataDir = join(app.getPath('userData'), appName);
const threadsDir = process.env.THREADS_DIR || join(dataDir, 'threads');
const workspaceDir = process.env.WORKSPACE_DIR || join(dataDir, 'workspace');
const port =
process.env.PORT ||
(!dev ? await getPort({ portRange: [30000, 40000] }) : 3000);
const gptscriptBin =
process.env.GPTSCRIPT_BIN ||
join(
dev ? join(resourcesDir, 'app.asar.unpacked') : '',
'node_modules',
'@gptscript-ai',
'gptscript',
'bin',
`gptscript${process.platform === 'win32' ? '.exe' : ''}`
);
const gatewayUrl =
process.env.GPTSCRIPT_GATEWAY_URL || 'https://gateway-api.gptscript.ai';

// Logging config
const logFormat = ({ data, level, message }) => [
message.date.toISOString(),
`[${message.variables.processType === 'main' ? 'server' : 'client'}]`,
`[${level.toUpperCase()}]`,
util.format(...data),
];

log.transports.console.format = logFormat;

Object.assign(log.transports.file, {
format: logFormat,
resolvePathFn: (variables) => {
return join(logsDir, `${variables.appName}.log`);
},
archiveLogFn: (file) => {
// Get the current Unix timestamp
const info = parse(file.toString());
const timestamp = Math.floor(Date.now() / 1000);

try {
renameSync(file, join(info.dir, `${info.name}.${timestamp}.${info.ext}`));
} catch (e) {
console.warn('failed to rotate log file', e);
}
},
});

log.initialize({
// Include logs gathered from clients via IPC
spyRendererConsole: true,
includeFutureSessions: true,
});

// Forward default console logging to electron-log
Object.assign(console, log.functions);

export const config = {
dev,
appName,
logsDir,
appDir,
resourcesDir,
dataDir,
threadsDir,
workspaceDir,
port,
gptscriptBin,
gatewayUrl,
};
79 changes: 27 additions & 52 deletions electron/main.mjs
Original file line number Diff line number Diff line change
@@ -1,64 +1,43 @@
import { app, shell, BrowserWindow } from 'electron';
import { getPort } from 'get-port-please';
import { startAppServer } from '../server/app.mjs';
import { join, dirname } from 'path';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import fixPath from 'fix-path';
import os from 'os';
import { config } from './config.mjs';

const appName = 'Acorn';
const gatewayUrl =
process.env.GPTSCRIPT_GATEWAY_URL || 'https://gateway-api.gptscript.ai';
const resourcesDir = dirname(app.getAppPath());
const dataDir = getDataDir(appName);

function getDataDir(appName) {
const userDataPath = app.getPath('userData');
return join(userDataPath, appName);
}

function ensureDirExists(dir) {
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
}
app.on('window-all-closed', () => app.quit());
app.on('ready', () => {
startServer(app.isPackaged);
});

async function startServer(isPackaged) {
const port = isPackaged
? await getPort({ portRange: [30000, 40000] })
: process.env.PORT || 3000;
const gptscriptBin = join(
isPackaged ? join(resourcesDir, 'app.asar.unpacked') : '',
'node_modules',
'@gptscript-ai',
'gptscript',
'bin',
`gptscript${process.platform === 'win32' ? '.exe' : ''}`
);

process.env.GPTSCRIPT_BIN = process.env.GPTSCRIPT_BIN || gptscriptBin;
process.env.THREADS_DIR = process.env.THREADS_DIR || join(dataDir, 'threads');
process.env.WORKSPACE_DIR =
process.env.WORKSPACE_DIR || join(dataDir, 'workspace');
process.env.GPTSCRIPT_GATEWAY_URL =
process.env.GPTSCRIPT_GATEWAY_URL || gatewayUrl;
process.env.GPTSCRIPT_OPENAPI_REVAMP = 'true';
// Fix path so that tools can find binaries installed on the system.
fixPath();

console.log(
`Starting app server with GPTSCRIPT_BIN="${process.env.GPTSCRIPT_BIN}"`
);
// Ensure the app's data directory exists
ensureDirExists(config.dataDir);

// Set up the browser tool to run in headless mode.
ensureDirExists(process.env.WORKSPACE_DIR);
ensureDirExists(config.workspaceDir);
writeFileSync(
`${process.env.WORKSPACE_DIR}/browsersettings.json`,
`${config.workspaceDir}/browsersettings.json`,
JSON.stringify({ headless: true })
);

// Project config onto environment variables to configure GPTScript/sdk-server and the Next.js app.
process.env.GPTSCRIPT_BIN = config.gptscriptBin;
process.env.THREADS_DIR = config.threadsDir;
process.env.WORKSPACE_DIR = config.workspaceDir;
process.env.GPTSCRIPT_GATEWAY_URL = config.gatewayUrl;
process.env.GPTSCRIPT_OPENAPI_REVAMP = 'true';

try {
const url = await startAppServer({
dev: !isPackaged,
dev: config.dev,
hostname: 'localhost',
port,
dir: app.getAppPath(),
port: config.port,
appDir: config.appDir,
});
console.log(`> ${isPackaged ? '' : 'Dev '}Electron app started at ${url}`);
createWindow(url);
Expand All @@ -73,9 +52,9 @@ function createWindow(url) {
const win = new BrowserWindow({
width: 1024,
height: 720,
frame: isMac ? false : true, // Use frame: true for Windows and Linux
frame: !isMac,
webPreferences: {
preload: join(app.getAppPath(), 'electron/preload.js'),
preload: join(config.appDir, 'electron/preload.mjs'),
nodeIntegration: true,
allowRunningInsecureContent: true,
webSecurity: false,
Expand All @@ -88,7 +67,7 @@ function createWindow(url) {
win.setWindowButtonVisibility(true);
}

// Open the NextJS app
// Open the Next.js app
win.loadURL(url);

win.webContents.on('did-fail-load', () =>
Expand All @@ -108,10 +87,6 @@ function createWindow(url) {
});
}

app.on('ready', () => {
fixPath();
ensureDirExists(dataDir);
startServer(app.isPackaged);
});

app.on('window-all-closed', () => app.quit());
function ensureDirExists(dir) {
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
}
4 changes: 4 additions & 0 deletions electron/preload.js → electron/preload.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { contextBridge, ipcRenderer } from 'electron';

// Import electron-log preload injection to enable log forwarding from clients (i.e. the renderer process) to the server
// (i.e. the main process).
import 'electron-log/preload.js';

contextBridge.exposeInMainWorld('electronAPI', {
on: (channel, callback) => {
ipcRenderer.on(channel, callback);
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"autoprefixer": "10.4.19",
"clsx": "^2.0.0",
"dotenv": "^16.4.5",
"electron-log": "^5.1.7",
"eslint-config-next": "14.2.1",
"fix-path": "^4.0.0",
"framer-motion": "^11.1.1",
Expand Down
4 changes: 2 additions & 2 deletions server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ dotenv.config({ path: ['.env', '.env.local'] });
const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = parseInt(process.env.GPTSCRIPT_PORT ?? '3000');
const dir = dirname(fileURLToPath(import.meta.url));
const appDir = dirname(fileURLToPath(import.meta.url));
const runFile = process.env.UI_RUN_FILE;
startAppServer({ dev, hostname, port, dir })
startAppServer({ dev, hostname, port, appDir })
.then((address) => {
let landingPage = address;
if (runFile) {
Expand Down
4 changes: 2 additions & 2 deletions server/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let serverRunning = false;
let gptscriptInitialized = false;
let gptscriptInitPromise = null;

export const startAppServer = ({ dev, hostname, port, dir }) => {
export const startAppServer = ({ dev, hostname, port, appDir }) => {
const address = `http://${hostname}:${port ?? 3000}`;

return new Promise((resolve, reject) => {
Expand All @@ -32,7 +32,7 @@ export const startAppServer = ({ dev, hostname, port, dir }) => {
hostname: hostname,
port: port,
conf: nextConfig,
dir: dir,
dir: appDir,
customServer: true,
});
const handler = app.getRequestHandler();
Expand Down