@@ -29,6 +29,208 @@ const 读取数字 = (value: unknown, fallback: number): number => {
2929 return Number . isFinite ( num ) ? num : fallback ;
3030} ;
3131
32+ /**
33+ * 校验数据集结构完整性,返回错误信息列表
34+ */
35+ const 校验分享数据集结构 = ( raw : any , index : number ) : string [ ] => {
36+ const errors : string [ ] = [ ] ;
37+ const prefix = `数据集[${ index + 1 } ]` ;
38+
39+ if ( ! raw || typeof raw !== 'object' ) {
40+ errors . push ( `${ prefix } : 无效的数据集对象` ) ;
41+ return errors ;
42+ }
43+
44+ const id = 读取文本 ( raw ?. id ) . trim ( ) ;
45+ const 标题 = 读取文本 ( raw ?. 标题 ) . trim ( ) ;
46+ const 作品名 = 读取文本 ( raw ?. 作品名 ) . trim ( ) ;
47+
48+ if ( ! id ) errors . push ( `${ prefix } : 缺少有效ID` ) ;
49+ if ( ! 标题 && ! 作品名 ) errors . push ( `${ prefix } : 缺少标题和作品名` ) ;
50+
51+ // 校验章节列表结构
52+ const 章节列表 = raw ?. 章节列表 ;
53+ if ( 章节列表 !== undefined && ! Array . isArray ( 章节列表 ) ) {
54+ errors . push ( `${ prefix } : 章节列表必须是数组` ) ;
55+ } else if ( Array . isArray ( 章节列表 ) ) {
56+ 章节列表 . forEach ( ( chapter : any , ci : number ) => {
57+ if ( ! chapter || typeof chapter !== 'object' ) {
58+ errors . push ( `${ prefix } .章节[${ ci } ]: 无效的章节对象` ) ;
59+ } else if ( ! 读取文本 ( chapter ?. id ) . trim ( ) ) {
60+ errors . push ( `${ prefix } .章节[${ ci } ]: 缺少有效ID` ) ;
61+ }
62+ } ) ;
63+ }
64+
65+ // 校验分段列表结构
66+ const 分段列表 = raw ?. 分段列表 ;
67+ if ( 分段列表 !== undefined && ! Array . isArray ( 分段列表 ) ) {
68+ errors . push ( `${ prefix } : 分段列表必须是数组` ) ;
69+ } else if ( Array . isArray ( 分段列表 ) ) {
70+ 分段列表 . forEach ( ( segment : any , si : number ) => {
71+ if ( ! segment || typeof segment !== 'object' ) {
72+ errors . push ( `${ prefix } .分段[${ si } ]: 无效的分段对象` ) ;
73+ } else if ( ! 读取文本 ( segment ?. id ) . trim ( ) ) {
74+ errors . push ( `${ prefix } .分段[${ si } ]: 缺少有效ID` ) ;
75+ }
76+ } ) ;
77+ }
78+
79+ // 校验注入树结构
80+ const 注入树 = raw ?. 注入树 ;
81+ if ( 注入树 !== undefined && ! Array . isArray ( 注入树 ) ) {
82+ errors . push ( `${ prefix } : 注入树必须是数组` ) ;
83+ } else if ( Array . isArray ( 注入树 ) ) {
84+ const validateNodes = ( nodes : any [ ] , path : string ) => {
85+ nodes . forEach ( ( node : any , ni : number ) => {
86+ if ( ! node || typeof node !== 'object' ) {
87+ errors . push ( `${ path } [${ ni } ]: 无效的节点对象` ) ;
88+ return ;
89+ }
90+ if ( ! 读取文本 ( node ?. id ) . trim ( ) ) {
91+ errors . push ( `${ path } [${ ni } ]: 缺少有效ID` ) ;
92+ }
93+ if ( Array . isArray ( node ?. 子节点 ) ) {
94+ validateNodes ( node . 子节点 , `${ path } [${ ni } ].子节点` ) ;
95+ }
96+ } ) ;
97+ } ;
98+ validateNodes ( 注入树 , `${ prefix } .注入树` ) ;
99+ }
100+
101+ return errors ;
102+ } ;
103+
104+ /**
105+ * 校验任务结构完整性
106+ */
107+ const 校验分享任务结构 = ( raw : any , index : number ) : string [ ] => {
108+ const errors : string [ ] = [ ] ;
109+ const prefix = `任务[${ index + 1 } ]` ;
110+
111+ if ( ! raw || typeof raw !== 'object' ) {
112+ errors . push ( `${ prefix } : 无效的任务对象` ) ;
113+ return errors ;
114+ }
115+
116+ if ( ! 读取文本 ( raw ?. 数据集ID ) . trim ( ) ) {
117+ errors . push ( `${ prefix } : 缺少数据集ID` ) ;
118+ }
119+
120+ // 校验已完成/失败分段ID列表是字符串数组
121+ const completedSegments = raw ?. 已完成分段ID列表 ;
122+ if ( completedSegments !== undefined && ! Array . isArray ( completedSegments ) ) {
123+ errors . push ( `${ prefix } : 已完成分段ID列表必须是数组` ) ;
124+ }
125+
126+ const failedSegments = raw ?. 失败分段ID列表 ;
127+ if ( failedSegments !== undefined && ! Array . isArray ( failedSegments ) ) {
128+ errors . push ( `${ prefix } : 失败分段ID列表必须是数组` ) ;
129+ }
130+
131+ return errors ;
132+ } ;
133+
134+ /**
135+ * 对导入的旧版本数据进行兼容性修复
136+ */
137+ const 兼容修复分享数据集 = ( raw : any ) : any => {
138+ if ( ! raw || typeof raw !== 'object' ) return raw ;
139+
140+ const version = Math . max ( 1 , Math . floor ( 读取数字 ( raw ?. schemaVersion , 1 ) ) ) ;
141+ let fixed = { ...raw } ;
142+
143+ // v1->v2: 确保存在分段模式字段
144+ if ( version < 2 ) {
145+ if ( ! fixed . 分段模式 ) {
146+ fixed . 分段模式 = 'single_chapter' ;
147+ }
148+ }
149+
150+ // v2->v3: 补全每批章数
151+ if ( version < 3 ) {
152+ if ( typeof fixed . 每批章数 !== 'number' || fixed . 每批章数 < 1 ) {
153+ fixed . 每批章数 = 1 ;
154+ }
155+ }
156+
157+ // v3->v4: 补全时间线起点
158+ if ( version < 4 ) {
159+ if ( ! fixed . 默认时间线起点 ) {
160+ fixed . 默认时间线起点 = 默认小说时间线起点 ;
161+ }
162+ fixed . 是否识别原著时间线 = 读取布尔 ( fixed . 是否识别原著时间线 , false ) ;
163+ }
164+
165+ // v4->v5: 补全核心角色摘要
166+ if ( version < 5 ) {
167+ if ( ! Array . isArray ( fixed . 核心角色摘要 ) ) {
168+ fixed . 核心角色摘要 = [ ] ;
169+ }
170+ if ( ! Array . isArray ( fixed . 核心角色 ) ) {
171+ fixed . 核心角色 = [ ] ;
172+ }
173+ }
174+
175+ // v5->v6: 补全当前阶段概括
176+ if ( version < 6 ) {
177+ if ( ! 读取文本 ( fixed . 当前阶段概括 ) ) {
178+ fixed . 当前阶段概括 = '' ;
179+ }
180+ }
181+
182+ // v6->v7: 确保注入树结构完整
183+ if ( version < 7 ) {
184+ if ( ! Array . isArray ( fixed . 注入树 ) ) {
185+ fixed . 注入树 = [ ] ;
186+ }
187+ }
188+
189+ // 更新版本号
190+ fixed . schemaVersion = 小说拆分数据集版本 ;
191+
192+ return fixed ;
193+ } ;
194+
195+ /**
196+ * 对导入的旧版本任务数据进行兼容性修复
197+ */
198+ const 兼容修复分享任务 = ( raw : any ) : any => {
199+ if ( ! raw || typeof raw !== 'object' ) return raw ;
200+
201+ const fixed = { ...raw } ;
202+
203+ // 确保存在必要字段
204+ if ( typeof fixed . 后台运行 !== 'boolean' ) {
205+ fixed . 后台运行 = true ;
206+ }
207+ if ( typeof fixed . 自动续跑 !== 'boolean' ) {
208+ fixed . 自动续跑 = true ;
209+ }
210+ if ( typeof fixed . 单次处理批量 !== 'number' || fixed . 单次处理批量 < 1 ) {
211+ fixed . 单次处理批量 = 1 ;
212+ }
213+ if ( typeof fixed . 自动重试次数 !== 'number' || fixed . 自动重试次数 < 0 ) {
214+ fixed . 自动重试次数 = 0 ;
215+ }
216+ if ( ! Array . isArray ( fixed . 已完成分段ID列表 ) ) {
217+ fixed . 已完成分段ID列表 = [ ] ;
218+ }
219+ if ( ! Array . isArray ( fixed . 失败分段ID列表 ) ) {
220+ fixed . 失败分段ID列表 = [ ] ;
221+ }
222+
223+ // 标准化状态值
224+ fixed . 状态 = [ 'queued' , 'running' , 'paused' , 'completed' , 'failed' , 'cancelled' , 'idle' ] . includes ( fixed . 状态 )
225+ ? fixed . 状态
226+ : 'idle' ;
227+ fixed . 当前阶段 = [ 'prepare' , 'segmenting' , 'processing' , 'snapshotting' , 'completed' , 'failed' , 'idle' ] . includes ( fixed . 当前阶段 )
228+ ? fixed . 当前阶段
229+ : 'idle' ;
230+
231+ return fixed ;
232+ } ;
233+
32234const 标准化登场角色项文本 = ( value : unknown ) : string => 读取文本 ( value )
33235 . replace ( / \r \n / g, '\n' )
34236 . split ( '\n' )
@@ -1007,7 +1209,7 @@ export const 导入小说拆分分享数据 = async (
10071209 const schema = 读取文本 ( parsed ?. schema ) . trim ( ) ;
10081210 const version = Math . max ( 1 , Math . floor ( 读取数字 ( parsed ?. version , 1 ) ) ) ;
10091211 if ( schema !== 小说拆分分享格式标识 ) {
1010- throw new Error ( `导入失败:分解数据格式“ ${ schema || '未知类型' } ” 不受支持。` ) ;
1212+ throw new Error ( `导入失败:分解数据格式" ${ schema || '未知类型' } " 不受支持。` ) ;
10111213 }
10121214 if ( version > 小说拆分分享版本 ) {
10131215 throw new Error ( `导入失败:分享包版本 ${ version } 高于当前支持版本 ${ 小说拆分分享版本 } 。` ) ;
@@ -1019,6 +1221,27 @@ export const 导入小说拆分分享数据 = async (
10191221
10201222 const sourceTasks = Array . isArray ( parsed ?. tasks ) ? parsed . tasks : [ ] ;
10211223 const sourceSnapshots = Array . isArray ( parsed ?. snapshots ) ? parsed . snapshots : [ ] ;
1224+
1225+ // 对源数据进行兼容修复和校验
1226+ const allValidationErrors : string [ ] = [ ] ;
1227+ const fixedSourceDatasets = sourceDatasets . map ( ( raw : any , index : number ) => {
1228+ const fixed = 兼容修复分享数据集 ( raw ) ;
1229+ const errors = 校验分享数据集结构 ( fixed , index ) ;
1230+ allValidationErrors . push ( ...errors ) ;
1231+ return fixed ;
1232+ } ) ;
1233+
1234+ const fixedSourceTasks = sourceTasks . map ( ( raw : any , index : number ) => {
1235+ const fixed = 兼容修复分享任务 ( raw ) ;
1236+ const errors = 校验分享任务结构 ( fixed , index ) ;
1237+ allValidationErrors . push ( ...errors ) ;
1238+ return fixed ;
1239+ } ) ;
1240+
1241+ // 如果有校验错误,记录警告但继续导入(兼容修复已尽可能修复问题)
1242+ if ( allValidationErrors . length > 0 ) {
1243+ console . warn ( '[小说分解导入] 部分数据结构存在兼容性问题,已自动修复:' , allValidationErrors ) ;
1244+ }
10221245 let rawMap = new Map < string , 小说拆分分享原文项结构 > ( ) ;
10231246 if ( options ?. includeRawText !== false ) {
10241247 const rawEntry = entries [ manifest . rawFile || 小说拆分分享原文文件 ] ;
@@ -1050,7 +1273,7 @@ export const 导入小说拆分分享数据 = async (
10501273
10511274 sourceDatasets . forEach ( ( rawDataset : any , datasetIndex : number ) => {
10521275 const normalizedDataset = 合并小说拆分分享原文 (
1053- 规范化小说拆分数据集 ( rawDataset ) ,
1276+ 规范化小说拆分数据集 ( fixedSourceDatasets [ datasetIndex ] ) ,
10541277 rawMap . get ( 读取文本 ( rawDataset ?. id ) . trim ( ) )
10551278 ) ;
10561279 const nextDatasetId = 生成ID ( 'novel_dataset' ) ;
@@ -1079,15 +1302,23 @@ export const 导入小说拆分分享数据 = async (
10791302 importedDatasetIds . push ( nextDatasetId ) ;
10801303
10811304 sourceTasks
1082- . filter ( ( item : any ) => 读取文本 ( item ?. 数据集ID ) . trim ( ) === normalizedDataset . id )
1083- . forEach ( ( rawTask : any ) => {
1084- const normalizedTask = 规范化小说拆分任务 ( rawTask ) ;
1305+ . filter ( ( item : any , taskIndex : number ) => {
1306+ const fixedTask = fixedSourceTasks [ taskIndex ] ;
1307+ return 读取文本 ( item ?. 数据集ID ) . trim ( ) === normalizedDataset . id ;
1308+ } )
1309+ . forEach ( ( rawTask : any , taskArrayIndex : number ) => {
1310+ // 找到在 sourceTasks 中的原始索引
1311+ const originalTaskIndex = sourceTasks . findIndex ( ( t : any ) =>
1312+ 读取文本 ( t ?. 数据集ID ) . trim ( ) === normalizedDataset . id
1313+ ) ;
1314+ const fixedTask = originalTaskIndex >= 0 ? fixedSourceTasks [ originalTaskIndex ] : rawTask ;
1315+ const normalizedTask = 规范化小说拆分任务 ( fixedTask ) ;
10851316 nextTasks . unshift ( 规范化小说拆分任务 ( {
10861317 ...normalizedTask ,
10871318 id : 生成ID ( 'novel_task' ) ,
10881319 数据集ID : nextDatasetId ,
1089- 已完成分段ID列表 : normalizedTask . 已完成分段ID列表 . map ( ( item ) => segmentIdMap . get ( item ) || item ) ,
1090- 失败分段ID列表 : normalizedTask . 失败分段ID列表 . map ( ( item ) => segmentIdMap . get ( item ) || item ) ,
1320+ 已完成分段ID列表 : normalizedTask . 已完成分段ID列表 . map ( ( item : string ) => segmentIdMap . get ( item ) || item ) ,
1321+ 失败分段ID列表 : normalizedTask . 失败分段ID列表 . map ( ( item : string ) => segmentIdMap . get ( item ) || item ) ,
10911322 createdAt : Date . now ( ) ,
10921323 updatedAt : Date . now ( )
10931324 } ) ) ;
0 commit comments