feat: 数据集版本管理

新增数据集版本创建、切换、激活与历史追溯能力,Mock 通过 localStorage 持久化版本状态并接入版本接口路由,预览页支持版本对比与内容回滚,列表补充版本入口,回归脚本扩充断言。
This commit is contained in:
caoxiaozhu
2026-07-13 10:31:20 +08:00
parent 4dde761348
commit 0d82930181
8 changed files with 1381 additions and 288 deletions

View File

@@ -118,7 +118,7 @@ onMounted(loadData)
<template #actions="{ row }">
<div class="action-buttons">
<el-button type="primary" link size="small" @click="handlePreview(row)">
<i class="fa fa-eye" style="margin-right: 4px" />预览
<i class="fa fa-eye" style="margin-right: 4px" />详情
</el-button>
<el-button type="success" link size="small" @click="handleDownload(row)">
<i class="fa fa-download" style="margin-right: 4px" />下载

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
export type DatasetRecordKind = 'object' | 'primitive' | 'invalid'
export interface DatasetRecord {
sourceIndex: number
displayIndex: number
raw: string
kind: DatasetRecordKind
value: unknown
error?: string
}
export interface DatasetRecordResult {
supported: boolean
records: DatasetRecord[]
error?: string
}
function isObjectRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function parseValue(raw: string, sourceIndex: number, displayIndex: number): DatasetRecord {
try {
const value: unknown = JSON.parse(raw)
return {
sourceIndex,
displayIndex,
raw,
kind: isObjectRecord(value) ? 'object' : 'primitive',
value,
}
} catch (error) {
return {
sourceIndex,
displayIndex,
raw,
kind: 'invalid',
value: raw,
error: error instanceof Error ? error.message : 'JSON 格式不正确',
}
}
}
/** 将 JSONL 或 JSON 文件解析为可逐条编辑的样本,保留原始物理位置。 */
export function parseDatasetRecords(content: string, fileName: string): DatasetRecordResult {
const normalizedName = fileName.toLowerCase()
if (normalizedName.endsWith('.jsonl')) {
const records: DatasetRecord[] = []
content.split('\n').forEach((line, sourceIndex) => {
if (!line.trim()) return
records.push(parseValue(line, sourceIndex, records.length + 1))
})
return { supported: true, records }
}
if (normalizedName.endsWith('.json')) {
try {
const root: unknown = JSON.parse(content)
const values = Array.isArray(root) ? root : [root]
return {
supported: true,
records: values.map((value, sourceIndex) => ({
sourceIndex,
displayIndex: sourceIndex + 1,
raw: JSON.stringify(value),
kind: isObjectRecord(value) ? 'object' : 'primitive',
value,
})),
}
} catch (error) {
return {
supported: true,
records: [{
sourceIndex: 0,
displayIndex: 1,
raw: content,
kind: 'invalid',
value: content,
error: error instanceof Error ? error.message : 'JSON 格式不正确',
}],
}
}
}
return { supported: false, records: [] }
}
/** 仅替换目标样本JSONL 中其他行(包括空行和原格式)保持不变。 */
export function updateDatasetRecord(
content: string,
fileName: string,
sourceIndex: number,
nextValue: unknown,
) {
const normalizedName = fileName.toLowerCase()
if (normalizedName.endsWith('.jsonl')) {
const lines = content.split('\n')
if (sourceIndex < 0 || sourceIndex >= lines.length) throw new Error('目标样本不存在')
lines[sourceIndex] = JSON.stringify(nextValue)
return lines.join('\n')
}
if (normalizedName.endsWith('.json')) {
let root: unknown
try {
root = JSON.parse(content)
} catch {
if (sourceIndex !== 0) throw new Error('目标样本不存在')
return JSON.stringify(nextValue, null, 2)
}
if (Array.isArray(root)) {
if (sourceIndex < 0 || sourceIndex >= root.length) throw new Error('目标样本不存在')
root[sourceIndex] = nextValue
return JSON.stringify(root, null, 2)
}
if (sourceIndex !== 0) throw new Error('目标样本不存在')
return JSON.stringify(nextValue, null, 2)
}
throw new Error('当前文件不支持逐条编辑')
}