picoclaw/web/frontend/src/components/chat/chat-empty-state.tsx
LC 3b3f95c44c
feat(web): refine model availability states and preserve API key preview placeholder (#2226)
* feat(web): clarify model availability and status display

- Rename model availability field from configured to available across backend API and frontend usage

- Keep status as reason classification (configured/unconfigured/unreachable) and show unreachable in UI

- Preserve API key preview even when local service is unreachable

- Update backend tests to assert both availability and status semantics

* fix(web): clarify unreachable model status and wording

- Show unreachable status in model cards instead of API key preview when service is down

- Keep API key placeholder preview in model settings whenever an API key is already saved

- Rename model status wording from configured to available across backend, frontend, and i18n

- Update backend model status tests to match renamed status semantics

* style(web): standardize formatting in handleListModels function

* refactor(web): enforce status field as required to follow backend behavior
2026-03-31 22:52:04 +08:00

87 lines
2.8 KiB
TypeScript

import {
IconPlugConnectedX,
IconRobot,
IconRobotOff,
IconStar,
} from "@tabler/icons-react"
import { Link } from "@tanstack/react-router"
import { useTranslation } from "react-i18next"
import { Button } from "@/components/ui/button"
interface ChatEmptyStateProps {
hasAvailableModels: boolean
defaultModelName: string
isConnected: boolean
}
export function ChatEmptyState({
hasAvailableModels,
defaultModelName,
isConnected,
}: ChatEmptyStateProps) {
const { t } = useTranslation()
if (!hasAvailableModels) {
return (
<div className="flex flex-col items-center justify-center py-20 opacity-70">
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-amber-500/10 text-amber-500">
<IconRobotOff className="h-8 w-8" />
</div>
<h3 className="mb-2 text-xl font-medium">
{t("chat.empty.noConfiguredModel")}
</h3>
<p className="text-muted-foreground mb-4 text-center text-sm">
{t("chat.empty.noConfiguredModelDescription")}
</p>
<Button asChild variant="outline" size="sm" className="px-4">
<Link to="/models">{t("chat.empty.goToModels")}</Link>
</Button>
</div>
)
}
if (!defaultModelName) {
return (
<div className="flex flex-col items-center justify-center py-20 opacity-70">
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-amber-500/10 text-amber-500">
<IconStar className="h-8 w-8" />
</div>
<h3 className="mb-2 text-xl font-medium">
{t("chat.empty.noSelectedModel")}
</h3>
<p className="text-muted-foreground mb-4 text-center text-sm">
{t("chat.empty.noSelectedModelDescription")}
</p>
</div>
)
}
if (!isConnected) {
return (
<div className="flex flex-col items-center justify-center py-20 opacity-70">
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-amber-500/10 text-amber-500">
<IconPlugConnectedX className="h-8 w-8" />
</div>
<h3 className="mb-2 text-xl font-medium">
{t("chat.empty.notRunning")}
</h3>
<p className="text-muted-foreground mb-4 text-center text-sm">
{t("chat.empty.notRunningDescription")}
</p>
</div>
)
}
return (
<div className="flex flex-col items-center justify-center py-20 opacity-70">
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-500/10 text-violet-500">
<IconRobot className="h-8 w-8" />
</div>
<h3 className="mb-2 text-xl font-medium">{t("chat.welcome")}</h3>
<p className="text-muted-foreground text-center text-sm">
{t("chat.welcomeDesc")}
</p>
</div>
)
}