feat(web): 登录请求无响应时提示检查网络
This commit is contained in:
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',
|
||||
|
||||
Reference in new issue
Block a user