diff --git a/docs/docs/waveai.mdx b/docs/docs/waveai.mdx index 6e3463d8bd..8d2da28b15 100644 --- a/docs/docs/waveai.mdx +++ b/docs/docs/waveai.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3.4 +sidebar_position: 1.5 id: "waveai" title: "Wave AI" --- diff --git a/emain/emain-window.ts b/emain/emain-window.ts index 4ceb6782d4..6aa3b9e31c 100644 --- a/emain/emain-window.ts +++ b/emain/emain-window.ts @@ -24,6 +24,7 @@ import { updater } from "./updater"; export type WindowOpts = { unamePlatform: string; + isPrimaryStartupWindow?: boolean; }; const MIN_WINDOW_WIDTH = 800; @@ -36,6 +37,7 @@ export const waveWindowMap = new Map(); // waveWindow export let focusedWaveWindow: WaveBrowserWindow = null; let cachedClientId: string = null; +let hasCompletedFirstRelaunch = false; async function getClientId() { if (cachedClientId != null) { @@ -51,6 +53,7 @@ type WindowActionQueueEntry = op: "switchtab"; tabId: string; setInBackend: boolean; + primaryStartupTab?: boolean; } | { op: "createtab"; @@ -346,31 +349,35 @@ export class WaveBrowserWindow extends BaseWindow { await this._queueActionInternal({ op: "switchworkspace", workspaceId }); } - async setActiveTab(tabId: string, setInBackend: boolean) { - console.log("setActiveTab", tabId, this.waveWindowId, this.workspaceId, setInBackend); - await this._queueActionInternal({ op: "switchtab", tabId, setInBackend }); + async setActiveTab(tabId: string, setInBackend: boolean, primaryStartupTab = false) { + console.log("setActiveTab", tabId, this.waveWindowId, this.workspaceId, setInBackend, primaryStartupTab ? "(primary startup)" : ""); + await this._queueActionInternal({ op: "switchtab", tabId, setInBackend, primaryStartupTab }); } - private async initializeTab(tabView: WaveTabView) { + private async initializeTab(tabView: WaveTabView, primaryStartupTab: boolean) { const clientId = await getClientId(); await tabView.initPromise; this.contentView.addChildView(tabView); - const initOpts = { + const initOpts: WaveInitOpts = { tabId: tabView.waveTabId, clientId: clientId, windowId: this.waveWindowId, activate: true, }; + if (primaryStartupTab) { + initOpts.primaryTabStartup = true; + } tabView.savedInitOpts = { ...initOpts }; tabView.savedInitOpts.activate = false; + delete tabView.savedInitOpts.primaryTabStartup; let startTime = Date.now(); - console.log("before wave ready, init tab, sending wave-init", tabView.waveTabId); + console.log("before wave ready, init tab, sending wave-init", tabView.waveTabId, primaryStartupTab ? "(primary startup)" : ""); tabView.webContents.send("wave-init", initOpts); await tabView.waveReadyPromise; console.log("wave-ready init time", Date.now() - startTime + "ms"); } - private async setTabViewIntoWindow(tabView: WaveTabView, tabInitialized: boolean) { + private async setTabViewIntoWindow(tabView: WaveTabView, tabInitialized: boolean, primaryStartupTab = false) { if (this.activeTabView == tabView) { return; } @@ -382,8 +389,8 @@ export class WaveBrowserWindow extends BaseWindow { this.activeTabView = tabView; this.allLoadedTabViews.set(tabView.waveTabId, tabView); if (!tabInitialized) { - console.log("initializing a new tab"); - const p1 = this.initializeTab(tabView); + console.log("initializing a new tab", primaryStartupTab ? "(primary startup)" : ""); + const p1 = this.initializeTab(tabView, primaryStartupTab); const p2 = this.repositionTabsSlowly(100); await Promise.all([p1, p2]); } else { @@ -541,7 +548,8 @@ export class WaveBrowserWindow extends BaseWindow { return; } const [tabView, tabInitialized] = await getOrCreateWebViewForTab(this.waveWindowId, tabId); - await this.setTabViewIntoWindow(tabView, tabInitialized); + const primaryStartupTabFlag = entry.op === "switchtab" ? entry.primaryStartupTab ?? false : false; + await this.setTabViewIntoWindow(tabView, tabInitialized, primaryStartupTabFlag); } catch (e) { console.log("error caught in processActionQueue", e); } finally { @@ -628,6 +636,7 @@ export async function createWindowForWorkspace(workspaceId: string) { } const newBwin = await createBrowserWindow(newWin, await RpcApi.GetFullConfigCommand(ElectronWshClient), { unamePlatform, + isPrimaryStartupWindow: false, }); newBwin.show(); } @@ -653,7 +662,7 @@ export async function createBrowserWindow( console.log("createBrowserWindow", waveWindow.oid, workspace.oid, workspace); const bwin = new WaveBrowserWindow(waveWindow, fullConfig, opts); if (workspace.activetabid) { - await bwin.setActiveTab(workspace.activetabid, false); + await bwin.setActiveTab(workspace.activetabid, false, opts.isPrimaryStartupWindow ?? false); } return bwin; } @@ -764,7 +773,10 @@ export async function createNewWaveWindow() { const existingWindowId = clientData.windowids[0]; const existingWindowData = (await ObjectService.GetObject("window:" + existingWindowId)) as WaveWindow; if (existingWindowData != null) { - const win = await createBrowserWindow(existingWindowData, fullConfig, { unamePlatform }); + const win = await createBrowserWindow(existingWindowData, fullConfig, { + unamePlatform, + isPrimaryStartupWindow: false, + }); win.show(); recreatedWindow = true; } @@ -774,7 +786,10 @@ export async function createNewWaveWindow() { return; } console.log("creating new window"); - const newBrowserWindow = await createBrowserWindow(null, fullConfig, { unamePlatform }); + const newBrowserWindow = await createBrowserWindow(null, fullConfig, { + unamePlatform, + isPrimaryStartupWindow: false, + }); newBrowserWindow.show(); } @@ -793,18 +808,26 @@ export async function relaunchBrowserWindows() { const clientData = await ClientService.GetClientData(); const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient); + const windowIds = clientData.windowids ?? []; const wins: WaveBrowserWindow[] = []; - for (const windowId of clientData.windowids.slice().reverse()) { + const isFirstRelaunch = !hasCompletedFirstRelaunch; + const primaryWindowId = windowIds.length > 0 ? windowIds[0] : null; + for (const windowId of windowIds.slice().reverse()) { const windowData: WaveWindow = await WindowService.GetWindow(windowId); if (windowData == null) { console.log("relaunch -- window data not found, closing window", windowId); await WindowService.CloseWindow(windowId, true); continue; } - console.log("relaunch -- creating window", windowId, windowData); - const win = await createBrowserWindow(windowData, fullConfig, { unamePlatform }); + const isPrimaryStartupWindow = isFirstRelaunch && windowId === primaryWindowId; + console.log("relaunch -- creating window", windowId, windowData, isPrimaryStartupWindow ? "(primary startup)" : ""); + const win = await createBrowserWindow(windowData, fullConfig, { + unamePlatform, + isPrimaryStartupWindow + }); wins.push(win); } + hasCompletedFirstRelaunch = true; for (const win of wins) { console.log("show window", win.waveWindowId); win.show(); diff --git a/emain/emain-wsh.ts b/emain/emain-wsh.ts index ecf34bc406..2b171e7b21 100644 --- a/emain/emain-wsh.ts +++ b/emain/emain-wsh.ts @@ -52,7 +52,10 @@ export class ElectronWshClientType extends WshClient { if (window == null) { throw new Error(`window ${windowId} not found`); } - ww = await createBrowserWindow(window, fullConfig, { unamePlatform }); + ww = await createBrowserWindow(window, fullConfig, { + unamePlatform, + isPrimaryStartupWindow: false, + }); } ww.focus(); } diff --git a/emain/emain.ts b/emain/emain.ts index 5e0748e390..81c3c529df 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -98,7 +98,10 @@ function handleWSEvent(evtMsg: WSEventType) { return; } const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient); - const newWin = await createBrowserWindow(windowData, fullConfig, { unamePlatform }); + const newWin = await createBrowserWindow(windowData, fullConfig, { + unamePlatform, + isPrimaryStartupWindow: false, + }); newWin.show(); } else if (evtMsg.eventtype == "electron:closewindow") { console.log("electron:closewindow", evtMsg.data); diff --git a/frontend/app/aipanel/telemetryrequired.tsx b/frontend/app/aipanel/telemetryrequired.tsx index 88d08f1d9f..8124a621c0 100644 --- a/frontend/app/aipanel/telemetryrequired.tsx +++ b/frontend/app/aipanel/telemetryrequired.tsx @@ -5,6 +5,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { cn } from "@/util/util"; import { useState } from "react"; +import { WaveAIModel } from "./waveai-model"; interface TelemetryRequiredMessageProps { className?: string; @@ -17,6 +18,9 @@ const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps) setIsEnabling(true); try { await RpcApi.WaveAIEnableTelemetryCommand(TabRpcClient); + setTimeout(() => { + WaveAIModel.getInstance().focusInput(); + }, 100); } catch (error) { console.error("Failed to enable telemetry:", error); setIsEnabling(false); diff --git a/frontend/app/modals/modalregistry.tsx b/frontend/app/modals/modalregistry.tsx index d45a1699d7..4733d33cff 100644 --- a/frontend/app/modals/modalregistry.tsx +++ b/frontend/app/modals/modalregistry.tsx @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import { MessageModal } from "@/app/modals/messagemodal"; -import { OnboardingModal } from "@/app/onboarding/onboarding"; +import { NewInstallOnboardingModal } from "@/app/onboarding/onboarding"; +import { UpgradeOnboardingModal } from "@/app/onboarding/onboarding-upgrade"; import { AboutModal } from "./about"; import { UserInputModal } from "./userinputmodal"; const modalRegistry: { [key: string]: React.ComponentType } = { - [OnboardingModal.displayName || "OnboardingModal"]: OnboardingModal, + [NewInstallOnboardingModal.displayName || "NewInstallOnboardingModal"]: NewInstallOnboardingModal, + [UpgradeOnboardingModal.displayName || "UpgradeOnboardingModal"]: UpgradeOnboardingModal, [UserInputModal.displayName || "UserInputModal"]: UserInputModal, [AboutModal.displayName || "AboutModal"]: AboutModal, [MessageModal.displayName || "MessageModal"]: MessageModal, diff --git a/frontend/app/modals/modalsrenderer.tsx b/frontend/app/modals/modalsrenderer.tsx index a0b2620a1b..e8a95ea925 100644 --- a/frontend/app/modals/modalsrenderer.tsx +++ b/frontend/app/modals/modalsrenderer.tsx @@ -1,16 +1,20 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { OnboardingModal } from "@/app/onboarding/onboarding"; -import { atoms, globalStore } from "@/store/global"; +import { NewInstallOnboardingModal } from "@/app/onboarding/onboarding"; +import { CurrentOnboardingVersion } from "@/app/onboarding/onboarding-features"; +import { UpgradeOnboardingModal } from "@/app/onboarding/onboarding-upgrade"; +import { atoms, globalPrimaryTabStartup, globalStore } from "@/store/global"; import { modalsModel } from "@/store/modalmodel"; import * as jotai from "jotai"; import { useEffect } from "react"; +import semver from "semver"; import { getModalComponent } from "./modalregistry"; const ModalsRenderer = () => { const clientData = jotai.useAtomValue(atoms.client); - const [tosOpen, setTosOpen] = jotai.useAtom(modalsModel.tosOpen); + const [newInstallOnboardingOpen, setNewInstallOnboardingOpen] = jotai.useAtom(modalsModel.newInstallOnboardingOpen); + const [upgradeOnboardingOpen, setUpgradeOnboardingOpen] = jotai.useAtom(modalsModel.upgradeOnboardingOpen); const [modals] = jotai.useAtom(modalsModel.modalsAtom); const rtn: React.ReactElement[] = []; for (const modal of modals) { @@ -19,14 +23,30 @@ const ModalsRenderer = () => { rtn.push(); } } - if (tosOpen) { - rtn.push(); + if (newInstallOnboardingOpen) { + rtn.push(); + } + if (upgradeOnboardingOpen) { + rtn.push(); } useEffect(() => { if (!clientData.tosagreed) { - setTosOpen(true); + setNewInstallOnboardingOpen(true); } }, [clientData]); + + useEffect(() => { + if (!globalPrimaryTabStartup) { + return; + } + if (!clientData.tosagreed) { + return; + } + const lastVersion = clientData.meta?.["onboarding:lastversion"] ?? "v0.0.0"; + if (semver.lt(lastVersion, CurrentOnboardingVersion)) { + setUpgradeOnboardingOpen(true); + } + }, []); useEffect(() => { globalStore.set(atoms.modalOpen, rtn.length > 0); }, [rtn]); diff --git a/frontend/app/onboarding/onboarding-features.tsx b/frontend/app/onboarding/onboarding-features.tsx index 794a82b791..2b88f7cb45 100644 --- a/frontend/app/onboarding/onboarding-features.tsx +++ b/frontend/app/onboarding/onboarding-features.tsx @@ -5,6 +5,8 @@ import Logo from "@/app/asset/logo.svg"; import { Button } from "@/app/element/button"; import { EmojiButton } from "@/app/element/emojibutton"; import { MagnifyIcon } from "@/app/element/magnify"; +import { atoms, globalStore } from "@/app/store/global"; +import * as WOS from "@/app/store/wos"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { isMacOS } from "@/util/platformutil"; @@ -13,6 +15,8 @@ import { FakeChat } from "./fakechat"; import { EditBashrcCommand, ViewLogoCommand, ViewShortcutsCommand } from "./onboarding-command"; import { FakeLayout } from "./onboarding-layout"; +export const CurrentOnboardingVersion = "v0.12.0"; + type FeaturePageName = "waveai" | "magnify" | "files"; const OnboardingFooter = ({ @@ -72,6 +76,7 @@ const WaveAIPage = ({ onNext, onSkip }: { onNext: () => void; onSkip: () => void event: "onboarding:fire", props: { "onboarding:feature": "waveai", + "onboarding:version": CurrentOnboardingVersion, }, }); } @@ -158,6 +163,7 @@ const MagnifyBlocksPage = ({ event: "onboarding:fire", props: { "onboarding:feature": "magnify", + "onboarding:version": CurrentOnboardingVersion, }, }); } @@ -216,6 +222,7 @@ const FilesPage = ({ onFinish, onPrev }: { onFinish: () => void; onPrev?: () => event: "onboarding:fire", props: { "onboarding:feature": "wsh", + "onboarding:version": CurrentOnboardingVersion, }, }); } @@ -300,9 +307,16 @@ export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) = const [currentPage, setCurrentPage] = useState("waveai"); useEffect(() => { + const clientId = globalStore.get(atoms.clientId); + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("client", clientId), + meta: { "onboarding:lastversion": CurrentOnboardingVersion }, + }); RpcApi.RecordTEventCommand(TabRpcClient, { event: "onboarding:start", - props: {}, + props: { + "onboarding:version": CurrentOnboardingVersion, + }, }); }, []); diff --git a/frontend/app/onboarding/onboarding-upgrade.tsx b/frontend/app/onboarding/onboarding-upgrade.tsx new file mode 100644 index 0000000000..be46238964 --- /dev/null +++ b/frontend/app/onboarding/onboarding-upgrade.tsx @@ -0,0 +1,171 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import Logo from "@/app/asset/logo.svg"; +import { Button } from "@/app/element/button"; +import { FlexiModal } from "@/app/modals/modal"; +import { OnboardingFeatures } from "@/app/onboarding/onboarding-features"; +import { atoms, globalStore } from "@/app/store/global"; +import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel"; +import { modalsModel } from "@/app/store/modalmodel"; +import * as WOS from "@/app/store/wos"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; +import { useEffect, useRef, useState } from "react"; +import { debounce } from "throttle-debounce"; + +const UpgradeOnboardingModal = () => { + const modalRef = useRef(null); + const [pageName, setPageName] = useState<"welcome" | "features">("welcome"); + const [isCompact, setIsCompact] = useState(window.innerHeight < 800); + + const updateModalHeight = () => { + const windowHeight = window.innerHeight; + setIsCompact(windowHeight < 800); + if (modalRef.current) { + const modalHeight = modalRef.current.offsetHeight; + const maxHeight = windowHeight * 0.9; + if (maxHeight < modalHeight) { + modalRef.current.style.height = `${maxHeight}px`; + } else { + modalRef.current.style.height = "auto"; + } + } + }; + + useEffect(() => { + updateModalHeight(); + const debouncedUpdateModalHeight = debounce(150, updateModalHeight); + window.addEventListener("resize", debouncedUpdateModalHeight); + return () => { + window.removeEventListener("resize", debouncedUpdateModalHeight); + }; + }, []); + + useEffect(() => { + disableGlobalKeybindings(); + return () => { + enableGlobalKeybindings(); + }; + }, []); + + const handleStarClick = async () => { + const clientId = globalStore.get(atoms.clientId); + await RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("client", clientId), + meta: { "onboarding:githubstar": true }, + }); + window.open("https://github.com/wavetermdev/waveterm", "_blank"); + setPageName("features"); + }; + + const handleAlreadyStarred = async () => { + const clientId = globalStore.get(atoms.clientId); + await RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("client", clientId), + meta: { "onboarding:githubstar": true }, + }); + setPageName("features"); + }; + + const handleMaybeLater = async () => { + const clientId = globalStore.get(atoms.clientId); + await RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("client", clientId), + meta: { "onboarding:githubstar": false }, + }); + setPageName("features"); + }; + + const handleFeaturesComplete = () => { + globalStore.set(modalsModel.upgradeOnboardingOpen, false); + setTimeout(() => { + globalRefocus(); + }, 10); + }; + + let pageComp: React.JSX.Element = null; + if (pageName === "welcome") { + pageComp = ( +
+
+
+ +
+
Welcome to Wave v0.12!
+
+ +
+
+
+ + Wave AI +
+
+

+ Wave AI is your new terminal assistant with full context. It can read your terminal + output, analyze widgets, access files, and help you solve problems faster. +

+

+ Wave AI is in active beta with included AI credits while we refine the experience. + We're actively improving it and would love your feedback in{" "} + + Discord + + . +

+
+
+ +
+ +
+
Thanks for being an early Wave adopter! ⭐
+
+ A GitHub star shows your support for Wave (and open-source) and helps us reach more + developers. +
+
+
+
+
+
+ + + +
+
+
+ ); + } else if (pageName === "features") { + pageComp = ; + } + + if (pageComp == null) { + return null; + } + + const paddingClass = isCompact ? "!py-3 !px-[30px]" : "!p-[30px]"; + const widthClass = pageName === "features" ? "w-[800px]" : "w-[560px]"; + + return ( + +
+
{pageComp}
+ + ); +}; + +UpgradeOnboardingModal.displayName = "UpgradeOnboardingModal"; + +export { UpgradeOnboardingModal }; diff --git a/frontend/app/onboarding/onboarding.tsx b/frontend/app/onboarding/onboarding.tsx index 350e11c241..31fde305fd 100644 --- a/frontend/app/onboarding/onboarding.tsx +++ b/frontend/app/onboarding/onboarding.tsx @@ -202,10 +202,10 @@ const NoTelemetryStarPage = ({ isCompact }: { isCompact: boolean }) => { }; const FeaturesPage = () => { - const [tosOpen, setTosOpen] = useAtom(modalsModel.tosOpen); + const [newInstallOnboardingOpen, setNewInstallOnboardingOpen] = useAtom(modalsModel.newInstallOnboardingOpen); const handleComplete = () => { - setTosOpen(false); + setNewInstallOnboardingOpen(false); setTimeout(() => { globalRefocus(); }, 10); @@ -214,7 +214,7 @@ const FeaturesPage = () => { return ; }; -const OnboardingModal = () => { +const NewInstallOnboardingModal = () => { const modalRef = useRef(null); const [pageName, setPageName] = useAtom(pageNameAtom); const clientData = useAtomValue(atoms.client); @@ -279,12 +279,13 @@ const OnboardingModal = () => { const widthClass = pageName === "features" ? "w-[800px]" : "w-[560px]"; return ( - -
{pageComp}
+ +
+
{pageComp}
); }; -OnboardingModal.displayName = "OnboardingModal"; +NewInstallOnboardingModal.displayName = "NewInstallOnboardingModal"; -export { OnboardingModal }; +export { NewInstallOnboardingModal }; diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 146721ec6d..425f8ee7d3 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -27,6 +27,7 @@ import { getFileSubject, waveEventSubscribe } from "./wps"; let atoms: GlobalAtomsType; let globalEnvironment: "electron" | "renderer"; +let globalPrimaryTabStartup: boolean = false; const blockComponentModelMap = new Map(); const Counters = new Map(); const ConnStatusMapAtom = atom(new Map>()); @@ -39,10 +40,12 @@ type GlobalInitOptions = { windowId: string; clientId: string; environment: "electron" | "renderer"; + primaryTabStartup?: boolean; }; function initGlobal(initOpts: GlobalInitOptions) { globalEnvironment = initOpts.environment; + globalPrimaryTabStartup = initOpts.primaryTabStartup ?? false; setPlatform(initOpts.platform); initGlobalAtoms(initOpts); } @@ -814,6 +817,7 @@ export { getSettingsPrefixAtom, getTabMetaKeyAtom, getUserName, + globalPrimaryTabStartup, globalStore, initGlobal, initGlobalWaveEventSubs, diff --git a/frontend/app/store/modalmodel.ts b/frontend/app/store/modalmodel.ts index 8d23de1a5c..a48db4fb4f 100644 --- a/frontend/app/store/modalmodel.ts +++ b/frontend/app/store/modalmodel.ts @@ -6,10 +6,12 @@ import { globalStore } from "./global"; class ModalsModel { modalsAtom: jotai.PrimitiveAtom>; - tosOpen: jotai.PrimitiveAtom; + newInstallOnboardingOpen: jotai.PrimitiveAtom; + upgradeOnboardingOpen: jotai.PrimitiveAtom; constructor() { - this.tosOpen = jotai.atom(false); + this.newInstallOnboardingOpen = jotai.atom(false); + this.upgradeOnboardingOpen = jotai.atom(false); this.modalsAtom = jotai.atom([]); } diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index 0067ea863a..a55814ca56 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -742,6 +742,9 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) const setSearchIndex = useSetAtom(searchProps.resultsIndex); const setNumSearchResults = useSetAtom(searchProps.resultsCount); searchProps.onSearch = useCallback((search: string) => { + if (!globalStore.get(model.domReady)) { + return; + } try { if (search) { model.webviewRef.current?.findInPage(search, { findNext: true }); @@ -753,6 +756,9 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) } }, []); searchProps.onNext = useCallback(() => { + if (!globalStore.get(model.domReady)) { + return; + } try { console.log("search next", searchVal); model.webviewRef.current?.findInPage(searchVal, { findNext: false, forward: true }); @@ -761,6 +767,9 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) } }, [searchVal]); searchProps.onPrev = useCallback(() => { + if (!globalStore.get(model.domReady)) { + return; + } try { console.log("search prev", searchVal); model.webviewRef.current?.findInPage(searchVal, { findNext: false, forward: false }); diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 3117291753..52a5f50d34 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -59,6 +59,7 @@ declare global { clientId: string; windowId: string; activate: boolean; + primaryTabStartup?: boolean; }; type ElectronApi = { diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 65f4d42950..fd0cfd0a4e 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -668,6 +668,7 @@ declare global { "vdom:route"?: string; "vdom:persist"?: boolean; "onboarding:githubstar"?: boolean; + "onboarding:lastversion"?: string; count?: number; }; @@ -935,6 +936,7 @@ declare global { "wsh:haderror"?: boolean; "conn:conntype"?: string; "onboarding:feature"?: "waveai" | "magnify" | "wsh"; + "onboarding:version"?: string; "display:height"?: number; "display:width"?: number; "display:dpr"?: number; diff --git a/frontend/wave.ts b/frontend/wave.ts index 9263b3a845..ec1a14b08a 100644 --- a/frontend/wave.ts +++ b/frontend/wave.ts @@ -164,6 +164,7 @@ async function initWave(initOpts: WaveInitOpts) { windowId: initOpts.windowId, platform, environment: "renderer", + primaryTabStartup: initOpts.primaryTabStartup, }); (window as any).globalAtoms = atoms; diff --git a/package-lock.json b/package-lock.json index dc23c81a4d..74aec0396c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.12.0-beta.1", + "version": "0.12.0-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.12.0-beta.1", + "version": "0.12.0-beta.2", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ @@ -74,6 +74,7 @@ "remark-gfm": "^4.0.1", "remark-github-blockquote-alert": "^1.3.1", "rxjs": "^7.8.2", + "semver": "^7.7.3", "shell-quote": "^1.8.3", "shiki": "^3.13.0", "sprintf-js": "^1.1.3", @@ -130,7 +131,6 @@ "prettier-plugin-jsdoc": "^1.3.3", "prettier-plugin-organize-imports": "^4.3.0", "sass": "1.91.0", - "semver": "^7.7.3", "storybook": "^8.6.14", "storybook-dark-mode": "^4.0.2", "tailwindcss": "^4.1.14", diff --git a/package.json b/package.json index c9b66b410f..ebaf3f0b81 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "prettier-plugin-jsdoc": "^1.3.3", "prettier-plugin-organize-imports": "^4.3.0", "sass": "1.91.0", - "semver": "^7.7.3", "storybook": "^8.6.14", "storybook-dark-mode": "^4.0.2", "tailwindcss": "^4.1.14", @@ -148,6 +147,7 @@ "rehype-slug": "^6.0.0", "remark-flexible-toc": "^1.2.3", "remark-gfm": "^4.0.1", + "semver": "^7.7.3", "remark-github-blockquote-alert": "^1.3.1", "rxjs": "^7.8.2", "shell-quote": "^1.8.3", diff --git a/pkg/aiusechat/tools_readdir.go b/pkg/aiusechat/tools_readdir.go index a0689904e4..b887427493 100644 --- a/pkg/aiusechat/tools_readdir.go +++ b/pkg/aiusechat/tools_readdir.go @@ -184,7 +184,7 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition { return uctypes.ToolDefinition{ Name: "read_dir", DisplayName: "Read Directory", - Description: "Read a directory from the filesystem and list its contents. Returns information about files and subdirectories including names, types, sizes, permissions, and modification times. Requires user approval.", + Description: "Read a directory from the filesystem and list its contents. Returns information about files and subdirectories including names, types, sizes, permissions, and modification times.", ToolLogName: "gen:readdir", Strict: false, InputSchema: map[string]any{ diff --git a/pkg/aiusechat/tools_readfile.go b/pkg/aiusechat/tools_readfile.go index 3aab51f730..cab94fd26c 100644 --- a/pkg/aiusechat/tools_readfile.go +++ b/pkg/aiusechat/tools_readfile.go @@ -194,7 +194,7 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition { return uctypes.ToolDefinition{ Name: "read_text_file", DisplayName: "Read Text File", - Description: "Read a text file from the filesystem. Can read specific line ranges or from the end. Detects and rejects binary files. Requires user approval.", + Description: "Read a text file from the filesystem. Can read specific line ranges or from the end. Detects and rejects binary files.", ToolLogName: "gen:readfile", Strict: false, InputSchema: map[string]any{ diff --git a/pkg/telemetry/telemetrydata/telemetrydata.go b/pkg/telemetry/telemetrydata/telemetrydata.go index c0cd01baa7..6b3a90e908 100644 --- a/pkg/telemetry/telemetrydata/telemetrydata.go +++ b/pkg/telemetry/telemetrydata/telemetrydata.go @@ -90,6 +90,7 @@ type TEventProps struct { WshHadError bool `json:"wsh:haderror,omitempty"` ConnType string `json:"conn:conntype,omitempty"` OnboardingFeature string `json:"onboarding:feature,omitempty" tstype:"\"waveai\" | \"magnify\" | \"wsh\""` + OnboardingVersion string `json:"onboarding:version,omitempty"` DisplayHeight int `json:"display:height,omitempty"` DisplayWidth int `json:"display:width,omitempty"` diff --git a/pkg/waveobj/metaconsts.go b/pkg/waveobj/metaconsts.go index bbd05a1701..e35d21e167 100644 --- a/pkg/waveobj/metaconsts.go +++ b/pkg/waveobj/metaconsts.go @@ -133,6 +133,7 @@ const ( MetaKey_VDomPersist = "vdom:persist" MetaKey_OnboardingGithubStar = "onboarding:githubstar" + MetaKey_OnboardingLastVersion = "onboarding:lastversion" MetaKey_Count = "count" ) diff --git a/pkg/waveobj/wtypemeta.go b/pkg/waveobj/wtypemeta.go index c8b73619cb..7bb357b9e7 100644 --- a/pkg/waveobj/wtypemeta.go +++ b/pkg/waveobj/wtypemeta.go @@ -136,7 +136,8 @@ type MetaTSType struct { VDomRoute string `json:"vdom:route,omitempty"` VDomPersist bool `json:"vdom:persist,omitempty"` - OnboardingGithubStar bool `json:"onboarding:githubstar,omitempty"` // for client + OnboardingGithubStar bool `json:"onboarding:githubstar,omitempty"` // for client + OnboardingLastVersion string `json:"onboarding:lastversion,omitempty"` // for client (tracks semver of last 'onboarding' shown) Count int `json:"count,omitempty"` // temp for cpu plot. will remove later }