fix Analyse
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 } };
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user