马良AI写作初始化仓库
This commit is contained in:
316
AINoval/lib/blocs/novel_import/novel_import_bloc.dart
Normal file
316
AINoval/lib/blocs/novel_import/novel_import_bloc.dart
Normal file
@@ -0,0 +1,316 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ainoval/models/import_status.dart';
|
||||
import 'package:ainoval/models/user_ai_model_config_model.dart';
|
||||
import 'package:ainoval/services/api_service/repositories/novel_repository.dart';
|
||||
import 'package:ainoval/utils/logger.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
part 'novel_import_event.dart';
|
||||
part 'novel_import_state.dart';
|
||||
|
||||
/// 小说导入Bloc - 支持新的三步导入流程
|
||||
class NovelImportBloc extends Bloc<NovelImportEvent, NovelImportState> {
|
||||
/// 创建小说导入Bloc
|
||||
NovelImportBloc({required this.novelRepository})
|
||||
: super(NovelImportInitial()) {
|
||||
// 第一步:上传文件获取预览
|
||||
on<UploadFileForPreview>(_onUploadFileForPreview);
|
||||
|
||||
// 第二步:获取导入预览
|
||||
on<GetImportPreview>(_onGetImportPreview);
|
||||
|
||||
// 第三步:确认并开始导入
|
||||
on<ConfirmAndStartImport>(_onConfirmAndStartImport);
|
||||
|
||||
// 导入状态更新
|
||||
on<ImportStatusUpdate>(_onImportStatusUpdate);
|
||||
|
||||
// 重置状态
|
||||
on<ResetImportState>(_onResetImportState);
|
||||
|
||||
// 清理预览会话
|
||||
on<CleanupPreviewSession>(_onCleanupPreviewSession);
|
||||
|
||||
// 传统导入(向后兼容)
|
||||
on<ImportNovelFile>(_onImportNovelFile);
|
||||
}
|
||||
|
||||
/// 小说仓库
|
||||
final NovelRepository novelRepository;
|
||||
|
||||
/// 导入状态订阅
|
||||
StreamSubscription<ImportStatus>? _importStatusSubscription;
|
||||
|
||||
/// 处理上传文件获取预览事件
|
||||
Future<void> _onUploadFileForPreview(
|
||||
UploadFileForPreview event, Emitter<NovelImportState> emit) async {
|
||||
emit(NovelImportUploading(message: '正在上传文件...'));
|
||||
|
||||
try {
|
||||
// 选择文件
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['txt'],
|
||||
withData: true,
|
||||
);
|
||||
|
||||
if (result == null || result.files.isEmpty) {
|
||||
emit(NovelImportInitial());
|
||||
return;
|
||||
}
|
||||
|
||||
final file = result.files.first;
|
||||
final fileBytes = file.bytes;
|
||||
final fileName = file.name;
|
||||
|
||||
if (fileBytes == null) {
|
||||
emit(NovelImportFailure(message: '无法读取文件数据'));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(NovelImportUploading(message: '正在上传文件到服务器...'));
|
||||
|
||||
// 上传文件并获取预览会话ID
|
||||
final previewSessionId = await novelRepository.uploadFileForPreview(fileBytes, fileName);
|
||||
|
||||
emit(NovelImportFileUploaded(
|
||||
previewSessionId: previewSessionId,
|
||||
fileName: fileName,
|
||||
fileSize: fileBytes.length,
|
||||
));
|
||||
} catch (e) {
|
||||
AppLogger.e('NovelImportBloc', '上传文件失败', e);
|
||||
emit(NovelImportFailure(message: '上传文件失败: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理获取导入预览事件
|
||||
Future<void> _onGetImportPreview(
|
||||
GetImportPreview event, Emitter<NovelImportState> emit) async {
|
||||
emit(NovelImportLoadingPreview(message: '正在解析文件...'));
|
||||
|
||||
try {
|
||||
// 获取导入预览
|
||||
final responseData = await novelRepository.getImportPreview(
|
||||
fileSessionId: event.previewSessionId,
|
||||
customTitle: event.customTitle,
|
||||
chapterLimit: event.chapterLimit,
|
||||
enableSmartContext: event.enableSmartContext,
|
||||
enableAISummary: event.enableAISummary,
|
||||
aiConfigId: event.aiConfigId,
|
||||
previewChapterCount: event.previewChapterCount,
|
||||
);
|
||||
|
||||
// 转换为ImportPreviewResponse对象
|
||||
final previewResponse = ImportPreviewResponse.fromJson(responseData);
|
||||
|
||||
emit(NovelImportPreviewReady(
|
||||
previewResponse: previewResponse,
|
||||
fileName: event.fileName,
|
||||
));
|
||||
} catch (e) {
|
||||
AppLogger.e('NovelImportBloc', '获取导入预览失败', e);
|
||||
emit(NovelImportFailure(message: '获取预览失败: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理确认并开始导入事件
|
||||
Future<void> _onConfirmAndStartImport(
|
||||
ConfirmAndStartImport event, Emitter<NovelImportState> emit) async {
|
||||
emit(NovelImportInProgress(status: 'CONFIRMING', message: '确认导入配置...'));
|
||||
|
||||
try {
|
||||
// 确认并开始导入
|
||||
final jobId = await novelRepository.confirmAndStartImport(
|
||||
previewSessionId: event.previewSessionId,
|
||||
finalTitle: event.finalTitle,
|
||||
selectedChapterIndexes: event.selectedChapterIndexes,
|
||||
enableSmartContext: event.enableSmartContext,
|
||||
enableAISummary: event.enableAISummary,
|
||||
aiConfigId: event.aiConfigId,
|
||||
);
|
||||
|
||||
emit(NovelImportInProgress(
|
||||
status: 'PROCESSING', message: '开始处理...', jobId: jobId));
|
||||
|
||||
// 订阅导入状态更新
|
||||
_importStatusSubscription?.cancel();
|
||||
_importStatusSubscription = novelRepository.getImportStatus(jobId).listen(
|
||||
(importStatus) {
|
||||
add(ImportStatusUpdate(
|
||||
status: importStatus.status,
|
||||
message: importStatus.message,
|
||||
jobId: jobId,
|
||||
progress: importStatus.progress,
|
||||
currentStep: importStatus.currentStep,
|
||||
processedChapters: importStatus.processedChapters,
|
||||
totalChapters: importStatus.totalChapters,
|
||||
));
|
||||
},
|
||||
onError: (error) {
|
||||
AppLogger.e('NovelImportBloc', '监听导入状态流错误', error);
|
||||
add(ImportStatusUpdate(
|
||||
status: 'FAILED',
|
||||
message: '监听导入状态失败: ${error.toString()}',
|
||||
jobId: jobId,
|
||||
));
|
||||
},
|
||||
onDone: () {
|
||||
AppLogger.i('NovelImportBloc', '导入状态流已关闭');
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.e('NovelImportBloc', '确认导入失败', e);
|
||||
emit(NovelImportFailure(message: '确认导入失败: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理导入状态更新事件
|
||||
void _onImportStatusUpdate(
|
||||
ImportStatusUpdate event, Emitter<NovelImportState> emit) {
|
||||
if (event.status == 'COMPLETED') {
|
||||
emit(NovelImportSuccess(message: event.message));
|
||||
_importStatusSubscription?.cancel();
|
||||
_importStatusSubscription = null;
|
||||
} else if (event.status == 'FAILED' || event.status == 'ERROR') {
|
||||
emit(NovelImportFailure(message: event.message));
|
||||
_importStatusSubscription?.cancel();
|
||||
_importStatusSubscription = null;
|
||||
} else {
|
||||
emit(NovelImportInProgress(
|
||||
status: event.status,
|
||||
message: event.message,
|
||||
jobId: event.jobId,
|
||||
progress: event.progress,
|
||||
currentStep: event.currentStep,
|
||||
processedChapters: event.processedChapters,
|
||||
totalChapters: event.totalChapters,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理清理预览会话事件
|
||||
Future<void> _onCleanupPreviewSession(
|
||||
CleanupPreviewSession event, Emitter<NovelImportState> emit) async {
|
||||
try {
|
||||
await novelRepository.cleanupPreviewSession(event.previewSessionId);
|
||||
AppLogger.i('NovelImportBloc', '预览会话已清理: ${event.previewSessionId}');
|
||||
} catch (e) {
|
||||
AppLogger.e('NovelImportBloc', '清理预览会话失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// 重置导入状态
|
||||
void _onResetImportState(
|
||||
ResetImportState event, Emitter<NovelImportState> emit) async {
|
||||
try {
|
||||
// 如果已经不是InProgress状态,不再重复取消
|
||||
if (state is! NovelImportInProgress) {
|
||||
emit(NovelImportInitial());
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录当前JobId,避免重复取消
|
||||
final currentState = state as NovelImportInProgress;
|
||||
final jobId = currentState.jobId;
|
||||
|
||||
// 立即切换到取消中状态,防止重复操作
|
||||
emit(NovelImportInProgress(
|
||||
status: 'CANCELLING',
|
||||
message: '正在取消导入...',
|
||||
jobId: jobId
|
||||
));
|
||||
|
||||
// 取消订阅
|
||||
await _importStatusSubscription?.cancel();
|
||||
_importStatusSubscription = null;
|
||||
|
||||
// 如果有JobId,尝试取消任务
|
||||
if (jobId != null) {
|
||||
// 通知服务器取消任务
|
||||
final success = await novelRepository.cancelImport(jobId);
|
||||
AppLogger.i('NovelImportBloc',
|
||||
'导入任务取消${success ? '成功' : '失败或已完成'}: $jobId');
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
emit(NovelImportInitial());
|
||||
} catch (e) {
|
||||
AppLogger.e('NovelImportBloc', '重置导入状态时出错', e);
|
||||
// 即使出错,也要确保状态被重置
|
||||
emit(NovelImportInitial());
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理传统导入小说文件事件(向后兼容)
|
||||
Future<void> _onImportNovelFile(
|
||||
ImportNovelFile event, Emitter<NovelImportState> emit) async {
|
||||
emit(NovelImportInProgress(status: 'PREPARING', message: '准备中...'));
|
||||
|
||||
try {
|
||||
// 选择文件
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['txt'],
|
||||
withData: true,
|
||||
);
|
||||
|
||||
if (result == null || result.files.isEmpty) {
|
||||
emit(NovelImportInitial());
|
||||
return;
|
||||
}
|
||||
|
||||
final file = result.files.first;
|
||||
final fileBytes = file.bytes;
|
||||
final fileName = file.name;
|
||||
|
||||
if (fileBytes == null) {
|
||||
emit(NovelImportFailure(message: '无法读取文件数据'));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(NovelImportInProgress(status: 'UPLOADING', message: '上传中...'));
|
||||
|
||||
// 上传文件并获取任务ID
|
||||
final jobId = await novelRepository.importNovel(fileBytes, fileName);
|
||||
|
||||
emit(NovelImportInProgress(
|
||||
status: 'PROCESSING', message: '处理中...', jobId: jobId));
|
||||
|
||||
// 订阅导入状态更新
|
||||
_importStatusSubscription?.cancel();
|
||||
_importStatusSubscription = novelRepository.getImportStatus(jobId).listen(
|
||||
(importStatus) {
|
||||
add(ImportStatusUpdate(
|
||||
status: importStatus.status,
|
||||
message: importStatus.message,
|
||||
jobId: jobId,
|
||||
));
|
||||
},
|
||||
onError: (error) {
|
||||
AppLogger.e('NovelImportBloc', '监听导入状态流错误', error);
|
||||
add(ImportStatusUpdate(
|
||||
status: 'FAILED',
|
||||
message: '监听导入状态失败: ${error.toString()}',
|
||||
jobId: jobId,
|
||||
));
|
||||
},
|
||||
onDone: () {
|
||||
AppLogger.i('NovelImportBloc', '导入状态流已关闭');
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.e('NovelImportBloc', '导入小说失败', e);
|
||||
emit(NovelImportFailure(message: '导入失败: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_importStatusSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
132
AINoval/lib/blocs/novel_import/novel_import_event.dart
Normal file
132
AINoval/lib/blocs/novel_import/novel_import_event.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
part of 'novel_import_bloc.dart';
|
||||
|
||||
/// 小说导入事件基类
|
||||
abstract class NovelImportEvent extends Equatable {
|
||||
const NovelImportEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// 第一步:上传文件获取预览
|
||||
class UploadFileForPreview extends NovelImportEvent {
|
||||
const UploadFileForPreview();
|
||||
}
|
||||
|
||||
/// 第二步:获取导入预览
|
||||
class GetImportPreview extends NovelImportEvent {
|
||||
const GetImportPreview({
|
||||
required this.previewSessionId,
|
||||
this.customTitle,
|
||||
this.chapterLimit,
|
||||
this.enableSmartContext = true,
|
||||
this.enableAISummary = false,
|
||||
this.aiConfigId,
|
||||
this.previewChapterCount = 10,
|
||||
required this.fileName,
|
||||
});
|
||||
|
||||
final String previewSessionId;
|
||||
final String? customTitle;
|
||||
final int? chapterLimit;
|
||||
final bool enableSmartContext;
|
||||
final bool enableAISummary;
|
||||
final String? aiConfigId;
|
||||
final int previewChapterCount;
|
||||
final String fileName;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
previewSessionId,
|
||||
customTitle,
|
||||
chapterLimit,
|
||||
enableSmartContext,
|
||||
enableAISummary,
|
||||
aiConfigId,
|
||||
previewChapterCount,
|
||||
fileName,
|
||||
];
|
||||
}
|
||||
|
||||
/// 第三步:确认并开始导入
|
||||
class ConfirmAndStartImport extends NovelImportEvent {
|
||||
const ConfirmAndStartImport({
|
||||
required this.previewSessionId,
|
||||
required this.finalTitle,
|
||||
this.selectedChapterIndexes,
|
||||
this.enableSmartContext = true,
|
||||
this.enableAISummary = false,
|
||||
this.aiConfigId,
|
||||
});
|
||||
|
||||
final String previewSessionId;
|
||||
final String finalTitle;
|
||||
final List<int>? selectedChapterIndexes;
|
||||
final bool enableSmartContext;
|
||||
final bool enableAISummary;
|
||||
final String? aiConfigId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
previewSessionId,
|
||||
finalTitle,
|
||||
selectedChapterIndexes,
|
||||
enableSmartContext,
|
||||
enableAISummary,
|
||||
aiConfigId,
|
||||
];
|
||||
}
|
||||
|
||||
/// 导入状态更新事件
|
||||
class ImportStatusUpdate extends NovelImportEvent {
|
||||
const ImportStatusUpdate({
|
||||
required this.status,
|
||||
required this.message,
|
||||
required this.jobId,
|
||||
this.progress,
|
||||
this.currentStep,
|
||||
this.processedChapters,
|
||||
this.totalChapters,
|
||||
});
|
||||
|
||||
final String status;
|
||||
final String message;
|
||||
final String jobId;
|
||||
final double? progress;
|
||||
final String? currentStep;
|
||||
final int? processedChapters;
|
||||
final int? totalChapters;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
status,
|
||||
message,
|
||||
jobId,
|
||||
progress,
|
||||
currentStep,
|
||||
processedChapters,
|
||||
totalChapters,
|
||||
];
|
||||
}
|
||||
|
||||
/// 重置导入状态
|
||||
class ResetImportState extends NovelImportEvent {
|
||||
const ResetImportState();
|
||||
}
|
||||
|
||||
/// 清理预览会话
|
||||
class CleanupPreviewSession extends NovelImportEvent {
|
||||
const CleanupPreviewSession({
|
||||
required this.previewSessionId,
|
||||
});
|
||||
|
||||
final String previewSessionId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [previewSessionId];
|
||||
}
|
||||
|
||||
/// 传统导入小说文件事件(向后兼容)
|
||||
class ImportNovelFile extends NovelImportEvent {
|
||||
const ImportNovelFile();
|
||||
}
|
||||
231
AINoval/lib/blocs/novel_import/novel_import_state.dart
Normal file
231
AINoval/lib/blocs/novel_import/novel_import_state.dart
Normal file
@@ -0,0 +1,231 @@
|
||||
part of 'novel_import_bloc.dart';
|
||||
|
||||
/// 小说导入状态基类
|
||||
abstract class NovelImportState extends Equatable {
|
||||
const NovelImportState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// 初始状态
|
||||
class NovelImportInitial extends NovelImportState {}
|
||||
|
||||
/// 第一步:上传文件中
|
||||
class NovelImportUploading extends NovelImportState {
|
||||
const NovelImportUploading({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// 第一步完成:文件已上传
|
||||
class NovelImportFileUploaded extends NovelImportState {
|
||||
const NovelImportFileUploaded({
|
||||
required this.previewSessionId,
|
||||
required this.fileName,
|
||||
required this.fileSize,
|
||||
});
|
||||
|
||||
final String previewSessionId;
|
||||
final String fileName;
|
||||
final int fileSize;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [previewSessionId, fileName, fileSize];
|
||||
}
|
||||
|
||||
/// 第二步:加载预览中
|
||||
class NovelImportLoadingPreview extends NovelImportState {
|
||||
const NovelImportLoadingPreview({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// 第二步完成:预览准备就绪
|
||||
class NovelImportPreviewReady extends NovelImportState {
|
||||
const NovelImportPreviewReady({
|
||||
required this.previewResponse,
|
||||
required this.fileName,
|
||||
});
|
||||
|
||||
final ImportPreviewResponse previewResponse;
|
||||
final String fileName;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [previewResponse, fileName];
|
||||
}
|
||||
|
||||
/// 第三步:导入进行中
|
||||
class NovelImportInProgress extends NovelImportState {
|
||||
const NovelImportInProgress({
|
||||
required this.status,
|
||||
required this.message,
|
||||
this.jobId,
|
||||
this.progress,
|
||||
this.currentStep,
|
||||
this.processedChapters,
|
||||
this.totalChapters,
|
||||
});
|
||||
|
||||
final String status;
|
||||
final String message;
|
||||
final String? jobId;
|
||||
final double? progress;
|
||||
final String? currentStep;
|
||||
final int? processedChapters;
|
||||
final int? totalChapters;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
status,
|
||||
message,
|
||||
jobId,
|
||||
progress,
|
||||
currentStep,
|
||||
processedChapters,
|
||||
totalChapters,
|
||||
];
|
||||
}
|
||||
|
||||
/// 导入成功
|
||||
class NovelImportSuccess extends NovelImportState {
|
||||
const NovelImportSuccess({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// 导入失败
|
||||
class NovelImportFailure extends NovelImportState {
|
||||
const NovelImportFailure({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// 导入预览响应数据类
|
||||
class ImportPreviewResponse {
|
||||
const ImportPreviewResponse({
|
||||
required this.previewSessionId,
|
||||
required this.detectedTitle,
|
||||
required this.totalChapterCount,
|
||||
required this.chapterPreviews,
|
||||
required this.totalWordCount,
|
||||
this.aiEstimation,
|
||||
this.warnings = const [],
|
||||
});
|
||||
|
||||
final String previewSessionId;
|
||||
final String detectedTitle;
|
||||
final int totalChapterCount;
|
||||
final List<ChapterPreview> chapterPreviews;
|
||||
final int totalWordCount;
|
||||
final AIEstimation? aiEstimation;
|
||||
final List<String> warnings;
|
||||
|
||||
factory ImportPreviewResponse.fromJson(Map<String, dynamic> json) {
|
||||
return ImportPreviewResponse(
|
||||
previewSessionId: json['previewSessionId'] as String,
|
||||
detectedTitle: json['detectedTitle'] as String,
|
||||
totalChapterCount: json['totalChapterCount'] as int,
|
||||
chapterPreviews: (json['chapterPreviews'] as List<dynamic>)
|
||||
.map((e) => ChapterPreview.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
totalWordCount: json['totalWordCount'] as int,
|
||||
aiEstimation: json['aiEstimation'] != null
|
||||
? AIEstimation.fromJson(json['aiEstimation'] as Map<String, dynamic>)
|
||||
: null,
|
||||
warnings: json['warnings'] != null
|
||||
? List<String>.from(json['warnings'] as List<dynamic>)
|
||||
: const [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 章节预览数据类
|
||||
class ChapterPreview {
|
||||
const ChapterPreview({
|
||||
required this.chapterIndex,
|
||||
required this.title,
|
||||
required this.contentPreview,
|
||||
required this.fullContentLength,
|
||||
required this.wordCount,
|
||||
this.selected = true,
|
||||
});
|
||||
|
||||
final int chapterIndex;
|
||||
final String title;
|
||||
final String contentPreview;
|
||||
final int fullContentLength;
|
||||
final int wordCount;
|
||||
final bool selected;
|
||||
|
||||
factory ChapterPreview.fromJson(Map<String, dynamic> json) {
|
||||
return ChapterPreview(
|
||||
chapterIndex: json['chapterIndex'] as int,
|
||||
title: json['title'] as String,
|
||||
contentPreview: json['contentPreview'] as String,
|
||||
fullContentLength: json['fullContentLength'] as int,
|
||||
wordCount: json['wordCount'] as int,
|
||||
selected: json['selected'] as bool? ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
ChapterPreview copyWith({
|
||||
int? chapterIndex,
|
||||
String? title,
|
||||
String? contentPreview,
|
||||
int? fullContentLength,
|
||||
int? wordCount,
|
||||
bool? selected,
|
||||
}) {
|
||||
return ChapterPreview(
|
||||
chapterIndex: chapterIndex ?? this.chapterIndex,
|
||||
title: title ?? this.title,
|
||||
contentPreview: contentPreview ?? this.contentPreview,
|
||||
fullContentLength: fullContentLength ?? this.fullContentLength,
|
||||
wordCount: wordCount ?? this.wordCount,
|
||||
selected: selected ?? this.selected,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// AI估算数据类
|
||||
class AIEstimation {
|
||||
const AIEstimation({
|
||||
required this.supported,
|
||||
this.estimatedTokens,
|
||||
this.estimatedCost,
|
||||
this.estimatedTimeMinutes,
|
||||
this.selectedModel,
|
||||
this.limitations,
|
||||
});
|
||||
|
||||
final bool supported;
|
||||
final int? estimatedTokens;
|
||||
final double? estimatedCost;
|
||||
final int? estimatedTimeMinutes;
|
||||
final String? selectedModel;
|
||||
final String? limitations;
|
||||
|
||||
factory AIEstimation.fromJson(Map<String, dynamic> json) {
|
||||
return AIEstimation(
|
||||
supported: json['supported'] as bool,
|
||||
estimatedTokens: json['estimatedTokens'] as int?,
|
||||
estimatedCost: json['estimatedCost'] as double?,
|
||||
estimatedTimeMinutes: json['estimatedTimeMinutes'] as int?,
|
||||
selectedModel: json['selectedModel'] as String?,
|
||||
limitations: json['limitations'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user