Skip to content
Closed
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
Expand Up @@ -6,7 +6,9 @@ import {
usePromptInputController,
} from "@superset/ui/ai-elements/prompt-input";
import type { ThinkingLevel } from "@superset/ui/ai-elements/thinking-toggle";
import { Button } from "@superset/ui/button";
import { workspaceTrpc } from "@superset/workspace-client";
import { useNavigate } from "@tanstack/react-router";
import type { ChatStatus, FileUIPart } from "ai";
import type React from "react";
import type { ReactNode } from "react";
Expand All @@ -27,7 +29,10 @@ import { FileDropOverlay } from "./components/FileDropOverlay";
import { LinkedIssues } from "./components/LinkedIssues";
import { SlashCommandPreview } from "./components/SlashCommandPreview";
import type { LinkedIssue } from "./types";
import { getErrorMessage } from "./utils/getErrorMessage";
import {
getErrorMessage,
isMissingModelProviderError,
} from "./utils/getErrorMessage";

interface ChatInputFooterProps {
workspaceId: string;
Expand Down Expand Up @@ -106,7 +111,9 @@ export function ChatInputFooter({

const [linkedIssues, setLinkedIssues] = useState<LinkedIssue[]>([]);
const inputRootRef = useRef<HTMLDivElement>(null);
const navigate = useNavigate();
const errorMessage = getErrorMessage(error);
const showModelProviderSetup = isMissingModelProviderError(errorMessage);
const focusShortcutText = useHotkeyDisplay("FOCUS_CHAT_INPUT").text;
const showFocusHint = focusShortcutText !== "Unassigned";

Expand Down Expand Up @@ -153,13 +160,33 @@ export function ChatInputFooter({
<ChatInputDropZone className="bg-background px-4 py-3">
{(dragType) => (
<div className="mx-auto w-full max-w-[680px]">
{errorMessage && (
<p
{showModelProviderSetup ? (
<div
role="alert"
className="mb-3 select-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"
className="mb-3 flex items-center justify-between gap-3 rounded-md border border-border bg-muted/50 px-4 py-2 text-sm"
>
{errorMessage}
</p>
<span className="select-text text-muted-foreground">
Chat needs a model provider. Connect Claude or add an API key to
get started.
</span>
<Button
size="sm"
variant="outline"
className="shrink-0"
onClick={() => void navigate({ to: "/settings/models" })}
>
Open Model Settings
</Button>
</div>
) : (
errorMessage && (
<p
role="alert"
className="mb-3 select-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"
>
{errorMessage}
</p>
)
Comment on lines +163 to +189

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add cursor-text to both error surfaces.

The setup message at Line [168] and the fallback error at Line [185] include select-text but not cursor-text. Add the missing class so users receive the required text-selection cursor.

Proposed fix
-							<span className="select-text text-muted-foreground">
+							<span className="select-text cursor-text text-muted-foreground">
...
-								className="mb-3 select-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"
+								className="mb-3 select-text cursor-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"

As per coding guidelines, rendered error messages must include select-text cursor-text classes so users can copy them; Sonner toasts are exempt.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{showModelProviderSetup ? (
<div
role="alert"
className="mb-3 select-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"
className="mb-3 flex items-center justify-between gap-3 rounded-md border border-border bg-muted/50 px-4 py-2 text-sm"
>
{errorMessage}
</p>
<span className="select-text text-muted-foreground">
Chat needs a model provider. Connect Claude or add an API key to
get started.
</span>
<Button
size="sm"
variant="outline"
className="shrink-0"
onClick={() => void navigate({ to: "/settings/models" })}
>
Open Model Settings
</Button>
</div>
) : (
errorMessage && (
<p
role="alert"
className="mb-3 select-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"
>
{errorMessage}
</p>
)
{showModelProviderSetup ? (
<div
role="alert"
className="mb-3 flex items-center justify-between gap-3 rounded-md border border-border bg-muted/50 px-4 py-2 text-sm"
>
<span className="select-text cursor-text text-muted-foreground">
Chat needs a model provider. Connect Claude or add an API key to
get started.
</span>
<Button
size="sm"
variant="outline"
className="shrink-0"
onClick={() => void navigate({ to: "/settings/models" })}
>
Open Model Settings
</Button>
</div>
) : (
errorMessage && (
<p
role="alert"
className="mb-3 select-text cursor-text rounded-md border border-destructive/20 bg-destructive/10 px-4 py-2 text-sm text-destructive"
>
{errorMessage}
</p>
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
around lines 163 - 189, Add the cursor-text class alongside select-text on both
error surfaces in ChatInputFooter: the setup message span within the
showModelProviderSetup branch and the fallback error paragraph rendering
errorMessage. Preserve all existing styling and behavior.

Source: Coding guidelines

)}
{pendingQuestion && onQuestionRespond && onQuestionCancel ? (
<QuestionInputOverlay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ export function getErrorMessage(error: unknown): string | null {
if (error) return "Unexpected chat error";
return null;
}

// Host-service throws this when the chat runtime can't find Anthropic/OpenAI
// credentials (packages/host-service/src/runtime/chat/chat.ts).
export function isMissingModelProviderError(
errorMessage: string | null,
): boolean {
if (!errorMessage) return false;
return /no model provider credentials/i.test(errorMessage);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ interface UseChatDisplayOptions {
// are freed shortly after their pane unmounts instead of accumulating on the heap.
const CHAT_QUERY_GC_TIME_MS = 60_000;

// While getSnapshot is failing (e.g. runtime can't start without model
// provider credentials), the 4fps poll would retry runtime creation — and
// hit the macOS keychain — four times a second. Back off until it recovers.
const ERROR_REFETCH_INTERVAL_MS = 5_000;

function toRefetchIntervalMs(fps: number): number {
if (!Number.isFinite(fps) || fps <= 0) return Math.floor(1000 / 60);
return Math.max(16, Math.floor(1000 / fps));
Expand Down Expand Up @@ -125,7 +130,10 @@ export function useChatDisplay(options: UseChatDisplayOptions) {
const refetchIntervalMs = toRefetchIntervalMs(fps);
const queryOptions = {
enabled: isQueryEnabled && queryInput !== undefined,
refetchInterval: refetchIntervalMs,
refetchInterval: (query: { state: { status: string } }) =>
query.state.status === "error"
? ERROR_REFETCH_INTERVAL_MS
: refetchIntervalMs,
refetchIntervalInBackground: true,
refetchOnWindowFocus: false,
gcTime: CHAT_QUERY_GC_TIME_MS,
Expand Down Expand Up @@ -153,10 +161,10 @@ export function useChatDisplay(options: UseChatDisplayOptions) {
: null;
const currentMessage = displayState?.currentMessage ?? null;
const isRunning = displayState?.isRunning ?? false;
const isConversationLoading =
isQueryEnabled &&
snapshotQuery.data === undefined &&
(snapshotQuery.isLoading || snapshotQuery.isFetching);
// isPending only (not isFetching): once the query errors, background
// retries must not flip the pane back to the loading spinner — that
// spinner/error alternation reads as the chat constantly reloading.
const isConversationLoading = isQueryEnabled && snapshotQuery.isPending;
const historicalMessages = snapshot?.messages ?? [];
const latestAssistantErrorMessage = isRunning
? null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "chat_sessions" DROP CONSTRAINT "chat_sessions_v2_workspace_id_v2_workspaces_id_fk";
Loading
Loading