Skip to content
Open
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
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 @@ const initialState: 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: (
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