| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- // 统一的 fetch 包装器:自动携带 Token、处理刷新和滑动过期
- let tokenRefreshPromise: Promise<string> | null = null
- async function doRefreshToken(rt: string): Promise<string> {
- tokenRefreshPromise = (async () => {
- try {
- const res = await fetch('/api/v1/auth/refresh', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ refresh_token: rt }),
- })
- if (!res.ok) throw new Error('Refresh failed')
- const data = await res.json()
- const newToken = data.data.token
- const newRt = data.data.refresh_token
- localStorage.setItem('token', newToken)
- localStorage.setItem('refresh_token', newRt)
- return newToken
- } finally {
- tokenRefreshPromise = null
- }
- })()
- return tokenRefreshPromise
- }
- async function apiFetch(url: string, init?: RequestInit): Promise<Response> {
- const headers: Record<string, string> = { ...(init?.headers as Record<string, string> || {}) }
- const storedToken = localStorage.getItem('token')
- if (storedToken) {
- headers['Authorization'] = `Bearer ${storedToken}`
- }
- // FormData 时不要设 Content-Type
- if (!(init?.body instanceof FormData)) {
- headers['Content-Type'] = headers['Content-Type'] || 'application/json'
- }
- let res = await fetch(url, { ...init, headers })
- // 滑动过期:检测 X-New-Token 响应头
- const newToken = res.headers.get('X-New-Token')
- if (newToken) {
- localStorage.setItem('token', newToken)
- }
- // 401 时尝试用 refresh_token 刷新一次
- if (res.status === 401 && storedToken) {
- const storedRt = localStorage.getItem('refresh_token')
- if (storedRt) {
- try {
- const freshToken = await doRefreshToken(storedRt)
- headers['Authorization'] = `Bearer ${freshToken}`
- res = await fetch(url, { ...init, headers })
- } catch {
- localStorage.removeItem('token')
- localStorage.removeItem('refresh_token')
- localStorage.removeItem('user')
- window.location.href = '/login'
- }
- }
- }
- if (!res.ok) {
- try {
- const err = await res.json()
- throw new Error(err.detail || err.error || err.message || `Request failed: ${res.status}`)
- } catch (e) {
- if (e instanceof Error) throw e
- throw new Error(`Request failed: ${res.status}`)
- }
- }
- return res
- }
- const api = {
- // --- Health ---
- health: () => apiFetch('/health').then(r => r.json()),
- // --- Models ---
- models: {
- list: () => apiFetch('/api/v1/models/').then(r => r.json()) as Promise<ModelInfo[]>,
- download: (modelId: string, useModelscope = false) =>
- apiFetch('/api/v1/models/download', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ model_id: modelId, use_modelscope: useModelscope }),
- }).then(r => r.json()) as Promise<ModelDownloadTaskResponse>,
- downloadStatus: (taskId: string) =>
- apiFetch(`/api/v1/models/download/${taskId}`).then(r => r.json()) as Promise<ModelDownloadTaskResponse>,
- listDownloads: () =>
- apiFetch('/api/v1/models/downloads').then(r => r.json()) as Promise<ModelDownloadTaskResponse[]>,
- cancelDownload: (taskId: string) =>
- apiFetch(`/api/v1/models/download/${taskId}/cancel`, { method: 'POST' }).then(r => r.json()),
- delete: (modelId: string) =>
- apiFetch(`/api/v1/models/${encodeURIComponent(modelId)}`, { method: 'DELETE' }).then(r => r.json()),
- getInfo: (modelId: string) =>
- apiFetch(`/api/v1/models/${encodeURIComponent(modelId)}`).then(r => r.json()) as Promise<ModelInfo>,
- test: (req: ModelTestRequest) =>
- apiFetch('/api/v1/models/test', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(req),
- }).then(r => r.json()) as Promise<ModelTestResponse>,
- },
- // --- Datasets ---
- datasets: {
- list: () => apiFetch('/api/v1/datasets/').then(r => r.json()) as Promise<DatasetInfo[]>,
- upload: (file: File) => {
- const form = new FormData()
- form.append('file', file)
- return apiFetch('/api/v1/datasets/upload', { method: 'POST', body: form }).then(r => r.json()) as Promise<DatasetInfo>
- },
- download: (datasetId: string, useModelscope = false) =>
- apiFetch('/api/v1/datasets/download', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ dataset_id: datasetId, use_modelscope: useModelscope }),
- }).then(r => r.json()) as Promise<DatasetDownloadTaskResponse>,
- downloadStatus: (taskId: string) =>
- apiFetch(`/api/v1/datasets/download/${taskId}`).then(r => r.json()) as Promise<DatasetDownloadTaskResponse>,
- listDownloads: () =>
- apiFetch('/api/v1/datasets/downloads').then(r => r.json()) as Promise<DatasetDownloadTaskResponse[]>,
- cancelDownload: (taskId: string) =>
- apiFetch(`/api/v1/datasets/download/${taskId}/cancel`, { method: 'POST' }).then(r => r.json()),
- preview: (id: string, rows = 10) =>
- apiFetch(`/api/v1/datasets/${id}/preview?rows=${rows}`).then(r => r.json()) as Promise<DatasetPreview>,
- validate: (id: string) =>
- apiFetch(`/api/v1/datasets/${id}/validate`, { method: 'POST' }).then(r => r.json()) as Promise<DatasetValidation>,
- delete: (id: string) =>
- apiFetch(`/api/v1/datasets/${id}`, { method: 'DELETE' }).then(r => r.json()),
- },
- // --- Training ---
- training: {
- list: () => apiFetch('/api/v1/training/jobs').then(r => r.json()) as Promise<TrainingJob[]>,
- create: (cfg: TrainingConfig) =>
- apiFetch('/api/v1/training/jobs', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(cfg),
- }).then(r => r.json()) as Promise<TrainingJob>,
- get: (id: string) =>
- apiFetch(`/api/v1/training/jobs/${id}`).then(r => r.json()) as Promise<TrainingJob>,
- cancel: (id: string) =>
- apiFetch(`/api/v1/training/jobs/${id}/cancel`, { method: 'POST' }).then(r => r.json()),
- },
- // --- Evaluation ---
- evaluation: {
- run: (cfg: EvalConfig) =>
- apiFetch('/api/v1/evaluation/run', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(cfg),
- }).then(r => r.json()) as Promise<EvalResult>,
- results: (id: string) =>
- apiFetch(`/api/v1/evaluation/${id}/results`).then(r => r.json()) as Promise<EvalResult>,
- },
- // --- Deployment ---
- deployment: {
- export: (cfg: DeployConfig) =>
- apiFetch('/api/v1/deployment/export', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(cfg),
- }).then(r => r.json()) as Promise<DeployResponse>,
- status: (id: string) =>
- apiFetch(`/api/v1/deployment/${id}/status`).then(r => r.json()) as Promise<DeployResponse>,
- },
- // --- Sample Center ---
- sampleCenter: {
- listKnowledgeBases: (page = 1, page_size = 20) =>
- apiFetch(`/api/v1/sample-center/knowledge-bases?page=${page}&page_size=${page_size}`)
- .then(r => r.json()) as Promise<KnowledgeBaseListResponse>,
- getKnowledgeBaseDetail: (kb_id: string) =>
- apiFetch(`/api/v1/sample-center/knowledge-bases/${kb_id}`)
- .then(r => r.json()) as Promise<KnowledgeBaseDetailResponse>,
- importFromKnowledgeBase: (kb_id: string, kb_name = '') =>
- apiFetch(`/api/v1/sample-center/knowledge-bases/${kb_id}/import?kb_name=${encodeURIComponent(kb_name)}`, {
- method: 'POST',
- }).then(r => r.json()) as Promise<KbImportResponse>,
- },
- // --- Inference ---
- inference: {
- generate: (req: InferenceRequest) =>
- apiFetch('/api/v1/inference/generate', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(req),
- }).then(r => r.json()) as Promise<InferenceResponse>,
- adapters: () =>
- apiFetch('/api/v1/inference/adapters').then(r => r.json()) as Promise<AdapterInfo[]>,
- },
- }
- export default api
- // --- Types ---
- interface ModelInfo {
- id: string
- name: string
- model_type: string
- path?: string
- is_downloaded: boolean
- context_length?: number
- supported_peft_methods: string[]
- }
- interface ModelTestRequest {
- model_id: string
- prompt: string
- max_new_tokens?: number
- temperature?: number
- top_p?: number
- }
- interface ModelTestResponse {
- model_id: string
- prompt: string
- generated_text: string
- error?: string
- }
- interface ModelDownloadResponse {
- model_id: string
- status: string
- path?: string
- error?: string
- }
- interface ModelDownloadTaskResponse {
- task_id: string
- model_id: string
- status: string
- use_modelscope?: boolean
- path?: string
- error?: string
- progress?: number
- created_at?: string
- }
- interface DatasetInfo {
- id: string
- name: string
- format: string
- record_count: number
- file_path: string
- created_at: string
- }
- interface DatasetDownloadResponse {
- dataset_id: string
- status: string
- path?: string
- error?: string
- }
- interface DatasetDownloadTaskResponse {
- task_id: string
- dataset_id: string
- status: string
- use_modelscope?: boolean
- path?: string
- error?: string
- record_count?: number
- created_at?: string
- }
- interface DatasetPreview {
- total_records: number
- preview_rows: { row_index: number; data: Record<string, unknown> }[]
- columns: string[]
- }
- interface DatasetValidation {
- is_valid: boolean
- errors?: string[]
- warnings?: string[]
- }
- interface TrainingJob {
- id: string
- model_id: string
- model_type: string
- peft_method: string
- status: string
- progress: number
- current_epoch: number
- current_step: number
- total_steps: number
- loss?: number
- created_at: string
- started_at?: string
- finished_at?: string
- error_message?: string
- adapter_path?: string
- }
- interface TrainingConfig {
- model_id: string
- model_type: string
- dataset_id: string
- peft_method: string
- task_type?: string
- dataset_template?: string
- epochs?: number
- batch_size?: number
- gradient_accumulation?: number
- learning_rate?: number
- max_seq_length?: number
- warmup_ratio?: number
- save_strategy?: string
- eval_strategy?: string
- eval_steps?: number
- lora_r?: number
- lora_alpha?: number
- lora_dropout?: number
- lora_target_modules?: string
- qlora_bits?: number
- deepspeed?: boolean
- num_gpus?: number
- }
- interface EvalConfig {
- job_id: string
- test_split_ratio?: number
- batch_size?: number
- metrics?: string[]
- }
- interface EvalResult {
- id: string
- job_id: string
- status: string
- progress: number
- metrics: Record<string, unknown>
- error: string | null
- created_at: string
- }
- interface DeployConfig {
- job_id: string
- merge_with_base?: boolean
- export_format?: string
- }
- interface DeployResponse {
- job_id: string
- status: string
- output_path?: string
- error?: string
- }
- interface AdapterInfo {
- id: string
- path: string
- base_model: string
- peft_type: string
- model_id?: string
- peft_method?: string
- task_type?: string
- created_at?: string
- }
- interface InferenceRequest {
- adapter_id: string
- prompt: string
- max_new_tokens?: number
- temperature?: number
- top_p?: number
- repetition_penalty?: number
- do_sample?: boolean
- }
- interface InferenceResponse {
- prompt: string
- generated_text: string
- generated_only: string
- tokens_generated: number
- error?: string
- }
- interface MetadataSchemaField {
- field_name_cn: string
- field_name_en: string
- field_type: string
- description: string
- }
- interface KnowledgeBaseItem {
- id: string
- name: string
- parent_table: string
- child_table: string
- document_count: number
- status: number
- created_at: string
- created_by: string
- metadata_schema: MetadataSchemaField[]
- }
- interface KnowledgeBaseListResponse {
- total: number
- page: number
- page_size: number
- items: KnowledgeBaseItem[]
- }
- interface KnowledgeBaseDetailResponse extends KnowledgeBaseItem {
- description: string
- updated_at: string
- }
- interface KbImportResponse {
- kb_id: string
- kb_name: string
- document_count: number
- metadata_schema: MetadataSchemaField[]
- parent_table: string
- child_table: string
- }
- 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 }
|