Skip to content

Commit c1ec313

Browse files
committed
feat: add electron app
Wrap the UI in an electron app. Signed-off-by: Nick Hale <[email protected]>
1 parent 3c12015 commit c1ec313

File tree

10 files changed

+3534
-174
lines changed

10 files changed

+3534
-174
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
# production
1616
/build
17+
/electron-dist
1718

1819
# misc
1920
.DS_Store

electron/build.mjs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import builder from 'electron-builder';
2+
import os from 'os';
3+
4+
const Platform = builder.Platform;
5+
6+
/**
7+
* @type {import('electron-builder').Configuration}
8+
*/
9+
const options = {
10+
appId: 'ai.gptscript.assistant-studio',
11+
productName: 'Assistant Studio',
12+
files: [
13+
"**/*",
14+
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
15+
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
16+
"!**/node_modules/.bin",
17+
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}",
18+
"!.editorconfig",
19+
"!**/._*",
20+
"!**/.next/cache",
21+
"!**/node_modules/@next/swc*",
22+
"!**/node_modules/@next/swc*/**",
23+
"!**/{.DS_Store,.git,.github,*.zip,*.tar.gz,.hg,.svn,CVS,RCS,SCCS,.gitignore,.vscode,.gitattributes,package-lock.json}",
24+
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
25+
"!**/{appveyor.yml,.travis.yml,circle.yml}",
26+
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
27+
],
28+
mac: {
29+
hardenedRuntime: true,
30+
gatekeeperAssess: false,
31+
entitlements: 'electron/entitlements.mac.plist',
32+
entitlementsInherit: 'electron/entitlements.mac.plist',
33+
icon: 'electron/icon.icns',
34+
category: 'public.app-category.productivity',
35+
target: 'dmg',
36+
},
37+
dmg: {
38+
writeUpdateInfo: false,
39+
},
40+
win: {
41+
artifactName: '${productName}-Setup-${version}.${ext}',
42+
target: {
43+
target: 'nsis',
44+
},
45+
},
46+
linux: {
47+
maintainer: 'Acorn Labs',
48+
category: 'Office',
49+
desktop: {
50+
StartupNotify: 'false',
51+
Encoding: 'UTF-8',
52+
MimeType: 'x-scheme-handler/deeplink',
53+
},
54+
icon: "electron/icon.icns",
55+
target: ['AppImage'],
56+
},
57+
compression: 'normal',
58+
removePackageScripts: true,
59+
nodeGypRebuild: false,
60+
buildDependenciesFromSource: false,
61+
directories: {
62+
// app: 'gptscript-ui',
63+
output: 'electron-dist'
64+
},
65+
nsis: {deleteAppDataOnUninstall: true},
66+
publish: {
67+
provider: "github",
68+
publishAutoUpdate: false,
69+
}
70+
};
71+
72+
function go() {
73+
const platform = os.platform();
74+
const arch = os.arch();
75+
76+
let targetPlatform;
77+
switch (platform) {
78+
case 'darwin':
79+
targetPlatform = 'mac';
80+
break;
81+
case 'win32':
82+
targetPlatform = 'windows';
83+
break;
84+
case 'linux':
85+
targetPlatform = 'linux';
86+
break;
87+
default:
88+
throw new Error(`Unsupported platform: ${platform}`);
89+
}
90+
console.log(`targetPlatform: ${targetPlatform}`)
91+
92+
builder
93+
.build({
94+
targets: Platform[targetPlatform.toUpperCase()].createTarget(),
95+
config: options,
96+
})
97+
.then((result) => {
98+
console.info('----------------------------');
99+
console.info('Platform:', platform);
100+
console.info('Architecture:', arch);
101+
console.info('Output:', JSON.stringify(result, null, 2));
102+
});
103+
}
104+
105+
go();

electron/entitlements.mac.plist

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
6+
<true/>
7+
<key>com.apple.security.cs.allow-jit</key>
8+
<true/>
9+
</dict>
10+
</plist>

electron/icon.icns

60.7 KB
Binary file not shown.

electron/icon.ico

37.3 KB
Binary file not shown.

electron/icon.png

55.2 KB
Loading

electron/main.mjs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { app, BrowserWindow } from "electron";
2+
import { getPort } from 'get-port-please';
3+
import { startAppServer } from '../server/app.mjs';
4+
import { join, dirname } from "path";
5+
import { homedir } from "os";
6+
import { existsSync, mkdirSync } from "fs";
7+
import fixPath from "fix-path";
8+
9+
const appName = 'assistant-studio'; // Replace with your app's name
10+
const gatewayUrl = process.env.GATEWAY_URL || 'https://gateway-api.gptscript.ai';
11+
const resourcesDir = dirname(app.getAppPath());
12+
const cacheDir = getCacheDir(appName);
13+
14+
function getCacheDir(appName) {
15+
const platform = process.platform;
16+
const baseDir = platform === 'win32' ? process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local') :
17+
platform === 'darwin' ? join(homedir(), 'Library', 'Caches') :
18+
process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
19+
return join(baseDir, appName);
20+
}
21+
22+
function ensureDirExists(dir) {
23+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
24+
}
25+
26+
async function startServer(isPackaged) {
27+
const port = isPackaged ? await getPort({ portRange: [30000, 40000] }) : 3000;
28+
const gptscriptBin = join(isPackaged ? resourcesDir : "", "node_modules", "@gptscript-ai", "gptscript", "bin", `gptscript${process.platform === "win32" ? ".exe" : ""}`);
29+
30+
process.env.GPTSCRIPT_BIN = process.env.GPTSCRIPT_BIN || gptscriptBin;
31+
process.env.THREADS_DIR = process.env.THREADS_DIR || join(cacheDir, "threads");
32+
process.env.WORKSPACE_DIR = process.env.WORKSPACE_DIR || join(cacheDir, "workspace");
33+
process.env.GATEWAY_URL = process.env.GATEWAY_URL || gatewayUrl;
34+
35+
try {
36+
const url = await startAppServer({ dev: !isPackaged, hostname: 'localhost', port, dir: app.getAppPath() });
37+
console.log(`> ${isPackaged ? "" : "Dev "}Electron app started at ${url}`);
38+
createWindow(url);
39+
} catch (err) {
40+
console.error(err);
41+
process.exit(1);
42+
}
43+
}
44+
45+
function createWindow(url) {
46+
const win = new BrowserWindow({
47+
width: 1024,
48+
height: 720,
49+
frame: false, // This removes the title bar
50+
webPreferences: {
51+
preload: join(app.getAppPath(), "electron/preload.js"),
52+
nodeIntegration: true,
53+
allowRunningInsecureContent: true,
54+
webSecurity: false,
55+
disableBlinkFeatures: "Autofill",
56+
}
57+
});
58+
59+
win.loadURL(url);
60+
win.webContents.on("did-fail-load", () => win.webContents.reloadIgnoringCache());
61+
}
62+
63+
app.on("ready", () => {
64+
fixPath();
65+
ensureDirExists(cacheDir);
66+
startServer(app.isPackaged);
67+
});
68+
69+
app.on("window-all-closed", () => app.quit());

electron/preload.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const electron = require('electron')
2+
3+
electron.contextBridge.exposeInMainWorld("electronAPI", {
4+
on: (channel, callback) => {
5+
electron.ipcRenderer.on(channel, callback);
6+
},
7+
send: (channel, args) => {
8+
electron.ipcRenderer.send(channel, args);
9+
}
10+
});

0 commit comments

Comments
 (0)