马良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,758 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import '../../models/preset_models.dart';
import '../../services/api_service/repositories/impl/admin_repository_impl.dart';
import '../../utils/logger.dart';
import '../../utils/web_theme.dart';
import '../../widgets/common/error_view.dart';
import '../../widgets/common/loading_indicator.dart';
import 'widgets/add_system_preset_dialog.dart';
import 'widgets/edit_system_preset_dialog.dart';
import 'widgets/system_preset_card.dart';
import 'package:flutter/services.dart';
/// 系统预设管理页面
/// 提供完整的系统AI预设管理功能包括
/// - 按功能类型分组显示系统预设
/// - 添加/编辑/删除系统预设
/// - 预设可见性管理
/// - 批量操作功能
/// - 使用统计查看
class SystemPresetsManagementScreen extends StatefulWidget {
const SystemPresetsManagementScreen({Key? key}) : super(key: key);
@override
State<SystemPresetsManagementScreen> createState() => _SystemPresetsManagementScreenState();
}
/// 系统预设管理内容主体,可以在不同布局中复用
class SystemPresetsManagementBody extends StatefulWidget {
const SystemPresetsManagementBody({Key? key}) : super(key: key);
@override
State<SystemPresetsManagementBody> createState() => _SystemPresetsManagementBodyState();
}
class _SystemPresetsManagementScreenState extends State<SystemPresetsManagementScreen> {
final GlobalKey<_SystemPresetsManagementBodyState> _bodyKey = GlobalKey<_SystemPresetsManagementBodyState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: WebTheme.getBackgroundColor(context),
foregroundColor: WebTheme.getTextColor(context),
title: Text(
'系统预设管理',
style: TextStyle(color: WebTheme.getTextColor(context)),
),
actions: [
IconButton(
onPressed: () => _bodyKey.currentState?._refreshData(),
icon: Icon(Icons.refresh, color: WebTheme.getTextColor(context)),
tooltip: '刷新',
),
IconButton(
onPressed: () => _bodyKey.currentState?._showStatistics(),
icon: Icon(Icons.analytics, color: WebTheme.getTextColor(context)),
tooltip: '统计信息',
),
IconButton(
onPressed: () => _showAddPresetDialog(context),
icon: Icon(Icons.add, color: WebTheme.getTextColor(context)),
tooltip: '添加系统预设',
),
],
),
backgroundColor: WebTheme.getBackgroundColor(context),
body: SystemPresetsManagementBody(key: _bodyKey),
);
}
void _showAddPresetDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AddSystemPresetDialog(
onSuccess: () => _bodyKey.currentState?._refreshData(),
),
);
}
}
class _SystemPresetsManagementBodyState extends State<SystemPresetsManagementBody> {
List<AIPromptPreset> _systemPresets = [];
Map<String, List<AIPromptPreset>> _presetsByFeatureType = {};
Map<String, dynamic> _statistics = {};
bool _isLoading = true;
String? _error;
String _selectedFeatureType = 'ALL';
List<String> _selectedPresets = [];
bool _batchMode = false;
final AdminRepositoryImpl _adminRepository = AdminRepositoryImpl();
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
await Future.wait([
_loadSystemPresets(),
_loadStatistics(),
]);
} catch (e) {
AppLogger.e('SystemPresetsManagement', '加载系统预设数据失败', e);
setState(() {
_error = e.toString();
});
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _loadSystemPresets() async {
try {
final presets = await _adminRepository.getSystemPresets(
featureType: _selectedFeatureType == 'ALL' ? null : _selectedFeatureType,
);
setState(() {
_systemPresets = presets;
_presetsByFeatureType = _groupPresetsByFeatureType(presets);
});
} catch (e) {
AppLogger.e('SystemPresetsManagement', '加载系统预设失败', e);
rethrow;
}
}
Future<void> _loadStatistics() async {
try {
final stats = await _adminRepository.getSystemPresetsStatistics();
setState(() {
_statistics = stats;
});
} catch (e) {
AppLogger.e('SystemPresetsManagement', '加载统计信息失败', e);
// 统计信息加载失败不影响主要功能
}
}
Map<String, List<AIPromptPreset>> _groupPresetsByFeatureType(List<AIPromptPreset> presets) {
return groupBy(presets, (preset) => preset.aiFeatureType);
}
void _refreshData() {
_loadData();
}
void _showStatistics() {
showDialog(
context: context,
builder: (context) => _StatisticsDialog(statistics: _statistics),
);
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Center(child: LoadingIndicator());
}
if (_error != null) {
return ErrorView(
error: _error!,
onRetry: _refreshData,
);
}
return Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1600),
child: Column(
children: [
_buildToolbar(),
_buildFilterTabs(),
Expanded(
child: _buildPresetsList(),
),
],
),
),
);
}
Widget _buildToolbar() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: WebTheme.getCardColor(context),
border: Border(
bottom: BorderSide(
color: WebTheme.getBorderColor(context),
width: 1,
),
),
),
child: Row(
children: [
if (_batchMode) ...[
Expanded(
child: Text(
'已选择 ${_selectedPresets.length} 个预设',
style: TextStyle(
color: WebTheme.getTextColor(context),
fontWeight: FontWeight.w500,
),
),
),
IconButton(
onPressed: _selectedPresets.isNotEmpty ? () => _batchToggleVisibility(true) : null,
icon: const Icon(Icons.visibility),
tooltip: '批量显示在快捷访问',
),
IconButton(
onPressed: _selectedPresets.isNotEmpty ? () => _batchToggleVisibility(false) : null,
icon: const Icon(Icons.visibility_off),
tooltip: '批量隐藏快捷访问',
),
IconButton(
onPressed: _selectedPresets.isNotEmpty ? _batchExport : null,
icon: const Icon(Icons.file_download),
tooltip: '导出选中预设',
),
IconButton(
onPressed: () {
setState(() {
_batchMode = false;
_selectedPresets.clear();
});
},
icon: const Icon(Icons.close),
tooltip: '退出批量模式',
),
] else ...[
Expanded(
child: Text(
'系统预设总数: ${_systemPresets.length}',
style: TextStyle(
color: WebTheme.getTextColor(context),
fontWeight: FontWeight.w500,
),
),
),
IconButton(
onPressed: _importPresets,
icon: const Icon(Icons.file_upload),
tooltip: '导入预设',
),
IconButton(
onPressed: () {
setState(() {
_batchMode = true;
});
},
icon: const Icon(Icons.checklist),
tooltip: '批量操作',
),
],
],
),
);
}
Widget _buildFilterTabs() {
final featureTypes = ['ALL', ..._presetsByFeatureType.keys.toList()..sort()];
return Container(
height: 50,
decoration: BoxDecoration(
color: WebTheme.getCardColor(context),
border: Border(
bottom: BorderSide(
color: WebTheme.getBorderColor(context),
width: 1,
),
),
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: featureTypes.length,
itemBuilder: (context, index) {
final featureType = featureTypes[index];
final isSelected = _selectedFeatureType == featureType;
final count = featureType == 'ALL'
? _systemPresets.length
: _presetsByFeatureType[featureType]?.length ?? 0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: ChoiceChip(
label: Text('$featureType ($count)'),
selected: isSelected,
onSelected: (selected) {
if (selected) {
setState(() {
_selectedFeatureType = featureType;
});
_loadSystemPresets();
}
},
),
);
},
),
);
}
Widget _buildPresetsList() {
if (_systemPresets.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.smart_button_outlined,
size: 64,
color: WebTheme.getTextColor(context).withOpacity(0.5),
),
const SizedBox(height: 16),
Text(
'暂无系统预设',
style: TextStyle(
color: WebTheme.getTextColor(context).withOpacity(0.7),
fontSize: 16,
),
),
const SizedBox(height: 8),
Text(
'点击右上角的加号创建第一个系统预设',
style: TextStyle(
color: WebTheme.getTextColor(context).withOpacity(0.5),
fontSize: 14,
),
),
],
),
);
}
final displayPresets = _selectedFeatureType == 'ALL'
? _systemPresets
: _presetsByFeatureType[_selectedFeatureType] ?? [];
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: displayPresets.length,
itemBuilder: (context, index) {
final preset = displayPresets[index];
return SystemPresetCard(
preset: preset,
isSelected: _selectedPresets.contains(preset.presetId),
batchMode: _batchMode,
onTap: () => _handlePresetTap(preset),
onEdit: () => _editPreset(preset),
onDelete: () => _deletePreset(preset),
onToggleVisibility: () => _togglePresetVisibility(preset),
onViewStats: () => _viewPresetStats(preset),
onViewDetails: () => _viewPresetDetails(preset),
onSelectionChanged: (selected) {
setState(() {
if (selected) {
_selectedPresets.add(preset.presetId);
} else {
_selectedPresets.remove(preset.presetId);
}
});
},
);
},
);
}
void _viewPresetDetails(AIPromptPreset preset) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('预设内容 - ${preset.presetName ?? ''}'),
content: SizedBox(
width: 700,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildPromptPreviewSection('系统提示词 (System Prompt)', preset.systemPrompt),
const SizedBox(height: 16),
_buildPromptPreviewSection('用户提示词 (User Prompt)', preset.userPrompt.isNotEmpty ? preset.userPrompt : '(未设置)'),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('关闭'),
),
],
);
},
);
}
Widget _buildPromptPreviewSection(String title, String content) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.code, size: 18),
const SizedBox(width: 8),
Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
const Spacer(),
if (content.isNotEmpty)
IconButton(
icon: const Icon(Icons.copy, size: 18),
tooltip: '复制',
onPressed: () {
Clipboard.setData(ClipboardData(text: content));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制到剪贴板')),
);
},
),
],
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withOpacity(0.2)),
),
child: SelectableText(
content.isNotEmpty ? content : '(空)',
style: const TextStyle(fontFamily: 'monospace', fontSize: 13, height: 1.4),
),
),
],
);
}
void _handlePresetTap(AIPromptPreset preset) {
if (_batchMode) {
final isSelected = _selectedPresets.contains(preset.presetId);
setState(() {
if (isSelected) {
_selectedPresets.remove(preset.presetId);
} else {
_selectedPresets.add(preset.presetId);
}
});
} else {
_editPreset(preset);
}
}
void _editPreset(AIPromptPreset preset) {
showDialog(
context: context,
builder: (context) => EditSystemPresetDialog(
preset: preset,
onSuccess: _refreshData,
),
);
}
Future<void> _deletePreset(AIPromptPreset preset) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除系统预设 "${preset.presetName}" 吗?此操作不可撤销。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('删除'),
),
],
),
);
if (confirmed == true) {
try {
await _adminRepository.deleteSystemPreset(preset.presetId);
_refreshData();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('系统预设 "${preset.presetName}" 已删除')),
);
}
} catch (e) {
AppLogger.e('SystemPresetsManagement', '删除系统预设失败', e);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('删除失败: $e')),
);
}
}
}
}
Future<void> _togglePresetVisibility(AIPromptPreset preset) async {
try {
await _adminRepository.toggleSystemPresetQuickAccess(preset.presetId);
_refreshData();
if (mounted) {
final status = !preset.showInQuickAccess ? '显示在快捷访问' : '隐藏快捷访问';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('预设 "${preset.presetName}" 已$status')),
);
}
} catch (e) {
AppLogger.e('SystemPresetsManagement', '切换预设可见性失败', e);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('操作失败: $e')),
);
}
}
}
void _viewPresetStats(AIPromptPreset preset) {
showDialog(
context: context,
builder: (context) => _PresetStatsDialog(presetId: preset.presetId),
);
}
Future<void> _batchToggleVisibility(bool visible) async {
try {
await _adminRepository.batchUpdateSystemPresetsVisibility(_selectedPresets, visible);
_refreshData();
if (mounted) {
final action = visible ? '显示在快捷访问' : '隐藏快捷访问';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已将 ${_selectedPresets.length} 个预设$action')),
);
}
setState(() {
_selectedPresets.clear();
_batchMode = false;
});
} catch (e) {
AppLogger.e('SystemPresetsManagement', '批量更新可见性失败', e);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('批量操作失败: $e')),
);
}
}
}
Future<void> _batchExport() async {
try {
final presets = await _adminRepository.exportSystemPresets(_selectedPresets);
// TODO: 实现文件导出功能
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已导出 ${presets.length} 个系统预设')),
);
}
} catch (e) {
AppLogger.e('SystemPresetsManagement', '导出预设失败', e);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('导出失败: $e')),
);
}
}
}
Future<void> _importPresets() async {
// TODO: 实现预设导入功能
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('导入功能开发中...')),
);
}
}
/// 统计信息对话框
class _StatisticsDialog extends StatelessWidget {
final Map<String, dynamic> statistics;
const _StatisticsDialog({required this.statistics});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('系统预设统计'),
content: SizedBox(
width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStatItem('总预设数', statistics['totalSystemPresets']?.toString() ?? '0'),
_buildStatItem('快捷访问预设', statistics['quickAccessCount']?.toString() ?? '0'),
_buildStatItem('总使用次数', statistics['totalUsage']?.toString() ?? '0'),
const SizedBox(height: 16),
const Text('按功能类型分布:', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
if (statistics['byFeatureType'] is Map<String, dynamic>)
...(statistics['byFeatureType'] as Map<String, dynamic>).entries.map(
(entry) => Padding(
padding: const EdgeInsets.only(left: 16, bottom: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(entry.key),
Text(entry.value.toString()),
],
),
),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('关闭'),
),
],
);
}
Widget _buildStatItem(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.w500)),
Text(value, style: const TextStyle(fontSize: 16)),
],
),
);
}
}
/// 预设统计对话框
class _PresetStatsDialog extends StatefulWidget {
final String presetId;
const _PresetStatsDialog({required this.presetId});
@override
State<_PresetStatsDialog> createState() => _PresetStatsDialogState();
}
class _PresetStatsDialogState extends State<_PresetStatsDialog> {
Map<String, dynamic>? _details;
bool _isLoading = true;
String? _error;
final AdminRepositoryImpl _adminRepository = AdminRepositoryImpl();
@override
void initState() {
super.initState();
_loadDetails();
}
Future<void> _loadDetails() async {
try {
final details = await _adminRepository.getSystemPresetDetails(widget.presetId);
setState(() {
_details = details;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('预设详情'),
content: SizedBox(
width: 400,
height: 300,
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(child: Text('加载失败: $_error'))
: _buildDetails(),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('关闭'),
),
],
);
}
Widget _buildDetails() {
if (_details == null) return const SizedBox();
final preset = _details!['preset'] as Map<String, dynamic>?;
final statistics = _details!['statistics'] as Map<String, dynamic>?;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (preset != null) ...[
Text('预设名称: ${preset['presetName'] ?? ''}'),
Text('功能类型: ${preset['aiFeatureType'] ?? ''}'),
Text('创建时间: ${preset['createdAt'] ?? ''}'),
Text('最后更新: ${preset['updatedAt'] ?? ''}'),
const SizedBox(height: 16),
const Text('使用统计:', style: TextStyle(fontWeight: FontWeight.bold)),
],
if (statistics != null) ...[
Text('使用次数: ${statistics['useCount'] ?? 0}'),
Text('最后使用: ${statistics['lastUsedAt'] ?? '从未使用'}'),
Text('创建天数: ${statistics['daysSinceCreated'] ?? 0}'),
if (statistics['daysSinceLastUsed'] != null)
Text('上次使用距今: ${statistics['daysSinceLastUsed']}'),
],
],
),
);
}
}