Update: 2026-07-05 00:06:55

This commit is contained in:
Hamza-Ayed
2026-07-05 00:06:55 +03:00
parent a0e9bd3cbb
commit 35c0a6680c
10 changed files with 300 additions and 0 deletions
@@ -0,0 +1,77 @@
<?php
// ============================================================
// marketing_engine/cron_weekly_report.php
// Script to be run via cron (e.g. weekly) to summarize all reports
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
require_once __DIR__ . '/../core/Services/SiroGeminiService.php';
require_once __DIR__ . '/../core/Services/FcmService.php';
try {
$con = Database::get('main');
// Fetch all reports from the last 7 days (that are not weekly themselves)
$stmt = $con->prepare("
SELECT platform, report_html, created_at
FROM marketing_reports
WHERE is_weekly = 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY created_at ASC
");
$stmt->execute();
$reports = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($reports)) {
echo "No reports generated in the last 7 days to summarize.\n";
exit;
}
// Combine all report data for Gemini
$combinedData = [];
foreach ($reports as $report) {
$combinedData[] = [
'platform' => $report['platform'],
'date' => $report['created_at'],
'content' => strip_tags($report['report_html']) // Send stripped HTML to save tokens
];
}
$gemini = new SiroGeminiService();
// Create a new method in GeminiService or use evaluatePostsForReporting with a specific prompt wrapper
$prompt = "You are a Chief Marketing Officer AI. Below are all the daily/individual social media intelligence reports collected over the last 7 days from our scraper bots. Please read through all of them and generate one comprehensive, executive 'Weekly Market Intelligence Summary'. Format it beautifully in HTML. Highlight key market trends, recurrent complaints, competitor activities, and actionable advice for the week.\n\nData:\n" . json_encode($combinedData);
$response = $gemini->callGemini($prompt, 'gemini-1.5-flash');
if (isset($response['candidates'][0]['content']['parts'][0]['text'])) {
$weeklyHtml = $response['candidates'][0]['content']['parts'][0]['text'];
// Save the weekly report
$insert = $con->prepare("INSERT INTO marketing_reports (platform, report_html, is_weekly) VALUES ('all', ?, 1)");
$insert->execute([$weeklyHtml]);
$reportId = $con->lastInsertId();
// Notify Admins
$fcm = new FcmService();
$fcm->send(
token: '/topics/admin_alerts',
title: 'Weekly Market Intelligence Summary 📈',
body: 'The comprehensive weekly social media report has been generated.',
data: [
'type' => 'marketing_report',
'report_id' => $reportId,
'is_weekly' => 1
],
category: 'marketing_report'
);
echo "Weekly report generated and saved successfully. ID: $reportId\n";
} else {
echo "Failed to generate report from Gemini.\n";
}
} catch (Exception $e) {
echo "Error generating weekly report: " . $e->getMessage() . "\n";
}
+28
View File
@@ -7,6 +7,7 @@
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
require_once __DIR__ . '/../core/Services/SiroGeminiService.php';
require_once __DIR__ . '/../core/Services/FcmService.php';
header('Content-Type: application/json');
@@ -87,11 +88,38 @@ try {
// Save to database
$stmt = $con->prepare("INSERT INTO marketing_reports (platform, report_html) VALUES (?, ?)");
$stmt->execute([$platform, $reportHtml]);
$reportId = $con->lastInsertId();
// Send FCM notification to admins
$fcm = new FcmService();
$fcm->send(
token: '/topics/admin_alerts',
title: 'New Social Media Intelligence Report 📊',
body: "A new autonomous analysis report for {$platform} has been generated.",
data: [
'type' => 'marketing_report',
'report_id' => $reportId,
'platform' => $platform
],
category: 'marketing_report'
);
}
echo json_encode(['status' => 'success', 'message' => 'Posts evaluated and report saved']);
break;
case 'get_reports':
$stmt = $con->prepare("
SELECT id, platform, report_html, is_weekly, created_at
FROM marketing_reports
ORDER BY created_at DESC
LIMIT 50
");
$stmt->execute();
$reports = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['status' => 'success', 'data' => $reports]);
break;
default:
echo json_encode(['status' => 'error', 'message' => 'Invalid action']);
}
+8
View File
@@ -68,3 +68,11 @@ INSERT IGNORE INTO `api_quotas` (`service_name`, `daily_usage`, `quota_limit`, `
('gemini', 0, 100, CURDATE()),
('elevenlabs', 0, 5, CURDATE()),
('creatomate', 0, 2, CURDATE());
CREATE TABLE IF NOT EXISTS marketing_reports (
id INT AUTO_INCREMENT PRIMARY KEY,
platform ENUM('facebook', 'instagram', 'tiktok', 'twitter', 'youtube', 'all') NOT NULL,
report_html TEXT NOT NULL,
is_weekly TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:get/get.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:siro_admin/controller/functions/device_info.dart';
import 'package:siro_admin/views/auth/login_page.dart';
@@ -184,6 +185,8 @@ class OtpHelper extends GetxController {
await box.write(BoxName.phoneVerified, true);
await box.write('admin_password', password);
FirebaseMessaging.instance.subscribeToTopic('admin_alerts');
mySnackbarSuccess('تم تسجيل الدخول بنجاح');
return true;
}
@@ -238,6 +241,7 @@ class OtpHelper extends GetxController {
String token = r(rawJwt.toString()).split(AppInformation.addd)[0];
if (!JwtDecoder.isExpired(token)) {
Log.print('Valid JWT found, skipping login.php (no OTP needed)');
FirebaseMessaging.instance.subscribeToTopic('admin_alerts');
Get.offAll(() => const AdminHomePage());
return;
}
@@ -275,6 +279,7 @@ class OtpHelper extends GetxController {
} else if (response['jwt'] != null) {
box.write(BoxName.jwt, c(response['jwt']));
}
FirebaseMessaging.instance.subscribeToTopic('admin_alerts');
try {
if (Get.isRegistered<DashboardController>()) {
Get.find<DashboardController>().getDashBoard();
@@ -241,6 +241,20 @@ class FirebaseMessagesController extends GetxController {
fireBaseTitles(message);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data['type'] == 'marketing_report') {
Get.toNamed('/social-intelligence');
}
});
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
if (message != null && message.data['type'] == 'marketing_report') {
Future.delayed(const Duration(seconds: 2), () {
Get.toNamed('/social-intelligence');
});
}
});
}
void fireBaseTitles(RemoteMessage message) {
+2
View File
@@ -8,6 +8,7 @@ import 'views/admin/complaints/complaint_list_page.dart';
import 'views/admin/drivers/driver_documents_review_page.dart';
import 'views/admin/marketing/marketing_page.dart';
import 'views/admin/marketing/heatmap_page.dart';
import 'views/admin/marketing/social_intelligence_screen.dart';
List<GetPage<dynamic>> routes = [
GetPage(name: "/", page: () => const AdminHomePage()),
@@ -19,4 +20,5 @@ List<GetPage<dynamic>> routes = [
GetPage(name: "/driver-docs", page: () => DriverDocsReviewPage()),
GetPage(name: "/marketing", page: () => const MarketingPage()),
GetPage(name: "/heatmap", page: () => const HeatmapPage()),
GetPage(name: "/social-intelligence", page: () => const SocialIntelligenceScreen()),
];
@@ -757,6 +757,8 @@ class _AdminHomePageState extends State<AdminHomePage>
() => Get.toNamed('/complaints')),
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, _info,
() => Get.toNamed('/driver-docs')),
ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA),
() => Get.toNamed('/social-intelligence')),
],
),
ActionCategory(
@@ -0,0 +1,139 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
class SocialIntelligenceController extends GetxController {
var isLoading = true.obs;
var reports = [].obs;
@override
void onInit() {
super.onInit();
fetchReports();
}
Future<void> fetchReports() async {
try {
isLoading(true);
final url = Uri.parse(
'${AppLink.server}/marketing_engine/index.php?action=get_reports');
final response = await http.get(url);
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'success') {
reports.value = data['data'] ?? [];
}
}
} catch (e) {
print('Error fetching reports: $e');
} finally {
isLoading(false);
}
}
}
class SocialIntelligenceScreen extends StatelessWidget {
const SocialIntelligenceScreen({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(SocialIntelligenceController());
return Scaffold(
backgroundColor: AppColor.bg,
appBar: AppBar(
title: const Text(
'استخبارات السوشيال ميديا (Social Intelligence)',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16),
),
backgroundColor: AppColor.bg,
elevation: 0,
foregroundColor: AppColor.textPrimary,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => controller.fetchReports(),
)
],
),
body: Obx(() {
if (controller.isLoading.value) {
return const Center(
child: CircularProgressIndicator(color: AppColor.accent));
}
if (controller.reports.isEmpty) {
return const Center(
child: Text(
'لا توجد تقارير حالياً',
style: TextStyle(color: AppColor.textSecondary),
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.reports.length,
itemBuilder: (context, index) {
final report = controller.reports[index];
final isWeekly = report['is_weekly'].toString() == '1';
return Card(
color: AppColor.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: isWeekly ? AppColor.accent : AppColor.divider,
width: isWeekly ? 1.5 : 1.0),
),
margin: const EdgeInsets.only(bottom: 16),
child: ExpansionTile(
iconColor: AppColor.accent,
collapsedIconColor: AppColor.textSecondary,
title: Text(
isWeekly
? '📈 تقرير السوق الأسبوعي الشامل'
: '📊 تقرير ${report['platform'].toString().toUpperCase()}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: isWeekly ? AppColor.accent : AppColor.textPrimary,
fontSize: 14,
),
),
subtitle: Text(
report['created_at'].toString(),
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 11),
),
children: [
Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: AppColor.divider)),
),
child: Directionality(
textDirection: TextDirection.rtl,
child: HtmlWidget(
report['report_html'] ?? '',
textStyle: const TextStyle(
color: AppColor.textPrimary,
fontSize: 13,
height: 1.6),
),
),
)
],
),
);
},
);
}),
);
}
}
+24
View File
@@ -185,6 +185,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
dart_earcut:
dependency: transitive
description:
@@ -552,6 +560,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_widget_from_html_core:
dependency: "direct main"
description:
name: flutter_widget_from_html_core
sha256: b1048fd119a14762e2361bd057da608148a895477846d6149109b2151d2f7abf
url: "https://pub.dev"
source: hosted
version: "0.15.2"
get:
dependency: "direct main"
description:
@@ -592,6 +608,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
html:
dependency: transitive
description:
name: html
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
url: "https://pub.dev"
source: hosted
version: "0.15.6"
http:
dependency: "direct main"
description:
+1
View File
@@ -68,6 +68,7 @@ dependencies:
flutter_staggered_animations: ^1.1.1
jailbreak_root_detection: ^1.1.5
package_info_plus: ^4.0.2
flutter_widget_from_html_core: ^0.15.1
image: any
path_provider: any