feat: 数据集版本管理
新增数据集版本创建、切换、激活与历史追溯能力,Mock 通过 localStorage 持久化版本状态并接入版本接口路由,预览页支持版本对比与内容回滚,列表补充版本入口,回归脚本扩充断言。
This commit is contained in:
@@ -22,6 +22,13 @@ import {
|
||||
mockTrainingLogFiles,
|
||||
mockLogContent,
|
||||
} from './data'
|
||||
import {
|
||||
activateDatasetVersion,
|
||||
appendDatasetVersion,
|
||||
createInitialVersionState,
|
||||
getActiveDatasetVersion,
|
||||
} from './datasetVersions'
|
||||
import type { StoredDatasetVersion, StoredDatasetVersionState } from './datasetVersions'
|
||||
|
||||
/** 模拟网络延迟 */
|
||||
function delay(ms = 200): Promise<void> {
|
||||
@@ -57,6 +64,41 @@ function stripBaseURL(url: string): string {
|
||||
return path
|
||||
}
|
||||
|
||||
function datasetFallbackContent(fileId: string) {
|
||||
const fallbackKey = fileId.endsWith('-readme') ? 'mock-readme' : 'mock-jsonl'
|
||||
return mockDatasetPreviews[fileId] ?? mockDatasetPreviews[fallbackKey] ?? ''
|
||||
}
|
||||
|
||||
function versionStorageKey(fileId: string) {
|
||||
return `mock:dataset-versions:${fileId}`
|
||||
}
|
||||
|
||||
function persistVersionState(fileId: string, state: StoredDatasetVersionState) {
|
||||
localStorage.setItem(versionStorageKey(fileId), JSON.stringify(state))
|
||||
}
|
||||
|
||||
function getVersionState(fileId: string): StoredDatasetVersionState {
|
||||
const persisted = localStorage.getItem(versionStorageKey(fileId))
|
||||
if (persisted) {
|
||||
try {
|
||||
const state = JSON.parse(persisted) as StoredDatasetVersionState
|
||||
if (state.versions?.length && state.active_version_id) return state
|
||||
} catch {
|
||||
// 版本数据损坏时回退到初始版本
|
||||
}
|
||||
}
|
||||
|
||||
const legacyContent = localStorage.getItem(`mock:dataset-file:${fileId}`)
|
||||
const state = createInitialVersionState(legacyContent ?? datasetFallbackContent(fileId))
|
||||
persistVersionState(fileId, state)
|
||||
return state
|
||||
}
|
||||
|
||||
function versionMetadata(version: StoredDatasetVersion) {
|
||||
const { content: _content, ...metadata } = version
|
||||
return metadata
|
||||
}
|
||||
|
||||
/** 通过路径 + method 匹配 mock 响应 */
|
||||
async function handleMock(config: AxiosRequestConfig) {
|
||||
await delay(150) // 模拟网络延迟
|
||||
@@ -133,9 +175,77 @@ async function handleMock(config: AxiosRequestConfig) {
|
||||
m = url.match(/^\/dataset-manage\/preview\/([^/]+)$/)
|
||||
if (m && method === 'get') {
|
||||
const fileId = decodeURIComponent(m[1])
|
||||
const fallbackKey = fileId.endsWith('-readme') ? 'mock-readme' : 'mock-jsonl'
|
||||
const content = mockDatasetPreviews[fileId] ?? mockDatasetPreviews[fallbackKey]
|
||||
return ok({ content })
|
||||
return ok({ content: getActiveDatasetVersion(getVersionState(fileId)).content })
|
||||
}
|
||||
if (m && method === 'put') return fail('历史版本不可覆盖,请创建新版本', 405)
|
||||
|
||||
m = url.match(/^\/dataset-manage\/versions\/([^/]+)$/)
|
||||
if (m && method === 'get') {
|
||||
const fileId = decodeURIComponent(m[1])
|
||||
const state = getVersionState(fileId)
|
||||
return ok({
|
||||
versions: state.versions.map(versionMetadata).sort((a, b) => b.version - a.version),
|
||||
active_version_id: state.active_version_id,
|
||||
})
|
||||
}
|
||||
if (m && method === 'post') {
|
||||
const fileId = decodeURIComponent(m[1])
|
||||
if (typeof body.content !== 'string') return fail('文件内容格式不正确', 400)
|
||||
const currentState = getVersionState(fileId)
|
||||
if (body.expected_current_version_id !== currentState.active_version_id) {
|
||||
return fail('当前版本已被其他用户更新,请刷新后重试', 409)
|
||||
}
|
||||
if (body.base_version_id !== currentState.active_version_id) {
|
||||
return fail('只能基于当前版本创建新版本', 409)
|
||||
}
|
||||
if (body.content === getActiveDatasetVersion(currentState).content) {
|
||||
return fail('数据内容没有变化,无需创建新版本', 400)
|
||||
}
|
||||
const nextState = appendDatasetVersion(
|
||||
currentState,
|
||||
body.content,
|
||||
new Date().toISOString(),
|
||||
typeof body.description === 'string' ? body.description : '在线编辑',
|
||||
)
|
||||
try {
|
||||
persistVersionState(fileId, nextState)
|
||||
} catch {
|
||||
return fail('文件内容过大,浏览器 Mock 存储空间不足', 413)
|
||||
}
|
||||
const version = getActiveDatasetVersion(nextState)
|
||||
return ok({ version: versionMetadata(version), content: version.content })
|
||||
}
|
||||
|
||||
m = url.match(/^\/dataset-manage\/versions\/([^/]+)\/active$/)
|
||||
if (m && method === 'put') {
|
||||
const fileId = decodeURIComponent(m[1])
|
||||
const currentState = getVersionState(fileId)
|
||||
if (body.expected_current_version_id !== currentState.active_version_id) {
|
||||
return fail('当前版本已被其他用户更新,请刷新后重试', 409)
|
||||
}
|
||||
let nextState: StoredDatasetVersionState
|
||||
try {
|
||||
nextState = activateDatasetVersion(currentState, String(body.version_id || ''))
|
||||
} catch (error) {
|
||||
return fail(error instanceof Error ? error.message : '版本不存在', 404)
|
||||
}
|
||||
try {
|
||||
persistVersionState(fileId, nextState)
|
||||
} catch {
|
||||
return fail('浏览器 Mock 存储空间不足', 413)
|
||||
}
|
||||
const version = getActiveDatasetVersion(nextState)
|
||||
return ok({ version: versionMetadata(version), content: version.content })
|
||||
}
|
||||
|
||||
m = url.match(/^\/dataset-manage\/versions\/([^/]+)\/([^/]+)$/)
|
||||
if (m && method === 'get') {
|
||||
const fileId = decodeURIComponent(m[1])
|
||||
const versionId = decodeURIComponent(m[2])
|
||||
const version = getVersionState(fileId).versions.find((item) => item.id === versionId)
|
||||
return version
|
||||
? ok({ version: versionMetadata(version), content: version.content })
|
||||
: fail('数据集版本不存在', 404)
|
||||
}
|
||||
|
||||
// ==================== 训练任务 ====================
|
||||
|
||||
Reference in New Issue
Block a user