From 1ecc44a6251c36d171305cfa65b6b7741ad417f2 Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 22 Apr 2026 06:31:46 +0200 Subject: [PATCH] =?UTF-8?q?Gespr=C3=A4chsthemen:=20Halluzinations-Schutz?= =?UTF-8?q?=20+=20Datenmangel-Hinweis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- nahbar/nahbar/AddMomentView.swift | 32 ++++++++++++++++++++++++++--- nahbar/nahbar/Localizable.xcstrings | 12 +++++++++++ nahbar/nahbar/SettingsView.swift | 4 ++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/nahbar/nahbar/AddMomentView.swift b/nahbar/nahbar/AddMomentView.swift index 7d6d3d6..7ecd605 100644 --- a/nahbar/nahbar/AddMomentView.swift +++ b/nahbar/nahbar/AddMomentView.swift @@ -494,6 +494,8 @@ struct AddMomentView: View { conversationResultView(result: result) case .error(let message): conversationErrorView(message: message) + case .insufficientData: + conversationInsufficientDataView } } .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 { VStack(alignment: .leading, spacing: 8) { Label("Vorschläge fehlgeschlagen", systemImage: "exclamationmark.triangle") @@ -644,7 +658,18 @@ struct AddMomentView: View { .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 { + guard hasEnoughHistory else { + conversationState = .insufficientData + return + } guard !AIAnalysisService.shared.isRateLimited else { return } conversationState = .loading do { @@ -668,14 +693,15 @@ private enum ConversationSuggestionUIState { case loading case result(ConversationSuggestionResult, Date) case error(String) + case insufficientData } extension ConversationSuggestionUIState: Equatable { static func == (lhs: ConversationSuggestionUIState, rhs: ConversationSuggestionUIState) -> Bool { switch (lhs, rhs) { - case (.idle, .idle), (.loading, .loading): return true - case (.error(let a), .error(let b)): return a == b - default: return false + case (.idle, .idle), (.loading, .loading), (.insufficientData, .insufficientData): return true + case (.error(let a), .error(let b)): return a == b + default: return false } } } diff --git a/nahbar/nahbar/Localizable.xcstrings b/nahbar/nahbar/Localizable.xcstrings index 90d1e14..0a9ae83 100644 --- a/nahbar/nahbar/Localizable.xcstrings +++ b/nahbar/nahbar/Localizable.xcstrings @@ -2622,6 +2622,7 @@ }, "Gesprächsthemen vorschlagen: KI-Impulse für bessere Treffen" : { "comment" : "PaywallView – Max feature list item", + "extractionState" : "stale", "localizations" : { "en" : { "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" : { "comment" : "LogbuchView – empty state title", "localizations" : { diff --git a/nahbar/nahbar/SettingsView.swift b/nahbar/nahbar/SettingsView.swift index 99d5b03..6b3a73b 100644 --- a/nahbar/nahbar/SettingsView.swift +++ b/nahbar/nahbar/SettingsView.swift @@ -806,9 +806,9 @@ enum AppLanguage: String, CaseIterable { var conversationInstruction: String { switch self { 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: - 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]" } }