fix: SSE bez nazwanych zdarzen (postep stal na 0); usun tryb testowy; opcja napisow (osadz/.srt)
All checks were successful
deploy / deploy (push) Successful in 28s

- jobs.controller: usun pole type z MessageEvent — onmessage w EventSource
  reaguje tylko na domyslne 'message', przez co front nie dostawal zdarzen
- usun trial (health, compose, deploy.yml, UI, zmienne Gitea)
- napisy: wybor osadzania w wideo lub osobnego pliku .srt (--convert-subs srt)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 11:22:12 +02:00
parent a16bb14298
commit 94278f119d
11 changed files with 46 additions and 48 deletions

View File

@@ -79,10 +79,10 @@ export default function App() {
const [mode, setMode] = useState<Mode>('mp3');
const [format, setFormat] = useState('320');
const [subtitles, setSubtitles] = useState(false);
const [subsMode, setSubsMode] = useState<'embed' | 'file'>('embed');
const [busy, setBusy] = useState(false);
const [progress, setProgress] = useState(0);
const [log, setLog] = useState<string[]>(['OK: wszystko gotowe do pobierania.']);
const [trial, setTrial] = useState<{ daysLeft: number | null; expires: string | null } | null>(null);
const esRef = useRef<EventSource | null>(null);
const jobRef = useRef<string | null>(null);
const logEndRef = useRef<HTMLDivElement | null>(null);
@@ -94,15 +94,6 @@ export default function App() {
setFormat(MODES.find((m) => m.key === mode)!.formats[0].value);
}, [mode]);
useEffect(() => {
fetch('/api/health')
.then((r) => r.json())
.then((h) => {
if (h?.trial?.enabled) setTrial({ daysLeft: h.trial.daysLeft, expires: h.trial.expires });
})
.catch(() => {});
}, []);
useEffect(() => {
logEndRef.current?.scrollIntoView({ block: 'end' });
}, [log]);
@@ -139,7 +130,7 @@ export default function App() {
const res = await fetch('/api/jobs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url.trim(), mode, format, subtitles }),
body: JSON.stringify({ url: url.trim(), mode, format, subtitles, subsMode }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { id } = await res.json();
@@ -262,21 +253,34 @@ export default function App() {
</select>
</div>
<div className="col">
<label className="field-label">Zapisz do:</label>
<label className="field-label">Napisy:</label>
<div className="save-to">
<span>Pobranie przez przeglądarkę (folder Pobrane)</span>
<label className="subs">
<input
type="checkbox"
checked={subtitles}
checked={subtitles && mode !== 'mp3'}
onChange={(e) => setSubtitles(e.target.checked)}
disabled={mode === 'mp3'}
/>
Napisy
Pobierz napisy
</label>
<select
className="select subs-select"
value={subsMode}
onChange={(e) => setSubsMode(e.target.value as 'embed' | 'file')}
disabled={!subtitles || mode === 'mp3'}
>
<option value="embed">Osadź w pliku wideo</option>
<option value="file">Osobny plik (.srt)</option>
</select>
</div>
</div>
</div>
<p className="hint">
Napisy dotyczą tylko faktycznie dostępnych napisów nie obejmują
automatycznie generowanych napisów YouTube. Gotowy plik pobiera się przez
przeglądarkę (folder Pobrane).
</p>
<div className="actions">
<button
@@ -298,12 +302,6 @@ export default function App() {
</div>
)}
{trial && (
<div className="trial">
Tryb testowy: zostało {trial.daysLeft} dni. Wygasa: {trial.expires}.
</div>
)}
<div className="logbox">
<div className="logbox-title">Log</div>
<div className="logbox-body">

View File

@@ -89,10 +89,13 @@ body {
.select:focus { outline: none; border-color: var(--blue-bright); }
.save-to {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
padding: 10px 14px; color: var(--muted); font-size: 13px;
display: flex; align-items: center; justify-content: space-between; gap: 10px;
padding: 8px 12px; color: var(--muted); font-size: 13px;
display: flex; align-items: center; gap: 12px;
}
.subs { display: flex; align-items: center; gap: 6px; color: var(--text); font-size: 13px; cursor: pointer; }
.subs { display: flex; align-items: center; gap: 6px; color: var(--text); font-size: 13px; cursor: pointer; white-space: nowrap; }
.subs-select { flex: 1; padding: 9px 10px; font-size: 13px; }
.subs-select:disabled { opacity: .5; }
.hint { color: var(--muted); font-size: 12px; margin: 8px 2px 0; line-height: 1.5; }
.actions { display: flex; gap: 12px; margin-top: 18px; }
.download-btn {
@@ -109,11 +112,6 @@ body {
.progress { margin-top: 12px; height: 6px; background: var(--panel); border-radius: 4px; overflow: hidden; }
.progress-bar { height: 100%; background: var(--blue-bright); transition: width .2s; }
.trial {
margin-top: 14px; background: var(--panel); border: 1px solid var(--border);
border-radius: 10px; padding: 12px 14px; color: var(--muted); font-size: 13px;
}
.logbox { margin-top: 16px; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
.logbox-title { font-weight: 700; margin-bottom: 8px; }
.logbox-body {