702 lines
21 KiB
Dart
702 lines
21 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
import '../../../models/prompt_models.dart';
|
|
import '../../../utils/web_theme.dart';
|
|
import '../../../widgets/common/dialog_container.dart';
|
|
import '../../../widgets/common/dialog_header.dart';
|
|
|
|
/// 模板详情查看对话框
|
|
class TemplateDetailsDialog extends StatefulWidget {
|
|
final EnhancedUserPromptTemplate template;
|
|
final Map<String, Object>? statistics;
|
|
|
|
const TemplateDetailsDialog({
|
|
Key? key,
|
|
required this.template,
|
|
this.statistics,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<TemplateDetailsDialog> createState() => _TemplateDetailsDialogState();
|
|
}
|
|
|
|
class _TemplateDetailsDialogState extends State<TemplateDetailsDialog> with TickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 3, vsync: this);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DialogContainer(
|
|
maxWidth: 900,
|
|
height: 700,
|
|
child: Column(
|
|
children: [
|
|
DialogHeader(
|
|
title: '模板详情 - ${widget.template.name}',
|
|
onClose: () => Navigator.of(context).pop(),
|
|
),
|
|
_buildTabs(),
|
|
Expanded(
|
|
child: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
_buildBasicInfoTab(),
|
|
_buildContentTab(),
|
|
_buildStatisticsTab(),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTabs() {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: WebTheme.getBorderColor(context),
|
|
),
|
|
),
|
|
),
|
|
child: TabBar(
|
|
controller: _tabController,
|
|
labelColor: WebTheme.getTextColor(context),
|
|
unselectedLabelColor: WebTheme.getTextColor(context).withOpacity(0.6),
|
|
indicatorColor: WebTheme.getPrimaryColor(context),
|
|
tabs: const [
|
|
Tab(
|
|
icon: Icon(Icons.info),
|
|
text: '基础信息',
|
|
),
|
|
Tab(
|
|
icon: Icon(Icons.code),
|
|
text: '提示词内容',
|
|
),
|
|
Tab(
|
|
icon: Icon(Icons.analytics),
|
|
text: '统计信息',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBasicInfoTab() {
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildInfoSection(),
|
|
const SizedBox(height: 24),
|
|
_buildStatusSection(),
|
|
const SizedBox(height: 24),
|
|
_buildTagsSection(),
|
|
const SizedBox(height: 24),
|
|
_buildMetadataSection(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoSection() {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.info, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'基本信息',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildInfoRow('模板名称', widget.template.name),
|
|
_buildInfoRow('模板ID', widget.template.id),
|
|
_buildInfoRow('功能类型', _getFeatureTypeLabel(widget.template.featureType.toApiString())),
|
|
_buildInfoRow('语言', _getLanguageLabel(widget.template.language)),
|
|
_buildInfoRow('版本', (widget.template.version ?? 1).toString()),
|
|
if (widget.template.description?.isNotEmpty == true)
|
|
_buildInfoRow('描述', widget.template.description!, maxLines: 3),
|
|
_buildInfoRow('作者ID', widget.template.authorId ?? '未知'),
|
|
_buildInfoRow('创建时间', _formatDateTime(widget.template.createdAt)),
|
|
_buildInfoRow('更新时间', _formatDateTime(widget.template.updatedAt)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusSection() {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.flag, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'状态信息',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Wrap(
|
|
spacing: 12,
|
|
runSpacing: 12,
|
|
children: [
|
|
_buildStatusChip(
|
|
label: '公开状态',
|
|
value: widget.template.isPublic == true ? '已发布' : '私有',
|
|
color: widget.template.isPublic == true ? Colors.green : Colors.grey,
|
|
),
|
|
_buildStatusChip(
|
|
label: '认证状态',
|
|
value: widget.template.isVerified == true ? '已认证' : '未认证',
|
|
color: widget.template.isVerified == true ? Colors.blue : Colors.grey,
|
|
),
|
|
_buildStatusChip(
|
|
label: '评分',
|
|
value: widget.template.rating > 0 ? widget.template.rating.toStringAsFixed(1) : '无评分',
|
|
color: _getRatingColor(widget.template.rating),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTagsSection() {
|
|
if (widget.template.tags.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.label, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'标签',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: widget.template.tags.map((tag) => _buildTag(tag)).toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMetadataSection() {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.data_object, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'元数据',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildInfoRow('使用次数', widget.template.usageCount.toString()),
|
|
_buildInfoRow('收藏次数', (widget.template.favoriteCount ?? 0).toString()),
|
|
if (widget.template.reviewedAt != null)
|
|
_buildInfoRow('审核时间', _formatDateTime(widget.template.reviewedAt)),
|
|
if (widget.template.reviewedBy?.isNotEmpty == true)
|
|
_buildInfoRow('审核人', widget.template.reviewedBy!),
|
|
if (widget.template.reviewComment?.isNotEmpty == true)
|
|
_buildInfoRow('审核备注', widget.template.reviewComment!, maxLines: 2),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildContentTab() {
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 系统提示词部分
|
|
_buildPromptSection(
|
|
title: '系统提示词 (System Prompt)',
|
|
content: widget.template.systemPrompt.isNotEmpty
|
|
? widget.template.systemPrompt
|
|
: '未设置系统提示词',
|
|
icon: Icons.settings,
|
|
isEmpty: widget.template.systemPrompt.isEmpty,
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// 用户提示词部分
|
|
_buildPromptSection(
|
|
title: '用户提示词 (User Prompt)',
|
|
content: widget.template.userPrompt.isNotEmpty
|
|
? widget.template.userPrompt
|
|
: '未设置用户提示词',
|
|
icon: Icons.person,
|
|
isEmpty: widget.template.userPrompt.isEmpty,
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// 占位符提示
|
|
_buildPlaceholderInfo(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPromptSection({
|
|
required String title,
|
|
required String content,
|
|
required IconData icon,
|
|
bool isEmpty = false,
|
|
}) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
const Spacer(),
|
|
if (!isEmpty)
|
|
IconButton(
|
|
onPressed: () => _copyToClipboard(content),
|
|
icon: const Icon(Icons.copy, size: 18),
|
|
tooltip: '复制到剪贴板',
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
width: double.infinity,
|
|
constraints: const BoxConstraints(minHeight: 120),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isEmpty
|
|
? Colors.grey.withOpacity(0.05)
|
|
: Colors.grey.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(
|
|
color: isEmpty
|
|
? Colors.grey.withOpacity(0.1)
|
|
: Colors.grey.withOpacity(0.2),
|
|
),
|
|
),
|
|
child: SelectableText(
|
|
content,
|
|
style: TextStyle(
|
|
fontFamily: 'monospace',
|
|
fontSize: 14,
|
|
height: 1.4,
|
|
color: isEmpty
|
|
? WebTheme.getSecondaryTextColor(context)
|
|
: WebTheme.getTextColor(context),
|
|
fontStyle: isEmpty ? FontStyle.italic : FontStyle.normal,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建占位符信息
|
|
Widget _buildPlaceholderInfo() {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.code, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'占位符说明',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'提示词中可以使用占位符来动态插入内容,常用占位符包括:',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
_buildPlaceholderExample('{content}', '要处理的主要内容'),
|
|
_buildPlaceholderExample('{context}', '上下文信息'),
|
|
_buildPlaceholderExample('{requirement}', '具体要求'),
|
|
_buildPlaceholderExample('{style}', '风格要求'),
|
|
_buildPlaceholderExample('{length}', '长度要求'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPlaceholderExample(String placeholder, String description) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: WebTheme.getPrimaryColor(context).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(4),
|
|
border: Border.all(
|
|
color: WebTheme.getPrimaryColor(context).withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Text(
|
|
placeholder,
|
|
style: TextStyle(
|
|
fontFamily: 'monospace',
|
|
fontSize: 12,
|
|
color: WebTheme.getPrimaryColor(context),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
description,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: WebTheme.getSecondaryTextColor(context),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatisticsTab() {
|
|
final stats = widget.statistics ?? {};
|
|
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildStatsCard('使用统计', [
|
|
_buildStatRow('总使用次数', stats['usageCount']?.toString() ?? '0'),
|
|
_buildStatRow('本月使用', stats['monthlyUsage']?.toString() ?? '0'),
|
|
_buildStatRow('本周使用', stats['weeklyUsage']?.toString() ?? '0'),
|
|
_buildStatRow('今日使用', stats['dailyUsage']?.toString() ?? '0'),
|
|
], Icons.play_arrow),
|
|
const SizedBox(height: 24),
|
|
_buildStatsCard('用户反馈', [
|
|
_buildStatRow('收藏次数', stats['favoriteCount']?.toString() ?? '0'),
|
|
_buildStatRow('平均评分', stats['averageRating']?.toString() ?? '0.0'),
|
|
_buildStatRow('评分人数', stats['ratingCount']?.toString() ?? '0'),
|
|
_buildStatRow('反馈次数', stats['feedbackCount']?.toString() ?? '0'),
|
|
], Icons.favorite),
|
|
const SizedBox(height: 24),
|
|
_buildStatsCard('性能数据', [
|
|
_buildStatRow('平均响应时间', '${stats['averageResponseTime'] ?? 0}ms'),
|
|
_buildStatRow('成功率', '${stats['successRate'] ?? 100}%'),
|
|
_buildStatRow('错误次数', stats['errorCount']?.toString() ?? '0'),
|
|
_buildStatRow('最后使用时间', _formatDateTime(stats['lastUsedAt'] as DateTime?)),
|
|
], Icons.speed),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatsCard(String title, List<Widget> children, IconData icon) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: WebTheme.getTextColor(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
...children,
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatRow(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),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoRow(String label, String value, {int maxLines = 1}) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 120,
|
|
child: Text(
|
|
'$label:',
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
style: TextStyle(
|
|
color: WebTheme.getTextColor(context).withOpacity(0.8),
|
|
),
|
|
maxLines: maxLines,
|
|
overflow: maxLines > 1 ? TextOverflow.ellipsis : null,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusChip({
|
|
required String label,
|
|
required String value,
|
|
required Color color,
|
|
}) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: color.withOpacity(0.3)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: color,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: color,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTag(String tag) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: WebTheme.getPrimaryColor(context).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: WebTheme.getPrimaryColor(context).withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Text(
|
|
tag,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: WebTheme.getPrimaryColor(context),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
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 _getLanguageLabel(String? language) {
|
|
switch (language) {
|
|
case 'zh':
|
|
return '中文';
|
|
case 'en':
|
|
return 'English';
|
|
case 'ja':
|
|
return '日本語';
|
|
case 'ko':
|
|
return '한국어';
|
|
default:
|
|
return language ?? '中文';
|
|
}
|
|
}
|
|
|
|
Color _getRatingColor(double? rating) {
|
|
if (rating == null) return WebTheme.getSecondaryTextColor(context);
|
|
if (rating >= 4.5) return WebTheme.success;
|
|
if (rating >= 3.5) return WebTheme.warning;
|
|
if (rating >= 2.0) return WebTheme.error;
|
|
return WebTheme.getSecondaryTextColor(context);
|
|
}
|
|
|
|
String _formatDateTime(DateTime? dateTime) {
|
|
if (dateTime == null) return '未设置';
|
|
|
|
final now = DateTime.now();
|
|
final difference = now.difference(dateTime);
|
|
|
|
if (difference.inDays > 0) {
|
|
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}';
|
|
} else if (difference.inHours > 0) {
|
|
return '${difference.inHours}小时前';
|
|
} else if (difference.inMinutes > 0) {
|
|
return '${difference.inMinutes}分钟前';
|
|
} else {
|
|
return '刚刚';
|
|
}
|
|
}
|
|
|
|
void _copyToClipboard(String text) {
|
|
Clipboard.setData(ClipboardData(text: text));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('已复制到剪贴板'),
|
|
duration: Duration(seconds: 2),
|
|
),
|
|
);
|
|
}
|
|
} |