From 8436fba4c14ff82537d19b99fe94e9a7e582426b Mon Sep 17 00:00:00 2001 From: Nick Hale <4175918+njhale@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:51:39 -0400 Subject: [PATCH] chore: open landing page with ui instead of using the llm Signed-off-by: Nick Hale <4175918+njhale@users.noreply.github.com> --- package-lock.json | 134 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- server.mjs | 132 ++++++++++++++++++++++++++++++--------------- tool.gpt | 85 ----------------------------- 4 files changed, 224 insertions(+), 130 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb5ee8af..322f9c9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "lodash": "^4.17.21", "next": "14.2.1", "next-themes": "^0.2.1", + "open": "^10.1.0", "postcss": "8.4.38", "react": "18.2.0", "react-dom": "18.2.0", @@ -4937,6 +4938,21 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -5441,6 +5457,34 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -5457,6 +5501,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -7076,6 +7132,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7137,6 +7208,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -7319,6 +7408,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -8785,6 +8889,24 @@ "wrappy": "1" } }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9575,6 +9697,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index cb222a43..627740e1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "node server.mjs", - "debug" : "node --inspect server.mjs", + "debug": "node --inspect server.mjs", "build": "next build", "start": "next start", "lint": "next lint" @@ -39,6 +39,7 @@ "lodash": "^4.17.21", "next": "14.2.1", "next-themes": "^0.2.1", + "open": "^10.1.0", "postcss": "8.4.38", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/server.mjs b/server.mjs index 4788a961..6a0775c3 100644 --- a/server.mjs +++ b/server.mjs @@ -1,10 +1,11 @@ -import { createServer } from "node:http"; +import {createServer} from "node:http"; import next from "next"; -import { Server } from "socket.io"; -import { GPTScript, RunEventType, RunState } from '@gptscript-ai/gptscript'; +import {Server} from "socket.io"; +import {GPTScript, RunEventType, RunState} from '@gptscript-ai/gptscript'; import dotenv from 'dotenv'; import fs from 'fs'; import path from 'path'; +import open from 'open'; dotenv.config({path: ['.env', '.env.local']}); @@ -16,21 +17,22 @@ const DISABLE_CACHE = process.env.DISABLE_CACHE === "true"; const dev = process.env.NODE_ENV !== "production"; const hostname = "localhost"; const port = parseInt(process.env.GPTSCRIPT_PORT || "3000"); -const app = next({ dev, hostname, port }); +const runFile = process.env.UI_RUN_FILE +const app = next({dev, hostname, port}); const handler = app.getRequestHandler(); let THREADS_DIR = process.env.THREADS_DIR || ""; let runningScript = null; app.prepare().then(() => { - const httpServer = createServer(handler); + const httpServer = createServer(handler); - const io = new Server(httpServer); - const gptscript = new GPTScript() + const io = new Server(httpServer); + const gptscript = new GPTScript() - io.on("connection", (socket) => { - io.emit("message", "connected"); - socket.on("run", async (file, tool, args, workspace, threadID) => { + io.on("connection", (socket) => { + io.emit("message", "connected"); + socket.on("run", async (file, tool, args, workspace, threadID) => { if (runningScript) { await runningScript.close(); runningScript = null; @@ -41,29 +43,43 @@ app.prepare().then(() => { } catch (e) { socket.emit("error", e); } - }); - }); - - httpServer - .once("error", (err) => { - console.error(err); - process.exit(1); - }) - .listen(port, () => { - console.log(`> Socket server is ready at http://${hostname}:${port}`); - }); + }); + }); + + httpServer + .once("error", (err) => { + console.error(err); + process.exit(1); + }) + .listen(port, () => { + console.log(`> Socket server is ready at http://${hostname}:${port}`); + }); + + // Open the landing page in the browser + let landingPage = `http://${hostname}:${port}` + if (runFile) { + // `--file` was provided, change the landing page URL to run the given script + landingPage = `${landingPage}/run?file=${runFile}` + } + + open(landingPage).then(() => { + console.log(`${landingPage} opened!`) + }).catch((err) => { + console.error(`Failed to open landing page ${landingPage}: ${err.message}`) + }) + }); const mount = async (file, tool, args, workspace, socket, threadID, gptscript) => { - const opts = { - input: JSON.stringify(args || {}), - disableCache: DISABLE_CACHE, + const opts = { + input: JSON.stringify(args || {}), + disableCache: DISABLE_CACHE, workspace: workspace, - prompt: true, - confirm: true, - }; + prompt: true, + confirm: true, + }; - if (tool) opts.subTool = tool; + if (tool) opts.subTool = tool; let state = {}; let statePath = '' @@ -75,24 +91,39 @@ const mount = async (file, tool, args, workspace, socket, threadID, gptscript) = opts.chatState = state.chatState; socket.emit("loaded", state.messages); } - } catch (e) {} + } catch (e) { + } // Start the script let runningScript = null; - socket.on("interrupt", async() => { if (runningScript) runningScript.close() }); - socket.on('disconnect', () => { if (runningScript) runningScript.close(); runningScript = null; }); + socket.on("interrupt", async () => { + if (runningScript) runningScript.close() + }); + socket.on('disconnect', () => { + if (runningScript) runningScript.close(); + runningScript = null; + }); if (!threadID || !state.chatState) { runningScript = await gptscript.run(file, opts) socket.emit("running"); // Handle initial runningScript events - runningScript.on(RunEventType.Event, data => socket.emit('progress', {frame: data, state: runningScript.calls})); - runningScript.on(RunEventType.Prompt, async (data) => socket.emit("promptRequest", {frame: data, state: runningScript.calls})); - runningScript.on(RunEventType.CallConfirm, (data) => socket.emit("confirmRequest", {frame: data, state: runningScript.calls})); + runningScript.on(RunEventType.Event, data => socket.emit('progress', { + frame: data, + state: runningScript.calls + })); + runningScript.on(RunEventType.Prompt, async (data) => socket.emit("promptRequest", { + frame: data, + state: runningScript.calls + })); + runningScript.on(RunEventType.CallConfirm, (data) => socket.emit("confirmRequest", { + frame: data, + state: runningScript.calls + })); socket.on("promptResponse", async (data) => await gptscript.promptResponse(data)); socket.on("confirmResponse", async (data) => await gptscript.confirm(data)); - + // Wait for the run to finish and emit any errors that occur. Specifically look for the "Run has been aborted" error // as this is a marker of an interrupt. @@ -106,12 +137,14 @@ const mount = async (file, tool, args, workspace, socket, threadID, gptscript) = if (threadID) { fs.writeFile(statePath, JSON.stringify(state), (err) => { - if (err) { socket.emit("error", err)} + if (err) { + socket.emit("error", err) + } }); } }) .catch((e) => e && e != "Run has been aborted" && socket.emit("error", e)) - + } else { socket.emit("running"); // temp socket.emit("resuming"); @@ -138,9 +171,18 @@ const mount = async (file, tool, args, workspace, socket, threadID, gptscript) = runningScript = runningScript.nextChat(message); } - runningScript.on(RunEventType.Event, data => socket.emit('progress', {frame: data, state: runningScript.calls})); - runningScript.on(RunEventType.Prompt, async (data) => socket.emit("promptRequest", {frame: data, state: runningScript.calls})); - runningScript.on(RunEventType.CallConfirm, (data) => socket.emit("confirmRequest", {frame: data, state: runningScript.calls})); + runningScript.on(RunEventType.Event, data => socket.emit('progress', { + frame: data, + state: runningScript.calls + })); + runningScript.on(RunEventType.Prompt, async (data) => socket.emit("promptRequest", { + frame: data, + state: runningScript.calls + })); + runningScript.on(RunEventType.CallConfirm, (data) => socket.emit("confirmRequest", { + frame: data, + state: runningScript.calls + })); socket.on("promptResponse", async (data) => await gptscript.promptResponse(data)); socket.on("confirmResponse", async (data) => await gptscript.confirm(data)); @@ -153,12 +195,14 @@ const mount = async (file, tool, args, workspace, socket, threadID, gptscript) = {type: USER, message: message}, {type: AGENT, message: output} ) - + state.chatState = runningScript.currentChatState(); - + if (threadID) { fs.writeFile(statePath, JSON.stringify(state), (err) => { - if (err) { socket.emit("error", err)} + if (err) { + socket.emit("error", err) + } }); } }) @@ -174,4 +218,4 @@ const dismount = (socket) => { socket.removeAllListeners("confirmResponse"); socket.removeAllListeners("userMessage"); socket.removeAllListeners("disconnect"); -} \ No newline at end of file +} diff --git a/tool.gpt b/tool.gpt index 2659539f..701128dd 100644 --- a/tool.gpt +++ b/tool.gpt @@ -1,86 +1 @@ -context: github.com/gptscript-ai/context/os -tools: service, port, open-file-nix, open-nix, open-file-windows, open-windows, open-file-mac, open-mac -params: file: (optional) A reference to a tool file. Can refer to either a local or remote file. - -Never run steps in parallel. - -Perform the following steps in the order they appear: -1. Run the service -2. Get the port for the running service -3. If no {file} was provided AND the local operating system is windows, use the open-windows tool then skip the remaining steps -3. If no {file} was provided AND the local operating system is Darwin, use the open-mac tool then skip the remaining steps -4. If no {file} was provided use the open-nix tool then skip the remaining steps -5. If the operating system is windows, use the open-file-windows tool then skip the remaining steps -5. If the operating system is Darwin, use the open-file-mac tool then skip the remaining steps -6. Use the open-file-nix tool - ---- - -name: service - #!sys.daemon (path=/api/readyz) /usr/bin/env npm run --prefix ${GPTSCRIPT_TOOL_DIR} dev - ---- - -name: port -tools: service -description: Get the port for the running service - -#!http://service.daemon.gptscript.local/api/port - ---- - -name: open-file-nix -arg: port: The port the service is listening on -arg: file: The file to open - -#!/usr/bin/env sh - -xdg-open http://localhost:${PORT}/run?file=${FILE} - ---- - -name: open-nix -arg: port: The port the service is listening on - -#!/usr/bin/env sh - -xdg-open http://localhost:${PORT} - ---- - -name: open-file-windows -arg: port: The port the service is listening on -arg: file: The file to open - -#!/usr/bin/env powershell.exe - -start http://localhost:$env:PORT/run?file=$env:FILE - ---- - -name: open-windows -arg: port: The port the service is listening on - -#!/usr/bin/env powershell.exe - -start http://localhost:$env:PORT - ---- - -name: open-file-mac -arg: port: The port the service is listening on -arg: file: The file to open - -#!/usr/bin/env sh - -open http://localhost:${PORT}/run?file=${FILE} - ---- - -name: open-mac -arg: port: The port the service is listening on - -#!/usr/bin/env sh - -open http://localhost:${PORT}