|
@@ -55,7 +55,7 @@ export default function CreatePage() {
|
|
|
const [inputMode, setInputMode] = useState<InputMode>("manual");
|
|
const [inputMode, setInputMode] = useState<InputMode>("manual");
|
|
|
const [text, setText] = useState("");
|
|
const [text, setText] = useState("");
|
|
|
const [template, setTemplate] = useState("news");
|
|
const [template, setTemplate] = useState("news");
|
|
|
- const [platform, setPlatform] = useState("bilibili");
|
|
|
|
|
|
|
+ const [platforms, setPlatforms] = useState<string[]>(["bilibili"]);
|
|
|
const [ttsProvider, setTtsProvider] = useState("openai-tts");
|
|
const [ttsProvider, setTtsProvider] = useState("openai-tts");
|
|
|
const [voiceId, setVoiceId] = useState("");
|
|
const [voiceId, setVoiceId] = useState("");
|
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
@@ -118,15 +118,17 @@ export default function CreatePage() {
|
|
|
setError(null);
|
|
setError(null);
|
|
|
try {
|
|
try {
|
|
|
const payload: any = {
|
|
const payload: any = {
|
|
|
- input: { template, platform, ttsProvider, voiceId },
|
|
|
|
|
|
|
+ input: { template, platforms, ttsProvider, voiceId },
|
|
|
};
|
|
};
|
|
|
- if (inputMode === "source" && selectedSource) {
|
|
|
|
|
|
|
+ // Prefer already-collected (and possibly edited) text. Only fall back to
|
|
|
|
|
+ // server-side collection if the textarea is still empty.
|
|
|
|
|
+ if (text.trim()) {
|
|
|
|
|
+ payload.input.text = text;
|
|
|
|
|
+ } else if (inputMode === "source" && selectedSource) {
|
|
|
payload.input.source = selectedSource;
|
|
payload.input.source = selectedSource;
|
|
|
if (Object.keys(sourceArgs).length > 0) {
|
|
if (Object.keys(sourceArgs).length > 0) {
|
|
|
payload.input.sourceArgs = sourceArgs;
|
|
payload.input.sourceArgs = sourceArgs;
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- payload.input.text = text;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const res = await fetch("/api/render", {
|
|
const res = await fetch("/api/render", {
|
|
@@ -150,6 +152,12 @@ export default function CreatePage() {
|
|
|
const canProceed =
|
|
const canProceed =
|
|
|
inputMode === "manual" ? text.trim().length > 0 : selectedSource.length > 0;
|
|
inputMode === "manual" ? text.trim().length > 0 : selectedSource.length > 0;
|
|
|
|
|
|
|
|
|
|
+ const togglePlatform = (p: string) => {
|
|
|
|
|
+ setPlatforms((prev) =>
|
|
|
|
|
+ prev.includes(p) ? prev.filter((x) => x !== p) : [...prev, p]
|
|
|
|
|
+ );
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// Source-specific arg fields
|
|
// Source-specific arg fields
|
|
|
const currentSource = sources.find((s) => s.name === selectedSource);
|
|
const currentSource = sources.find((s) => s.name === selectedSource);
|
|
|
const needsOwnerRepo = currentSource?.name === "github-repo";
|
|
const needsOwnerRepo = currentSource?.name === "github-repo";
|
|
@@ -337,23 +345,34 @@ export default function CreatePage() {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="form-group">
|
|
<div className="form-group">
|
|
|
- <label>Platform</label>
|
|
|
|
|
- <select
|
|
|
|
|
- className="form-select"
|
|
|
|
|
- value={platform}
|
|
|
|
|
- onChange={(e) => setPlatform(e.target.value)}
|
|
|
|
|
- >
|
|
|
|
|
- {PLATFORM_PRESET_KEYS.map((p) => (
|
|
|
|
|
- <option key={p} value={p}>{PLATFORMS_INFO[p]}</option>
|
|
|
|
|
- ))}
|
|
|
|
|
- </select>
|
|
|
|
|
|
|
+ <label>Platforms (select one or more)</label>
|
|
|
|
|
+ <div className="card-grid">
|
|
|
|
|
+ {PLATFORM_PRESET_KEYS.map((p) => {
|
|
|
|
|
+ const selected = platforms.includes(p);
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div
|
|
|
|
|
+ key={p}
|
|
|
|
|
+ className={`card card-hover template-card ${selected ? "selected" : ""}`}
|
|
|
|
|
+ style={selected ? { borderColor: "var(--primary)", boxShadow: "0 0 0 1px var(--primary)" } : {}}
|
|
|
|
|
+ onClick={() => togglePlatform(p)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <h3>{p}</h3>
|
|
|
|
|
+ <p>{PLATFORMS_INFO[p]}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ display: "flex", gap: 12 }}>
|
|
<div style={{ display: "flex", gap: 12 }}>
|
|
|
<button className="btn btn-secondary" onClick={() => setStep("input")}>
|
|
<button className="btn btn-secondary" onClick={() => setStep("input")}>
|
|
|
Back
|
|
Back
|
|
|
</button>
|
|
</button>
|
|
|
- <button className="btn btn-primary" onClick={() => setStep("voice")}>
|
|
|
|
|
|
|
+ <button
|
|
|
|
|
+ className="btn btn-primary"
|
|
|
|
|
+ disabled={platforms.length === 0}
|
|
|
|
|
+ onClick={() => setStep("voice")}
|
|
|
|
|
+ >
|
|
|
Next
|
|
Next
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
@@ -404,8 +423,8 @@ export default function CreatePage() {
|
|
|
<div style={{ fontWeight: 600 }}>{TEMPLATES_INFO[template]?.label} ({template})</div>
|
|
<div style={{ fontWeight: 600 }}>{TEMPLATES_INFO[template]?.label} ({template})</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="card">
|
|
<div className="card">
|
|
|
- <div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 4 }}>Platform</div>
|
|
|
|
|
- <div style={{ fontWeight: 600 }}>{platform}</div>
|
|
|
|
|
|
|
+ <div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 4 }}>Platforms</div>
|
|
|
|
|
+ <div style={{ fontWeight: 600 }}>{platforms.join(", ")}</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="card">
|
|
<div className="card">
|
|
|
<div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 4 }}>TTS Provider</div>
|
|
<div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 4 }}>TTS Provider</div>
|
|
@@ -414,9 +433,11 @@ export default function CreatePage() {
|
|
|
<div className="card">
|
|
<div className="card">
|
|
|
<div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 4 }}>Input</div>
|
|
<div style={{ fontSize: 12, color: "var(--text-muted)", marginBottom: 4 }}>Input</div>
|
|
|
<div style={{ fontWeight: 600 }}>
|
|
<div style={{ fontWeight: 600 }}>
|
|
|
- {inputMode === "source"
|
|
|
|
|
- ? `Source: ${selectedSource}`
|
|
|
|
|
- : `${text.length} chars`}
|
|
|
|
|
|
|
+ {text.trim()
|
|
|
|
|
+ ? `${text.length} chars`
|
|
|
|
|
+ : inputMode === "source"
|
|
|
|
|
+ ? `Source: ${selectedSource} (will collect at render time)`
|
|
|
|
|
+ : "empty"}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|