import 'dart:math'; import '../../../../core/logging/logger.dart'; import '../../../../core/network/api_problem.dart'; import '../apis/divination_api.dart'; import '../models/divination_backend_models.dart'; import '../models/divination_params.dart'; class DivinationRunService { const DivinationRunService({required DivinationApi api}) : _api = api; final DivinationApi _api; static final Logger _logger = getLogger('features.divination.run_service'); Future getPointsBalance() { return _api.getPointsBalance(); } Future run({ required DivinationParams params, required List yaoStates, void Function()? onDerived, void Function()? onTextMessageEnd, }) async { final threadId = _uuidV4(); final runId = 'run_${DateTime.now().millisecondsSinceEpoch}'; await _api.enqueueRun( params: params, yaoStates: yaoStates, threadId: threadId, runId: runId, ); DerivedDivinationData? derived; String signLevel = ''; String summary = ''; List conclusion = const []; List focusPoints = const []; List advice = const []; List keywords = const []; String answer = ''; await for (final event in _api.streamEvents( threadId: threadId, runId: runId, )) { final type = event['type'] as String? ?? ''; _logger.debug( message: 'Received run SSE event', extra: { 'threadId': threadId, 'runId': runId, 'type': type, }, ); if (type == 'DIVINATION_DERIVED') { final payload = event['divination']; if (payload is! Map) { throw const FormatException('DIVINATION_DERIVED 缺少 divination 结构'); } derived = DerivedDivinationData.fromJson(payload); onDerived?.call(); continue; } if (type == 'TEXT_MESSAGE_END') { signLevel = _requiredString(event, 'sign_level'); summary = _requiredString(event, 'summary'); conclusion = _requiredStringList(event, 'conclusion'); focusPoints = _requiredStringList(event, 'focus_points'); advice = _requiredStringList(event, 'advice'); keywords = _requiredStringList(event, 'keywords'); answer = _requiredString(event, 'answer'); onTextMessageEnd?.call(); continue; } if (type == 'RUN_ERROR') { _logger.warning( message: 'Run ended with RUN_ERROR event', extra: { 'threadId': threadId, 'runId': runId, 'code': event['code'], 'detail': event['detail'], 'message': event['message'], }, ); throw ApiProblem( status: 500, title: 'Run failed', detail: event['detail'] as String? ?? '解卦失败', code: event['code'] as String?, ); } if (type == 'RUN_FINISHED') { break; } } if (derived == null) { _logger.warning( message: 'Missing DIVINATION_DERIVED before RUN_FINISHED', extra: {'threadId': threadId, 'runId': runId}, ); throw const FormatException('未收到 DIVINATION_DERIVED 事件'); } return DivinationRunAggregate( derived: derived, signLevel: signLevel, summary: summary, conclusion: conclusion, focusPoints: focusPoints, advice: advice, keywords: keywords, answer: answer, ); } String _requiredString(Map json, String key) { final value = json[key]; if (value is! String || value.isEmpty) { throw FormatException('缺少字段: $key'); } return value; } List _requiredStringList(Map json, String key) { final raw = json[key]; if (raw is! List) { throw FormatException('缺少列表字段: $key'); } return raw .map((item) { if (item is! String || item.isEmpty) { throw FormatException('字段 $key 存在非法项'); } return item; }) .toList(growable: false); } String _uuidV4() { final random = Random.secure(); final bytes = List.generate(16, (_) => random.nextInt(256)); bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; String toHex(int b) => b.toRadixString(16).padLeft(2, '0'); final b = bytes.map(toHex).toList(growable: false); return '${b[0]}${b[1]}${b[2]}${b[3]}-${b[4]}${b[5]}-${b[6]}${b[7]}-${b[8]}${b[9]}-${b[10]}${b[11]}${b[12]}${b[13]}${b[14]}${b[15]}'; } }