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:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" : {
|
||||||
|
|||||||
@@ -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]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user