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() {
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 };
}

View File

@@ -7,7 +7,12 @@ const DB_FILE = process.env.DB_PATH || join(__dirname, 'data.json');
function loadDB() {
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 } };
}

View File

@@ -83,6 +83,7 @@ router.post('/from-tiktok', async (req, res) => {
let extracted = null;
let aiStatus = ollamaAvailable ? 'available' : 'unavailable';
let aiMessage = '';
if (ollamaAvailable && tiktokData.title) {
try {
extracted = await extractRecipe(tiktokData.title);
@@ -90,13 +91,19 @@ router.post('/from-tiktok', async (req, res) => {
} catch (e) {
console.error('Ollama Extraktion fehlgeschlagen:', e.message);
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({
tiktok: tiktokData,
ollama: ollamaAvailable,
aiStatus,
aiMessage,
extracted,
});
} catch (error) {
@@ -115,6 +122,7 @@ router.post('/from-instagram', async (req, res) => {
let extracted = null;
let aiStatus = ollamaAvailable ? 'available' : 'unavailable';
let aiMessage = '';
if (ollamaAvailable && instagramData.title) {
try {
extracted = await extractRecipe(instagramData.title);
@@ -122,13 +130,19 @@ router.post('/from-instagram', async (req, res) => {
} catch (e) {
console.error('Ollama Extraktion fehlgeschlagen:', e.message);
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({
instagram: instagramData,
ollama: ollamaAvailable,
aiStatus,
aiMessage,
extracted,
});
} catch (error) {

View File

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

View File

@@ -72,42 +72,40 @@ export async function checkAiStatus() {
return apiFetch(`${API_BASE}/ai-status`);
}
export async function fetchAnalyticsOverview(token) {
const res = await fetch('/api/analytics/overview', {
async function analyticsFetch(url, token) {
const res = await fetch(url, {
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) {
const params = new URLSearchParams();
if (from) params.set('from', from);
if (to) params.set('to', to);
const res = await fetch(`/api/analytics/pageviews?${params}`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
return analyticsFetch(`/api/analytics/pageviews?${params}`, token);
}
export async function fetchAnalyticsUsers(token) {
const res = await fetch('/api/analytics/users', {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
return analyticsFetch('/api/analytics/users', token);
}
export async function fetchAnalyticsRecipes(token) {
const res = await fetch('/api/analytics/recipes', {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
return analyticsFetch('/api/analytics/recipes', token);
}
export async function fetchAnalyticsActivity(token, userId) {
const params = new URLSearchParams();
if (userId) params.set('user_id', userId);
const res = await fetch(`/api/analytics/activity?${params}`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
return analyticsFetch(`/api/analytics/activity?${params}`, token);
}

View File

@@ -31,6 +31,7 @@ export default function AddRecipe() {
const [source, setSource] = useState('');
const [hasData, setHasData] = useState(false);
const [aiStatus, setAiStatus] = useState(null);
const [aiMessage, setAiMessage] = useState('');
const handleExtract = async () => {
if (!url.trim()) return;
@@ -43,6 +44,7 @@ export default function AddRecipe() {
setLoading(true);
setError('');
setAiStatus(null);
setAiMessage('');
try {
let data;
if (detected === 'instagram') {
@@ -69,6 +71,7 @@ export default function AddRecipe() {
setName(detected === 'instagram' ? (data.instagram?.title || '') : (data.tiktok?.title || ''));
}
setAiStatus(data.aiStatus || (data.extracted ? 'success' : 'unavailable'));
setAiMessage(data.aiMessage || '');
setHasData(true);
} catch (e) {
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">
<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>
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' && (

View File

@@ -68,6 +68,7 @@ export default function Analytics() {
const [users, setUsers] = useState([]);
const [activity, setActivity] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
load();
@@ -88,12 +89,13 @@ export default function Analytics() {
setActivity(act);
} catch (e) {
console.error('Analytics Fehler:', e);
setError(e.message);
}
setLoading(false);
};
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 (
<div className="max-w-6xl mx-auto space-y-8">