fix Analyse

This commit is contained in:
nadja
2026-08-26 16:17:54 +02:00
parent dbd31c6cd0
commit 97e6adefad
7 changed files with 54 additions and 23 deletions

View File

@@ -7,7 +7,12 @@ const DB_FILE = process.env.ANALYTICS_PATH || join(__dirname, 'analytics.json');
function loadDB() { function loadDB() {
if (existsSync(DB_FILE)) { if (existsSync(DB_FILE)) {
return JSON.parse(readFileSync(DB_FILE, 'utf-8')); try {
return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
} catch (e) {
console.error('Analytics Datei korrupt, Backup erstellt:', e.message);
try { writeFileSync(DB_FILE + '.bak', readFileSync(DB_FILE, 'utf-8')); } catch {}
}
} }
return { events: [], _counter: 0 }; return { events: [], _counter: 0 };
} }

View File

@@ -7,7 +7,12 @@ const DB_FILE = process.env.DB_PATH || join(__dirname, 'data.json');
function loadDB() { function loadDB() {
if (existsSync(DB_FILE)) { if (existsSync(DB_FILE)) {
return JSON.parse(readFileSync(DB_FILE, 'utf-8')); try {
return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
} catch (e) {
console.error('DB Datei korrupt, Backup erstellt:', e.message);
try { writeFileSync(DB_FILE + '.bak', readFileSync(DB_FILE, 'utf-8')); } catch {}
}
} }
return { users: [], recipes: [], invite_tokens: [], _counters: { users: 0, recipes: 0, invite_tokens: 0 } }; return { users: [], recipes: [], invite_tokens: [], _counters: { users: 0, recipes: 0, invite_tokens: 0 } };
} }

View File

@@ -83,6 +83,7 @@ router.post('/from-tiktok', async (req, res) => {
let extracted = null; let extracted = null;
let aiStatus = ollamaAvailable ? 'available' : 'unavailable'; let aiStatus = ollamaAvailable ? 'available' : 'unavailable';
let aiMessage = '';
if (ollamaAvailable && tiktokData.title) { if (ollamaAvailable && tiktokData.title) {
try { try {
extracted = await extractRecipe(tiktokData.title); extracted = await extractRecipe(tiktokData.title);
@@ -90,13 +91,19 @@ router.post('/from-tiktok', async (req, res) => {
} catch (e) { } catch (e) {
console.error('Ollama Extraktion fehlgeschlagen:', e.message); console.error('Ollama Extraktion fehlgeschlagen:', e.message);
aiStatus = 'error'; aiStatus = 'error';
aiMessage = e.message;
} }
} else if (!ollamaAvailable) {
aiMessage = 'Ollama-Server nicht erreichbar';
} else if (!tiktokData.title) {
aiMessage = 'Kein Titel im Video gefunden';
} }
res.json({ res.json({
tiktok: tiktokData, tiktok: tiktokData,
ollama: ollamaAvailable, ollama: ollamaAvailable,
aiStatus, aiStatus,
aiMessage,
extracted, extracted,
}); });
} catch (error) { } catch (error) {
@@ -115,6 +122,7 @@ router.post('/from-instagram', async (req, res) => {
let extracted = null; let extracted = null;
let aiStatus = ollamaAvailable ? 'available' : 'unavailable'; let aiStatus = ollamaAvailable ? 'available' : 'unavailable';
let aiMessage = '';
if (ollamaAvailable && instagramData.title) { if (ollamaAvailable && instagramData.title) {
try { try {
extracted = await extractRecipe(instagramData.title); extracted = await extractRecipe(instagramData.title);
@@ -122,13 +130,19 @@ router.post('/from-instagram', async (req, res) => {
} catch (e) { } catch (e) {
console.error('Ollama Extraktion fehlgeschlagen:', e.message); console.error('Ollama Extraktion fehlgeschlagen:', e.message);
aiStatus = 'error'; aiStatus = 'error';
aiMessage = e.message;
} }
} else if (!ollamaAvailable) {
aiMessage = 'Ollama-Server nicht erreichbar';
} else if (!instagramData.title) {
aiMessage = 'Kein Titel im Video gefunden';
} }
res.json({ res.json({
instagram: instagramData, instagram: instagramData,
ollama: ollamaAvailable, ollama: ollamaAvailable,
aiStatus, aiStatus,
aiMessage,
extracted, extracted,
}); });
} catch (error) { } catch (error) {

View File

@@ -22,6 +22,7 @@ export async function extractRecipe(description) {
prompt: `Extrahiere ein Rezept aus dieser TikTok-Videobeschreibung:\n\n${description}`, prompt: `Extrahiere ein Rezept aus dieser TikTok-Videobeschreibung:\n\n${description}`,
stream: false, stream: false,
}), }),
signal: AbortSignal.timeout(120000),
}); });
if (!response.ok) { if (!response.ok) {

View File

@@ -72,42 +72,40 @@ export async function checkAiStatus() {
return apiFetch(`${API_BASE}/ai-status`); return apiFetch(`${API_BASE}/ai-status`);
} }
export async function fetchAnalyticsOverview(token) { async function analyticsFetch(url, token) {
const res = await fetch('/api/analytics/overview', { const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
return res.json(); const text = await res.text();
if (!res.ok) {
let msg = `Server-Fehler (${res.status})`;
try { msg = JSON.parse(text).error || msg; } catch {}
throw new Error(msg);
}
return JSON.parse(text);
}
export async function fetchAnalyticsOverview(token) {
return analyticsFetch('/api/analytics/overview', token);
} }
export async function fetchAnalyticsPageviews(token, from, to) { export async function fetchAnalyticsPageviews(token, from, to) {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (from) params.set('from', from); if (from) params.set('from', from);
if (to) params.set('to', to); if (to) params.set('to', to);
const res = await fetch(`/api/analytics/pageviews?${params}`, { return analyticsFetch(`/api/analytics/pageviews?${params}`, token);
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
} }
export async function fetchAnalyticsUsers(token) { export async function fetchAnalyticsUsers(token) {
const res = await fetch('/api/analytics/users', { return analyticsFetch('/api/analytics/users', token);
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
} }
export async function fetchAnalyticsRecipes(token) { export async function fetchAnalyticsRecipes(token) {
const res = await fetch('/api/analytics/recipes', { return analyticsFetch('/api/analytics/recipes', token);
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
} }
export async function fetchAnalyticsActivity(token, userId) { export async function fetchAnalyticsActivity(token, userId) {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (userId) params.set('user_id', userId); if (userId) params.set('user_id', userId);
const res = await fetch(`/api/analytics/activity?${params}`, { return analyticsFetch(`/api/analytics/activity?${params}`, token);
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
} }

View File

@@ -31,6 +31,7 @@ export default function AddRecipe() {
const [source, setSource] = useState(''); const [source, setSource] = useState('');
const [hasData, setHasData] = useState(false); const [hasData, setHasData] = useState(false);
const [aiStatus, setAiStatus] = useState(null); const [aiStatus, setAiStatus] = useState(null);
const [aiMessage, setAiMessage] = useState('');
const handleExtract = async () => { const handleExtract = async () => {
if (!url.trim()) return; if (!url.trim()) return;
@@ -43,6 +44,7 @@ export default function AddRecipe() {
setLoading(true); setLoading(true);
setError(''); setError('');
setAiStatus(null); setAiStatus(null);
setAiMessage('');
try { try {
let data; let data;
if (detected === 'instagram') { if (detected === 'instagram') {
@@ -69,6 +71,7 @@ export default function AddRecipe() {
setName(detected === 'instagram' ? (data.instagram?.title || '') : (data.tiktok?.title || '')); setName(detected === 'instagram' ? (data.instagram?.title || '') : (data.tiktok?.title || ''));
} }
setAiStatus(data.aiStatus || (data.extracted ? 'success' : 'unavailable')); setAiStatus(data.aiStatus || (data.extracted ? 'success' : 'unavailable'));
setAiMessage(data.aiMessage || '');
setHasData(true); setHasData(true);
} catch (e) { } catch (e) {
setError('Fehler beim Abrufen der Video-Daten. Überprüfe die URL.'); setError('Fehler beim Abrufen der Video-Daten. Überprüfe die URL.');
@@ -152,7 +155,10 @@ export default function AddRecipe() {
<svg className="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 20 20"> <svg className="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" /> <path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg> </svg>
KI konnte nicht extrahieren füll die Felder manuell aus. <span>
KI-Extraktion fehlgeschlagen{aiMessage && <span className="text-yellow-600">: {aiMessage}</span>}
<span className="text-gray-400 ml-1"> füll die Felder manuell aus.</span>
</span>
</> </>
)} )}
{aiStatus === 'unavailable' && ( {aiStatus === 'unavailable' && (

View File

@@ -68,6 +68,7 @@ export default function Analytics() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [activity, setActivity] = useState([]); const [activity, setActivity] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
load(); load();
@@ -88,12 +89,13 @@ export default function Analytics() {
setActivity(act); setActivity(act);
} catch (e) { } catch (e) {
console.error('Analytics Fehler:', e); console.error('Analytics Fehler:', e);
setError(e.message);
} }
setLoading(false); setLoading(false);
}; };
if (loading) return <div className="text-center py-12 text-gray-400">Laden...</div>; if (loading) return <div className="text-center py-12 text-gray-400">Laden...</div>;
if (!overview) return <div className="text-center py-12 text-gray-400">Fehler beim Laden der Analytics-Daten.</div>; if (!overview) return <div className="text-center py-12 text-gray-400">Fehler beim Laden der Analytics-Daten. {error && <span className="text-red-400 block mt-1">{error}</span>}</div>;
return ( return (
<div className="max-w-6xl mx-auto space-y-8"> <div className="max-w-6xl mx-auto space-y-8">