Skip to content

Commit 126a289

Browse files
committed
persist aipanel open + width
1 parent 7c4041e commit 126a289

6 files changed

Lines changed: 81 additions & 10 deletions

File tree

frontend/app/aipanel/aipanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const AIPanelComponent = memo(({ className, onClose }: AIPanelProps) => {
2626
const [input, setInput] = useState("");
2727
const [isDragOver, setIsDragOver] = useState(false);
2828
const [errorMessage, setErrorMessage] = useState<string>("");
29-
const modelRef = useRef(new WaveAIModel());
29+
const modelRef = useRef(new WaveAIModel(globalStore.get(atoms.staticTabId)));
3030
const model = modelRef.current;
3131
const realMessageRef = useRef<AIMessage>(null);
3232
const inputRef = useRef<AIPanelInputRef>(null);

frontend/app/aipanel/waveai-model.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ export class WaveAIModel {
1717
widgetAccess: jotai.PrimitiveAtom<boolean> = jotai.atom(true);
1818
droppedFiles: jotai.PrimitiveAtom<DroppedFile[]> = jotai.atom([]);
1919
chatId: jotai.PrimitiveAtom<string> = jotai.atom(crypto.randomUUID());
20+
21+
private tabId: string;
2022

21-
constructor() {
22-
// Model initialization
23+
constructor(tabId: string) {
24+
this.tabId = tabId;
2325
}
2426

2527
addFile(file: File): DroppedFile {
@@ -72,4 +74,5 @@ export class WaveAIModel {
7274
this.clearFiles();
7375
globalStore.set(this.chatId, crypto.randomUUID());
7476
}
77+
7578
}

frontend/app/workspace/workspace-layout-model.ts

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,76 @@
33

44
import { isDev } from "@/store/global";
55
import { ImperativePanelGroupHandle, ImperativePanelHandle } from "react-resizable-panels";
6+
import * as jotai from "jotai";
7+
import { getTabMetaKeyAtom } from "@/app/store/global";
8+
import { RpcApi } from "@/app/store/wshclientapi";
9+
import { TabRpcClient } from "@/app/store/wshrpcutil";
10+
import * as WOS from "@/app/store/wos";
11+
import { globalStore } from "@/app/store/jotaiStore";
12+
import { atoms } from "@/store/global";
13+
import { debounce } from "lodash-es";
614

715
const AIPANEL_DEFAULTWIDTH = 300;
816
const AIPANEL_MINWIDTH = 250;
917
const AIPANEL_MAXWIDTHRATIO = 0.5;
1018

1119
class WorkspaceLayoutModel {
12-
aiPanelVisible: boolean;
1320
aiPanelRef: ImperativePanelHandle | null;
1421
panelGroupRef: ImperativePanelGroupHandle | null;
15-
aiPanelWidth: number;
1622
inResize: boolean;
17-
23+
private aiPanelVisible: boolean;
24+
private aiPanelWidth: number;
25+
private debouncedPersistWidth: (width: number) => void;
26+
private initialized: boolean = false;
27+
1828
constructor() {
19-
this.aiPanelVisible = isDev();
2029
this.aiPanelRef = null;
2130
this.panelGroupRef = null;
22-
this.aiPanelWidth = AIPANEL_DEFAULTWIDTH;
2331
this.inResize = false;
32+
this.aiPanelVisible = isDev();
33+
this.aiPanelWidth = AIPANEL_DEFAULTWIDTH;
34+
35+
this.debouncedPersistWidth = debounce((width: number) => {
36+
try {
37+
RpcApi.SetMetaCommand(TabRpcClient, {
38+
oref: WOS.makeORef("tab", this.getTabId()),
39+
meta: { "waveai:panelwidth": width },
40+
});
41+
} catch (e) {
42+
console.warn("Failed to persist panel width:", e);
43+
}
44+
}, 300);
45+
}
46+
47+
private initializeFromTabMeta(): void {
48+
if (this.initialized) return;
49+
this.initialized = true;
50+
51+
try {
52+
const savedVisible = globalStore.get(this.getPanelOpenAtom());
53+
const savedWidth = globalStore.get(this.getPanelWidthAtom());
54+
55+
if (savedVisible != null) {
56+
this.aiPanelVisible = savedVisible;
57+
}
58+
if (savedWidth != null) {
59+
this.aiPanelWidth = savedWidth;
60+
}
61+
} catch (e) {
62+
console.warn("Failed to initialize from tab meta:", e);
63+
}
64+
}
65+
66+
private getTabId(): string {
67+
return globalStore.get(atoms.staticTabId);
68+
}
69+
70+
private getPanelOpenAtom(): jotai.Atom<boolean> {
71+
return getTabMetaKeyAtom(this.getTabId(), "waveai:panelopen");
72+
}
73+
74+
private getPanelWidthAtom(): jotai.Atom<number> {
75+
return getTabMetaKeyAtom(this.getTabId(), "waveai:panelwidth");
2476
}
2577

2678
registerRefs(aiPanelRef: ImperativePanelHandle, panelGroupRef: ImperativePanelGroupHandle): void {
@@ -38,7 +90,7 @@ class WorkspaceLayoutModel {
3890
const aiPanelPercentage = this.getAIPanelPercentage(currentWindowWidth);
3991
const mainContentPercentage = this.getMainContentPercentage(currentWindowWidth);
4092

41-
if (this.aiPanelVisible) {
93+
if (this.getAIPanelVisible()) {
4294
this.aiPanelRef.expand();
4395
} else {
4496
this.aiPanelRef.collapse();
@@ -63,6 +115,7 @@ class WorkspaceLayoutModel {
63115
}
64116

65117
getAIPanelVisible(): boolean {
118+
this.initializeFromTabMeta();
66119
return this.aiPanelVisible;
67120
}
68121

@@ -71,15 +124,21 @@ class WorkspaceLayoutModel {
71124
return;
72125
}
73126
this.aiPanelVisible = visible;
127+
RpcApi.SetMetaCommand(TabRpcClient, {
128+
oref: WOS.makeORef("tab", this.getTabId()),
129+
meta: { "waveai:panelopen": visible },
130+
});
74131
this.syncAIPanelRef();
75132
}
76133

77134
getAIPanelWidth(): number {
135+
this.initializeFromTabMeta();
78136
return this.aiPanelWidth;
79137
}
80138

81139
setAIPanelWidth(width: number): void {
82140
this.aiPanelWidth = width;
141+
this.debouncedPersistWidth(width);
83142
}
84143

85144
getAIPanelPercentage(windowWidth: number): number {
@@ -102,7 +161,7 @@ class WorkspaceLayoutModel {
102161
if (!isDev()) {
103162
return;
104163
}
105-
if (!this.aiPanelVisible) {
164+
if (!this.getAIPanelVisible()) {
106165
return;
107166
}
108167
const clampedWidth = this.getClampedAIPanelWidth(width, windowWidth);

frontend/types/gotypes.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,8 @@ declare global {
583583
"bg:blendmode"?: string;
584584
"bg:bordercolor"?: string;
585585
"bg:activebordercolor"?: string;
586+
"waveai:panelopen"?: boolean;
587+
"waveai:panelwidth"?: number;
586588
"term:*"?: boolean;
587589
"term:fontsize"?: number;
588590
"term:fontfamily"?: string;

pkg/waveobj/metaconsts.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ const (
9292
MetaKey_BgBorderColor = "bg:bordercolor"
9393
MetaKey_BgActiveBorderColor = "bg:activebordercolor"
9494

95+
MetaKey_WaveAiPanelOpen = "waveai:panelopen"
96+
MetaKey_WaveAiPanelWidth = "waveai:panelwidth"
97+
9598
MetaKey_TermClear = "term:*"
9699
MetaKey_TermFontSize = "term:fontsize"
97100
MetaKey_TermFontFamily = "term:fontfamily"

pkg/waveobj/wtypemeta.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ type MetaTSType struct {
9595
BgBorderColor string `json:"bg:bordercolor,omitempty"` // frame:bordercolor
9696
BgActiveBorderColor string `json:"bg:activebordercolor,omitempty"` // frame:activebordercolor
9797

98+
// for tabs
99+
WaveAiPanelOpen bool `json:"waveai:panelopen,omitempty"`
100+
WaveAiPanelWidth int `json:"waveai:panelwidth,omitempty"`
101+
98102
TermClear bool `json:"term:*,omitempty"`
99103
TermFontSize int `json:"term:fontsize,omitempty"`
100104
TermFontFamily string `json:"term:fontfamily,omitempty"`

0 commit comments

Comments
 (0)