马良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,487 @@
import 'package:flutter/material.dart';
import 'package:ainoval/models/preset_models.dart';
import 'package:ainoval/services/ai_preset_service.dart';
import 'package:ainoval/utils/web_theme.dart';
import 'package:ainoval/utils/logger.dart';
/// 预设下拉框按钮组件
/// 替换原有的预设按钮,提供下拉框选择预设的功能
class PresetDropdownButton extends StatefulWidget {
/// 构造函数
const PresetDropdownButton({
super.key,
required this.featureType,
this.currentPreset,
this.onPresetSelected,
this.onCreatePreset,
this.onManagePresets,
this.novelId,
this.label = '预设',
});
/// AI功能类型用于过滤预设
final String featureType;
/// 当前选中的预设
final AIPromptPreset? currentPreset;
/// 预设选择回调
final ValueChanged<AIPromptPreset>? onPresetSelected;
/// 创建预设回调
final VoidCallback? onCreatePreset;
/// 管理预设回调
final VoidCallback? onManagePresets;
/// 小说ID用于过滤预设
final String? novelId;
/// 按钮标签
final String label;
@override
State<PresetDropdownButton> createState() => _PresetDropdownButtonState();
}
class _PresetDropdownButtonState extends State<PresetDropdownButton> {
final String _tag = 'PresetDropdownButton';
List<AIPromptPreset> _recentPresets = [];
List<AIPromptPreset> _favoritePresets = [];
List<AIPromptPreset> _recommendedPresets = [];
bool _isLoading = false;
OverlayEntry? _overlayEntry;
final LayerLink _layerLink = LayerLink();
final GlobalKey _buttonKey = GlobalKey();
@override
void initState() {
super.initState();
_loadPresets();
}
@override
void dispose() {
_removeOverlay();
super.dispose();
}
/// 加载预设数据
Future<void> _loadPresets() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
try {
final presetService = AIPresetService();
// 使用新的统一接口获取功能预设列表
final presetListResponse = await presetService.getFeaturePresetList(
widget.featureType,
novelId: widget.novelId,
);
setState(() {
_recentPresets = presetListResponse.recentUsed.map((item) => item.preset).toList();
_favoritePresets = presetListResponse.favorites.map((item) => item.preset).toList();
_recommendedPresets = presetListResponse.recommended.map((item) => item.preset).toList();
_isLoading = false;
});
AppLogger.d(_tag, '预设数据加载完成: 最近${_recentPresets.length}个, 收藏${_favoritePresets.length}个, 推荐${_recommendedPresets.length}');
} catch (e) {
setState(() {
_isLoading = false;
});
AppLogger.e(_tag, '加载预设数据失败', e);
}
}
/// 显示下拉菜单
void _showDropdown() {
if (_overlayEntry != null) {
_removeOverlay();
return;
}
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry!);
}
/// 移除下拉菜单
void _removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
/// 创建下拉菜单覆盖层
OverlayEntry _createOverlayEntry() {
final RenderBox renderBox = _buttonKey.currentContext?.findRenderObject() as RenderBox;
final size = renderBox.size;
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: _removeOverlay,
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
// 透明背景,点击关闭
Positioned.fill(
child: Container(color: Colors.transparent),
),
// 下拉菜单
CompositedTransformFollower(
link: _layerLink,
showWhenUnlinked: false,
offset: Offset(0, size.height + 2),
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(6),
color: Colors.transparent,
child: Container(
width: 240, // 减小宽度使其更紧凑
constraints: const BoxConstraints(maxHeight: 320), // 减小最大高度
decoration: BoxDecoration(
color: WebTheme.getSurfaceColor(context),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: WebTheme.isDarkMode(context)
? WebTheme.darkGrey300
: WebTheme.grey300,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: _buildDropdownContent(),
),
),
),
],
),
),
);
}
/// 构建下拉菜单内容
Widget _buildDropdownContent() {
return ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 当前预设(如果有)
if (widget.currentPreset != null) ...[
_buildSectionHeader('当前预设'),
_buildPresetItem(
widget.currentPreset!,
isSelected: true,
showCheckmark: true,
),
_buildDivider(),
],
// 最近使用预设
if (_recentPresets.isNotEmpty) ...[
_buildSectionHeader('最近使用'),
..._recentPresets.take(3).map((preset) => _buildPresetItem(preset)), // 减少显示数量
if (_favoritePresets.isNotEmpty || _recommendedPresets.isNotEmpty) _buildDivider(),
],
// 收藏预设
if (_favoritePresets.isNotEmpty) ...[
_buildSectionHeader('收藏预设'),
..._favoritePresets.take(3).map((preset) => _buildPresetItem(preset)), // 减少显示数量
if (_recommendedPresets.isNotEmpty) _buildDivider(),
],
// 推荐预设
if (_recommendedPresets.isNotEmpty) ...[
_buildSectionHeader('推荐预设'),
..._recommendedPresets.take(3).map((preset) => _buildPresetItem(preset)), // 减少显示数量
_buildDivider(),
],
// 空状态
if (_recentPresets.isEmpty && _favoritePresets.isEmpty && _recommendedPresets.isEmpty && widget.currentPreset == null) ...[
_buildEmptyState(),
_buildDivider(),
],
// 操作按钮
_buildActionButtons(),
],
),
);
}
/// 构建分组标题
Widget _buildSectionHeader(String title) {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 8, 12, 4), // 减少内边距
child: Text(
title,
style: WebTheme.labelSmall.copyWith(
color: WebTheme.getSecondaryTextColor(context),
fontWeight: FontWeight.w600,
),
),
);
}
/// 构建预设项
Widget _buildPresetItem(
AIPromptPreset preset, {
bool isSelected = false,
bool showCheckmark = false,
}) {
return WebTheme.getMaterialWrapper(
child: InkWell(
onTap: () {
_removeOverlay();
widget.onPresetSelected?.call(preset);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), // 减少内边距
child: Row(
children: [
// 预设信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Flexible( // 使用 Flexible 而不是 Expanded 避免溢出
child: Text(
preset.displayName,
style: WebTheme.bodySmall.copyWith(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
color: isSelected
? WebTheme.getTextColor(context, isPrimary: true)
: WebTheme.getTextColor(context, isPrimary: false),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (preset.isFavorite) ...[
const SizedBox(width: 4),
Icon(
Icons.star,
size: 10,
color: Colors.amber.shade600,
),
],
],
),
if (preset.presetDescription != null && preset.presetDescription!.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
preset.presetDescription!,
style: WebTheme.labelSmall.copyWith(
color: WebTheme.getSecondaryTextColor(context),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
// 选中标记
if (showCheckmark) ...[
const SizedBox(width: 6),
Icon(
Icons.check,
size: 14,
color: WebTheme.getTextColor(context, isPrimary: false),
),
],
],
),
),
),
);
}
/// 构建分割线
Widget _buildDivider() {
return Container(
height: 1,
margin: const EdgeInsets.symmetric(horizontal: 8),
color: WebTheme.isDarkMode(context)
? WebTheme.darkGrey200
: WebTheme.grey200,
);
}
/// 构建空状态
Widget _buildEmptyState() {
return Container(
padding: const EdgeInsets.all(16), // 减少内边距
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.info_outline,
size: 24, // 减小图标尺寸
color: WebTheme.getSecondaryTextColor(context),
),
const SizedBox(height: 6),
Text(
'暂无预设',
style: WebTheme.bodySmall.copyWith(
fontWeight: FontWeight.w500,
color: WebTheme.getTextColor(context, isPrimary: false),
),
),
const SizedBox(height: 2),
Text(
'创建第一个预设来快速重用配置',
style: WebTheme.labelSmall.copyWith(
color: WebTheme.getSecondaryTextColor(context),
),
textAlign: TextAlign.center,
),
],
),
);
}
/// 构建操作按钮
Widget _buildActionButtons() {
return Container(
padding: const EdgeInsets.all(8), // 减少内边距
child: Row(
children: [
// 创建预设按钮
Expanded(
child: TextButton.icon(
onPressed: () {
_removeOverlay();
widget.onCreatePreset?.call();
},
icon: Icon(
Icons.add,
size: 14,
color: WebTheme.getSecondaryTextColor(context),
),
label: Text(
'创建',
style: WebTheme.labelSmall.copyWith(
color: WebTheme.getSecondaryTextColor(context),
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), // 减少内边距
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
),
),
const SizedBox(width: 4),
// 管理预设按钮
Expanded(
child: TextButton.icon(
onPressed: () {
_removeOverlay();
widget.onManagePresets?.call();
},
icon: Icon(
Icons.settings,
size: 14,
color: WebTheme.getSecondaryTextColor(context),
),
label: Text(
'管理',
style: WebTheme.labelSmall.copyWith(
color: WebTheme.getSecondaryTextColor(context),
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), // 减少内边距
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
// 获取当前预设的显示名称,如果太长则截断
String displayText = widget.currentPreset?.presetName ?? widget.label;
if (displayText.length > 8) { // 限制显示长度避免溢出
displayText = '${displayText.substring(0, 6)}...';
}
return CompositedTransformTarget(
link: _layerLink,
child: WebTheme.getMaterialWrapper(
child: InkWell(
key: _buttonKey,
onTap: _showDropdown,
borderRadius: BorderRadius.circular(4),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), // 大幅减少内边距
constraints: const BoxConstraints(
minWidth: 60,
maxWidth: 120, // 限制最大宽度避免溢出
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.tune,
size: 14, // 减小图标尺寸
color: WebTheme.getSecondaryTextColor(context),
),
const SizedBox(width: 4),
Flexible( // 使用 Flexible 而不是固定宽度
child: Text(
displayText,
style: WebTheme.labelSmall.copyWith(
fontWeight: FontWeight.w600,
color: WebTheme.getSecondaryTextColor(context),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 2),
Icon(
Icons.keyboard_arrow_down,
size: 12, // 减小图标尺寸
color: WebTheme.getSecondaryTextColor(context),
),
],
),
),
),
),
);
}
}