client.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // 统一的 fetch 包装器:自动携带 Token、处理刷新和滑动过期
  2. let tokenRefreshPromise: Promise<string> | null = null
  3. async function doRefreshToken(rt: string): Promise<string> {
  4. tokenRefreshPromise = (async () => {
  5. try {
  6. const res = await fetch('/api/v1/auth/refresh', {
  7. method: 'POST',
  8. headers: { 'Content-Type': 'application/json' },
  9. body: JSON.stringify({ refresh_token: rt }),
  10. })
  11. if (!res.ok) throw new Error('Refresh failed')
  12. const data = await res.json()
  13. const newToken = data.data.token
  14. const newRt = data.data.refresh_token
  15. localStorage.setItem('token', newToken)
  16. localStorage.setItem('refresh_token', newRt)
  17. return newToken
  18. } finally {
  19. tokenRefreshPromise = null
  20. }
  21. })()
  22. return tokenRefreshPromise
  23. }
  24. async function apiFetch(url: string, init?: RequestInit): Promise<Response> {
  25. const headers: Record<string, string> = { ...(init?.headers as Record<string, string> || {}) }
  26. const storedToken = localStorage.getItem('token')
  27. if (storedToken) {
  28. headers['Authorization'] = `Bearer ${storedToken}`
  29. }
  30. // FormData 时不要设 Content-Type
  31. if (!(init?.body instanceof FormData)) {
  32. headers['Content-Type'] = headers['Content-Type'] || 'application/json'
  33. }
  34. let res = await fetch(url, { ...init, headers })
  35. // 滑动过期:检测 X-New-Token 响应头
  36. const newToken = res.headers.get('X-New-Token')
  37. if (newToken) {
  38. localStorage.setItem('token', newToken)
  39. }
  40. // 401 时尝试用 refresh_token 刷新一次
  41. if (res.status === 401 && storedToken) {
  42. const storedRt = localStorage.getItem('refresh_token')
  43. if (storedRt) {
  44. try {
  45. const freshToken = await doRefreshToken(storedRt)
  46. headers['Authorization'] = `Bearer ${freshToken}`
  47. res = await fetch(url, { ...init, headers })
  48. } catch {
  49. localStorage.removeItem('token')
  50. localStorage.removeItem('refresh_token')
  51. localStorage.removeItem('user')
  52. window.location.href = '/login'
  53. }
  54. }
  55. }
  56. if (!res.ok) {
  57. try {
  58. const err = await res.json()
  59. throw new Error(err.detail || err.error || err.message || `Request failed: ${res.status}`)
  60. } catch (e) {
  61. if (e instanceof Error) throw e
  62. throw new Error(`Request failed: ${res.status}`)
  63. }
  64. }
  65. return res
  66. }
  67. const api = {
  68. // --- Health ---
  69. health: () => apiFetch('/health').then(r => r.json()),
  70. // --- Models ---
  71. models: {
  72. list: () => apiFetch('/api/v1/models/').then(r => r.json()) as Promise<ModelInfo[]>,
  73. download: (modelId: string, useModelscope = false) =>
  74. apiFetch('/api/v1/models/download', {
  75. method: 'POST',
  76. headers: { 'Content-Type': 'application/json' },
  77. body: JSON.stringify({ model_id: modelId, use_modelscope: useModelscope }),
  78. }).then(r => r.json()) as Promise<ModelDownloadTaskResponse>,
  79. downloadStatus: (taskId: string) =>
  80. apiFetch(`/api/v1/models/download/${taskId}`).then(r => r.json()) as Promise<ModelDownloadTaskResponse>,
  81. listDownloads: () =>
  82. apiFetch('/api/v1/models/downloads').then(r => r.json()) as Promise<ModelDownloadTaskResponse[]>,
  83. cancelDownload: (taskId: string) =>
  84. apiFetch(`/api/v1/models/download/${taskId}/cancel`, { method: 'POST' }).then(r => r.json()),
  85. delete: (modelId: string) =>
  86. apiFetch(`/api/v1/models/${encodeURIComponent(modelId)}`, { method: 'DELETE' }).then(r => r.json()),
  87. getInfo: (modelId: string) =>
  88. apiFetch(`/api/v1/models/${encodeURIComponent(modelId)}`).then(r => r.json()) as Promise<ModelInfo>,
  89. test: (req: ModelTestRequest) =>
  90. apiFetch('/api/v1/models/test', {
  91. method: 'POST',
  92. headers: { 'Content-Type': 'application/json' },
  93. body: JSON.stringify(req),
  94. }).then(r => r.json()) as Promise<ModelTestResponse>,
  95. },
  96. // --- Datasets ---
  97. datasets: {
  98. list: () => apiFetch('/api/v1/datasets/').then(r => r.json()) as Promise<DatasetInfo[]>,
  99. upload: (file: File) => {
  100. const form = new FormData()
  101. form.append('file', file)
  102. return apiFetch('/api/v1/datasets/upload', { method: 'POST', body: form }).then(r => r.json()) as Promise<DatasetInfo>
  103. },
  104. download: (datasetId: string, useModelscope = false) =>
  105. apiFetch('/api/v1/datasets/download', {
  106. method: 'POST',
  107. headers: { 'Content-Type': 'application/json' },
  108. body: JSON.stringify({ dataset_id: datasetId, use_modelscope: useModelscope }),
  109. }).then(r => r.json()) as Promise<DatasetDownloadTaskResponse>,
  110. downloadStatus: (taskId: string) =>
  111. apiFetch(`/api/v1/datasets/download/${taskId}`).then(r => r.json()) as Promise<DatasetDownloadTaskResponse>,
  112. listDownloads: () =>
  113. apiFetch('/api/v1/datasets/downloads').then(r => r.json()) as Promise<DatasetDownloadTaskResponse[]>,
  114. cancelDownload: (taskId: string) =>
  115. apiFetch(`/api/v1/datasets/download/${taskId}/cancel`, { method: 'POST' }).then(r => r.json()),
  116. preview: (id: string, rows = 10) =>
  117. apiFetch(`/api/v1/datasets/${id}/preview?rows=${rows}`).then(r => r.json()) as Promise<DatasetPreview>,
  118. validate: (id: string) =>
  119. apiFetch(`/api/v1/datasets/${id}/validate`, { method: 'POST' }).then(r => r.json()) as Promise<DatasetValidation>,
  120. delete: (id: string) =>
  121. apiFetch(`/api/v1/datasets/${id}`, { method: 'DELETE' }).then(r => r.json()),
  122. },
  123. // --- Training ---
  124. training: {
  125. list: () => apiFetch('/api/v1/training/jobs').then(r => r.json()) as Promise<TrainingJob[]>,
  126. create: (cfg: TrainingConfig) =>
  127. apiFetch('/api/v1/training/jobs', {
  128. method: 'POST',
  129. headers: { 'Content-Type': 'application/json' },
  130. body: JSON.stringify(cfg),
  131. }).then(r => r.json()) as Promise<TrainingJob>,
  132. get: (id: string) =>
  133. apiFetch(`/api/v1/training/jobs/${id}`).then(r => r.json()) as Promise<TrainingJob>,
  134. cancel: (id: string) =>
  135. apiFetch(`/api/v1/training/jobs/${id}/cancel`, { method: 'POST' }).then(r => r.json()),
  136. },
  137. // --- Evaluation ---
  138. evaluation: {
  139. run: (cfg: EvalConfig) =>
  140. apiFetch('/api/v1/evaluation/run', {
  141. method: 'POST',
  142. headers: { 'Content-Type': 'application/json' },
  143. body: JSON.stringify(cfg),
  144. }).then(r => r.json()) as Promise<EvalResult>,
  145. results: (id: string) =>
  146. apiFetch(`/api/v1/evaluation/${id}/results`).then(r => r.json()) as Promise<EvalResult>,
  147. },
  148. // --- Deployment ---
  149. deployment: {
  150. export: (cfg: DeployConfig) =>
  151. apiFetch('/api/v1/deployment/export', {
  152. method: 'POST',
  153. headers: { 'Content-Type': 'application/json' },
  154. body: JSON.stringify(cfg),
  155. }).then(r => r.json()) as Promise<DeployResponse>,
  156. status: (id: string) =>
  157. apiFetch(`/api/v1/deployment/${id}/status`).then(r => r.json()) as Promise<DeployResponse>,
  158. },
  159. // --- Sample Center ---
  160. sampleCenter: {
  161. listKnowledgeBases: (page = 1, page_size = 20) =>
  162. apiFetch(`/api/v1/sample-center/knowledge-bases?page=${page}&page_size=${page_size}`)
  163. .then(r => r.json()) as Promise<KnowledgeBaseListResponse>,
  164. getKnowledgeBaseDetail: (kb_id: string) =>
  165. apiFetch(`/api/v1/sample-center/knowledge-bases/${kb_id}`)
  166. .then(r => r.json()) as Promise<KnowledgeBaseDetailResponse>,
  167. importFromKnowledgeBase: (kb_id: string, kb_name = '') =>
  168. apiFetch(`/api/v1/sample-center/knowledge-bases/${kb_id}/import?kb_name=${encodeURIComponent(kb_name)}`, {
  169. method: 'POST',
  170. }).then(r => r.json()) as Promise<KbImportResponse>,
  171. },
  172. // --- Inference ---
  173. inference: {
  174. generate: (req: InferenceRequest) =>
  175. apiFetch('/api/v1/inference/generate', {
  176. method: 'POST',
  177. headers: { 'Content-Type': 'application/json' },
  178. body: JSON.stringify(req),
  179. }).then(r => r.json()) as Promise<InferenceResponse>,
  180. adapters: () =>
  181. apiFetch('/api/v1/inference/adapters').then(r => r.json()) as Promise<AdapterInfo[]>,
  182. },
  183. }
  184. export default api
  185. // --- Types ---
  186. interface ModelInfo {
  187. id: string
  188. name: string
  189. model_type: string
  190. path?: string
  191. is_downloaded: boolean
  192. context_length?: number
  193. supported_peft_methods: string[]
  194. }
  195. interface ModelTestRequest {
  196. model_id: string
  197. prompt: string
  198. max_new_tokens?: number
  199. temperature?: number
  200. top_p?: number
  201. }
  202. interface ModelTestResponse {
  203. model_id: string
  204. prompt: string
  205. generated_text: string
  206. error?: string
  207. }
  208. interface ModelDownloadResponse {
  209. model_id: string
  210. status: string
  211. path?: string
  212. error?: string
  213. }
  214. interface ModelDownloadTaskResponse {
  215. task_id: string
  216. model_id: string
  217. status: string
  218. use_modelscope?: boolean
  219. path?: string
  220. error?: string
  221. progress?: number
  222. created_at?: string
  223. }
  224. interface DatasetInfo {
  225. id: string
  226. name: string
  227. format: string
  228. record_count: number
  229. file_path: string
  230. created_at: string
  231. }
  232. interface DatasetDownloadResponse {
  233. dataset_id: string
  234. status: string
  235. path?: string
  236. error?: string
  237. }
  238. interface DatasetDownloadTaskResponse {
  239. task_id: string
  240. dataset_id: string
  241. status: string
  242. use_modelscope?: boolean
  243. path?: string
  244. error?: string
  245. record_count?: number
  246. created_at?: string
  247. }
  248. interface DatasetPreview {
  249. total_records: number
  250. preview_rows: { row_index: number; data: Record<string, unknown> }[]
  251. columns: string[]
  252. }
  253. interface DatasetValidation {
  254. is_valid: boolean
  255. errors?: string[]
  256. warnings?: string[]
  257. }
  258. interface TrainingJob {
  259. id: string
  260. model_id: string
  261. model_type: string
  262. peft_method: string
  263. status: string
  264. progress: number
  265. current_epoch: number
  266. current_step: number
  267. total_steps: number
  268. loss?: number
  269. created_at: string
  270. started_at?: string
  271. finished_at?: string
  272. error_message?: string
  273. adapter_path?: string
  274. }
  275. interface TrainingConfig {
  276. model_id: string
  277. model_type: string
  278. dataset_id: string
  279. peft_method: string
  280. task_type?: string
  281. dataset_template?: string
  282. epochs?: number
  283. batch_size?: number
  284. gradient_accumulation?: number
  285. learning_rate?: number
  286. max_seq_length?: number
  287. warmup_ratio?: number
  288. save_strategy?: string
  289. eval_strategy?: string
  290. eval_steps?: number
  291. lora_r?: number
  292. lora_alpha?: number
  293. lora_dropout?: number
  294. lora_target_modules?: string
  295. qlora_bits?: number
  296. deepspeed?: boolean
  297. num_gpus?: number
  298. }
  299. interface EvalConfig {
  300. job_id: string
  301. test_split_ratio?: number
  302. batch_size?: number
  303. metrics?: string[]
  304. }
  305. interface EvalResult {
  306. id: string
  307. job_id: string
  308. status: string
  309. progress: number
  310. metrics: Record<string, unknown>
  311. error: string | null
  312. created_at: string
  313. }
  314. interface DeployConfig {
  315. job_id: string
  316. merge_with_base?: boolean
  317. export_format?: string
  318. }
  319. interface DeployResponse {
  320. job_id: string
  321. status: string
  322. output_path?: string
  323. error?: string
  324. }
  325. interface AdapterInfo {
  326. id: string
  327. path: string
  328. base_model: string
  329. peft_type: string
  330. model_id?: string
  331. peft_method?: string
  332. task_type?: string
  333. created_at?: string
  334. }
  335. interface InferenceRequest {
  336. adapter_id: string
  337. prompt: string
  338. max_new_tokens?: number
  339. temperature?: number
  340. top_p?: number
  341. repetition_penalty?: number
  342. do_sample?: boolean
  343. }
  344. interface InferenceResponse {
  345. prompt: string
  346. generated_text: string
  347. generated_only: string
  348. tokens_generated: number
  349. error?: string
  350. }
  351. interface MetadataSchemaField {
  352. field_name_cn: string
  353. field_name_en: string
  354. field_type: string
  355. description: string
  356. }
  357. interface KnowledgeBaseItem {
  358. id: string
  359. name: string
  360. parent_table: string
  361. child_table: string
  362. document_count: number
  363. status: number
  364. created_at: string
  365. created_by: string
  366. metadata_schema: MetadataSchemaField[]
  367. }
  368. interface KnowledgeBaseListResponse {
  369. total: number
  370. page: number
  371. page_size: number
  372. items: KnowledgeBaseItem[]
  373. }
  374. interface KnowledgeBaseDetailResponse extends KnowledgeBaseItem {
  375. description: string
  376. updated_at: string
  377. }
  378. interface KbImportResponse {
  379. kb_id: string
  380. kb_name: string
  381. document_count: number
  382. metadata_schema: MetadataSchemaField[]
  383. parent_table: string
  384. child_table: string
  385. }
  386. export type { ModelInfo, ModelTestRequest, ModelTestResponse, ModelDownloadResponse, ModelDownloadTaskResponse, DatasetInfo, DatasetDownloadResponse, DatasetDownloadTaskResponse, DatasetPreview, DatasetValidation, TrainingJob, TrainingConfig, EvalConfig, EvalResult, DeployConfig, DeployResponse, AdapterInfo, InferenceRequest, InferenceResponse, KnowledgeBaseItem, KnowledgeBaseListResponse, KnowledgeBaseDetailResponse, KbImportResponse }