Make WaveAI context menu consistent with kebab menu (#2517)

This commit is contained in:
Mike Sawka 2025-11-04 13:56:42 -08:00 committed by GitHub
parent a5fb38e640
commit 2c53393cda
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 58 additions and 54 deletions

View file

@ -3,23 +3,26 @@
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";
export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose?: () => void): Promise<void> {
export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boolean): Promise<void> {
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({
@ -103,6 +106,19 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose?: ()
}
);
} 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",
@ -150,14 +166,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);
}
}

View file

@ -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);
@ -256,10 +252,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 +485,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => {
>
{(isDragOver || isReactDndDragOver) && <AIDragOverlay />}
{showBlockMask && <AIBlockMask />}
<AIPanelHeader onClose={onClose} model={model} onClearChat={handleClearChat} />
<AIPanelHeader />
<AIRateLimitStrip />
<div key="main-content" className="flex-1 flex flex-col min-h-0">
@ -504,7 +496,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => {
{messages.length === 0 && initialLoadDone ? (
<div
className="flex-1 overflow-y-auto p-2"
onContextMenu={(e) => handleWaveAIContextMenu(e, onClose)}
onContextMenu={(e) => handleWaveAIContextMenu(e, true)}
>
{model.inBuilder ? <AIBuilderWelcomeMessage /> : <AIWelcomeMessage />}
</div>
@ -512,7 +504,7 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => {
<AIPanelMessages
messages={messages}
status={status}
onContextMenu={(e) => handleWaveAIContextMenu(e, onClose)}
onContextMenu={(e) => handleWaveAIContextMenu(e, true)}
/>
)}
{errorMessage && (
@ -529,10 +521,10 @@ const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => {
AIPanelComponentInner.displayName = "AIPanelInner";
const AIPanelComponent = ({ onClose }: AIPanelProps) => {
const AIPanelComponent = () => {
return (
<ErrorBoundary>
<AIPanelComponentInner onClose={onClose} />
<AIPanelComponentInner />
</ErrorBoundary>
);
};

View file

@ -1,38 +1,18 @@
// 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(() => {
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, false);
};
return (

View file

@ -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);
}
}

View file

@ -58,7 +58,7 @@ const WorkspaceElem = memo(() => {
>
<Panel ref={aiPanelRef} collapsible defaultSize={initialAiPanelPercentage} order={1} className="overflow-hidden">
<div ref={aiPanelWrapperRef} className="w-full h-full">
<AIPanel onClose={() => workspaceLayoutModel.setAIPanelVisible(false)} />
<AIPanel />
</div>
</Panel>
<PanelResizeHandle className="w-0.5 bg-transparent hover:bg-gray-500/20 transition-colors" />