feat(web): 登录请求无响应时提示检查网络

This commit is contained in:
lsdsjy committed 2026-09-30 17:23:16 +08:00
1 parent 08f0721e9d
commit f3168f2be4
18 files changed
+55 -16

No files matched your search

@@ -9,8 +9,8 @@
en: 9ff7ac2d70f70b59
zh: ded522c4cfd6e040
/deepseek-ai-dsh-client-ui-settings-account/summary:
en: 9ff357621753be69
zh: 3f7ede82acc9fa31
en: 9578b8d11eb138a1
zh: d031db76109178ad
/deepseek-ai-dsh-client-ui-settings-account/table-of-contents:
en: c26f81e8adf0128b
zh: e11ab371f51f66bc
@@ -13,6 +13,8 @@ Server-expired account credentials clear the displayed account details and emit
## Summary
The sign-in dialog offers network troubleshooting only for `no-response` failures. HTTP, business, response-validation, storage, and account-stream failures retain the generic retry message.
The Account settings section displays DeepSeek login state and offers browser sign-in and cancellation; the sidebar account menu provides Platform sign-out. Desktop users also receive a resumable introduction to account credit and presentation preferences.
## Table of Contents
@@ -13,6 +13,8 @@ kind: "package-reference"
## 概述
登录弹窗仅对 `no-response` 失败提示检查网络。HTTP、业务、响应校验、存储和账号状态流失败仍显示通用重试提示。
设置中的账号页面显示 DeepSeek 登录状态,并提供浏览器登录和取消;侧边栏账号菜单提供 Platform 退出登录。Desktop 用户还会进入可恢复的引导,了解账号额度并选择展示偏好。
## 目录
@@ -33,6 +33,8 @@ export function SignInDialog({ account, colorScheme, start, cancel, close, useAp
const active = busy || phase === 'initializing' || phase === 'waiting-browser' || phase === 'exchanging' || phase === 'committing'
const expired = phase === 'expired'
const error = failed || account.loginFailed || account.failed || phase === 'failed'
const failureKey = !failed && !account.loginFailed && !account.failed
&& phase === 'failed' && attempt?.errorCode === 'no-response' ? 'noResponse' : 'failed'
const waiting = active && !error
const committing = phase === 'committing'
useEffect(() => { if (account.view?.status === 'credential-stored') close() }, [account.view?.status, close])
@@ -71,7 +73,7 @@ export function SignInDialog({ account, colorScheme, start, cancel, close, useAp
{t(copyResult?.messageKey ?? 'copyLink')}
</button>{t('browserDescription')}
</p> : <p className={css.description}>
{error ? t('failed') : expired ? t('timeoutDescription') : t('loginDescription')}
{error ? t(failureKey) : expired ? t('timeoutDescription') : t('loginDescription')}
</p>}
</div>
<div className={css.actions}>
@@ -23,6 +23,7 @@ export const en = {
initializing: 'Starting sign in…', waiting: 'Continue in your browser',
completing: 'Completing sign in…', expired: 'Sign in expired. Try again.',
failed: 'Could not complete the operation. Try again.',
noResponse: 'Something went wrong. Please check your network connection and try again.',
settingsSignedOutTitle: 'You are not signed in to DeepSeek Harness',
settingsSignedOutDescription: 'Sign in to DeepSeek Harness to get your dedicated API Key',
signInDescription: 'Use your DeepSeek account to get started.',
@@ -58,6 +59,7 @@ export const zh: Record<AccountKey, string> = {
initializing: '正在发起登录…', waiting: '请在浏览器中继续',
completing: '正在完成登录…', expired: '登录已过期,请重试。',
failed: '操作未完成,请重试。',
noResponse: '操作未完成,可能是网络异常,请检查你的网络状况后重试',
settingsSignedOutTitle: '当前未登录 DeepSeek Harness 账号',
settingsSignedOutDescription: '登录 DeepSeek Harness 账号获取专属 API Key',
signInDescription: '登录 DeepSeek 账号以开始使用',
@@ -0,0 +1 @@
Could not sign inSomething went wrong. Please check your network connection and try again.Add API KeySign in again
@@ -0,0 +1 @@
登录失败操作未完成,可能是网络异常,请检查你的网络状况后重试添加 API Key重新登录
@@ -191,3 +191,24 @@ it('does not report workspace login views or login choices', async () => {
expect(props.useApiKey).toHaveBeenCalledOnce()
expect(track).not.toHaveBeenCalled()
})
it.each([en, zh])('shows network advice only when the login request received no response', async (copy) => {
const props = mount({ id, phase: 'failed', errorCode: 'no-response' }, copy)
await expect(`${screen.getByRole('dialog').textContent}\n`).toMatchFileSnapshot(`./expected/login-no-response-${copy === en ? 'en' : 'zh'}.txt`)
await act(async () => { fireEvent.click(screen.getByRole('button', { name: copy.retry })) })
expect(props.start).toHaveBeenCalledOnce()
})
it.each(['network', 'protocol', 'storage'] as const)('keeps generic copy for %s failures', (errorCode) => {
mount({ id, phase: 'failed', errorCode })
expect(screen.getByText(en.failed)).toBeTruthy()
expect(screen.queryByText(en.noResponse)).toBeNull()
})
it('does not reuse network advice for a subsequent start failure', async () => {
const props = mount({ id, phase: 'failed', errorCode: 'no-response' })
props.start.mockRejectedValueOnce(new Error('RPC failed'))
await act(async () => { fireEvent.click(screen.getByRole('button', { name: en.retry })) })
expect(screen.getByText(en.failed)).toBeTruthy()
expect(screen.queryByText(en.noResponse)).toBeNull()
})
@@ -9,8 +9,8 @@
en: 7be860d52e1202db
zh: a2612757253d67a6
/deepseek-ai-dsh-deepseek-account-platform/summary:
en: ce3da383e4b065d8
zh: 56d68ad8ee1c27a8
en: 64ed24b5cac74d92
zh: ca9deb0d61b500db
/deepseek-ai-dsh-deepseek-account-platform/table-of-contents:
en: af856ba79fe31b8e
zh: 15b7d1a5d1856de2
@@ -17,6 +17,8 @@ Profile and balance HTTP 401 responses or top-level response code `40003` (inval
## Summary
Platform requests report `no-response` when fetch rejects before returning a Response, including request timeouts. HTTP errors and response-body failures do not use this code; authorization-attempt expiry retains `expired`.
Sign in through the system browser and keep the account credential in the existing local credential store. Local cancellation prevents late callbacks and exchange responses from signing the user in.
Device identity reads validate the existing login device record and expose only its ID, the current account ID, and the shared login OS version string.
@@ -17,6 +17,8 @@ getPlatformSession 仅在已存授权的 issuer 与 platformOrigin 一致时导
## 概述
当 fetch 在返回 Response 前拒绝(包括单次请求超时)时,Platform 请求报告 `no-response`。HTTP 错误和响应体失败不使用此代码;授权尝试到期仍使用 `expired`。
通过系统浏览器登录,并将账号凭证保存在现有本地凭证存储中。本地取消会阻止迟到的回调和兑换响应使用户登录。
设备身份读取会校验已有登录设备记录,仅提供设备 ID、当前账户 ID 和与登录共用的操作系统版本字符串。
@@ -1,11 +1,11 @@
/** Validated platform HTTP messages and restricted browser destinations. */
import { z } from 'zod'
import type { AccountBonusOrderId } from '@deepseek-ai/dsh-deepseek-account/types'
import type { AccountBonusOrderId, SignInErrorCode } from '@deepseek-ai/dsh-deepseek-account/types'
/** Protocol errors expose a stable code, never a response body or authorization URL. */
export class PlatformAuthError extends Error {
/** @param code - safe error classification. */
constructor(readonly code: 'network' | 'protocol' | 'expired' | 'storage') { super(`account: ${code}`) }
constructor(readonly code: SignInErrorCode) { super(`account: ${code}`) }
}
/** An authenticated Platform request was rejected with HTTP 401 or code 40003. */
@@ -170,8 +170,8 @@ async function platformRequest(url: string, init: RequestInit, signal: AbortSign
try {
response = await fetch(url, { ...init, redirect: 'error', signal })
} catch {
console.info('[deepseek-account] request failed', { path, errorCode: 'network', aborted: signal.aborted })
throw new PlatformAuthError('network')
console.info('[deepseek-account] request failed', { path, errorCode: 'no-response', aborted: signal.aborted })
throw new PlatformAuthError('no-response')
}
console.info('[deepseek-account] response', { path, status: response.status })
if (response.status === 401 && new Headers(init.headers).has('x-dsh-auth-token')) {
@@ -44,9 +44,9 @@ it('reports transport failure without exposing the thrown error', async () => {
const fetcher = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('private-network-detail'))
try {
await expect(requestPlatform('https://platform.deepseek.com', 'auth_init', {},
new AbortController().signal, {})).rejects.toThrow('account: network')
new AbortController().signal, {})).rejects.toThrow('account: no-response')
const logged = JSON.stringify(output.mock.calls)
expect(logged).toContain('network')
expect(logged).toContain('no-response')
expect(logged).not.toContain('private-network-detail')
} finally {
fetcher.mockRestore()
@@ -9,8 +9,8 @@
en: fcacd6de573d7636
zh: 6dbbc05a66dbb126
/deepseek-ai-dsh-deepseek-account/summary:
en: c41643b81a07a489
zh: 2c16999de40a67b9
en: 8e8365fea2a07a9f
zh: c8eef466dd505ca2
/deepseek-ai-dsh-deepseek-account/table-of-contents:
en: af856ba79fe31b8e
zh: 15b7d1a5d1856de2
@@ -19,6 +19,8 @@ getUnnotifiedBonuses returns the granted bonuses Platform has not yet recorded a
## Summary
`SignInErrorCode` distinguishes `no-response` (fetch returned no Response) from HTTP and response-validation failures so clients can offer network troubleshooting.
Account consumers read stored login state, start or cancel a browser login, and sign out without editing API keys. Host model consumers resolve account credentials only for the provider-configured inference origin.
`getDeviceIdentity()` returns existing device and account IDs plus the login OS version string, without credentials or device creation.
@@ -19,6 +19,8 @@ getUnnotifiedBonuses 返回平台尚未记录为已展示的赠金及其所属
## 概述
`SignInErrorCode` 将 `no-response`(fetch 未返回 Response)与 HTTP 和响应校验失败区分开,使客户端可以提示检查网络。
账号使用方可读取本地登录状态、发起或取消浏览器登录,并在保留 API Key 的情况下退出。Host 模型使用方仅能为提供者配置的推理来源解析账号凭证。
`getDeviceIdentity()` 返回已有设备和账户 ID 以及登录使用的操作系统版本字符串,不返回凭据,也不创建设备标识。
@@ -13,8 +13,8 @@ export interface AccountClientMetadata {
/** Identity of one local login attempt, unrelated to the platform request ID. */
export type SignInAttemptId = Branded<'SignInAttemptId'>
/** Safe failure codes rendered through the caller's locale dictionary. */
export type SignInErrorCode = 'network' | 'protocol' | 'expired' | 'storage'
/** Safe failure codes rendered through the caller's locale dictionary; no-response means fetch returned no Response. */
export type SignInErrorCode = 'no-response' | 'network' | 'protocol' | 'expired' | 'storage'
/** Latest login attempt, including terminal outcomes until the next attempt. */
export interface SignInAttemptView {
readonly id: SignInAttemptId
@@ -7039,7 +7039,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'SignInErrorCode',
declaration: 'export type SignInErrorCode = \'network\' | \'protocol\' | \'expired\' | \'storage\';',
declaration: 'export type SignInErrorCode = \'no-response\' | \'network\' | \'protocol\' | \'expired\' | \'storage\';',
},
{
name: 'SkillCandidate',