fix(frontend): polish model and credential UX; remove Providers nav
- remove the Providers item from sidebar navigation and locale keys - simplify chat composer by dropping attach/voice action buttons - support ReactNode titles in credential cards and add provider brand icons - refine sheet header/footer styling and device-code footer button hierarchy - disable “Set default” when a model is unconfigured or already default
This commit is contained in:
parent
747c86aa89
commit
ce92d1f062
12 changed files with 50 additions and 59 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import { IconChevronRight } from "@tabler/icons-react"
|
||||
import {
|
||||
IconAtom,
|
||||
IconCloud,
|
||||
IconKey,
|
||||
IconListDetails,
|
||||
IconMessageCircle,
|
||||
|
|
@ -39,7 +38,6 @@ const navGroups = [
|
|||
label: "navigation.model_group",
|
||||
defaultOpen: true,
|
||||
items: [
|
||||
{ title: "navigation.providers", url: "/providers", icon: IconCloud },
|
||||
{ title: "navigation.models", url: "/models", icon: IconAtom },
|
||||
{ title: "navigation.credentials", url: "/credentials", icon: IconKey },
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
import { IconArrowUp, IconMicrophone, IconPaperclip } from "@tabler/icons-react"
|
||||
import { IconArrowUp } from "@tabler/icons-react"
|
||||
import type { KeyboardEvent } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import TextareaAutosize from "react-textarea-autosize"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
|
||||
interface ChatComposerProps {
|
||||
input: string
|
||||
|
|
@ -50,35 +45,7 @@ export function ChatComposer({
|
|||
/>
|
||||
|
||||
<div className="mt-2 flex items-center justify-between px-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground size-8 rounded-full"
|
||||
disabled={!isConnected}
|
||||
>
|
||||
<IconPaperclip className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("chat.attach")}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground size-8 rounded-full"
|
||||
disabled={!isConnected}
|
||||
>
|
||||
<IconMicrophone className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("chat.voice")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">{/* action buttons */}</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { IconKey, IconLoader2, IconPlayerStopFilled } from "@tabler/icons-react"
|
||||
import {
|
||||
IconKey,
|
||||
IconLoader2,
|
||||
IconPlayerStopFilled,
|
||||
IconSparkles,
|
||||
} from "@tabler/icons-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import type { OAuthProviderStatus } from "@/api/oauth"
|
||||
|
|
@ -33,7 +38,14 @@ export function AnthropicCredentialCard({
|
|||
|
||||
return (
|
||||
<CredentialCard
|
||||
title="Anthropic"
|
||||
title={
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="border-muted inline-flex size-6 items-center justify-center rounded-full border">
|
||||
<IconSparkles className="size-3.5" />
|
||||
</span>
|
||||
<span>Anthropic</span>
|
||||
</span>
|
||||
}
|
||||
description={t(
|
||||
"credentials.providers.anthropic.description",
|
||||
"Uses token login for Claude access.",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
IconBrandGoogle,
|
||||
IconLoader2,
|
||||
IconLockOpen,
|
||||
IconPlayerStopFilled,
|
||||
|
|
@ -31,7 +32,14 @@ export function AntigravityCredentialCard({
|
|||
|
||||
return (
|
||||
<CredentialCard
|
||||
title="Google Antigravity"
|
||||
title={
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="border-muted inline-flex size-6 items-center justify-center rounded-full border">
|
||||
<IconBrandGoogle className="size-3.5" />
|
||||
</span>
|
||||
<span>Google Antigravity</span>
|
||||
</span>
|
||||
}
|
||||
description={t(
|
||||
"credentials.providers.antigravity.description",
|
||||
"Uses browser OAuth for Google Cloud Code Assist.",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { OAuthProviderStatus } from "@/api/oauth"
|
|||
import { ProviderStatusLine } from "./provider-status-line"
|
||||
|
||||
interface CredentialCardProps {
|
||||
title: string
|
||||
title: ReactNode
|
||||
description: string
|
||||
status: OAuthProviderStatus["status"]
|
||||
authMethod?: string
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function DeviceCodeSheet({
|
|||
side="right"
|
||||
className="data-[side=right]:!w-full data-[side=right]:sm:!w-[480px] data-[side=right]:sm:!max-w-[480px]"
|
||||
>
|
||||
<SheetHeader className="border-b px-6 py-5">
|
||||
<SheetHeader className="border-b-muted border-b px-6 py-5">
|
||||
<SheetTitle>
|
||||
{t("credentials.device.title", "OpenAI Device Login")}
|
||||
</SheetTitle>
|
||||
|
|
@ -81,15 +81,15 @@ export function DeviceCodeSheet({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<SheetFooter className="border-t px-6 py-4">
|
||||
<Button variant="outline" asChild disabled={!flow?.verify_url}>
|
||||
<SheetFooter className="border-t-muted border-t px-6 py-4">
|
||||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
{t("common.cancel", "Close")}
|
||||
</Button>
|
||||
<Button asChild disabled={!flow?.verify_url}>
|
||||
<a href={flow?.verify_url || "#"} target="_blank" rel="noreferrer">
|
||||
{t("credentials.device.open", "Open Verification Page")}
|
||||
</a>
|
||||
</Button>
|
||||
<Button onClick={() => onOpenChange(false)}>
|
||||
{t("common.cancel", "Close")}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,14 @@ export function OpenAICredentialCard({
|
|||
|
||||
return (
|
||||
<CredentialCard
|
||||
title="OpenAI"
|
||||
title={
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="border-muted inline-flex size-6 items-center justify-center rounded-full border">
|
||||
<IconBrandOpenai className="size-3.5" />
|
||||
</span>
|
||||
<span>OpenAI</span>
|
||||
</span>
|
||||
}
|
||||
description={t(
|
||||
"credentials.providers.openai.description",
|
||||
"Supports browser OAuth, device code, and token login.",
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export function EditModelSheet({
|
|||
side="right"
|
||||
className="flex flex-col gap-0 p-0 data-[side=right]:!w-full data-[side=right]:sm:!w-[560px] data-[side=right]:sm:!max-w-[560px]"
|
||||
>
|
||||
<SheetHeader className="border-b px-6 py-5">
|
||||
<SheetHeader className="border-b-muted border-b px-6 py-5">
|
||||
<SheetTitle className="text-base">
|
||||
{t("models.edit.title", { name: model?.model_name })}
|
||||
</SheetTitle>
|
||||
|
|
@ -286,7 +286,7 @@ export function EditModelSheet({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<SheetFooter className="border-t px-6 py-4">
|
||||
<SheetFooter className="border-t-muted border-t px-6 py-4">
|
||||
<Button variant="ghost" onClick={onClose} disabled={saving}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export function ModelCard({
|
|||
}: ModelCardProps) {
|
||||
const { t } = useTranslation()
|
||||
const isOAuth = model.auth_method === "oauth"
|
||||
const canSetDefault = model.configured && !model.is_default
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -78,7 +79,7 @@ export function ModelCard({
|
|||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => onSetDefault(model)}
|
||||
disabled={settingDefault}
|
||||
disabled={settingDefault || !canSetDefault}
|
||||
title={t("models.action.setDefault")}
|
||||
>
|
||||
{settingDefault ? (
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ import { getProviderKey, getProviderLabel } from "./provider-label"
|
|||
import { ProviderSection } from "./provider-section"
|
||||
|
||||
const PROVIDER_PRIORITY: Record<string, number> = {
|
||||
openai: 0,
|
||||
gemini: 1,
|
||||
anthropic: 2,
|
||||
zhipu: 3,
|
||||
deepseek: 4,
|
||||
volcengine: 5,
|
||||
volcengine: 0,
|
||||
openai: 1,
|
||||
gemini: 2,
|
||||
anthropic: 3,
|
||||
zhipu: 4,
|
||||
deepseek: 5,
|
||||
openrouter: 6,
|
||||
qwen: 7,
|
||||
moonshot: 8,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
"navigation": {
|
||||
"chat": "Chat",
|
||||
"model_group": "Models",
|
||||
"providers": "Providers",
|
||||
"models": "Models",
|
||||
"credentials": "Credentials",
|
||||
"services": "Services",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
"navigation": {
|
||||
"chat": "对话",
|
||||
"model_group": "模型",
|
||||
"providers": "服务商",
|
||||
"models": "模型",
|
||||
"credentials": "凭据",
|
||||
"services": "服务",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue