马良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,382 @@
import 'package:flutter/material.dart';
import 'package:ainoval/utils/web_theme.dart';
/// AI生成工具栏
/// 在流式输出文本时显示提供Apply、Retry、Discard、Section等操作
class AIGenerationToolbar extends StatefulWidget {
const AIGenerationToolbar({
super.key,
required this.layerLink,
required this.onApply,
required this.onRetry,
required this.onDiscard,
required this.onSection,
required this.wordCount,
required this.modelName,
this.isGenerating = false,
this.onClosed,
this.showAbove = false,
this.onStop,
this.offsetAbove = -60.0,
this.offsetBelow = 30.0,
});
/// 用于定位工具栏的层链接
final LayerLink layerLink;
/// 应用生成的文本
final VoidCallback onApply;
/// 重新生成
final VoidCallback onRetry;
/// 丢弃生成的文本
final VoidCallback onDiscard;
/// 分段功能
final VoidCallback onSection;
/// 停止生成
final VoidCallback? onStop;
/// 生成文本的字数
final int wordCount;
/// 使用的模型名称
final String modelName;
/// 是否正在生成中
final bool isGenerating;
/// 工具栏关闭回调
final VoidCallback? onClosed;
/// 是否显示在上方
final bool showAbove;
/// 上方显示时的Y偏移量
final double offsetAbove;
/// 下方显示时的Y偏移量
final double offsetBelow;
@override
State<AIGenerationToolbar> createState() => _AIGenerationToolbarState();
}
class _AIGenerationToolbarState extends State<AIGenerationToolbar> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final isLight = !isDark;
return CompositedTransformFollower(
link: widget.layerLink,
offset: widget.showAbove ? Offset(0, widget.offsetAbove) : Offset(0, widget.offsetBelow),
followerAnchor: Alignment.topCenter,
targetAnchor: Alignment.topCenter,
showWhenUnlinked: false,
child: MouseRegion(
cursor: SystemMouseCursors.click,
opaque: true,
hitTestBehavior: HitTestBehavior.opaque,
child: Material(
type: MaterialType.transparency,
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
child: _buildToolbarContainer(isLightTheme: isLight),
),
),
),
);
}
/// 构建工具栏容器
Widget _buildToolbarContainer({required bool isLightTheme}) {
return Container(
decoration: BoxDecoration(
// 统一使用 WebTheme 色系
color: isLightTheme ? WebTheme.black : WebTheme.getSurfaceColor(context),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: WebTheme.getShadowColor(context, opacity: isLightTheme ? 0.3 : 0.1),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
border: Border.all(
color: WebTheme.getSecondaryBorderColor(context),
width: 1,
),
),
child: LayoutBuilder(
builder: (context, constraints) {
// 估算内容总宽度
final contentWidth = _estimateContentWidth();
// 如果空间不足,使用垂直布局
if (contentWidth > constraints.maxWidth && constraints.maxWidth > 0) {
return _buildVerticalLayout(isLightTheme);
} else {
return _buildHorizontalLayout(isLightTheme);
}
},
),
);
}
/// 构建水平布局
Widget _buildHorizontalLayout(bool isLightTheme) {
return IntrinsicWidth(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 操作按钮区域
Flexible(
child: Container(
padding: const EdgeInsets.all(2),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildActionButton(
icon: Icons.check,
label: 'Apply',
tooltip: '应用生成的文本',
onPressed: widget.isGenerating ? null : widget.onApply,
),
if (widget.isGenerating && widget.onStop != null)
_buildActionButton(
icon: Icons.stop,
label: 'Stop',
tooltip: '停止生成',
onPressed: widget.onStop,
)
else
_buildActionButton(
icon: Icons.refresh,
label: 'Retry',
tooltip: '重新生成',
onPressed: widget.isGenerating ? null : widget.onRetry,
),
_buildActionButton(
icon: Icons.close,
label: 'Discard',
tooltip: widget.isGenerating ? '停止并丢弃生成的文本' : '丢弃生成的文本',
onPressed: widget.onDiscard,
),
_buildActionButton(
icon: Icons.crop_free,
label: 'Section',
tooltip: '分段处理',
onPressed: widget.isGenerating ? null : widget.onSection,
),
],
),
),
),
),
// 分隔线
Container(
width: 1,
height: 32,
color: WebTheme.getSecondaryBorderColor(context),
),
// 信息区域
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: _buildInfoContent(),
),
),
],
),
);
}
/// 构建垂直布局(当空间不足时)
Widget _buildVerticalLayout(bool isLightTheme) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// 操作按钮区域
Container(
padding: const EdgeInsets.all(2),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildActionButton(
icon: Icons.check,
label: 'Apply',
tooltip: '应用生成的文本',
onPressed: widget.isGenerating ? null : widget.onApply,
),
if (widget.isGenerating && widget.onStop != null)
_buildActionButton(
icon: Icons.stop,
label: 'Stop',
tooltip: '停止生成',
onPressed: widget.onStop,
)
else
_buildActionButton(
icon: Icons.refresh,
label: 'Retry',
tooltip: '重新生成',
onPressed: widget.isGenerating ? null : widget.onRetry,
),
_buildActionButton(
icon: Icons.close,
label: 'Discard',
tooltip: widget.isGenerating ? '停止并丢弃生成的文本' : '丢弃生成的文本',
onPressed: widget.onDiscard,
),
_buildActionButton(
icon: Icons.crop_free,
label: 'Section',
tooltip: '分段处理',
onPressed: widget.isGenerating ? null : widget.onSection,
),
],
),
),
),
// 分隔线
Container(
width: double.infinity,
height: 1,
color: WebTheme.getSecondaryBorderColor(context),
),
// 信息区域
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: _buildInfoContent(),
),
],
);
}
/// 构建信息内容
Widget _buildInfoContent() {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isGenerating) ...[
const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 1.5,
valueColor: AlwaysStoppedAnimation<Color>(WebTheme.white),
),
),
const SizedBox(width: 8),
Flexible(
child: Text(
'生成中...',
style: const TextStyle(
color: WebTheme.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
],
Flexible(
child: Text(
'${widget.wordCount} Words',
style: const TextStyle(
color: WebTheme.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
const Text(
', ',
style: TextStyle(
color: WebTheme.white,
fontSize: 12,
),
),
Flexible(
child: Text(
widget.modelName,
style: const TextStyle(
color: WebTheme.white,
fontSize: 12,
fontStyle: FontStyle.italic,
),
overflow: TextOverflow.ellipsis,
),
),
],
);
}
/// 估算内容总宽度
double _estimateContentWidth() {
// 操作按钮: 4个按钮 * 80px ≈ 320px
// 分隔线: 1px
// 信息区域: 约150px
// 内边距: 约30px
return 320 + 1 + 150 + 30; // ≈ 501px
}
/// 构建操作按钮
Widget _buildActionButton({
required IconData icon,
required String label,
required String tooltip,
required VoidCallback? onPressed,
}) {
final isEnabled = onPressed != null;
return Tooltip(
message: tooltip,
child: MouseRegion(
cursor: isEnabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden,
opaque: true,
child: InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(6),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 14,
color: isEnabled
? WebTheme.white
: WebTheme.white,
),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
color: isEnabled
? WebTheme.white
: WebTheme.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
),
);
}
}