checkpoint on domain sockets + update background colors + transparency (#160)

This commit is contained in:
Mike Sawka
2024-07-26 13:30:11 -07:00
committed by GitHub
parent f1837d6fae
commit 9df9c99fbd
31 changed files with 1000 additions and 436 deletions
+9 -3
View File
@@ -8,11 +8,12 @@ import (
"os" "os"
"reflect" "reflect"
"sort" "sort"
"strings"
"github.com/wavetermdev/thenextwave/pkg/service" "github.com/wavetermdev/thenextwave/pkg/service"
"github.com/wavetermdev/thenextwave/pkg/tsgen" "github.com/wavetermdev/thenextwave/pkg/tsgen"
"github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/util/utilfn"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver" "github.com/wavetermdev/thenextwave/pkg/wshrpc"
) )
func generateTypesFile(tsTypesMap map[reflect.Type]string) error { func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
@@ -43,6 +44,10 @@ func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
return iname < jname return iname < jname
}) })
for _, key := range keys { for _, key := range keys {
// don't output generic types
if strings.Index(key.Name(), "[") != -1 {
continue
}
tsCode := tsTypesMap[key] tsCode := tsTypesMap[key]
istr := utilfn.IndentString(" ", tsCode) istr := utilfn.IndentString(" ", tsCode)
fmt.Fprint(fd, istr) fmt.Fprint(fd, istr)
@@ -79,16 +84,17 @@ func generateWshServerFile(tsTypeMap map[reflect.Type]string) error {
return err return err
} }
defer fd.Close() defer fd.Close()
declMap := wshrpc.GenerateWshCommandDeclMap()
fmt.Fprintf(os.Stderr, "generating wshserver file to %s\n", fd.Name()) fmt.Fprintf(os.Stderr, "generating wshserver file to %s\n", fd.Name())
fmt.Fprintf(fd, "// Copyright 2024, Command Line Inc.\n") fmt.Fprintf(fd, "// Copyright 2024, Command Line Inc.\n")
fmt.Fprintf(fd, "// SPDX-License-Identifier: Apache-2.0\n\n") fmt.Fprintf(fd, "// SPDX-License-Identifier: Apache-2.0\n\n")
fmt.Fprintf(fd, "// generated by cmd/generate/main-generate.go\n\n") fmt.Fprintf(fd, "// generated by cmd/generate/main-generate.go\n\n")
fmt.Fprintf(fd, "import * as WOS from \"./wos\";\n\n") fmt.Fprintf(fd, "import * as WOS from \"./wos\";\n\n")
orderedKeys := utilfn.GetOrderedMapKeys(wshserver.WshServerCommandToDeclMap) orderedKeys := utilfn.GetOrderedMapKeys(declMap)
fmt.Fprintf(fd, "// WshServerCommandToDeclMap\n") fmt.Fprintf(fd, "// WshServerCommandToDeclMap\n")
fmt.Fprintf(fd, "class WshServerType {\n") fmt.Fprintf(fd, "class WshServerType {\n")
for _, methodDecl := range orderedKeys { for _, methodDecl := range orderedKeys {
methodDecl := wshserver.WshServerCommandToDeclMap[methodDecl] methodDecl := declMap[methodDecl]
methodStr := tsgen.GenerateWshServerMethod(methodDecl, tsTypeMap) methodStr := tsgen.GenerateWshServerMethod(methodDecl, tsTypeMap)
fmt.Fprint(fd, methodStr) fmt.Fprint(fd, methodStr)
fmt.Fprintf(fd, "\n") fmt.Fprintf(fd, "\n")
@@ -8,11 +8,10 @@ import (
"os" "os"
"github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/util/utilfn"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver" "github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
) )
func genMethod_ResponseStream(fd *os.File, methodDecl *wshserver.WshServerMethodDecl) { func genMethod_ResponseStream(fd *os.File, methodDecl *wshrpc.WshRpcMethodDecl) {
fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName) fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName)
var dataType string var dataType string
dataVarName := "nil" dataVarName := "nil"
@@ -29,7 +28,7 @@ func genMethod_ResponseStream(fd *os.File, methodDecl *wshserver.WshServerMethod
fmt.Fprintf(fd, "}\n\n") fmt.Fprintf(fd, "}\n\n")
} }
func genMethod_Call(fd *os.File, methodDecl *wshserver.WshServerMethodDecl) { func genMethod_Call(fd *os.File, methodDecl *wshrpc.WshRpcMethodDecl) {
fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName) fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName)
var dataType string var dataType string
dataVarName := "nil" dataVarName := "nil"
@@ -70,14 +69,14 @@ func main() {
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshutil\"\n") fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshutil\"\n")
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshrpc\"\n") fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshrpc\"\n")
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/waveobj\"\n") fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/waveobj\"\n")
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/waveai\"\n")
fmt.Fprintf(fd, ")\n\n") fmt.Fprintf(fd, ")\n\n")
for _, key := range utilfn.GetOrderedMapKeys(wshserver.WshServerCommandToDeclMap) { wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
methodDecl := wshserver.WshServerCommandToDeclMap[key] for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
if methodDecl.CommandType == wshutil.RpcType_ResponseStream { methodDecl := wshDeclMap[key]
if methodDecl.CommandType == wshrpc.RpcType_ResponseStream {
genMethod_ResponseStream(fd, methodDecl) genMethod_ResponseStream(fd, methodDecl)
} else if methodDecl.CommandType == wshutil.RpcType_Call { } else if methodDecl.CommandType == wshrpc.RpcType_Call {
genMethod_Call(fd, methodDecl) genMethod_Call(fd, methodDecl)
} else { } else {
panic("unsupported command type " + methodDecl.CommandType) panic("unsupported command type " + methodDecl.CommandType)
+1 -1
View File
@@ -43,7 +43,7 @@ func runReadFile(cmd *cobra.Command, args []string) {
fmt.Fprintf(os.Stderr, "error resolving oref: %v\r\n", err) fmt.Fprintf(os.Stderr, "error resolving oref: %v\r\n", err)
return return
} }
resp64, err := wshclient.ReadFile(RpcClient, wshrpc.CommandFileData{ZoneId: fullORef.OID, FileName: args[1]}, &wshrpc.WshRpcCommandOpts{Timeout: 5000}) resp64, err := wshclient.FileReadCommand(RpcClient, wshrpc.CommandFileData{ZoneId: fullORef.OID, FileName: args[1]}, &wshrpc.WshRpcCommandOpts{Timeout: 5000})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "error reading file: %v\r\n", err) fmt.Fprintf(os.Stderr, "error reading file: %v\r\n", err)
return return
+59 -26
View File
@@ -34,6 +34,7 @@ const waveSrvReady: Promise<boolean> = new Promise((resolve, _) => {
}); });
let globalIsQuitting = false; let globalIsQuitting = false;
let globalIsStarting = true; let globalIsStarting = true;
let globalIsRelaunching = false;
const isDev = !electronApp.isPackaged; const isDev = !electronApp.isPackaged;
const isDevVite = isDev && process.env.ELECTRON_RENDERER_URL; const isDevVite = isDev && process.env.ELECTRON_RENDERER_URL;
@@ -214,7 +215,8 @@ async function handleWSEvent(evtMsg: WSEventType) {
return; return;
} }
const clientData = await services.ClientService.GetClientData(); const clientData = await services.ClientService.GetClientData();
const newWin = createBrowserWindow(clientData.oid, windowData); const settings = await services.FileService.GetSettingsConfig();
const newWin = createBrowserWindow(clientData.oid, windowData, settings);
await newWin.readyPromise; await newWin.readyPromise;
newWin.show(); newWin.show();
} else if (evtMsg.eventtype == "electron:closewindow") { } else if (evtMsg.eventtype == "electron:closewindow") {
@@ -290,7 +292,11 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
// note, this does not *show* the window. // note, this does not *show* the window.
// to show, await win.readyPromise and then win.show() // to show, await win.readyPromise and then win.show()
function createBrowserWindow(clientId: string, waveWindow: WaveWindow): WaveBrowserWindow { function createBrowserWindow(
clientId: string,
waveWindow: WaveWindow,
settings: SettingsConfigType
): WaveBrowserWindow {
let winBounds = { let winBounds = {
x: waveWindow.pos.x, x: waveWindow.pos.x,
y: waveWindow.pos.y, y: waveWindow.pos.y,
@@ -298,7 +304,7 @@ function createBrowserWindow(clientId: string, waveWindow: WaveWindow): WaveBrow
height: waveWindow.winsize.height, height: waveWindow.winsize.height,
}; };
winBounds = ensureBoundsAreVisible(winBounds); winBounds = ensureBoundsAreVisible(winBounds);
const bwin = new electron.BrowserWindow({ const winOpts: Electron.BrowserWindowConstructorOptions = {
titleBarStyle: "hiddenInset", titleBarStyle: "hiddenInset",
x: winBounds.x, x: winBounds.x,
y: winBounds.y, y: winBounds.y,
@@ -316,8 +322,14 @@ function createBrowserWindow(clientId: string, waveWindow: WaveWindow): WaveBrow
}, },
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
backgroundColor: "#000000", };
}); const isTransparent = settings?.window?.transparent ?? true;
if (isTransparent) {
winOpts.transparent = true;
} else {
winOpts.backgroundColor = "#222222";
}
const bwin = new electron.BrowserWindow(winOpts);
(bwin as any).waveWindowId = waveWindow.oid; (bwin as any).waveWindowId = waveWindow.oid;
let readyResolve: (value: void) => void; let readyResolve: (value: void) => void;
(bwin as any).readyPromise = new Promise((resolve, _) => { (bwin as any).readyPromise = new Promise((resolve, _) => {
@@ -519,7 +531,8 @@ electron.ipcMain.on("getEnv", (event, varName) => {
async function createNewWaveWindow() { async function createNewWaveWindow() {
const clientData = await services.ClientService.GetClientData(); const clientData = await services.ClientService.GetClientData();
const newWindow = await services.ClientService.MakeWindow(); const newWindow = await services.ClientService.MakeWindow();
const newBrowserWindow = createBrowserWindow(clientData.oid, newWindow); const settings = await services.FileService.GetSettingsConfig();
const newBrowserWindow = createBrowserWindow(clientData.oid, newWindow, settings);
newBrowserWindow.show(); newBrowserWindow.show();
} }
@@ -616,6 +629,12 @@ function makeAppMenu() {
{ {
role: "forceReload", role: "forceReload",
}, },
{
label: "Relaunch All Windows",
click: () => {
relaunchBrowserWindows();
},
},
{ {
role: "toggleDevTools", role: "toggleDevTools",
}, },
@@ -663,6 +682,9 @@ function makeAppMenu() {
} }
electronApp.on("window-all-closed", () => { electronApp.on("window-all-closed", () => {
if (globalIsRelaunching) {
return;
}
if (unamePlatform !== "darwin") { if (unamePlatform !== "darwin") {
electronApp.quit(); electronApp.quit();
} }
@@ -857,6 +879,36 @@ async function configureAutoUpdater() {
} }
// ====== AUTO-UPDATER ====== // // ====== AUTO-UPDATER ====== //
async function relaunchBrowserWindows() {
globalIsRelaunching = true;
const windows = electron.BrowserWindow.getAllWindows();
for (const window of windows) {
window.removeAllListeners();
window.close();
}
globalIsRelaunching = false;
const clientData = await services.ClientService.GetClientData();
const settings = await services.FileService.GetSettingsConfig();
const wins: WaveBrowserWindow[] = [];
for (const windowId of clientData.windowids.slice().reverse()) {
const windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
if (windowData == null) {
services.WindowService.CloseWindow(windowId).catch((e) => {
/* ignore */
});
continue;
}
const win = createBrowserWindow(clientData.oid, windowData, settings);
wins.push(win);
}
for (const win of wins) {
await win.readyPromise;
console.log("show", win.waveWindowId);
win.show();
}
}
async function appMain() { async function appMain() {
const startTs = Date.now(); const startTs = Date.now();
const instanceLock = electronApp.requestSingleInstanceLock(); const instanceLock = electronApp.requestSingleInstanceLock();
@@ -877,27 +929,8 @@ async function appMain() {
} }
const ready = await waveSrvReady; const ready = await waveSrvReady;
console.log("wavesrv ready signal received", ready, Date.now() - startTs, "ms"); console.log("wavesrv ready signal received", ready, Date.now() - startTs, "ms");
console.log("get client data");
const clientData = await services.ClientService.GetClientData();
console.log("client data ready");
await electronApp.whenReady(); await electronApp.whenReady();
const wins: WaveBrowserWindow[] = []; relaunchBrowserWindows();
for (const windowId of clientData.windowids.slice().reverse()) {
const windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
if (windowData == null) {
services.WindowService.CloseWindow(windowId).catch((e) => {
/* ignore */
});
continue;
}
const win = createBrowserWindow(clientData.oid, windowData);
wins.push(win);
}
for (const win of wins) {
await win.readyPromise;
console.log("show", win.waveWindowId);
win.show();
}
configureAutoUpdater(); configureAutoUpdater();
globalIsStarting = false; globalIsStarting = false;
+2
View File
@@ -14,6 +14,8 @@ body {
font: var(--base-font); font: var(--base-font);
overflow: hidden; overflow: hidden;
-webkit-font-smoothing: auto; -webkit-font-smoothing: auto;
backface-visibility: hidden;
transform: translateZ(0);
} }
*::-webkit-scrollbar { *::-webkit-scrollbar {
+27
View File
@@ -16,6 +16,7 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { CenteredDiv } from "./element/quickelems"; import { CenteredDiv } from "./element/quickelems";
import clsx from "clsx"; import clsx from "clsx";
import Color from "color";
import "overlayscrollbars/overlayscrollbars.css"; import "overlayscrollbars/overlayscrollbars.css";
import "./app.less"; import "./app.less";
@@ -200,6 +201,31 @@ function switchBlock(tabId: string, offsetX: number, offsetY: number) {
} }
} }
function AppSettingsUpdater() {
const settings = jotai.useAtomValue(atoms.settingsConfigAtom);
React.useEffect(() => {
let isTransparent = settings?.window?.transparent ?? true;
let opacity = util.boundNumber(settings?.window?.opacity ?? 0.8, 0, 1);
let baseBgColor = settings?.window?.bgcolor;
console.log("window settings", settings.window);
if (isTransparent) {
document.body.classList.add("is-transparent");
const rootStyles = getComputedStyle(document.documentElement);
if (baseBgColor == null) {
baseBgColor = rootStyles.getPropertyValue("--main-bg-color").trim();
}
const color = new Color(baseBgColor);
const rgbaColor = color.alpha(opacity).string();
document.body.style.backgroundColor = rgbaColor;
} else {
document.body.classList.remove("is-transparent");
document.body.style.opacity = null;
}
}, [settings?.window]);
return null;
}
const AppInner = () => { const AppInner = () => {
const client = jotai.useAtomValue(atoms.client); const client = jotai.useAtomValue(atoms.client);
const windowData = jotai.useAtomValue(atoms.waveWindow); const windowData = jotai.useAtomValue(atoms.waveWindow);
@@ -251,6 +277,7 @@ const AppInner = () => {
const isFullScreen = jotai.useAtomValue(atoms.isFullScreen); const isFullScreen = jotai.useAtomValue(atoms.isFullScreen);
return ( return (
<div className={clsx("mainapp", PLATFORM, { fullscreen: isFullScreen })} onContextMenu={handleContextMenu}> <div className={clsx("mainapp", PLATFORM, { fullscreen: isFullScreen })} onContextMenu={handleContextMenu}>
<AppSettingsUpdater />
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<Workspace /> <Workspace />
</DndProvider> </DndProvider>
+1 -1
View File
@@ -59,7 +59,7 @@
padding: 2px; padding: 2px;
.block-frame-default-inner { .block-frame-default-inner {
background-color: rgba(0, 0, 0, 0.5); background-color: var(--block-bg-color);
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 8px; border-radius: 8px;
+55 -25
View File
@@ -7,14 +7,19 @@ import * as WOS from "./wos";
// WshServerCommandToDeclMap // WshServerCommandToDeclMap
class WshServerType { class WshServerType {
// command "controller:input" [call] // command "authenticate" [call]
BlockInputCommand(data: CommandBlockInputData, opts?: WshRpcCommandOpts): Promise<void> { AuthenticateCommand(data: string, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("controller:input", data, opts); return WOS.wshServerRpcHelper_call("authenticate", data, opts);
} }
// command "controller:restart" [call] // command "controllerinput" [call]
BlockRestartCommand(data: CommandBlockRestartData, opts?: WshRpcCommandOpts): Promise<void> { ControllerInputCommand(data: CommandBlockInputData, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("controller:restart", data, opts); return WOS.wshServerRpcHelper_call("controllerinput", data, opts);
}
// command "controllerrestart" [call]
ControllerRestartCommand(data: CommandBlockRestartData, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("controllerrestart", data, opts);
} }
// command "createblock" [call] // command "createblock" [call]
@@ -27,24 +32,49 @@ class WshServerType {
return WOS.wshServerRpcHelper_call("deleteblock", data, opts); return WOS.wshServerRpcHelper_call("deleteblock", data, opts);
} }
// command "file:append" [call] // command "eventpublish" [call]
AppendFileCommand(data: CommandFileData, opts?: WshRpcCommandOpts): Promise<void> { EventPublishCommand(data: WaveEvent, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("file:append", data, opts); return WOS.wshServerRpcHelper_call("eventpublish", data, opts);
} }
// command "file:appendijson" [call] // command "eventrecv" [call]
AppendIJsonCommand(data: CommandAppendIJsonData, opts?: WshRpcCommandOpts): Promise<void> { EventRecvCommand(data: WaveEvent, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("file:appendijson", data, opts); return WOS.wshServerRpcHelper_call("eventrecv", data, opts);
} }
// command "file:read" [call] // command "eventsub" [call]
ReadFile(data: CommandFileData, opts?: WshRpcCommandOpts): Promise<string> { EventSubCommand(data: SubscriptionRequest, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("file:read", data, opts); return WOS.wshServerRpcHelper_call("eventsub", data, opts);
} }
// command "file:write" [call] // command "eventunsub" [call]
WriteFile(data: CommandFileData, opts?: WshRpcCommandOpts): Promise<void> { EventUnsubCommand(data: SubscriptionRequest, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("file:write", data, opts); return WOS.wshServerRpcHelper_call("eventunsub", data, opts);
}
// command "eventunsuball" [call]
EventUnsubAllCommand(opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("eventunsuball", null, opts);
}
// command "fileappend" [call]
FileAppendCommand(data: CommandFileData, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("fileappend", data, opts);
}
// command "fileappendijson" [call]
FileAppendIJsonCommand(data: CommandAppendIJsonData, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("fileappendijson", data, opts);
}
// command "fileread" [call]
FileReadCommand(data: CommandFileData, opts?: WshRpcCommandOpts): Promise<string> {
return WOS.wshServerRpcHelper_call("fileread", data, opts);
}
// command "filewrite" [call]
FileWriteCommand(data: CommandFileData, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("filewrite", data, opts);
} }
// command "getmeta" [call] // command "getmeta" [call]
@@ -68,18 +98,18 @@ class WshServerType {
} }
// command "setview" [call] // command "setview" [call]
BlockSetViewCommand(data: CommandBlockSetViewData, opts?: WshRpcCommandOpts): Promise<void> { SetViewCommand(data: CommandBlockSetViewData, opts?: WshRpcCommandOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("setview", data, opts); return WOS.wshServerRpcHelper_call("setview", data, opts);
} }
// command "stream:waveai" [responsestream] // command "streamtest" [responsestream]
RespStreamWaveAi(data: OpenAiStreamRequest, opts?: WshRpcCommandOpts): AsyncGenerator<OpenAIPacketType, void, boolean> { StreamTestCommand(opts?: WshRpcCommandOpts): AsyncGenerator<number, void, boolean> {
return WOS.wshServerRpcHelper_responsestream("stream:waveai", data, opts); return WOS.wshServerRpcHelper_responsestream("streamtest", null, opts);
} }
// command "streamtest" [responsestream] // command "streamwaveai" [responsestream]
RespStreamTest(opts?: WshRpcCommandOpts): AsyncGenerator<number, void, boolean> { StreamWaveAiCommand(data: OpenAiStreamRequest, opts?: WshRpcCommandOpts): AsyncGenerator<OpenAIPacketType, void, boolean> {
return WOS.wshServerRpcHelper_responsestream("streamtest", null, opts); return WOS.wshServerRpcHelper_responsestream("streamwaveai", data, opts);
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@
--title-font-size: 18px; --title-font-size: 18px;
--secondary-text-color: rgb(195, 200, 194); --secondary-text-color: rgb(195, 200, 194);
--grey-text-color: #666; --grey-text-color: #666;
--main-bg-color: #454444; --main-bg-color: rgb(34, 34, 34);
--border-color: #333333; --border-color: #333333;
--base-font: normal 14px / normal "Inter", sans-serif; --base-font: normal 14px / normal "Inter", sans-serif;
--fixed-font: normal 12px / normal "Hack", monospace; --fixed-font: normal 12px / normal "Hack", monospace;
@@ -19,7 +19,7 @@
--warning-color: rgb(224, 185, 86); --warning-color: rgb(224, 185, 86);
--success-color: rgb(78, 154, 6); --success-color: rgb(78, 154, 6);
--hover-bg-color: rgba(255, 255, 255, 0.1); --hover-bg-color: rgba(255, 255, 255, 0.1);
--block-bg-color: rgba(255, 255, 255, 0.05); --block-bg-color: rgba(0, 0, 0, 0.5);
/* scrollbar colors */ /* scrollbar colors */
--scrollbar-background-color: transparent; --scrollbar-background-color: transparent;
+2 -2
View File
@@ -214,7 +214,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
} }
if (shellProcStatusRef.current != "running" && keyutil.checkKeyPressed(waveEvent, "Enter")) { if (shellProcStatusRef.current != "running" && keyutil.checkKeyPressed(waveEvent, "Enter")) {
// restart // restart
WshServer.BlockRestartCommand({ blockid: blockId }); WshServer.ControllerRestartCommand({ blockid: blockId });
return false; return false;
} }
} }
@@ -263,7 +263,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
return false; return false;
} }
const b64data = btoa(asciiVal); const b64data = btoa(asciiVal);
WshServer.BlockInputCommand({ blockid: blockId, inputdata64: b64data }); WshServer.ControllerInputCommand({ blockid: blockId, inputdata64: b64data });
return true; return true;
}; };
+1 -1
View File
@@ -76,7 +76,7 @@ export class TermWrap {
handleTermData(data: string) { handleTermData(data: string) {
const b64data = btoa(data); const b64data = btoa(data);
WshServer.BlockInputCommand({ blockid: this.blockId, inputdata64: b64data }); WshServer.ControllerInputCommand({ blockid: this.blockId, inputdata64: b64data });
} }
addFocusListener(focusFn: () => void) { addFocusListener(focusFn: () => void) {
+1 -1
View File
@@ -137,7 +137,7 @@ export class WaveAiModel implements ViewModel {
opts: opts, opts: opts,
prompt: prompt, prompt: prompt,
}; };
const aiGen = WshServer.RespStreamWaveAi(beMsg); const aiGen = WshServer.StreamWaveAiCommand(beMsg);
let temp = async () => { let temp = async () => {
let fullMsg = ""; let fullMsg = "";
for await (const msg of aiGen) { for await (const msg of aiGen) {
+28 -5
View File
@@ -187,7 +187,7 @@ declare global {
// waveobj.ORef // waveobj.ORef
type ORef = string; type ORef = string;
// waveai.OpenAIOptsType // wshrpc.OpenAIOptsType
type OpenAIOptsType = { type OpenAIOptsType = {
model: string; model: string;
apitoken: string; apitoken: string;
@@ -197,7 +197,7 @@ declare global {
timeout?: number; timeout?: number;
}; };
// waveai.OpenAIPacketType // wshrpc.OpenAIPacketType
type OpenAIPacketType = { type OpenAIPacketType = {
type: string; type: string;
model?: string; model?: string;
@@ -209,21 +209,21 @@ declare global {
error?: string; error?: string;
}; };
// waveai.OpenAIPromptMessageType // wshrpc.OpenAIPromptMessageType
type OpenAIPromptMessageType = { type OpenAIPromptMessageType = {
role: string; role: string;
content: string; content: string;
name?: string; name?: string;
}; };
// waveai.OpenAIUsageType // wshrpc.OpenAIUsageType
type OpenAIUsageType = { type OpenAIUsageType = {
prompt_tokens?: number; prompt_tokens?: number;
completion_tokens?: number; completion_tokens?: number;
total_tokens?: number; total_tokens?: number;
}; };
// waveai.OpenAiStreamRequest // wshrpc.OpenAiStreamRequest
type OpenAiStreamRequest = { type OpenAiStreamRequest = {
clientid?: string; clientid?: string;
opts: OpenAIOptsType; opts: OpenAIOptsType;
@@ -270,6 +270,7 @@ declare global {
blockheader: BlockHeaderOpts; blockheader: BlockHeaderOpts;
autoupdate: AutoUpdateOpts; autoupdate: AutoUpdateOpts;
termthemes: {[key: string]: TermThemeType}; termthemes: {[key: string]: TermThemeType};
window: WindowSettingsType;
}; };
// wstore.StickerClickOptsType // wstore.StickerClickOptsType
@@ -293,6 +294,13 @@ declare global {
display: StickerDisplayOptsType; display: StickerDisplayOptsType;
}; };
// wshrpc.SubscriptionRequest
type SubscriptionRequest = {
event: string;
scopes?: string[];
allscopes?: boolean;
};
// wstore.Tab // wstore.Tab
type Tab = WaveObj & { type Tab = WaveObj & {
name: string; name: string;
@@ -428,6 +436,14 @@ declare global {
error: string; error: string;
}; };
// wshrpc.WaveEvent
type WaveEvent = {
event: string;
scopes?: string[];
sender?: string;
data?: any;
};
// filestore.WaveFile // filestore.WaveFile
type WaveFile = { type WaveFile = {
zoneid: string; zoneid: string;
@@ -497,6 +513,13 @@ declare global {
height: number; height: number;
}; };
// wconfig.WindowSettingsType
type WindowSettingsType = {
transparent: boolean;
opacity: number;
bgcolor: string;
};
// wstore.Workspace // wstore.Workspace
type Workspace = WaveObj & { type Workspace = WaveObj & {
name: string; name: string;
+5
View File
@@ -34,6 +34,10 @@ function base64ToArray(b64: string): Uint8Array {
return rtnArr; return rtnArr;
} }
function boundNumber(num: number, min: number, max: number): number {
return Math.min(Math.max(num, min), max);
}
// works for json-like objects (arrays, objects, strings, numbers, booleans) // works for json-like objects (arrays, objects, strings, numbers, booleans)
function jsonDeepEqual(v1: any, v2: any): boolean { function jsonDeepEqual(v1: any, v2: any): boolean {
if (v1 === v2) { if (v1 === v2) {
@@ -193,6 +197,7 @@ function getCrypto() {
export { export {
base64ToArray, base64ToArray,
base64ToString, base64ToString,
boundNumber,
fireAndForget, fireAndForget,
getCrypto, getCrypto,
getPromiseState, getPromiseState,
+1
View File
@@ -27,6 +27,7 @@ loadFonts();
(window as any).globalWS = globalWS; (window as any).globalWS = globalWS;
(window as any).WOS = WOS; (window as any).WOS = WOS;
(window as any).globalStore = globalStore; (window as any).globalStore = globalStore;
(window as any).globalAtoms = atoms;
(window as any).WshServer = WshServer; (window as any).WshServer = WshServer;
(window as any).isFullScreen = false; (window as any).isFullScreen = false;
+2
View File
@@ -76,11 +76,13 @@
"@table-nav/core": "^0.0.7", "@table-nav/core": "^0.0.7",
"@table-nav/react": "^0.0.7", "@table-nav/react": "^0.0.7",
"@tanstack/react-table": "^8.19.3", "@tanstack/react-table": "^8.19.3",
"@types/color": "^3.0.6",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0", "@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"color": "^4.2.3",
"dayjs": "^1.11.12", "dayjs": "^1.11.12",
"electron-updater": "6.3.1", "electron-updater": "6.3.1",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
+71 -8
View File
@@ -6,7 +6,9 @@ package blockcontroller
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -21,12 +23,13 @@ import (
"github.com/wavetermdev/thenextwave/pkg/shellexec" "github.com/wavetermdev/thenextwave/pkg/shellexec"
"github.com/wavetermdev/thenextwave/pkg/wavebase" "github.com/wavetermdev/thenextwave/pkg/wavebase"
"github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshutil" "github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wstore" "github.com/wavetermdev/thenextwave/pkg/wstore"
) )
// set by main-server.go (for dependency inversion) // set by main-server.go (for dependency inversion)
var WshServerFactoryFn func(inputCh chan []byte, outputCh chan []byte, initialCtx wshutil.RpcContext) = nil var WshServerFactoryFn func(inputCh chan []byte, outputCh chan []byte, initialCtx wshrpc.RpcContext) = nil
const ( const (
BlockController_Shell = "shell" BlockController_Shell = "shell"
@@ -205,6 +208,46 @@ func (bc *BlockController) resetTerminalState() {
} }
} }
func getMetaBool(meta map[string]any, key string, def bool) bool {
val, found := meta[key]
if !found {
return def
}
if val == nil {
return def
}
if bval, ok := val.(bool); ok {
return bval
}
return def
}
func getMetaStr(meta map[string]any, key string, def string) string {
val, found := meta[key]
if !found {
return def
}
if val == nil {
return def
}
if sval, ok := val.(string); ok {
return sval
}
return def
}
// every byte is 4-bits of randomness
func randomHexString(numHexDigits int) (string, error) {
numBytes := (numHexDigits + 1) / 2 // Calculate the number of bytes needed
bytes := make([]byte, numBytes)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
hexStr := hex.EncodeToString(bytes)
return hexStr[:numHexDigits], nil // Return the exact number of hex digits
}
func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[string]any) error { func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[string]any) error {
// create a circular blockfile for the output // create a circular blockfile for the output
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
@@ -232,12 +275,35 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
if shellProcErr != nil { if shellProcErr != nil {
return shellProcErr return shellProcErr
} }
var remoteDomainSocketName string
remoteName := getMetaStr(blockMeta, "connection", "")
isRemote := remoteName != ""
if isRemote {
randStr, err := randomHexString(16) // 64-bits of randomness
if err != nil {
return fmt.Errorf("error generating random string: %w", err)
}
remoteDomainSocketName = fmt.Sprintf("/tmp/waveterm-%s.sock", randStr)
}
var cmdStr string var cmdStr string
cmdOpts := shellexec.CommandOptsType{ cmdOpts := shellexec.CommandOptsType{
Env: make(map[string]string), Env: make(map[string]string),
} }
// temporary for blockid (will switch to a JWT at some point) if !getMetaBool(blockMeta, "nowsh", false) {
cmdOpts.Env["LC_WAVETERM_BLOCKID"] = bc.BlockId if isRemote {
jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, remoteDomainSocketName)
if err != nil {
return fmt.Errorf("error making jwt token: %w", err)
}
cmdOpts.Env["WAVETERM_JWT"] = jwtStr
} else {
jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, wavebase.GetDomainSocketName())
if err != nil {
return fmt.Errorf("error making jwt token: %w", err)
}
cmdOpts.Env["WAVETERM_JWT"] = jwtStr
}
}
if bc.ControllerType == BlockController_Shell { if bc.ControllerType == BlockController_Shell {
cmdOpts.Interactive = true cmdOpts.Interactive = true
cmdOpts.Login = true cmdOpts.Login = true
@@ -284,11 +350,8 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
} else { } else {
return fmt.Errorf("unknown controller type %q", bc.ControllerType) return fmt.Errorf("unknown controller type %q", bc.ControllerType)
} }
// pty buffer equivalent for ssh? i think if i have the ecmd or session i can manage it with output
// pty write needs stdin, so if i provide that, i might be able to write that way
// need a way to handle setsize???
var shellProc *shellexec.ShellProc var shellProc *shellexec.ShellProc
if remoteName, ok := blockMeta["connection"].(string); ok && remoteName != "" { if remoteName != "" {
shellProc, err = shellexec.StartRemoteShellProc(rc.TermSize, cmdStr, cmdOpts, remoteName) shellProc, err = shellexec.StartRemoteShellProc(rc.TermSize, cmdStr, cmdOpts, remoteName)
if err != nil { if err != nil {
return err return err
@@ -309,7 +372,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
messageCh := make(chan []byte, 32) messageCh := make(chan []byte, 32)
ptyBuffer := wshutil.MakePtyBuffer(wshutil.WaveOSCPrefix, bc.ShellProc.Pty, messageCh) ptyBuffer := wshutil.MakePtyBuffer(wshutil.WaveOSCPrefix, bc.ShellProc.Pty, messageCh)
outputCh := make(chan []byte, 32) outputCh := make(chan []byte, 32)
WshServerFactoryFn(messageCh, outputCh, wshutil.RpcContext{BlockId: bc.BlockId, TabId: bc.TabId}) WshServerFactoryFn(messageCh, outputCh, wshrpc.RpcContext{BlockId: bc.BlockId, TabId: bc.TabId})
go func() { go func() {
// handles regular output from the pty (goes to the blockfile and xterm) // handles regular output from the pty (goes to the blockfile and xterm)
defer func() { defer func() {
+22 -22
View File
@@ -20,7 +20,6 @@ import (
"github.com/wavetermdev/thenextwave/pkg/wconfig" "github.com/wavetermdev/thenextwave/pkg/wconfig"
"github.com/wavetermdev/thenextwave/pkg/web/webcmd" "github.com/wavetermdev/thenextwave/pkg/web/webcmd"
"github.com/wavetermdev/thenextwave/pkg/wshrpc" "github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver"
"github.com/wavetermdev/thenextwave/pkg/wshutil" "github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wstore" "github.com/wavetermdev/thenextwave/pkg/wstore"
) )
@@ -61,10 +60,13 @@ var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem()
var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
var updatesRtnRType = reflect.TypeOf(wstore.UpdatesRtnType{}) var updatesRtnRType = reflect.TypeOf(wstore.UpdatesRtnType{})
var orefRType = reflect.TypeOf((*waveobj.ORef)(nil)).Elem() var orefRType = reflect.TypeOf((*waveobj.ORef)(nil)).Elem()
var wshRpcInterfaceRType = reflect.TypeOf((*wshrpc.WshRpcInterface)(nil)).Elem()
func generateTSMethodTypes(method reflect.Method, tsTypesMap map[reflect.Type]string) error { func generateTSMethodTypes(method reflect.Method, tsTypesMap map[reflect.Type]string, skipFirstArg bool) error {
for idx := 1; idx < method.Type.NumIn(); idx++ { for idx := 0; idx < method.Type.NumIn(); idx++ {
// skip receiver if skipFirstArg && idx == 0 {
continue
}
inType := method.Type.In(idx) inType := method.Type.In(idx)
GenerateTSType(inType, tsTypesMap) GenerateTSType(inType, tsTypesMap)
} }
@@ -159,14 +161,13 @@ var tsRenameMap = map[string]string{
func generateTSTypeInternal(rtype reflect.Type, tsTypesMap map[reflect.Type]string) (string, []reflect.Type) { func generateTSTypeInternal(rtype reflect.Type, tsTypesMap map[reflect.Type]string) (string, []reflect.Type) {
var buf bytes.Buffer var buf bytes.Buffer
waveObjType := reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
tsTypeName := rtype.Name() tsTypeName := rtype.Name()
if tsRename, ok := tsRenameMap[tsTypeName]; ok { if tsRename, ok := tsRenameMap[tsTypeName]; ok {
tsTypeName = tsRename tsTypeName = tsRename
} }
var isWaveObj bool var isWaveObj bool
buf.WriteString(fmt.Sprintf("// %s\n", rtype.String())) buf.WriteString(fmt.Sprintf("// %s\n", rtype.String()))
if rtype.Implements(waveObjType) || reflect.PointerTo(rtype).Implements(waveObjType) { if rtype.Implements(waveObjRType) || reflect.PointerTo(rtype).Implements(waveObjRType) {
isWaveObj = true isWaveObj = true
buf.WriteString(fmt.Sprintf("type %s = WaveObj & {\n", tsTypeName)) buf.WriteString(fmt.Sprintf("type %s = WaveObj & {\n", tsTypeName))
} else { } else {
@@ -253,6 +254,9 @@ func GenerateTSType(rtype reflect.Type, tsTypesMap map[reflect.Type]string) {
if rtype == nil { if rtype == nil {
return return
} }
if rtype.Kind() == reflect.Chan {
rtype = rtype.Elem()
}
if rtype == metaRType { if rtype == metaRType {
tsTypesMap[metaRType] = GenerateMetaType() tsTypesMap[metaRType] = GenerateMetaType()
return return
@@ -397,17 +401,17 @@ func GenerateServiceClass(serviceName string, serviceObj any, tsTypesMap map[ref
return sb.String() return sb.String()
} }
func GenerateWshServerMethod(methodDecl *wshserver.WshServerMethodDecl, tsTypesMap map[reflect.Type]string) string { func GenerateWshServerMethod(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMap map[reflect.Type]string) string {
if methodDecl.CommandType == wshutil.RpcType_ResponseStream { if methodDecl.CommandType == wshrpc.RpcType_ResponseStream {
return GenerateWshServerMethod_ResponseStream(methodDecl, tsTypesMap) return GenerateWshServerMethod_ResponseStream(methodDecl, tsTypesMap)
} else if methodDecl.CommandType == wshutil.RpcType_Call { } else if methodDecl.CommandType == wshrpc.RpcType_Call {
return GenerateWshServerMethod_Call(methodDecl, tsTypesMap) return GenerateWshServerMethod_Call(methodDecl, tsTypesMap)
} else { } else {
panic(fmt.Sprintf("cannot generate wshserver commandtype %q", methodDecl.CommandType)) panic(fmt.Sprintf("cannot generate wshserver commandtype %q", methodDecl.CommandType))
} }
} }
func GenerateWshServerMethod_ResponseStream(methodDecl *wshserver.WshServerMethodDecl, tsTypesMap map[reflect.Type]string) string { func GenerateWshServerMethod_ResponseStream(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMap map[reflect.Type]string) string {
var sb strings.Builder var sb strings.Builder
sb.WriteString(fmt.Sprintf(" // command %q [%s]\n", methodDecl.Command, methodDecl.CommandType)) sb.WriteString(fmt.Sprintf(" // command %q [%s]\n", methodDecl.Command, methodDecl.CommandType))
respType := "any" respType := "any"
@@ -429,7 +433,7 @@ func GenerateWshServerMethod_ResponseStream(methodDecl *wshserver.WshServerMetho
return sb.String() return sb.String()
} }
func GenerateWshServerMethod_Call(methodDecl *wshserver.WshServerMethodDecl, tsTypesMap map[reflect.Type]string) string { func GenerateWshServerMethod_Call(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMap map[reflect.Type]string) string {
var sb strings.Builder var sb strings.Builder
sb.WriteString(fmt.Sprintf(" // command %q [%s]\n", methodDecl.Command, methodDecl.CommandType)) sb.WriteString(fmt.Sprintf(" // command %q [%s]\n", methodDecl.Command, methodDecl.CommandType))
rtnType := "Promise<void>" rtnType := "Promise<void>"
@@ -469,7 +473,7 @@ func GenerateServiceTypes(tsTypesMap map[reflect.Type]string) error {
serviceType := reflect.TypeOf(serviceObj) serviceType := reflect.TypeOf(serviceObj)
for midx := 0; midx < serviceType.NumMethod(); midx++ { for midx := 0; midx < serviceType.NumMethod(); midx++ {
method := serviceType.Method(midx) method := serviceType.Method(midx)
err := generateTSMethodTypes(method, tsTypesMap) err := generateTSMethodTypes(method, tsTypesMap, true)
if err != nil { if err != nil {
return fmt.Errorf("error generating TS method types for %s.%s: %v", serviceType, method.Name, err) return fmt.Errorf("error generating TS method types for %s.%s: %v", serviceType, method.Name, err)
} }
@@ -480,16 +484,12 @@ func GenerateServiceTypes(tsTypesMap map[reflect.Type]string) error {
func GenerateWshServerTypes(tsTypesMap map[reflect.Type]string) error { func GenerateWshServerTypes(tsTypesMap map[reflect.Type]string) error {
GenerateTSType(reflect.TypeOf(wshrpc.WshRpcCommandOpts{}), tsTypesMap) GenerateTSType(reflect.TypeOf(wshrpc.WshRpcCommandOpts{}), tsTypesMap)
for _, methodDecl := range wshserver.WshServerCommandToDeclMap { rtype := wshRpcInterfaceRType
GenerateTSType(methodDecl.CommandDataType, tsTypesMap) for midx := 0; midx < rtype.NumMethod(); midx++ {
if methodDecl.DefaultResponseDataType != nil { method := rtype.Method(midx)
GenerateTSType(methodDecl.DefaultResponseDataType, tsTypesMap) err := generateTSMethodTypes(method, tsTypesMap, false)
} if err != nil {
for _, rtype := range methodDecl.RequestDataTypes { return fmt.Errorf("error generating TS method types for %s.%s: %v", rtype, method.Name, err)
GenerateTSType(rtype, tsTypesMap)
}
for _, rtype := range methodDecl.ResponseDataTypes {
GenerateTSType(rtype, tsTypesMap)
} }
} }
return nil return nil
+26
View File
@@ -800,3 +800,29 @@ func MoveSliceIdxToFront[T any](arr []T, idx int) []T {
rtn = append(rtn, arr[idx+1:]...) rtn = append(rtn, arr[idx+1:]...)
return rtn return rtn
} }
// matches a delimited string with a pattern string
// the pattern string can contain "*" to match a single part, or "**" to match the rest of the string
// note that "**" may only appear at the end of the string
func StarMatchString(pattern string, s string, delimiter string) bool {
patternParts := strings.Split(pattern, delimiter)
stringParts := strings.Split(s, delimiter)
pLen, sLen := len(patternParts), len(stringParts)
for i := 0; i < pLen; i++ {
if patternParts[i] == "**" {
// '**' must be at the end to be valid
return i == pLen-1
}
if i >= sLen {
// If string is exhausted but pattern is not
return false
}
if patternParts[i] != "*" && patternParts[i] != stringParts[i] {
// If current parts don't match and pattern part is not '*'
return false
}
}
// Check if both pattern and string are fully matched
return pLen == sLen
}
+41 -79
View File
@@ -23,12 +23,6 @@ const OpenAIPacketStr = "openai"
const OpenAICloudReqStr = "openai-cloudreq" const OpenAICloudReqStr = "openai-cloudreq"
const PacketEOFStr = "EOF" const PacketEOFStr = "EOF"
type OpenAIUsageType struct {
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
TotalTokens int `json:"total_tokens,omitempty"`
}
type OpenAICmdInfoPacketOutputType struct { type OpenAICmdInfoPacketOutputType struct {
Model string `json:"model,omitempty"` Model string `json:"model,omitempty"`
Created int64 `json:"created,omitempty"` Created int64 `json:"created,omitempty"`
@@ -37,19 +31,8 @@ type OpenAICmdInfoPacketOutputType struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
} }
type OpenAIPacketType struct { func MakeOpenAIPacket() *wshrpc.OpenAIPacketType {
Type string `json:"type"` return &wshrpc.OpenAIPacketType{Type: OpenAIPacketStr}
Model string `json:"model,omitempty"`
Created int64 `json:"created,omitempty"`
FinishReason string `json:"finish_reason,omitempty"`
Usage *OpenAIUsageType `json:"usage,omitempty"`
Index int `json:"index,omitempty"`
Text string `json:"text,omitempty"`
Error string `json:"error,omitempty"`
}
func MakeOpenAIPacket() *OpenAIPacketType {
return &OpenAIPacketType{Type: OpenAIPacketStr}
} }
type OpenAICmdInfoChatMessage struct { type OpenAICmdInfoChatMessage struct {
@@ -60,27 +43,12 @@ type OpenAICmdInfoChatMessage struct {
UserEngineeredQuery string `json:"userengineeredquery,omitempty"` UserEngineeredQuery string `json:"userengineeredquery,omitempty"`
} }
type OpenAIPromptMessageType struct {
Role string `json:"role"`
Content string `json:"content"`
Name string `json:"name,omitempty"`
}
type OpenAICloudReqPacketType struct { type OpenAICloudReqPacketType struct {
Type string `json:"type"` Type string `json:"type"`
ClientId string `json:"clientid"` ClientId string `json:"clientid"`
Prompt []OpenAIPromptMessageType `json:"prompt"` Prompt []wshrpc.OpenAIPromptMessageType `json:"prompt"`
MaxTokens int `json:"maxtokens,omitempty"` MaxTokens int `json:"maxtokens,omitempty"`
MaxChoices int `json:"maxchoices,omitempty"` MaxChoices int `json:"maxchoices,omitempty"`
}
type OpenAIOptsType struct {
Model string `json:"model"`
APIToken string `json:"apitoken"`
BaseURL string `json:"baseurl,omitempty"`
MaxTokens int `json:"maxtokens,omitempty"`
MaxChoices int `json:"maxchoices,omitempty"`
Timeout int `json:"timeout,omitempty"`
} }
func MakeOpenAICloudReqPacket() *OpenAICloudReqPacketType { func MakeOpenAICloudReqPacket() *OpenAICloudReqPacketType {
@@ -89,12 +57,6 @@ func MakeOpenAICloudReqPacket() *OpenAICloudReqPacketType {
} }
} }
type OpenAiStreamRequest struct {
ClientId string `json:"clientid,omitempty"`
Opts *OpenAIOptsType `json:"opts"`
Prompt []OpenAIPromptMessageType `json:"prompt"`
}
func GetWSEndpoint() string { func GetWSEndpoint() string {
return PCloudWSEndpoint return PCloudWSEndpoint
if !wavebase.IsDevMode() { if !wavebase.IsDevMode() {
@@ -116,18 +78,18 @@ const PCloudWSEndpointVarName = "PCLOUD_WS_ENDPOINT"
const CloudWebsocketConnectTimeout = 1 * time.Minute const CloudWebsocketConnectTimeout = 1 * time.Minute
func convertUsage(resp openaiapi.ChatCompletionResponse) *OpenAIUsageType { func convertUsage(resp openaiapi.ChatCompletionResponse) *wshrpc.OpenAIUsageType {
if resp.Usage.TotalTokens == 0 { if resp.Usage.TotalTokens == 0 {
return nil return nil
} }
return &OpenAIUsageType{ return &wshrpc.OpenAIUsageType{
PromptTokens: resp.Usage.PromptTokens, PromptTokens: resp.Usage.PromptTokens,
CompletionTokens: resp.Usage.CompletionTokens, CompletionTokens: resp.Usage.CompletionTokens,
TotalTokens: resp.Usage.TotalTokens, TotalTokens: resp.Usage.TotalTokens,
} }
} }
func ConvertPrompt(prompt []OpenAIPromptMessageType) []openaiapi.ChatCompletionMessage { func ConvertPrompt(prompt []wshrpc.OpenAIPromptMessageType) []openaiapi.ChatCompletionMessage {
var rtn []openaiapi.ChatCompletionMessage var rtn []openaiapi.ChatCompletionMessage
for _, p := range prompt { for _, p := range prompt {
msg := openaiapi.ChatCompletionMessage{Role: p.Role, Content: p.Content, Name: p.Name} msg := openaiapi.ChatCompletionMessage{Role: p.Role, Content: p.Content, Name: p.Name}
@@ -136,31 +98,31 @@ func ConvertPrompt(prompt []OpenAIPromptMessageType) []openaiapi.ChatCompletionM
return rtn return rtn
} }
func RunCloudCompletionStream(ctx context.Context, request OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[OpenAIPacketType] { func RunCloudCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
rtn := make(chan wshrpc.RespOrErrorUnion[OpenAIPacketType]) rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType])
go func() { go func() {
log.Printf("start: %v", request) log.Printf("start: %v", request)
defer close(rtn) defer close(rtn)
if request.Opts == nil { if request.Opts == nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("no openai opts found")} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("no openai opts found")}
return return
} }
websocketContext, dialCancelFn := context.WithTimeout(context.Background(), CloudWebsocketConnectTimeout) websocketContext, dialCancelFn := context.WithTimeout(context.Background(), CloudWebsocketConnectTimeout)
defer dialCancelFn() defer dialCancelFn()
conn, _, err := websocket.DefaultDialer.DialContext(websocketContext, GetWSEndpoint(), nil) conn, _, err := websocket.DefaultDialer.DialContext(websocketContext, GetWSEndpoint(), nil)
if err == context.DeadlineExceeded {
rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, timed out connecting to cloud server: %v", err)}
return
} else if err != nil {
rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket connect error: %v", err)}
return
}
defer func() { defer func() {
err = conn.Close() err = conn.Close()
if err != nil { if err != nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("unable to close openai channel: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("unable to close openai channel: %v", err)}
} }
}() }()
if err == context.DeadlineExceeded {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, timed out connecting to cloud server: %v", err)}
return
} else if err != nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket connect error: %v", err)}
return
}
reqPk := MakeOpenAICloudReqPacket() reqPk := MakeOpenAICloudReqPacket()
reqPk.ClientId = request.ClientId reqPk.ClientId = request.ClientId
reqPk.Prompt = request.Prompt reqPk.Prompt = request.Prompt
@@ -168,12 +130,12 @@ func RunCloudCompletionStream(ctx context.Context, request OpenAiStreamRequest)
reqPk.MaxChoices = request.Opts.MaxChoices reqPk.MaxChoices = request.Opts.MaxChoices
configMessageBuf, err := json.Marshal(reqPk) configMessageBuf, err := json.Marshal(reqPk)
if err != nil { if err != nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, packet marshal error: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, packet marshal error: %v", err)}
return return
} }
err = conn.WriteMessage(websocket.TextMessage, configMessageBuf) err = conn.WriteMessage(websocket.TextMessage, configMessageBuf)
if err != nil { if err != nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket write config error: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket write config error: %v", err)}
return return
} }
for { for {
@@ -184,14 +146,14 @@ func RunCloudCompletionStream(ctx context.Context, request OpenAiStreamRequest)
} }
if err != nil { if err != nil {
log.Printf("err received: %v", err) log.Printf("err received: %v", err)
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket error reading message: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket error reading message: %v", err)}
break break
} }
var streamResp *OpenAIPacketType var streamResp *wshrpc.OpenAIPacketType
err = json.Unmarshal(socketMessage, &streamResp) err = json.Unmarshal(socketMessage, &streamResp)
log.Printf("ai resp: %v", streamResp) log.Printf("ai resp: %v", streamResp)
if err != nil { if err != nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket response json decode error: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket response json decode error: %v", err)}
break break
} }
if streamResp.Error == PacketEOFStr { if streamResp.Error == PacketEOFStr {
@@ -199,30 +161,30 @@ func RunCloudCompletionStream(ctx context.Context, request OpenAiStreamRequest)
break break
} else if streamResp.Error != "" { } else if streamResp.Error != "" {
// use error from server directly // use error from server directly
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("%v", streamResp.Error)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("%v", streamResp.Error)}
break break
} }
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Response: *streamResp} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Response: *streamResp}
} }
}() }()
return rtn return rtn
} }
func RunLocalCompletionStream(ctx context.Context, request OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[OpenAIPacketType] { func RunLocalCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
rtn := make(chan wshrpc.RespOrErrorUnion[OpenAIPacketType]) rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType])
go func() { go func() {
log.Printf("start2: %v", request) log.Printf("start2: %v", request)
defer close(rtn) defer close(rtn)
if request.Opts == nil { if request.Opts == nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("no openai opts found")} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("no openai opts found")}
return return
} }
if request.Opts.Model == "" { if request.Opts.Model == "" {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("no openai model specified")} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("no openai model specified")}
return return
} }
if request.Opts.BaseURL == "" && request.Opts.APIToken == "" { if request.Opts.BaseURL == "" && request.Opts.APIToken == "" {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("no api token")} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("no api token")}
return return
} }
clientConfig := openaiapi.DefaultConfig(request.Opts.APIToken) clientConfig := openaiapi.DefaultConfig(request.Opts.APIToken)
@@ -241,7 +203,7 @@ func RunLocalCompletionStream(ctx context.Context, request OpenAiStreamRequest)
} }
apiResp, err := client.CreateChatCompletionStream(ctx, req) apiResp, err := client.CreateChatCompletionStream(ctx, req)
if err != nil { if err != nil {
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("error calling openai API: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("error calling openai API: %v", err)}
return return
} }
sentHeader := false sentHeader := false
@@ -253,14 +215,14 @@ func RunLocalCompletionStream(ctx context.Context, request OpenAiStreamRequest)
} }
if err != nil { if err != nil {
log.Printf("err received2: %v", err) log.Printf("err received2: %v", err)
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket error reading message: %v", err)} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("OpenAI request, websocket error reading message: %v", err)}
break break
} }
if streamResp.Model != "" && !sentHeader { if streamResp.Model != "" && !sentHeader {
pk := MakeOpenAIPacket() pk := MakeOpenAIPacket()
pk.Model = streamResp.Model pk.Model = streamResp.Model
pk.Created = streamResp.Created pk.Created = streamResp.Created
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Response: *pk} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Response: *pk}
sentHeader = true sentHeader = true
} }
for _, choice := range streamResp.Choices { for _, choice := range streamResp.Choices {
@@ -268,15 +230,15 @@ func RunLocalCompletionStream(ctx context.Context, request OpenAiStreamRequest)
pk.Index = choice.Index pk.Index = choice.Index
pk.Text = choice.Delta.Content pk.Text = choice.Delta.Content
pk.FinishReason = string(choice.FinishReason) pk.FinishReason = string(choice.FinishReason)
rtn <- wshrpc.RespOrErrorUnion[OpenAIPacketType]{Response: *pk} rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Response: *pk}
} }
} }
}() }()
return rtn return rtn
} }
func marshalResponse(resp openaiapi.ChatCompletionResponse) []*OpenAIPacketType { func marshalResponse(resp openaiapi.ChatCompletionResponse) []*wshrpc.OpenAIPacketType {
var rtn []*OpenAIPacketType var rtn []*wshrpc.OpenAIPacketType
headerPk := MakeOpenAIPacket() headerPk := MakeOpenAIPacket()
headerPk.Model = resp.Model headerPk.Model = resp.Model
headerPk.Created = resp.Created headerPk.Created = resp.Created
@@ -292,14 +254,14 @@ func marshalResponse(resp openaiapi.ChatCompletionResponse) []*OpenAIPacketType
return rtn return rtn
} }
func CreateErrorPacket(errStr string) *OpenAIPacketType { func CreateErrorPacket(errStr string) *wshrpc.OpenAIPacketType {
errPk := MakeOpenAIPacket() errPk := MakeOpenAIPacket()
errPk.FinishReason = "error" errPk.FinishReason = "error"
errPk.Error = errStr errPk.Error = errStr
return errPk return errPk
} }
func CreateTextPacket(text string) *OpenAIPacketType { func CreateTextPacket(text string) *wshrpc.OpenAIPacketType {
pk := MakeOpenAIPacket() pk := MakeOpenAIPacket()
pk.Text = text pk.Text = text
return pk return pk

Some files were not shown because too many files have changed in this diff Show More