马良AI写作初始化仓库

This commit is contained in:
邓滨杰
2025-09-10 00:07:52 +08:00
parent 3c06bb1a03
commit 39c0f8840f
1309 changed files with 318528 additions and 0 deletions

View File

@@ -0,0 +1,465 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ainoval/utils/logger.dart';
import 'package:flutter_quill/quill_delta.dart';
/// AI生成内容处理器
/// 用于为AI生成的内容添加蓝色样式标识并管理临时状态
class AIGeneratedContentProcessor {
static const String _tag = 'AIGeneratedContentProcessor';
/// AI生成内容的自定义属性名
static const String aiGeneratedAttr = 'ai-generated';
/// AI生成内容样式属性名用于CSS选择器识别
static const String aiGeneratedStyleAttr = 'ai-generated-style';
/// 🆕 隐藏文本的自定义属性名(用于重构时隐藏原文本)
static const String hiddenTextAttr = 'hidden-text';
/// 🆕 隐藏文本样式属性名用于CSS选择器识别
static const String hiddenTextStyleAttr = 'hidden-text-style';
/// 🎯 为指定范围的文本添加AI生成标识
static void markAsAIGenerated({
required QuillController controller,
required int startOffset,
required int length,
}) {
try {
//AppLogger.d(_tag, '🎨 标记AI生成内容: 位置 $startOffset-${startOffset + length}');
// 保存当前选择
final originalSelection = controller.selection;
// 创建AI生成内容的自定义属性
const aiGeneratedAttribute = Attribute(
aiGeneratedAttr,
AttributeScope.inline,
'true',
);
// 创建AI生成内容样式属性用于CSS识别
const aiGeneratedStyleAttribute = Attribute(
aiGeneratedStyleAttr,
AttributeScope.inline,
'generated',
);
// 应用AI生成标识属性
controller.formatText(startOffset, length, aiGeneratedAttribute);
controller.formatText(startOffset, length, aiGeneratedStyleAttribute);
// 恢复选择状态
controller.updateSelection(originalSelection, ChangeSource.silent);
//AppLogger.v(_tag, '✅ AI生成内容标记完成');
} catch (e) {
AppLogger.e(_tag, '标记AI生成内容失败', e);
}
}
/// 🆕 为指定范围的文本添加隐藏标识(用于重构时隐藏原文本)
static void markAsHidden({
required QuillController controller,
required int startOffset,
required int length,
}) {
try {
AppLogger.i(_tag, '🫥 标记隐藏文本: 位置 $startOffset-${startOffset + length}');
// 保存当前选择
final originalSelection = controller.selection;
// 创建隐藏文本的自定义属性
const hiddenAttribute = Attribute(
hiddenTextAttr,
AttributeScope.inline,
'true',
);
// 创建隐藏文本样式属性用于CSS识别
const hiddenStyleAttribute = Attribute(
hiddenTextStyleAttr,
AttributeScope.inline,
'hidden',
);
// 应用隐藏标识属性
controller.formatText(startOffset, length, hiddenAttribute);
controller.formatText(startOffset, length, hiddenStyleAttribute);
// 恢复选择状态
controller.updateSelection(originalSelection, ChangeSource.silent);
AppLogger.v(_tag, '✅ 隐藏文本标记完成');
} catch (e) {
AppLogger.e(_tag, '标记隐藏文本失败', e);
}
}
/// 🎯 移除AI生成标识将内容转为正常文本
static void removeAIGeneratedMarks({
required QuillController controller,
int? startOffset,
int? length,
}) {
try {
AppLogger.i(_tag, '🗑️ 移除AI生成标识');
final document = controller.document;
final plainText = document.toPlainText();
final removeStart = startOffset ?? 0;
final removeLength = length ?? plainText.length;
if (removeLength <= 0) return;
// 保存当前选择
final originalSelection = controller.selection;
// 移除AI生成相关的属性
final removeAttributes = [
Attribute(aiGeneratedAttr, AttributeScope.inline, null),
Attribute(aiGeneratedStyleAttr, AttributeScope.inline, null),
];
for (final attr in removeAttributes) {
controller.formatText(removeStart, removeLength, attr);
}
// 恢复选择状态
controller.updateSelection(originalSelection, ChangeSource.silent);
AppLogger.i(_tag, '✅ AI生成标识移除完成');
} catch (e) {
AppLogger.e(_tag, '移除AI生成标识失败', e);
}
}
/// 🆕 移除隐藏标识,显示文本(用于恢复原文本)
static void removeHiddenMarks({
required QuillController controller,
int? startOffset,
int? length,
}) {
try {
AppLogger.i(_tag, '👁️ 移除隐藏标识,显示文本');
final document = controller.document;
final plainText = document.toPlainText();
final removeStart = startOffset ?? 0;
final removeLength = length ?? plainText.length;
if (removeLength <= 0) return;
// 保存当前选择
final originalSelection = controller.selection;
// 移除隐藏相关的属性
final removeAttributes = [
Attribute(hiddenTextAttr, AttributeScope.inline, null),
Attribute(hiddenTextStyleAttr, AttributeScope.inline, null),
];
for (final attr in removeAttributes) {
controller.formatText(removeStart, removeLength, attr);
}
// 恢复选择状态
controller.updateSelection(originalSelection, ChangeSource.silent);
AppLogger.i(_tag, '✅ 隐藏标识移除完成,文本已显示');
} catch (e) {
AppLogger.e(_tag, '移除隐藏标识失败', e);
}
}
/// 🎯 检查指定范围是否包含AI生成内容
static bool hasAIGeneratedContent({
required QuillController controller,
required int startOffset,
required int length,
}) {
try {
final document = controller.document;
// 遍历指定范围内的所有节点检查是否有AI生成标识
final delta = document.toDelta();
int currentOffset = 0;
for (final operation in delta.operations) {
if (operation.isInsert) {
final opLength = operation.length!;
final opEnd = currentOffset + opLength;
// 检查操作是否与指定范围重叠
if (currentOffset < startOffset + length && opEnd > startOffset) {
// 检查操作的属性中是否包含AI生成标识
final attributes = operation.attributes;
if (attributes != null && attributes.containsKey(aiGeneratedAttr)) {
return true;
}
}
currentOffset = opEnd;
}
}
return false;
} catch (e) {
AppLogger.e(_tag, '检查AI生成内容失败', e);
return false;
}
}
/// 🎯 获取所有AI生成内容的范围
static List<({int start, int length})> getAIGeneratedRanges({
required QuillController controller,
}) {
final ranges = <({int start, int length})>[];
try {
final document = controller.document;
final delta = document.toDelta();
int currentOffset = 0;
for (final operation in delta.operations) {
if (operation.isInsert) {
final opLength = operation.length!;
// 检查操作的属性中是否包含AI生成标识
final attributes = operation.attributes;
if (attributes != null && attributes.containsKey(aiGeneratedAttr)) {
ranges.add((start: currentOffset, length: opLength));
}
currentOffset += opLength;
}
}
AppLogger.d(_tag, '📍 找到 ${ranges.length} 个AI生成内容范围');
} catch (e) {
AppLogger.e(_tag, '获取AI生成内容范围失败', e);
}
return ranges;
}
/// 🎯 获取自定义样式构建器用于处理AI生成内容和隐藏文本的显示样式
static TextStyle Function(Attribute) getCustomStyleBuilder() {
return (Attribute attribute) {
// 处理AI生成内容的样式标记
if (attribute.key == aiGeneratedStyleAttr &&
attribute.value == 'generated') {
return const TextStyle(
color: Color(0xFF2196F3), // 蓝色文字
// 可以添加更多样式,如背景色、下划线等
);
}
// 🆕 处理隐藏文本的样式标记
if (attribute.key == hiddenTextStyleAttr &&
attribute.value == 'hidden') {
return const TextStyle(
color: Color(0x40000000), // 25%透明度的黑色,几乎看不见
decoration: TextDecoration.lineThrough, // 删除线
decorationColor: Color(0x60FF0000), // 半透明红色删除线
decorationThickness: 1.5,
// 可选:背景色表示这是被隐藏的内容
// backgroundColor: Color(0x10FF0000), // 淡红色背景
);
}
return const TextStyle();
};
}
/// 🎯 清除所有AI生成标识通常在apply时调用
static void clearAllAIGeneratedMarks({
required QuillController controller,
}) {
try {
AppLogger.i(_tag, '🧹 清除所有AI生成标识');
removeAIGeneratedMarks(
controller: controller,
startOffset: 0,
length: controller.document.toPlainText().length,
);
} catch (e) {
AppLogger.e(_tag, '清除所有AI生成标识失败', e);
}
}
/// 🆕 获取所有隐藏文本的范围
static List<({int start, int length})> getHiddenTextRanges({
required QuillController controller,
}) {
final ranges = <({int start, int length})>[];
try {
final document = controller.document;
final delta = document.toDelta();
int currentOffset = 0;
for (final operation in delta.operations) {
if (operation.isInsert) {
final opLength = operation.length!;
// 检查操作的属性中是否包含隐藏标识
final attributes = operation.attributes;
if (attributes != null && attributes.containsKey(hiddenTextAttr)) {
ranges.add((start: currentOffset, length: opLength));
}
currentOffset += opLength;
}
}
AppLogger.d(_tag, '📍 找到 ${ranges.length} 个隐藏文本范围');
} catch (e) {
AppLogger.e(_tag, '获取隐藏文本范围失败', e);
}
return ranges;
}
/// 🆕 检查指定范围是否包含隐藏文本
static bool hasHiddenText({
required QuillController controller,
required int startOffset,
required int length,
}) {
try {
final document = controller.document;
// 遍历指定范围内的所有节点,检查是否有隐藏标识
final delta = document.toDelta();
int currentOffset = 0;
for (final operation in delta.operations) {
if (operation.isInsert) {
final opLength = operation.length!;
final opEnd = currentOffset + opLength;
// 检查操作是否与指定范围重叠
if (currentOffset < startOffset + length && opEnd > startOffset) {
// 检查操作的属性中是否包含隐藏标识
final attributes = operation.attributes;
if (attributes != null && attributes.containsKey(hiddenTextAttr)) {
return true;
}
}
currentOffset = opEnd;
}
}
return false;
} catch (e) {
AppLogger.e(_tag, '检查隐藏文本失败', e);
return false;
}
}
/// 🆕 获取过滤掉隐藏文本的纯文本内容(用于保存)
static String getVisibleTextOnly({
required QuillController controller,
}) {
try {
final document = controller.document;
final delta = document.toDelta();
final visibleText = StringBuffer();
for (final operation in delta.operations) {
if (operation.isInsert) {
final text = operation.data.toString();
final attributes = operation.attributes;
// 只包含非隐藏的文本
if (attributes == null || !attributes.containsKey(hiddenTextAttr)) {
visibleText.write(text);
}
}
}
final result = visibleText.toString();
AppLogger.d(_tag, '📝 过滤后可见文本长度: ${result.length}');
return result;
} catch (e) {
AppLogger.e(_tag, '获取可见文本失败', e);
return controller.document.toPlainText(); // 回退到原始文本
}
}
/// 🆕 获取过滤掉隐藏文本的Delta JSON用于保存
static String getVisibleDeltaJsonOnly({
required QuillController controller,
}) {
try {
final document = controller.document;
final originalDelta = document.toDelta();
final visibleOperations = <Map<String, dynamic>>[];
for (final operation in originalDelta.operations) {
if (operation.isInsert) {
final attributes = operation.attributes;
// 只包含非隐藏的操作
if (attributes == null || !attributes.containsKey(hiddenTextAttr)) {
visibleOperations.add(operation.toJson());
}
} else {
// 保留非插入操作(删除、保持等)
visibleOperations.add(operation.toJson());
}
}
final visibleDeltaJson = {'ops': visibleOperations};
AppLogger.d(_tag, '📝 过滤后Delta操作数量: ${visibleOperations.length}');
return jsonEncode(visibleDeltaJson);
} catch (e) {
AppLogger.e(_tag, '获取可见Delta JSON失败', e);
return jsonEncode(controller.document.toDelta().toJson()); // 回退到原始Delta
}
}
/// 🎯 检查文档是否包含任何AI生成内容
static bool hasAnyAIGeneratedContent({
required QuillController controller,
}) {
try {
final ranges = getAIGeneratedRanges(controller: controller);
return ranges.isNotEmpty;
} catch (e) {
AppLogger.e(_tag, '检查AI生成内容失败', e);
return false;
}
}
/// 🆕 检查文档是否包含任何隐藏文本
static bool hasAnyHiddenText({
required QuillController controller,
}) {
try {
final ranges = getHiddenTextRanges(controller: controller);
return ranges.isNotEmpty;
} catch (e) {
AppLogger.e(_tag, '检查隐藏文本失败', e);
return false;
}
}
}