Initial commit: InkFlow — EPUB vers livre audio local (MLX/Kokoro)
This commit is contained in:
99
frontend/src/BookView.jsx
Normal file
99
frontend/src/BookView.jsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { api, subscribeState } from "./api.js";
|
||||
import { StatusChip, ProgressBar, Spinner } from "./ui.jsx";
|
||||
import Chapters from "./Chapters.jsx";
|
||||
import AnalysisEditor from "./AnalysisEditor.jsx";
|
||||
import CastEditor from "./CastEditor.jsx";
|
||||
import PronunciationEditor from "./PronunciationEditor.jsx";
|
||||
|
||||
const STAGES = [
|
||||
{ key: "analyze", label: "Analyse", action: (s) => api.analyze(s), hint: "Découpe le texte, détecte les locuteurs et le casting." },
|
||||
{ key: "cast", label: "Casting", action: (s) => api.castAuto(s), hint: "Attribue une voix à chaque personnage." },
|
||||
{ key: "pronounce", label: "Prononciations", action: (s) => api.pronounce(s), hint: "Repère les mots à risque de mauvaise prononciation." },
|
||||
];
|
||||
|
||||
export default function BookView({ slug, onBack }) {
|
||||
const [data, setData] = useState(null);
|
||||
const [state, setState] = useState(null);
|
||||
const [tab, setTab] = useState("chapters");
|
||||
|
||||
useEffect(() => {
|
||||
api.getBook(slug).then((d) => { setData(d); setState(d.state); });
|
||||
const unsub = subscribeState(slug, setState);
|
||||
return unsub;
|
||||
}, [slug]);
|
||||
|
||||
if (!data) return <p className="text-ink-muted"><Spinner /> chargement…</p>;
|
||||
const { book } = data;
|
||||
const st = state || data.state;
|
||||
const busy = !!st.active_stage;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<button onClick={onBack} className="text-sm text-ink-muted hover:text-ink-text">← Bibliothèque</button>
|
||||
|
||||
<div className="flex gap-5">
|
||||
{book.cover_file && (
|
||||
<img src={api.coverUrl(slug)} alt="" className="h-44 rounded-md border border-ink-edge object-cover" />
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<h1 className="font-serif text-2xl">{book.title}</h1>
|
||||
<p className="text-ink-muted">{book.author}</p>
|
||||
<p className="mt-1 text-sm text-ink-muted">{book.chapters.filter((c) => c.render).length} chapitres à narrer</p>
|
||||
|
||||
{busy && (
|
||||
<div className="mt-4 max-w-md space-y-1">
|
||||
<div className="flex justify-between text-xs text-ink-accent">
|
||||
<span>{st.active_detail || st.active_stage}</span>
|
||||
<span>{Math.round((st.active_progress || 0) * 100)}%</span>
|
||||
</div>
|
||||
<ProgressBar value={st.active_progress} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pipeline */}
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
{STAGES.map((stage) => {
|
||||
const status = st.stages?.[stage.key] || "pending";
|
||||
return (
|
||||
<div key={stage.key} className="card p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">{stage.label}</span>
|
||||
<StatusChip status={status} />
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-ink-muted">{stage.hint}</p>
|
||||
<button className="btn-ghost mt-3" disabled={busy}
|
||||
onClick={() => stage.action(slug)}>
|
||||
{status === "done" ? "Relancer" : "Lancer"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Onglets */}
|
||||
<div className="flex gap-1 border-b border-ink-edge">
|
||||
{[
|
||||
["chapters", "Chapitres"],
|
||||
["analysis", "Analyse"],
|
||||
["cast", "Casting"],
|
||||
["pron", "Prononciation"],
|
||||
].map(([key, label]) => (
|
||||
<button key={key} onClick={() => setTab(key)}
|
||||
className={`px-4 py-2 text-sm ${tab === key
|
||||
? "border-b-2 border-ink-accent text-ink-text"
|
||||
: "text-ink-muted hover:text-ink-text"}`}>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{tab === "chapters" && <Chapters slug={slug} book={book} state={st} busy={busy} />}
|
||||
{tab === "analysis" && <AnalysisEditor slug={slug} book={book} state={st} />}
|
||||
{tab === "cast" && <CastEditor slug={slug} busy={busy} />}
|
||||
{tab === "pron" && <PronunciationEditor slug={slug} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user