Gesprächsthemen: Halluzinations-Schutz + Datenmangel-Hinweis

- Code-seitiger Guard: < 2 Momente/Einträge → .insufficientData-State
  statt API-Call (verhindert Halluzinationen bei leeren Profilen)
- UI: Info-Hinweis "Noch zu wenig Verlauf für persönliche Vorschläge"
- Prompt: STRIKT-Anweisung, nur vorhandene Daten zu verwenden

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 06:31:46 +02:00
parent f1de4bfd30
commit 1ecc44a625
3 changed files with 43 additions and 5 deletions
+29 -3
View File
@@ -494,6 +494,8 @@ struct AddMomentView: View {
conversationResultView(result: result) conversationResultView(result: result)
case .error(let message): case .error(let message):
conversationErrorView(message: message) conversationErrorView(message: message)
case .insufficientData:
conversationInsufficientDataView
} }
} }
.background(theme.surfaceCard) .background(theme.surfaceCard)
@@ -625,6 +627,18 @@ struct AddMomentView: View {
} }
} }
private var conversationInsufficientDataView: some View {
HStack(spacing: 10) {
Image(systemName: "info.circle")
.font(.system(size: 14))
.foregroundStyle(theme.contentTertiary)
Text("Noch zu wenig Verlauf für persönliche Vorschläge.")
.font(.system(size: 14))
.foregroundStyle(theme.contentSecondary)
}
.padding(16)
}
private func conversationErrorView(message: String) -> some View { private func conversationErrorView(message: String) -> some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Label("Vorschläge fehlgeschlagen", systemImage: "exclamationmark.triangle") Label("Vorschläge fehlgeschlagen", systemImage: "exclamationmark.triangle")
@@ -644,7 +658,18 @@ struct AddMomentView: View {
.padding(16) .padding(16)
} }
/// Mindestanzahl an Momenten + Log-Einträgen für sinnvolle KI-Vorschläge.
private var hasEnoughHistory: Bool {
let momentCount = person.sortedMoments.count
let logCount = person.sortedLogEntries.count
return momentCount + logCount >= 2
}
private func loadConversationSuggestions() async { private func loadConversationSuggestions() async {
guard hasEnoughHistory else {
conversationState = .insufficientData
return
}
guard !AIAnalysisService.shared.isRateLimited else { return } guard !AIAnalysisService.shared.isRateLimited else { return }
conversationState = .loading conversationState = .loading
do { do {
@@ -668,14 +693,15 @@ private enum ConversationSuggestionUIState {
case loading case loading
case result(ConversationSuggestionResult, Date) case result(ConversationSuggestionResult, Date)
case error(String) case error(String)
case insufficientData
} }
extension ConversationSuggestionUIState: Equatable { extension ConversationSuggestionUIState: Equatable {
static func == (lhs: ConversationSuggestionUIState, rhs: ConversationSuggestionUIState) -> Bool { static func == (lhs: ConversationSuggestionUIState, rhs: ConversationSuggestionUIState) -> Bool {
switch (lhs, rhs) { switch (lhs, rhs) {
case (.idle, .idle), (.loading, .loading): return true case (.idle, .idle), (.loading, .loading), (.insufficientData, .insufficientData): return true
case (.error(let a), .error(let b)): return a == b case (.error(let a), .error(let b)): return a == b
default: return false default: return false
} }
} }
} }
+12
View File
@@ -2622,6 +2622,7 @@
}, },
"Gesprächsthemen vorschlagen: KI-Impulse für bessere Treffen" : { "Gesprächsthemen vorschlagen: KI-Impulse für bessere Treffen" : {
"comment" : "PaywallView Max feature list item", "comment" : "PaywallView Max feature list item",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@@ -4049,6 +4050,17 @@
} }
} }
}, },
"Noch zu wenig Verlauf für persönliche Vorschläge." : {
"comment" : "AddMomentView conversation suggestions insufficient data message",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Not enough history yet for personal suggestions."
}
}
}
},
"Noch keine Einträge" : { "Noch keine Einträge" : {
"comment" : "LogbuchView empty state title", "comment" : "LogbuchView empty state title",
"localizations" : { "localizations" : {
+2 -2
View File
@@ -806,9 +806,9 @@ enum AppLanguage: String, CaseIterable {
var conversationInstruction: String { var conversationInstruction: String {
switch self { switch self {
case .german: case .german:
return "Du bereitest mich auf ein bevorstehendes Treffen mit dieser Person vor. Basierend auf unserer bisherigen Beziehung, gib mir sehr knappe Vorschläge. Maximal 8 Wörter pro Punkt. Keine Erklärungen, keine Sätze nur Stichwörter oder kurze Fragen. Antworte in exakt diesem Format:\n\nTHEMEN: [2-3 Stichworte oder kurze Fragen, je max. 8 Wörter, kommasepariert]\nGESPRAECHSRETTER: [2-3 kurze Impulse, je max. 8 Wörter, kommasepariert]\nTIEFE: [ein konkreter Tipp, max. 12 Wörter]" return "Du bereitest mich auf ein bevorstehendes Treffen mit dieser Person vor. Halte dich STRIKT an die vorhandenen Momente und Log-Einträge. Erfinde KEINE Details, Erlebnisse oder Themen die nicht explizit in den Daten stehen. Gib mir sehr knappe Vorschläge maximal 8 Wörter pro Punkt, nur Stichwörter oder kurze Fragen. Antworte in exakt diesem Format:\n\nTHEMEN: [2-3 Stichworte oder kurze Fragen aus den echten Daten, kommasepariert]\nGESPRAECHSRETTER: [2-3 kurze Impulse, je max. 8 Wörter, kommasepariert]\nTIEFE: [ein konkreter Tipp, max. 12 Wörter]"
case .english: case .english:
return "You are preparing me for an upcoming meeting with this person. Based on our relationship history, give me very concise suggestions. Maximum 8 words per point. No explanations, no full sentences keywords or short questions only. Respond in exactly this format:\n\nTHEMEN: [2-3 keywords or short questions, max. 8 words each, comma-separated]\nGESPRAECHSRETTER: [2-3 short impulses, max. 8 words each, comma-separated]\nTIEFE: [one concrete tip, max. 12 words]" return "You are preparing me for an upcoming meeting with this person. Stick STRICTLY to the available moments and log entries. Do NOT invent details, experiences or topics that are not explicitly in the data. Give very concise suggestions maximum 8 words per point, keywords or short questions only. Respond in exactly this format:\n\nTHEMEN: [2-3 keywords or short questions from the actual data, comma-separated]\nGESPRAECHSRETTER: [2-3 short impulses, max. 8 words each, comma-separated]\nTIEFE: [one concrete tip, max. 12 words]"
} }
} }