马良AI写作初始化仓库
This commit is contained in:
501
AINoval/lib/widgets/common/prompt_preview_widget.dart
Normal file
501
AINoval/lib/widgets/common/prompt_preview_widget.dart
Normal file
@@ -0,0 +1,501 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_syntax_view/flutter_syntax_view.dart';
|
||||
import 'package:ainoval/models/ai_request_models.dart';
|
||||
import 'package:ainoval/utils/web_theme.dart';
|
||||
import 'package:ainoval/utils/content_formatter.dart';
|
||||
|
||||
/// 提示词预览组件
|
||||
/// 用于显示AI请求的预览内容,使用固定宽度布局,根据内容决定长度
|
||||
class PromptPreviewWidget extends StatefulWidget {
|
||||
const PromptPreviewWidget({
|
||||
super.key,
|
||||
required this.previewResponse,
|
||||
this.onCopyToClipboard,
|
||||
this.showActions = true,
|
||||
this.fixedWidth = 680.0, // 固定宽度,可以根据需要调整
|
||||
});
|
||||
|
||||
/// 预览响应数据
|
||||
final UniversalAIPreviewResponse previewResponse;
|
||||
|
||||
/// 复制到剪贴板回调
|
||||
final VoidCallback? onCopyToClipboard;
|
||||
|
||||
/// 是否显示操作按钮
|
||||
final bool showActions;
|
||||
|
||||
/// 固定宽度
|
||||
final double fixedWidth;
|
||||
|
||||
@override
|
||||
State<PromptPreviewWidget> createState() => _PromptPreviewWidgetState();
|
||||
}
|
||||
|
||||
class _PromptPreviewWidgetState extends State<PromptPreviewWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = WebTheme.isDarkMode(context);
|
||||
|
||||
return Container(
|
||||
width: widget.fixedWidth,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(4), // 最小内边距,紧贴表单边缘
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 顶部统计和操作栏
|
||||
_buildHeaderActions(context, isDark),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// 系统提示词部分
|
||||
if (widget.previewResponse.systemPrompt.isNotEmpty) ...[
|
||||
_buildPromptSection(
|
||||
context: context,
|
||||
isDark: isDark,
|
||||
title: '系统提示词',
|
||||
content: widget.previewResponse.systemPrompt,
|
||||
wordCount: widget.previewResponse.systemPromptWordCount,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
|
||||
// 用户提示词部分
|
||||
if (widget.previewResponse.userPrompt.isNotEmpty) ...[
|
||||
_buildPromptSection(
|
||||
context: context,
|
||||
isDark: isDark,
|
||||
title: '用户提示词',
|
||||
content: widget.previewResponse.userPrompt,
|
||||
wordCount: widget.previewResponse.userPromptWordCount,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
|
||||
// 上下文信息部分(如果有)
|
||||
if (widget.previewResponse.context != null && widget.previewResponse.context!.isNotEmpty) ...[
|
||||
_buildPromptSection(
|
||||
context: context,
|
||||
isDark: isDark,
|
||||
title: '上下文信息',
|
||||
content: widget.previewResponse.context!,
|
||||
wordCount: widget.previewResponse.contextWordCount,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建顶部统计和操作栏
|
||||
Widget _buildHeaderActions(BuildContext context, bool isDark) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), // 减少内边距
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? WebTheme.darkGrey50 : WebTheme.grey50,
|
||||
borderRadius: BorderRadius.circular(4), // 减少圆角
|
||||
border: Border.all(
|
||||
color: isDark ? WebTheme.darkGrey200 : WebTheme.grey200,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 预览图标
|
||||
Icon(
|
||||
Icons.preview_outlined,
|
||||
size: 14, // 减少图标大小
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
|
||||
Text(
|
||||
'提示词预览',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: WebTheme.getTextColor(context, isPrimary: true),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13, // 减少字体大小
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// 复制到剪贴板按钮
|
||||
if (widget.showActions) ...[
|
||||
_buildActionButton(
|
||||
context: context,
|
||||
isDark: isDark,
|
||||
icon: Icons.content_copy_outlined,
|
||||
label: '复制',
|
||||
onPressed: () => _copyToClipboard(context, widget.previewResponse.preview),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
|
||||
// 总字数统计
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), // 减少内边距
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(3), // 减少圆角
|
||||
),
|
||||
child: Text(
|
||||
'${widget.previewResponse.totalWordCount} 字',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10, // 减少字体大小
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建提示词区块
|
||||
Widget _buildPromptSection({
|
||||
required BuildContext context,
|
||||
required bool isDark,
|
||||
required String title,
|
||||
required String content,
|
||||
required int wordCount,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 区块标题和操作
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), // 减少内边距
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? WebTheme.darkGrey100 : WebTheme.grey100,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(4), // 减少圆角
|
||||
topRight: Radius.circular(4),
|
||||
),
|
||||
border: Border.all(
|
||||
color: isDark ? WebTheme.darkGrey300 : WebTheme.grey300,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: WebTheme.getTextColor(context, isPrimary: true),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12, // 减少字体大小
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// 字数统计
|
||||
Text(
|
||||
'$wordCount 字',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
fontSize: 10, // 减少字体大小
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// 复制按钮
|
||||
_buildActionButton(
|
||||
context: context,
|
||||
isDark: isDark,
|
||||
icon: Icons.content_copy_outlined,
|
||||
label: '复制',
|
||||
isSmall: true,
|
||||
onPressed: () => _copyToClipboard(context, content),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 内容区域 - 固定宽度,根据内容决定高度
|
||||
_buildContentArea(context, isDark, content),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建内容区域
|
||||
Widget _buildContentArea(BuildContext context, bool isDark, String content) {
|
||||
// 计算内容行数来决定高度
|
||||
final lines = content.split('\n');
|
||||
final contentHeight = (lines.length * 18.0) + 16.0; // 每行18px高度 + 减少上下padding
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 50, // 减少最小高度
|
||||
maxHeight: contentHeight > 250 ? 250 : contentHeight, // 减少最大高度
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: isDark ? WebTheme.darkGrey300 : WebTheme.grey300,
|
||||
width: 1,
|
||||
),
|
||||
right: BorderSide(
|
||||
color: isDark ? WebTheme.darkGrey300 : WebTheme.grey300,
|
||||
width: 1,
|
||||
),
|
||||
bottom: BorderSide(
|
||||
color: isDark ? WebTheme.darkGrey300 : WebTheme.grey300,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(4), // 减少圆角
|
||||
bottomRight: Radius.circular(4),
|
||||
),
|
||||
color: isDark ? WebTheme.darkGrey50 : WebTheme.white,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 行号区域
|
||||
Container(
|
||||
width: 35, // 减少宽度
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 50,
|
||||
maxHeight: contentHeight > 250 ? 250 : contentHeight,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? WebTheme.darkGrey100 : WebTheme.grey50,
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: isDark ? WebTheme.darkGrey300 : WebTheme.grey300,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: _buildLineNumbers(lines),
|
||||
),
|
||||
|
||||
// 内容区域
|
||||
Expanded(
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 50,
|
||||
maxHeight: contentHeight > 250 ? 250 : contentHeight,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8), // 减少内边距
|
||||
child: SelectableText(
|
||||
content,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Courier New',
|
||||
fontSize: 12, // 减少字体大小
|
||||
height: 1.4, // 调整行高
|
||||
color: isDark ? WebTheme.darkGrey800 : WebTheme.grey800,
|
||||
letterSpacing: 0.1, // 减少字符间距
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建行号
|
||||
Widget _buildLineNumbers(List<String> lines) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8), // 减少内边距
|
||||
child: Column(
|
||||
children: List.generate(lines.length, (index) {
|
||||
return Container(
|
||||
height: 16.8, // 匹配调整后的文本行高 (12 * 1.4)
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${index + 1}',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Courier New',
|
||||
fontSize: 9, // 减少字体大小
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建操作按钮
|
||||
Widget _buildActionButton({
|
||||
required BuildContext context,
|
||||
required bool isDark,
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback onPressed,
|
||||
bool isSmall = false,
|
||||
}) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
borderRadius: BorderRadius.circular(3), // 减少圆角
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isSmall ? 4 : 6, // 减少内边距
|
||||
vertical: isSmall ? 2 : 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: isDark ? WebTheme.darkGrey400 : WebTheme.grey400,
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(3), // 减少圆角
|
||||
color: isDark ? WebTheme.darkGrey100 : WebTheme.white,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: isSmall ? 10 : 12, // 减少图标大小
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
if (!isSmall) ...[
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
fontSize: 10, // 减少字体大小
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 复制到剪贴板
|
||||
void _copyToClipboard(BuildContext context, String text) {
|
||||
Clipboard.setData(ClipboardData(text: text));
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('已复制到剪贴板'),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: Colors.green.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: const EdgeInsets.all(16),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 提示词预览加载组件
|
||||
/// 用于显示加载状态,加载图标位于中央
|
||||
class PromptPreviewLoadingWidget extends StatelessWidget {
|
||||
const PromptPreviewLoadingWidget({
|
||||
super.key,
|
||||
this.message = '正在生成预览...',
|
||||
});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
message,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 提示词预览对话框
|
||||
/// 独立的对话框版本,可以单独使用
|
||||
class PromptPreviewDialog extends StatelessWidget {
|
||||
const PromptPreviewDialog({
|
||||
super.key,
|
||||
required this.previewResponse,
|
||||
this.onGenerate,
|
||||
});
|
||||
|
||||
final UniversalAIPreviewResponse previewResponse;
|
||||
final VoidCallback? onGenerate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(Icons.preview_outlined),
|
||||
const SizedBox(width: 8),
|
||||
const Text('提示词预览'),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 720,
|
||||
height: 600,
|
||||
child: PromptPreviewWidget(
|
||||
previewResponse: previewResponse,
|
||||
showActions: true,
|
||||
fixedWidth: 680,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('关闭'),
|
||||
),
|
||||
if (onGenerate != null)
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
onGenerate!();
|
||||
},
|
||||
child: const Text('生成'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示提示词预览对话框的便捷函数
|
||||
Future<void> showPromptPreviewDialog(
|
||||
BuildContext context, {
|
||||
required UniversalAIPreviewResponse previewResponse,
|
||||
VoidCallback? onGenerate,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) => PromptPreviewDialog(
|
||||
previewResponse: previewResponse,
|
||||
onGenerate: onGenerate,
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user