Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { ChatLaunchConfig } from "shared/tabs-types";
import { SessionSelector } from "./components/SessionSelector";
import { ChatPaneInterface as WorkspaceChatInterface } from "./components/WorkspaceChatInterface";
import { useWorkspaceChatController } from "./hooks/useWorkspaceChatController";

Expand All @@ -16,46 +15,24 @@ export function ChatPane({
initialLaunchConfig?: ChatLaunchConfig | null;
onConsumeLaunchConfig?: () => void;
}) {
const {
organizationId,
workspacePath,
sessionItems,
handleSelectSession,
handleNewChat,
handleDeleteSession,
getOrCreateSession,
} = useWorkspaceChatController({
onSessionIdChange,
sessionId,
workspaceId,
});
const { organizationId, workspacePath, handleNewChat, getOrCreateSession } =
useWorkspaceChatController({
onSessionIdChange,
sessionId,
workspaceId,
});

return (
<div className="flex h-full w-full min-h-0 flex-col">
<div className="flex h-8 shrink-0 items-center border-b border-border px-2">
<SessionSelector
currentSessionId={sessionId}
sessions={sessionItems}
fallbackTitle="New Chat"
onSelectSession={handleSelectSession}
onNewChat={handleNewChat}
onDeleteSession={handleDeleteSession}
/>
</div>

<div className="min-h-0 flex-1">
<WorkspaceChatInterface
getOrCreateSession={getOrCreateSession}
initialLaunchConfig={initialLaunchConfig ?? null}
onConsumeLaunchConfig={onConsumeLaunchConfig}
isFocused
onResetSession={handleNewChat}
sessionId={sessionId}
workspaceId={workspaceId}
organizationId={organizationId}
cwd={workspacePath}
/>
</div>
</div>
<WorkspaceChatInterface
getOrCreateSession={getOrCreateSession}
initialLaunchConfig={initialLaunchConfig ?? null}
onConsumeLaunchConfig={onConsumeLaunchConfig}
isFocused
onResetSession={handleNewChat}
sessionId={sessionId}
workspaceId={workspaceId}
organizationId={organizationId}
cwd={workspacePath}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { RendererContext } from "@superset/panes";
import { useCallback } from "react";
import { getV2NotificationSourcesForPane } from "renderer/stores/v2-notifications";
import { V2NotificationStatusIndicator } from "../../../../../../components/V2NotificationStatusIndicator";
import type { ChatPaneData, PaneViewerData } from "../../../../../../types";
import { useWorkspaceChatController } from "../../hooks/useWorkspaceChatController";
import { SessionSelector } from "../SessionSelector";

interface ChatPaneTitleProps {
context: RendererContext<PaneViewerData>;
workspaceId: string;
}

export function ChatPaneTitle({ context, workspaceId }: ChatPaneTitleProps) {
const data = context.pane.data as ChatPaneData;
const { sessionId } = data;
const { actions } = context;

const onSessionIdChange = useCallback(
(nextSessionId: string | null) => {
actions.updateData({ ...data, sessionId: nextSessionId });
},
[actions, data],
);

const {
sessionItems,
handleSelectSession,
handleNewChat,
handleDeleteSession,
} = useWorkspaceChatController({
workspaceId,
sessionId,
onSessionIdChange,
});
Comment on lines +26 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Duplicate useWorkspaceChatController per pane

ChatPaneTitle (rendered by renderTitle) and ChatPane (rendered by renderPane) each independently call useWorkspaceChatController, so two separate useLiveQuery subscriptions and two workspace.get tRPC queries run per chat pane. ChatPane still needs organizationId, workspacePath, and getOrCreateSession from the hook, so the overlap can't be eliminated completely — but the subscription to the session list is doubled for every open chat pane. This is consistent with how TerminalSessionDropdown works in the terminal pane, but worth noting as a scalability consideration.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/ChatPaneTitle/ChatPaneTitle.tsx
Line: 26-35

Comment:
**Duplicate `useWorkspaceChatController` per pane**

`ChatPaneTitle` (rendered by `renderTitle`) and `ChatPane` (rendered by `renderPane`) each independently call `useWorkspaceChatController`, so two separate `useLiveQuery` subscriptions and two `workspace.get` tRPC queries run per chat pane. `ChatPane` still needs `organizationId`, `workspacePath`, and `getOrCreateSession` from the hook, so the overlap can't be eliminated completely — but the subscription to the session list is doubled for every open chat pane. This is consistent with how `TerminalSessionDropdown` works in the terminal pane, but worth noting as a scalability consideration.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +26 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect useWorkspaceChatController for side effects (queries, subscriptions, mutations, effects).
fd -t f 'useWorkspaceChatController' --exec cat {}
echo "----- callers -----"
rg -nP '\buseWorkspaceChatController\s*\(' -C2

Repository: superset-sh/superset

Length of output: 8096


🏁 Script executed:

#!/bin/bash
# Check if ChatPaneTitle is imported/used in ChatPane
echo "=== Checking ChatPane.tsx for ChatPaneTitle import/usage ==="
cat "apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/ChatPane/ChatPane.tsx"

echo -e "\n=== Checking ChatPaneTitle full file ==="
cat "apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/ChatPaneTitle/ChatPaneTitle.tsx"

Repository: superset-sh/superset

Length of output: 2825


🏁 Script executed:

#!/bin/bash
# Search for imports of ChatPaneTitle
echo "=== Finding ChatPaneTitle imports ==="
rg 'ChatPaneTitle' --type ts --type tsx -g '!node_modules'

# Check what the pane registry structure looks like
echo -e "\n=== Checking pane registry hook to understand pane rendering ==="
fd -t f 'usePaneRegistry' --path '*/hooks/*' | head -5
</script>

Repository: superset-sh/superset

Length of output: 551


🏁 Script executed:

#!/bin/bash
# Search more broadly for ChatPaneTitle
echo "=== Searching for ChatPaneTitle ==="
rg 'ChatPaneTitle' -t ts -t tsx

# Look for how pane titles/components are registered
echo -e "\n=== Finding usePaneRegistry hook ==="
find . -name '*usePaneRegistry*' -type f | grep -v node_modules

# Check if ChatPaneTitle is exported in a barrel export
echo -e "\n=== Checking ChatPane directory index files ==="
find "apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/ChatPane" -name 'index.*' -type f -exec cat {} \;

Repository: superset-sh/superset

Length of output: 3700


🏁 Script executed:

#!/bin/bash
# Read usePaneRegistry to see how panes are structured
echo "=== usePaneRegistry.tsx ===" 
cat "apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx"

Repository: superset-sh/superset

Length of output: 15908


Lift useWorkspaceChatController to avoid duplicate side effects.

useWorkspaceChatController is called twice for the same pane — once in ChatPaneTitle (for session list UI) and once in ChatPane (for chat runtime). The hook runs queries, subscriptions, and mutations, so duplicate calls cause duplicate workspace queries, duplicate session subscriptions, and duplicate mutation setups.

Consider lifting the hook call to a shared parent (the pane registry's chat config or a context provider scoped to the pane) and passing only the needed values as props to each child, or split the hook into separate "session-list" and "chat-runtime" hooks so side effects run exactly once per pane.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/ChatPaneTitle/ChatPaneTitle.tsx
around lines 26 - 35, ChatPaneTitle and ChatPane both call
useWorkspaceChatController, causing duplicate queries/subscriptions/mutations;
lift the hook out to a single owner (e.g., the pane registry's chat config or a
pane-scoped context) so side effects run once and pass down only needed values
(sessionItems, handleSelectSession, handleNewChat, handleDeleteSession, and any
runtime handlers) as props to ChatPaneTitle and ChatPane, or alternatively split
useWorkspaceChatController into two hooks (useWorkspaceSessionList and
useWorkspaceChatRuntime) and replace the duplicated calls in ChatPaneTitle and
ChatPane with the appropriate lighter hook; update the parent component that
renders ChatPane and ChatPaneTitle to call the single hook (or both split hooks)
and forward the derived handlers/state.


return (
<div className="flex min-w-0 flex-1 items-center gap-1.5">
<SessionSelector
currentSessionId={sessionId}
sessions={sessionItems}
fallbackTitle="New Chat"
onSelectSession={handleSelectSession}
onNewChat={handleNewChat}
onDeleteSession={handleDeleteSession}
/>
<V2NotificationStatusIndicator
workspaceId={workspaceId}
sources={getV2NotificationSourcesForPane(context.pane)}
/>
</div>
);
Comment on lines +14 to +52
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 context.isActive unused — pane-focus text styling dropped

The old inline renderTitle applied ctx.isActive ? "text-foreground" : "text-muted-foreground" to the title span. ChatPaneTitle receives the full context (which includes context.isActive) but never threads it into SessionSelector, so the header title no longer changes color when the pane gains or loses focus. Compare with FilePaneTabTitle which explicitly forwards isActive to apply that transition. If SessionSelector handles its own active-state styling this is fine; otherwise the pane header will appear static regardless of focus.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/ChatPaneTitle/ChatPaneTitle.tsx
Line: 14-52

Comment:
**`context.isActive` unused — pane-focus text styling dropped**

The old inline `renderTitle` applied `ctx.isActive ? "text-foreground" : "text-muted-foreground"` to the title span. `ChatPaneTitle` receives the full `context` (which includes `context.isActive`) but never threads it into `SessionSelector`, so the header title no longer changes color when the pane gains or loses focus. Compare with `FilePaneTabTitle` which explicitly forwards `isActive` to apply that transition. If `SessionSelector` handles its own active-state styling this is fine; otherwise the pane header will appear static regardless of focus.

How can I resolve this? If you propose a fix, please make it concise.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChatPaneTitle } from "./ChatPaneTitle";
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ export function ChatPaneInterface({
getOrCreateSession,
onResetSession,
onUserMessageSubmitted,
onRawSnapshotChange,
}: ChatPaneInterfaceProps) {
const { models: availableModels, defaultModel } = useAvailableModels();
const selectedModelId = useChatPreferencesStore(
Expand Down Expand Up @@ -498,23 +497,6 @@ export function ChatPaneInterface({
bumpFooterScroll();
}, [bumpFooterScroll, pendingQuestion]);

useEffect(() => {
onRawSnapshotChange?.({
sessionId,
isRunning: canAbort,
currentMessage: currentMessage ?? null,
messages: messages ?? [],
error,
});
}, [
canAbort,
currentMessage,
error,
messages,
onRawSnapshotChange,
sessionId,
]);

useEffect(() => {
messagesLengthRef.current = messages?.length ?? 0;
}, [messages]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import type { UseChatDisplayReturn } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/hooks/useWorkspaceChatDisplay";
import type { ChatLaunchConfig } from "shared/tabs-types";

export interface ChatRawSnapshot {
sessionId: string | null;
isRunning: boolean;
currentMessage: UseChatDisplayReturn["currentMessage"] | null;
messages: UseChatDisplayReturn["messages"];
error: unknown;
}

export interface ChatPaneInterfaceProps {
sessionId: string | null;
initialLaunchConfig: ChatLaunchConfig | null;
Expand All @@ -25,5 +16,4 @@ export interface ChatPaneInterfaceProps {
getOrCreateSession: () => Promise<string>;
onResetSession: () => Promise<void>;
onUserMessageSubmitted?: (message: string) => void;
onRawSnapshotChange?: (snapshot: ChatRawSnapshot) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import type {
} from "../../types";
import { BrowserPane, BrowserPaneToolbar } from "./components/BrowserPane";
import { ChatPane } from "./components/ChatPane";
import { ChatPaneTitle } from "./components/ChatPane/components/ChatPaneTitle";
import { CommentPane } from "./components/CommentPane";
import { DiffPane } from "./components/DiffPane";
import { FilePane } from "./components/FilePane";
Expand Down Expand Up @@ -427,21 +428,7 @@ export function usePaneRegistry(
getIcon: () => <MessageSquare className="size-3.5" />,
getTitle: () => "Chat",
renderTitle: (ctx: RendererContext<PaneViewerData>) => (
<div className="flex min-w-0 flex-1 items-center gap-1.5">
<MessageSquare className="size-3.5 shrink-0" />
<span
className={cn(
"min-w-0 flex-1 truncate text-xs transition-colors duration-150",
ctx.isActive ? "text-foreground" : "text-muted-foreground",
)}
>
Chat
</span>
<V2NotificationStatusIndicator
workspaceId={workspaceId}
sources={getV2NotificationSourcesForPane(ctx.pane)}
/>
</div>
<ChatPaneTitle context={ctx} workspaceId={workspaceId} />
),
renderPane: (ctx: RendererContext<PaneViewerData>) => {
const data = ctx.pane.data as ChatPaneData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import {
ChatRuntimeServiceProvider,
ChatServiceProvider,
} from "@superset/chat/client";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { CopyIcon } from "lucide-react";
import { useCallback } from "react";
import type { MosaicBranch } from "react-mosaic-component";
import { createChatServiceIpcClient } from "renderer/components/Chat/utils/chat-service-client";
import { env } from "renderer/env.renderer";
import { electronQueryClient } from "renderer/providers/ElectronTRPCProvider";
import { useTabsStore } from "renderer/stores/tabs/store";
import type { SplitPaneOptions, Tab } from "renderer/stores/tabs/types";
Expand All @@ -16,7 +13,6 @@ import { BasePaneWindow, PaneToolbarActions } from "../components";
import { ChatPaneInterface } from "./ChatPaneInterface";
import { SessionSelector } from "./components/SessionSelector";
import { useChatPaneController } from "./hooks/useChatPaneController";
import { useChatRawSnapshot } from "./hooks/useChatRawSnapshot";
import { createChatRuntimeServiceIpcClient } from "./utils/chat-runtime-service-client";

const chatRuntimeIpcClient = createChatRuntimeServiceIpcClient();
Expand Down Expand Up @@ -66,7 +62,6 @@ export function ChatPane({
onMoveToTab,
onMoveToNewTab,
}: ChatPaneProps) {
const showDevToolbarActions = env.NODE_ENV === "development";
const isFocused = useTabsStore((s) => s.focusedPaneIds[tabId] === paneId);
const equalizePaneSplits = useTabsStore((s) => s.equalizePaneSplits);
const paneName = useTabsStore((s) => s.panes[paneId]?.name ?? "New Chat");
Expand All @@ -90,11 +85,6 @@ export function ChatPane({
paneId,
workspaceId,
});
const {
snapshotAvailableForSession,
handleRawSnapshotChange,
handleCopyRawSnapshot,
} = useChatRawSnapshot({ sessionId });

const applySubmittedMessageFallbackTitle = useCallback(
(message: string) => {
Expand Down Expand Up @@ -165,27 +155,6 @@ export function ChatPane({
splitOrientation={handlers.splitOrientation}
onSplitPane={handlers.onSplitPane}
onClosePane={handlers.onClosePane}
leadingActions={
showDevToolbarActions ? (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => {
void handleCopyRawSnapshot();
}}
disabled={!snapshotAvailableForSession}
className="rounded p-0.5 text-muted-foreground/60 transition-colors hover:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-40"
>
<CopyIcon className="size-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom" showArrow={false}>
Copy raw chat JSON (dev)
</TooltipContent>
</Tooltip>
) : null
}
closeHotkeyId="CLOSE_TERMINAL"
/>
</div>
Expand Down Expand Up @@ -224,9 +193,6 @@ export function ChatPane({
onStartFreshSession={handleStartFreshSession}
onConsumeLaunchConfig={consumeLaunchConfig}
onUserMessageSubmitted={applySubmittedMessageFallbackTitle}
onRawSnapshotChange={
showDevToolbarActions ? handleRawSnapshotChange : undefined
}
/>
</div>
</TabContentContextMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ export function ChatPaneInterface({
onStartFreshSession,
onConsumeLaunchConfig,
onUserMessageSubmitted,
onRawSnapshotChange,
}: ChatPaneInterfaceProps) {
const { models: availableModels, defaultModel } = useAvailableModels();
const selectedModelId = useChatPreferencesStore(
Expand Down Expand Up @@ -521,23 +520,6 @@ export function ChatPaneInterface({
setSubmitStatus(undefined);
}, [isRunning]);

useEffect(() => {
onRawSnapshotChange?.({
sessionId,
isRunning: canAbort,
currentMessage: currentMessage ?? null,
messages: messages ?? [],
error,
});
}, [
canAbort,
currentMessage,
error,
messages,
onRawSnapshotChange,
sessionId,
]);

useEffect(() => {
messagesLengthRef.current = messages?.length ?? 0;
}, [messages]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import type { UseChatDisplayReturn } from "@superset/chat/client";
import type { StartFreshSessionResult } from "renderer/components/Chat/ChatInterface/types";
import type { ChatLaunchConfig } from "shared/tabs-types";

export interface ChatRawSnapshot {
sessionId: string | null;
isRunning: boolean;
currentMessage: UseChatDisplayReturn["currentMessage"] | null;
messages: UseChatDisplayReturn["messages"];
error: unknown;
}

export interface ChatPaneInterfaceProps {
paneId: string;
sessionId: string | null;
Expand All @@ -23,5 +14,4 @@ export interface ChatPaneInterfaceProps {
onStartFreshSession: () => Promise<StartFreshSessionResult>;
onConsumeLaunchConfig: () => void;
onUserMessageSubmitted?: (message: string) => void;
onRawSnapshotChange?: (snapshot: ChatRawSnapshot) => void;
}

This file was deleted.

This file was deleted.

Loading