From 258464156bf49878ee9a65d9f03d3c0869d9ebb2 Mon Sep 17 00:00:00 2001 From: Ender Date: Thu, 23 Oct 2025 22:43:20 +0200 Subject: [PATCH] feat(editor): wire transcript into draft editor with local save; update PLAN; ensure API dev script present --- PLAN.md | 9 ++++-- apps/admin/src/components/EditorShell.tsx | 32 ++++++++++++++++--- apps/admin/src/features/recorder/Recorder.tsx | 6 ++-- apps/api/package.json | 4 +++ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/PLAN.md b/PLAN.md index ad4e0fd..19d80ea 100644 --- a/PLAN.md +++ b/PLAN.md @@ -68,8 +68,13 @@ Voice-first authoring tool for single-user Ghost blog. Capture audio, refine wit ## Upcoming Next Actions - [x] Backend endpoint for audio upload `/api/media/audio` (accept WebM/PCM) — implemented with MinIO via AWS SDK v3 - [x] S3-compatible adapter using MinIO (`S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`) -- [ ] Backend STT endpoint `/api/stt` (download from MinIO, call OpenAI STT, return transcript) -- [ ] Add STT trigger in UI: call `/api/stt` with `{ bucket, key }` and render transcript +- [x] Backend STT endpoint `/api/stt` (download from MinIO, call OpenAI STT, return transcript) +- [x] Add STT trigger in UI: call `/api/stt` with `{ bucket, key }` and render transcript + +## Next Priorities +- [ ] Save transcript into an editor document (draft state) and display in editor. +- [ ] List uploaded media items and allow re-use/deletion. +- [ ] Add simple document persistence API (CRUD) and local storage autosave. ## MinIO Integration Checklist - [ ] Deploy MinIO on VPS (console `:9001`, API `:9000`). diff --git a/apps/admin/src/components/EditorShell.tsx b/apps/admin/src/components/EditorShell.tsx index b119594..074101b 100644 --- a/apps/admin/src/components/EditorShell.tsx +++ b/apps/admin/src/components/EditorShell.tsx @@ -1,18 +1,40 @@ -import { Box, Typography } from '@mui/material'; +import { Box, Button, Stack, TextField, Typography } from '@mui/material'; import AdminLayout from '../layout/AdminLayout'; import Recorder from '../features/recorder/Recorder'; +import { useEffect, useState } from 'react'; export default function EditorShell({ onLogout }: { onLogout?: () => void }) { + const [draft, setDraft] = useState(''); + + useEffect(() => { + const saved = localStorage.getItem('voxblog_draft'); + if (saved) setDraft(saved); + }, []); + + const saveDraft = () => { + localStorage.setItem('voxblog_draft', draft); + }; + return ( Welcome to VoxBlog Editor - - - Coming next: rich editor, AI tools, and Ghost publish flow. - + setDraft(t)} /> + + Draft + setDraft(e.target.value)} + /> + + + + ); diff --git a/apps/admin/src/features/recorder/Recorder.tsx b/apps/admin/src/features/recorder/Recorder.tsx index 10090b6..8613486 100644 --- a/apps/admin/src/features/recorder/Recorder.tsx +++ b/apps/admin/src/features/recorder/Recorder.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { Box, Button, Stack, Typography } from '@mui/material'; -export default function Recorder() { +export default function Recorder({ onTranscript }: { onTranscript?: (t: string) => void }) { const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); const [recording, setRecording] = useState(false); @@ -103,7 +103,9 @@ export default function Recorder() { throw new Error(`STT failed: ${res.status} ${txt}`); } const data = await res.json(); - setTranscript(data.transcript || ''); + const t: string = data.transcript || ''; + setTranscript(t); + if (onTranscript) onTranscript(t); } catch (e: any) { setError(e?.message || 'Transcription failed'); } diff --git a/apps/api/package.json b/apps/api/package.json index cb729a5..837e7ab 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -32,6 +32,7 @@ "api" ], "dependencies": { + "@aws-sdk/client-s3": "^3.916.0", "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", @@ -51,6 +52,7 @@ "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", + "multer": "^2.0.2", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", @@ -62,11 +64,13 @@ "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", + "undici": "^7.16.0", "vary": "^1.1.2" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.3", + "@types/multer": "^2.0.0", "@types/node": "^24.6.0", "after": "0.8.2", "connect-redis": "^8.0.1",