From 6942abd9c6b17672c95093bca3ac67d2d6fb4ab9 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 16:06:10 -0800 Subject: [PATCH 1/7] Nice Chat UI --- .../TabsContent/GroupStrip/GroupStrip.tsx | 22 + .../TabView/ChatPane/ChatInterface.tsx | 398 ++++++++++++++++++ .../TabsContent/TabView/ChatPane/ChatPane.tsx | 59 +++ .../TabsContent/TabView/ChatPane/index.ts | 1 + .../ContentView/TabsContent/TabView/index.tsx | 17 + .../desktop/src/renderer/stores/tabs/store.ts | 35 ++ .../desktop/src/renderer/stores/tabs/types.ts | 1 + .../desktop/src/renderer/stores/tabs/utils.ts | 42 ++ apps/desktop/src/shared/tabs-types.ts | 11 +- docs/ai-chat-plan.md | 10 +- 10 files changed, 590 insertions(+), 6 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPane.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx index 6924e6c9571..5d1aed4cb32 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx @@ -10,6 +10,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useNavigate, useParams } from "@tanstack/react-router"; import { useCallback, useMemo, useState } from "react"; import { + HiMiniChatBubbleLeftRight, HiMiniChevronDown, HiMiniCog6Tooth, HiMiniCommandLine, @@ -41,6 +42,7 @@ export function GroupStrip() { const activeTabIds = useTabsStore((s) => s.activeTabIds); const tabHistoryStacks = useTabsStore((s) => s.tabHistoryStacks); const { addTab, openPreset } = useTabsWithPresets(); + const addChatTab = useTabsStore((s) => s.addChatTab); const renameTab = useTabsStore((s) => s.renameTab); const removeTab = useTabsStore((s) => s.removeTab); const setActiveTab = useTabsStore((s) => s.setActiveTab); @@ -89,6 +91,11 @@ export function GroupStrip() { addTab(activeWorkspaceId); }; + const handleAddChat = () => { + if (!activeWorkspaceId) return; + addChatTab(activeWorkspaceId); + }; + const handleSelectPreset = (preset: Parameters[1]) => { if (!activeWorkspaceId) return; openPreset(activeWorkspaceId, preset); @@ -176,6 +183,21 @@ export function GroupStrip() { })} )} + + + + + + New Chat + + movePaneToNewTab(paneId)} isLastPaneInTab={checkIsLastPaneInTab} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx new file mode 100644 index 00000000000..c3333851372 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx @@ -0,0 +1,398 @@ +import { + CodeBlock, + CodeBlockCopyButton, +} from "@superset/ui/ai-elements/code-block"; +import { + Confirmation, + ConfirmationAction, + ConfirmationActions, + ConfirmationRequest, + ConfirmationTitle, +} from "@superset/ui/ai-elements/confirmation"; +import { + Conversation, + ConversationContent, + ConversationEmptyState, + ConversationScrollButton, +} from "@superset/ui/ai-elements/conversation"; +import { Loader } from "@superset/ui/ai-elements/loader"; +import { + Message, + MessageContent, + MessageResponse, +} from "@superset/ui/ai-elements/message"; +import { + Plan, + PlanContent, + PlanDescription, + PlanHeader, + PlanTitle, +} from "@superset/ui/ai-elements/plan"; +import { + PromptInput, + PromptInputFooter, + PromptInputSubmit, + PromptInputTextarea, +} from "@superset/ui/ai-elements/prompt-input"; +import { + Reasoning, + ReasoningContent, + ReasoningTrigger, +} from "@superset/ui/ai-elements/reasoning"; +import { Suggestion, Suggestions } from "@superset/ui/ai-elements/suggestion"; +import { + Tool, + ToolContent, + ToolHeader, + ToolInput, + ToolOutput, +} from "@superset/ui/ai-elements/tool"; +import { useCallback, useState } from "react"; +import { HiMiniChatBubbleLeftRight } from "react-icons/hi2"; + +type MessageRole = "user" | "assistant" | "system"; + +interface ToolCall { + id: string; + name: string; + state: "input-available" | "output-available" | "output-error"; + input: Record; + output?: unknown; + errorText?: string; + approval?: { + id: string; + approved?: boolean; + reason?: string; + }; +} + +interface PlanData { + title: string; + description: string; + steps: Array<{ label: string; done: boolean }>; +} + +interface ChatMessage { + id: string; + role: MessageRole; + content: string; + reasoning?: string; + toolCalls?: ToolCall[]; + plan?: PlanData; + codeBlock?: { code: string; language: string }; +} + +const MOCK_MESSAGES: ChatMessage[] = [ + { + id: "1", + role: "user", + content: "Can you help me fix the authentication bug in the login flow?", + }, + { + id: "2", + role: "assistant", + content: "", + plan: { + title: "Fix authentication bug", + description: + "I'll investigate the login flow and fix the token validation issue.", + steps: [ + { label: "Search for auth-related files", done: true }, + { label: "Identify the bug in validateCredentials", done: true }, + { label: "Apply the fix", done: false }, + ], + }, + }, + { + id: "3", + role: "assistant", + content: + "I'll take a look at the authentication flow. Let me search for the relevant files first.", + reasoning: + "The user wants help with an authentication bug. I should look at the login-related files to understand the current implementation before suggesting fixes.", + toolCalls: [ + { + id: "tc-1", + name: "bash", + state: "output-available", + input: { command: 'grep -r "login" src/auth/ --include="*.ts"' }, + output: + "src/auth/login.ts:export async function login(credentials: Credentials) {\nsrc/auth/login.ts: const token = await validateCredentials(credentials);\nsrc/auth/session.ts: if (!session.isValid()) { return redirect('/login'); }", + }, + { + id: "tc-2", + name: "edit", + state: "output-available", + input: { + file: "src/auth/login.ts", + operation: "replace", + }, + output: "File updated successfully", + approval: { + id: "approval-1", + approved: true, + }, + }, + ], + }, + { + id: "4", + role: "assistant", + content: + "I found the issue. The `validateCredentials` function doesn't handle the case where the token has expired. Here's the fix:", + codeBlock: { + language: "typescript", + code: `export async function login(credentials: Credentials) { + const token = await validateCredentials(credentials); + if (!token || isTokenExpired(token)) { + throw new AuthError('Invalid or expired credentials'); + } + return createSession(token); +}`, + }, + }, + { + id: "5", + role: "assistant", + content: + "Now I need to install a dependency. Allow me to run this command?", + toolCalls: [ + { + id: "tc-3", + name: "bash", + state: "output-available", + input: { command: "bun add jose" }, + output: undefined, + approval: { + id: "approval-2", + }, + }, + ], + }, +]; + +const SUGGESTIONS = [ + "Fix the login bug", + "Write tests for auth", + "Refactor the session manager", + "Add rate limiting to API", +]; + +export function ChatInterface() { + const [messages, setMessages] = useState(MOCK_MESSAGES); + const [isLoading, setIsLoading] = useState(false); + + const handleSend = useCallback((message: { text: string }) => { + if (!message.text.trim()) return; + + const userMessage: ChatMessage = { + id: `msg-${Date.now()}`, + role: "user", + content: message.text, + }; + + setMessages((prev) => [...prev, userMessage]); + setIsLoading(true); + + // Simulate assistant response + setTimeout(() => { + const assistantMessage: ChatMessage = { + id: `msg-${Date.now()}-reply`, + role: "assistant", + content: + "This is a mock response. The chat backend is not connected yet.", + }; + setMessages((prev) => [...prev, assistantMessage]); + setIsLoading(false); + }, 1000); + }, []); + + const handleSuggestion = useCallback( + (suggestion: string) => { + handleSend({ text: suggestion }); + }, + [handleSend], + ); + + return ( +
+ + + {messages.length === 0 ? ( + <> + + } + /> + + {SUGGESTIONS.map((s) => ( + + ))} + + + ) : ( + messages.map((msg) => ( + + )) + )} + {isLoading && ( + + +
+ + Thinking... +
+
+
+ )} +
+ +
+ +
+
+ {messages.length > 0 && ( + + {SUGGESTIONS.map((s) => ( + + ))} + + )} + + + +
+ + + +
+
+
+ ); +} + +function ChatMessageItem({ message }: { message: ChatMessage }) { + return ( + + + {message.reasoning && ( + + + {message.reasoning} + + )} + + {message.plan && } + + {message.content && ( + {message.content} + )} + + {message.codeBlock && ( + + + + )} + + {message.toolCalls?.map((tc) => ( +
+ + + + + {(tc.output || tc.errorText) && ( + + )} + + + + {tc.approval && ( + + + {tc.approval.approved === undefined + ? `Allow ${tc.name}?` + : tc.approval.approved + ? `${tc.name} was approved` + : `${tc.name} was denied`} + + + + + Deny + + + Allow + + + + + )} +
+ ))} +
+
+ ); +} + +function PlanBlock({ plan }: { plan: PlanData }) { + return ( + + +
+ {plan.title} + {plan.description} +
+
+ +
    + {plan.steps.map((step) => ( +
  • + + {step.done ? "\u2713" : "\u25CB"} + + + {step.label} + +
  • + ))} +
+
+
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPane.tsx new file mode 100644 index 00000000000..cd8fe505887 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPane.tsx @@ -0,0 +1,59 @@ +import type { MosaicBranch } from "react-mosaic-component"; +import { BasePaneWindow, PaneToolbarActions } from "../components"; +import { ChatInterface } from "./ChatInterface"; + +interface ChatPaneProps { + paneId: string; + path: MosaicBranch[]; + isActive: boolean; + tabId: string; + workspaceId: string; + splitPaneAuto: ( + tabId: string, + sourcePaneId: string, + dimensions: { width: number; height: number }, + path?: MosaicBranch[], + ) => void; + removePane: (paneId: string) => void; + setFocusedPane: (tabId: string, paneId: string) => void; +} + +export function ChatPane({ + paneId, + path, + isActive, + tabId, + workspaceId: _workspaceId, + splitPaneAuto, + removePane, + setFocusedPane, +}: ChatPaneProps) { + return ( + ( +
+
+ + Chat + +
+ +
+ )} + > + +
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/index.ts new file mode 100644 index 00000000000..50356fa07cf --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/index.ts @@ -0,0 +1 @@ +export { ChatPane } from "./ChatPane"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx index d4f6ed59fe3..a78481b40b8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx @@ -16,6 +16,7 @@ import { cleanLayout, extractPaneIdsFromLayout, } from "renderer/stores/tabs/utils"; +import { ChatPane } from "./ChatPane"; import { FileViewerPane } from "./FileViewerPane"; import { TabPane } from "./TabPane"; @@ -157,6 +158,22 @@ export function TabView({ tab }: TabViewProps) { ); } + // Route chat panes to ChatPane component + if (paneInfo.type === "chat") { + return ( + + ); + } + // Default: terminal panes return ( ()( return { tabId: tab.id, paneId: pane.id }; }, + addChatTab: (workspaceId: string) => { + const state = get(); + + const { tab, pane } = createChatTabWithPane(workspaceId, state.tabs); + + const currentActiveId = state.activeTabIds[workspaceId]; + const historyStack = state.tabHistoryStacks[workspaceId] || []; + const newHistoryStack = currentActiveId + ? [ + currentActiveId, + ...historyStack.filter((id) => id !== currentActiveId), + ] + : historyStack; + + set({ + tabs: [...state.tabs, tab], + panes: { ...state.panes, [pane.id]: pane }, + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: tab.id, + }, + focusedPaneIds: { + ...state.focusedPaneIds, + [tab.id]: pane.id, + }, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: newHistoryStack, + }, + }); + + return { tabId: tab.id, paneId: pane.id }; + }, + addTabWithMultiplePanes: ( workspaceId: string, options: AddTabWithMultiplePanesOptions, diff --git a/apps/desktop/src/renderer/stores/tabs/types.ts b/apps/desktop/src/renderer/stores/tabs/types.ts index 58a613e88d0..dd6b7acb523 100644 --- a/apps/desktop/src/renderer/stores/tabs/types.ts +++ b/apps/desktop/src/renderer/stores/tabs/types.ts @@ -68,6 +68,7 @@ export interface TabsStore extends TabsState { workspaceId: string, options?: AddTabOptions, ) => { tabId: string; paneId: string }; + addChatTab: (workspaceId: string) => { tabId: string; paneId: string }; addTabWithMultiplePanes: ( workspaceId: string, options: AddTabWithMultiplePanesOptions, diff --git a/apps/desktop/src/renderer/stores/tabs/utils.ts b/apps/desktop/src/renderer/stores/tabs/utils.ts index d638edb5aac..f32cb6c883e 100644 --- a/apps/desktop/src/renderer/stores/tabs/utils.ts +++ b/apps/desktop/src/renderer/stores/tabs/utils.ts @@ -212,6 +212,48 @@ export const createFileViewerPane = ( }; }; +/** + * Creates a new chat pane + */ +export const createChatPane = (tabId: string): Pane => { + const id = generateId("pane"); + + return { + id, + tabId, + type: "chat", + name: "Chat", + chat: { + sessionId: generateId("chat-session"), + }, + }; +}; + +/** + * Creates a new tab with a chat pane atomically + */ +export const createChatTabWithPane = ( + workspaceId: string, + existingTabs: Tab[] = [], +): { tab: Tab; pane: Pane } => { + const tabId = generateId("tab"); + const pane = createChatPane(tabId); + + const workspaceTabs = existingTabs.filter( + (t) => t.workspaceId === workspaceId, + ); + + const tab: Tab = { + id: tabId, + name: `Chat ${workspaceTabs.filter((t) => t.name.startsWith("Chat")).length + 1}`, + workspaceId, + layout: pane.id, + createdAt: Date.now(), + }; + + return { tab, pane }; +}; + /** * Generates a static tab name based on existing tabs * (e.g., "Terminal 1", "Terminal 2", finding the next available number) diff --git a/apps/desktop/src/shared/tabs-types.ts b/apps/desktop/src/shared/tabs-types.ts index cdc8c56923f..f725b2a4297 100644 --- a/apps/desktop/src/shared/tabs-types.ts +++ b/apps/desktop/src/shared/tabs-types.ts @@ -8,7 +8,7 @@ import type { ChangeCategory } from "./changes-types"; /** * Pane types that can be displayed within a tab */ -export type PaneType = "terminal" | "webview" | "file-viewer"; +export type PaneType = "terminal" | "webview" | "file-viewer" | "chat"; /** * Pane status for agent lifecycle indicators @@ -118,6 +118,15 @@ export interface Pane { cwd?: string | null; // Current working directory cwdConfirmed?: boolean; // True if cwd confirmed via OSC-7, false if seeded fileViewer?: FileViewerState; // For file-viewer panes + chat?: ChatPaneState; // For chat panes +} + +/** + * Chat pane-specific properties + */ +export interface ChatPaneState { + /** Session ID for the chat session */ + sessionId: string; } /** diff --git a/docs/ai-chat-plan.md b/docs/ai-chat-plan.md index 78e520060ad..a52809bca7c 100644 --- a/docs/ai-chat-plan.md +++ b/docs/ai-chat-plan.md @@ -970,11 +970,11 @@ All files below are created and typechecking. Compatibility fix: `DurableStream. 3. ~~**Phase A2** — Vendor proxy into `apps/streams` (copy 17 files, adjust 3 import paths)~~ ✅ DONE 4. ~~**Phase B** — Claude agent endpoint + SDK-to-AI chunk converter (2 new files)~~ ✅ DONE 5. ~~**Phase C2** — Simplify desktop session manager~~ ✅ DONE -6. **Phase C3** — Handle drafts -7. **Phase D** — Database schema + migration -8. **Phase E** — API tRPC router -9. **Phase F** — Desktop chat UI -10. **Phase G** — Web chat UI +6. **Phase F** — Desktop chat UI (works with existing proxy, no DB needed) +7. **Phase C3** — Handle drafts (local state + typing indicators) +8. **Phase G** — Web chat UI +9. **Phase D** — Database schema + migration (persistent storage) +10. **Phase E** — API tRPC router (web session management) --- From eefb37a091cf5fda95beb513edc663ff6238a45d Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 16:15:18 -0800 Subject: [PATCH 2/7] More elements --- .../TabView/ChatPane/ChatInterface.tsx | 375 ++++++++++++++---- 1 file changed, 298 insertions(+), 77 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx index c3333851372..8ae05092cde 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx @@ -1,3 +1,8 @@ +import { + Checkpoint, + CheckpointIcon, + CheckpointTrigger, +} from "@superset/ui/ai-elements/checkpoint"; import { CodeBlock, CodeBlockCopyButton, @@ -15,12 +20,25 @@ import { ConversationEmptyState, ConversationScrollButton, } from "@superset/ui/ai-elements/conversation"; -import { Loader } from "@superset/ui/ai-elements/loader"; import { Message, + MessageAction, + MessageActions, MessageContent, MessageResponse, } from "@superset/ui/ai-elements/message"; +import { + ModelSelector, + ModelSelectorContent, + ModelSelectorEmpty, + ModelSelectorGroup, + ModelSelectorInput, + ModelSelectorItem, + ModelSelectorList, + ModelSelectorLogo, + ModelSelectorName, + ModelSelectorTrigger, +} from "@superset/ui/ai-elements/model-selector"; import { Plan, PlanContent, @@ -30,16 +48,26 @@ import { } from "@superset/ui/ai-elements/plan"; import { PromptInput, + PromptInputButton, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, + PromptInputTools, } from "@superset/ui/ai-elements/prompt-input"; import { Reasoning, ReasoningContent, ReasoningTrigger, } from "@superset/ui/ai-elements/reasoning"; +import { Shimmer } from "@superset/ui/ai-elements/shimmer"; import { Suggestion, Suggestions } from "@superset/ui/ai-elements/suggestion"; +import { + Task, + TaskContent, + TaskItem, + TaskItemFile, + TaskTrigger, +} from "@superset/ui/ai-elements/task"; import { Tool, ToolContent, @@ -47,8 +75,14 @@ import { ToolInput, ToolOutput, } from "@superset/ui/ai-elements/tool"; -import { useCallback, useState } from "react"; -import { HiMiniChatBubbleLeftRight } from "react-icons/hi2"; +import { Fragment, useCallback, useState } from "react"; +import { + HiMiniArrowPath, + HiMiniAtSymbol, + HiMiniChatBubbleLeftRight, + HiMiniClipboard, + HiMiniPaperClip, +} from "react-icons/hi2"; type MessageRole = "user" | "assistant" | "system"; @@ -59,11 +93,10 @@ interface ToolCall { input: Record; output?: unknown; errorText?: string; - approval?: { - id: string; - approved?: boolean; - reason?: string; - }; + approval?: + | { id: string } + | { id: string; approved: true; reason?: string } + | { id: string; approved: false; reason?: string }; } interface PlanData { @@ -72,6 +105,11 @@ interface PlanData { steps: Array<{ label: string; done: boolean }>; } +interface TaskData { + title: string; + files: string[]; +} + interface ChatMessage { id: string; role: MessageRole; @@ -79,9 +117,35 @@ interface ChatMessage { reasoning?: string; toolCalls?: ToolCall[]; plan?: PlanData; - codeBlock?: { code: string; language: string }; + codeBlocks?: Array<{ code: string; language: string }>; + tasks?: TaskData[]; + checkpoint?: string; +} + +interface ModelOption { + id: string; + name: string; + description: string; } +const MODELS: ModelOption[] = [ + { + id: "claude-opus-4-6", + name: "Claude Opus 4.6", + description: "Most capable — complex tasks, deep reasoning", + }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + description: "Balanced — fast and capable", + }, + { + id: "claude-haiku-4-5-20251001", + name: "Claude Haiku 4.5", + description: "Fastest — quick tasks, low cost", + }, +]; + const MOCK_MESSAGES: ChatMessage[] = [ { id: "1", @@ -92,14 +156,18 @@ const MOCK_MESSAGES: ChatMessage[] = [ id: "2", role: "assistant", content: "", + reasoning: + "The user wants help with an authentication bug. I should first plan my approach, then investigate the codebase to understand the current implementation before suggesting fixes. Let me start by searching for auth-related files.", plan: { title: "Fix authentication bug", description: "I'll investigate the login flow and fix the token validation issue.", steps: [ { label: "Search for auth-related files", done: true }, + { label: "Read the login implementation", done: true }, { label: "Identify the bug in validateCredentials", done: true }, { label: "Apply the fix", done: false }, + { label: "Run tests to verify", done: false }, ], }, }, @@ -107,80 +175,172 @@ const MOCK_MESSAGES: ChatMessage[] = [ id: "3", role: "assistant", content: - "I'll take a look at the authentication flow. Let me search for the relevant files first.", - reasoning: - "The user wants help with an authentication bug. I should look at the login-related files to understand the current implementation before suggesting fixes.", + "Let me search for the relevant files and understand the current implementation.", + tasks: [ + { + title: "Found auth-related files", + files: [ + "src/auth/login.ts", + "src/auth/session.ts", + "src/auth/validators.ts", + "src/auth/types.ts", + ], + }, + ], toolCalls: [ { id: "tc-1", - name: "bash", + name: "Glob", state: "output-available", - input: { command: 'grep -r "login" src/auth/ --include="*.ts"' }, + input: { pattern: "src/auth/**/*.ts" }, output: - "src/auth/login.ts:export async function login(credentials: Credentials) {\nsrc/auth/login.ts: const token = await validateCredentials(credentials);\nsrc/auth/session.ts: if (!session.isValid()) { return redirect('/login'); }", + "src/auth/login.ts\nsrc/auth/session.ts\nsrc/auth/validators.ts\nsrc/auth/types.ts", }, { id: "tc-2", - name: "edit", + name: "Read", + state: "output-available", + input: { file_path: "src/auth/login.ts" }, + output: + "export async function login(credentials: Credentials) {\n const token = await validateCredentials(credentials);\n return createSession(token);\n}\n\nexport async function validateCredentials(credentials: Credentials) {\n const { email, password } = credentials;\n const user = await findUserByEmail(email);\n if (!user || !await verifyPassword(password, user.passwordHash)) {\n return null;\n }\n return generateToken(user);\n}", + }, + { + id: "tc-3", + name: "Grep", state: "output-available", input: { - file: "src/auth/login.ts", - operation: "replace", - }, - output: "File updated successfully", - approval: { - id: "approval-1", - approved: true, + pattern: "isTokenExpired|tokenExpir", + path: "src/auth/", }, + output: "No matches found.", }, ], + checkpoint: "Investigation complete", }, { id: "4", role: "assistant", content: - "I found the issue. The `validateCredentials` function doesn't handle the case where the token has expired. Here's the fix:", - codeBlock: { - language: "typescript", - code: `export async function login(credentials: Credentials) { + "I found the issue. The `login` function doesn't check if the returned token is null or expired before creating a session. Here's the fix:", + codeBlocks: [ + { + language: "typescript", + code: `export async function login(credentials: Credentials) { const token = await validateCredentials(credentials); if (!token || isTokenExpired(token)) { throw new AuthError('Invalid or expired credentials'); } return createSession(token); }`, - }, + }, + ], + toolCalls: [ + { + id: "tc-4", + name: "Edit", + state: "output-available", + input: { + file_path: "src/auth/login.ts", + old_string: + "const token = await validateCredentials(credentials);\n return createSession(token);", + new_string: + "const token = await validateCredentials(credentials);\n if (!token || isTokenExpired(token)) {\n throw new AuthError('Invalid or expired credentials');\n }\n return createSession(token);", + }, + output: "File updated successfully.", + approval: { + id: "approval-1", + approved: true, + }, + }, + ], }, { id: "5", role: "assistant", + content: "Let me run the tests to make sure the fix works correctly.", + toolCalls: [ + { + id: "tc-5", + name: "Bash", + state: "output-error", + input: { command: "bun test src/auth/" }, + output: undefined, + errorText: + "FAIL src/auth/login.test.ts\n ✓ login with valid credentials (3ms)\n ✓ login with invalid credentials (1ms)\n ✗ login with expired token (2ms)\n Expected: AuthError\n Received: undefined\n\n Missing import: isTokenExpired is not defined", + }, + ], + }, + { + id: "6", + role: "assistant", content: - "Now I need to install a dependency. Allow me to run this command?", + "The test revealed I forgot to import `isTokenExpired`. Let me fix that and also add the missing `AuthError` import.", toolCalls: [ { - id: "tc-3", - name: "bash", + id: "tc-6", + name: "Edit", state: "output-available", - input: { command: "bun add jose" }, - output: undefined, + input: { + file_path: "src/auth/login.ts", + old_string: 'import { generateToken } from "./token";', + new_string: + 'import { generateToken, isTokenExpired } from "./token";\nimport { AuthError } from "./errors";', + }, + output: "File updated successfully.", approval: { id: "approval-2", + approved: true, + }, + }, + { + id: "tc-7", + name: "Bash", + state: "output-available", + input: { command: "bun test src/auth/" }, + output: + "PASS src/auth/login.test.ts\n ✓ login with valid credentials (3ms)\n ✓ login with invalid credentials (1ms)\n ✓ login with expired token (2ms)\n\nTest Suites: 1 passed, 1 total\nTests: 3 passed, 3 total", + approval: { + id: "approval-3", + approved: true, }, }, ], }, + { + id: "7", + role: "assistant", + content: + "All tests pass now. The fix adds proper null-check and expiration validation to the `login` function, along with the missing imports for `isTokenExpired` and `AuthError`.", + codeBlocks: [ + { + language: "typescript", + code: `import { generateToken, isTokenExpired } from "./token"; +import { AuthError } from "./errors"; + +export async function login(credentials: Credentials) { + const token = await validateCredentials(credentials); + if (!token || isTokenExpired(token)) { + throw new AuthError('Invalid or expired credentials'); + } + return createSession(token); +}`, + }, + ], + }, ]; const SUGGESTIONS = [ - "Fix the login bug", + "Explain this codebase", + "Fix the failing tests", "Write tests for auth", - "Refactor the session manager", - "Add rate limiting to API", + "Refactor to async/await", ]; export function ChatInterface() { const [messages, setMessages] = useState(MOCK_MESSAGES); const [isLoading, setIsLoading] = useState(false); + const [selectedModel, setSelectedModel] = useState(MODELS[1]); + const [modelSelectorOpen, setModelSelectorOpen] = useState(false); const handleSend = useCallback((message: { text: string }) => { if (!message.text.trim()) return; @@ -194,7 +354,6 @@ export function ChatInterface() { setMessages((prev) => [...prev, userMessage]); setIsLoading(true); - // Simulate assistant response setTimeout(() => { const assistantMessage: ChatMessage = { id: `msg-${Date.now()}-reply`, @@ -223,9 +382,7 @@ export function ChatInterface() { - } + icon={} /> {SUGGESTIONS.map((s) => ( @@ -239,16 +396,25 @@ export function ChatInterface() { ) : ( messages.map((msg) => ( - + + {msg.checkpoint && ( + + + + {msg.checkpoint} + + + )} + + )) )} {isLoading && ( -
- - Thinking... -
+ + Thinking... +
)} @@ -261,19 +427,61 @@ export function ChatInterface() { {messages.length > 0 && ( {SUGGESTIONS.map((s) => ( - + ))} )} -
- + + + + + + + + + + + + {selectedModel.name} + + + + + + No models found. + + {MODELS.map((model) => ( + { + setSelectedModel(model); + setModelSelectorOpen(false); + }} + > + +
+ + {model.name} + + + {model.description} + +
+
+ ))} +
+
+
+
+
+
@@ -299,14 +507,28 @@ function ChatMessageItem({ message }: { message: ChatMessage }) { {message.content} )} - {message.codeBlock && ( + {message.tasks?.map((task) => ( + + + + {task.files.map((file) => ( + + {file} + + ))} + + + ))} + + {message.codeBlocks?.map((block) => ( - )} + ))} {message.toolCalls?.map((tc) => (
@@ -319,34 +541,26 @@ function ChatMessageItem({ message }: { message: ChatMessage }) { {(tc.output || tc.errorText) && ( - + )} {tc.approval && ( - + - {tc.approval.approved === undefined - ? `Allow ${tc.name}?` - : tc.approval.approved + {"approved" in tc.approval + ? tc.approval.approved ? `${tc.name} was approved` - : `${tc.name} was denied`} + : `${tc.name} was denied` + : `Allow ${tc.name}?`} Deny - - Allow - + Allow @@ -354,6 +568,17 @@ function ChatMessageItem({ message }: { message: ChatMessage }) {
))} + + {message.role === "assistant" && message.content && ( + + + + + + + + + )} ); } @@ -373,18 +598,14 @@ function PlanBlock({ plan }: { plan: PlanData }) {
  • {step.done ? "\u2713" : "\u25CB"} {step.label} From d63b577da875bd7eb2fdbbb8527852163c6542de Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 16:21:54 -0800 Subject: [PATCH 3/7] Move Chat --- .../{ => ChatInterface}/ChatInterface.tsx | 45 +------------------ .../TabView/ChatPane/ChatInterface/index.ts | 1 + .../TabView/ChatPane/ChatInterface/types.ts | 43 ++++++++++++++++++ 3 files changed, 45 insertions(+), 44 deletions(-) rename apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/{ => ChatInterface}/ChatInterface.tsx (94%) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/index.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/types.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx similarity index 94% rename from apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx index 8ae05092cde..9f95b3202b8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx @@ -83,50 +83,7 @@ import { HiMiniClipboard, HiMiniPaperClip, } from "react-icons/hi2"; - -type MessageRole = "user" | "assistant" | "system"; - -interface ToolCall { - id: string; - name: string; - state: "input-available" | "output-available" | "output-error"; - input: Record; - output?: unknown; - errorText?: string; - approval?: - | { id: string } - | { id: string; approved: true; reason?: string } - | { id: string; approved: false; reason?: string }; -} - -interface PlanData { - title: string; - description: string; - steps: Array<{ label: string; done: boolean }>; -} - -interface TaskData { - title: string; - files: string[]; -} - -interface ChatMessage { - id: string; - role: MessageRole; - content: string; - reasoning?: string; - toolCalls?: ToolCall[]; - plan?: PlanData; - codeBlocks?: Array<{ code: string; language: string }>; - tasks?: TaskData[]; - checkpoint?: string; -} - -interface ModelOption { - id: string; - name: string; - description: string; -} +import type { ChatMessage, ModelOption, PlanData } from "./types"; const MODELS: ModelOption[] = [ { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/index.ts new file mode 100644 index 00000000000..b95d3f925aa --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/index.ts @@ -0,0 +1 @@ +export { ChatInterface } from "./ChatInterface"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/types.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/types.ts new file mode 100644 index 00000000000..69cc150311f --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/types.ts @@ -0,0 +1,43 @@ +export type MessageRole = "user" | "assistant" | "system"; + +export interface ToolCall { + id: string; + name: string; + state: "input-available" | "output-available" | "output-error"; + input: Record; + output?: unknown; + errorText?: string; + approval?: + | { id: string } + | { id: string; approved: true; reason?: string } + | { id: string; approved: false; reason?: string }; +} + +export interface PlanData { + title: string; + description: string; + steps: Array<{ label: string; done: boolean }>; +} + +export interface TaskData { + title: string; + files: string[]; +} + +export interface ChatMessage { + id: string; + role: MessageRole; + content: string; + reasoning?: string; + toolCalls?: ToolCall[]; + plan?: PlanData; + codeBlocks?: Array<{ code: string; language: string }>; + tasks?: TaskData[]; + checkpoint?: string; +} + +export interface ModelOption { + id: string; + name: string; + description: string; +} From 273c38db3982b6798d5c03ea1f7d9201f0f7f1f8 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 16:29:01 -0800 Subject: [PATCH 4/7] persist --- apps/desktop/src/lib/trpc/routers/ui-state/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/lib/trpc/routers/ui-state/index.ts b/apps/desktop/src/lib/trpc/routers/ui-state/index.ts index e9f19043286..17b97abbdd6 100644 --- a/apps/desktop/src/lib/trpc/routers/ui-state/index.ts +++ b/apps/desktop/src/lib/trpc/routers/ui-state/index.ts @@ -33,7 +33,7 @@ const fileViewerStateSchema = z.object({ const paneSchema = z.object({ id: z.string(), tabId: z.string(), - type: z.enum(["terminal", "webview", "file-viewer"]), + type: z.enum(["terminal", "webview", "file-viewer", "chat"]), name: z.string(), isNew: z.boolean().optional(), status: z.enum(["idle", "working", "permission", "review"]).optional(), @@ -43,6 +43,7 @@ const paneSchema = z.object({ cwd: z.string().nullable().optional(), cwdConfirmed: z.boolean().optional(), fileViewer: fileViewerStateSchema.optional(), + chat: z.object({ sessionId: z.string() }).optional(), }); /** From 1c73b87f9f5f44c488c20c97c976d7cfc6b84149 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 16:35:56 -0800 Subject: [PATCH 5/7] Refactor interface component --- .../ChatPane/ChatInterface/ChatInterface.tsx | 441 +----------------- .../ChatMessageItem/ChatMessageItem.tsx | 86 ++++ .../components/ChatMessageItem/index.ts | 1 + .../components/ModelPicker/ModelPicker.tsx | 64 +++ .../components/ModelPicker/index.ts | 1 + .../components/PlanBlock/PlanBlock.tsx | 43 ++ .../components/PlanBlock/index.ts | 1 + .../ToolCallBlock/ToolCallBlock.tsx | 56 +++ .../components/ToolCallBlock/index.ts | 1 + .../ChatPane/ChatInterface/constants.ts | 209 +++++++++ 10 files changed, 471 insertions(+), 432 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/ChatMessageItem.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/index.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/ModelPicker.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/index.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/PlanBlock.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/index.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/ToolCallBlock.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/index.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/constants.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx index 9f95b3202b8..fbb5c5a201f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx @@ -3,49 +3,13 @@ import { CheckpointIcon, CheckpointTrigger, } from "@superset/ui/ai-elements/checkpoint"; -import { - CodeBlock, - CodeBlockCopyButton, -} from "@superset/ui/ai-elements/code-block"; -import { - Confirmation, - ConfirmationAction, - ConfirmationActions, - ConfirmationRequest, - ConfirmationTitle, -} from "@superset/ui/ai-elements/confirmation"; import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton, } from "@superset/ui/ai-elements/conversation"; -import { - Message, - MessageAction, - MessageActions, - MessageContent, - MessageResponse, -} from "@superset/ui/ai-elements/message"; -import { - ModelSelector, - ModelSelectorContent, - ModelSelectorEmpty, - ModelSelectorGroup, - ModelSelectorInput, - ModelSelectorItem, - ModelSelectorList, - ModelSelectorLogo, - ModelSelectorName, - ModelSelectorTrigger, -} from "@superset/ui/ai-elements/model-selector"; -import { - Plan, - PlanContent, - PlanDescription, - PlanHeader, - PlanTitle, -} from "@superset/ui/ai-elements/plan"; +import { Message, MessageContent } from "@superset/ui/ai-elements/message"; import { PromptInput, PromptInputButton, @@ -54,244 +18,18 @@ import { PromptInputTextarea, PromptInputTools, } from "@superset/ui/ai-elements/prompt-input"; -import { - Reasoning, - ReasoningContent, - ReasoningTrigger, -} from "@superset/ui/ai-elements/reasoning"; import { Shimmer } from "@superset/ui/ai-elements/shimmer"; import { Suggestion, Suggestions } from "@superset/ui/ai-elements/suggestion"; -import { - Task, - TaskContent, - TaskItem, - TaskItemFile, - TaskTrigger, -} from "@superset/ui/ai-elements/task"; -import { - Tool, - ToolContent, - ToolHeader, - ToolInput, - ToolOutput, -} from "@superset/ui/ai-elements/tool"; import { Fragment, useCallback, useState } from "react"; import { - HiMiniArrowPath, HiMiniAtSymbol, HiMiniChatBubbleLeftRight, - HiMiniClipboard, HiMiniPaperClip, } from "react-icons/hi2"; -import type { ChatMessage, ModelOption, PlanData } from "./types"; - -const MODELS: ModelOption[] = [ - { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - description: "Most capable — complex tasks, deep reasoning", - }, - { - id: "claude-sonnet-4-5-20250929", - name: "Claude Sonnet 4.5", - description: "Balanced — fast and capable", - }, - { - id: "claude-haiku-4-5-20251001", - name: "Claude Haiku 4.5", - description: "Fastest — quick tasks, low cost", - }, -]; - -const MOCK_MESSAGES: ChatMessage[] = [ - { - id: "1", - role: "user", - content: "Can you help me fix the authentication bug in the login flow?", - }, - { - id: "2", - role: "assistant", - content: "", - reasoning: - "The user wants help with an authentication bug. I should first plan my approach, then investigate the codebase to understand the current implementation before suggesting fixes. Let me start by searching for auth-related files.", - plan: { - title: "Fix authentication bug", - description: - "I'll investigate the login flow and fix the token validation issue.", - steps: [ - { label: "Search for auth-related files", done: true }, - { label: "Read the login implementation", done: true }, - { label: "Identify the bug in validateCredentials", done: true }, - { label: "Apply the fix", done: false }, - { label: "Run tests to verify", done: false }, - ], - }, - }, - { - id: "3", - role: "assistant", - content: - "Let me search for the relevant files and understand the current implementation.", - tasks: [ - { - title: "Found auth-related files", - files: [ - "src/auth/login.ts", - "src/auth/session.ts", - "src/auth/validators.ts", - "src/auth/types.ts", - ], - }, - ], - toolCalls: [ - { - id: "tc-1", - name: "Glob", - state: "output-available", - input: { pattern: "src/auth/**/*.ts" }, - output: - "src/auth/login.ts\nsrc/auth/session.ts\nsrc/auth/validators.ts\nsrc/auth/types.ts", - }, - { - id: "tc-2", - name: "Read", - state: "output-available", - input: { file_path: "src/auth/login.ts" }, - output: - "export async function login(credentials: Credentials) {\n const token = await validateCredentials(credentials);\n return createSession(token);\n}\n\nexport async function validateCredentials(credentials: Credentials) {\n const { email, password } = credentials;\n const user = await findUserByEmail(email);\n if (!user || !await verifyPassword(password, user.passwordHash)) {\n return null;\n }\n return generateToken(user);\n}", - }, - { - id: "tc-3", - name: "Grep", - state: "output-available", - input: { - pattern: "isTokenExpired|tokenExpir", - path: "src/auth/", - }, - output: "No matches found.", - }, - ], - checkpoint: "Investigation complete", - }, - { - id: "4", - role: "assistant", - content: - "I found the issue. The `login` function doesn't check if the returned token is null or expired before creating a session. Here's the fix:", - codeBlocks: [ - { - language: "typescript", - code: `export async function login(credentials: Credentials) { - const token = await validateCredentials(credentials); - if (!token || isTokenExpired(token)) { - throw new AuthError('Invalid or expired credentials'); - } - return createSession(token); -}`, - }, - ], - toolCalls: [ - { - id: "tc-4", - name: "Edit", - state: "output-available", - input: { - file_path: "src/auth/login.ts", - old_string: - "const token = await validateCredentials(credentials);\n return createSession(token);", - new_string: - "const token = await validateCredentials(credentials);\n if (!token || isTokenExpired(token)) {\n throw new AuthError('Invalid or expired credentials');\n }\n return createSession(token);", - }, - output: "File updated successfully.", - approval: { - id: "approval-1", - approved: true, - }, - }, - ], - }, - { - id: "5", - role: "assistant", - content: "Let me run the tests to make sure the fix works correctly.", - toolCalls: [ - { - id: "tc-5", - name: "Bash", - state: "output-error", - input: { command: "bun test src/auth/" }, - output: undefined, - errorText: - "FAIL src/auth/login.test.ts\n ✓ login with valid credentials (3ms)\n ✓ login with invalid credentials (1ms)\n ✗ login with expired token (2ms)\n Expected: AuthError\n Received: undefined\n\n Missing import: isTokenExpired is not defined", - }, - ], - }, - { - id: "6", - role: "assistant", - content: - "The test revealed I forgot to import `isTokenExpired`. Let me fix that and also add the missing `AuthError` import.", - toolCalls: [ - { - id: "tc-6", - name: "Edit", - state: "output-available", - input: { - file_path: "src/auth/login.ts", - old_string: 'import { generateToken } from "./token";', - new_string: - 'import { generateToken, isTokenExpired } from "./token";\nimport { AuthError } from "./errors";', - }, - output: "File updated successfully.", - approval: { - id: "approval-2", - approved: true, - }, - }, - { - id: "tc-7", - name: "Bash", - state: "output-available", - input: { command: "bun test src/auth/" }, - output: - "PASS src/auth/login.test.ts\n ✓ login with valid credentials (3ms)\n ✓ login with invalid credentials (1ms)\n ✓ login with expired token (2ms)\n\nTest Suites: 1 passed, 1 total\nTests: 3 passed, 3 total", - approval: { - id: "approval-3", - approved: true, - }, - }, - ], - }, - { - id: "7", - role: "assistant", - content: - "All tests pass now. The fix adds proper null-check and expiration validation to the `login` function, along with the missing imports for `isTokenExpired` and `AuthError`.", - codeBlocks: [ - { - language: "typescript", - code: `import { generateToken, isTokenExpired } from "./token"; -import { AuthError } from "./errors"; - -export async function login(credentials: Credentials) { - const token = await validateCredentials(credentials); - if (!token || isTokenExpired(token)) { - throw new AuthError('Invalid or expired credentials'); - } - return createSession(token); -}`, - }, - ], - }, -]; - -const SUGGESTIONS = [ - "Explain this codebase", - "Fix the failing tests", - "Write tests for auth", - "Refactor to async/await", -]; +import { ChatMessageItem } from "./components/ChatMessageItem"; +import { ModelPicker } from "./components/ModelPicker"; +import { MOCK_MESSAGES, MODELS, SUGGESTIONS } from "./constants"; +import type { ChatMessage, ModelOption } from "./types"; export function ChatInterface() { const [messages, setMessages] = useState(MOCK_MESSAGES); @@ -398,45 +136,12 @@ export function ChatInterface() { - - - - - {selectedModel.name} - - - - - - No models found. - - {MODELS.map((model) => ( - { - setSelectedModel(model); - setModelSelectorOpen(false); - }} - > - -
    - - {model.name} - - - {model.description} - -
    -
    - ))} -
    -
    -
    -
    + /> @@ -446,131 +151,3 @@ export function ChatInterface() {
  • ); } - -function ChatMessageItem({ message }: { message: ChatMessage }) { - return ( - - - {message.reasoning && ( - - - {message.reasoning} - - )} - - {message.plan && } - - {message.content && ( - {message.content} - )} - - {message.tasks?.map((task) => ( - - - - {task.files.map((file) => ( - - {file} - - ))} - - - ))} - - {message.codeBlocks?.map((block) => ( - - - - ))} - - {message.toolCalls?.map((tc) => ( -
    - - - - - {(tc.output || tc.errorText) && ( - - )} - - - - {tc.approval && ( - - - {"approved" in tc.approval - ? tc.approval.approved - ? `${tc.name} was approved` - : `${tc.name} was denied` - : `Allow ${tc.name}?`} - - - - - Deny - - Allow - - - - )} -
    - ))} -
    - - {message.role === "assistant" && message.content && ( - - - - - - - - - )} -
    - ); -} - -function PlanBlock({ plan }: { plan: PlanData }) { - return ( - - -
    - {plan.title} - {plan.description} -
    -
    - -
      - {plan.steps.map((step) => ( -
    • - - {step.done ? "\u2713" : "\u25CB"} - - - {step.label} - -
    • - ))} -
    -
    -
    - ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/ChatMessageItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/ChatMessageItem.tsx new file mode 100644 index 00000000000..25a7016859a --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/ChatMessageItem.tsx @@ -0,0 +1,86 @@ +import { + CodeBlock, + CodeBlockCopyButton, +} from "@superset/ui/ai-elements/code-block"; +import { + Message, + MessageAction, + MessageActions, + MessageContent, + MessageResponse, +} from "@superset/ui/ai-elements/message"; +import { + Reasoning, + ReasoningContent, + ReasoningTrigger, +} from "@superset/ui/ai-elements/reasoning"; +import { + Task, + TaskContent, + TaskItem, + TaskItemFile, + TaskTrigger, +} from "@superset/ui/ai-elements/task"; +import { HiMiniArrowPath, HiMiniClipboard } from "react-icons/hi2"; +import type { ChatMessage } from "../../types"; +import { PlanBlock } from "../PlanBlock"; +import { ToolCallBlock } from "../ToolCallBlock"; + +export function ChatMessageItem({ message }: { message: ChatMessage }) { + return ( + + + {message.reasoning && ( + + + {message.reasoning} + + )} + + {message.plan && } + + {message.content && ( + {message.content} + )} + + {message.tasks?.map((task) => ( + + + + {task.files.map((file) => ( + + {file} + + ))} + + + ))} + + {message.codeBlocks?.map((block) => ( + + + + ))} + + {message.toolCalls?.map((tc) => ( + + ))} + + + {message.role === "assistant" && message.content && ( + + + + + + + + + )} + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/index.ts new file mode 100644 index 00000000000..9d4b48394b5 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatMessageItem/index.ts @@ -0,0 +1 @@ +export { ChatMessageItem } from "./ChatMessageItem"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/ModelPicker.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/ModelPicker.tsx new file mode 100644 index 00000000000..bf57f434872 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/ModelPicker.tsx @@ -0,0 +1,64 @@ +import { + ModelSelector, + ModelSelectorContent, + ModelSelectorEmpty, + ModelSelectorGroup, + ModelSelectorInput, + ModelSelectorItem, + ModelSelectorList, + ModelSelectorLogo, + ModelSelectorName, + ModelSelectorTrigger, +} from "@superset/ui/ai-elements/model-selector"; +import { PromptInputButton } from "@superset/ui/ai-elements/prompt-input"; +import { MODELS } from "../../constants"; +import type { ModelOption } from "../../types"; + +export function ModelPicker({ + selectedModel, + onSelectModel, + open, + onOpenChange, +}: { + selectedModel: ModelOption; + onSelectModel: (model: ModelOption) => void; + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + return ( + + + + + {selectedModel.name} + + + + + + No models found. + + {MODELS.map((model) => ( + { + onSelectModel(model); + onOpenChange(false); + }} + > + +
    + {model.name} + + {model.description} + +
    +
    + ))} +
    +
    +
    +
    + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/index.ts new file mode 100644 index 00000000000..cdb83810862 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ModelPicker/index.ts @@ -0,0 +1 @@ +export { ModelPicker } from "./ModelPicker"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/PlanBlock.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/PlanBlock.tsx new file mode 100644 index 00000000000..ccfad86a4c8 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/PlanBlock.tsx @@ -0,0 +1,43 @@ +import { + Plan, + PlanContent, + PlanDescription, + PlanHeader, + PlanTitle, +} from "@superset/ui/ai-elements/plan"; +import type { PlanData } from "../../types"; + +export function PlanBlock({ plan }: { plan: PlanData }) { + return ( + + +
    + {plan.title} + {plan.description} +
    +
    + +
      + {plan.steps.map((step) => ( +
    • + + {step.done ? "\u2713" : "\u25CB"} + + + {step.label} + +
    • + ))} +
    +
    +
    + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/index.ts new file mode 100644 index 00000000000..6225627aebc --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/PlanBlock/index.ts @@ -0,0 +1 @@ +export { PlanBlock } from "./PlanBlock"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/ToolCallBlock.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/ToolCallBlock.tsx new file mode 100644 index 00000000000..8718215dbba --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/ToolCallBlock.tsx @@ -0,0 +1,56 @@ +import { + Confirmation, + ConfirmationAction, + ConfirmationActions, + ConfirmationRequest, + ConfirmationTitle, +} from "@superset/ui/ai-elements/confirmation"; +import { + Tool, + ToolContent, + ToolHeader, + ToolInput, + ToolOutput, +} from "@superset/ui/ai-elements/tool"; +import type { ToolCall } from "../../types"; + +export function ToolCallBlock({ toolCall }: { toolCall: ToolCall }) { + return ( +
    + + + + + {(toolCall.output || toolCall.errorText) && ( + + )} + + + + {toolCall.approval && ( + + + {"approved" in toolCall.approval + ? toolCall.approval.approved + ? `${toolCall.name} was approved` + : `${toolCall.name} was denied` + : `Allow ${toolCall.name}?`} + + + + Deny + Allow + + + + )} +
    + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/index.ts new file mode 100644 index 00000000000..589339b3253 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ToolCallBlock/index.ts @@ -0,0 +1 @@ +export { ToolCallBlock } from "./ToolCallBlock"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/constants.ts new file mode 100644 index 00000000000..f4ea3c09da5 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/constants.ts @@ -0,0 +1,209 @@ +import type { ChatMessage, ModelOption } from "./types"; + +export const MODELS: ModelOption[] = [ + { + id: "claude-opus-4-6", + name: "Claude Opus 4.6", + description: "Most capable — complex tasks, deep reasoning", + }, + { + id: "claude-sonnet-4-5-20250929", + name: "Claude Sonnet 4.5", + description: "Balanced — fast and capable", + }, + { + id: "claude-haiku-4-5-20251001", + name: "Claude Haiku 4.5", + description: "Fastest — quick tasks, low cost", + }, +]; + +export const MOCK_MESSAGES: ChatMessage[] = [ + { + id: "1", + role: "user", + content: "Can you help me fix the authentication bug in the login flow?", + }, + { + id: "2", + role: "assistant", + content: "", + reasoning: + "The user wants help with an authentication bug. I should first plan my approach, then investigate the codebase to understand the current implementation before suggesting fixes. Let me start by searching for auth-related files.", + plan: { + title: "Fix authentication bug", + description: + "I'll investigate the login flow and fix the token validation issue.", + steps: [ + { label: "Search for auth-related files", done: true }, + { label: "Read the login implementation", done: true }, + { label: "Identify the bug in validateCredentials", done: true }, + { label: "Apply the fix", done: false }, + { label: "Run tests to verify", done: false }, + ], + }, + }, + { + id: "3", + role: "assistant", + content: + "Let me search for the relevant files and understand the current implementation.", + tasks: [ + { + title: "Found auth-related files", + files: [ + "src/auth/login.ts", + "src/auth/session.ts", + "src/auth/validators.ts", + "src/auth/types.ts", + ], + }, + ], + toolCalls: [ + { + id: "tc-1", + name: "Glob", + state: "output-available", + input: { pattern: "src/auth/**/*.ts" }, + output: + "src/auth/login.ts\nsrc/auth/session.ts\nsrc/auth/validators.ts\nsrc/auth/types.ts", + }, + { + id: "tc-2", + name: "Read", + state: "output-available", + input: { file_path: "src/auth/login.ts" }, + output: + "export async function login(credentials: Credentials) {\n const token = await validateCredentials(credentials);\n return createSession(token);\n}\n\nexport async function validateCredentials(credentials: Credentials) {\n const { email, password } = credentials;\n const user = await findUserByEmail(email);\n if (!user || !await verifyPassword(password, user.passwordHash)) {\n return null;\n }\n return generateToken(user);\n}", + }, + { + id: "tc-3", + name: "Grep", + state: "output-available", + input: { + pattern: "isTokenExpired|tokenExpir", + path: "src/auth/", + }, + output: "No matches found.", + }, + ], + checkpoint: "Investigation complete", + }, + { + id: "4", + role: "assistant", + content: + "I found the issue. The `login` function doesn't check if the returned token is null or expired before creating a session. Here's the fix:", + codeBlocks: [ + { + language: "typescript", + code: `export async function login(credentials: Credentials) { + const token = await validateCredentials(credentials); + if (!token || isTokenExpired(token)) { + throw new AuthError('Invalid or expired credentials'); + } + return createSession(token); +}`, + }, + ], + toolCalls: [ + { + id: "tc-4", + name: "Edit", + state: "output-available", + input: { + file_path: "src/auth/login.ts", + old_string: + "const token = await validateCredentials(credentials);\n return createSession(token);", + new_string: + "const token = await validateCredentials(credentials);\n if (!token || isTokenExpired(token)) {\n throw new AuthError('Invalid or expired credentials');\n }\n return createSession(token);", + }, + output: "File updated successfully.", + approval: { + id: "approval-1", + approved: true, + }, + }, + ], + }, + { + id: "5", + role: "assistant", + content: "Let me run the tests to make sure the fix works correctly.", + toolCalls: [ + { + id: "tc-5", + name: "Bash", + state: "output-error", + input: { command: "bun test src/auth/" }, + output: undefined, + errorText: + "FAIL src/auth/login.test.ts\n ✓ login with valid credentials (3ms)\n ✓ login with invalid credentials (1ms)\n ✗ login with expired token (2ms)\n Expected: AuthError\n Received: undefined\n\n Missing import: isTokenExpired is not defined", + }, + ], + }, + { + id: "6", + role: "assistant", + content: + "The test revealed I forgot to import `isTokenExpired`. Let me fix that and also add the missing `AuthError` import.", + toolCalls: [ + { + id: "tc-6", + name: "Edit", + state: "output-available", + input: { + file_path: "src/auth/login.ts", + old_string: 'import { generateToken } from "./token";', + new_string: + 'import { generateToken, isTokenExpired } from "./token";\nimport { AuthError } from "./errors";', + }, + output: "File updated successfully.", + approval: { + id: "approval-2", + approved: true, + }, + }, + { + id: "tc-7", + name: "Bash", + state: "output-available", + input: { command: "bun test src/auth/" }, + output: + "PASS src/auth/login.test.ts\n ✓ login with valid credentials (3ms)\n ✓ login with invalid credentials (1ms)\n ✓ login with expired token (2ms)\n\nTest Suites: 1 passed, 1 total\nTests: 3 passed, 3 total", + approval: { + id: "approval-3", + approved: true, + }, + }, + ], + }, + { + id: "7", + role: "assistant", + content: + "All tests pass now. The fix adds proper null-check and expiration validation to the `login` function, along with the missing imports for `isTokenExpired` and `AuthError`.", + codeBlocks: [ + { + language: "typescript", + code: `import { generateToken, isTokenExpired } from "./token"; +import { AuthError } from "./errors"; + +export async function login(credentials: Credentials) { + const token = await validateCredentials(credentials); + if (!token || isTokenExpired(token)) { + throw new AuthError('Invalid or expired credentials'); + } + return createSession(token); +}`, + }, + ], + }, +]; + +export const SUGGESTIONS = [ + "Explain this codebase", + "Fix the failing tests", + "Write tests for auth", + "Refactor to async/await", +]; From d5d1a09ec827d502007506844e9f37120de4a6e2 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 16:48:59 -0800 Subject: [PATCH 6/7] Add context window usage indicator to chat prompt footer Shows token usage (input, output, reasoning, cache) with cost breakdown on hover, using the Context ai-element component. --- .../ChatPane/ChatInterface/ChatInterface.tsx | 8 ++- .../ContextIndicator/ContextIndicator.tsx | 50 +++++++++++++++++++ .../components/ContextIndicator/index.ts | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/ContextIndicator.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/index.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx index fbb5c5a201f..663c4047e11 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/ChatInterface.tsx @@ -27,6 +27,7 @@ import { HiMiniPaperClip, } from "react-icons/hi2"; import { ChatMessageItem } from "./components/ChatMessageItem"; +import { ContextIndicator } from "./components/ContextIndicator"; import { ModelPicker } from "./components/ModelPicker"; import { MOCK_MESSAGES, MODELS, SUGGESTIONS } from "./constants"; import type { ChatMessage, ModelOption } from "./types"; @@ -143,7 +144,12 @@ export function ChatInterface() { onOpenChange={setModelSelectorOpen} /> - +
    + + +
    diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/ContextIndicator.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/ContextIndicator.tsx new file mode 100644 index 00000000000..da893e09e3c --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/ContextIndicator.tsx @@ -0,0 +1,50 @@ +import { + Context, + ContextCacheUsage, + ContextContent, + ContextContentBody, + ContextContentFooter, + ContextContentHeader, + ContextInputUsage, + ContextOutputUsage, + ContextReasoningUsage, + ContextTrigger, +} from "@superset/ui/ai-elements/context"; + +const MOCK_CONTEXT = { + usedTokens: 84_200, + maxTokens: 200_000, + modelId: "claude-sonnet-4-5-20250929", + usage: { + inputTokens: 42_100, + outputTokens: 18_300, + totalTokens: 84_200, + reasoningTokens: 12_800, + cachedInputTokens: 11_000, + }, +} as const; + +export function ContextIndicator() { + return ( + + + + + +
    + + + + +
    +
    + +
    +
    + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/index.ts new file mode 100644 index 00000000000..6dc9d24f2fe --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ContextIndicator/index.ts @@ -0,0 +1 @@ +export { ContextIndicator } from "./ContextIndicator"; From a4b63691f3c2af16e78b960ea56766cd1d0d8834 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Fri, 6 Feb 2026 17:08:04 -0800 Subject: [PATCH 7/7] Gate chat pane behind ai-chat feature flag Hide the "New Chat" button and ChatPane rendering behind the existing AI_CHAT PostHog feature flag (enabled for @superset.sh). --- .../TabsContent/GroupStrip/GroupStrip.tsx | 35 +++++++++++-------- .../ContentView/TabsContent/TabView/index.tsx | 6 +++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx index 5d1aed4cb32..3085fd2fbc0 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx @@ -1,3 +1,4 @@ +import { FEATURE_FLAGS } from "@superset/shared/constants"; import { Button } from "@superset/ui/button"; import { DropdownMenu, @@ -8,6 +9,7 @@ import { } from "@superset/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useNavigate, useParams } from "@tanstack/react-router"; +import { useFeatureFlagEnabled } from "posthog-js/react"; import { useCallback, useMemo, useState } from "react"; import { HiMiniChatBubbleLeftRight, @@ -50,6 +52,7 @@ export function GroupStrip() { const movePaneToNewTab = useTabsStore((s) => s.movePaneToNewTab); const reorderTabs = useTabsStore((s) => s.reorderTabs); + const hasAiChat = useFeatureFlagEnabled(FEATURE_FLAGS.AI_CHAT); const { presets } = usePresets(); const isDark = useIsDarkTheme(); const navigate = useNavigate(); @@ -183,21 +186,23 @@ export function GroupStrip() { })} )} - - - - - - New Chat - - + {hasAiChat && ( + + + + + + New Chat + + + )} movePaneToNewTab(paneId)} isLastPaneInTab={checkIsLastPaneInTab} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx index a78481b40b8..7e0fb8ad393 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx @@ -1,6 +1,8 @@ import "react-mosaic-component/react-mosaic-component.css"; import "./mosaic-theme.css"; +import { FEATURE_FLAGS } from "@superset/shared/constants"; +import { useFeatureFlagEnabled } from "posthog-js/react"; import { useCallback, useEffect, useMemo } from "react"; import { Mosaic, @@ -34,6 +36,7 @@ export function TabView({ tab }: TabViewProps) { const focusedPaneId = useTabsStore((s) => s.focusedPaneIds[tab.id]); const movePaneToTab = useTabsStore((s) => s.movePaneToTab); const movePaneToNewTab = useTabsStore((s) => s.movePaneToNewTab); + const hasAiChat = useFeatureFlagEnabled(FEATURE_FLAGS.AI_CHAT); const allTabs = useTabsStore((s) => s.tabs); const allPanes = useTabsStore((s) => s.panes); @@ -159,7 +162,7 @@ export function TabView({ tab }: TabViewProps) { } // Route chat panes to ChatPane component - if (paneInfo.type === "chat") { + if (paneInfo.type === "chat" && hasAiChat) { return (