Trae Bot commited on
Commit
98277cb
·
1 Parent(s): 8336f26

Fix .gitignore excluding frontend/src/lib and add missing TS modules

Browse files
.gitignore CHANGED
@@ -8,7 +8,7 @@ dist/
8
  downloads/
9
  eggs/
10
  .eggs/
11
- lib/
12
  lib64/
13
  parts/
14
  sdist/
 
8
  downloads/
9
  eggs/
10
  .eggs/
11
+ # lib/
12
  lib64/
13
  parts/
14
  sdist/
frontend/src/lib/api.ts ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios, { AxiosError, type AxiosRequestConfig } from 'axios'
2
+
3
+ export type ApiError = {
4
+ status: number
5
+ message: string
6
+ data?: unknown
7
+ }
8
+
9
+ export type ApiResponse<T> = {
10
+ code: number
11
+ msg: string
12
+ data?: T | null
13
+ }
14
+
15
+ const defaultBaseUrl = 'http://localhost:8000/api/v1'
16
+ const baseURL = import.meta.env.VITE_API_BASE_URL || defaultBaseUrl
17
+
18
+ export const http = axios.create({
19
+ baseURL,
20
+ timeout: 30000,
21
+ })
22
+
23
+ function normalizePath(path: string) {
24
+ if (path.startsWith('http://') || path.startsWith('https://')) return path
25
+ return path.replace(/^\/+/, '')
26
+ }
27
+
28
+ function extractWrappedErrorMessage(data: unknown) {
29
+ if (!data || typeof data !== 'object') return undefined
30
+ const obj = data as Record<string, unknown>
31
+ const msg = obj.msg
32
+ const code = obj.code
33
+ if (typeof msg === 'string' && typeof code === 'number') return `API ${code}: ${msg}`
34
+ if (typeof msg === 'string') return msg
35
+ return undefined
36
+ }
37
+
38
+ function toApiError(error: unknown): ApiError {
39
+ if (axios.isAxiosError(error)) {
40
+ const axiosError = error as AxiosError
41
+ const status = axiosError.response?.status ?? 0
42
+ const wrappedMsg = extractWrappedErrorMessage(axiosError.response?.data)
43
+ const message =
44
+ wrappedMsg ||
45
+ (typeof axiosError.response?.data === 'string'
46
+ ? axiosError.response.data
47
+ : axiosError.message)
48
+
49
+ return {
50
+ status,
51
+ message,
52
+ data: axiosError.response?.data,
53
+ }
54
+ }
55
+
56
+ return {
57
+ status: 0,
58
+ message: error instanceof Error ? error.message : String(error),
59
+ }
60
+ }
61
+
62
+ export async function apiGet<T>(path: string, config?: AxiosRequestConfig) {
63
+ try {
64
+ const res = await http.get<T>(normalizePath(path), config)
65
+ return res.data
66
+ } catch (error) {
67
+ throw toApiError(error)
68
+ }
69
+ }
70
+
71
+ export function unwrapApiResponse<T>(value: unknown) {
72
+ if (!value || typeof value !== 'object') return { ok: false as const, error: '响应不是对象' }
73
+ const obj = value as Record<string, unknown>
74
+ if (typeof obj.code !== 'number' || typeof obj.msg !== 'string') {
75
+ return { ok: false as const, error: '响应缺少 code/msg 字段' }
76
+ }
77
+ const code = obj.code
78
+ const msg = obj.msg
79
+ const data = obj.data as T | null | undefined
80
+ if (code !== 200) {
81
+ return { ok: false as const, error: `API ${code}: ${msg}`, code, msg, data }
82
+ }
83
+ return { ok: true as const, data }
84
+ }
85
+
86
+ export async function apiGetWrapped<T>(path: string, config?: AxiosRequestConfig) {
87
+ const raw = await apiGet<unknown>(path, config)
88
+ const parsed = unwrapApiResponse<T>(raw)
89
+ if (parsed.ok) return parsed.data as T
90
+ if (parsed.error === '响应缺少 code/msg 字段') return raw as T
91
+ const apiError: ApiError = {
92
+ status: 0,
93
+ message: parsed.error,
94
+ data: raw,
95
+ }
96
+ throw apiError
97
+ }
98
+
99
+ export async function apiPost<TRes, TReq>(
100
+ path: string,
101
+ data: TReq,
102
+ config?: AxiosRequestConfig,
103
+ ) {
104
+ try {
105
+ const res = await http.post<TRes>(normalizePath(path), data, config)
106
+ return res.data
107
+ } catch (error) {
108
+ throw toApiError(error)
109
+ }
110
+ }
111
+
112
+ export async function apiPostWrapped<TRes, TReq>(
113
+ path: string,
114
+ data: TReq,
115
+ config?: AxiosRequestConfig,
116
+ ) {
117
+ const raw = await apiPost<unknown, TReq>(path, data, config)
118
+ const parsed = unwrapApiResponse<TRes>(raw)
119
+ if (parsed.ok) return parsed.data as TRes
120
+ if (parsed.error === '响应缺少 code/msg 字段') return raw as TRes
121
+ const apiError: ApiError = {
122
+ status: 0,
123
+ message: parsed.error,
124
+ data: raw,
125
+ }
126
+ throw apiError
127
+ }
128
+
129
+ export async function apiGetText(path: string, config?: AxiosRequestConfig) {
130
+ try {
131
+ const res = await http.get<string>(normalizePath(path), {
132
+ ...config,
133
+ responseType: 'text',
134
+ })
135
+ return res.data
136
+ } catch (error) {
137
+ throw toApiError(error)
138
+ }
139
+ }
frontend/src/lib/business.ts ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { apiGetWrapped } from './api'
2
+
3
+ export type GeneratedPostRecord = {
4
+ id: number
5
+ material_id: number | null
6
+ prompt_id: number | null
7
+ content: string | null
8
+ status: string | null
9
+ created_at: string | null
10
+ compliance_status: string | null
11
+ medical_risk_level: string | null
12
+ review_status: string | null
13
+ hit_words: string | null
14
+ }
15
+
16
+ export type GeneratedPostListResponse = {
17
+ posts: GeneratedPostRecord[]
18
+ total: number
19
+ limit: number
20
+ offset: number
21
+ }
22
+
23
+ export type LeadRecord = {
24
+ id: number
25
+ interaction_id: number | null
26
+ contact_info: string | null
27
+ status: string | null
28
+ created_at: string | null
29
+ last_sync_at: string | null
30
+ }
31
+
32
+ export type LeadListResponse = {
33
+ leads: LeadRecord[]
34
+ total: number
35
+ limit: number
36
+ offset: number
37
+ }
38
+
39
+ export async function listGeneratedPosts(params: { limit: number; offset: number; status?: string }) {
40
+ const search = new URLSearchParams()
41
+ search.set('limit', String(params.limit))
42
+ search.set('offset', String(params.offset))
43
+ if (params.status) search.set('status', params.status)
44
+ return apiGetWrapped<GeneratedPostListResponse>(`business/posts?${search.toString()}`)
45
+ }
46
+
47
+ export async function listLeads(params: { limit: number; offset: number; status?: string }) {
48
+ const search = new URLSearchParams()
49
+ search.set('limit', String(params.limit))
50
+ search.set('offset', String(params.offset))
51
+ if (params.status) search.set('status', params.status)
52
+ return apiGetWrapped<LeadListResponse>(`business/leads?${search.toString()}`)
53
+ }
frontend/src/lib/content.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ApiError } from './api'
2
+ import { apiGetWrapped } from './api'
3
+
4
+ export type RawNoteRecord = {
5
+ id: number
6
+ source_platform: string | null
7
+ content: string | null
8
+ author: string | null
9
+ url: string | null
10
+ created_at: string | null
11
+ }
12
+
13
+ export type RawNoteListResponse = {
14
+ notes: RawNoteRecord[]
15
+ total: number
16
+ limit: number
17
+ offset: number
18
+ query: string | null
19
+ }
20
+
21
+ export type CleanedNoteRecord = {
22
+ id: number
23
+ raw_note_id: number | null
24
+ cleaned_content: string | null
25
+ created_at: string | null
26
+ raw_author: string | null
27
+ raw_url: string | null
28
+ }
29
+
30
+ export type CleanedNoteListResponse = {
31
+ notes: CleanedNoteRecord[]
32
+ total: number
33
+ limit: number
34
+ offset: number
35
+ query: string | null
36
+ }
37
+
38
+ export type ListContentParams = {
39
+ limit: number
40
+ offset: number
41
+ query?: string
42
+ }
43
+
44
+ function buildSearch(params: ListContentParams) {
45
+ const search = new URLSearchParams()
46
+ search.set('limit', String(params.limit))
47
+ search.set('offset', String(params.offset))
48
+ const q = String(params.query || '').trim()
49
+ if (q) search.set('query', q)
50
+ return search.toString()
51
+ }
52
+
53
+ export async function listRawNotes(params: ListContentParams) {
54
+ return apiGetWrapped<RawNoteListResponse>(`content/raw-notes?${buildSearch(params)}`)
55
+ }
56
+
57
+ export async function listCleanedNotes(params: ListContentParams) {
58
+ return apiGetWrapped<CleanedNoteListResponse>(`content/cleaned-notes?${buildSearch(params)}`)
59
+ }
60
+
61
+ export function isOrchestratorDbUnavailable(error: ApiError | null | undefined) {
62
+ if (!error) return false
63
+ if (error.status !== 503) return false
64
+ if (typeof error.message === 'string' && error.message.toLowerCase().includes('orchestrator db')) return true
65
+ const data = error.data
66
+ if (!data || typeof data !== 'object') return false
67
+ const obj = data as Record<string, unknown>
68
+ return obj.code === 10010 || obj.msg === 'orchestrator db unavailable'
69
+ }
frontend/src/lib/errors.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { apiGetWrapped } from './api'
2
+ import type { TaskRecord } from './tasks'
3
+
4
+ export type ErrorSummaryResponse = {
5
+ scan_limit: number
6
+ scanned: number
7
+ error_kind_counts: Record<string, number>
8
+ tasks: TaskRecord[]
9
+ total: number
10
+ limit: number
11
+ offset: number
12
+ }
13
+
14
+ export type ErrorSummaryParams = {
15
+ scan_limit?: number
16
+ limit: number
17
+ offset: number
18
+ status?: string[]
19
+ error_kind?: string[]
20
+ }
21
+
22
+ function join(values?: string[]) {
23
+ const parts = (values || []).map((v) => String(v).trim()).filter((v) => v !== '')
24
+ return parts.length ? parts.join(',') : undefined
25
+ }
26
+
27
+ export async function getErrorSummary(params: ErrorSummaryParams) {
28
+ const search = new URLSearchParams()
29
+ search.set('limit', String(params.limit))
30
+ search.set('offset', String(params.offset))
31
+
32
+ if (typeof params.scan_limit === 'number' && Number.isFinite(params.scan_limit) && params.scan_limit > 0) {
33
+ search.set('scan_limit', String(Math.floor(params.scan_limit)))
34
+ }
35
+
36
+ const status = join(params.status)
37
+ const errorKind = join(params.error_kind)
38
+ if (status) search.set('status', status)
39
+ if (errorKind) search.set('error_kind', errorKind)
40
+
41
+ return apiGetWrapped<ErrorSummaryResponse>(`errors/summary?${search.toString()}`)
42
+ }
frontend/src/lib/polling.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type PollOptions<T> = {
2
+ intervalMs: number
3
+ immediate?: boolean
4
+ shouldStop?: (value: T) => boolean
5
+ signal?: AbortSignal
6
+ }
7
+
8
+ function sleep(ms: number, signal?: AbortSignal) {
9
+ if (ms <= 0) return Promise.resolve()
10
+ return new Promise<void>((resolve, reject) => {
11
+ const timer = window.setTimeout(() => {
12
+ signal?.removeEventListener('abort', onAbort)
13
+ resolve()
14
+ }, ms)
15
+
16
+ function onAbort() {
17
+ window.clearTimeout(timer)
18
+ reject(new DOMException('Aborted', 'AbortError'))
19
+ }
20
+
21
+ if (signal) {
22
+ if (signal.aborted) return onAbort()
23
+ signal.addEventListener('abort', onAbort, { once: true })
24
+ }
25
+ })
26
+ }
27
+
28
+ export async function poll<T>(fn: () => Promise<T>, options: PollOptions<T>) {
29
+ const { intervalMs, immediate = true, shouldStop, signal } = options
30
+
31
+ if (!immediate) await sleep(intervalMs, signal)
32
+
33
+ while (true) {
34
+ if (signal?.aborted) throw new DOMException('Aborted', 'AbortError')
35
+ const value = await fn()
36
+ if (shouldStop?.(value)) return value
37
+ await sleep(intervalMs, signal)
38
+ }
39
+ }
frontend/src/lib/resources.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { apiGetWrapped, apiPostWrapped } from './api'
2
+
3
+ export type AccountSnapshotItem = {
4
+ id: string
5
+ tags: string[]
6
+ risk_score: number
7
+ enabled: boolean
8
+ available: boolean
9
+ cooldown_remaining_s: number
10
+ cooldown_until: string | null
11
+ last_error_kind: string | null
12
+ last_error_at: string | null
13
+ }
14
+
15
+ export type AccountPoolSnapshotResponse = {
16
+ now: string
17
+ accounts: AccountSnapshotItem[]
18
+ seed: number
19
+ }
20
+
21
+ export type SessionLightReport = {
22
+ id: string
23
+ account_id: string | null
24
+ has_cookie: boolean
25
+ has_storage_state: boolean
26
+ cookie_ok: boolean
27
+ storage_state_ok: boolean
28
+ cookie_reason: string | null
29
+ storage_state_reason: string | null
30
+ checked_at: string
31
+ }
32
+
33
+ export type SessionPoolLightCheckResponse = {
34
+ now: string
35
+ sessions: SessionLightReport[]
36
+ }
37
+
38
+ export type ProxyPoolSnapshotResponse = {
39
+ available_count: number
40
+ avg_score: number
41
+ ejected_total: number
42
+ failures_total_by_reason: Record<string, number>
43
+ recent_failures_by_reason: Record<string, number>
44
+ last_fail_reasons_by_reason: Record<string, number>
45
+ }
46
+
47
+ export async function getResourceAccounts() {
48
+ return apiGetWrapped<AccountPoolSnapshotResponse>('resources/accounts')
49
+ }
50
+
51
+ export async function getResourceSessions() {
52
+ return apiGetWrapped<SessionPoolLightCheckResponse>('resources/sessions')
53
+ }
54
+
55
+ export async function getResourceProxies() {
56
+ return apiGetWrapped<ProxyPoolSnapshotResponse>('resources/proxies')
57
+ }
58
+
59
+ export async function cooldownAccount(accountId: string, seconds: number) {
60
+ return apiPostWrapped<{ account_id: string, cooldown_seconds: number }, { seconds: number }>(
61
+ `resources/accounts/${encodeURIComponent(accountId)}/cooldown`,
62
+ { seconds }
63
+ )
64
+ }
65
+
66
+ export async function disableAccount(accountId: string) {
67
+ return apiPostWrapped<{ account_id: string, disabled: boolean }, {}>(
68
+ `resources/accounts/${encodeURIComponent(accountId)}/disable`,
69
+ {}
70
+ )
71
+ }
72
+
frontend/src/lib/tasks.ts ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios'
2
+ import type { ApiError } from './api'
3
+ import { apiGetWrapped, apiPostWrapped, http, unwrapApiResponse } from './api'
4
+
5
+ export type TaskStatus =
6
+ | 'queued'
7
+ | 'running'
8
+ | 'retrying'
9
+ | 'fallback_running'
10
+ | 'waiting_rpa'
11
+ | 'rpa_running'
12
+ | 'rpa_imported'
13
+ | 'rpa_failed'
14
+ | 'risk_paused'
15
+ | 'succeeded'
16
+ | 'failed'
17
+
18
+ export type TaskError = {
19
+ kind?: string
20
+ message?: string
21
+ [key: string]: unknown
22
+ }
23
+
24
+ export type TaskRecord = {
25
+ id: string
26
+ status: TaskStatus
27
+ task_type: string
28
+ target: string
29
+ payload: Record<string, unknown>
30
+ engine?: string | null
31
+ callback?: unknown
32
+ created: number
33
+ started?: number | null
34
+ finished?: number | null
35
+ retry_count: number
36
+ error?: TaskError | null
37
+ }
38
+
39
+ export type TaskListResponse = {
40
+ tasks: TaskRecord[]
41
+ total: number
42
+ limit: number
43
+ offset: number
44
+ }
45
+
46
+ export type TaskStatusResponse = {
47
+ task: TaskRecord
48
+ }
49
+
50
+ export type TaskCreateRequest = {
51
+ task_type: string
52
+ target?: string | null
53
+ engine?: string | null
54
+ payload?: Record<string, unknown>
55
+ }
56
+
57
+ export type TaskCreateResponse = {
58
+ task: TaskRecord
59
+ }
60
+
61
+ export type TaskResultResponse = {
62
+ task_id: string
63
+ status: TaskStatus
64
+ raw: unknown | null
65
+ normalized: unknown | null
66
+ meta: Record<string, unknown>
67
+ }
68
+
69
+ export type ListTasksParams = {
70
+ limit: number
71
+ offset: number
72
+ status?: string[]
73
+ task_type?: string[]
74
+ engine?: string[]
75
+ error_kind?: string[]
76
+ sort?: string
77
+ }
78
+
79
+ function normalizePath(path: string) {
80
+ return path.replace(/^\/+/, '')
81
+ }
82
+
83
+ function toApiError(error: unknown): ApiError {
84
+ if (axios.isAxiosError(error)) {
85
+ if (!error.response) {
86
+ return { status: 0, message: error.message }
87
+ }
88
+ const status = error.response?.status ?? 0
89
+ const body = error.response?.data
90
+ const wrapped = unwrapApiResponse<unknown>(body)
91
+ const message =
92
+ wrapped.ok || wrapped.error === '响应缺少 code/msg 字段'
93
+ ? error.message
94
+ : wrapped.error
95
+ return { status, message, data: body }
96
+ }
97
+
98
+ return {
99
+ status: 0,
100
+ message: error instanceof Error ? error.message : String(error),
101
+ }
102
+ }
103
+
104
+ function join(values?: string[]) {
105
+ const parts = (values || []).map((v) => String(v).trim()).filter((v) => v !== '')
106
+ return parts.length ? parts.join(',') : undefined
107
+ }
108
+
109
+ export async function listTasks(params: ListTasksParams) {
110
+ const search = new URLSearchParams()
111
+ search.set('limit', String(params.limit))
112
+ search.set('offset', String(params.offset))
113
+ const status = join(params.status)
114
+ const taskType = join(params.task_type)
115
+ const engine = join(params.engine)
116
+ const errorKind = join(params.error_kind)
117
+ const sort = String(params.sort || '').trim()
118
+
119
+ if (status) search.set('status', status)
120
+ if (taskType) search.set('task_type', taskType)
121
+ if (engine) search.set('engine', engine)
122
+ if (errorKind) search.set('error_kind', errorKind)
123
+ if (sort) search.set('sort', sort)
124
+
125
+ return apiGetWrapped<TaskListResponse>(`tasks?${search.toString()}`)
126
+ }
127
+
128
+ export async function createTask(payload: TaskCreateRequest) {
129
+ const res = await apiPostWrapped<TaskCreateResponse, TaskCreateRequest>('tasks', payload)
130
+ return res.task
131
+ }
132
+
133
+ export async function retryTask(taskId: string) {
134
+ const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/retry`, {})
135
+ return res.task
136
+ }
137
+
138
+ export async function cancelTask(taskId: string) {
139
+ const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/cancel`, {})
140
+ return res.task
141
+ }
142
+
143
+ export async function markTaskRpa(taskId: string) {
144
+ const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/mark-rpa`, {})
145
+ return res.task
146
+ }
147
+
148
+ export async function getTask(taskId: string) {
149
+ const res = await apiGetWrapped<TaskStatusResponse>(`tasks/${encodeURIComponent(taskId)}`)
150
+ return res.task
151
+ }
152
+
153
+ export async function getTaskResult(taskId: string, signal?: AbortSignal) {
154
+ try {
155
+ const res = await http.get<unknown>(normalizePath(`tasks/${encodeURIComponent(taskId)}/result`), {
156
+ validateStatus: () => true,
157
+ signal,
158
+ })
159
+ if (res.status === 409) return { ready: false as const, body: res.data }
160
+ if (res.status !== 200) {
161
+ const wrapped = unwrapApiResponse<unknown>(res.data)
162
+ const message =
163
+ wrapped.ok || wrapped.error === '响应缺少 code/msg 字段'
164
+ ? `HTTP ${res.status}`
165
+ : wrapped.error
166
+ const apiError: ApiError = { status: res.status, message, data: res.data }
167
+ throw apiError
168
+ }
169
+
170
+ const wrapped = unwrapApiResponse<TaskResultResponse>(res.data)
171
+ if (!wrapped.ok) {
172
+ const apiError: ApiError = { status: res.status, message: wrapped.error, data: res.data }
173
+ throw apiError
174
+ }
175
+
176
+ return { ready: true as const, data: wrapped.data ?? null }
177
+ } catch (e) {
178
+ if (axios.isAxiosError(e) && e.code === 'ERR_CANCELED') {
179
+ throw new DOMException('Aborted', 'AbortError')
180
+ }
181
+ if (
182
+ !!e &&
183
+ typeof e === 'object' &&
184
+ 'status' in e &&
185
+ typeof (e as { status: unknown }).status === 'number' &&
186
+ 'message' in e &&
187
+ typeof (e as { message: unknown }).message === 'string'
188
+ ) {
189
+ throw e as ApiError
190
+ }
191
+ throw toApiError(e)
192
+ }
193
+ }