Files
Avtex/lib/screens/admin/ai_stats_screen.dart
T
Alexyz f1e0f6b5ef feat: Add Nanaxe server with Docker, GitHub Actions CI/CD, and auto-deployment
- Add complete server infrastructure with Gitea, API, PostgreSQL
- Implement Obsidian-compatible markdown mindmap converter
- Add Flutter service for server synchronization
- Configure GitHub Actions for automated build and deploy
- Add production docker-compose with health checks
- Include VPS setup and deployment scripts
- Add comprehensive deployment documentation
2026-04-19 22:18:25 +05:00

291 lines
9.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AIStatsScreen extends ConsumerStatefulWidget {
const AIStatsScreen({super.key});
@override
ConsumerState<AIStatsScreen> createState() => _AIStatsScreenState();
}
class _AIStatsScreenState extends ConsumerState<AIStatsScreen> {
int _totalRequests = 0;
int _totalTokens = 0;
int _mockRequests = 0;
int _realRequests = 0;
Map<String, int> _requestsByType = {};
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadStats();
}
Future<void> _loadStats() async {
try {
final prefs = await SharedPreferences.getInstance();
setState(() {
_totalRequests = prefs.getInt('ai_total_requests') ?? 0;
_totalTokens = prefs.getInt('ai_total_tokens') ?? 0;
_mockRequests = prefs.getInt('ai_mock_requests') ?? 0;
_realRequests = prefs.getInt('ai_real_requests') ?? 0;
_requestsByType = {
'Генерация подзадач': prefs.getInt('ai_subtasks') ?? 0,
'Приоритизация': prefs.getInt('ai_priority') ?? 0,
'Описания': prefs.getInt('ai_descriptions') ?? 0,
'Mind-карты': prefs.getInt('ai_mindmaps') ?? 0,
'Планирование': prefs.getInt('ai_scheduling') ?? 0,
};
_isLoading = false;
});
} catch (e) {
setState(() => _isLoading = false);
}
}
Future<void> _resetStats() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Сбросить статистику?'),
content: const Text('Все данные об использовании AI будут удалены'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Отмена'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Сбросить'),
),
],
),
);
if (confirmed == true) {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('ai_total_requests');
await prefs.remove('ai_total_tokens');
await prefs.remove('ai_mock_requests');
await prefs.remove('ai_real_requests');
await prefs.remove('ai_subtasks');
await prefs.remove('ai_priority');
await prefs.remove('ai_descriptions');
await prefs.remove('ai_mindmaps');
await prefs.remove('ai_scheduling');
await _loadStats();
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('AI Статистика'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadStats,
tooltip: 'Обновить',
),
IconButton(
icon: const Icon(Icons.delete_sweep),
onPressed: _resetStats,
tooltip: 'Сбросить',
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Общая статистика
_buildStatCard(
'Общая статистика',
[
_buildBigStat('Всего запросов', _totalRequests.toString()),
const Divider(),
_buildStatRow('Mock запросов', _mockRequests.toString()),
_buildStatRow('Реальных запросов', _realRequests.toString()),
_buildStatRow('Использовано токенов', _totalTokens.toString()),
],
),
const SizedBox(height: 24),
// Запросы по типам
_buildStatCard(
'Запросы по типам',
_requestsByType.entries
.map((e) => _buildStatRow(e.key, e.value.toString()))
.toList(),
),
const SizedBox(height: 24),
// График использования (простой)
_buildStatCard(
'Использование',
[
SizedBox(
height: 150,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.end,
children: _requestsByType.entries.map((entry) {
final max = _requestsByType.values.fold(
1, (prev, element) => prev > element ? prev : element);
final height = (entry.value / max) * 100;
return Column(
children: [
Text(entry.value.toString()),
const SizedBox(height: 4),
Container(
width: 30,
height: height,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue, Colors.blueAccent],
),
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 8),
SizedBox(
width: 60,
child: Text(
entry.key.split(' ')[0],
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 10),
overflow: TextOverflow.ellipsis,
),
),
],
);
}).toList(),
),
),
],
),
const SizedBox(height: 24),
// Информация
_buildInfoCard(),
],
),
);
}
Widget _buildStatCard(String title, List<Widget> children) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
...children,
],
),
);
}
Widget _buildBigStat(String label, String value) {
return Column(
children: [
Text(
value,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 4),
Text(label, style: TextStyle(color: Colors.grey[600])),
],
);
}
Widget _buildStatRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: Colors.grey[600])),
Text(
value,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
],
),
);
}
Widget _buildInfoCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.info_outline, color: Colors.blue),
const SizedBox(width: 8),
Text(
'Информация',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
const SizedBox(height: 12),
const Text(
'• Сейчас работает MOCK режим\n'
'• Реальные запросы к DeepSeek отключены\n'
'• Токены не расходуются\n\n'
'Для включения реального AI:\n'
'1. Пополните баланс DeepSeek\n'
'2. Установите useMockMode = false\n'
'3. Перезапустите приложение',
style: TextStyle(fontSize: 13),
),
],
),
);
}
}