Skip to content

Commit c55631d

Browse files
committed
builder window updates, use gpt-5.4, webview/devtools fixes, scrollbar fixes, and more
1 parent f5d8ed6 commit c55631d

15 files changed

Lines changed: 204 additions & 51 deletions

File tree

emain/emain-builder.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { ClientService } from "@/app/store/services";
55
import { RpcApi } from "@/app/store/wshclientapi";
66
import { randomUUID } from "crypto";
7-
import { BrowserWindow } from "electron";
7+
import { BrowserWindow, webContents } from "electron";
88
import { globalEvents } from "emain/emain-events";
99
import path from "path";
1010
import { getElectronAppBasePath, isDevVite, unamePlatform } from "./emain-platform";
@@ -87,6 +87,20 @@ export async function createBuilderWindow(appId: string): Promise<BuilderWindowT
8787
typedBuilderWindow.builderAppId = appId;
8888
typedBuilderWindow.savedInitOpts = initOpts;
8989

90+
typedBuilderWindow.on("close", () => {
91+
const wc = typedBuilderWindow.webContents;
92+
if (wc.isDevToolsOpened()) {
93+
wc.closeDevTools();
94+
}
95+
for (const guest of webContents.getAllWebContents()) {
96+
if (guest.getType() === "webview" && guest.hostWebContents?.id === wc.id) {
97+
if (guest.isDevToolsOpened()) {
98+
guest.closeDevTools();
99+
}
100+
}
101+
}
102+
});
103+
90104
typedBuilderWindow.on("focus", () => {
91105
focusedBuilderWindow = typedBuilderWindow;
92106
console.log("builder window focused", builderId);

emain/emain-ipc.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,17 @@ export function initIpcHandlers() {
490490
console.error("Error deleting builder rtinfo:", e);
491491
}
492492
}
493+
const wc = bw.webContents;
494+
if (wc.isDevToolsOpened()) {
495+
wc.closeDevTools();
496+
}
497+
for (const guest of electron.webContents.getAllWebContents()) {
498+
if (guest.getType() === "webview" && guest.hostWebContents?.id === wc.id) {
499+
if (guest.isDevToolsOpened()) {
500+
guest.closeDevTools();
501+
}
502+
}
503+
}
493504
bw.destroy();
494505
});
495506

emain/emain-window.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ClientService, ObjectService, WindowService, WorkspaceService } from "@
55
import { waveEventSubscribeSingle } from "@/app/store/wps";
66
import { RpcApi } from "@/app/store/wshclientapi";
77
import { fireAndForget } from "@/util/util";
8-
import { BaseWindow, BaseWindowConstructorOptions, dialog, globalShortcut, ipcMain, screen } from "electron";
8+
import { BaseWindow, BaseWindowConstructorOptions, dialog, globalShortcut, ipcMain, screen, webContents } from "electron";
99
import { globalEvents } from "emain/emain-events";
1010
import path from "path";
1111
import { debounce } from "throttle-debounce";
@@ -299,6 +299,7 @@ export class WaveBrowserWindow extends BaseWindow {
299299
if (this.isDestroyed()) {
300300
return;
301301
}
302+
this.closeAllDevTools();
302303
console.log("win 'close' handler fired", this.waveWindowId);
303304
if (getGlobalIsQuitting() || updater?.status == "installing" || getGlobalIsRelaunching()) {
304305
return;
@@ -358,6 +359,24 @@ export class WaveBrowserWindow extends BaseWindow {
358359
setTimeout(() => globalEvents.emit("windows-updated"), 50);
359360
}
360361

362+
private closeAllDevTools() {
363+
for (const tabView of this.allLoadedTabViews.values()) {
364+
if (tabView.webContents?.isDevToolsOpened()) {
365+
tabView.webContents.closeDevTools();
366+
}
367+
}
368+
const tabViewIds = new Set(
369+
[...this.allLoadedTabViews.values()].map((tv) => tv.webContents?.id).filter((id) => id != null)
370+
);
371+
for (const wc of webContents.getAllWebContents()) {
372+
if (wc.getType() === "webview" && tabViewIds.has(wc.hostWebContents?.id)) {
373+
if (wc.isDevToolsOpened()) {
374+
wc.closeDevTools();
375+
}
376+
}
377+
}
378+
}
379+
361380
private removeAllChildViews() {
362381
for (const tabView of this.allLoadedTabViews.values()) {
363382
if (!this.isDestroyed()) {

frontend/app/aipanel/waveai-model.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ export interface DroppedFile {
4141
previewUrl?: string;
4242
}
4343

44+
const BuilderAIModeConfigs: Record<string, AIModeConfigType> = {
45+
"waveaibuilder@default": {
46+
"display:name": "Builder Default",
47+
"display:order": -2,
48+
"display:icon": "sparkles",
49+
"display:description": "Good mix of speed and accuracy\n(gpt-5.4 with minimal thinking)",
50+
"ai:provider": "wave",
51+
"ai:switchcompat": ["wavecloud"],
52+
"waveai:premium": true,
53+
},
54+
"waveaibuilder@deep": {
55+
"display:name": "Builder Deep",
56+
"display:order": -1,
57+
"display:icon": "lightbulb",
58+
"display:description": "Slower but most capable\n(gpt-5.4 with full reasoning)",
59+
"ai:provider": "wave",
60+
"ai:switchcompat": ["wavecloud"],
61+
"waveai:premium": true,
62+
},
63+
};
64+
4465
export class WaveAIModel {
4566
private static instance: WaveAIModel | null = null;
4667
inputRef: React.RefObject<AIPanelInputRef> | null = null;
@@ -80,7 +101,11 @@ export class WaveAIModel {
80101
this.orefContext = orefContext;
81102
this.inBuilder = inBuilder;
82103
this.chatId = jotai.atom(null) as jotai.PrimitiveAtom<string>;
83-
this.aiModeConfigs = atoms.waveaiModeConfigAtom;
104+
if (inBuilder) {
105+
this.aiModeConfigs = jotai.atom(BuilderAIModeConfigs) as jotai.Atom<Record<string, AIModeConfigType>>;
106+
} else {
107+
this.aiModeConfigs = atoms.waveaiModeConfigAtom;
108+
}
84109

85110
this.hasPremiumAtom = jotai.atom((get) => {
86111
const rateLimitInfo = get(atoms.waveAIRateLimitInfoAtom);
@@ -118,7 +143,7 @@ export class WaveAIModel {
118143
this.defaultModeAtom = jotai.atom((get) => {
119144
const telemetryEnabled = get(getSettingsKeyAtom("telemetry:enabled")) ?? false;
120145
if (this.inBuilder) {
121-
return telemetryEnabled ? "waveai@balanced" : "invalid";
146+
return telemetryEnabled ? "waveaibuilder@default" : "invalid";
122147
}
123148
const aiModeConfigs = get(this.aiModeConfigs);
124149
if (!telemetryEnabled) {

frontend/app/view/webview/webview.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { fireAndForget, useAtomValueSafe } from "@/util/util";
2121
import clsx from "clsx";
2222
import { WebviewTag } from "electron";
2323
import { Atom, PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai";
24-
import { Fragment, createRef, memo, useCallback, useEffect, useRef, useState } from "react";
24+
import { Fragment, createRef, memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2525
import "./webview.scss";
2626
import type { WebViewEnv } from "./webviewenv";
2727

@@ -951,6 +951,15 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps)
951951
}, 100);
952952
}
953953

954+
useLayoutEffect(() => {
955+
return () => {
956+
const webview = model.webviewRef.current;
957+
if (webview?.isDevToolsOpened()) {
958+
webview.closeDevTools();
959+
}
960+
};
961+
}, []);
962+
954963
useEffect(() => {
955964
return () => {
956965
globalStore.set(model.domReady, false);

frontend/builder/builder-apppanel.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ const BuilderAppPanel = memo(() => {
257257
model.switchBuilderApp();
258258
}, [model]);
259259

260+
const handleOpenDevToolsClick = useCallback(() => {
261+
model.openPreviewDevTools();
262+
}, [model]);
263+
260264
const handleKebabClick = useCallback(
261265
(e: React.MouseEvent) => {
262266
const menu: ContextMenuItem[] = [
@@ -267,14 +271,21 @@ const BuilderAppPanel = memo(() => {
267271
{
268272
type: "separator",
269273
},
274+
{
275+
label: "Open DevTools",
276+
click: handleOpenDevToolsClick,
277+
},
278+
{
279+
type: "separator",
280+
},
270281
{
271282
label: "Switch App",
272283
click: handleSwitchAppClick,
273284
},
274285
];
275286
ContextMenuModel.getInstance().showContextMenu(menu, e);
276287
},
277-
[handleSwitchAppClick, handlePublishClick]
288+
[handleSwitchAppClick, handlePublishClick, handleOpenDevToolsClick]
278289
);
279290

280291
return (

frontend/builder/store/builder-apppanel-model.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { RpcApi } from "@/app/store/wshclientapi";
77
import { TabRpcClient } from "@/app/store/wshrpcutil";
88
import { atoms, getApi, WOS } from "@/store/global";
99
import { base64ToString, stringToBase64 } from "@/util/util";
10+
import { WebviewTag } from "electron";
1011
import { atom, type Atom, type PrimitiveAtom } from "jotai";
1112
import type * as MonacoTypes from "monaco-editor";
1213
import { debounce } from "throttle-debounce";
@@ -35,6 +36,7 @@ export class BuilderAppPanelModel {
3536
saveNeededAtom!: Atom<boolean>;
3637
focusElemRef: { current: HTMLInputElement | null } = { current: null };
3738
monacoEditorRef: { current: MonacoTypes.editor.IStandaloneCodeEditor | null } = { current: null };
39+
webviewRef: { current: WebviewTag | null } = { current: null };
3840
statusUnsubFn: (() => void) | null = null;
3941
appGoUpdateUnsubFn: (() => void) | null = null;
4042
debouncedRestart: (() => void) & { cancel: () => void };
@@ -314,6 +316,15 @@ export class BuilderAppPanelModel {
314316
this.monacoEditorRef.current = ref;
315317
}
316318

319+
openPreviewDevTools() {
320+
if (!this.webviewRef.current) return;
321+
if (this.webviewRef.current.isDevToolsOpened()) {
322+
this.webviewRef.current.closeDevTools();
323+
} else {
324+
this.webviewRef.current.openDevTools();
325+
}
326+
}
327+
317328
dispose() {
318329
if (this.statusUnsubFn) {
319330
this.statusUnsubFn();

frontend/builder/tabs/builder-previewtab.tsx

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ const ErrorStateView = memo(({ errorMsg }: { errorMsg: string }) => {
7070
<div className="flex flex-col gap-3">
7171
<h2 className="text-2xl font-semibold text-error">Secrets Required</h2>
7272
<p className="text-base text-secondary leading-relaxed">
73-
This app requires secrets that must be configured. Please use the Secrets tab to set and bind
74-
the required secrets for your app to run.
73+
This app requires secrets that must be configured. Please use the Secrets tab to set and
74+
bind the required secrets for your app to run.
7575
</p>
7676
<div className="text-left bg-panel border border-error/30 rounded-lg p-4 max-h-96 overflow-auto mt-2">
7777
<pre className="text-sm text-secondary whitespace-pre-wrap font-mono">{displayMsg}</pre>
@@ -178,47 +178,48 @@ const BuilderPreviewTab = memo(() => {
178178
const originalContent = useAtomValue(model.originalContentAtom);
179179
const builderStatus = useAtomValue(model.builderStatusAtom);
180180
const builderId = useAtomValue(atoms.builderId);
181-
182181
const fileExists = originalContent.length > 0;
183-
184-
if (isLoading) {
185-
return null;
186-
}
187-
188-
if (builderStatus?.status === "error") {
189-
return <ErrorStateView errorMsg={builderStatus?.errormsg || ""} />;
190-
}
191-
192-
if (!fileExists) {
193-
return <EmptyStateView />;
194-
}
182+
const [lastKnownUrl, setLastKnownUrl] = useState<string>(null);
195183

196184
const status = builderStatus?.status || "init";
185+
const isWebViewActive = status === "running" && builderStatus?.port && builderStatus.port !== 0;
197186

198-
if (status === "init") {
199-
return null;
200-
}
201-
202-
if (status === "building") {
203-
return <BuildingStateView />;
204-
}
205-
206-
if (status === "stopped") {
207-
return <StoppedStateView onStart={() => model.startBuilder()} />;
187+
if (isWebViewActive) {
188+
const previewUrl = `http://localhost:${builderStatus.port}/?clientid=wave:${builderId}`;
189+
if (previewUrl !== lastKnownUrl) {
190+
setLastKnownUrl(previewUrl);
191+
}
208192
}
209193

210-
const shouldShowWebView = status === "running" && builderStatus?.port && builderStatus.port !== 0;
211-
212-
if (shouldShowWebView) {
213-
const previewUrl = `http://localhost:${builderStatus.port}/?clientid=wave:${builderId}`;
214-
return (
215-
<div className="w-full h-full">
216-
<webview src={previewUrl} className="w-full h-full" />
217-
</div>
218-
);
194+
let overlay = null;
195+
if (!isLoading && !isWebViewActive) {
196+
if (builderStatus?.status === "error") {
197+
overlay = <ErrorStateView errorMsg={builderStatus?.errormsg || ""} />;
198+
} else if (!fileExists || status === "init") {
199+
overlay = <EmptyStateView />;
200+
} else if (status === "building") {
201+
overlay = <BuildingStateView />;
202+
} else if (status === "stopped") {
203+
overlay = <StoppedStateView onStart={() => model.startBuilder()} />;
204+
}
219205
}
220206

221-
return null;
207+
return (
208+
<div className="w-full h-full relative">
209+
{lastKnownUrl && (
210+
<webview
211+
ref={model.webviewRef}
212+
src={lastKnownUrl}
213+
className="w-full h-full"
214+
style={{
215+
visibility: isWebViewActive ? "visible" : "hidden",
216+
pointerEvents: isWebViewActive ? "auto" : "none",
217+
}}
218+
/>
219+
)}
220+
{overlay && <div className="absolute inset-0">{overlay}</div>}
221+
</div>
222+
);
222223
});
223224

224225
BuilderPreviewTab.displayName = "BuilderPreviewTab";

pkg/aiusechat/uctypes/uctypes.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,11 @@ const (
163163
)
164164

165165
const (
166-
AIModeQuick = "waveai@quick"
167-
AIModeBalanced = "waveai@balanced"
168-
AIModeDeep = "waveai@deep"
166+
AIModeQuick = "waveai@quick"
167+
AIModeBalanced = "waveai@balanced"
168+
AIModeDeep = "waveai@deep"
169+
AIModeBuilderDefault = "waveaibuilder@default"
170+
AIModeBuilderDeep = "waveaibuilder@deep"
169171
)
170172

171173
const (

0 commit comments

Comments
 (0)