fix Analyse
This commit is contained in:
@@ -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)) {
|
||||||
|
try {
|
||||||
return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
|
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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
try {
|
||||||
return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
|
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 } };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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' && (
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user