Tab-based multi-chat system supporting concurrent parent and subagent sessions, per-session stop controls, layout/scroll fixes, and attachment display improvements.
179 lines
4.2 KiB
TypeScript
179 lines
4.2 KiB
TypeScript
import {
|
|
type Tab,
|
|
type TabState,
|
|
generateTabId,
|
|
openTab,
|
|
} from "./tab-state";
|
|
|
|
export function isChatTab(tab: Tab | undefined | null): tab is Tab {
|
|
return tab?.type === "chat";
|
|
}
|
|
|
|
export function isSubagentChatTab(tab: Tab | undefined | null): tab is Tab {
|
|
return Boolean(tab?.type === "chat" && tab.sessionKey);
|
|
}
|
|
|
|
export function createBlankChatTab(title = "New Chat"): Tab {
|
|
return {
|
|
id: generateTabId(),
|
|
type: "chat",
|
|
title,
|
|
};
|
|
}
|
|
|
|
export function createParentChatTab(params: {
|
|
sessionId: string;
|
|
title?: string;
|
|
}): Tab {
|
|
return {
|
|
id: generateTabId(),
|
|
type: "chat",
|
|
title: params.title || "New Chat",
|
|
sessionId: params.sessionId,
|
|
};
|
|
}
|
|
|
|
export function createSubagentChatTab(params: {
|
|
sessionKey: string;
|
|
parentSessionId: string;
|
|
title?: string;
|
|
}): Tab {
|
|
return {
|
|
id: generateTabId(),
|
|
type: "chat",
|
|
title: params.title || "Subagent",
|
|
sessionKey: params.sessionKey,
|
|
parentSessionId: params.parentSessionId,
|
|
};
|
|
}
|
|
|
|
export function bindParentSessionToChatTab(
|
|
state: TabState,
|
|
tabId: string,
|
|
sessionId: string | null,
|
|
): TabState {
|
|
return {
|
|
...state,
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === tabId
|
|
? {
|
|
...tab,
|
|
sessionId: sessionId ?? undefined,
|
|
sessionKey: undefined,
|
|
}
|
|
: tab,
|
|
),
|
|
};
|
|
}
|
|
|
|
export function updateChatTabTitle(
|
|
state: TabState,
|
|
tabId: string,
|
|
title: string,
|
|
): TabState {
|
|
return {
|
|
...state,
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === tabId && tab.title !== title
|
|
? { ...tab, title }
|
|
: tab,
|
|
),
|
|
};
|
|
}
|
|
|
|
export function syncParentChatTabTitles(
|
|
state: TabState,
|
|
sessions: Array<{ id: string; title: string }>,
|
|
): TabState {
|
|
const titleBySessionId = new Map(sessions.map((session) => [session.id, session.title]));
|
|
let changed = false;
|
|
const tabs = state.tabs.map((tab) => {
|
|
if (tab.type !== "chat" || !tab.sessionId) {
|
|
return tab;
|
|
}
|
|
const nextTitle = titleBySessionId.get(tab.sessionId);
|
|
if (!nextTitle || nextTitle === tab.title) {
|
|
return tab;
|
|
}
|
|
changed = true;
|
|
return { ...tab, title: nextTitle };
|
|
});
|
|
return changed ? { ...state, tabs } : state;
|
|
}
|
|
|
|
export function syncSubagentChatTabTitles(
|
|
state: TabState,
|
|
subagents: Array<{ childSessionKey: string; label?: string; task: string }>,
|
|
): TabState {
|
|
const titleBySessionKey = new Map(
|
|
subagents.map((subagent) => [subagent.childSessionKey, subagent.label || subagent.task]),
|
|
);
|
|
let changed = false;
|
|
const tabs = state.tabs.map((tab) => {
|
|
if (tab.type !== "chat" || !tab.sessionKey) {
|
|
return tab;
|
|
}
|
|
const nextTitle = titleBySessionKey.get(tab.sessionKey);
|
|
if (!nextTitle || nextTitle === tab.title) {
|
|
return tab;
|
|
}
|
|
changed = true;
|
|
return { ...tab, title: nextTitle };
|
|
});
|
|
return changed ? { ...state, tabs } : state;
|
|
}
|
|
|
|
export function openOrFocusParentChatTab(
|
|
state: TabState,
|
|
params: { sessionId: string; title?: string },
|
|
): TabState {
|
|
return openTab(state, createParentChatTab(params));
|
|
}
|
|
|
|
export function openOrFocusSubagentChatTab(
|
|
state: TabState,
|
|
params: { sessionKey: string; parentSessionId: string; title?: string },
|
|
): TabState {
|
|
return openTab(state, createSubagentChatTab(params));
|
|
}
|
|
|
|
export function closeChatTabsForSession(
|
|
state: TabState,
|
|
sessionId: string,
|
|
): TabState {
|
|
const tabs = state.tabs.filter((tab) => {
|
|
if (tab.pinned) {
|
|
return true;
|
|
}
|
|
if (tab.type !== "chat") {
|
|
return true;
|
|
}
|
|
return tab.sessionId !== sessionId && tab.parentSessionId !== sessionId;
|
|
});
|
|
|
|
const activeStillExists = tabs.some((tab) => tab.id === state.activeTabId);
|
|
return {
|
|
tabs,
|
|
activeTabId: activeStillExists ? state.activeTabId : tabs[tabs.length - 1]?.id ?? null,
|
|
};
|
|
}
|
|
|
|
export function resolveChatIdentityForTab(tab: Tab | undefined | null): {
|
|
sessionId: string | null;
|
|
subagentKey: string | null;
|
|
} {
|
|
if (!tab || tab.type !== "chat") {
|
|
return { sessionId: null, subagentKey: null };
|
|
}
|
|
if (tab.sessionKey) {
|
|
return {
|
|
sessionId: tab.parentSessionId ?? null,
|
|
subagentKey: tab.sessionKey,
|
|
};
|
|
}
|
|
return {
|
|
sessionId: tab.sessionId ?? null,
|
|
subagentKey: null,
|
|
};
|
|
}
|