Skip to content

Commit a7d76d3

Browse files
authored
add ability to change thinking/maxtokens from waveai context menu (#2504)
1 parent bb12ed6 commit a7d76d3

File tree

10 files changed

+228
-54
lines changed

10 files changed

+228
-54
lines changed

emain/emain-window.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { updater } from "./updater";
2525
export type WindowOpts = {
2626
unamePlatform: string;
2727
isPrimaryStartupWindow?: boolean;
28+
foregroundWindow?: boolean;
2829
};
2930

3031
export const MinWindowWidth = 800;
@@ -193,7 +194,7 @@ export class WaveBrowserWindow extends BaseWindow {
193194

194195
super(winOpts);
195196
const fullscreenOnLaunch = fullConfig?.settings["window:fullscreenonlaunch"];
196-
if (fullscreenOnLaunch) {
197+
if (fullscreenOnLaunch && opts.foregroundWindow) {
197198
this.once("show", () => {
198199
this.setFullScreen(true);
199200
});
@@ -852,6 +853,7 @@ export async function relaunchBrowserWindows() {
852853
const win = await createBrowserWindow(windowData, fullConfig, {
853854
unamePlatform,
854855
isPrimaryStartupWindow,
856+
foregroundWindow: windowId === primaryWindowId,
855857
});
856858
wins.push(win);
857859
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
5+
import { ContextMenuModel } from "@/app/store/contextmenu";
6+
import { RpcApi } from "@/app/store/wshclientapi";
7+
import { TabRpcClient } from "@/app/store/wshrpcutil";
8+
import { WaveAIModel } from "./waveai-model";
9+
10+
export async function handleWaveAIContextMenu(e: React.MouseEvent, onClose?: () => void): Promise<void> {
11+
e.preventDefault();
12+
e.stopPropagation();
13+
14+
const model = WaveAIModel.getInstance();
15+
const menu: ContextMenuItem[] = [];
16+
17+
const hasSelection = waveAIHasSelection();
18+
if (hasSelection) {
19+
menu.push({
20+
role: "copy",
21+
});
22+
menu.push({ type: "separator" });
23+
}
24+
25+
menu.push({
26+
label: "New Chat",
27+
click: () => {
28+
model.clearChat();
29+
},
30+
});
31+
32+
menu.push({ type: "separator" });
33+
34+
const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, {
35+
oref: model.orefContext,
36+
});
37+
38+
const currentThinkingLevel = rtInfo?.["waveai:thinkinglevel"] ?? "medium";
39+
const defaultTokens = model.inBuilder ? 24576 : 4096;
40+
const currentMaxTokens = rtInfo?.["waveai:maxoutputtokens"] ?? defaultTokens;
41+
42+
const thinkingLevelSubmenu: ContextMenuItem[] = [
43+
{
44+
label: "Low",
45+
type: "checkbox",
46+
checked: currentThinkingLevel === "low",
47+
click: () => {
48+
RpcApi.SetRTInfoCommand(TabRpcClient, {
49+
oref: model.orefContext,
50+
data: { "waveai:thinkinglevel": "low" },
51+
});
52+
},
53+
},
54+
{
55+
label: "Medium",
56+
type: "checkbox",
57+
checked: currentThinkingLevel === "medium",
58+
click: () => {
59+
RpcApi.SetRTInfoCommand(TabRpcClient, {
60+
oref: model.orefContext,
61+
data: { "waveai:thinkinglevel": "medium" },
62+
});
63+
},
64+
},
65+
{
66+
label: "High",
67+
type: "checkbox",
68+
checked: currentThinkingLevel === "high",
69+
click: () => {
70+
RpcApi.SetRTInfoCommand(TabRpcClient, {
71+
oref: model.orefContext,
72+
data: { "waveai:thinkinglevel": "high" },
73+
});
74+
},
75+
},
76+
];
77+
78+
const maxTokensSubmenu: ContextMenuItem[] = [];
79+
80+
if (model.inBuilder) {
81+
maxTokensSubmenu.push(
82+
{
83+
label: "24k",
84+
type: "checkbox",
85+
checked: currentMaxTokens === 24576,
86+
click: () => {
87+
RpcApi.SetRTInfoCommand(TabRpcClient, {
88+
oref: model.orefContext,
89+
data: { "waveai:maxoutputtokens": 24576 },
90+
});
91+
},
92+
},
93+
{
94+
label: "64k (Pro)",
95+
type: "checkbox",
96+
checked: currentMaxTokens === 65536,
97+
click: () => {
98+
RpcApi.SetRTInfoCommand(TabRpcClient, {
99+
oref: model.orefContext,
100+
data: { "waveai:maxoutputtokens": 65536 },
101+
});
102+
},
103+
}
104+
);
105+
} else {
106+
maxTokensSubmenu.push(
107+
{
108+
label: "4k",
109+
type: "checkbox",
110+
checked: currentMaxTokens === 4096,
111+
click: () => {
112+
RpcApi.SetRTInfoCommand(TabRpcClient, {
113+
oref: model.orefContext,
114+
data: { "waveai:maxoutputtokens": 4096 },
115+
});
116+
},
117+
},
118+
{
119+
label: "16k (Pro)",
120+
type: "checkbox",
121+
checked: currentMaxTokens === 16384,
122+
click: () => {
123+
RpcApi.SetRTInfoCommand(TabRpcClient, {
124+
oref: model.orefContext,
125+
data: { "waveai:maxoutputtokens": 16384 },
126+
});
127+
},
128+
},
129+
{
130+
label: "64k (Pro)",
131+
type: "checkbox",
132+
checked: currentMaxTokens === 65536,
133+
click: () => {
134+
RpcApi.SetRTInfoCommand(TabRpcClient, {
135+
oref: model.orefContext,
136+
data: { "waveai:maxoutputtokens": 65536 },
137+
});
138+
},
139+
}
140+
);
141+
}
142+
143+
menu.push({
144+
label: "Thinking Level",
145+
submenu: thinkingLevelSubmenu,
146+
});
147+
148+
menu.push({
149+
label: "Max Output Tokens",
150+
submenu: maxTokensSubmenu,
151+
});
152+
153+
menu.push({ type: "separator" });
154+
155+
menu.push({
156+
label: "Hide Wave AI",
157+
click: () => {
158+
onClose?.();
159+
},
160+
});
161+
162+
ContextMenuModel.showContextMenu(menu, e);
163+
}

frontend/app/aipanel/aipanel.tsx

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright 2025, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

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

206206
interface AIPanelProps {
207-
className?: string;
208207
onClose?: () => void;
209208
}
210209

211-
const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
210+
const AIPanelComponentInner = memo(({ onClose }: AIPanelProps) => {
212211
const [isDragOver, setIsDragOver] = useState(false);
213212
const [isReactDndDragOver, setIsReactDndDragOver] = useState(false);
214213
const [initialLoadDone, setInitialLoadDone] = useState(false);
@@ -467,49 +466,15 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
467466
}, 0);
468467
};
469468

470-
const handleMessagesContextMenu = (e: React.MouseEvent) => {
471-
e.preventDefault();
472-
e.stopPropagation();
473-
474-
const menu: ContextMenuItem[] = [];
475-
476-
const hasSelection = waveAIHasSelection();
477-
if (hasSelection) {
478-
menu.push({
479-
role: "copy",
480-
});
481-
menu.push({ type: "separator" });
482-
}
483-
484-
menu.push({
485-
label: "New Chat",
486-
click: () => {
487-
model.clearChat();
488-
},
489-
});
490-
491-
menu.push({ type: "separator" });
492-
493-
menu.push({
494-
label: "Hide Wave AI",
495-
click: () => {
496-
onClose?.();
497-
},
498-
});
499-
500-
ContextMenuModel.showContextMenu(menu, e);
501-
};
502-
503469
const showBlockMask = isLayoutMode && showOverlayBlockNums;
504470

505471
return (
506472
<div
507473
ref={containerRef}
508474
data-waveai-panel="true"
509475
className={cn(
510-
"bg-gray-900 flex flex-col relative h-[calc(100%-4px)]",
511-
model.inBuilder ? "mt-0" : "mt-1",
512-
className,
476+
"bg-gray-900 flex flex-col relative",
477+
model.inBuilder ? "mt-0 h-full" : "mt-1 h-[calc(100%-4px)]",
513478
(isDragOver || isReactDndDragOver) && "bg-gray-800 border-accent",
514479
isFocused ? "border-2 border-accent" : "border-2 border-transparent"
515480
)}
@@ -537,14 +502,17 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
537502
) : (
538503
<>
539504
{messages.length === 0 && initialLoadDone ? (
540-
<div className="flex-1 overflow-y-auto p-2" onContextMenu={handleMessagesContextMenu}>
505+
<div
506+
className="flex-1 overflow-y-auto p-2"
507+
onContextMenu={(e) => handleWaveAIContextMenu(e, onClose)}
508+
>
541509
{model.inBuilder ? <AIBuilderWelcomeMessage /> : <AIWelcomeMessage />}
542510
</div>
543511
) : (
544512
<AIPanelMessages
545513
messages={messages}
546514
status={status}
547-
onContextMenu={handleMessagesContextMenu}
515+
onContextMenu={(e) => handleWaveAIContextMenu(e, onClose)}
548516
/>
549517
)}
550518
{errorMessage && (
@@ -561,10 +529,10 @@ const AIPanelComponentInner = memo(({ className, onClose }: AIPanelProps) => {
561529

562530
AIPanelComponentInner.displayName = "AIPanelInner";
563531

564-
const AIPanelComponent = ({ className, onClose }: AIPanelProps) => {
532+
const AIPanelComponent = ({ onClose }: AIPanelProps) => {
565533
return (
566534
<ErrorBoundary>
567-
<AIPanelComponentInner className={className} onClose={onClose} />
535+
<AIPanelComponentInner onClose={onClose} />
568536
</ErrorBoundary>
569537
);
570538
};

frontend/app/aipanel/waveai-model.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class WaveAIModel {
5050
private useChatStop: (() => void) | null = null;
5151
// Used for injecting Wave-specific message data into DefaultChatTransport's prepareSendMessagesRequest
5252
realMessage: AIMessage | null = null;
53-
private orefContext: ORef;
53+
orefContext: ORef;
5454
inBuilder: boolean = false;
5555

5656
widgetAccessAtom!: jotai.Atom<boolean>;
@@ -65,7 +65,9 @@ export class WaveAIModel {
6565
isChatEmpty: boolean = true;
6666
isWaveAIFocusedAtom!: jotai.Atom<boolean>;
6767
panelVisibleAtom!: jotai.Atom<boolean>;
68-
restoreBackupModalToolCallId: jotai.PrimitiveAtom<string | null> = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
68+
restoreBackupModalToolCallId: jotai.PrimitiveAtom<string | null> = jotai.atom(null) as jotai.PrimitiveAtom<
69+
string | null
70+
>;
6971
restoreBackupStatus: jotai.PrimitiveAtom<"idle" | "processing" | "success" | "error"> = jotai.atom("idle");
7072
restoreBackupError: jotai.PrimitiveAtom<string> = jotai.atom(null) as jotai.PrimitiveAtom<string>;
7173

frontend/builder/builder-app.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import * as keyutil from "@/util/keyutil";
99
import { isBlank } from "@/util/util";
1010
import { Provider, useAtomValue } from "jotai";
1111
import { useEffect } from "react";
12+
import { DndProvider } from "react-dnd";
13+
import { HTML5Backend } from "react-dnd-html5-backend";
1214

1315
type BuilderAppProps = {
1416
initOpts: BuilderInitOpts;
@@ -41,7 +43,9 @@ function BuilderAppInner() {
4143
WaveApp Builder{!isBlank(builderAppId) && ` (${builderAppId})`}
4244
</div>
4345
</div>
44-
{isBlank(builderAppId) ? <AppSelectionModal /> : <BuilderWorkspace />}
46+
<DndProvider backend={HTML5Backend}>
47+
{isBlank(builderAppId) ? <AppSelectionModal /> : <BuilderWorkspace />}
48+
</DndProvider>
4549
</div>
4650
);
4751
}

frontend/builder/builder-workspace.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const BuilderWorkspace = memo(() => {
9797
<div className="flex-1 overflow-hidden">
9898
<PanelGroup direction="horizontal" onLayout={handleHorizontalLayout}>
9999
<Panel defaultSize={layout.chat} minSize={20}>
100-
<AIPanel className="w-full h-full" />
100+
<AIPanel />
101101
</Panel>
102102
<PanelResizeHandle className="w-0.5 bg-transparent hover:bg-gray-500/20 transition-colors" />
103103
<Panel defaultSize={100 - layout.chat} minSize={20}>

frontend/types/gotypes.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,8 @@ declare global {
819819
"builder:appid"?: string;
820820
"builder:env"?: {[key: string]: string};
821821
"waveai:chatid"?: string;
822+
"waveai:thinkinglevel"?: string;
823+
"waveai:maxoutputtokens"?: number;
822824
};
823825

824826
// iochantypes.Packet

pkg/aiusechat/openai/openai-convertmessage.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,22 @@ func debugPrintReq(req *OpenAIRequest, endpoint string) {
137137
for _, tool := range req.Tools {
138138
toolNames = append(toolNames, tool.Name)
139139
}
140-
log.Printf("model %s\n", req.Model)
140+
modelInfo := req.Model
141+
var details []string
142+
if req.Reasoning != nil && req.Reasoning.Effort != "" {
143+
details = append(details, fmt.Sprintf("reasoning: %s", req.Reasoning.Effort))
144+
}
145+
if req.MaxOutputTokens > 0 {
146+
details = append(details, fmt.Sprintf("max_tokens: %d", req.MaxOutputTokens))
147+
}
148+
if len(details) > 0 {
149+
log.Printf("model %s (%s)\n", modelInfo, strings.Join(details, ", "))
150+
} else {
151+
log.Printf("model %s\n", modelInfo)
152+
}
141153
if len(toolNames) > 0 {
142154
log.Printf("tools: %s\n", strings.Join(toolNames, ","))
143155
}
144-
// log.Printf("reasoning %v\n", req.Reasoning)
145156

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

0 commit comments

Comments
 (0)