feat: improve iFlow cookie auth UX with duplicate config handling

- Add 409 conflict handling for duplicate iFlow config files
  - Add key creation hint in cookie login section
  - Move extra actions button after delete button for consistency
  - Improve OAuth status badge display logic (hide when idle)
  - Add config toggle enable/disable i18n translations
  - Adjust item-actions spacing from sm to md
This commit is contained in:
Supra4E8C
2025-12-14 18:04:26 +08:00
parent aea1ceb6be
commit ea2ce4047f
5 changed files with 70 additions and 35 deletions

View File

@@ -447,10 +447,13 @@
"iflow_cookie_label": "Cookie Value:", "iflow_cookie_label": "Cookie Value:",
"iflow_cookie_placeholder": "Paste browser cookie, e.g. sessionid=...;", "iflow_cookie_placeholder": "Paste browser cookie, e.g. sessionid=...;",
"iflow_cookie_hint": "Submit an existing cookie to finish login without opening the authorization link; the credential file will be saved automatically.", "iflow_cookie_hint": "Submit an existing cookie to finish login without opening the authorization link; the credential file will be saved automatically.",
"iflow_cookie_key_hint": "Note: Create a key on the platform first.",
"iflow_cookie_button": "Submit Cookie Login", "iflow_cookie_button": "Submit Cookie Login",
"iflow_cookie_status_success": "Cookie login succeeded and credentials are saved.", "iflow_cookie_status_success": "Cookie login succeeded and credentials are saved.",
"iflow_cookie_status_error": "Cookie login failed:", "iflow_cookie_status_error": "Cookie login failed:",
"iflow_cookie_status_duplicate": "Duplicate config:",
"iflow_cookie_start_error": "Failed to submit cookie login:", "iflow_cookie_start_error": "Failed to submit cookie login:",
"iflow_cookie_config_duplicate": "A config file already exists (duplicate). Remove the existing file and try again if you want to re-save it.",
"iflow_cookie_required": "Please provide the Cookie value first.", "iflow_cookie_required": "Please provide the Cookie value first.",
"iflow_cookie_result_title": "Cookie Login Result", "iflow_cookie_result_title": "Cookie Login Result",
"iflow_cookie_result_email": "Account", "iflow_cookie_result_email": "Account",

View File

@@ -148,6 +148,8 @@
"excluded_models_placeholder": "用逗号或换行分隔,例如: gemini-1.5-pro, gemini-1.5-flash", "excluded_models_placeholder": "用逗号或换行分隔,例如: gemini-1.5-pro, gemini-1.5-flash",
"excluded_models_hint": "留空表示不过滤;保存时会自动去重并忽略空白。", "excluded_models_hint": "留空表示不过滤;保存时会自动去重并忽略空白。",
"excluded_models_count": "排除 {{count}} 个模型", "excluded_models_count": "排除 {{count}} 个模型",
"config_toggle_label": "启用",
"config_disabled_badge": "已停用",
"codex_title": "Codex API 配置", "codex_title": "Codex API 配置",
"codex_add_button": "添加配置", "codex_add_button": "添加配置",
"codex_empty_title": "暂无Codex配置", "codex_empty_title": "暂无Codex配置",
@@ -445,10 +447,13 @@
"iflow_cookie_label": "Cookie 内容:", "iflow_cookie_label": "Cookie 内容:",
"iflow_cookie_placeholder": "粘贴浏览器中的 Cookie例如 sessionid=...;", "iflow_cookie_placeholder": "粘贴浏览器中的 Cookie例如 sessionid=...;",
"iflow_cookie_hint": "直接提交 Cookie 以完成登录(无需打开授权链接),服务端将自动保存凭据。", "iflow_cookie_hint": "直接提交 Cookie 以完成登录(无需打开授权链接),服务端将自动保存凭据。",
"iflow_cookie_key_hint": "提示:需在平台上先创建 Key。",
"iflow_cookie_button": "提交 Cookie 登录", "iflow_cookie_button": "提交 Cookie 登录",
"iflow_cookie_status_success": "Cookie 登录成功,凭据已保存。", "iflow_cookie_status_success": "Cookie 登录成功,凭据已保存。",
"iflow_cookie_status_error": "Cookie 登录失败:", "iflow_cookie_status_error": "Cookie 登录失败:",
"iflow_cookie_status_duplicate": "配置文件重复:",
"iflow_cookie_start_error": "提交 Cookie 登录失败:", "iflow_cookie_start_error": "提交 Cookie 登录失败:",
"iflow_cookie_config_duplicate": "检测到配置文件已存在(重复),如需重新保存请先删除原文件后重试。",
"iflow_cookie_required": "请先填写 Cookie 内容", "iflow_cookie_required": "请先填写 Cookie 内容",
"iflow_cookie_result_title": "Cookie 登录结果", "iflow_cookie_result_title": "Cookie 登录结果",
"iflow_cookie_result_email": "账号", "iflow_cookie_result_email": "账号",
@@ -636,6 +641,8 @@
"claude_config_added": "Claude配置添加成功", "claude_config_added": "Claude配置添加成功",
"claude_config_updated": "Claude配置更新成功", "claude_config_updated": "Claude配置更新成功",
"claude_config_deleted": "Claude配置删除成功", "claude_config_deleted": "Claude配置删除成功",
"config_enabled": "配置已启用",
"config_disabled": "配置已停用",
"field_required": "必填字段不能为空", "field_required": "必填字段不能为空",
"openai_provider_required": "请填写提供商名称和Base URL", "openai_provider_required": "请填写提供商名称和Base URL",
"openai_provider_added": "OpenAI提供商添加成功", "openai_provider_added": "OpenAI提供商添加成功",

View File

@@ -1046,7 +1046,6 @@ export function AiProvidersPage() {
<div key={keyField(item)} className="item-row" style={rowDisabled ? { opacity: 0.6 } : undefined}> <div key={keyField(item)} className="item-row" style={rowDisabled ? { opacity: 0.6 } : undefined}>
<div className="item-meta">{renderContent(item, index)}</div> <div className="item-meta">{renderContent(item, index)}</div>
<div className="item-actions"> <div className="item-actions">
{options?.renderExtraActions ? options.renderExtraActions(item, index) : null}
<Button <Button
variant="secondary" variant="secondary"
size="sm" size="sm"
@@ -1063,6 +1062,7 @@ export function AiProvidersPage() {
> >
{deleteLabel || t('common.delete')} {deleteLabel || t('common.delete')}
</Button> </Button>
{options?.renderExtraActions ? options.renderExtraActions(item, index) : null}
</div> </div>
</div> </div>
); );

View File

@@ -20,6 +20,7 @@ interface IFlowCookieState {
loading: boolean; loading: boolean;
result?: IFlowCookieAuthResponse; result?: IFlowCookieAuthResponse;
error?: string; error?: string;
errorType?: 'error' | 'warning';
} }
const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabelKey: string }[] = [ const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabelKey: string }[] = [
@@ -122,18 +123,35 @@ export function OAuthPage() {
showNotification(t('auth_login.iflow_cookie_required'), 'warning'); showNotification(t('auth_login.iflow_cookie_required'), 'warning');
return; return;
} }
setIflowCookie((prev) => ({ ...prev, loading: true, error: undefined, result: undefined })); setIflowCookie((prev) => ({
...prev,
loading: true,
error: undefined,
errorType: undefined,
result: undefined
}));
try { try {
const res = await oauthApi.iflowCookieAuth(cookie); const res = await oauthApi.iflowCookieAuth(cookie);
if (res.status === 'ok') { if (res.status === 'ok') {
setIflowCookie((prev) => ({ ...prev, loading: false, result: res })); setIflowCookie((prev) => ({ ...prev, loading: false, result: res }));
showNotification(t('auth_login.iflow_cookie_status_success'), 'success'); showNotification(t('auth_login.iflow_cookie_status_success'), 'success');
} else { } else {
setIflowCookie((prev) => ({ ...prev, loading: false, error: res.error })); setIflowCookie((prev) => ({
...prev,
loading: false,
error: res.error,
errorType: 'error'
}));
showNotification(`${t('auth_login.iflow_cookie_status_error')} ${res.error || ''}`, 'error'); showNotification(`${t('auth_login.iflow_cookie_status_error')} ${res.error || ''}`, 'error');
} }
} catch (err: any) { } catch (err: any) {
setIflowCookie((prev) => ({ ...prev, loading: false, error: err?.message })); if (err?.status === 409) {
const message = t('auth_login.iflow_cookie_config_duplicate');
setIflowCookie((prev) => ({ ...prev, loading: false, error: message, errorType: 'warning' }));
showNotification(message, 'warning');
return;
}
setIflowCookie((prev) => ({ ...prev, loading: false, error: err?.message, errorType: 'error' }));
showNotification(`${t('auth_login.iflow_cookie_start_error')} ${err?.message || ''}`, 'error'); showNotification(`${t('auth_login.iflow_cookie_start_error')} ${err?.message || ''}`, 'error');
} }
}; };
@@ -185,15 +203,13 @@ export function OAuthPage() {
</div> </div>
</div> </div>
)} )}
{!isDisabled && ( {!isDisabled && state.status && state.status !== 'idle' && (
<div className="status-badge" style={{ marginTop: 8 }}> <div className="status-badge" style={{ marginTop: 8 }}>
{state.status === 'success' {state.status === 'success'
? t('auth_login.codex_oauth_status_success') ? t('auth_login.codex_oauth_status_success')
: state.status === 'error' : state.status === 'error'
? `${t('auth_login.codex_oauth_status_error')} ${state.error || ''}` ? `${t('auth_login.codex_oauth_status_error')} ${state.error || ''}`
: state.status === 'waiting' : t('auth_login.codex_oauth_status_waiting')}
? t('auth_login.codex_oauth_status_waiting')
: t('common.info')}
</div> </div>
)} )}
</Card> </Card>
@@ -211,6 +227,9 @@ export function OAuthPage() {
} }
> >
<div className="hint">{t('auth_login.iflow_cookie_hint')}</div> <div className="hint">{t('auth_login.iflow_cookie_hint')}</div>
<div className="hint" style={{ marginTop: 4 }}>
{t('auth_login.iflow_cookie_key_hint')}
</div>
<div className="form-item" style={{ marginTop: 12 }}> <div className="form-item" style={{ marginTop: 12 }}>
<label className="label">{t('auth_login.iflow_cookie_label')}</label> <label className="label">{t('auth_login.iflow_cookie_label')}</label>
<Input <Input
@@ -220,8 +239,14 @@ export function OAuthPage() {
/> />
</div> </div>
{iflowCookie.error && ( {iflowCookie.error && (
<div className="status-badge error" style={{ marginTop: 8 }}> <div
{t('auth_login.iflow_cookie_status_error')} {iflowCookie.error} className={`status-badge ${iflowCookie.errorType === 'warning' ? 'warning' : 'error'}`}
style={{ marginTop: 8 }}
>
{iflowCookie.errorType === 'warning'
? t('auth_login.iflow_cookie_status_duplicate')
: t('auth_login.iflow_cookie_status_error')}{' '}
{iflowCookie.error}
</div> </div>
)} )}
{iflowCookie.result && iflowCookie.result.status === 'ok' && ( {iflowCookie.result && iflowCookie.result.status === 'ok' && (

View File

@@ -407,7 +407,7 @@
.item-actions { .item-actions {
display: flex; display: flex;
gap: $spacing-sm; gap: $spacing-md;
} }
} }