174 lines
5.5 KiB
Dart
174 lines
5.5 KiB
Dart
part of 'relationship_repository.dart';
|
|
|
|
extension LocalRepositoryAiDigestOperations on LocalRepository {
|
|
Future<void> saveAiSuggestionDrafts(List<AiSuggestionDraft> drafts) async {
|
|
if (drafts.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
final LocalDataState current = _requireState();
|
|
final Set<String> existingKeys = current.aiSuggestionDrafts
|
|
.map(_aiDraftDedupeKey)
|
|
.toSet();
|
|
final List<AiSuggestionDraft> uniqueDrafts = drafts
|
|
.where((AiSuggestionDraft draft) {
|
|
if (!_personExists(current.people, draft.personId)) {
|
|
return false;
|
|
}
|
|
return existingKeys.add(_aiDraftDedupeKey(draft));
|
|
})
|
|
.toList(growable: false);
|
|
|
|
if (uniqueDrafts.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
await _setState(
|
|
current.copyWith(
|
|
aiSuggestionDrafts: <AiSuggestionDraft>[
|
|
...uniqueDrafts,
|
|
...current.aiSuggestionDrafts,
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> acceptAiSuggestionDraft(String draftId) async {
|
|
final LocalDataState current = _requireState();
|
|
final AiSuggestionDraft? draft = current.aiSuggestionDrafts
|
|
.where((AiSuggestionDraft item) => item.id == draftId)
|
|
.cast<AiSuggestionDraft?>()
|
|
.firstOrNull;
|
|
if (draft == null || draft.status != AiSuggestionStatus.pending) {
|
|
return;
|
|
}
|
|
if (!_personExists(current.people, draft.personId)) {
|
|
await _setAiSuggestionStatus(draftId, AiSuggestionStatus.dismissed);
|
|
return;
|
|
}
|
|
|
|
final DateTime now = DateTime.now();
|
|
final List<AiSuggestionDraft> drafts = current.aiSuggestionDrafts
|
|
.map(
|
|
(AiSuggestionDraft item) => item.id == draftId
|
|
? item.copyWith(status: AiSuggestionStatus.accepted)
|
|
: item,
|
|
)
|
|
.toList(growable: false);
|
|
|
|
switch (draft.kind) {
|
|
case AiSuggestionKind.giftIdea:
|
|
case AiSuggestionKind.eventIdea:
|
|
final RelationshipIdea idea = RelationshipIdea(
|
|
id: 'i-${_uuid.v4()}',
|
|
personId: draft.personId,
|
|
type: draft.kind == AiSuggestionKind.giftIdea
|
|
? IdeaType.gift
|
|
: IdeaType.event,
|
|
title: draft.title,
|
|
details: _aiIdeaDetails(draft),
|
|
createdAt: now,
|
|
);
|
|
await _setState(
|
|
current.copyWith(
|
|
aiSuggestionDrafts: drafts,
|
|
ideas: <RelationshipIdea>[idea, ...current.ideas],
|
|
),
|
|
);
|
|
await _enqueueChange(
|
|
_buildEnvelope(
|
|
entityType: _ideaEntityType(idea.type),
|
|
entityId: idea.id,
|
|
op: ChangeOperation.upsert,
|
|
payload: _ideaPayload(idea),
|
|
),
|
|
);
|
|
case AiSuggestionKind.checkIn:
|
|
final DashboardTask task = DashboardTask(
|
|
id: 't-${_uuid.v4()}',
|
|
title: draft.title,
|
|
description: _aiDraftDetails(draft),
|
|
when: draft.suggestedFor ?? now.add(const Duration(days: 1)),
|
|
);
|
|
await _setState(
|
|
current.copyWith(
|
|
aiSuggestionDrafts: drafts,
|
|
tasks: <DashboardTask>[task, ...current.tasks],
|
|
),
|
|
);
|
|
case AiSuggestionKind.reminder:
|
|
final ReminderRule reminder = ReminderRule(
|
|
id: 'r-${_uuid.v4()}',
|
|
personId: draft.personId,
|
|
title: draft.title,
|
|
cadence: ReminderCadence.weekly,
|
|
nextAt: draft.suggestedFor ?? now.add(const Duration(days: 7)),
|
|
);
|
|
await _setState(
|
|
current.copyWith(
|
|
aiSuggestionDrafts: drafts,
|
|
reminders: <ReminderRule>[reminder, ...current.reminders],
|
|
),
|
|
);
|
|
await _enqueueChange(
|
|
_buildEnvelope(
|
|
entityType: 'reminderRule',
|
|
entityId: reminder.id,
|
|
op: ChangeOperation.upsert,
|
|
payload: _reminderPayload(reminder),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> dismissAiSuggestionDraft(String draftId) {
|
|
return _setAiSuggestionStatus(draftId, AiSuggestionStatus.dismissed);
|
|
}
|
|
|
|
Future<void> _setAiSuggestionStatus(
|
|
String draftId,
|
|
AiSuggestionStatus status,
|
|
) async {
|
|
final LocalDataState current = _requireState();
|
|
final List<AiSuggestionDraft> drafts = current.aiSuggestionDrafts
|
|
.map(
|
|
(AiSuggestionDraft item) =>
|
|
item.id == draftId ? item.copyWith(status: status) : item,
|
|
)
|
|
.toList(growable: false);
|
|
await _setState(current.copyWith(aiSuggestionDrafts: drafts));
|
|
}
|
|
}
|
|
|
|
String _aiDraftDedupeKey(AiSuggestionDraft draft) {
|
|
return <String>[
|
|
draft.personId,
|
|
draft.kind.name,
|
|
draft.title.trim().toLowerCase(),
|
|
draft.details.trim().toLowerCase(),
|
|
].join('|');
|
|
}
|
|
|
|
String _aiDraftDetails(AiSuggestionDraft draft) {
|
|
final String reason = draft.reason == null || draft.reason!.trim().isEmpty
|
|
? ''
|
|
: '\n\nWhy: ${draft.reason!.trim()}';
|
|
return '${draft.details.trim()}$reason'.trim();
|
|
}
|
|
|
|
String _aiIdeaDetails(AiSuggestionDraft draft) {
|
|
final List<String> parts = <String>[
|
|
_aiDraftDetails(draft),
|
|
if (draft.eventLocation != null && draft.eventLocation!.trim().isNotEmpty)
|
|
'Location: ${draft.eventLocation!.trim()}',
|
|
if (draft.eventUrl != null && draft.eventUrl!.trim().isNotEmpty)
|
|
'Event: ${draft.eventUrl!.trim()}',
|
|
if (draft.sourceUrls.isNotEmpty) 'Sources: ${draft.sourceUrls.join(', ')}',
|
|
].where((String value) => value.trim().isNotEmpty).toList(growable: false);
|
|
return parts.join('\n\n').trim();
|
|
}
|
|
|
|
extension<T> on Iterable<T> {
|
|
T? get firstOrNull => isEmpty ? null : first;
|
|
}
|