From 8ae6e47d9b66355022378f7c7c8a80341bffaa3a Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 19 Dec 2024 10:35:50 -0800 Subject: [PATCH 01/33] fix blockstore panic (#1570) --- pkg/filestore/blockstore_cache.go | 3 +++ pkg/wshutil/wshrpc.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/filestore/blockstore_cache.go b/pkg/filestore/blockstore_cache.go index f8608654b..ab48e7a83 100644 --- a/pkg/filestore/blockstore_cache.go +++ b/pkg/filestore/blockstore_cache.go @@ -227,6 +227,9 @@ func (entry *CacheEntry) readAt(ctx context.Context, offset int64, size int64, r offset += truncateAmt size -= truncateAmt } + if size <= 0 { + return realDataOffset, nil, nil + } } partMap := file.computePartMap(offset, size) dataEntryMap, err := entry.loadDataPartsForRead(ctx, getPartIdxsFromMap(partMap)) diff --git a/pkg/wshutil/wshrpc.go b/pkg/wshutil/wshrpc.go index 4bbbbd01e..24bb8d483 100644 --- a/pkg/wshutil/wshrpc.go +++ b/pkg/wshutil/wshrpc.go @@ -342,7 +342,10 @@ func (w *WshRpc) runServer() { continue } if msg.IsRpcRequest() { - go w.handleRequest(&msg) + go func() { + defer panichandler.PanicHandler("handleRequest:goroutine") + w.handleRequest(&msg) + }() } else { respCh := w.getResponseCh(msg.ResId) if respCh == nil { From eff12635d72452355801acd5165caaa955490e7b Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 19 Dec 2024 10:41:28 -0800 Subject: [PATCH 02/33] transparent terminal themes (#1561) --- docs/docs/config.mdx | 2 + docs/docs/keybindings.mdx | 8 +++ frontend/app/view/term/term.tsx | 59 +++++++++++++++- frontend/app/view/term/termtheme.ts | 3 +- frontend/app/view/term/termutil.ts | 22 +++++- frontend/types/gotypes.d.ts | 2 + pkg/waveobj/metaconsts.go | 1 + pkg/waveobj/wtypemeta.go | 1 + pkg/wconfig/defaultconfig/termthemes.json | 86 +++++++++++++++-------- pkg/wconfig/metaconsts.go | 1 + pkg/wconfig/settingsconfig.go | 1 + 11 files changed, 151 insertions(+), 35 deletions(-) diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index c97ef839b..b1c7b89c5 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -45,6 +45,8 @@ wsh editconfig | term:localshellopts | string[] | set to pass additional parameters to the term:localshellpath (example: `["-NoLogo"]` for PowerShell will remove the copyright notice) | | term:copyonselect | bool | set to false to disable terminal copy-on-select | | term:scrollback | int | size of terminal scrollback buffer, max is 10000 | +| term:theme | string | preset name of terminal theme to apply by default (default is "default-dark") | +| term:transparency | float64 | set the background transparency of terminal theme (default 0.5, 0 = not transparent, 1.0 = fully transparent) | | editor:minimapenabled | bool | set to false to disable editor minimap | | editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false | | editor:wordwrap | bool | set to true to enable word wrapping in the editor (defaults to false) | diff --git a/docs/docs/keybindings.mdx b/docs/docs/keybindings.mdx index cfb1569e5..7bd2775d6 100644 --- a/docs/docs/keybindings.mdx +++ b/docs/docs/keybindings.mdx @@ -71,6 +71,14 @@ replace "Cmd" with "Alt" (note that "Ctrl" is "Ctrl" on both Mac, Windows, and L | ---------------- | ------------- | | | Clear AI Chat | +## Terminal Keybindings + +| Key | Function | +| ----------------------- | -------------- | +| | Copy | +| | Paste | +| | Clear Terminal | + ## Customizeable Systemwide Global Hotkey Wave allows setting a custom global hotkey to focus your most recent window from anywhere in your computer. For more information on this, see [the config docs](./config#customizable-systemwide-global-hotkey). diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index a834adfd5..d6ad8eec9 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -24,6 +24,7 @@ import { } from "@/store/global"; import * as services from "@/store/services"; import * as keyutil from "@/util/keyutil"; +import { boundNumber } from "@/util/util"; import clsx from "clsx"; import debug from "debug"; import * as jotai from "jotai"; @@ -62,6 +63,7 @@ class TermViewModel implements ViewModel { vdomToolbarTarget: jotai.PrimitiveAtom; fontSizeAtom: jotai.Atom; termThemeNameAtom: jotai.Atom; + termTransparencyAtom: jotai.Atom; noPadding: jotai.PrimitiveAtom; endIconButtons: jotai.Atom; shellProcFullStatus: jotai.PrimitiveAtom; @@ -203,10 +205,17 @@ class TermViewModel implements ViewModel { return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme; }); }); + this.termTransparencyAtom = useBlockAtom(blockId, "termtransparencyatom", () => { + return jotai.atom((get) => { + let value = get(getOverrideConfigAtom(this.blockId, "term:transparency")) ?? 0.5; + return boundNumber(value, 0, 1); + }); + }); this.blockBg = jotai.atom((get) => { const fullConfig = get(atoms.fullConfigAtom); const themeName = get(this.termThemeNameAtom); - const [_, bgcolor] = computeTheme(fullConfig, themeName); + const termTransparency = get(this.termTransparencyAtom); + const [_, bgcolor] = computeTheme(fullConfig, themeName, termTransparency); if (bgcolor != null) { return { bg: bgcolor }; } @@ -407,6 +416,11 @@ class TermViewModel implements ViewModel { event.preventDefault(); event.stopPropagation(); return false; + } else if (keyutil.checkKeyPressed(waveEvent, "Cmd:k")) { + event.preventDefault(); + event.stopPropagation(); + this.termRef.current?.terminal?.clear(); + return false; } const shellProcStatus = globalStore.get(this.shellProcStatus); if ((shellProcStatus == "done" || shellProcStatus == "init") && keyutil.checkKeyPressed(waveEvent, "Enter")) { @@ -453,6 +467,7 @@ class TermViewModel implements ViewModel { const termThemeKeys = Object.keys(termThemes); const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme")); const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12; + const transparencyMeta = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:transparency")); const blockData = globalStore.get(this.blockAtom); const overrideFontSize = blockData?.meta?.["term:fontsize"]; @@ -474,6 +489,41 @@ class TermViewModel implements ViewModel { checked: curThemeName == null, click: () => this.setTerminalTheme(null), }); + const transparencySubMenu: ContextMenuItem[] = []; + transparencySubMenu.push({ + label: "Default", + type: "checkbox", + checked: transparencyMeta == null, + click: () => { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "term:transparency": null }, + }); + }, + }); + transparencySubMenu.push({ + label: "Transparent Background", + type: "checkbox", + checked: transparencyMeta == 0.5, + click: () => { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "term:transparency": 0.5 }, + }); + }, + }); + transparencySubMenu.push({ + label: "No Transparency", + type: "checkbox", + checked: transparencyMeta == 0, + click: () => { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "term:transparency": 0 }, + }); + }, + }); + const fontSizeSubMenu: ContextMenuItem[] = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].map( (fontSize: number) => { return { @@ -508,6 +558,10 @@ class TermViewModel implements ViewModel { label: "Font Size", submenu: fontSizeSubMenu, }); + fullMenu.push({ + label: "Transparency", + submenu: transparencySubMenu, + }); fullMenu.push({ type: "separator" }); fullMenu.push({ label: "Force Restart Controller", @@ -734,7 +788,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { React.useEffect(() => { const fullConfig = globalStore.get(atoms.fullConfigAtom); const termThemeName = globalStore.get(model.termThemeNameAtom); - const [termTheme, _] = computeTheme(fullConfig, termThemeName); + const termTransparency = globalStore.get(model.termTransparencyAtom); + const [termTheme, _] = computeTheme(fullConfig, termThemeName, termTransparency); let termScrollback = 1000; if (termSettings?.["term:scrollback"]) { termScrollback = Math.floor(termSettings["term:scrollback"]); diff --git a/frontend/app/view/term/termtheme.ts b/frontend/app/view/term/termtheme.ts index 8852ae15a..32937b5be 100644 --- a/frontend/app/view/term/termtheme.ts +++ b/frontend/app/view/term/termtheme.ts @@ -17,7 +17,8 @@ interface TermThemeProps { const TermThemeUpdater = ({ blockId, model, termRef }: TermThemeProps) => { const fullConfig = useAtomValue(atoms.fullConfigAtom); const blockTermTheme = useAtomValue(model.termThemeNameAtom); - const [theme, _] = computeTheme(fullConfig, blockTermTheme); + const transparency = useAtomValue(model.termTransparencyAtom); + const [theme, _] = computeTheme(fullConfig, blockTermTheme, transparency); useEffect(() => { if (termRef.current?.terminal) { termRef.current.terminal.options.theme = theme; diff --git a/frontend/app/view/term/termutil.ts b/frontend/app/view/term/termutil.ts index 1bed0e6d5..6b2eb357c 100644 --- a/frontend/app/view/term/termutil.ts +++ b/frontend/app/view/term/termutil.ts @@ -2,14 +2,32 @@ // SPDX-License-Identifier: Apache-2.0 export const DefaultTermTheme = "default-dark"; +import { colord } from "colord"; -// returns (theme, bgcolor) -function computeTheme(fullConfig: FullConfigType, themeName: string): [TermThemeType, string] { +function applyTransparencyToColor(hexColor: string, transparency: number): string { + const alpha = 1 - transparency; // transparency is already 0-1 + return colord(hexColor).alpha(alpha).toHex(); +} + +// returns (theme, bgcolor, transparency (0 - 1.0)) +function computeTheme( + fullConfig: FullConfigType, + themeName: string, + termTransparency: number +): [TermThemeType, string] { let theme: TermThemeType = fullConfig?.termthemes?.[themeName]; if (theme == null) { theme = fullConfig?.termthemes?.[DefaultTermTheme] || ({} as any); } const themeCopy = { ...theme }; + if (termTransparency != null && termTransparency > 0) { + if (themeCopy.background) { + themeCopy.background = applyTransparencyToColor(themeCopy.background, termTransparency); + } + if (themeCopy.selectionBackground) { + themeCopy.selectionBackground = applyTransparencyToColor(themeCopy.selectionBackground, termTransparency); + } + } let bgcolor = themeCopy.background; themeCopy.background = "#00000000"; return [themeCopy, bgcolor]; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 831c9e734..15838e1ab 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -491,6 +491,7 @@ declare global { "term:scrollback"?: number; "term:vdomblockid"?: string; "term:vdomtoolbarblockid"?: string; + "term:transparency"?: number; "web:zoom"?: number; "markdown:fontsize"?: number; "markdown:fixedfontsize"?: number; @@ -641,6 +642,7 @@ declare global { "term:localshellopts"?: string[]; "term:scrollback"?: number; "term:copyonselect"?: boolean; + "term:transparency"?: number; "editor:minimapenabled"?: boolean; "editor:stickyscrollenabled"?: boolean; "editor:wordwrap"?: boolean; diff --git a/pkg/waveobj/metaconsts.go b/pkg/waveobj/metaconsts.go index 9071bf326..6b65249b6 100644 --- a/pkg/waveobj/metaconsts.go +++ b/pkg/waveobj/metaconsts.go @@ -93,6 +93,7 @@ const ( MetaKey_TermScrollback = "term:scrollback" MetaKey_TermVDomSubBlockId = "term:vdomblockid" MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid" + MetaKey_TermTransparency = "term:transparency" MetaKey_WebZoom = "web:zoom" diff --git a/pkg/waveobj/wtypemeta.go b/pkg/waveobj/wtypemeta.go index 41f0b7be5..bbf30cffb 100644 --- a/pkg/waveobj/wtypemeta.go +++ b/pkg/waveobj/wtypemeta.go @@ -94,6 +94,7 @@ type MetaTSType struct { TermScrollback *int `json:"term:scrollback,omitempty"` TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"` TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"` + TermTransparency *float64 `json:"term:transparency,omitempty"` // default 0.5 WebZoom float64 `json:"web:zoom,omitempty"` diff --git a/pkg/wconfig/defaultconfig/termthemes.json b/pkg/wconfig/defaultconfig/termthemes.json index ea8a5f1a0..6ac212f31 100644 --- a/pkg/wconfig/defaultconfig/termthemes.json +++ b/pkg/wconfig/defaultconfig/termthemes.json @@ -22,12 +22,38 @@ "cmdtext": "#f0f0f0", "foreground": "#c1c1c1", "selectionBackground": "", - "background": "#00000077", + "background": "#000000", "cursor": "" }, + "onedarkpro": { + "display:name": "One Dark Pro", + "display:order": 2, + "background": "#282C34", + "foreground": "#ABB2BF", + "cursor": "#D7DAE0", + "selectionBackground": "#528BFF", + "black": "#3F4451", + "red": "#E05561", + "green": "#8CC265", + "yellow": "#D18F52", + "blue": "#4AA5F0", + "magenta": "#C162DE", + "cyan": "#42B3C2", + "white": "#D7DAE0", + "brightBlack": "#4F5666", + "brightRed": "#FF616E", + "brightGreen": "#A5E075", + "brightYellow": "#F0A45D", + "brightBlue": "#4DC4FF", + "brightMagenta": "#DE73FF", + "brightCyan": "#4CD1E0", + "brightWhite": "#E6E6E6", + "gray": "#495162", + "cmdtext": "#ABB2BF" + }, "dracula": { "display:name": "Dracula", - "display:order": 2, + "display:order": 3, "black": "#21222C", "red": "#FF5555", "green": "#50FA7B", @@ -53,7 +79,7 @@ }, "monokai": { "display:name": "Monokai", - "display:order": 3, + "display:order": 4, "black": "#1B1D1E", "red": "#F92672", "green": "#A6E22E", @@ -79,7 +105,7 @@ }, "campbell": { "display:name": "Campbell", - "display:order": 4, + "display:order": 5, "black": "#0C0C0C", "red": "#C50F1F", "green": "#13A10E", @@ -105,7 +131,7 @@ }, "warmyellow": { "display:name": "Warm Yellow", - "display:order": 4, + "display:order": 6, "black": "#3C3228", "red": "#E67E22", "green": "#A5D6A7", @@ -127,30 +153,30 @@ "selectionBackground": "#B7950B", "cursor": "#F9D784" }, - "onedarkpro": { - "display:name": "One Dark Pro", - "display:order": 1.5, - "background": "#282C34", - "foreground": "#ABB2BF", - "cursor": "#D7DAE0", - "selectionBackground": "#528BFF", - "black": "#3F4451", - "red": "#E05561", - "green": "#8CC265", - "yellow": "#D18F52", - "blue": "#4AA5F0", - "magenta": "#C162DE", - "cyan": "#42B3C2", - "white": "#D7DAE0", - "brightBlack": "#4F5666", - "brightRed": "#FF616E", - "brightGreen": "#A5E075", - "brightYellow": "#F0A45D", - "brightBlue": "#4DC4FF", - "brightMagenta": "#DE73FF", - "brightCyan": "#4CD1E0", - "brightWhite": "#E6E6E6", - "gray": "#495162", - "cmdtext": "#ABB2BF" + "rosepine": { + "display:name": "Rose Pine", + "display:order": 7, + "black": "#26233a", + "red": "#eb6f92", + "green": "#3e8fb0", + "yellow": "#f6c177", + "blue": "#9ccfd8", + "magenta": "#c4a7e7", + "cyan": "#ebbcba", + "white": "#e0def4", + "brightBlack": "#908caa", + "brightRed": "#ff8cab", + "brightGreen": "#9ccfb0", + "brightYellow": "#ffd196", + "brightBlue": "#bee6e0", + "brightMagenta": "#e2c4ff", + "brightCyan": "#ffd1d0", + "brightWhite": "#fffaf3", + "gray": "#908caa", + "cmdtext": "#e0def4", + "foreground": "#e0def4", + "selectionBackground": "#403d52", + "background": "#191724", + "cursor": "#524f67" } } diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index a1ddaeb4a..3197b9ad0 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -33,6 +33,7 @@ const ( ConfigKey_TermLocalShellOpts = "term:localshellopts" ConfigKey_TermScrollback = "term:scrollback" ConfigKey_TermCopyOnSelect = "term:copyonselect" + ConfigKey_TermTransparency = "term:transparency" ConfigKey_EditorMinimapEnabled = "editor:minimapenabled" ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index b1461eb6f..b65a73a05 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -60,6 +60,7 @@ type SettingsType struct { TermLocalShellOpts []string `json:"term:localshellopts,omitempty"` TermScrollback *int64 `json:"term:scrollback,omitempty"` TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"` + TermTransparency *float64 `json:"term:transparency,omitempty"` EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"` EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"` From f12a2266b583681bc27118be2abc840e821eb4fe Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:46:45 +0000 Subject: [PATCH 03/33] chore: bump package version to 0.10.2-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6497727ea..97254ed47 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.2-beta.0", + "version": "0.10.2-beta.1", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From 2e2a6491e88438eacab313a5c3bd524fff199791 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:46:35 -0800 Subject: [PATCH 04/33] SSH Config Panic Handler (#1571) Match statements in files that are included in an ssh config still seem to cause panics with the ssh_config library. This adds a panic handler to catch them, and prevent the app from crashing. It does not resolve the underlying issue which will need to be done later. --- pkg/remote/sshclient.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/remote/sshclient.go b/pkg/remote/sshclient.go index bcc4291c2..3b2b0cc90 100644 --- a/pkg/remote/sshclient.go +++ b/pkg/remote/sshclient.go @@ -23,6 +23,7 @@ import ( "github.com/kevinburke/ssh_config" "github.com/skeema/knownhosts" + "github.com/wavetermdev/waveterm/pkg/panichandler" "github.com/wavetermdev/waveterm/pkg/trimquotes" "github.com/wavetermdev/waveterm/pkg/userinput" "github.com/wavetermdev/waveterm/pkg/util/shellutil" @@ -750,7 +751,13 @@ func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *w // note that a `var == "yes"` will default to false // but `var != "no"` will default to true // when given unexpected strings -func findSshConfigKeywords(hostPattern string) (*wshrpc.ConnKeywords, error) { +func findSshConfigKeywords(hostPattern string) (connKeywords *wshrpc.ConnKeywords, outErr error) { + defer func() { + err := panichandler.PanicHandler("sshclient:find-ssh-config-keywords") + if err != nil { + outErr = err + } + }() WaveSshConfigUserSettings().ReloadConfigs() sshKeywords := &wshrpc.ConnKeywords{} var err error From 12d58c90a74b6f255bbb7aad839b6d9e43c0e15f Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 19 Dec 2024 12:16:03 -0800 Subject: [PATCH 05/33] Make TOS modal buttons larger (#1573) --- frontend/app/modals/tos.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/modals/tos.scss b/frontend/app/modals/tos.scss index 72d8015af..1db743d36 100644 --- a/frontend/app/modals/tos.scss +++ b/frontend/app/modals/tos.scss @@ -230,6 +230,7 @@ justify-content: center; button { + padding: 8px 20px; font-size: 14px; } From 8a22149f2f12a667954bd6b7b0f27e4325cb934f Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 19 Dec 2024 12:41:03 -0800 Subject: [PATCH 06/33] fix markdown last margin (#1575) --- frontend/app/element/markdown.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/app/element/markdown.scss b/frontend/app/element/markdown.scss index 4bee56855..b49cdccfd 100644 --- a/frontend/app/element/markdown.scss +++ b/frontend/app/element/markdown.scss @@ -24,6 +24,10 @@ overflow: hidden; } + *:last-child { + margin-bottom: 0 !important; + } + .heading:not(.heading ~ .heading) { margin-top: 0 !important; } From 76e8bc4bae6f8dd146e92e608449005b34f98d9d Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:43:22 -0800 Subject: [PATCH 07/33] fix: unquote zdotdir for starting zsh in wsl (#1574) Due to the way this command is run, the quotes are not being handled by the shell. Removing them allows them to be interpreted correctly in most cases. This resolves #1569 --- pkg/shellexec/shellexec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go index 2c811d85c..a6fcf00e4 100644 --- a/pkg/shellexec/shellexec.go +++ b/pkg/shellexec/shellexec.go @@ -215,7 +215,7 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st } if isZshShell(shellPath) { - shellOpts = append(shellOpts, fmt.Sprintf(`ZDOTDIR="%s/.waveterm/%s"`, homeDir, shellutil.ZshIntegrationDir)) + shellOpts = append(shellOpts, fmt.Sprintf(`ZDOTDIR=%s/.waveterm/%s`, homeDir, shellutil.ZshIntegrationDir)) } shellOpts = append(shellOpts, shellPath) shellOpts = append(shellOpts, subShellOpts...) From a940de6df6b3175e88d65379fe4f7b093a63f199 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:55:36 -0800 Subject: [PATCH 08/33] fix: use updates ssh_config version with match fix (#1576) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 772c34c62..aa784a472 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,6 @@ require ( golang.org/x/net v0.29.0 // indirect ) -replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20241027232332-ed124367682d +replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34 replace github.com/creack/pty => github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b diff --git a/go.sum b/go.sum index 8429144ec..437167932 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b h1:wFBKF5k5xbJQU8bYgc github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b/go.mod h1:N1CYNinssZru+ikvYTgVbVeSi21thHUTCoJ9xMvWe+s= github.com/wavetermdev/htmltoken v0.2.0 h1:sFVPPemlDv7/jg7n4Hx1AEF2m9MVAFjFpELWfhi/DlM= github.com/wavetermdev/htmltoken v0.2.0/go.mod h1:5FM0XV6zNYiNza2iaTcFGj+hnMtgqumFHO31Z8euquk= -github.com/wavetermdev/ssh_config v0.0.0-20241027232332-ed124367682d h1:ArHaUBaiQWUqBzM2G/oLlm3Be0kwUMDt9vTNOWIfOd0= -github.com/wavetermdev/ssh_config v0.0.0-20241027232332-ed124367682d/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34 h1:I8VZVTZEXhnzfN7jB9a7TZYpzNO48sCUWMRXHM9XWSA= +github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= From 0f15e4ff56b88a788a0a9ee5af0aaf0547176374 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:01:12 -0800 Subject: [PATCH 09/33] fix: bound custom fontsizes between 6 and 64 (#1577) Adds bounding for markdown (and AI) fontsize and fixedfontsize. --- frontend/app/element/markdown.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx index 5e6f596cf..a59fa877a 100644 --- a/frontend/app/element/markdown.tsx +++ b/frontend/app/element/markdown.tsx @@ -9,7 +9,7 @@ import { resolveSrcSet, transformBlocks, } from "@/app/element/markdown-util"; -import { useAtomValueSafe } from "@/util/util"; +import { boundNumber, useAtomValueSafe } from "@/util/util"; import { clsx } from "clsx"; import { Atom } from "jotai"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; @@ -380,10 +380,10 @@ const Markdown = ({ const mergedStyle = { ...style }; if (fontSizeOverride != null) { - mergedStyle["--markdown-font-size"] = `${fontSizeOverride}px`; + mergedStyle["--markdown-font-size"] = `${boundNumber(fontSizeOverride, 6, 64)}px`; } if (fixedFontSizeOverride != null) { - mergedStyle["--markdown-fixed-font-size"] = `${fixedFontSizeOverride}px`; + mergedStyle["--markdown-fixed-font-size"] = `${boundNumber(fixedFontSizeOverride, 6, 64)}px`; } return (
From 8235f34921525568688059cc20b99d1af7c0f924 Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:23:37 +0000 Subject: [PATCH 10/33] chore: bump package version to 0.10.2-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97254ed47..6c24ec9c4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.2-beta.1", + "version": "0.10.2-beta.2", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From 009bd39cb0e45903bf28ecaf3158ff73731a1d3d Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 19 Dec 2024 13:49:48 -0800 Subject: [PATCH 11/33] Search box component (#1579) --- frontend/app/element/search.scss | 43 +++++++ frontend/app/element/search.stories.tsx | 127 +++++++++++++++++++ frontend/app/element/search.tsx | 115 +++++++++++++++++ frontend/app/element/searchinput.scss | 0 frontend/app/element/searchinput.stories.tsx | 32 ----- frontend/app/element/searchinput.tsx | 20 --- frontend/app/theme.scss | 2 + 7 files changed, 287 insertions(+), 52 deletions(-) create mode 100644 frontend/app/element/search.scss create mode 100644 frontend/app/element/search.stories.tsx create mode 100644 frontend/app/element/search.tsx delete mode 100644 frontend/app/element/searchinput.scss delete mode 100644 frontend/app/element/searchinput.stories.tsx delete mode 100644 frontend/app/element/searchinput.tsx diff --git a/frontend/app/element/search.scss b/frontend/app/element/search.scss new file mode 100644 index 000000000..6a1f68f9b --- /dev/null +++ b/frontend/app/element/search.scss @@ -0,0 +1,43 @@ +.search-container { + display: flex; + flex-direction: row; + background-color: var(--modal-bg-color); + border: 1px solid var(--accent-color); + border-radius: var(--modal-border-radius); + box-shadow: var(--modal-box-shadow); + color: var(--main-text-color); + padding: 5px 5px 5px 10px; + gap: 5px; + width: 50%; + max-width: 300px; + min-width: 200px; + + input { + flex: 1 1 auto; + border: none; + font-size: 14px; + height: 100%; + padding: 0; + border-radius: 0; + } + + .search-results { + font-size: 12px; + margin: auto 0; + color: var(--secondary-text-color); + + &.hidden { + display: none; + } + } + + .right-buttons { + display: flex; + gap: 5px; + padding-left: 5px; + border-left: 1px solid var(--modal-border-color); + button { + font-size: 12px; + } + } +} diff --git a/frontend/app/element/search.stories.tsx b/frontend/app/element/search.stories.tsx new file mode 100644 index 000000000..c44fd54d6 --- /dev/null +++ b/frontend/app/element/search.stories.tsx @@ -0,0 +1,127 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import type { Meta, StoryObj } from "@storybook/react"; +import { useSetAtom } from "jotai"; +import { useEffect } from "react"; +import { Popover } from "./popover"; +import { Search, useSearch } from "./search"; + +const meta: Meta = { + title: "Elements/Search", + component: Search, + args: {}, +}; + +export default meta; +type Story = StoryObj; + +export const DefaultSearch: Story = { + render: (args) => { + const props = useSearch(); + const setIsOpen = useSetAtom(props.isOpenAtom); + useEffect(() => { + setIsOpen(true); + }, []); + return ( +
} + style={{ + border: "2px solid black", + width: "100%", + height: "200px", + background: "var(--main-bg-color)", + }} + > + +
+ ); + }, + args: {}, +}; + +export const Results10: Story = { + render: (args) => { + const props = useSearch(); + const setIsOpen = useSetAtom(props.isOpenAtom); + const setNumResults = useSetAtom(props.numResultsAtom); + useEffect(() => { + setIsOpen(true); + setNumResults(10); + }, []); + return ( +
} + style={{ + border: "2px solid black", + width: "100%", + height: "200px", + background: "var(--main-bg-color)", + }} + > + +
+ ); + }, + args: {}, +}; + +export const InputAndResults10: Story = { + render: (args) => { + const props = useSearch(); + const setIsOpen = useSetAtom(props.isOpenAtom); + const setNumResults = useSetAtom(props.numResultsAtom); + const setSearch = useSetAtom(props.searchAtom); + useEffect(() => { + setIsOpen(true); + setNumResults(10); + setSearch("search term"); + }, []); + return ( +
} + style={{ + border: "2px solid black", + width: "100%", + height: "200px", + background: "var(--main-bg-color)", + }} + > + +
+ ); + }, + args: {}, +}; + +export const LongInputAndResults10: Story = { + render: (args) => { + const props = useSearch(); + const setIsOpen = useSetAtom(props.isOpenAtom); + const setNumResults = useSetAtom(props.numResultsAtom); + const setSearch = useSetAtom(props.searchAtom); + useEffect(() => { + setIsOpen(true); + setNumResults(10); + setSearch("search term ".repeat(10).trimEnd()); + }, []); + return ( +
} + style={{ + border: "2px solid black", + width: "100%", + height: "200px", + background: "var(--main-bg-color)", + }} + > + +
+ ); + }, + args: {}, +}; diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx new file mode 100644 index 000000000..8a18e753c --- /dev/null +++ b/frontend/app/element/search.tsx @@ -0,0 +1,115 @@ +import { autoUpdate, FloatingPortal, Middleware, offset, useDismiss, useFloating } from "@floating-ui/react"; +import clsx from "clsx"; +import { atom, PrimitiveAtom, useAtom, useAtomValue } from "jotai"; +import { memo, useCallback, useRef, useState } from "react"; +import { IconButton } from "./iconbutton"; +import { Input } from "./input"; +import "./search.scss"; + +type SearchProps = { + searchAtom: PrimitiveAtom; + indexAtom: PrimitiveAtom; + numResultsAtom: PrimitiveAtom; + isOpenAtom: PrimitiveAtom; + anchorRef?: React.RefObject; + offsetX?: number; + offsetY?: number; +}; + +const SearchComponent = ({ + searchAtom, + indexAtom, + numResultsAtom, + isOpenAtom, + anchorRef, + offsetX = 10, + offsetY = 10, +}: SearchProps) => { + const [isOpen, setIsOpen] = useAtom(isOpenAtom); + const [search, setSearch] = useAtom(searchAtom); + const [index, setIndex] = useAtom(indexAtom); + const numResults = useAtomValue(numResultsAtom); + + const handleOpenChange = useCallback((open: boolean) => { + setIsOpen(open); + }, []); + + const middleware: Middleware[] = []; + middleware.push( + offset(({ rects }) => ({ + mainAxis: -rects.floating.height - offsetY, + crossAxis: -offsetX, + })) + ); + + const { refs, floatingStyles, context } = useFloating({ + placement: "top-end", + open: isOpen, + onOpenChange: handleOpenChange, + whileElementsMounted: autoUpdate, + middleware, + elements: { + reference: anchorRef!.current, + }, + }); + + const dismiss = useDismiss(context); + + const prevDecl: IconButtonDecl = { + elemtype: "iconbutton", + icon: "chevron-up", + title: "Previous Result", + disabled: index === 0, + click: () => setIndex(index - 1), + }; + + const nextDecl: IconButtonDecl = { + elemtype: "iconbutton", + icon: "chevron-down", + title: "Next Result", + disabled: !numResults || index === numResults - 1, + click: () => setIndex(index + 1), + }; + + const closeDecl: IconButtonDecl = { + elemtype: "iconbutton", + icon: "xmark-large", + title: "Close", + click: () => setIsOpen(false), + }; + + return ( + <> + {isOpen && ( + +
+ +
+ {index + 1}/{numResults} +
+
+ + + +
+
+
+ )} + + ); +}; + +export const Search = memo(SearchComponent) as typeof SearchComponent; + +export function useSearch(anchorRef?: React.RefObject): SearchProps { + const [searchAtom] = useState(atom("")); + const [indexAtom] = useState(atom(0)); + const [numResultsAtom] = useState(atom(0)); + const [isOpenAtom] = useState(atom(false)); + anchorRef ??= useRef(null); + return { searchAtom, indexAtom, numResultsAtom, isOpenAtom, anchorRef }; +} diff --git a/frontend/app/element/searchinput.scss b/frontend/app/element/searchinput.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/app/element/searchinput.stories.tsx b/frontend/app/element/searchinput.stories.tsx deleted file mode 100644 index 359b0d8e3..000000000 --- a/frontend/app/element/searchinput.stories.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import type { Meta, StoryObj } from "@storybook/react"; -import { SearchInput } from "./searchinput"; - -const meta: Meta = { - title: "Elements/SearchInput", - component: SearchInput, - argTypes: { - className: { - description: "Custom class for styling the input group", - control: { type: "text" }, - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const DefaultSearchInput: Story = { - render: (args) => { - const handleSearch = () => { - console.log("Search triggered"); - }; - - return ; - }, - args: { - className: "custom-search-input", - }, -}; diff --git a/frontend/app/element/searchinput.tsx b/frontend/app/element/searchinput.tsx deleted file mode 100644 index b977a5197..000000000 --- a/frontend/app/element/searchinput.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { Button } from "./button"; -import { Input, InputGroup, InputRightElement } from "./input"; - -const SearchInput = () => { - return ( - - - - - - - ); -}; - -export { SearchInput }; diff --git a/frontend/app/theme.scss b/frontend/app/theme.scss index 9f25430c4..ac5d1cf85 100644 --- a/frontend/app/theme.scss +++ b/frontend/app/theme.scss @@ -83,8 +83,10 @@ --modal-bg-color: #232323; --modal-header-bottom-border-color: rgba(241, 246, 243, 0.15); --modal-border-color: rgba(255, 255, 255, 0.12); /* toggle colors */ + --modal-border-radius: 6px; --toggle-bg-color: var(--border-color); --modal-shadow-color: rgba(0, 0, 0, 0.8); + --modal-box-shadow: box-shadow: 0px 8px 24px 0px var(--modal-shadow-color); --toggle-thumb-color: var(--main-text-color); --toggle-checked-bg-color: var(--accent-color); From 82626533610c3387952183a736bc24a1140fbe70 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 19 Dec 2024 13:50:26 -0800 Subject: [PATCH 12/33] Make all keybindings in docsite have title (#1580) --- docs/src/components/kbd.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/components/kbd.tsx b/docs/src/components/kbd.tsx index 69fee9ebf..d8550521a 100644 --- a/docs/src/components/kbd.tsx +++ b/docs/src/components/kbd.tsx @@ -38,9 +38,9 @@ function convertKey(platform: Platform, key: string): [any, string, boolean] { return ["⇧", "Shift", true]; } if (key == "Escape") { - return ["Esc", null, false]; + return ["Esc", "Escape", false]; } - return [key, null, false]; + return [key.length > 1 ? key : key.toUpperCase(), key, false]; } // Custom KBD component @@ -50,7 +50,7 @@ const KbdInternal = ({ k }: { k: string }) => { const keyElems = keys.map((key, i) => { const [displayKey, title, symbol] = convertKey(platform, key); return ( - + {displayKey} ); From caa98b471ca9bce34aeb3351bda152f702e7a4f2 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 19 Dec 2024 14:08:51 -0800 Subject: [PATCH 13/33] release notes for v0.10.2 (#1578) --- docs/docs/releasenotes.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/docs/releasenotes.mdx b/docs/docs/releasenotes.mdx index 1f4e8bed2..10179f483 100644 --- a/docs/docs/releasenotes.mdx +++ b/docs/docs/releasenotes.mdx @@ -6,6 +6,25 @@ sidebar_position: 200 # Release Notes +### v0.10.2 — Dec 19, 2024 + +Quick update to v0.10 with new features and bug fixes. + +- Global hotkey support [docs](https://docs.waveterm.dev/config#customizable-systemwide-global-hotkey) +- Added configuration to override the font size for markdown, AI-chat, and preview editor [docs](https://docs.waveterm.dev/config) +- Added ability to set independent zoom level for the web view (right click block header) +- New `wsh wavepath` command to open the config directory, data directory, and log file +- [bugfix] Fixed crash when /etc/sshd_config contained an unsupported Match directive (most common on Fedora) +- [bugfix] Workspaces are now more consistent across windows, closes associated window when Workspaces are deleted +- [bugfix] Fixed zsh on WSL +- [bugfix] Fixed long-standing bug around control sequences sometimes showing up in terminal output when switching tabs +- Lots of new examples in the docs for shell overrides, presets, widgets, and connections +- Other bug fixes and UI updates + +### v0.10.1 — Dec 12, 2024 + +Quick update to fix the workspace app menu actions. Also fixes workspace switching to always open a new window when invoked from a non-workspace window. This reduces the chance of losing a non-workspace window's tabs accidentally. + ### v0.10.0 — Dec 11, 2024 Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple work environments. We've added powerful new command execution capabilities with `wsh run`, allowing you to launch and control commands in dedicated blocks. This release also brings significant improvements to SSH with a new connections configuration system for managing your remote environments. From 2c5aae1a98314dc954861721377599967f4f5921 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:10:19 -0800 Subject: [PATCH 14/33] fix: bound code editor font size (#1581) --- frontend/app/view/codeeditor/codeeditor.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index aca418143..6eafb2b94 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useOverrideConfigAtom } from "@/app/store/global"; +import { boundNumber } from "@/util/util"; import loader from "@monaco-editor/loader"; import { Editor, Monaco } from "@monaco-editor/react"; import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; @@ -122,7 +123,7 @@ export function CodeEditor({ blockId, text, language, filename, meta, onChange, const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false; const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false; const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false; - const fontSize = useOverrideConfigAtom(blockId, "editor:fontsize"); + const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64); const theme = "wave-theme-dark"; React.useEffect(() => { From 133d7ce5a7ca9eedc0df1fdbd4cdb51a008c8fcf Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:34:29 +0000 Subject: [PATCH 15/33] chore: bump package version to 0.10.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c24ec9c4..34ff05417 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.2-beta.2", + "version": "0.10.2", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From fca6068599610e3b81f02cf755b5e06f267c698b Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 19 Dec 2024 18:39:22 -0800 Subject: [PATCH 16/33] fix terminal selection colors when transparency is turned on (#1584) fix some more of the selection background colors and fix onedarkpro (was too washed out and not using the correct official colors) --- pkg/wconfig/defaultconfig/termthemes.json | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pkg/wconfig/defaultconfig/termthemes.json b/pkg/wconfig/defaultconfig/termthemes.json index 6ac212f31..d0a667f0a 100644 --- a/pkg/wconfig/defaultconfig/termthemes.json +++ b/pkg/wconfig/defaultconfig/termthemes.json @@ -28,16 +28,15 @@ "onedarkpro": { "display:name": "One Dark Pro", "display:order": 2, - "background": "#282C34", + "background": "#21252B", "foreground": "#ABB2BF", "cursor": "#D7DAE0", - "selectionBackground": "#528BFF", "black": "#3F4451", - "red": "#E05561", - "green": "#8CC265", + "red": "#E06C75", + "green": "#98C379", "yellow": "#D18F52", - "blue": "#4AA5F0", - "magenta": "#C162DE", + "blue": "#61AFEF", + "magenta": "#C678DD", "cyan": "#42B3C2", "white": "#D7DAE0", "brightBlack": "#4F5666", @@ -73,7 +72,6 @@ "gray": "#6272A4", "cmdtext": "#F8F8F2", "foreground": "#F8F8F2", - "selectionBackground": "#44475a", "background": "#282a36", "cursor": "#f8f8f2" }, @@ -99,7 +97,6 @@ "gray": "#75715E", "cmdtext": "#F8F8F2", "foreground": "#F8F8F2", - "selectionBackground": "#49483E", "background": "#272822", "cursor": "#F8F8F2" }, @@ -125,7 +122,7 @@ "gray": "#767676", "cmdtext": "#CCCCCC", "foreground": "#CCCCCC", - "selectionBackground": "#3A96DD", + "selectionBackground": "#3A96DD77", "background": "#0C0C0C", "cursor": "#CCCCCC" }, @@ -150,7 +147,7 @@ "brightWhite": "#FFFFFF", "background": "#2B2620", "foreground": "#F2E6D4", - "selectionBackground": "#B7950B", + "selectionBackground": "#B7950B77", "cursor": "#F9D784" }, "rosepine": { @@ -175,7 +172,6 @@ "gray": "#908caa", "cmdtext": "#e0def4", "foreground": "#e0def4", - "selectionBackground": "#403d52", "background": "#191724", "cursor": "#524f67" } From 1b8144f452f229161b2731085dd9b2459810d4e4 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 19 Dec 2024 18:45:02 -0800 Subject: [PATCH 17/33] ignore coderabbit, update for v0.10.3 (#1585) --- .github/workflows/merge-gatekeeper.yml | 2 +- docs/docs/releasenotes.mdx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge-gatekeeper.yml b/.github/workflows/merge-gatekeeper.yml index 7a8953cdf..a433e4c88 100644 --- a/.github/workflows/merge-gatekeeper.yml +++ b/.github/workflows/merge-gatekeeper.yml @@ -23,4 +23,4 @@ jobs: uses: upsidr/merge-gatekeeper@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - ignored: Test Onboarding, Analyze (go), Analyze (javascript-typescript), License Compliance + ignored: Test Onboarding, Analyze (go), Analyze (javascript-typescript), License Compliance, CodeRabbit diff --git a/docs/docs/releasenotes.mdx b/docs/docs/releasenotes.mdx index 10179f483..4240730e1 100644 --- a/docs/docs/releasenotes.mdx +++ b/docs/docs/releasenotes.mdx @@ -6,7 +6,7 @@ sidebar_position: 200 # Release Notes -### v0.10.2 — Dec 19, 2024 +### v0.10.3 — Dec 19, 2024 Quick update to v0.10 with new features and bug fixes. @@ -21,6 +21,8 @@ Quick update to v0.10 with new features and bug fixes. - Lots of new examples in the docs for shell overrides, presets, widgets, and connections - Other bug fixes and UI updates +(note, v0.10.2 and v0.10.3's release notes have been merged together) + ### v0.10.1 — Dec 12, 2024 Quick update to fix the workspace app menu actions. Also fixes workspace switching to always open a new window when invoked from a non-workspace window. This reduces the chance of losing a non-workspace window's tabs accidentally. From b6f04ef99437c45dd44d0c8ed2cc66b31cb57def Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 02:46:04 +0000 Subject: [PATCH 18/33] chore: bump package version to 0.10.2 From fca868e5973216500ee8c35ac9bf5a5484e9a19e Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 02:48:27 +0000 Subject: [PATCH 19/33] chore: bump package version to 0.10.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34ff05417..6d418e97d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.2", + "version": "0.10.3", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From 9f9190b93c28c6113936546c8804793deab5c6f5 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Fri, 20 Dec 2024 03:18:44 -0800 Subject: [PATCH 20/33] fix: dynamically determine the chat window height (#1588) The AI chat window was not allowing the user to resize it due to an interacting scss class being removed. This replaces the max-height from the removed class with a dynamic calculation to resize properly. --- frontend/app/view/waveai/waveai.tsx | 145 +++++++++++++++------------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index 0bdc2412b..63434914a 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -4,6 +4,7 @@ import { Button } from "@/app/element/button"; import { Markdown } from "@/app/element/markdown"; import { TypingIndicator } from "@/app/element/typingindicator"; +import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions"; import { RpcResponseHelper, WshClient } from "@/app/store/wshclient"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; @@ -506,84 +507,88 @@ interface ChatWindowProps { messages: ChatMessageType[]; msgWidths: Object; model: WaveAiModel; + height: number; } const ChatWindow = memo( - forwardRef(({ chatWindowRef, messages, msgWidths, model }, ref) => { - const [isUserScrolling, setIsUserScrolling] = useState(false); + forwardRef( + ({ chatWindowRef, messages, msgWidths, model, height }, ref) => { + const [isUserScrolling, setIsUserScrolling] = useState(false); - const osRef = useRef(null); - const prevMessagesLenRef = useRef(messages.length); + const osRef = useRef(null); + const prevMessagesLenRef = useRef(messages.length); - useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef); + useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef); - useEffect(() => { - if (osRef.current && osRef.current.osInstance()) { - const { viewport } = osRef.current.osInstance().elements(); - const curMessagesLen = messages.length; - if (prevMessagesLenRef.current !== curMessagesLen || !isUserScrolling) { - setIsUserScrolling(false); - viewport.scrollTo({ - behavior: "auto", - top: chatWindowRef.current?.scrollHeight || 0, - }); - } - - prevMessagesLenRef.current = curMessagesLen; - } - }, [messages, isUserScrolling]); - - useEffect(() => { - if (osRef.current && osRef.current.osInstance()) { - const { viewport } = osRef.current.osInstance().elements(); - - const handleUserScroll = () => { - setIsUserScrolling(true); - }; - - viewport.addEventListener("wheel", handleUserScroll, { passive: true }); - viewport.addEventListener("touchmove", handleUserScroll, { passive: true }); - - return () => { - viewport.removeEventListener("wheel", handleUserScroll); - viewport.removeEventListener("touchmove", handleUserScroll); - if (osRef.current && osRef.current.osInstance()) { - osRef.current.osInstance().destroy(); + useEffect(() => { + if (osRef.current && osRef.current.osInstance()) { + const { viewport } = osRef.current.osInstance().elements(); + const curMessagesLen = messages.length; + if (prevMessagesLenRef.current !== curMessagesLen || !isUserScrolling) { + setIsUserScrolling(false); + viewport.scrollTo({ + behavior: "auto", + top: chatWindowRef.current?.scrollHeight || 0, + }); } - }; - } - }, []); - const handleScrollbarInitialized = (instance: OverlayScrollbars) => { - const { viewport } = instance.elements(); - viewport.removeAttribute("tabindex"); - viewport.scrollTo({ - behavior: "auto", - top: chatWindowRef.current?.scrollHeight || 0, - }); - }; + prevMessagesLenRef.current = curMessagesLen; + } + }, [messages, isUserScrolling]); - const handleScrollbarUpdated = (instance: OverlayScrollbars) => { - const { viewport } = instance.elements(); - viewport.removeAttribute("tabindex"); - }; + useEffect(() => { + if (osRef.current && osRef.current.osInstance()) { + const { viewport } = osRef.current.osInstance().elements(); - return ( - -
-
- {messages.map((chitem, idx) => ( - - ))} -
-
- ); - }) + const handleUserScroll = () => { + setIsUserScrolling(true); + }; + + viewport.addEventListener("wheel", handleUserScroll, { passive: true }); + viewport.addEventListener("touchmove", handleUserScroll, { passive: true }); + + return () => { + viewport.removeEventListener("wheel", handleUserScroll); + viewport.removeEventListener("touchmove", handleUserScroll); + if (osRef.current && osRef.current.osInstance()) { + osRef.current.osInstance().destroy(); + } + }; + } + }, []); + + const handleScrollbarInitialized = (instance: OverlayScrollbars) => { + const { viewport } = instance.elements(); + viewport.removeAttribute("tabindex"); + viewport.scrollTo({ + behavior: "auto", + top: chatWindowRef.current?.scrollHeight || 0, + }); + }; + + const handleScrollbarUpdated = (instance: OverlayScrollbars) => { + const { viewport } = instance.elements(); + viewport.removeAttribute("tabindex"); + }; + + return ( + +
+
+ {messages.map((chitem, idx) => ( + + ))} +
+
+ ); + } + ) ); interface ChatInputProps { @@ -657,6 +662,8 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { const chatWindowRef = useRef(null); const osRef = useRef(null); const inputRef = useRef(null); + const waveAiDims = useDimensionsWithExistingRef(waveaiRef); + const chatInputDims = useDimensionsWithExistingRef(inputRef); const [value, setValue] = useState(""); const [selectedBlockIdx, setSelectedBlockIdx] = useState(null); @@ -829,6 +836,8 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { messages={messages} msgWidths={msgWidths} model={model} + height={waveAiDims?.height - chatInputDims?.height - 28 ?? 400} + // the 28 is a magic number it the moment but it makes the spacing look good />
From cf578b1d89594a9a2646d3b40b88c4449f7dec54 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 20 Dec 2024 08:55:28 -0800 Subject: [PATCH 21/33] Allow empty workspace name while editing workspace (#1592) Fixes a bug where if you deleted all but one character in the workspace name, you couldn't delete the final character. To fix this, I have made the workspace editor save a separate entry from the backend. The backend will also only update its DB value and notify the frontend if something was actually edited. If you delete all the characters in the name and don't put anything new in, though, the name will be whatever the last character you had was, since the name of a saved workspace cannot be empty. --- frontend/app/tab/workspaceswitcher.tsx | 1 + .../workspaceservice/workspaceservice.go | 5 ++++- pkg/wcore/workspace.go | 21 ++++++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/frontend/app/tab/workspaceswitcher.tsx b/frontend/app/tab/workspaceswitcher.tsx index bd995a611..018a9e50c 100644 --- a/frontend/app/tab/workspaceswitcher.tsx +++ b/frontend/app/tab/workspaceswitcher.tsx @@ -153,6 +153,7 @@ const WorkspaceSwitcherItem = ({ const isCurrentWorkspace = activeWorkspace.oid === workspace.oid; const setWorkspace = useCallback((newWorkspace: Workspace) => { + setWorkspaceEntry({ ...workspaceEntry, workspace: newWorkspace }); if (newWorkspace.name != "") { fireAndForget(() => WorkspaceService.UpdateWorkspace( diff --git a/pkg/service/workspaceservice/workspaceservice.go b/pkg/service/workspaceservice/workspaceservice.go index 1c86ff77b..b6c97ac8b 100644 --- a/pkg/service/workspaceservice/workspaceservice.go +++ b/pkg/service/workspaceservice/workspaceservice.go @@ -45,10 +45,13 @@ func (svc *WorkspaceService) UpdateWorkspace_Meta() tsgenmeta.MethodMeta { func (svc *WorkspaceService) UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (waveobj.UpdatesRtnType, error) { ctx = waveobj.ContextWithUpdates(ctx) - _, err := wcore.UpdateWorkspace(ctx, workspaceId, name, icon, color, applyDefaults) + _, updated, err := wcore.UpdateWorkspace(ctx, workspaceId, name, icon, color, applyDefaults) if err != nil { return nil, fmt.Errorf("error updating workspace: %w", err) } + if !updated { + return nil, nil + } wps.Broker.Publish(wps.WaveEvent{ Event: wps.Event_WorkspaceUpdate}) diff --git a/pkg/wcore/workspace.go b/pkg/wcore/workspace.go index b3143b2ab..1cc8ce1cf 100644 --- a/pkg/wcore/workspace.go +++ b/pkg/wcore/workspace.go @@ -68,26 +68,34 @@ func CreateWorkspace(ctx context.Context, name string, icon string, color string wps.Broker.Publish(wps.WaveEvent{ Event: wps.Event_WorkspaceUpdate}) - return UpdateWorkspace(ctx, ws.OID, name, icon, color, applyDefaults) + ws, _, err = UpdateWorkspace(ctx, ws.OID, name, icon, color, applyDefaults) + return ws, err } -func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (*waveobj.Workspace, error) { +// Returns updated workspace, whether it was updated, error. +func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (*waveobj.Workspace, bool, error) { ws, err := GetWorkspace(ctx, workspaceId) + updated := false if err != nil { - return nil, fmt.Errorf("workspace %s not found: %w", workspaceId, err) + return nil, updated, fmt.Errorf("workspace %s not found: %w", workspaceId, err) } if name != "" { ws.Name = name + updated = true } else if applyDefaults && ws.Name == "" { ws.Name = fmt.Sprintf("New Workspace (%s)", ws.OID[0:5]) + updated = true } if icon != "" { ws.Icon = icon + updated = true } else if applyDefaults && ws.Icon == "" { ws.Icon = WorkspaceIcons[0] + updated = true } if color != "" { ws.Color = color + updated = true } else if applyDefaults && ws.Color == "" { wsList, err := ListWorkspaces(ctx) if err != nil { @@ -95,9 +103,12 @@ func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon wsList = waveobj.WorkspaceList{} } ws.Color = WorkspaceColors[len(wsList)%len(WorkspaceColors)] + updated = true } - wstore.DBUpdate(ctx, ws) - return ws, nil + if updated { + wstore.DBUpdate(ctx, ws) + } + return ws, updated, nil } // If force is true, it will delete even if workspace is named. From bbece2d80de2f55d4016b4990fa3659839361ed7 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 20 Dec 2024 09:04:42 -0800 Subject: [PATCH 22/33] Update go net package (#1593) There's a potential DDoS vulnerability in the older version of the net package that we were using. It likely isn't something that would impact us, but we should still update for posterity. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aa784a472..448f0f818 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.33.0 // indirect ) replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34 diff --git a/go.sum b/go.sum index 437167932..d560a54ab 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 87309a06abafce555765d22875fcde5f90793ff7 Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:34:52 +0000 Subject: [PATCH 23/33] chore: bump package version to 0.10.4-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d418e97d..91b39c14e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.3", + "version": "0.10.4-beta.0", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From e0c875afebca450dfbb881ba766721443f57fa86 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 20 Dec 2024 11:49:29 -0800 Subject: [PATCH 24/33] Remove WaveAI dynamic height adjustment, use pure CSS, also fix scrolling (#1594) This makes the chat window flex-grow so we no longer need to manually fix its height. It also cleans up some other styling. It also fixes the scroll handlers so we detect when the user is at the bottom of the chat window so we can follow the latest message. It also fixes some circular references in the callbacks that were causing React to bug out. --- frontend/app/view/waveai/waveai.scss | 132 ++++++++++++------------ frontend/app/view/waveai/waveai.tsx | 144 +++++++++++++++------------ 2 files changed, 144 insertions(+), 132 deletions(-) diff --git a/frontend/app/view/waveai/waveai.scss b/frontend/app/view/waveai/waveai.scss index 4a2020e5c..2d463fd88 100644 --- a/frontend/app/view/waveai/waveai.scss +++ b/frontend/app/view/waveai/waveai.scss @@ -4,100 +4,99 @@ .waveai { display: flex; flex-direction: column; - overflow: hidden; height: 100%; width: 100%; .waveai-chat { - flex-grow: 1; - > .scrollable { - flex-flow: column nowrap; - margin-bottom: 0; + flex: 1 1 auto; + overflow: hidden; + .chat-window-container { overflow-y: auto; - min-height: 100%; + margin-bottom: 0; + height: 100%; .chat-window { + flex-flow: column nowrap; display: flex; - flex-direction: column; gap: 8px; // This is the filler that will push the chat messages to the bottom until the chat window is full .filler { flex: 1 1 auto; } - } - .chat-msg-container { - display: flex; - gap: 8px; - .chat-msg { - margin: 10px 0; + .chat-msg-container { display: flex; - align-items: flex-start; - border-radius: 8px; - - &.chat-msg-header { + gap: 8px; + .chat-msg { + margin: 10px 0; display: flex; - flex-direction: column; - justify-content: flex-start; + align-items: flex-start; + border-radius: 8px; - .icon-box { - padding-top: 0; - border-radius: 4px; - background-color: rgb(from var(--highlight-bg-color) r g b / 0.05); + &.chat-msg-header { display: flex; - padding: 6px; - } - } + flex-direction: column; + justify-content: flex-start; - &.chat-msg-assistant { - color: var(--main-text-color); - background-color: rgb(from var(--highlight-bg-color) r g b / 0.1); - margin-right: auto; - padding: 10px; - max-width: 85%; - - .markdown { - width: 100%; - - pre { - white-space: pre-wrap; - word-break: break-word; - max-width: 100%; - overflow-x: auto; - margin-left: 0; + .icon-box { + padding-top: 0; + border-radius: 4px; + background-color: rgb(from var(--highlight-bg-color) r g b / 0.05); + display: flex; + padding: 6px; } } - } - &.chat-msg-user { - margin-left: auto; - padding: 10px; - max-width: 85%; - background-color: rgb(from var(--accent-color) r g b / 0.15); - } - &.chat-msg-error { - color: var(--main-text-color); - background-color: rgb(from var(--error-color) r g b / 0.25); - margin-right: auto; - padding: 10px; - max-width: 85%; + &.chat-msg-assistant { + color: var(--main-text-color); + background-color: rgb(from var(--highlight-bg-color) r g b / 0.1); + margin-right: auto; + padding: 10px; + max-width: 85%; - .markdown { - width: 100%; + .markdown { + width: 100%; - pre { - white-space: pre-wrap; - word-break: break-word; - max-width: 100%; - overflow-x: auto; - margin-left: 0; + pre { + white-space: pre-wrap; + word-break: break-word; + max-width: 100%; + overflow-x: auto; + margin-left: 0; + } } } - } + &.chat-msg-user { + margin-left: auto; + padding: 10px; + max-width: 85%; + background-color: rgb(from var(--accent-color) r g b / 0.15); + } - &.typing-indicator { - margin-top: 4px; + &.chat-msg-error { + color: var(--main-text-color); + background-color: rgb(from var(--error-color) r g b / 0.25); + margin-right: auto; + padding: 10px; + max-width: 85%; + + .markdown { + width: 100%; + + pre { + white-space: pre-wrap; + word-break: break-word; + max-width: 100%; + overflow-x: auto; + margin-left: 0; + } + } + } + + &.typing-indicator { + margin-top: 4px; + } } } } @@ -105,6 +104,7 @@ } .waveai-controls { + flex: 0 0 auto; display: flex; flex-direction: row; align-items: center; diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index 63434914a..e3c131759 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -4,7 +4,6 @@ import { Button } from "@/app/element/button"; import { Markdown } from "@/app/element/markdown"; import { TypingIndicator } from "@/app/element/typingindicator"; -import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions"; import { RpcResponseHelper, WshClient } from "@/app/store/wshclient"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; @@ -17,6 +16,7 @@ import { atom, Atom, PrimitiveAtom, useAtomValue, WritableAtom } from "jotai"; import type { OverlayScrollbars } from "overlayscrollbars"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; +import { debounce, throttle } from "throttle-debounce"; import "./waveai.scss"; interface ChatMessageType { @@ -434,8 +434,6 @@ function makeWaveAiViewModel(blockId: string): WaveAiModel { const ChatItem = ({ chatItem, model }: ChatItemProps) => { const { user, text } = chatItem; - const cssVar = "--panel-bg-color"; - const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim(); const fontSize = useOverrideConfigAtom(model.blockId, "ai:fontsize"); const fixedFontSize = useOverrideConfigAtom(model.blockId, "ai:fixedfontsize"); const renderContent = useMemo(() => { @@ -507,25 +505,23 @@ interface ChatWindowProps { messages: ChatMessageType[]; msgWidths: Object; model: WaveAiModel; - height: number; } const ChatWindow = memo( - forwardRef( - ({ chatWindowRef, messages, msgWidths, model, height }, ref) => { - const [isUserScrolling, setIsUserScrolling] = useState(false); + forwardRef(({ chatWindowRef, messages, msgWidths, model }, ref) => { + const [isUserScrolling, setIsUserScrolling] = useState(false); - const osRef = useRef(null); - const prevMessagesLenRef = useRef(messages.length); + const osRef = useRef(null); + const prevMessagesLenRef = useRef(messages.length); - useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef); + useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef); - useEffect(() => { - if (osRef.current && osRef.current.osInstance()) { + const handleNewMessage = useCallback( + throttle(100, (messages: ChatMessageType[]) => { + if (osRef.current?.osInstance()) { const { viewport } = osRef.current.osInstance().elements(); const curMessagesLen = messages.length; if (prevMessagesLenRef.current !== curMessagesLen || !isUserScrolling) { - setIsUserScrolling(false); viewport.scrollTo({ behavior: "auto", top: chatWindowRef.current?.scrollHeight || 0, @@ -534,61 +530,81 @@ const ChatWindow = memo( prevMessagesLenRef.current = curMessagesLen; } - }, [messages, isUserScrolling]); + }), + [isUserScrolling] + ); - useEffect(() => { - if (osRef.current && osRef.current.osInstance()) { - const { viewport } = osRef.current.osInstance().elements(); + useEffect(() => { + handleNewMessage(messages); + }, [messages]); - const handleUserScroll = () => { - setIsUserScrolling(true); - }; - - viewport.addEventListener("wheel", handleUserScroll, { passive: true }); - viewport.addEventListener("touchmove", handleUserScroll, { passive: true }); - - return () => { - viewport.removeEventListener("wheel", handleUserScroll); - viewport.removeEventListener("touchmove", handleUserScroll); - if (osRef.current && osRef.current.osInstance()) { - osRef.current.osInstance().destroy(); - } - }; + // Wait 300 ms after the user stops scrolling to determine if the user is within 300px of the bottom of the chat window. + // If so, unset the user scrolling flag. + const determineUnsetScroll = useCallback( + debounce(300, () => { + const { viewport } = osRef.current.osInstance().elements(); + if (viewport.scrollTop > chatWindowRef.current?.clientHeight - viewport.clientHeight - 30) { + setIsUserScrolling(false); } - }, []); + }), + [] + ); - const handleScrollbarInitialized = (instance: OverlayScrollbars) => { - const { viewport } = instance.elements(); - viewport.removeAttribute("tabindex"); - viewport.scrollTo({ - behavior: "auto", - top: chatWindowRef.current?.scrollHeight || 0, - }); - }; + const handleUserScroll = useCallback( + throttle(100, () => { + setIsUserScrolling(true); + determineUnsetScroll(); + }), + [] + ); - const handleScrollbarUpdated = (instance: OverlayScrollbars) => { - const { viewport } = instance.elements(); - viewport.removeAttribute("tabindex"); - }; + useEffect(() => { + if (osRef.current?.osInstance()) { + const { viewport } = osRef.current.osInstance().elements(); - return ( - -
-
- {messages.map((chitem, idx) => ( - - ))} -
-
- ); - } - ) + viewport.addEventListener("wheel", handleUserScroll, { passive: true }); + viewport.addEventListener("touchmove", handleUserScroll, { passive: true }); + + return () => { + viewport.removeEventListener("wheel", handleUserScroll); + viewport.removeEventListener("touchmove", handleUserScroll); + if (osRef.current && osRef.current.osInstance()) { + osRef.current.osInstance().destroy(); + } + }; + } + }, []); + + const handleScrollbarInitialized = (instance: OverlayScrollbars) => { + const { viewport } = instance.elements(); + viewport.removeAttribute("tabindex"); + viewport.scrollTo({ + behavior: "auto", + top: chatWindowRef.current?.scrollHeight || 0, + }); + }; + + const handleScrollbarUpdated = (instance: OverlayScrollbars) => { + const { viewport } = instance.elements(); + viewport.removeAttribute("tabindex"); + }; + + return ( + +
+
+ {messages.map((chitem, idx) => ( + + ))} +
+
+ ); + }) ); interface ChatInputProps { @@ -662,8 +678,6 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { const chatWindowRef = useRef(null); const osRef = useRef(null); const inputRef = useRef(null); - const waveAiDims = useDimensionsWithExistingRef(waveaiRef); - const chatInputDims = useDimensionsWithExistingRef(inputRef); const [value, setValue] = useState(""); const [selectedBlockIdx, setSelectedBlockIdx] = useState(null); @@ -836,8 +850,6 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { messages={messages} msgWidths={msgWidths} model={model} - height={waveAiDims?.height - chatInputDims?.height - 28 ?? 400} - // the 28 is a magic number it the moment but it makes the spacing look good />
From 9793f9898cd97d2d5f0ec916a4315aab0a409ab6 Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:57:30 +0000 Subject: [PATCH 25/33] chore: bump package version to 0.10.4-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91b39c14e..eb34744c4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.4-beta.0", + "version": "0.10.4-beta.1", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From b17e613d74a074eb46e84e884841364388815602 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:51:32 -0800 Subject: [PATCH 26/33] fix: add leading slash to default remote shell (#1596) Adds a leading slash to `/bin/bash` as the default shell if no other shell has been found --- cmd/wsh/cmd/wshcmd-shell-unix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/wsh/cmd/wshcmd-shell-unix.go b/cmd/wsh/cmd/wshcmd-shell-unix.go index dfe044aeb..b3b85b446 100644 --- a/cmd/wsh/cmd/wshcmd-shell-unix.go +++ b/cmd/wsh/cmd/wshcmd-shell-unix.go @@ -58,5 +58,5 @@ func shellCmdInner() string { } } // none found - return "bin/bash\n" + return "/bin/bash\n" } From 4280a0981fa0b6553f05bcc9ec1e0c7a40d98447 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 20 Dec 2024 17:01:19 -0500 Subject: [PATCH 27/33] Fix more WaveAI scroll issues (#1597) This adds a split atom for the messages so that the WaveAI component and the ChatWindow component don't actually need to watch changes to all of the messages. This makes the repaining a lot less expensive and makes it easier to scroll while new messages come in. I also increased the tolerance on the `determineUnsetScroll` callback so that the bottom message won't get unattached as easily. --- frontend/app/view/waveai/waveai.tsx | 60 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index e3c131759..6ac27e721 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -13,6 +13,7 @@ import { BlockService, ObjectService } from "@/store/services"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import { fireAndForget, isBlank, makeIconClass } from "@/util/util"; import { atom, Atom, PrimitiveAtom, useAtomValue, WritableAtom } from "jotai"; +import { splitAtom } from "jotai/utils"; import type { OverlayScrollbars } from "overlayscrollbars"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; @@ -30,7 +31,7 @@ const outline = "2px solid var(--accent-color)"; const slidingWindowSize = 30; interface ChatItemProps { - chatItem: ChatMessageType; + chatItemAtom: Atom; model: WaveAiModel; } @@ -73,6 +74,8 @@ export class WaveAiModel implements ViewModel { preIconButton?: Atom; endIconButtons?: Atom; messagesAtom: PrimitiveAtom>; + messagesSplitAtom: SplitAtom>; + latestMessageAtom: Atom; addMessageAtom: WritableAtom; updateLastMessageAtom: WritableAtom; removeLastMessageAtom: WritableAtom; @@ -93,6 +96,8 @@ export class WaveAiModel implements ViewModel { this.viewIcon = atom("sparkles"); this.viewName = atom("Wave AI"); this.messagesAtom = atom([]); + this.messagesSplitAtom = splitAtom(this.messagesAtom); + this.latestMessageAtom = atom((get) => get(this.messagesAtom).slice(-1)[0]); this.presetKey = atom((get) => { const metaPresetKey = get(this.blockAtom).meta["ai:preset"]; const globalPresetKey = get(atoms.settingsAtom)["ai:preset"]; @@ -406,10 +411,8 @@ export class WaveAiModel implements ViewModel { } useWaveAi() { - const messages = useAtomValue(this.messagesAtom); return { - messages, - sendMessage: this.sendMessage.bind(this), + sendMessage: this.sendMessage.bind(this) as (text: string) => void, }; } @@ -432,7 +435,8 @@ function makeWaveAiViewModel(blockId: string): WaveAiModel { return waveAiModel; } -const ChatItem = ({ chatItem, model }: ChatItemProps) => { +const ChatItem = ({ chatItemAtom, model }: ChatItemProps) => { + const chatItem = useAtomValue(chatItemAtom); const { user, text } = chatItem; const fontSize = useOverrideConfigAtom(model.blockId, "ai:fontsize"); const fixedFontSize = useOverrideConfigAtom(model.blockId, "ai:fixedfontsize"); @@ -502,49 +506,49 @@ const ChatItem = ({ chatItem, model }: ChatItemProps) => { interface ChatWindowProps { chatWindowRef: React.RefObject; - messages: ChatMessageType[]; msgWidths: Object; model: WaveAiModel; } const ChatWindow = memo( - forwardRef(({ chatWindowRef, messages, msgWidths, model }, ref) => { - const [isUserScrolling, setIsUserScrolling] = useState(false); - + forwardRef(({ chatWindowRef, msgWidths, model }, ref) => { + const isUserScrolling = useRef(false); const osRef = useRef(null); - const prevMessagesLenRef = useRef(messages.length); + const splitMessages = useAtomValue(model.messagesSplitAtom) as Atom[]; + const latestMessage = useAtomValue(model.latestMessageAtom); + const prevMessagesLenRef = useRef(splitMessages.length); useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef); const handleNewMessage = useCallback( - throttle(100, (messages: ChatMessageType[]) => { + throttle(100, (messagesLen: number) => { if (osRef.current?.osInstance()) { + console.log("handleNewMessage", messagesLen, isUserScrolling.current); const { viewport } = osRef.current.osInstance().elements(); - const curMessagesLen = messages.length; - if (prevMessagesLenRef.current !== curMessagesLen || !isUserScrolling) { + if (prevMessagesLenRef.current !== messagesLen || !isUserScrolling.current) { viewport.scrollTo({ behavior: "auto", top: chatWindowRef.current?.scrollHeight || 0, }); } - prevMessagesLenRef.current = curMessagesLen; + prevMessagesLenRef.current = messagesLen; } }), - [isUserScrolling] + [] ); useEffect(() => { - handleNewMessage(messages); - }, [messages]); + handleNewMessage(splitMessages.length); + }, [splitMessages, latestMessage]); // Wait 300 ms after the user stops scrolling to determine if the user is within 300px of the bottom of the chat window. // If so, unset the user scrolling flag. const determineUnsetScroll = useCallback( debounce(300, () => { const { viewport } = osRef.current.osInstance().elements(); - if (viewport.scrollTop > chatWindowRef.current?.clientHeight - viewport.clientHeight - 30) { - setIsUserScrolling(false); + if (viewport.scrollTop > chatWindowRef.current?.clientHeight - viewport.clientHeight - 100) { + isUserScrolling.current = false; } }), [] @@ -552,7 +556,7 @@ const ChatWindow = memo( const handleUserScroll = useCallback( throttle(100, () => { - setIsUserScrolling(true); + isUserScrolling.current = true; determineUnsetScroll(); }), [] @@ -598,8 +602,8 @@ const ChatWindow = memo( >
- {messages.map((chitem, idx) => ( - + {splitMessages.map((chitem, idx) => ( + ))}
@@ -673,7 +677,7 @@ const ChatInput = forwardRef( ); const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { - const { messages, sendMessage } = model.useWaveAi(); + const { sendMessage } = model.useWaveAi(); const waveaiRef = useRef(null); const chatWindowRef = useRef(null); const osRef = useRef(null); @@ -737,7 +741,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { sendMessage(value); setValue(""); setSelectedBlockIdx(null); - }, [messages, value]); + }, [value]); const updateScrollTop = () => { const pres = chatWindowRef.current?.querySelectorAll("pre"); @@ -844,13 +848,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { return (
- +
From 62dcf1326dd910b78472155feddec30bc669169e Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 20 Dec 2024 17:02:08 -0500 Subject: [PATCH 28/33] Allow users to close windows without confirming, delete last window (#1598) Adds `window:savelastwindow` and `window:confirmclose` settings, which can be used to alter the default window close behavior. Both of these default to `true` to maintain the existing default behavior. If `window:savelastwindow` is unset, the same logic will be used as if the user had more than one window remaining (see below). If `window:confirmonclose` is unset, the user will no longer be prompted to confirm if they are closing a window whose workspace has unsaved changes (the workspace is not named and it has more than one tab). --- docs/docs/config.mdx | 7 ++++- emain/emain-window.ts | 40 ++++++++++++------------- frontend/types/gotypes.d.ts | 2 ++ pkg/wconfig/defaultconfig/settings.json | 4 ++- pkg/wconfig/metaconsts.go | 2 ++ pkg/wconfig/settingsconfig.go | 2 ++ 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index b1c7b89c5..8cadea106 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -76,9 +76,11 @@ wsh editconfig | window:showmenubar | bool | set to use the OS-native menu bar (Windows and Linux only, requires app restart) | | window:nativetitlebar | bool | set to use the OS-native title bar, rather than the overlay (Windows and Linux only, requires app restart) | | window:disablehardwareacceleration | bool | set to disable Chromium hardware acceleration to resolve graphical bugs (requires app restart) | +| window:savelastwindow | bool | when `true`, the last window that is closed is preserved and is reopened the next time the app is launched (defaults to `true`) | +| window:confirmonclose | bool | when `true`, a prompt will ask a user to confirm that they want to close a window if it has an unsaved workspace with more than one tab (defaults to `true`) | | telemetry:enabled | bool | set to enable/disable telemetry | -For reference this is the current default configuration (v0.9.3): +For reference, this is the current default configuration (v0.10.4): ```json { @@ -90,6 +92,7 @@ For reference this is the current default configuration (v0.9.3): "autoupdate:installonquit": true, "autoupdate:intervalms": 3600000, "conn:askbeforewshinstall": true, + "conn:wshenabled": true, "editor:minimapenabled": true, "web:defaulturl": "https://github.com/wavetermdev/waveterm", "web:defaultsearch": "https://www.google.com/search?q={query}", @@ -100,6 +103,8 @@ For reference this is the current default configuration (v0.9.3): "window:magnifiedblocksize": 0.9, "window:magnifiedblockblurprimarypx": 10, "window:magnifiedblockblursecondarypx": 2, + "window:confirmclose": true, + "window:savelastwindow": true, "telemetry:enabled": true, "term:copyonselect": true } diff --git a/emain/emain-window.ts b/emain/emain-window.ts index 7cc076ad5..068703a70 100644 --- a/emain/emain-window.ts +++ b/emain/emain-window.ts @@ -229,21 +229,26 @@ export class WaveBrowserWindow extends BaseWindow { e.preventDefault(); fireAndForget(async () => { const numWindows = waveWindowMap.size; - if (numWindows > 1) { - console.log("numWindows > 1", numWindows); - const workspace = await WorkspaceService.GetWorkspace(this.workspaceId); - console.log("workspace", workspace); - if (isNonEmptyUnsavedWorkspace(workspace)) { - console.log("workspace has no name, icon, and multiple tabs", workspace); - const choice = dialog.showMessageBoxSync(this, { - type: "question", - buttons: ["Cancel", "Close Window"], - title: "Confirm", - message: "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?", - }); - if (choice === 0) { - console.log("user cancelled close window", this.waveWindowId); - return; + const fullConfig = await FileService.GetFullConfig(); + if (numWindows > 1 || !fullConfig.settings["window:savelastwindow"]) { + console.log("numWindows > 1 or user does not want last window saved", numWindows); + if (fullConfig.settings["window:confirmclose"]) { + console.log("confirmclose", this.waveWindowId); + const workspace = await WorkspaceService.GetWorkspace(this.workspaceId); + console.log("workspace", workspace); + if (isNonEmptyUnsavedWorkspace(workspace)) { + console.log("workspace has no name, icon, and multiple tabs", workspace); + const choice = dialog.showMessageBoxSync(this, { + type: "question", + buttons: ["Cancel", "Close Window"], + title: "Confirm", + message: + "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?", + }); + if (choice === 0) { + console.log("user cancelled close window", this.waveWindowId); + return; + } } } console.log("deleteAllowed = true", this.waveWindowId); @@ -270,11 +275,6 @@ export class WaveBrowserWindow extends BaseWindow { this.destroy(); return; } - const numWindows = waveWindowMap.size; - if (numWindows == 0) { - console.log("win no windows left", this.waveWindowId); - return; - } if (this.deleteAllowed) { console.log("win removing window from backend DB", this.waveWindowId); fireAndForget(() => WindowService.CloseWindow(this.waveWindowId, true)); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 15838e1ab..22f582776 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -679,6 +679,8 @@ declare global { "window:magnifiedblocksize"?: number; "window:magnifiedblockblurprimarypx"?: number; "window:magnifiedblockblursecondarypx"?: number; + "window:confirmclose"?: boolean; + "window:savelastwindow"?: boolean; "telemetry:*"?: boolean; "telemetry:enabled"?: boolean; "conn:*"?: boolean; diff --git a/pkg/wconfig/defaultconfig/settings.json b/pkg/wconfig/defaultconfig/settings.json index fa8d98f88..3da7e6fd2 100644 --- a/pkg/wconfig/defaultconfig/settings.json +++ b/pkg/wconfig/defaultconfig/settings.json @@ -7,7 +7,7 @@ "autoupdate:installonquit": true, "autoupdate:intervalms": 3600000, "conn:askbeforewshinstall": true, - "conn:wshenabled": true, + "conn:wshenabled": true, "editor:minimapenabled": true, "web:defaulturl": "https://github.com/wavetermdev/waveterm", "web:defaultsearch": "https://www.google.com/search?q={query}", @@ -18,6 +18,8 @@ "window:magnifiedblocksize": 0.9, "window:magnifiedblockblurprimarypx": 10, "window:magnifiedblockblursecondarypx": 2, + "window:confirmclose": true, + "window:savelastwindow": true, "telemetry:enabled": true, "term:copyonselect": true } diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index 3197b9ad0..e7cf00c0a 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -79,6 +79,8 @@ const ( ConfigKey_WindowMagnifiedBlockSize = "window:magnifiedblocksize" ConfigKey_WindowMagnifiedBlockBlurPrimaryPx = "window:magnifiedblockblurprimarypx" ConfigKey_WindowMagnifiedBlockBlurSecondaryPx = "window:magnifiedblockblursecondarypx" + ConfigKey_WindowConfirmClose = "window:confirmclose" + ConfigKey_WindowSaveLastWindow = "window:savelastwindow" ConfigKey_TelemetryClear = "telemetry:*" ConfigKey_TelemetryEnabled = "telemetry:enabled" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index b65a73a05..e4325fb66 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -106,6 +106,8 @@ type SettingsType struct { WindowMagnifiedBlockSize *float64 `json:"window:magnifiedblocksize,omitempty"` WindowMagnifiedBlockBlurPrimaryPx *int64 `json:"window:magnifiedblockblurprimarypx,omitempty"` WindowMagnifiedBlockBlurSecondaryPx *int64 `json:"window:magnifiedblockblursecondarypx,omitempty"` + WindowConfirmClose bool `json:"window:confirmclose,omitempty"` + WindowSaveLastWindow bool `json:"window:savelastwindow,omitempty"` TelemetryClear bool `json:"telemetry:*,omitempty"` TelemetryEnabled bool `json:"telemetry:enabled,omitempty"` From 861b431e87f51d3d92cff60322a4c899cb52ede0 Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:03:12 +0000 Subject: [PATCH 29/33] chore: bump package version to 0.10.4-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb34744c4..00bf68310 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.4-beta.1", + "version": "0.10.4-beta.2", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From 1f5634a1b603bfc01caaa86cc4202d97546e01a8 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Fri, 20 Dec 2024 19:00:26 -0500 Subject: [PATCH 30/33] Only create starter workspace on first launch (#1599) --- pkg/wcore/wcore.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/wcore/wcore.go b/pkg/wcore/wcore.go index 780344135..4b7b1558a 100644 --- a/pkg/wcore/wcore.go +++ b/pkg/wcore/wcore.go @@ -25,6 +25,7 @@ func EnsureInitialData() error { ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) defer cancelFn() client, err := wstore.DBGetSingleton[*waveobj.Client](ctx) + firstLaunch := false if err == wstore.ErrNotFound { client, err = CreateClient(ctx) if err != nil { @@ -34,6 +35,7 @@ func EnsureInitialData() error { if migrateErr != nil { log.Printf("error migrating old history: %v\n", migrateErr) } + firstLaunch = true } if client.TempOID == "" { log.Println("client.TempOID is empty") @@ -53,12 +55,16 @@ func EnsureInitialData() error { log.Println("client has windows") return nil } - log.Println("client has no windows, creating starter workspace") - starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", false, true) - if err != nil { - return fmt.Errorf("error creating starter workspace: %w", err) + wsId := "" + if firstLaunch { + log.Println("client has no windows and first launch, creating starter workspace") + starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", false, true) + if err != nil { + return fmt.Errorf("error creating starter workspace: %w", err) + } + wsId = starterWs.OID } - _, err = CreateWindow(ctx, nil, starterWs.OID) + _, err = CreateWindow(ctx, nil, wsId) if err != nil { return fmt.Errorf("error creating window: %w", err) } From 49c438dd461ad968687c10f3985c2418b23bcd3d Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:26:53 +0000 Subject: [PATCH 31/33] chore: bump package version to 0.10.4-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 00bf68310..2492f4314 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.4-beta.2", + "version": "0.10.4-beta.3", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" From 62a610e3dd59fc6e51f370d8eb4058d35892fb63 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:22:15 -0800 Subject: [PATCH 32/33] Release notes for v0.10.4 (#1600) --- docs/docs/releasenotes.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/docs/releasenotes.mdx b/docs/docs/releasenotes.mdx index 4240730e1..fb44f2746 100644 --- a/docs/docs/releasenotes.mdx +++ b/docs/docs/releasenotes.mdx @@ -6,6 +6,15 @@ sidebar_position: 200 # Release Notes +### v0.10.4 — Dec 20, 2024 + +Quick update with bug fixes and new configuration options + +- Added "window:confirmclose" and "window:savelastwindow" configuration options +- [bugfix] Fixed broken scroll bar in the AI widget +- [bugfix] Fixed default path for wsh shell detection (used in remote connections) +- Dependency updates + ### v0.10.3 — Dec 19, 2024 Quick update to v0.10 with new features and bug fixes. From 53a64fc4433cd3da86025b6b47ffc014b2bdb35b Mon Sep 17 00:00:00 2001 From: "wave-builder[bot]" <181805596+wave-builder[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 02:40:53 +0000 Subject: [PATCH 33/33] chore: bump package version to 0.10.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2492f4314..eadcdb21d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.10.4-beta.3", + "version": "0.10.4", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm"