From cc9dba27900e3cb8caa9a2eea86cbf8d2c9c0e95 Mon Sep 17 00:00:00 2001 From: Horus Date: Fri, 27 Feb 2026 15:25:06 +0100 Subject: [PATCH] Fix transcripts: use API route for server-side fs --- app/api/transcripts/route.ts | 74 ++++++++++++++++++++ app/mission-control/transcripts/page.tsx | 89 +++++++++++------------- 2 files changed, 116 insertions(+), 47 deletions(-) create mode 100644 app/api/transcripts/route.ts diff --git a/app/api/transcripts/route.ts b/app/api/transcripts/route.ts new file mode 100644 index 0000000..c86dcba --- /dev/null +++ b/app/api/transcripts/route.ts @@ -0,0 +1,74 @@ +import { NextRequest, NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; + +const STORAGE_FILE = path.join(process.cwd(), "data", "transcripts.json"); + +function ensureStorage() { + const dir = path.dirname(STORAGE_FILE); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + if (!fs.existsSync(STORAGE_FILE)) { + fs.writeFileSync(STORAGE_FILE, "[]"); + } +} + +function getTranscripts() { + ensureStorage(); + return JSON.parse(fs.readFileSync(STORAGE_FILE, "utf-8")); +} + +function saveTranscripts(data: any[]) { + ensureStorage(); + fs.writeFileSync(STORAGE_FILE, JSON.stringify(data, null, 2)); +} + +export async function GET() { + const transcripts = getTranscripts(); + return NextResponse.json(transcripts); +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { videoUrl, videoId, title, transcript, categories, action } = body; + + if (action === "delete") { + const transcripts = getTranscripts().filter(t => t.videoId !== videoId); + saveTranscripts(transcripts); + return NextResponse.json({ success: true, transcripts }); + } + + // Save or update transcript + let vid = videoId; + if (!vid && videoUrl) { + const match = videoUrl.match(/(?:v=|\/)([0-9A-Za-z_-]{11})/); + vid = match ? match[1] : videoUrl; + } + + const data = { + videoId: vid, + videoUrl: videoUrl || `https://www.youtube.com/watch?v=${vid}`, + title: title || `Video ${vid}`, + transcript: transcript || "", + categories: categories || [], + savedAt: new Date().toISOString() + }; + + const transcripts = getTranscripts(); + const existing = transcripts.findIndex(t => t.videoId === vid); + + if (existing >= 0) { + transcripts[existing] = data; + } else { + transcripts.unshift(data); + } + + saveTranscripts(transcripts); + return NextResponse.json({ success: true, transcripts }); + + } catch (error) { + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/app/mission-control/transcripts/page.tsx b/app/mission-control/transcripts/page.tsx index f7333ff..8551771 100644 --- a/app/mission-control/transcripts/page.tsx +++ b/app/mission-control/transcripts/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useState, useEffect } from "react"; -import fs from "fs"; const AVAILABLE_CATEGORIES = [ "SiteMente", @@ -15,17 +14,6 @@ const AVAILABLE_CATEGORIES = [ "Personal Growth" ]; -const STORAGE_FILE = "/root/.openclaw/workspace/SiteMente/data/transcripts.json"; - -function getTranscripts() { - try { - if (fs.existsSync(STORAGE_FILE)) { - return JSON.parse(fs.readFileSync(STORAGE_FILE, "utf-8")); - } - } catch (e) {} - return []; -} - export default function TranscriptsPage() { const [transcripts, setTranscripts] = useState([]); const [videoUrl, setVideoUrl] = useState(""); @@ -36,9 +24,15 @@ export default function TranscriptsPage() { const [saving, setSaving] = useState(false); useEffect(() => { - setTranscripts(getTranscripts()); + fetchTranscripts(); }, []); + const fetchTranscripts = async () => { + const res = await fetch("/api/transcripts"); + const data = await res.json(); + setTranscripts(data); + }; + const toggleCategory = (cat: string) => { setSelectedCategories(prev => prev.includes(cat) @@ -51,35 +45,42 @@ export default function TranscriptsPage() { if (!videoUrl || !transcriptText) return; setSaving(true); - // Extract video ID - const match = videoUrl.match(/(?:v=|\/)([0-9A-Za-z_-]{11})/); - const videoId = match ? match[1] : ""; + const res = await fetch("/api/transcripts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + videoUrl, + title: title || videoUrl, + transcript: transcriptText, + categories: selectedCategories + }) + }); - const data = { - videoId, - videoUrl, - title: title || `Video ${videoId}`, - transcript: transcriptText, - categories: selectedCategories, - savedAt: new Date().toISOString() - }; - - // Save directly - const current = getTranscripts(); - const existing = current.findIndex(t => t.videoId === videoId); - if (existing >= 0) { - current[existing] = data; - } else { - current.unshift(data); - } - - fs.writeFileSync(STORAGE_FILE, JSON.stringify(current, null, 2)); - setTranscripts(current); + const data = await res.json(); setSaving(false); - setVideoUrl(""); - setTranscriptText(""); - setTitle(""); - setSelectedCategories([]); + + if (data.transcripts) { + setTranscripts(data.transcripts); + setVideoUrl(""); + setTranscriptText(""); + setTitle(""); + setSelectedCategories([]); + } + }; + + const deleteTranscript = async (videoId: string) => { + if (!confirm("Delete this transcript?")) return; + + const res = await fetch("/api/transcripts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "delete", videoId }) + }); + + const data = await res.json(); + if (data.transcripts) { + setTranscripts(data.transcripts); + } }; const filteredTranscripts = filterCategory === "all" @@ -191,13 +192,7 @@ export default function TranscriptsPage() { {new Date(t.savedAt).toLocaleDateString()}