From cf84d777865cbb9e654f8ccd2f7682fd1f257da2 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 4 Nov 2025 10:34:00 -0800 Subject: [PATCH 1/4] unify kebab and context menu --- frontend/app/aipanel/aipanel-contextmenu.ts | 16 ++++++++------ frontend/app/aipanel/aipanel.tsx | 10 +++------ frontend/app/aipanel/aipanelheader.tsx | 24 ++++----------------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index db6edc9577..efd754ff97 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -7,19 +7,21 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WaveAIModel } from "./waveai-model"; -export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose?: () => void): Promise { +export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose: (() => void) | undefined, showCopy: boolean): Promise { e.preventDefault(); e.stopPropagation(); const model = WaveAIModel.getInstance(); const menu: ContextMenuItem[] = []; - const hasSelection = waveAIHasSelection(); - if (hasSelection) { - menu.push({ - role: "copy", - }); - menu.push({ type: "separator" }); + if (showCopy) { + const hasSelection = waveAIHasSelection(); + if (hasSelection) { + menu.push({ + role: "copy", + }); + menu.push({ type: "separator" }); + } } menu.push({ diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 6ef092909d..b7a75961ac 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -256,10 +256,6 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { // console.log("AICHAT messages", messages); - const handleClearChat = useCallback(() => { - model.clearChat(); - }, [model]); - const handleKeyDown = (waveEvent: WaveKeyboardEvent): boolean => { if (checkKeyPressed(waveEvent, "Cmd:k")) { model.clearChat(); @@ -493,7 +489,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { > {(isDragOver || isReactDndDragOver) && } {showBlockMask && } - +
@@ -504,7 +500,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { {messages.length === 0 && initialLoadDone ? (
handleWaveAIContextMenu(e, onClose)} + onContextMenu={(e) => handleWaveAIContextMenu(e, onClose, true)} > {model.inBuilder ? : }
@@ -512,7 +508,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { handleWaveAIContextMenu(e, onClose)} + onContextMenu={(e) => handleWaveAIContextMenu(e, onClose, true)} /> )} {errorMessage && ( diff --git a/frontend/app/aipanel/aipanelheader.tsx b/frontend/app/aipanel/aipanelheader.tsx index d37287cb0d..55fb4287e5 100644 --- a/frontend/app/aipanel/aipanelheader.tsx +++ b/frontend/app/aipanel/aipanelheader.tsx @@ -1,38 +1,22 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { ContextMenuModel } from "@/app/store/contextmenu"; +import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu"; import { useAtomValue } from "jotai"; import { memo } from "react"; import { WaveAIModel } from "./waveai-model"; interface AIPanelHeaderProps { onClose?: () => void; - model: WaveAIModel; - onClearChat?: () => void; } -export const AIPanelHeader = memo(({ onClose, model, onClearChat }: AIPanelHeaderProps) => { +export const AIPanelHeader = memo(({ onClose }: AIPanelHeaderProps) => { + const model = WaveAIModel.getInstance(); const widgetAccess = useAtomValue(model.widgetAccessAtom); const inBuilder = model.inBuilder; const handleKebabClick = (e: React.MouseEvent) => { - const menu: ContextMenuItem[] = [ - { - label: "New Chat", - click: () => { - onClearChat?.(); - }, - }, - { type: "separator" }, - { - label: "Hide Wave AI", - click: () => { - onClose?.(); - }, - }, - ]; - ContextMenuModel.showContextMenu(menu, e); + handleWaveAIContextMenu(e, onClose, false); }; return ( From c7aed9cee8f7a9ba6359b69ee598b43750db1d81 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 4 Nov 2025 10:40:00 -0800 Subject: [PATCH 2/4] handle panel closing in the model. remove Hide Wave AI option from context menu if in builder... --- frontend/app/aipanel/aipanel-contextmenu.ts | 18 ++++++++++-------- frontend/app/aipanel/aipanel.tsx | 6 +++--- frontend/app/aipanel/aipanelheader.tsx | 8 ++------ frontend/app/aipanel/waveai-model.tsx | 14 ++++++++++++++ 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index efd754ff97..9c81828baf 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -7,7 +7,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WaveAIModel } from "./waveai-model"; -export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose: (() => void) | undefined, showCopy: boolean): Promise { +export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boolean): Promise { e.preventDefault(); e.stopPropagation(); @@ -152,14 +152,16 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose: (() submenu: maxTokensSubmenu, }); - menu.push({ type: "separator" }); + if (model.canCloseWaveAIPanel()) { + menu.push({ type: "separator" }); - menu.push({ - label: "Hide Wave AI", - click: () => { - onClose?.(); - }, - }); + menu.push({ + label: "Hide Wave AI", + click: () => { + model.closeWaveAIPanel(); + }, + }); + } ContextMenuModel.showContextMenu(menu, e); } \ No newline at end of file diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index b7a75961ac..2afcbdb30b 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -489,7 +489,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { > {(isDragOver || isReactDndDragOver) && } {showBlockMask && } - +
@@ -500,7 +500,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { {messages.length === 0 && initialLoadDone ? (
handleWaveAIContextMenu(e, onClose, true)} + onContextMenu={(e) => handleWaveAIContextMenu(e, true)} > {model.inBuilder ? : }
@@ -508,7 +508,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { handleWaveAIContextMenu(e, onClose, true)} + onContextMenu={(e) => handleWaveAIContextMenu(e, true)} /> )} {errorMessage && ( diff --git a/frontend/app/aipanel/aipanelheader.tsx b/frontend/app/aipanel/aipanelheader.tsx index 55fb4287e5..d6b5d43cb2 100644 --- a/frontend/app/aipanel/aipanelheader.tsx +++ b/frontend/app/aipanel/aipanelheader.tsx @@ -6,17 +6,13 @@ import { useAtomValue } from "jotai"; import { memo } from "react"; import { WaveAIModel } from "./waveai-model"; -interface AIPanelHeaderProps { - onClose?: () => void; -} - -export const AIPanelHeader = memo(({ onClose }: AIPanelHeaderProps) => { +export const AIPanelHeader = memo(() => { const model = WaveAIModel.getInstance(); const widgetAccess = useAtomValue(model.widgetAccessAtom); const inBuilder = model.inBuilder; const handleKebabClick = (e: React.MouseEvent) => { - handleWaveAIContextMenu(e, onClose, false); + handleWaveAIContextMenu(e, false); }; return ( diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index 3daeb4872c..d19f890586 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -542,4 +542,18 @@ export class WaveAIModel { globalStore.set(this.restoreBackupStatus, "error"); } } + + canCloseWaveAIPanel(): boolean { + if (this.inBuilder) { + return false; + } + return true; + } + + closeWaveAIPanel() { + if (this.inBuilder) { + return; + } + WorkspaceLayoutModel.getInstance().setAIPanelVisible(false); + } } From 78e3c64ce0349928b81e6ac7005dd24df9e43286 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 4 Nov 2025 10:42:04 -0800 Subject: [PATCH 3/4] remove now unused onClose prop everywhere --- frontend/app/aipanel/aipanel.tsx | 10 +++------- frontend/app/workspace/workspace.tsx | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 2afcbdb30b..1fb3c05ba4 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -203,11 +203,7 @@ const AIErrorMessage = memo(({ errorMessage, onClear }: AIErrorMessageProps) => AIErrorMessage.displayName = "AIErrorMessage"; -interface AIPanelProps { - onClose?: () => void; -} - -const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { +const AIPanelComponentInner = memo(() => { const [isDragOver, setIsDragOver] = useState(false); const [isReactDndDragOver, setIsReactDndDragOver] = useState(false); const [initialLoadDone, setInitialLoadDone] = useState(false); @@ -525,10 +521,10 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => { AIPanelComponentInner.displayName = "AIPanelInner"; -const AIPanelComponent = ({ onClose }: AIPanelProps) => { +const AIPanelComponent = () => { return ( - + ); }; diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index e624e5f2a4..3a74047462 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -58,7 +58,7 @@ const WorkspaceElem = memo(() => { >
- workspaceLayoutModel.setAIPanelVisible(false)} /> +
From 1542262c4a6706b62d0b73adb3d6a8e62ef815c2 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 4 Nov 2025 13:56:01 -0800 Subject: [PATCH 4/4] add 1k output tokens (just in dev mode for testing) --- frontend/app/aipanel/aipanel-contextmenu.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index 9c81828baf..a783af7a4c 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -3,6 +3,7 @@ import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils"; import { ContextMenuModel } from "@/app/store/contextmenu"; +import { isDev } from "@/app/store/global"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WaveAIModel } from "./waveai-model"; @@ -105,6 +106,19 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } ); } else { + if (isDev()) { + maxTokensSubmenu.push({ + label: "1k (Dev Testing)", + type: "checkbox", + checked: currentMaxTokens === 1024, + click: () => { + RpcApi.SetRTInfoCommand(TabRpcClient, { + oref: model.orefContext, + data: { "waveai:maxoutputtokens": 1024 }, + }); + }, + }); + } maxTokensSubmenu.push( { label: "4k", @@ -164,4 +178,4 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } ContextMenuModel.showContextMenu(menu, e); -} \ No newline at end of file +}