mirror of
https://github.com/wavetermdev/waveterm
synced 2026-05-23 08:48:28 +00:00
Make WaveAI context menu consistent with kebab menu (#2517)
This commit is contained in:
parent
a5fb38e640
commit
2c53393cda
5 changed files with 58 additions and 54 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue