Skip to content
46 changes: 46 additions & 0 deletions gui/src/components/TabBar/TabBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { XMarkIcon } from "@heroicons/react/24/outline";
import React, { useCallback, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useAppSelector } from "../../redux/hooks";
import styled from "styled-components";
import { defaultBorderRadius } from "..";
import { newSession } from "../../redux/slices/sessionSlice";
Expand All @@ -10,10 +11,16 @@ import {
removeTab,
setActiveTab,
setTabs,
setTabMode,
setTabModel,
} from "../../redux/slices/tabsSlice";
import { AppDispatch, RootState } from "../../redux/store";
import { loadSession, saveCurrentSession } from "../../redux/thunks/session";
import { varWithFallback } from "../../styles/theme";
import { useAuth } from "../../context/Auth";
import { selectSelectedChatModel } from "../../redux/slices/configSlice";
import { updateSelectedModelByRole } from "../../redux/thunks/updateSelectedModelByRole";
import { setMode } from "../../redux/slices/sessionSlice";

// Haven't set up theme colors for tabs yet
// Will keep it simple and choose from existing ones. Comments show vars we could use
Expand Down Expand Up @@ -137,6 +144,11 @@ export const TabBar = React.forwardRef<HTMLDivElement>((_, ref) => {
(state: RootState) => state.session.history.length > 0,
);
const tabs = useSelector((state: RootState) => state.tabs.tabs);
const selectedModel = useAppSelector(selectSelectedChatModel);
const { selectedProfile } = useAuth();
const mode = useAppSelector((state: RootState) => state.session.mode);
const activeTab = tabs.find((tab) => tab.isActive);
const activeTabId = activeTab?.id;

// Simple UUID generator for our needs
const generateId = useCallback(() => {
Expand All @@ -155,6 +167,22 @@ export const TabBar = React.forwardRef<HTMLDivElement>((_, ref) => {
);
}, [currentSessionId, currentSessionTitle]);

// Persist selected model into the active tab in Redux
useEffect(() => {
if (activeTabId && selectedModel?.title) {
dispatch(
setTabModel({ id: activeTabId, modelTitle: selectedModel.title }),
);
}
}, [activeTabId, selectedModel?.title, mode, dispatch]);

// Persist the currently selected mode into the active tab
useEffect(() => {
if (activeTabId && mode) {
dispatch(setTabMode({ id: activeTabId, mode }));
}
}, [activeTabId, mode, dispatch]);

const handleNewTab = async () => {
// Save current session before creating new one
if (hasHistory) {
Expand All @@ -171,6 +199,8 @@ export const TabBar = React.forwardRef<HTMLDivElement>((_, ref) => {
title: `Chat ${tabs.length + 1}`,
isActive: true,
sessionId: undefined,
modelTitle: selectedModel?.title,
mode,
}),
);
};
Expand All @@ -196,6 +226,22 @@ export const TabBar = React.forwardRef<HTMLDivElement>((_, ref) => {
}

dispatch(setActiveTab(id));

// restore mode for this tab (if set)
if (targetTab.mode) {
dispatch(setMode(targetTab.mode));
}

// restore model for this tab
if (targetTab.modelTitle && selectedProfile) {
void dispatch(
updateSelectedModelByRole({
selectedProfile,
role: "chat",
modelTitle: targetTab.modelTitle,
}),
);
}
};

const handleTabClose = async (id: string) => {
Expand Down
28 changes: 28 additions & 0 deletions gui/src/redux/slices/tabsSlice.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { MessageModes } from "core";

export interface Tab {
id: string;
title: string;
isActive: boolean;
sessionId?: string;
modelTitle?: string; // store per-tab model
mode?: MessageModes; // store per-tab mode ('chat' | 'plan' | 'agent')
}

interface TabsState {
Expand All @@ -17,6 +20,8 @@ export const INITIAL_TABS_STATE: TabsState = {
id: Date.now().toString(36) + Math.random().toString(36).substring(2),
title: "Chat 1",
isActive: true,
modelTitle: undefined,
mode: "chat",
},
],
};
Expand All @@ -37,6 +42,26 @@ export const tabsSlice = createSlice({
tab.id === id ? { ...tab, ...updates } : tab,
);
},
// Set the model title for a specific tab
setTabModel: (
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 9, 2025

Choose a reason for hiding this comment

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

This PR's implementation contradicts its stated architectural goal. It re-introduces per-tab state management for model and mode (in tabsSlice.ts) instead of removing it, and the logic in TabBar.tsx incorrectly uses this tab-level state as the source of truth, overriding the intended session-level persistence.

Prompt for AI agents
Address the following comment on gui/src/redux/slices/tabsSlice.ts at line 46:

<comment>This PR&#39;s implementation contradicts its stated architectural goal. It re-introduces per-tab state management for model and mode (in `tabsSlice.ts`) instead of removing it, and the logic in `TabBar.tsx` incorrectly uses this tab-level state as the source of truth, overriding the intended session-level persistence.</comment>

<file context>
@@ -37,6 +42,26 @@ export const tabsSlice = createSlice({
       );
     },
+    // Set the model title for a specific tab
+    setTabModel: (
+      state,
+      action: PayloadAction&lt;{ id: string; modelTitle: string }&gt;,
</file context>

✅ Addressed in a3b5ee8

state,
action: PayloadAction<{ id: string; modelTitle: string }>,
) => {
const { id, modelTitle } = action.payload;
state.tabs = state.tabs.map((tab) =>
tab.id === id ? { ...tab, modelTitle } : tab,
);
},
// Set the mode for a specific tab
setTabMode: (
state,
action: PayloadAction<{ id: string; mode: MessageModes }>,
) => {
const { id, mode } = action.payload;
state.tabs = state.tabs.map((tab) =>
tab.id === id ? { ...tab, mode } : tab,
);
},
addTab: (state, action: PayloadAction<Tab>) => {
state.tabs = state.tabs
.map((tab) => ({
Expand Down Expand Up @@ -122,6 +147,7 @@ export const tabsSlice = createSlice({
title: currentSessionTitle,
isActive: true,
sessionId: currentSessionId,
modelTitle: undefined,
});
}
},
Expand All @@ -131,6 +157,8 @@ export const tabsSlice = createSlice({
export const {
setTabs,
updateTab,
setTabModel,
setTabMode,
addTab,
removeTab,
setActiveTab,
Expand Down
Loading