feat: 实现用户画像、占卜历史与后端用户管理模块

This commit is contained in:
ZL-Q
2026-04-06 01:28:10 +08:00
parent d87b2e1e3a
commit 8a18b3528b
77 changed files with 5850 additions and 2604 deletions
@@ -8,6 +8,7 @@ import '../../../../core/network/api_problem.dart';
import '../../../../data/network/api_client.dart';
import '../models/divination_backend_models.dart';
import '../models/divination_params.dart';
import '../models/divination_result.dart';
class DivinationApi {
const DivinationApi({required ApiClient apiClient}) : _apiClient = apiClient;
@@ -37,6 +38,67 @@ class DivinationApi {
return RunAcceptedData.fromJson(json);
}
Future<List<DivinationResultData>> getHistoryRecords({
required String userId,
}) async {
final json = await _apiClient.getJson('/api/v1/agent/history');
final messagesRaw = json['messages'];
if (messagesRaw is! List<dynamic>) {
return const <DivinationResultData>[];
}
final records = <DivinationResultData>[];
for (final raw in messagesRaw) {
if (raw is! Map<String, dynamic>) {
continue;
}
if (raw['role'] != 'assistant') {
continue;
}
final agentOutputRaw = raw['agent_output'];
if (agentOutputRaw is! Map<String, dynamic>) {
continue;
}
final derivedRaw = agentOutputRaw['divination_derived'];
if (derivedRaw is! Map<String, dynamic>) {
continue;
}
try {
final derived = DerivedDivinationData.fromJson(derivedRaw);
final divinationTime = _resolveHistoryTime(raw, derived);
final params = DivinationParams(
method: _methodFromText(derived.divinationMethod),
questionType: _questionTypeFromText(derived.questionType),
question: derived.question,
divinationTime: divinationTime,
coinBalance: 0,
userId: userId,
);
final aggregate = DivinationRunAggregate(
derived: derived,
signLevel: _asString(agentOutputRaw['sign_level']),
summary: _asString(agentOutputRaw['summary']),
conclusion: _asStringList(agentOutputRaw['conclusion']),
focusPoints: _asStringList(agentOutputRaw['focus_points']),
advice: _asStringList(agentOutputRaw['advice']),
keywords: _asStringList(agentOutputRaw['keywords']),
answer: _asString(agentOutputRaw['answer']),
);
records.add(aggregate.toViewData(params));
} catch (error, stackTrace) {
_logger.warning(
message: 'Skip malformed history assistant message',
extra: <String, dynamic>{
'error': error.toString(),
'stackTrace': stackTrace.toString(),
},
);
continue;
}
}
return records;
}
Stream<Map<String, dynamic>> streamEvents({
required String threadId,
required String runId,
@@ -217,6 +279,55 @@ String _questionTypeToText(QuestionType type) {
};
}
QuestionType _questionTypeFromText(String raw) {
return switch (raw) {
'事业' => QuestionType.career,
'情感' => QuestionType.love,
'财富' => QuestionType.wealth,
'运势' => QuestionType.fortune,
'解梦' => QuestionType.dream,
'健康' => QuestionType.health,
'学业' => QuestionType.study,
'寻物' => QuestionType.search,
_ => QuestionType.other,
};
}
DivinationMethod _methodFromText(String raw) {
return raw == '自动起卦' ? DivinationMethod.auto : DivinationMethod.manual;
}
DateTime _resolveHistoryTime(
Map<String, dynamic> message,
DerivedDivinationData derived,
) {
final timestamp = message['timestamp'];
if (timestamp is String) {
final parsed = DateTime.tryParse(timestamp);
if (parsed != null) {
return parsed.toLocal();
}
}
final derivedTime = DateTime.tryParse(derived.divinationTime);
if (derivedTime != null) {
return derivedTime.toLocal();
}
return DateTime.now();
}
String _asString(Object? value) {
return value is String ? value : '';
}
List<String> _asStringList(Object? value) {
if (value is! List<dynamic>) {
return const <String>[];
}
return value.whereType<String>().toList(growable: false);
}
String _yaoTypeToText(YaoType type) {
return switch (type) {
YaoType.youngYang => '少阳',