马良AI写作初始化仓库
This commit is contained in:
533
AINoval/lib/screens/admin/widgets/template_review_dialog.dart
Normal file
533
AINoval/lib/screens/admin/widgets/template_review_dialog.dart
Normal file
@@ -0,0 +1,533 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/prompt_models.dart';
|
||||
import '../../../utils/logger.dart';
|
||||
|
||||
/// 模板审核对话框
|
||||
class TemplateReviewDialog extends StatefulWidget {
|
||||
final EnhancedUserPromptTemplate template;
|
||||
final Function(bool approved, String? comment) onReview;
|
||||
|
||||
const TemplateReviewDialog({
|
||||
Key? key,
|
||||
required this.template,
|
||||
required this.onReview,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<TemplateReviewDialog> createState() => _TemplateReviewDialogState();
|
||||
}
|
||||
|
||||
class _TemplateReviewDialogState extends State<TemplateReviewDialog> {
|
||||
final _reviewCommentController = TextEditingController();
|
||||
|
||||
String _reviewAction = 'approve'; // 'approve', 'reject'
|
||||
bool _setAsVerified = false;
|
||||
bool _isLoading = false;
|
||||
|
||||
static const Map<String, String> _actionLabels = {
|
||||
'approve': '通过审核',
|
||||
'reject': '拒绝',
|
||||
};
|
||||
|
||||
static const Map<String, Color> _actionColors = {
|
||||
'approve': Colors.green,
|
||||
'reject': Colors.red,
|
||||
};
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_reviewCommentController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
child: Container(
|
||||
width: 700,
|
||||
constraints: const BoxConstraints(maxHeight: 800),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTemplateInfo(),
|
||||
const SizedBox(height: 24),
|
||||
_buildTemplateContent(),
|
||||
const SizedBox(height: 24),
|
||||
_buildReviewSection(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
_buildActionButtons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.rate_review, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'模板审核',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Spacer(),
|
||||
_buildStatusChip(),
|
||||
const SizedBox(width: 16),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip() {
|
||||
String status;
|
||||
Color color;
|
||||
|
||||
if (widget.template.isVerified == true) {
|
||||
status = '已认证';
|
||||
color = Colors.green;
|
||||
} else if (widget.template.isPublic == true) {
|
||||
status = '已发布';
|
||||
color = Colors.blue;
|
||||
} else {
|
||||
status = '待审核';
|
||||
color = Colors.orange;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: color,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemplateInfo() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.info_outline, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'模板信息',
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 基本信息
|
||||
_buildInfoRow('模板名称', widget.template.name),
|
||||
if (widget.template.description?.isNotEmpty == true)
|
||||
_buildInfoRow('描述', widget.template.description!),
|
||||
_buildInfoRow('功能类型', _getFeatureTypeLabel(widget.template.featureType.toApiString())),
|
||||
if (widget.template.authorId?.isNotEmpty == true)
|
||||
_buildInfoRow('作者', widget.template.authorId!),
|
||||
_buildInfoRow('版本', (widget.template.version ?? 1).toString()),
|
||||
_buildInfoRow('语言', widget.template.language ?? 'zh'),
|
||||
_buildInfoRow('创建时间', _formatDateTime(widget.template.createdAt)),
|
||||
_buildInfoRow('使用次数', '${widget.template.usageCount} 次'),
|
||||
_buildInfoRow('收藏次数', '${widget.template.favoriteCount ?? 0} 次'),
|
||||
if (widget.template.rating > 0)
|
||||
_buildInfoRow('评分', widget.template.rating.toStringAsFixed(1)),
|
||||
|
||||
// 标签
|
||||
if (widget.template.tags.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'标签:',
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: widget.template.tags.map((tag) =>
|
||||
_buildTag(tag)).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(value),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTag(String tag) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemplateContent() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'模板内容',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.template.systemPrompt.isNotEmpty) ...[
|
||||
Text(
|
||||
'系统提示词:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
widget.template.systemPrompt,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
if (widget.template.userPrompt.isNotEmpty) ...[
|
||||
Text(
|
||||
'用户提示词:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
widget.template.userPrompt,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildReviewSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'审核操作',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 审核动作选择
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'审核结果:',
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Column(
|
||||
children: _actionLabels.entries.map((entry) {
|
||||
return RadioListTile<String>(
|
||||
title: Text(
|
||||
entry.value,
|
||||
style: TextStyle(
|
||||
color: _actionColors[entry.key],
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
value: entry.key,
|
||||
groupValue: _reviewAction,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_reviewAction = value!;
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
if (_reviewAction == 'approve') ...[
|
||||
const SizedBox(height: 12),
|
||||
CheckboxListTile(
|
||||
title: const Text('同时设为认证模板'),
|
||||
subtitle: const Text('为该模板添加官方认证标识'),
|
||||
value: _setAsVerified,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_setAsVerified = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 审核评论
|
||||
TextFormField(
|
||||
controller: _reviewCommentController,
|
||||
decoration: InputDecoration(
|
||||
labelText: '审核备注',
|
||||
hintText: _getCommentHint(),
|
||||
border: const OutlineInputBorder(),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
maxLines: 4,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getCommentHint() {
|
||||
switch (_reviewAction) {
|
||||
case 'approve':
|
||||
return '可以添加通过审核的说明(可选)';
|
||||
case 'reject':
|
||||
return '请说明拒绝的原因';
|
||||
case 'request_changes':
|
||||
return '请详细说明需要修改的内容';
|
||||
default:
|
||||
return '请输入审核备注';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildActionButtons() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _isLoading ? null : () => Navigator.of(context).pop(),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _submitReview,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _actionColors[_reviewAction],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: Text(_actionLabels[_reviewAction]!),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _submitReview() async {
|
||||
if (_reviewAction == 'reject') {
|
||||
if (_reviewCommentController.text.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('拒绝审核时请填写审核备注')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final reviewComment = _reviewCommentController.text.trim();
|
||||
final approved = _reviewAction == 'approve';
|
||||
|
||||
await widget.onReview(approved, reviewComment.isEmpty ? null : reviewComment);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.e('TemplateReviewDialog', '提交模板审核失败', e);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('提交失败: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getFeatureTypeLabel(String? featureType) {
|
||||
switch (featureType) {
|
||||
case 'AI_CHAT':
|
||||
return 'AI聊天';
|
||||
case 'TEXT_EXPANSION':
|
||||
return '文本扩写';
|
||||
case 'TEXT_REFACTOR':
|
||||
return '文本润色';
|
||||
case 'TEXT_SUMMARY':
|
||||
return '文本总结';
|
||||
case 'SCENE_TO_SUMMARY':
|
||||
return '场景转摘要';
|
||||
case 'SUMMARY_TO_SCENE':
|
||||
return '摘要转场景';
|
||||
case 'NOVEL_GENERATION':
|
||||
return '小说生成';
|
||||
case 'PROFESSIONAL_FICTION_CONTINUATION':
|
||||
return '专业续写';
|
||||
case 'SCENE_BEAT_GENERATION':
|
||||
return '场景节拍生成';
|
||||
default:
|
||||
return featureType ?? '未知';
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime? dateTime) {
|
||||
if (dateTime == null) return '';
|
||||
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(dateTime);
|
||||
|
||||
if (difference.inDays > 0) {
|
||||
return '${difference.inDays}天前';
|
||||
} else if (difference.inHours > 0) {
|
||||
return '${difference.inHours}小时前';
|
||||
} else if (difference.inMinutes > 0) {
|
||||
return '${difference.inMinutes}分钟前';
|
||||
} else {
|
||||
return '刚刚';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user