Diagnose-Feinschliff: Art-Abgleich, nur Fließtext, konsequentes Du
Die angegebene Art ist jetzt eine unbestätigte Angabe: Claude prüft selbst, welche Pflanze zu sehen ist, beurteilt bei Abweichung die Pflanze im Bild und meldet matchesSpecies=false — die App zeigt dann einen Warnhinweis im Ergebnis-Sheet. Prompt verbietet HTML/Markdown (kein <br>) und schreibt durchgängiges Duzen vor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
18b060413b
commit
b665bae95f
6 changed files with 68 additions and 4 deletions
|
|
@ -131,6 +131,7 @@ async function askClaude<T>(
|
||||||
*/
|
*/
|
||||||
interface DiagnosisResult {
|
interface DiagnosisResult {
|
||||||
healthy: boolean;
|
healthy: boolean;
|
||||||
|
matchesSpecies: boolean;
|
||||||
summary: string;
|
summary: string;
|
||||||
details: string;
|
details: string;
|
||||||
treatment: string;
|
treatment: string;
|
||||||
|
|
@ -168,8 +169,15 @@ export const diagnosePlant = onCall(
|
||||||
request.data.speciesHint.slice(0, 200) :
|
request.data.speciesHint.slice(0, 200) :
|
||||||
"";
|
"";
|
||||||
|
|
||||||
|
// Die Art ist nur eine UNBESTÄTIGTE Angabe des Besitzers — Claude soll
|
||||||
|
// selbst prüfen, ob das Foto wirklich diese Pflanze zeigt (der Nutzer
|
||||||
|
// kann versehentlich das Foto einer anderen Pflanze hochladen).
|
||||||
const speciesPart = speciesHint ?
|
const speciesPart = speciesHint ?
|
||||||
`Es handelt sich vermutlich um "${speciesHint}". ` :
|
`Laut Besitzer soll es sich um "${speciesHint}" handeln — verlasse ` +
|
||||||
|
"dich NICHT darauf, sondern prüfe selbst, welche Pflanze auf dem " +
|
||||||
|
"Foto zu sehen ist. Zeigt das Foto eindeutig eine andere Art, " +
|
||||||
|
"beurteile die Pflanze, die tatsächlich zu sehen ist, und nenne " +
|
||||||
|
"ihre Art im Kurzbefund. " :
|
||||||
"";
|
"";
|
||||||
const diagnosis = await askClaude<DiagnosisResult>(
|
const diagnosis = await askClaude<DiagnosisResult>(
|
||||||
imageBase64,
|
imageBase64,
|
||||||
|
|
@ -177,10 +185,14 @@ export const diagnosePlant = onCall(
|
||||||
`der Pflanze auf dem Foto. ${speciesPart}` +
|
`der Pflanze auf dem Foto. ${speciesPart}` +
|
||||||
"Achte auf Blattverfärbungen, Flecken, Schädlinge, Schimmel, " +
|
"Achte auf Blattverfärbungen, Flecken, Schädlinge, Schimmel, " +
|
||||||
"Trockenheit, Überwässerung und ähnliche Anzeichen. " +
|
"Trockenheit, Überwässerung und ähnliche Anzeichen. " +
|
||||||
"Antworte auf Deutsch, in einfacher, freundlicher Sprache " +
|
"Sprache: Deutsch, einfach und freundlich (auch für ältere Menschen " +
|
||||||
"(auch für ältere Menschen gut verständlich, keine Fachbegriffe ohne " +
|
"gut verständlich, keine Fachbegriffe ohne Erklärung). Sprich die " +
|
||||||
"Erklärung). Antworte NUR mit JSON im Format " +
|
"Person immer mit „du“ an, niemals mit „Sie“. Schreibe reinen " +
|
||||||
|
"Fließtext ohne HTML-Tags (kein <br> o. Ä.) und ohne " +
|
||||||
|
"Markdown-Formatierung. Antworte NUR mit JSON im Format " +
|
||||||
'{"healthy": <true wenn die Pflanze gesund wirkt, sonst false>, ' +
|
'{"healthy": <true wenn die Pflanze gesund wirkt, sonst false>, ' +
|
||||||
|
'"matchesSpecies": <false NUR wenn eine Art angegeben wurde und das ' +
|
||||||
|
"Foto eindeutig eine andere Pflanzenart zeigt, sonst true>, " +
|
||||||
'"summary": "<Kurzbefund in 1 Satz>", ' +
|
'"summary": "<Kurzbefund in 1 Satz>", ' +
|
||||||
'"details": "<2-3 Sätze: was ist zu sehen und was ist die ' +
|
'"details": "<2-3 Sätze: was ist zu sehen und was ist die ' +
|
||||||
'wahrscheinliche Ursache>", ' +
|
'wahrscheinliche Ursache>", ' +
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
class DiagnosisResult {
|
class DiagnosisResult {
|
||||||
const DiagnosisResult({
|
const DiagnosisResult({
|
||||||
required this.healthy,
|
required this.healthy,
|
||||||
|
required this.matchesSpecies,
|
||||||
required this.summary,
|
required this.summary,
|
||||||
required this.details,
|
required this.details,
|
||||||
required this.treatment,
|
required this.treatment,
|
||||||
|
|
@ -15,6 +16,11 @@ class DiagnosisResult {
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool healthy;
|
final bool healthy;
|
||||||
|
|
||||||
|
/// false, wenn das Foto laut KI eindeutig eine andere Pflanzenart zeigt
|
||||||
|
/// als die im Profil hinterlegte (z. B. versehentlich falsches Foto).
|
||||||
|
final bool matchesSpecies;
|
||||||
|
|
||||||
final String summary;
|
final String summary;
|
||||||
final String details;
|
final String details;
|
||||||
final String treatment;
|
final String treatment;
|
||||||
|
|
@ -43,6 +49,7 @@ class PlantDiagnosisService {
|
||||||
final data = response.data;
|
final data = response.data;
|
||||||
return DiagnosisResult(
|
return DiagnosisResult(
|
||||||
healthy: data['healthy'] as bool? ?? false,
|
healthy: data['healthy'] as bool? ?? false,
|
||||||
|
matchesSpecies: data['matchesSpecies'] as bool? ?? true,
|
||||||
summary: data['summary'] as String? ?? '',
|
summary: data['summary'] as String? ?? '',
|
||||||
details: data['details'] as String? ?? '',
|
details: data['details'] as String? ?? '',
|
||||||
treatment: data['treatment'] as String? ?? '',
|
treatment: data['treatment'] as String? ?? '',
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,34 @@ class _DiagnosisSectionState extends ConsumerState<_DiagnosisSection> {
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
children: [
|
children: [
|
||||||
|
if (!result.matchesSpecies) ...[
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.errorContainer,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.swap_horiz,
|
||||||
|
color: theme.colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
l10n.diagnosisSpeciesMismatch(widget.plant.nickname),
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,12 @@
|
||||||
"diagnosisTreatmentTitle": "Was du tun kannst",
|
"diagnosisTreatmentTitle": "Was du tun kannst",
|
||||||
"diagnosisPreventionTitle": "So beugst du vor",
|
"diagnosisPreventionTitle": "So beugst du vor",
|
||||||
"diagnosisDisclaimer": "Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.",
|
"diagnosisDisclaimer": "Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.",
|
||||||
|
"diagnosisSpeciesMismatch": "Das Foto zeigt offenbar eine andere Pflanze als „{name}“. Die Einschätzung unten bezieht sich auf die Pflanze im Bild.",
|
||||||
|
"@diagnosisSpeciesMismatch": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
"diagnosisFailed": "Die Untersuchung hat nicht geklappt. Bitte versuche es erneut.",
|
"diagnosisFailed": "Die Untersuchung hat nicht geklappt. Bitte versuche es erneut.",
|
||||||
"saveError": "Speichern fehlgeschlagen. Bitte versuche es erneut.",
|
"saveError": "Speichern fehlgeschlagen. Bitte versuche es erneut.",
|
||||||
"saveErrorDetail": "Speichern fehlgeschlagen ({detail}). Bitte versuche es erneut.",
|
"saveErrorDetail": "Speichern fehlgeschlagen ({detail}). Bitte versuche es erneut.",
|
||||||
|
|
|
||||||
|
|
@ -862,6 +862,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.'**
|
/// **'Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.'**
|
||||||
String get diagnosisDisclaimer;
|
String get diagnosisDisclaimer;
|
||||||
|
|
||||||
|
/// No description provided for @diagnosisSpeciesMismatch.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Das Foto zeigt offenbar eine andere Pflanze als „{name}“. Die Einschätzung unten bezieht sich auf die Pflanze im Bild.'**
|
||||||
|
String diagnosisSpeciesMismatch(String name);
|
||||||
|
|
||||||
/// No description provided for @diagnosisFailed.
|
/// No description provided for @diagnosisFailed.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get diagnosisDisclaimer =>
|
String get diagnosisDisclaimer =>
|
||||||
'Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.';
|
'Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String diagnosisSpeciesMismatch(String name) {
|
||||||
|
return 'Das Foto zeigt offenbar eine andere Pflanze als „$name“. Die Einschätzung unten bezieht sich auf die Pflanze im Bild.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get diagnosisFailed =>
|
String get diagnosisFailed =>
|
||||||
'Die Untersuchung hat nicht geklappt. Bitte versuche es erneut.';
|
'Die Untersuchung hat nicht geklappt. Bitte versuche es erneut.';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue