* 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
87 lines
2.8 KiB
TypeScript
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>
|
|
)
|
|
}
|