Fix transcripts: use API route for server-side fs
This commit is contained in:
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
const AVAILABLE_CATEGORIES = [
|
const AVAILABLE_CATEGORIES = [
|
||||||
"SiteMente",
|
"SiteMente",
|
||||||
@@ -15,17 +14,6 @@ const AVAILABLE_CATEGORIES = [
|
|||||||
"Personal Growth"
|
"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() {
|
export default function TranscriptsPage() {
|
||||||
const [transcripts, setTranscripts] = useState<any[]>([]);
|
const [transcripts, setTranscripts] = useState<any[]>([]);
|
||||||
const [videoUrl, setVideoUrl] = useState("");
|
const [videoUrl, setVideoUrl] = useState("");
|
||||||
@@ -36,9 +24,15 @@ export default function TranscriptsPage() {
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTranscripts(getTranscripts());
|
fetchTranscripts();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchTranscripts = async () => {
|
||||||
|
const res = await fetch("/api/transcripts");
|
||||||
|
const data = await res.json();
|
||||||
|
setTranscripts(data);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleCategory = (cat: string) => {
|
const toggleCategory = (cat: string) => {
|
||||||
setSelectedCategories(prev =>
|
setSelectedCategories(prev =>
|
||||||
prev.includes(cat)
|
prev.includes(cat)
|
||||||
@@ -51,35 +45,42 @@ export default function TranscriptsPage() {
|
|||||||
if (!videoUrl || !transcriptText) return;
|
if (!videoUrl || !transcriptText) return;
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
|
||||||
// Extract video ID
|
const res = await fetch("/api/transcripts", {
|
||||||
const match = videoUrl.match(/(?:v=|\/)([0-9A-Za-z_-]{11})/);
|
method: "POST",
|
||||||
const videoId = match ? match[1] : "";
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
videoUrl,
|
||||||
|
title: title || videoUrl,
|
||||||
|
transcript: transcriptText,
|
||||||
|
categories: selectedCategories
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const data = {
|
const data = await res.json();
|
||||||
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);
|
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
setVideoUrl("");
|
|
||||||
setTranscriptText("");
|
if (data.transcripts) {
|
||||||
setTitle("");
|
setTranscripts(data.transcripts);
|
||||||
setSelectedCategories([]);
|
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"
|
const filteredTranscripts = filterCategory === "all"
|
||||||
@@ -191,13 +192,7 @@ export default function TranscriptsPage() {
|
|||||||
{new Date(t.savedAt).toLocaleDateString()}
|
{new Date(t.savedAt).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => deleteTranscript(t.videoId)}
|
||||||
if (confirm("Delete this transcript?")) {
|
|
||||||
const updated = transcripts.filter(x => x.videoId !== t.videoId);
|
|
||||||
fs.writeFileSync(STORAGE_FILE, JSON.stringify(updated, null, 2));
|
|
||||||
setTranscripts(updated);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="text-red-400 hover:text-red-300"
|
className="text-red-400 hover:text-red-300"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
|
|||||||
Reference in New Issue
Block a user