feat(ai-settings): replace useless API-key eye with in-field Clear
The API-key PasswordInput is a write-only buffer — the saved key never loads back, so the built-in visibility toggle reveals nothing. When a key is saved and the buffer is empty, render a red Clear (X) ActionIcon in the field's rightSection (which suppresses the Mantine eye); while typing a new key the default eye returns. Applied to all three AI endpoints (Chat, Embeddings, Voice/STT). Handlers and payload logic unchanged. Implements docs/backlog/api-key-field-clear-in-place-of-eye.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { z } from "zod/v4";
|
||||
import {
|
||||
Anchor,
|
||||
ActionIcon,
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
@@ -15,12 +15,13 @@ import {
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { zod4Resolver } from "mantine-form-zod-resolver";
|
||||
import { IconPencil } from "@tabler/icons-react";
|
||||
import { IconPencil, IconX } from "@tabler/icons-react";
|
||||
import { useAtom } from "jotai";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -430,19 +431,34 @@ export default function AiProviderSettings() {
|
||||
disabled={isLoading}
|
||||
{...form.getInputProps("chatModel")}
|
||||
/>
|
||||
<Stack gap={4}>
|
||||
<PasswordInput
|
||||
label={t("API key")}
|
||||
placeholder={hasApiKey ? t("•••• set") : ""}
|
||||
autoComplete="off"
|
||||
{...form.getInputProps("apiKey")}
|
||||
/>
|
||||
{hasApiKey && (
|
||||
<Anchor component="button" type="button" c="red" size="xs" onClick={handleClearKey}>
|
||||
{t("Clear")}
|
||||
</Anchor>
|
||||
)}
|
||||
</Stack>
|
||||
{/* The key field is write-only: the stored key never loads back, so
|
||||
the built-in visibility toggle reveals nothing. Replace it with a
|
||||
Clear action in the right section. Passing rightSection suppresses
|
||||
the eye (Mantine). While typing a new key (buffer non-empty) fall
|
||||
back to the default eye. */}
|
||||
<PasswordInput
|
||||
label={t("API key")}
|
||||
placeholder={hasApiKey ? t("•••• set") : ""}
|
||||
autoComplete="off"
|
||||
rightSection={
|
||||
hasApiKey && form.values.apiKey.length === 0 ? (
|
||||
<Tooltip label={t("Clear")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
size="sm"
|
||||
type="button"
|
||||
aria-label={t("Clear")}
|
||||
onClick={handleClearKey}
|
||||
>
|
||||
<IconX size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : undefined
|
||||
}
|
||||
rightSectionPointerEvents="all"
|
||||
{...form.getInputProps("apiKey")}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
@@ -535,29 +551,35 @@ export default function AiProviderSettings() {
|
||||
disabled={isLoading}
|
||||
{...form.getInputProps("embeddingModel")}
|
||||
/>
|
||||
<Stack gap={4}>
|
||||
<PasswordInput
|
||||
label={t("Embedding API key")}
|
||||
placeholder={
|
||||
hasEmbeddingApiKey
|
||||
? t("•••• set")
|
||||
: t("Leave empty to use the chat API key")
|
||||
}
|
||||
autoComplete="off"
|
||||
{...form.getInputProps("embeddingApiKey")}
|
||||
/>
|
||||
{hasEmbeddingApiKey && (
|
||||
<Anchor
|
||||
component="button"
|
||||
type="button"
|
||||
c="red"
|
||||
size="xs"
|
||||
onClick={handleClearEmbeddingKey}
|
||||
>
|
||||
{t("Clear")}
|
||||
</Anchor>
|
||||
)}
|
||||
</Stack>
|
||||
{/* Write-only key field: Clear replaces the useless visibility
|
||||
toggle in the right section (see the Chat block for the rationale). */}
|
||||
<PasswordInput
|
||||
label={t("Embedding API key")}
|
||||
placeholder={
|
||||
hasEmbeddingApiKey
|
||||
? t("•••• set")
|
||||
: t("Leave empty to use the chat API key")
|
||||
}
|
||||
autoComplete="off"
|
||||
rightSection={
|
||||
hasEmbeddingApiKey && form.values.embeddingApiKey.length === 0 ? (
|
||||
<Tooltip label={t("Clear")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
size="sm"
|
||||
type="button"
|
||||
aria-label={t("Clear")}
|
||||
onClick={handleClearEmbeddingKey}
|
||||
>
|
||||
<IconX size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : undefined
|
||||
}
|
||||
rightSectionPointerEvents="all"
|
||||
{...form.getInputProps("embeddingApiKey")}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
@@ -654,29 +676,35 @@ export default function AiProviderSettings() {
|
||||
disabled={isLoading}
|
||||
{...form.getInputProps("sttModel")}
|
||||
/>
|
||||
<Stack gap={4}>
|
||||
<PasswordInput
|
||||
label={t("API key")}
|
||||
placeholder={
|
||||
hasSttApiKey
|
||||
? t("•••• set")
|
||||
: t("Leave empty to use the chat API key")
|
||||
}
|
||||
autoComplete="off"
|
||||
{...form.getInputProps("sttApiKey")}
|
||||
/>
|
||||
{hasSttApiKey && (
|
||||
<Anchor
|
||||
component="button"
|
||||
type="button"
|
||||
c="red"
|
||||
size="xs"
|
||||
onClick={handleClearSttKey}
|
||||
>
|
||||
{t("Clear")}
|
||||
</Anchor>
|
||||
)}
|
||||
</Stack>
|
||||
{/* Write-only key field: Clear replaces the useless visibility
|
||||
toggle in the right section (see the Chat block for the rationale). */}
|
||||
<PasswordInput
|
||||
label={t("API key")}
|
||||
placeholder={
|
||||
hasSttApiKey
|
||||
? t("•••• set")
|
||||
: t("Leave empty to use the chat API key")
|
||||
}
|
||||
autoComplete="off"
|
||||
rightSection={
|
||||
hasSttApiKey && form.values.sttApiKey.length === 0 ? (
|
||||
<Tooltip label={t("Clear")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
size="sm"
|
||||
type="button"
|
||||
aria-label={t("Clear")}
|
||||
onClick={handleClearSttKey}
|
||||
>
|
||||
<IconX size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : undefined
|
||||
}
|
||||
rightSectionPointerEvents="all"
|
||||
{...form.getInputProps("sttApiKey")}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Select
|
||||
|
||||
Reference in New Issue
Block a user