Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { updater } from "./updater";
export type WindowOpts = {
unamePlatform: string;
isPrimaryStartupWindow?: boolean;
foregroundWindow?: boolean;
};

export const MinWindowWidth = 800;
Expand Down Expand Up @@ -193,7 +194,7 @@ export class WaveBrowserWindow extends BaseWindow {

super(winOpts);
const fullscreenOnLaunch = fullConfig?.settings["window:fullscreenonlaunch"];
if (fullscreenOnLaunch) {
if (fullscreenOnLaunch && opts.foregroundWindow) {
this.once("show", () => {
this.setFullScreen(true);
});
Expand Down Expand Up @@ -852,6 +853,7 @@ export async function relaunchBrowserWindows() {
const win = await createBrowserWindow(windowData, fullConfig, {
unamePlatform,
isPrimaryStartupWindow,
foregroundWindow: windowId === primaryWindowId,
});
wins.push(win);
}
Expand Down
163 changes: 163 additions & 0 deletions frontend/app/aipanel/aipanel-contextmenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
import { ContextMenuModel } from "@/app/store/contextmenu";
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> {
e.preventDefault();
e.stopPropagation();

const model = WaveAIModel.getInstance();
const menu: ContextMenuItem[] = [];

Comment on lines +15 to +16
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Import ContextMenuItem to avoid build breakage.

ContextMenuItem is referenced here but never imported, so TypeScript will fail to compile this module. Pull the type in from the context menu store.

-import { ContextMenuModel } from "@/app/store/contextmenu";
+import { ContextMenuModel } from "@/app/store/contextmenu";
+import type { ContextMenuItem } from "@/app/store/contextmenu";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/app/aipanel/aipanel-contextmenu.ts around lines 15-16, the variable
is typed as ContextMenuItem but that type is not imported, causing TypeScript
build errors; add an import for the ContextMenuItem type from the context menu
store module (e.g. import the type from the project's context-menu store file,
using an "import type" if available) so the type is resolved and compilation
succeeds.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, most of these are defined in custom.d.ts (and for backend types in gotypes.d.ts) so they are already global and auto-imported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sawka, thank you for the clarification! I understand now—types defined in custom.d.ts and gotypes.d.ts are ambient declarations and globally available throughout the codebase without explicit imports. My suggestion to import ContextMenuItem was unnecessary. I appreciate you explaining this project convention!


✏️ Learnings added
Learnt from: sawka
Repo: wavetermdev/waveterm PR: 2504
File: frontend/app/aipanel/aipanel-contextmenu.ts:15-16
Timestamp: 2025-11-01T00:57:23.015Z
Learning: In the waveterm codebase, types defined in custom.d.ts are globally available and do not require explicit imports. Backend types defined in gotypes.d.ts are also globally available.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

const hasSelection = waveAIHasSelection();
if (hasSelection) {
menu.push({
role: "copy",
});
menu.push({ type: "separator" });
}

menu.push({
label: "New Chat",
click: () => {
model.clearChat();
},
});

menu.push({ type: "separator" });

const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
});

const currentThinkingLevel = rtInfo?.["waveai:thinkinglevel"] ?? "medium";
const defaultTokens = model.inBuilder ? 24576 : 4096;
const currentMaxTokens = rtInfo?.["waveai:maxoutputtokens"] ?? defaultTokens;

const thinkingLevelSubmenu: ContextMenuItem[] = [
{
label: "Low",
type: "checkbox",
checked: currentThinkingLevel === "low",
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:thinkinglevel": "low" },
});
},
},
{
label: "Medium",
type: "checkbox",
checked: currentThinkingLevel === "medium",
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:thinkinglevel": "medium" },
});
},
},
{
label: "High",
type: "checkbox",
checked: currentThinkingLevel === "high",
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:thinkinglevel": "high" },
});
},
},
];

const maxTokensSubmenu: ContextMenuItem[] = [];

if (model.inBuilder) {
maxTokensSubmenu.push(
{
label: "24k",
type: "checkbox",
checked: currentMaxTokens === 24576,
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:maxoutputtokens": 24576 },
});
},
},
{
label: "64k (Pro)",
type: "checkbox",
checked: currentMaxTokens === 65536,
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:maxoutputtokens": 65536 },
});
},
}
);
} else {
maxTokensSubmenu.push(
{
label: "4k",
type: "checkbox",
checked: currentMaxTokens === 4096,
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:maxoutputtokens": 4096 },
});
},
},
{
label: "16k (Pro)",
type: "checkbox",
checked: currentMaxTokens === 16384,
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:maxoutputtokens": 16384 },
});
},
},
{
label: "64k (Pro)",
type: "checkbox",
checked: currentMaxTokens === 65536,
click: () => {
RpcApi.SetRTInfoCommand(TabRpcClient, {
oref: model.orefContext,
data: { "waveai:maxoutputtokens": 65536 },
});
},
}
);
}

menu.push({
label: "Thinking Level",
submenu: thinkingLevelSubmenu,
});

menu.push({
label: "Max Output Tokens",
submenu: maxTokensSubmenu,
});

menu.push({ type: "separator" });

menu.push({
label: "Hide Wave AI",
click: () => {
onClose?.();
},
});

ContextMenuModel.showContextMenu(menu, e);
}
54 changes: 11 additions & 43 deletions frontend/app/aipanel/aipanel.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu";
import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
import { ErrorBoundary } from "@/app/element/errorboundary";
import { ContextMenuModel } from "@/app/store/contextmenu";
import { atoms, getSettingsKeyAtom } from "@/app/store/global";
import { globalStore } from "@/app/store/jotaiStore";
import { checkKeyPressed, keydownWrapper } from "@/util/keyutil";
Expand Down Expand Up @@ -204,11 +204,10 @@ const AIErrorMessage = memo(({ errorMessage, onClear }: AIErrorMessageProps) =>
AIErrorMessage.displayName = "AIErrorMessage";

interface AIPanelProps {
className?: string;
onClose?: () => void;
}

const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => {
const [isDragOver, setIsDragOver] = useState(false);
const [isReactDndDragOver, setIsReactDndDragOver] = useState(false);
const [initialLoadDone, setInitialLoadDone] = useState(false);
Expand Down Expand Up @@ -467,49 +466,15 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
}, 0);
};

const handleMessagesContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

const menu: ContextMenuItem[] = [];

const hasSelection = waveAIHasSelection();
if (hasSelection) {
menu.push({
role: "copy",
});
menu.push({ type: "separator" });
}

menu.push({
label: "New Chat",
click: () => {
model.clearChat();
},
});

menu.push({ type: "separator" });

menu.push({
label: "Hide Wave AI",
click: () => {
onClose?.();
},
});

ContextMenuModel.showContextMenu(menu, e);
};

const showBlockMask = isLayoutMode && showOverlayBlockNums;

return (
<div
ref={containerRef}
data-waveai-panel="true"
className={cn(
"bg-gray-900 flex flex-col relative h-[calc(100%-4px)]",
model.inBuilder ? "mt-0" : "mt-1",
className,
"bg-gray-900 flex flex-col relative",
model.inBuilder ? "mt-0 h-full" : "mt-1 h-[calc(100%-4px)]",
(isDragOver || isReactDndDragOver) && "bg-gray-800 border-accent",
isFocused ? "border-2 border-accent" : "border-2 border-transparent"
)}
Expand Down Expand Up @@ -537,14 +502,17 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
) : (
<>
{messages.length === 0 && initialLoadDone ? (
<div className="flex-1 overflow-y-auto p-2" onContextMenu={handleMessagesContextMenu}>
<div
className="flex-1 overflow-y-auto p-2"
onContextMenu={(e) => handleWaveAIContextMenu(e, onClose)}
>
{model.inBuilder ? <AIBuilderWelcomeMessage /> : <AIWelcomeMessage />}
</div>
) : (
<AIPanelMessages
messages={messages}
status={status}
onContextMenu={handleMessagesContextMenu}
onContextMenu={(e) => handleWaveAIContextMenu(e, onClose)}
/>
)}
{errorMessage && (
Expand All @@ -561,10 +529,10 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {

AIPanelComponentInner.displayName = "AIPanelInner";

const AIPanelComponent = ({ className, onClose }: AIPanelProps) => {
const AIPanelComponent = ({ onClose }: AIPanelProps) => {
return (
<ErrorBoundary>
<AIPanelComponentInner className={className} onClose={onClose} />
<AIPanelComponentInner onClose={onClose} />
</ErrorBoundary>
);
};
Expand Down
6 changes: 4 additions & 2 deletions frontend/app/aipanel/waveai-model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class WaveAIModel {
private useChatStop: (() => void) | null = null;
// Used for injecting Wave-specific message data into DefaultChatTransport's prepareSendMessagesRequest
realMessage: AIMessage | null = null;
private orefContext: ORef;
orefContext: ORef;
inBuilder: boolean = false;

widgetAccessAtom!: jotai.Atom<boolean>;
Expand All @@ -65,7 +65,9 @@ export class WaveAIModel {
isChatEmpty: boolean = true;
isWaveAIFocusedAtom!: jotai.Atom<boolean>;
panelVisibleAtom!: jotai.Atom<boolean>;
restoreBackupModalToolCallId: jotai.PrimitiveAtom<string | null> = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
restoreBackupModalToolCallId: jotai.PrimitiveAtom<string | null> = jotai.atom(null) as jotai.PrimitiveAtom<
string | null
>;
restoreBackupStatus: jotai.PrimitiveAtom<"idle" | "processing" | "success" | "error"> = jotai.atom("idle");
restoreBackupError: jotai.PrimitiveAtom<string> = jotai.atom(null) as jotai.PrimitiveAtom<string>;

Expand Down
6 changes: 5 additions & 1 deletion frontend/builder/builder-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as keyutil from "@/util/keyutil";
import { isBlank } from "@/util/util";
import { Provider, useAtomValue } from "jotai";
import { useEffect } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

type BuilderAppProps = {
initOpts: BuilderInitOpts;
Expand Down Expand Up @@ -41,7 +43,9 @@ function BuilderAppInner() {
WaveApp Builder{!isBlank(builderAppId) && ` (${builderAppId})`}
</div>
</div>
{isBlank(builderAppId) ? <AppSelectionModal /> : <BuilderWorkspace />}
<DndProvider backend={HTML5Backend}>
{isBlank(builderAppId) ? <AppSelectionModal /> : <BuilderWorkspace />}
</DndProvider>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/builder/builder-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const BuilderWorkspace = memo(() => {
<div className="flex-1 overflow-hidden">
<PanelGroup direction="horizontal" onLayout={handleHorizontalLayout}>
<Panel defaultSize={layout.chat} minSize={20}>
<AIPanel className="w-full h-full" />
<AIPanel />
</Panel>
<PanelResizeHandle className="w-0.5 bg-transparent hover:bg-gray-500/20 transition-colors" />
<Panel defaultSize={100 - layout.chat} minSize={20}>
Expand Down
2 changes: 2 additions & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,8 @@ declare global {
"builder:appid"?: string;
"builder:env"?: {[key: string]: string};
"waveai:chatid"?: string;
"waveai:thinkinglevel"?: string;
"waveai:maxoutputtokens"?: number;
};

// iochantypes.Packet
Expand Down
15 changes: 13 additions & 2 deletions pkg/aiusechat/openai/openai-convertmessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,22 @@ func debugPrintReq(req *OpenAIRequest, endpoint string) {
for _, tool := range req.Tools {
toolNames = append(toolNames, tool.Name)
}
log.Printf("model %s\n", req.Model)
modelInfo := req.Model
var details []string
if req.Reasoning != nil && req.Reasoning.Effort != "" {
details = append(details, fmt.Sprintf("reasoning: %s", req.Reasoning.Effort))
}
if req.MaxOutputTokens > 0 {
details = append(details, fmt.Sprintf("max_tokens: %d", req.MaxOutputTokens))
}
if len(details) > 0 {
log.Printf("model %s (%s)\n", modelInfo, strings.Join(details, ", "))
} else {
log.Printf("model %s\n", modelInfo)
}
if len(toolNames) > 0 {
log.Printf("tools: %s\n", strings.Join(toolNames, ","))
}
// log.Printf("reasoning %v\n", req.Reasoning)

log.Printf("inputs (%d):", len(req.Input))
for idx, input := range req.Input {
Expand Down
Loading
Loading