Skip to content

Commit c0fa80d

Browse files
committed
fix(settings): clarify BeatAPI connection state
1 parent a676ff2 commit c0fa80d

5 files changed

Lines changed: 153 additions & 85 deletions

File tree

‎messages/en.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@
672672
"triggerConfiguredLabel": "API configuration — BeatAPI connected",
673673
"loadingConnection": "Loading BeatAPI connection",
674674
"title": "Connections",
675-
"description": "Paste your own BeatAPI API key. No shared key is included.",
675+
"description": "Connect BeatAPI when you want to use remote generation models.",
676676
"providerTab": "AI Provider",
677677
"storageTab": "Storage",
678678
"activeProvider": "Active provider",
@@ -683,13 +683,19 @@
683683
"mediaValue": "Image + video",
684684
"keyLabel": "API Key",
685685
"keyPlaceholder": "sk_xxxxxxxxxxxx",
686-
"keyConfigured": "Configured",
687-
"keyNotConfigured": "Not configured",
688686
"save": "Save",
689687
"saving": "Saving…",
690688
"saved": "API key saved",
691-
"connectHint": "Stored in this local workspace and sent only to the official BeatAPI endpoint.",
692-
"replaceKey": "Replace key",
689+
"connectedTitle": "BeatAPI connected",
690+
"connectedDescription": "Your saved API key is ready for remote generation.",
691+
"savedKeyLabel": "Saved API key",
692+
"notConnectedTitle": "BeatAPI not connected",
693+
"notConnectedDescription": "Add an API key to enable image and video generation.",
694+
"changeKey": "Change API key",
695+
"changeKeyTitle": "Enter a new API key",
696+
"changeKeyDescription": "Your current key stays active until the new key is verified.",
697+
"connect": "Connect BeatAPI",
698+
"saveNewKey": "Save new key",
693699
"cancelReplace": "Cancel",
694700
"getKey": "Get a BeatAPI API key →",
695701
"storage": {

‎messages/ja.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@
672672
"triggerConfiguredLabel": "API設定 — BeatAPI接続済み",
673673
"loadingConnection": "BeatAPI接続を読み込み中",
674674
"title": "接続",
675-
"description": "ご自身のBeatAPI APIキーを貼り付けてください。共有キーは含まれていません。",
675+
"description": "リモート生成モデルを使用するときにBeatAPI APIキーを接続します。",
676676
"providerTab": "AIプロバイダー",
677677
"storageTab": "ストレージ",
678678
"activeProvider": "使用中のプロバイダー",
@@ -683,13 +683,19 @@
683683
"mediaValue": "画像 + 動画",
684684
"keyLabel": "API Key",
685685
"keyPlaceholder": "sk_xxxxxxxxxxxx",
686-
"keyConfigured": "設定済み",
687-
"keyNotConfigured": "未設定",
688686
"save": "保存",
689687
"saving": "保存中…",
690688
"saved": "APIキーを保存しました",
691-
"connectHint": "このローカルワークスペースに保存され、BeatAPIの公式エンドポイントにのみ送信されます。",
692-
"replaceKey": "キーを変更",
689+
"connectedTitle": "BeatAPI接続済み",
690+
"connectedDescription": "保存済みのAPIキーでリモート生成を利用できます。",
691+
"savedKeyLabel": "保存済みAPIキー",
692+
"notConnectedTitle": "BeatAPI未接続",
693+
"notConnectedDescription": "APIキーを追加すると画像と動画を生成できます。",
694+
"changeKey": "APIキーを変更",
695+
"changeKeyTitle": "新しいAPIキーを入力",
696+
"changeKeyDescription": "新しいキーの確認が完了するまで、現在のキーが保持されます。",
697+
"connect": "BeatAPIに接続",
698+
"saveNewKey": "新しいキーを保存",
693699
"cancelReplace": "キャンセル",
694700
"getKey": "BeatAPI APIキーを取得 →",
695701
"storage": {

‎messages/zh.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@
672672
"triggerConfiguredLabel": "API 配置 — BeatAPI 已连接",
673673
"loadingConnection": "正在读取 BeatAPI 连接",
674674
"title": "连接配置",
675-
"description": "粘贴你自己的 BeatAPI API Key,项目不内置共享密钥。",
675+
"description": "需要使用远程生成模型时,连接你的 BeatAPI API Key。",
676676
"providerTab": "AI Provider",
677677
"storageTab": "存储",
678678
"activeProvider": "当前提供商",
@@ -683,13 +683,19 @@
683683
"mediaValue": "图片 + 视频",
684684
"keyLabel": "API Key",
685685
"keyPlaceholder": "sk_xxxxxxxxxxxx",
686-
"keyConfigured": "已配置",
687-
"keyNotConfigured": "未配置",
688686
"save": "保存",
689687
"saving": "保存中…",
690688
"saved": "API Key 已保存",
691-
"connectHint": "保存在本地工作台,只会发往 BeatAPI 官方接口。",
692-
"replaceKey": "更换密钥",
689+
"connectedTitle": "BeatAPI 已连接",
690+
"connectedDescription": "已保存的 API Key 可用于远程生成。",
691+
"savedKeyLabel": "当前 API Key",
692+
"notConnectedTitle": "BeatAPI 未连接",
693+
"notConnectedDescription": "添加 API Key 后即可生成图片和视频。",
694+
"changeKey": "更换 API Key",
695+
"changeKeyTitle": "输入新的 API Key",
696+
"changeKeyDescription": "新 Key 验证成功前,当前 Key 会继续保留。",
697+
"connect": "连接 BeatAPI",
698+
"saveNewKey": "保存新 Key",
693699
"cancelReplace": "取消",
694700
"getKey": "获取 BeatAPI API Key →",
695701
"storage": {

‎src/components/app/product-page-shell.test.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ test('API configuration exposes persisted connection state without revealing the
6363
assert.match(apiConfigSource, /triggerConfiguredLabel/);
6464
assert.match(apiConfigSource, /apiKeyPreview/);
6565
assert.match(apiConfigSource, /maskApiKeyPreview/);
66+
assert.match(apiConfigSource, /connectedTitle/);
67+
assert.match(apiConfigSource, /savedKeyLabel/);
68+
assert.match(apiConfigSource, /changeKeyDescription/);
6669
assert.match(apiConfigSource, /bg-\[#c7f36b\]/);
70+
assert.doesNotMatch(apiConfigSource, /connectHint/);
71+
assert.doesNotMatch(apiConfigSource, /replaceKey/);
6772
assert.doesNotMatch(apiConfigSource, /type="text"[\s\S]{0,120}id="beatapi-key"/);
6873
});

‎src/components/app/workspace-api-config-dialog.tsx‎

Lines changed: 115 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Cloud, Database } from 'lucide-react';
1+
import { CheckCircle2, Cloud, Database, KeyRound } from 'lucide-react';
22
import { useEffect, useState } from 'react';
33
import { toast } from 'sonner';
44

@@ -121,87 +121,132 @@ function ApiConfigForm({
121121
) : null}
122122
</div>
123123

124-
<div>
125-
<div className="mb-2 flex items-center justify-between">
126-
<label
127-
htmlFor="beatapi-key"
128-
className="text-[12px] font-medium text-white/45"
129-
>
130-
{t('keyLabel')}
131-
</label>
132-
{configured && !replacing ? (
133-
<span className="rounded-full border border-[#ff7a33]/30 bg-[#ff7a33]/10 px-2 py-0.5 text-[11px] font-medium text-[#ff9a62]">
134-
{t('keyConfigured')}
124+
{!loaded ? (
125+
<div
126+
className="h-[148px] animate-pulse rounded-[16px] border border-white/[0.08] bg-white/[0.04] motion-reduce:animate-none"
127+
aria-label={t('loadingConnection')}
128+
/>
129+
) : configured && !replacing ? (
130+
<div
131+
className="rounded-[16px] border border-[#c7f36b]/20 bg-[#c7f36b]/[0.045] p-4"
132+
role="status"
133+
>
134+
<div className="flex items-start gap-3">
135+
<span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full bg-[#c7f36b]/12 text-[#c7f36b]">
136+
<CheckCircle2 className="size-4" aria-hidden="true" />
135137
</span>
136-
) : null}
137-
</div>
138+
<div className="min-w-0">
139+
<p className="text-[14px] font-semibold text-white">
140+
{t('connectedTitle')}
141+
</p>
142+
<p className="mt-0.5 text-[12px] leading-5 text-white/48">
143+
{t('connectedDescription')}
144+
</p>
145+
</div>
146+
</div>
138147

139-
{!loaded ? (
140-
<div
141-
className="h-11 animate-pulse rounded-[12px] border border-white/[0.08] bg-white/[0.04] motion-reduce:animate-none"
142-
aria-label={t('loadingConnection')}
143-
/>
144-
) : showEditor ? (
145-
<input
146-
id="beatapi-key"
147-
type="password"
148-
value={apiKey}
149-
onChange={(event) => setApiKey(event.target.value)}
150-
placeholder={t('keyPlaceholder')}
151-
autoComplete="off"
152-
className={inputClassName}
153-
/>
154-
) : (
155-
<div className="flex h-11 items-center rounded-[12px] border border-white/[0.12] bg-white/[0.04] px-3.5 font-mono text-[13px] text-white/80">
156-
{state?.apiKeyPreview}
148+
<div className="mt-4 flex items-center justify-between gap-4 rounded-[11px] border border-white/[0.08] bg-black/15 px-3 py-2.5">
149+
<span className="text-[11px] font-medium uppercase tracking-[0.08em] text-white/38">
150+
{t('savedKeyLabel')}
151+
</span>
152+
<code className="truncate font-mono text-[12px] text-white/72">
153+
{state?.apiKeyPreview}
154+
</code>
157155
</div>
158-
)}
159156

160-
<p className="mt-2 text-[12px] leading-5 text-white/40">
161-
{t('connectHint')}
162-
</p>
163-
<div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1">
164-
{configured && !replacing ? (
157+
<div className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-2">
165158
<button
166159
type="button"
167160
onClick={() => setReplacing(true)}
168-
className="text-[13px] font-semibold text-white/55 transition hover:text-white"
161+
className="rounded-[9px] border border-white/[0.12] bg-white/[0.04] px-3 py-2 text-[12px] font-semibold text-white/72 transition hover:border-white/20 hover:bg-white/[0.07] hover:text-white"
169162
>
170-
{t('replaceKey')}
163+
{t('changeKey')}
171164
</button>
172-
) : null}
173-
{replacing ? (
174-
<button
175-
type="button"
176-
onClick={() => {
177-
setReplacing(false);
178-
setApiKey('');
179-
}}
180-
className="text-[13px] font-semibold text-white/55 transition hover:text-white"
165+
<a
166+
href="https://beatapi.io/dashboard/apikeys"
167+
target="_blank"
168+
rel="noreferrer"
169+
className="inline-flex text-[12px] font-semibold text-[#ff8b4d] transition hover:text-[#ffa26b]"
181170
>
182-
{t('cancelReplace')}
183-
</button>
184-
) : null}
185-
<a
186-
href="https://beatapi.io/dashboard/apikeys"
187-
target="_blank"
188-
rel="noreferrer"
189-
className="inline-flex text-[13px] font-semibold text-[#ff8b4d] transition hover:text-[#ffa26b]"
190-
>
191-
{t('getKey')}
192-
</a>
171+
{t('getKey')}
172+
</a>
173+
</div>
193174
</div>
194-
</div>
175+
) : showEditor ? (
176+
<div className="space-y-4">
177+
<div
178+
className="flex items-start gap-3 rounded-[14px] border border-white/[0.09] bg-white/[0.025] p-3.5"
179+
role="status"
180+
>
181+
<span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full bg-[#ff7a33]/10 text-[#ff9258]">
182+
<KeyRound className="size-4" aria-hidden="true" />
183+
</span>
184+
<div className="min-w-0">
185+
<p className="text-[13px] font-semibold text-white">
186+
{replacing ? t('changeKeyTitle') : t('notConnectedTitle')}
187+
</p>
188+
<p className="mt-0.5 text-[12px] leading-5 text-white/45">
189+
{replacing
190+
? t('changeKeyDescription')
191+
: t('notConnectedDescription')}
192+
</p>
193+
</div>
194+
</div>
195195

196-
{showEditor ? (
197-
<button
198-
type="button"
199-
disabled={saving || !apiKey.trim()}
200-
onClick={() => void save()}
201-
className={saveButtonClassName}
202-
>
203-
{saving ? t('saving') : t('save')}
204-
</button>
196+
<div>
197+
<label
198+
htmlFor="beatapi-key"
199+
className="mb-2 block text-[12px] font-medium text-white/45"
200+
>
201+
{t('keyLabel')}
202+
</label>
203+
<input
204+
id="beatapi-key"
205+
type="password"
206+
value={apiKey}
207+
onChange={(event) => setApiKey(event.target.value)}
208+
placeholder={t('keyPlaceholder')}
209+
autoComplete="off"
210+
className={inputClassName}
211+
/>
212+
</div>
213+
214+
<button
215+
type="button"
216+
disabled={saving || !apiKey.trim()}
217+
onClick={() => void save()}
218+
className={saveButtonClassName}
219+
>
220+
{saving
221+
? t('saving')
222+
: replacing
223+
? t('saveNewKey')
224+
: t('connect')}
225+
</button>
226+
227+
<div className="flex flex-wrap items-center gap-x-4 gap-y-2">
228+
{replacing ? (
229+
<button
230+
type="button"
231+
onClick={() => {
232+
setReplacing(false);
233+
setApiKey('');
234+
}}
235+
className="text-[12px] font-semibold text-white/55 transition hover:text-white"
236+
>
237+
{t('cancelReplace')}
238+
</button>
239+
) : null}
240+
<a
241+
href="https://beatapi.io/dashboard/apikeys"
242+
target="_blank"
243+
rel="noreferrer"
244+
className="inline-flex text-[12px] font-semibold text-[#ff8b4d] transition hover:text-[#ffa26b]"
245+
>
246+
{t('getKey')}
247+
</a>
248+
</div>
249+
</div>
205250
) : null}
206251
</div>
207252
);

0 commit comments

Comments
 (0)