docs: remove implemented api-key-field-clear backlog plan
The in-field Clear for the API key fields is implemented and merged via this branch, so the backlog plan is no longer needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,157 +0,0 @@
|
|||||||
# Поле «API key»: убрать бесполезный «глазок», поставить Clear на его место
|
|
||||||
|
|
||||||
Статус: **план, код не менялся.** UI-задача на клиенте. Бэкенда не касается.
|
|
||||||
|
|
||||||
## Суть
|
|
||||||
|
|
||||||
В настройках AI-провайдера (Workspace settings → AI) у каждого из трёх
|
|
||||||
эндпоинтов есть поле `PasswordInput` для API-ключа. Когда ключ уже сохранён на
|
|
||||||
сервере, поле показывает плейсхолдер `•••• set`, а справа — встроенный в
|
|
||||||
Mantine `PasswordInput` тогл видимости («глазок»). Под полем отдельной строкой
|
|
||||||
висит ссылка **Clear**.
|
|
||||||
|
|
||||||
Проблема: **«глазок» бессмысленен.** Поле ключа — write-only буфер: реальный
|
|
||||||
ключ в него никогда не загружается (сервер отдаёт только факт «ключ есть»,
|
|
||||||
`hasApiKey`, см. `ai-provider-settings.tsx:120-130, 154-177`). Когда ключ
|
|
||||||
сохранён, буфер пустой → нажатие «глазка» показывает пустоту. Полезного смысла
|
|
||||||
нет.
|
|
||||||
|
|
||||||
Хотим: **в состоянии «ключ сохранён» показывать кнопку Clear прямо на месте
|
|
||||||
«глазка» (в правой секции поля), а не отдельной ссылкой снизу.** Сделать это во
|
|
||||||
**всех трёх эндпоинтах** (Chat / LLM, Embeddings, Voice / STT).
|
|
||||||
|
|
||||||
## Где править (точные места)
|
|
||||||
|
|
||||||
Один файл:
|
|
||||||
[ai-provider-settings.tsx](apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx)
|
|
||||||
|
|
||||||
Три одинаковых по структуре блока — `<Stack gap={4}>` с `PasswordInput` + ссылкой
|
|
||||||
`<Anchor>Clear</Anchor>` снизу:
|
|
||||||
|
|
||||||
1. **Chat / LLM** — строки ~433-445 (`apiKey`, `hasApiKey`, `handleClearKey`).
|
|
||||||
2. **Embeddings** — строки ~538-560 (`embeddingApiKey`, `hasEmbeddingApiKey`,
|
|
||||||
`handleClearEmbeddingKey`).
|
|
||||||
3. **Voice / STT** — строки ~657-679 (`sttApiKey`, `hasSttApiKey`,
|
|
||||||
`handleClearSttKey`).
|
|
||||||
|
|
||||||
Обработчики очистки (`handleClearKey` / `handleClearEmbeddingKey` /
|
|
||||||
`handleClearSttKey`, строки 239-255) и вся логика буферов/payload
|
|
||||||
(`buildPayload`, строки 179-222) — **остаются без изменений.** Меняется только
|
|
||||||
разметка трёх полей.
|
|
||||||
|
|
||||||
## Ключевой факт Mantine (подтверждён по докам)
|
|
||||||
|
|
||||||
У `PasswordInput`: **если передать свой `rightSection`, встроенный тогл
|
|
||||||
видимости («глазок») не рендерится** (Mantine docs, PasswordInput → «Usage
|
|
||||||
without visibility toggle»: *“When the `rightSection` prop is used, the
|
|
||||||
visibility toggle button is not rendered.”*).
|
|
||||||
|
|
||||||
То есть «поставить Clear на место глазка» = передать в `PasswordInput`
|
|
||||||
`rightSection` с кнопкой Clear. Отдельный костыль для скрытия глазка не нужен.
|
|
||||||
|
|
||||||
## Рекомендуемое поведение
|
|
||||||
|
|
||||||
Показывать Clear в правой секции **только когда ключ сохранён И буфер пуст**
|
|
||||||
(`hasApiKey && form.values.apiKey.length === 0`). Как только пользователь
|
|
||||||
начинает вводить НОВЫЙ ключ (буфер непустой) — возвращать дефолтный «глазок»:
|
|
||||||
вот тут он осмыслен (проверить, что набрал). После клика по Clear обработчик
|
|
||||||
ставит `hasApiKey=false` → `rightSection` снова `undefined` → поле становится
|
|
||||||
обычным пустым `PasswordInput` с глазком для ввода свежего ключа. Поведение
|
|
||||||
самосогласованное.
|
|
||||||
|
|
||||||
Альтернатива (проще, но грубее): показывать Clear всегда, пока `hasApiKey`
|
|
||||||
(без проверки буфера). Тогда при вводе нового поверх старого глазка не будет.
|
|
||||||
Допустимо, но теряем удобную проверку набранного. Рекомендуется вариант с
|
|
||||||
проверкой буфера.
|
|
||||||
|
|
||||||
## Эскиз правки (на примере Chat-поля; для двух других — аналогично)
|
|
||||||
|
|
||||||
Было:
|
|
||||||
```tsx
|
|
||||||
<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>
|
|
||||||
```
|
|
||||||
|
|
||||||
Стало:
|
|
||||||
```tsx
|
|
||||||
{/* 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"
|
|
||||||
aria-label={t("Clear")}
|
|
||||||
onClick={handleClearKey}
|
|
||||||
>
|
|
||||||
<IconX size={16} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
rightSectionPointerEvents="all"
|
|
||||||
{...form.getInputProps("apiKey")}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Изменения по каждому из трёх блоков:
|
|
||||||
- Убрать обёртку `<Stack gap={4}>…</Stack>` и ссылку `<Anchor>Clear</Anchor>`
|
|
||||||
снизу (Clear переезжает внутрь поля). После удаления `Stack` второй ребёнок
|
|
||||||
`<Group grow>` — сам `PasswordInput`; раскладка «Model | API key» в две
|
|
||||||
колонки сохраняется.
|
|
||||||
- Подставить свои переменные/обработчики: эндпоинт 2 — `hasEmbeddingApiKey` /
|
|
||||||
`embeddingApiKey` / `handleClearEmbeddingKey`; эндпоинт 3 — `hasSttApiKey` /
|
|
||||||
`sttApiKey` / `handleClearSttKey`.
|
|
||||||
|
|
||||||
## Тонкости / на что смотреть
|
|
||||||
|
|
||||||
- **Импорты.** Добавить `ActionIcon`, `Tooltip` из `@mantine/core` и `IconX`
|
|
||||||
из `@tabler/icons-react` (рядом с уже импортируемым `IconPencil`). После
|
|
||||||
переезда Clear внутрь поля `Anchor` может стать неиспользуемым — проверить и
|
|
||||||
убрать из импорта, иначе словим lint-ошибку `no-unused-vars`.
|
|
||||||
- **Кликабельность правой секции.** У `Input`/`PasswordInput` правая секция по
|
|
||||||
умолчанию не всегда принимает клики — задать `rightSectionPointerEvents="all"`,
|
|
||||||
чтобы клик по Clear срабатывал.
|
|
||||||
- **Тип кнопки.** `ActionIcon` рендерит `<button>` (по умолчанию `type="button"`).
|
|
||||||
Формы как `<form onSubmit>` тут нет — Save висит на отдельной `type="button"`
|
|
||||||
кнопке (строки 735-744), так что случайного сабмита не будет. Для надёжности
|
|
||||||
можно явно проставить `type="button"`.
|
|
||||||
- **i18n.** Новый строковый ключ не нужен: `t("Clear")` уже используется
|
|
||||||
(бывшая ссылка). Тултип и `aria-label` переиспользуют его. Плейсхолдер
|
|
||||||
`•••• set` не трогаем.
|
|
||||||
- **Ширина правой секции.** Иконка X помещается в штатный размер секции (как и
|
|
||||||
глазок). Если решат оставить именно слово «Clear» текстом вместо иконки —
|
|
||||||
понадобится `rightSectionWidth`, иначе текст обрежется. Рекомендуется
|
|
||||||
иконка + тултип (компактно, как глазок).
|
|
||||||
- **Доступность.** Обязателен `aria-label={t("Clear")}` на `ActionIcon` (иконка
|
|
||||||
без видимого текста).
|
|
||||||
|
|
||||||
## Опционально (вне «трёх эндпоинтов»)
|
|
||||||
|
|
||||||
Тот же паттерн «бесполезный глазок + Clear снизу» есть в форме внешнего
|
|
||||||
MCP-сервера —
|
|
||||||
[ai-mcp-server-form.tsx](apps/client/src/features/workspace/components/settings/components/ai-mcp-server-form.tsx)
|
|
||||||
(поле Authorization-заголовков, `PasswordInput` строка ~193, плейсхолдер
|
|
||||||
`•••• set` строка ~196, `Anchor`-Clear строки ~207-209, обработчик
|
|
||||||
`handleClearHeaders`). В запросе он не входит в «три эндпоинта», но логически
|
|
||||||
страдает тем же. Можно причесать заодно для единообразия — отдельным мелким
|
|
||||||
шагом, по той же схеме.
|
|
||||||
Reference in New Issue
Block a user