File size: 6,650 Bytes
be02369 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
import React, { useState, useCallback, useEffect } from 'react';
import ImageUploader from '../components/ImageUploader';
import AnalysisDisplay from '../components/AnalysisDisplay';
import Spinner from '../components/Spinner';
import { SparklesIcon, AlertTriangleIcon } from '../components/icons';
import { analyzeBonsai, getProtectionProfile, isAIConfigured } from '../services/geminiService';
import type { BonsaiAnalysis, BonsaiTree, View } from '../types';
import { AppStatus } from '../types';
interface AiStewardViewProps {
setActiveView: (view: View) => void;
}
const AiStewardView: React.FC<AiStewardViewProps> = ({ setActiveView }) => {
const [status, setStatus] = useState<AppStatus>(AppStatus.IDLE);
const [analysisResult, setAnalysisResult] = useState<BonsaiAnalysis | null>(null);
const [error, setError] = useState<string>('');
const [currentImage, setCurrentImage] = useState<string>('');
const [currentLocation, setCurrentLocation] = useState('');
const [prefilledSpecies, setPrefilledSpecies] = useState('');
const aiConfigured = isAIConfigured();
useEffect(() => {
const species = window.sessionStorage.getItem('prefilled-species');
if (species) {
setPrefilledSpecies(species);
window.sessionStorage.removeItem('prefilled-species');
}
}, []);
const handleAnalyze = useCallback(async (imageBase64: string, species: string, location: string) => {
setStatus(AppStatus.ANALYZING);
setError('');
setAnalysisResult(null);
setCurrentImage(imageBase64);
setCurrentLocation(location);
try {
const result = await analyzeBonsai(imageBase64, species, location);
if (result) {
setAnalysisResult(result);
setStatus(AppStatus.SUCCESS);
} else {
throw new Error('Failed to get analysis. The AI may be busy, or there was an issue with the request. Please try again.');
}
} catch (e: any) {
setError(e.message);
setStatus(AppStatus.ERROR);
}
}, []);
const handleSaveToDiary = async () => {
try {
if (!analysisResult || !currentImage || !currentLocation) {
alert("Cannot save. Analysis data is missing.");
return;
}
const treeName = window.prompt("What would you like to name this tree in your garden?", analysisResult.species);
if (!treeName) return; // User cancelled
// Fetch the protection profile for the new tree
const protectionProfileData = await getProtectionProfile(analysisResult.species);
const newTree: BonsaiTree = {
id: `tree-${Date.now()}`,
name: treeName,
species: analysisResult.species,
acquiredDate: new Date().toISOString(),
source: "AI Steward Analysis",
location: currentLocation,
initialPhoto: currentImage,
logs: [],
analysisHistory: [{ date: new Date().toISOString(), analysis: analysisResult }],
protectionProfile: protectionProfileData ? { ...protectionProfileData, alertsEnabled: true } : undefined,
};
const storageKey = `yuki-app-bonsai-diary-trees`;
const existingTreesJSON = window.localStorage.getItem(storageKey);
const existingTrees: BonsaiTree[] = existingTreesJSON ? JSON.parse(existingTreesJSON) : [];
const updatedTrees = [...existingTrees, newTree];
window.localStorage.setItem(storageKey, JSON.stringify(updatedTrees));
window.localStorage.setItem('yuki-bonsai-diary-newly-added-tree-id', newTree.id);
setActiveView('garden');
} catch (error) {
console.error("Failed to save tree to garden:", error);
alert("Could not save the tree to your garden. An unexpected error occurred. Please check browser permissions for storage and try again.");
}
};
const handleReset = () => {
setStatus(AppStatus.IDLE);
setAnalysisResult(null);
setError('');
setCurrentImage('');
setCurrentLocation('');
setPrefilledSpecies('');
};
const renderContent = () => {
switch (status) {
case AppStatus.ANALYZING:
return <div className="flex justify-center items-center h-full"><Spinner /></div>;
case AppStatus.SUCCESS:
return analysisResult ? <AnalysisDisplay analysis={analysisResult} onReset={handleReset} onSaveToDiary={handleSaveToDiary} treeImageBase64={currentImage} /> : null;
case AppStatus.ERROR:
return (
<div className="text-center p-8 bg-white rounded-lg shadow-lg border border-red-200 max-w-md mx-auto">
<h3 className="text-xl font-semibold text-red-700">An Error Occurred</h3>
<p className="text-stone-600 mt-2">{error}</p>
<button
onClick={handleReset}
className="mt-6 bg-green-700 text-white font-semibold py-2 px-6 rounded-lg hover:bg-green-600 transition-colors"
>
Try Again
</button>
</div>
);
case AppStatus.IDLE:
default:
return (
<>
<ImageUploader onAnalyze={handleAnalyze} isAnalyzing={false} defaultSpecies={prefilledSpecies} disabled={!aiConfigured}/>
{!aiConfigured && (
<div className="mt-6 p-4 bg-yellow-50 text-yellow-800 rounded-lg border border-yellow-200 text-center max-w-2xl mx-auto">
<div className="flex items-center justify-center gap-2">
<AlertTriangleIcon className="w-5 h-5"/>
<h3 className="font-semibold">AI Features Disabled</h3>
</div>
<p className="text-sm mt-1">
Please set your Gemini API key in the{' '}
<button onClick={() => setActiveView('settings')} className="font-bold underline hover:text-yellow-900">
Settings page
</button>
{' '}to enable this feature.
</p>
</div>
)}
</>
);
}
};
return (
<div className="space-y-8">
<header className="text-center">
<h2 className="text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl flex items-center justify-center gap-3">
<SparklesIcon className="w-8 h-8 text-green-600" />
New Tree Analysis
</h2>
<p className="mt-4 text-lg leading-8 text-stone-600 max-w-2xl mx-auto">
Welcome a new tree to your collection. Get an instant, expert analysis from Yuki, our AI Bonsai Sensei.
</p>
</header>
<div className="w-full">
{renderContent()}
</div>
</div>
);
};
export default AiStewardView; |