feat: finalize AI analyzer, sync endpoints, and stabilize builds

This commit is contained in:
2026-05-19 12:27:08 +07:00
parent f697ef16cd
commit 23d0cf6f66
23 changed files with 375 additions and 142 deletions
@@ -1,5 +1,23 @@
enum ObstacleDirection { left, center, right }
class BoundingBox {
final double left;
final double top;
final double right;
final double bottom;
const BoundingBox({
required this.left,
required this.top,
required this.right,
required this.bottom,
});
double get width => right - left;
double get height => bottom - top;
double get centerX => left + width / 2;
}
class DetectionResult {
final String label;
final double confidence;
@@ -26,12 +44,73 @@ class DetectionResult {
}
class ObstacleAnalyzer {
DetectionResult analyzeFallback({String label = 'person', double confidence = 0.86}) {
static const double frameWidth = 640.0;
static const double frameHeight = 480.0;
ObstacleDirection analyzeDirection(BoundingBox box) {
final cx = box.centerX;
if (cx < frameWidth * 0.33) return ObstacleDirection.left;
if (cx > frameWidth * 0.67) return ObstacleDirection.right;
return ObstacleDirection.center;
}
String estimateDistance(BoundingBox box) {
final ratio = box.height / frameHeight;
if (ratio > 0.60) return 'Very Close (< 1m)';
if (ratio > 0.35) return 'Close (1-2m)';
if (ratio > 0.15) return 'Medium (2-4m)';
return 'Far (> 4m)';
}
String buildTtsMessage(DetectionResult result) {
final directionLabel = switch (result.direction) {
ObstacleDirection.left => 'kiri',
ObstacleDirection.center => 'depan',
ObstacleDirection.right => 'kanan',
};
return 'Hati-hati, ${result.label} di $directionLabel. '
'Jarak ${result.estimatedDistance}.';
}
DetectionResult? prioritize(List<DetectionResult> detections) {
if (detections.isEmpty) return null;
const order = [
'Very Close (< 1m)',
'Very Close',
'Close (1-2m)',
'Close',
'Medium (2-4m)',
'Medium',
'Far (> 4m)',
'Far',
];
final sorted = List<DetectionResult>.of(detections);
sorted.sort((a, b) {
final ai = order.indexOf(a.estimatedDistance);
final bi = order.indexOf(b.estimatedDistance);
final aRank = ai == -1 ? order.length : ai;
final bRank = bi == -1 ? order.length : bi;
return aRank.compareTo(bRank);
});
return sorted.first;
}
List<DetectionResult> filterByConfidence(
List<DetectionResult> detections,
double threshold,
) {
return detections.where((d) => d.confidence >= threshold).toList();
}
DetectionResult analyzeFallback({
String label = 'person',
double confidence = 0.86,
}) {
return DetectionResult(
label: label,
confidence: confidence,
direction: ObstacleDirection.center,
estimatedDistance: 'Close',
estimatedDistance: 'Close (1-2m)',
);
}
}
@@ -61,6 +61,7 @@ class AppConstants {
await prefs.setString(_selectedYoloModelKey, path);
}
// Agora - ganti dengan App ID dari agora.io
static const String agoraAppId = 'YOUR_AGORA_APP_ID';
// Agora App ID diisi saat build: --dart-define=AGORA_APP_ID=...
static const String agoraAppId =
String.fromEnvironment('AGORA_APP_ID', defaultValue: '');
}
@@ -20,7 +20,7 @@ class CallService {
}
Future<int?> getPairedReceiverId() async {
final res = await _apiClient.dio.get('/pairing/status');
final res = await _apiClient.dio.get('/shared/pairing/status');
final data = res.data['data'];
if (data is! Map<String, dynamic>) return null;
final id = data['pairedWithId'];
@@ -72,6 +72,10 @@ class CallService {
int uid = 0,
}) async {
try {
if (AppConstants.agoraAppId.isEmpty) {
debugPrint('Agora join skipped: AGORA_APP_ID is not configured');
return false;
}
_engine ??= createAgoraRtcEngine();
await _engine!.initialize(const RtcEngineContext(appId: AppConstants.agoraAppId));
await _engine!.enableAudio();