464 lines
15 KiB
Dart
464 lines
15 KiB
Dart
import 'package:didvan/config/design_config.dart';
|
|
import 'package:didvan/config/theme_data.dart';
|
|
import 'package:didvan/models/ai/ai_chat_args.dart';
|
|
import 'package:didvan/models/ai/bots_model.dart';
|
|
import 'package:didvan/views/ai/ai_chat_page.dart';
|
|
import 'package:didvan/views/ai/ai_chat_state.dart';
|
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
|
import 'package:didvan/views/widgets/hoshan_home_app_bar.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
|
import 'package:didvan/views/ai/ai_state.dart';
|
|
|
|
class AiSectionPage extends StatefulWidget {
|
|
const AiSectionPage({super.key});
|
|
|
|
@override
|
|
State<AiSectionPage> createState() => _AiSectionPageState();
|
|
}
|
|
|
|
class _AiSectionGridItem {
|
|
final String title;
|
|
final String description;
|
|
final String iconPath;
|
|
final void Function(BuildContext context) onTap;
|
|
|
|
_AiSectionGridItem({
|
|
required this.title,
|
|
required this.description,
|
|
required this.iconPath,
|
|
required this.onTap,
|
|
});
|
|
}
|
|
|
|
class _AiSectionPageState extends State<AiSectionPage>
|
|
with TickerProviderStateMixin {
|
|
late final List<_AiSectionGridItem> _gridItems;
|
|
late AnimationController _fadeController;
|
|
late AnimationController _slideController;
|
|
late Animation<double> _fadeAnimation;
|
|
late Animation<Offset> _slideAnimation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_fadeController = AnimationController(
|
|
duration: const Duration(milliseconds: 800),
|
|
vsync: this,
|
|
);
|
|
|
|
_fadeAnimation = CurvedAnimation(
|
|
parent: _fadeController,
|
|
curve: Curves.easeIn,
|
|
);
|
|
|
|
_slideController = AnimationController(
|
|
duration: const Duration(milliseconds: 1000),
|
|
vsync: this,
|
|
);
|
|
|
|
_slideAnimation = Tween<Offset>(
|
|
begin: const Offset(0, 0.3),
|
|
end: Offset.zero,
|
|
).animate(CurvedAnimation(
|
|
parent: _slideController,
|
|
curve: Curves.easeOutCubic,
|
|
));
|
|
|
|
_fadeController.forward();
|
|
_slideController.forward();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final historyAiChatState = context.read<HistoryAiChatState>();
|
|
if (historyAiChatState.bots.isEmpty) {
|
|
historyAiChatState.getBots();
|
|
}
|
|
|
|
final aiState = context.read<AiState>();
|
|
final aiChatState = context.read<AiChatState>();
|
|
|
|
aiState.onClearChatCallback = () async {
|
|
await aiChatState.clearChat();
|
|
};
|
|
|
|
aiState.endChat();
|
|
if (aiState.tools == null) {
|
|
aiState.getTools();
|
|
}
|
|
});
|
|
|
|
_gridItems = [
|
|
_AiSectionGridItem(
|
|
title: 'ساخت عکس',
|
|
description: 'ایجاد تصاویر خلاقانه با هوش مصنوعی',
|
|
iconPath: 'lib/assets/icons/create image.svg',
|
|
onTap: (context) {
|
|
final aiState = context.read<AiState>();
|
|
aiState.endChat();
|
|
if (aiState.tools != null && aiState.tools!.isNotEmpty) {
|
|
try {
|
|
final tool = aiState.tools![0];
|
|
if (tool.bots != null && tool.bots!.isNotEmpty) {
|
|
aiState.startChat(AiChatArgs(bot: tool.bots!.first));
|
|
}
|
|
} catch (e) {
|
|
debugPrint('خطا در یافتن ابزار تصویرساز: $e');
|
|
}
|
|
}
|
|
},
|
|
),
|
|
_AiSectionGridItem(
|
|
title: 'ترجمه',
|
|
description: 'ترجمه متون به زبانهای مختلف',
|
|
iconPath: 'lib/assets/icons/translate.svg',
|
|
onTap: (context) {
|
|
final aiState = context.read<AiState>();
|
|
aiState.endChat();
|
|
if (aiState.tools != null && aiState.tools!.length > 2) {
|
|
try {
|
|
final tool = aiState.tools![2];
|
|
if (tool.bots != null && tool.bots!.isNotEmpty) {
|
|
aiState.startChat(AiChatArgs(bot: tool.bots!.first));
|
|
}
|
|
} catch (e) {
|
|
debugPrint('خطا در یافتن ابزار ترجمه: $e');
|
|
}
|
|
}
|
|
},
|
|
),
|
|
_AiSectionGridItem(
|
|
title: 'خلاصهساز',
|
|
description: 'خلاصهسازی متن با هوش مصنوعی',
|
|
iconPath: 'lib/assets/icons/summary.svg',
|
|
onTap: (context) {
|
|
final aiState = context.read<AiState>();
|
|
aiState.endChat();
|
|
|
|
final aisummeryBot = BotsModel(
|
|
id: 100,
|
|
name: 'Aisummery',
|
|
responseType: 'text',
|
|
attachmentType: ['pdf', 'image', 'audio'],
|
|
attachment: 1,
|
|
);
|
|
|
|
aiState.startChat(AiChatArgs(bot: aisummeryBot));
|
|
},
|
|
),
|
|
_AiSectionGridItem(
|
|
title: 'متن به صوت',
|
|
description: 'تبدیل متن به فایل صوتی',
|
|
iconPath: 'lib/assets/icons/text to voice.svg',
|
|
onTap: (context) {
|
|
final aiState = context.read<AiState>();
|
|
aiState.endChat();
|
|
|
|
final textToSpeechBot = BotsModel(
|
|
id: 101,
|
|
name: 'aiaudio',
|
|
responseType: 'audio',
|
|
attachmentType: [],
|
|
attachment: 0,
|
|
);
|
|
|
|
aiState.startChat(AiChatArgs(bot: textToSpeechBot));
|
|
},
|
|
),
|
|
_AiSectionGridItem(
|
|
title: 'تحلیل و ترسیم نمودار',
|
|
description: 'ساخت نمودار با تحلیل هوشمند',
|
|
iconPath: 'lib/assets/icons/chart-analysis.svg',
|
|
onTap: (context) {
|
|
final aiState = context.read<AiState>();
|
|
aiState.endChat();
|
|
|
|
final chartAnalysisBot = BotsModel(
|
|
id: 27,
|
|
name: 'chart-analysis',
|
|
responseType: 'text',
|
|
attachmentType: ['image', 'pdf'],
|
|
attachment: 1,
|
|
);
|
|
|
|
aiState.startChat(AiChatArgs(bot: chartAnalysisBot));
|
|
},
|
|
),
|
|
_AiSectionGridItem(
|
|
title: 'ساخت ویدیو',
|
|
description: 'تولید ویدیو با هوش مصنوعی',
|
|
iconPath: 'lib/assets/icons/video creator.svg',
|
|
onTap: (context) {
|
|
final aiState = context.read<AiState>();
|
|
aiState.endChat();
|
|
|
|
final aiVideoBot = BotsModel(
|
|
id: 102,
|
|
name: 'aivideo',
|
|
responseType: 'video',
|
|
attachmentType: [],
|
|
attachment: 0,
|
|
);
|
|
|
|
aiState.startChat(AiChatArgs(bot: aiVideoBot));
|
|
},
|
|
),
|
|
];
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_fadeController.dispose();
|
|
_slideController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final aiState = context.watch<AiState>();
|
|
|
|
return Column(
|
|
children: [
|
|
if (!aiState.isChatting) const HoshanHomeAppBar(),
|
|
Expanded(
|
|
child: aiState.isChatting
|
|
? AiChatPage(args: aiState.currentChatArgs!)
|
|
: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
FadeTransition(
|
|
opacity: _fadeAnimation,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
'lib/assets/icons/clarity_tools-line.svg'),
|
|
const SizedBox(
|
|
width: 8,
|
|
),
|
|
DidvanText(
|
|
'جعبه ابزار استراتژیک هوشان',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
color: Theme.of(context).colorScheme.title,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SlideTransition(
|
|
position: _slideAnimation,
|
|
child: FadeTransition(
|
|
opacity: _fadeAnimation,
|
|
child: SizedBox(
|
|
height: 400,
|
|
child: _buildAiGrid(context, aiState),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildAiGrid(BuildContext context, AiState aiState) {
|
|
if (aiState.tools == null) {
|
|
if (aiState.loading) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
return const Center(
|
|
child: DidvanText('لیست ابزارها هنوز بارگذاری نشده یا خالی است.'));
|
|
}
|
|
|
|
return ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: true,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
|
itemCount: (_gridItems.length / 2).ceil(),
|
|
itemBuilder: (context, columnIndex) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
left: columnIndex == (_gridItems.length / 2).ceil() - 1 ? 0 : 4.0,
|
|
right: 16.0,
|
|
),
|
|
child: SizedBox(
|
|
width: 180,
|
|
height: 340,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
if (columnIndex * 2 < _gridItems.length)
|
|
TweenAnimationBuilder<double>(
|
|
duration: Duration(milliseconds: 600 + (columnIndex * 100)),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOut,
|
|
builder: (context, value, child) {
|
|
return Opacity(
|
|
opacity: value,
|
|
child: Transform.translate(
|
|
offset: Offset(50 * (1 - value), 0),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: SizedBox(
|
|
width: 180,
|
|
height: 160,
|
|
child: _buildGridItemCard(
|
|
context, _gridItems[columnIndex * 2]),
|
|
),
|
|
),
|
|
if (columnIndex * 2 + 1 < _gridItems.length)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 16.0),
|
|
child: TweenAnimationBuilder<double>(
|
|
duration:
|
|
Duration(milliseconds: 700 + (columnIndex * 100)),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOut,
|
|
builder: (context, value, child) {
|
|
return Opacity(
|
|
opacity: value,
|
|
child: Transform.translate(
|
|
offset: Offset(50 * (1 - value), 0),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: SizedBox(
|
|
width: 180,
|
|
height: 160,
|
|
child: _buildGridItemCard(
|
|
context, _gridItems[columnIndex * 2 + 1]),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildGridItemCard(BuildContext context, _AiSectionGridItem item) {
|
|
return _AnimatedGridCard(item: item);
|
|
}
|
|
}
|
|
|
|
class _AnimatedGridCard extends StatefulWidget {
|
|
final _AiSectionGridItem item;
|
|
|
|
const _AnimatedGridCard({required this.item});
|
|
|
|
@override
|
|
State<_AnimatedGridCard> createState() => _AnimatedGridCardState();
|
|
}
|
|
|
|
class _AnimatedGridCardState extends State<_AnimatedGridCard> {
|
|
bool _isHovered = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return TweenAnimationBuilder<double>(
|
|
duration: const Duration(milliseconds: 400),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOutBack,
|
|
builder: (context, value, child) {
|
|
return Transform.scale(
|
|
scale: value,
|
|
child: child,
|
|
);
|
|
},
|
|
child: MouseRegion(
|
|
onEnter: (_) => setState(() => _isHovered = true),
|
|
onExit: (_) => setState(() => _isHovered = false),
|
|
child: InkWell(
|
|
onTap: () => widget.item.onTap(context),
|
|
borderRadius: BorderRadius.circular(25),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
transform: Matrix4.identity()
|
|
..translate(0.0, _isHovered ? -8.0 : 0.0),
|
|
decoration: BoxDecoration(
|
|
color: DesignConfig.isDark
|
|
? const Color.fromARGB(255, 78, 82, 84)
|
|
: const Color.fromARGB(255, 245, 245, 245),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: _isHovered
|
|
? const Color.fromARGB(255, 0, 126, 167)
|
|
: const Color.fromARGB(255, 184, 184, 184),
|
|
width: _isHovered ? 2 : 1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
// ignore: deprecated_member_use
|
|
color: Colors.black.withOpacity(_isHovered ? 0.15 : 0.05),
|
|
blurRadius: _isHovered ? 20 : 10,
|
|
offset: Offset(0, _isHovered ? 8 : 4),
|
|
),
|
|
],
|
|
),
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TweenAnimationBuilder<double>(
|
|
duration: const Duration(milliseconds: 600),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.elasticOut,
|
|
builder: (context, value, child) {
|
|
return Transform.rotate(
|
|
angle: (1 - value) * 0.5,
|
|
child: Opacity(
|
|
opacity: value,
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: AnimatedScale(
|
|
duration: const Duration(milliseconds: 200),
|
|
scale: _isHovered ? 1.1 : 1.0,
|
|
child: SvgPicture.asset(
|
|
widget.item.iconPath,
|
|
width: 48,
|
|
height: 48,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
DidvanText(
|
|
widget.item.title,
|
|
style: Theme.of(context).textTheme.titleSmall,
|
|
color: const Color.fromARGB(255, 0, 126, 167),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Expanded(
|
|
child: DidvanText(
|
|
widget.item.description,
|
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
color: Theme.of(context).colorScheme.caption,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|