fix: proper tab isolation with updateTabById

- Added updateTabById function to store for updating specific tabs by ID
- Capture tab ID at request start (requestTabId)
- All streaming updates now go to the captured tab ID, not activeTabId
- Switching tabs during streaming no longer pollutes other tabs
- Local UI state only updates if still on the same tab
- Complete isolation: each tab's history/preview/agent is independent
This commit is contained in:
Gemini AI
2025-12-29 03:14:07 +04:00
Unverified
parent 0e1bdb0c06
commit 9996622b12
2 changed files with 33 additions and 20 deletions

View File

@@ -418,6 +418,7 @@ export default function AIAssist() {
addAIAssistTab, addAIAssistTab,
removeAIAssistTab, removeAIAssistTab,
updateActiveTab, updateActiveTab,
updateTabById,
selectedProvider, selectedProvider,
selectedModels, selectedModels,
setSelectedModel setSelectedModel
@@ -515,6 +516,10 @@ export default function AIAssist() {
const finalInput = forcedPrompt || input; const finalInput = forcedPrompt || input;
if (!finalInput.trim() || isProcessing) return; if (!finalInput.trim() || isProcessing) return;
// CRITICAL: Capture the tab ID at the start of this request
const requestTabId = activeTabId;
if (!requestTabId) return;
const controller = new AbortController(); const controller = new AbortController();
setAbortController(controller); setAbortController(controller);
@@ -526,7 +531,7 @@ export default function AIAssist() {
timestamp: new Date(), timestamp: new Date(),
}; };
const newHistory = [...aiAssistHistory, userMsg]; const newHistory = [...aiAssistHistory, userMsg];
updateActiveTab({ history: newHistory }); updateTabById(requestTabId, { history: newHistory });
setInput(""); setInput("");
} }
@@ -540,9 +545,10 @@ export default function AIAssist() {
timestamp: new Date() timestamp: new Date()
}; };
// Update history in active tab // Update history in the REQUEST's tab (not current active tab)
const updatedHistory = [...aiAssistHistory, assistantMsg]; const startingHistory = [...aiAssistHistory, { role: "user" as const, content: finalInput, timestamp: new Date() }];
updateActiveTab({ history: updatedHistory }); const updatedHistory = [...startingHistory, assistantMsg];
updateTabById(requestTabId, { history: updatedHistory });
try { try {
let accumulated = ""; let accumulated = "";
@@ -571,22 +577,21 @@ export default function AIAssist() {
if (streamStatus) setStatus(streamStatus); if (streamStatus) setStatus(streamStatus);
if (preview && JSON.stringify(preview) !== JSON.stringify(lastParsedPreview)) { // Only update local state if we're still on the same tab
setPreviewData(preview); if (activeTabId === requestTabId) {
lastParsedPreview = preview; if (preview && JSON.stringify(preview) !== JSON.stringify(lastParsedPreview)) {
setShowCanvas(true); setPreviewData(preview);
if (isPreviewRenderable(preview)) setViewMode("preview"); lastParsedPreview = preview;
setShowCanvas(true);
if (isPreviewRenderable(preview)) setViewMode("preview");
}
// Save preview data to tab if (agent !== currentAgent) {
updateActiveTab({ previewData: preview }); setCurrentAgent(agent);
}
} }
if (agent !== currentAgent) { // Always update the REQUEST's tab (by ID), not the active tab
setCurrentAgent(agent);
updateActiveTab({ currentAgent: agent });
}
// Stream updates to current tab history
const lastMsg = { const lastMsg = {
role: "assistant" as const, role: "assistant" as const,
content: accumulated, content: accumulated,
@@ -595,8 +600,10 @@ export default function AIAssist() {
timestamp: new Date() timestamp: new Date()
}; };
updateActiveTab({ updateTabById(requestTabId, {
history: [...updatedHistory.slice(0, -1), lastMsg] history: [...updatedHistory.slice(0, -1), lastMsg],
previewData: preview || undefined,
currentAgent: agent
}); });
}, },
signal: controller.signal signal: controller.signal
@@ -617,7 +624,7 @@ export default function AIAssist() {
console.error("Assist error:", error); console.error("Assist error:", error);
const message = error instanceof Error ? error.message : "AI Assist failed"; const message = error instanceof Error ? error.message : "AI Assist failed";
const errorMsg: AIAssistMessage = { role: "assistant", content: message, timestamp: new Date() }; const errorMsg: AIAssistMessage = { role: "assistant", content: message, timestamp: new Date() };
updateActiveTab({ history: [...aiAssistHistory, errorMsg] }); updateTabById(requestTabId, { history: [...aiAssistHistory, errorMsg] });
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
setAbortController(null); setAbortController(null);

View File

@@ -56,6 +56,7 @@ interface AppState {
addAIAssistTab: (agent?: string) => void; addAIAssistTab: (agent?: string) => void;
removeAIAssistTab: (id: string) => void; removeAIAssistTab: (id: string) => void;
updateActiveTab: (updates: Partial<AIAssistTab>) => void; updateActiveTab: (updates: Partial<AIAssistTab>) => void;
updateTabById: (tabId: string, updates: Partial<AIAssistTab>) => void;
setLanguage: (lang: "en" | "ru" | "he") => void; setLanguage: (lang: "en" | "ru" | "he") => void;
setSelectedProvider: (provider: ModelProvider) => void; setSelectedProvider: (provider: ModelProvider) => void;
@@ -149,6 +150,11 @@ const useStore = create<AppState>((set) => ({
t.id === state.activeTabId ? { ...t, ...updates } : t t.id === state.activeTabId ? { ...t, ...updates } : t
) )
})), })),
updateTabById: (tabId, updates) => set((state) => ({
aiAssistTabs: state.aiAssistTabs.map(t =>
t.id === tabId ? { ...t, ...updates } : t
)
})),
setLanguage: (lang) => set({ language: lang }), setLanguage: (lang) => set({ language: lang }),
setSelectedProvider: (provider) => set({ selectedProvider: provider }), setSelectedProvider: (provider) => set({ selectedProvider: provider }),