fix(web): restore chat composer disabled-state messaging and clean up code (#2526)

This commit is contained in:
wenjie
2026-04-15 11:24:27 +08:00
committed by GitHub
parent 773a94c414
commit 51ab3b1385
10 changed files with 184 additions and 161 deletions
+24 -9
View File
@@ -14,6 +14,7 @@ import { Link } from "@tanstack/react-router"
import * as React from "react"
import { useTranslation } from "react-i18next"
import { postLauncherDashboardLogout } from "@/api/launcher-auth"
import {
AlertDialog,
AlertDialogAction,
@@ -40,7 +41,6 @@ import {
} from "@/components/ui/tooltip"
import { useGateway } from "@/hooks/use-gateway.ts"
import { useTheme } from "@/hooks/use-theme.ts"
import { postLauncherDashboardLogout } from "@/api/launcher-auth"
export function AppHeader() {
const { i18n, t } = useTranslation()
@@ -198,27 +198,42 @@ export function AppHeader() {
<IconPower className="h-4 w-4 opacity-80" />
</Button>
</TooltipTrigger>
<TooltipContent>{gwError ?? t("header.gateway.action.stop")}</TooltipContent>
<TooltipContent>
{gwError ?? t("header.gateway.action.stop")}
</TooltipContent>
</Tooltip>
) : (
<Tooltip delayDuration={(gwError || (!canStart && startReason)) ? 0 : 700}>
<Tooltip
delayDuration={gwError || (!canStart && startReason) ? 0 : 700}
>
<TooltipTrigger asChild>
{/* Wrap in span so the tooltip still fires when the button is disabled */}
<span
className={!canStart && startReason ? "cursor-not-allowed" : undefined}
className={
!canStart && startReason ? "cursor-not-allowed" : undefined
}
tabIndex={!canStart && startReason ? 0 : undefined}
>
<Button
variant={
isStarting || isRestarting || isStopping ? "secondary" : "default"
isStarting || isRestarting || isStopping
? "secondary"
: "default"
}
size="sm"
data-tour="gateway-button"
className={`h-8 gap-2 px-3 ${isStopped ? "bg-green-500 text-white hover:bg-green-600" : ""
} ${!canStart ? "pointer-events-none" : ""}`}
className={`h-8 gap-2 px-3 ${
isStopped
? "bg-green-500 text-white hover:bg-green-600"
: ""
} ${!canStart ? "pointer-events-none" : ""}`}
onClick={handleGatewayToggle}
disabled={
gwLoading || isStarting || isRestarting || isStopping || !canStart
gwLoading ||
isStarting ||
isRestarting ||
isStopping ||
!canStart
}
>
{gwLoading || isStarting || isRestarting || isStopping ? (
@@ -238,7 +253,7 @@ export function AppHeader() {
</Button>
</span>
</TooltipTrigger>
{(gwError || (!canStart && startReason)) ? (
{gwError || (!canStart && startReason) ? (
<TooltipContent>{gwError ?? startReason}</TooltipContent>
) : null}
</Tooltip>
@@ -42,15 +42,11 @@ export function ChatComposer({
}: ChatComposerProps) {
const { t } = useTranslation()
const canInput = inputDisabledReason === null
const placeholder = canInput
? t("chat.placeholder")
: t(`chat.disabledPlaceholder.${inputDisabledReason}`)
const inputDisabledReason = (() => {
if (!isConnected) return t("chat.inputDisabled.notConnected")
if (!hasDefaultModel) return t("chat.inputDisabled.noModel")
return null
})()
const disabledMessage =
inputDisabledReason === null
? null
: t(`chat.disabledPlaceholder.${inputDisabledReason}`)
const placeholder = disabledMessage ?? t("chat.placeholder")
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.nativeEvent.isComposing) return
@@ -95,7 +91,7 @@ export function ChatComposer({
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={!canInput}
title={inputDisabledReason || undefined}
title={disabledMessage || undefined}
className={cn(
"placeholder:text-muted-foreground/50 max-h-[200px] min-h-[60px] resize-none border-0 bg-transparent px-2 py-1 text-[15px] shadow-none transition-colors focus-visible:ring-0 focus-visible:outline-none dark:bg-transparent",
!canInput && "cursor-not-allowed",
@@ -103,9 +99,9 @@ export function ChatComposer({
minRows={1}
maxRows={8}
/>
{!canInput && inputDisabledReason && (
<div className="px-3 py-1 text-xs text-muted-foreground">
{inputDisabledReason}
{!canInput && disabledMessage && (
<div className="text-muted-foreground px-3 py-1 text-xs">
{disabledMessage}
</div>
)}
@@ -5,8 +5,8 @@ import { toast } from "sonner"
import { AssistantMessage } from "@/components/chat/assistant-message"
import {
type ChatInputDisabledReason,
ChatComposer,
type ChatInputDisabledReason,
} from "@/components/chat/chat-composer"
import { ChatEmptyState } from "@/components/chat/chat-empty-state"
import { ModelSelector } from "@/components/chat/model-selector"