This commit is contained in:
Linrador 2026-05-29 08:43:13 +02:00
parent fd0bab67fb
commit fa7df129f4
7 changed files with 120 additions and 179 deletions

View File

@ -1,2 +1,2 @@
HTTPS_ENABLED=0 HTTPS_ENABLED=1
AUTH_RP_ORIGINS=https://l14pbbk95100006.tegdssd.de:9999,https://l14pbbk95100006.tegdssd.de:5173,https://localhost:9999,https://127.0.0.1:9999,https://10.0.1.25:9999,http://localhost:5173,http://127.0.0.1:5173,http://10.0.1.25:5173 AUTH_RP_ORIGINS=https://l14pbbk95100006.tegdssd.de:9999,https://l14pbbk95100006.tegdssd.de:5173,https://localhost:9999,https://127.0.0.1:9999,https://10.0.1.25:9999,http://localhost:5173,http://127.0.0.1:5173,http://10.0.1.25:5173

View File

@ -8,7 +8,7 @@ import Tabs, { type TabItem } from './components/ui/Tabs'
import RecorderSettings from './components/ui/RecorderSettings' import RecorderSettings from './components/ui/RecorderSettings'
import FinishedDownloads from './components/ui/FinishedDownloads' import FinishedDownloads from './components/ui/FinishedDownloads'
import Player from './components/ui/Player' import Player from './components/ui/Player'
import type { RecordJob } from './types' import type { RecordJob, JobEvent, RecorderSettingsState, TaskStateEvent, AnalysisProgressEvent, BackgroundSplitProgress } from './types'
import Downloads from './components/ui/Downloads' import Downloads from './components/ui/Downloads'
import ModelsTab from './components/ui/ModelsTab' import ModelsTab from './components/ui/ModelsTab'
import ProgressBar from './components/ui/ProgressBar' import ProgressBar from './components/ui/ProgressBar'
@ -57,118 +57,6 @@ async function apiJSON<T>(url: string, init?: RequestInit): Promise<T> {
return res.json() as Promise<T> return res.json() as Promise<T>
} }
type RecorderSettingsState = {
recordDir: string
doneDir: string
ffmpegPath?: string
autoAddToDownloadList?: boolean
autoStartAddedDownloads?: boolean
enableConcurrentDownloadsLimit?: boolean
maxConcurrentDownloads?: number
useChaturbateApi?: boolean
useMyFreeCamsWatcher?: boolean
autoDeleteSmallDownloads?: boolean
autoDeleteSmallDownloadsBelowMB?: number
blurPreviews?: boolean
teaserPlayback?: 'still' | 'hover' | 'all'
teaserAudio?: boolean
lowDiskPauseBelowGB?: number
enableNotifications?: boolean
}
type JobEvent = {
type?: 'job_upsert' | 'job_remove'
model?: string
jobId?: string
status?: string
phase?: string
progress?: number
sourceUrl?: string
output?: string
startedAt?: string
startedAtMs?: number
endedAt?: string
endedAtMs?: number
sizeBytes?: number
durationSeconds?: number
previewState?: string
roomStatus?: string
isOnline?: boolean
modelImageUrl?: string
modelChatRoomUrl?: string
ts?: number
error?: string
postWorkKey?: string
postWork?: {
state?: string
position?: number
waiting?: number
running?: number
maxParallel?: number
}
// ✅ neu für finished_postwork
file?: string
assetId?: string
queue?: 'postwork' | 'enrich'
state?: 'queued' | 'running' | 'done' | 'error' | 'missing'
label?: string
position?: number
waiting?: number
running?: number
maxParallel?: number
}
type TaskStateEvent = {
type?: 'task_state'
generateAssetsRunning?: boolean
regenerateAssetsRunning?: boolean
checkVideosRunning?: boolean
cleanupRunning?: boolean
anyRunning?: boolean
ts?: number
}
type AnalysisProgressEvent = {
type?: 'analysis_progress'
scope?: string
requestId?: string
analysisRequestId?: string
running?: boolean
phase?: 'starting' | 'running' | 'done' | 'error' | string
progress?: number
current?: number
total?: number
file?: string
sourceFile?: string
message?: string
error?: string
startedAtMs?: number
finishedAtMs?: number
durationMs?: number
ts?: number
}
type BackgroundSplitProgress = {
phase: 'preparing' | 'splitting' | 'done' | 'error'
total: number
current: number
completed: number
segmentProgress?: number
overallProgress?: number
message?: string
segment?: {
index: number
label: string
start: number
end: number
duration: number
}
startedAtMs: number
sourceFile: string
}
const DEFAULT_RECORDER_SETTINGS: RecorderSettingsState = { const DEFAULT_RECORDER_SETTINGS: RecorderSettingsState = {
recordDir: 'records', recordDir: 'records',
doneDir: 'records/done', doneDir: 'records/done',

View File

@ -2274,15 +2274,18 @@ export default function FinishedDownloads({
showOnlyCorrupt showOnlyCorrupt
const globalFilterActive = searchActiveForGlobalFetch const globalFilterActive = searchActiveForGlobalFetch
const effectiveAllMode = globalFilterActive || loadMode === 'all' const mobileCardStackMode = isSmall && view === 'cards'
const shouldLoadAllDoneJobs =
globalFilterActive || loadMode === 'all' || mobileCardStackMode
const effectiveAllMode = shouldLoadAllDoneJobs
const DEFAULT_GALLERY_PAGE_SIZE = 8 const DEFAULT_GALLERY_PAGE_SIZE = 8
const [galleryPageSize, setGalleryPageSize] = useState<number>( const [galleryPageSize, setGalleryPageSize] = useState<number>(
DEFAULT_GALLERY_PAGE_SIZE DEFAULT_GALLERY_PAGE_SIZE
) )
const lastGalleryScaleIndexRef = useRef(galleryCardScaleIndex)
const handleGalleryRecommendedPageSizeChange = useCallback((rawSize: number) => { const handleGalleryRecommendedPageSizeChange = useCallback((rawSize: number) => {
const rounded = Math.round(Number(rawSize)) const rounded = Math.round(Number(rawSize))
if (!Number.isFinite(rounded) || rounded <= 0) return if (!Number.isFinite(rounded) || rounded <= 0) return
@ -2290,23 +2293,16 @@ export default function FinishedDownloads({
const next = Math.max(1, Math.min(24, rounded)) const next = Math.max(1, Math.min(24, rounded))
setGalleryPageSize((current) => { setGalleryPageSize((current) => {
const scaleChanged = lastGalleryScaleIndexRef.current !== galleryCardScaleIndex if (current === next) return current
if (scaleChanged) {
lastGalleryScaleIndexRef.current = galleryCardScaleIndex
return next
}
// Wichtig:
// Nach Page-Wechsel/ResizeObserver kann kurz 8 statt 10 gemeldet werden.
// Nicht automatisch verkleinern, sonst verschwinden bereits geladene Cards.
if (next < current) {
return current
}
return next return next
}) })
}, [galleryCardScaleIndex])
onPageSizeChange?.(next)
if (page !== 1) {
onPageChange(1)
}
}, [onPageSizeChange, onPageChange, page])
const effectivePageSize = const effectivePageSize =
view === 'gallery' view === 'gallery'
@ -2314,12 +2310,12 @@ export default function FinishedDownloads({
: pageSize : pageSize
const doneJobsPage = const doneJobsPage =
globalFilterActive shouldLoadAllDoneJobs
? (allDoneJobs ?? doneJobs) ? (allDoneJobs ?? doneJobs)
: doneJobs : doneJobs
const doneTotalPage = const doneTotalPage =
globalFilterActive shouldLoadAllDoneJobs
? (allDoneJobs?.length ?? doneTotal) ? (allDoneJobs?.length ?? doneTotal)
: doneTotal : doneTotal
@ -2578,12 +2574,12 @@ export default function FinishedDownloads({
return visibleRows.slice(start, end) return visibleRows.slice(start, end)
} }
if (view === 'gallery') {
return visibleRows.slice(0, effectivePageSize)
}
return visibleRows return visibleRows
}, [visibleRows, page, effectivePageSize, effectiveAllMode, view]) }, [visibleRows, page, effectivePageSize, effectiveAllMode])
const cardRows = mobileCardStackMode ? visibleRows : pageRows
const activeVisibleRows = pageRows
const emptyFolder = const emptyFolder =
!finishedDownloadsLoading && !finishedDownloadsLoading &&
@ -2605,10 +2601,10 @@ export default function FinishedDownloads({
const allPageSelected = useMemo(() => { const allPageSelected = useMemo(() => {
return ( return (
pageRows.length > 0 && activeVisibleRows.length > 0 &&
pageRows.every((j) => selectionStore.isSelected(keyFor(j))) activeVisibleRows.every((j) => selectionStore.isSelected(keyFor(j)))
) )
}, [pageRows, keyFor, selectionStore, selectionVersion]) }, [activeVisibleRows, keyFor, selectionStore, selectionVersion])
const hasSearchQuery = (searchQuery || '').trim() !== '' const hasSearchQuery = (searchQuery || '').trim() !== ''
const selectionActionsVisible = selectedCount > 0 && selectionActionsOpen const selectionActionsVisible = selectedCount > 0 && selectionActionsOpen
@ -2617,7 +2613,7 @@ export default function FinishedDownloads({
const selectedKeys = useMemo(() => { const selectedKeys = useMemo(() => {
const next = new Set<string>() const next = new Set<string>()
for (const j of pageRows) { for (const j of activeVisibleRows) {
const k = keyFor(j) const k = keyFor(j)
if (selectionStore.isSelected(k)) { if (selectionStore.isSelected(k)) {
next.add(k) next.add(k)
@ -2625,7 +2621,7 @@ export default function FinishedDownloads({
} }
return next return next
}, [pageRows, keyFor, selectionStore, selectionVersion]) }, [activeVisibleRows, keyFor, selectionStore, selectionVersion])
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// callbacks // callbacks
@ -3801,7 +3797,7 @@ export default function FinishedDownloads({
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
useEffect(() => { useEffect(() => {
if (!globalFilterActive) { if (!shouldLoadAllDoneJobs) {
setAllDoneJobs(null) setAllDoneJobs(null)
setAllDoneJobsLoading(false) setAllDoneJobsLoading(false)
return return
@ -3832,7 +3828,7 @@ export default function FinishedDownloads({
return () => { return () => {
cancelled = true cancelled = true
} }
}, [globalFilterActive, loadAllDoneJobs]) }, [shouldLoadAllDoneJobs, loadAllDoneJobs])
useEffect(() => { useEffect(() => {
if (!showOnlyCorrupt) return if (!showOnlyCorrupt) return
@ -5421,7 +5417,7 @@ export default function FinishedDownloads({
{view === 'cards' && ( {view === 'cards' && (
<div className={isSmall ? ['relative z-0 pt-10', cardsMobileOffsetTopClass, cardsMobileOffsetBottomClass].filter(Boolean).join(' ') : ''}> <div className={isSmall ? ['relative z-0 pt-10', cardsMobileOffsetTopClass, cardsMobileOffsetBottomClass].filter(Boolean).join(' ') : ''}>
<FinishedDownloadsCardsView <FinishedDownloadsCardsView
rows={pageRows} rows={cardRows}
selectionStore={selectionStore} selectionStore={selectionStore}
onToggleSelected={toggleSelected} onToggleSelected={toggleSelected}
onToggleSelectAllPage={handleToggleSelectAllPage} onToggleSelectAllPage={handleToggleSelectAllPage}
@ -5596,6 +5592,7 @@ export default function FinishedDownloads({
</main> </main>
</div> </div>
{!mobileCardStackMode ? (
<div className="fixed inset-x-0 bottom-4 z-[70] flex justify-center px-4 pointer-events-none"> <div className="fixed inset-x-0 bottom-4 z-[70] flex justify-center px-4 pointer-events-none">
<div className="pointer-events-auto flex w-full max-w-fit flex-col items-center gap-3"> <div className="pointer-events-auto flex w-full max-w-fit flex-col items-center gap-3">
<div className="shadow-2xl"> <div className="shadow-2xl">
@ -5620,6 +5617,7 @@ export default function FinishedDownloads({
</div> </div>
</div> </div>
</div> </div>
) : null}
</> </>
) )
} }

View File

@ -1010,11 +1010,13 @@ function FinishedDownloadsCardsView({
}, [isSmall, mobileOrderedRows, keyFor]) }, [isSmall, mobileOrderedRows, keyFor])
const mobileStackDepth = 3 const mobileStackDepth = 3
const mobilePreloadDepth = mobileStackDepth + 3
const mobileLoadedStackRows = isSmall ? mobileOrderedRows.slice(0, mobilePreloadDepth) : [] const mobileLoadedStackRows = isSmall
const mobileVisibleStackRows = mobileLoadedStackRows.slice(0, mobileStackDepth) ? mobileOrderedRows.slice(0, mobileStackDepth)
const mobilePreloadedRow = mobileLoadedStackRows[mobileStackDepth] ?? null : []
const mobileVisibleStackRows = mobileLoadedStackRows
const mobilePreloadedRow = null
const mobileTopRow = mobileVisibleStackRows[0] ?? null const mobileTopRow = mobileVisibleStackRows[0] ?? null
const mobileTopKey = isSmall && mobileVisibleStackRows[0] const mobileTopKey = isSmall && mobileVisibleStackRows[0]

View File

@ -193,8 +193,7 @@ function FinishedDownloadsGalleryView({
Math.floor((containerWidth + gapPx) / (minCardWidth + gapPx)) Math.floor((containerWidth + gapPx) / (minCardWidth + gapPx))
) )
// mobil etwas mehr vertikale Füllung, desktop wie bisher ca. 2 Reihen const rowsPerPage = 2
const rowsPerPage = containerWidth < 640 ? 3 : 2
const nextPageSize = clamp(columns * rowsPerPage, rowsPerPage, 24) const nextPageSize = clamp(columns * rowsPerPage, rowsPerPage, 24)

View File

@ -809,7 +809,7 @@ function TrainingStageOverlay(props: {
aria-hidden="true" aria-hidden="true"
draggable={false} draggable={false}
className={[ className={[
'absolute inset-0 z-0 h-full w-full scale-105 object-cover blur-[1px]', 'absolute inset-0 z-0 h-full w-full object-contain blur-[1px]',
'transition-opacity duration-500 ease-out will-change-opacity motion-reduce:transition-none', 'transition-opacity duration-500 ease-out will-change-opacity motion-reduce:transition-none',
backgroundVisible ? 'opacity-80' : 'opacity-0', backgroundVisible ? 'opacity-80' : 'opacity-0',
].join(' ')} ].join(' ')}
@ -4955,7 +4955,7 @@ export default function TrainingTab(props: {
<div className="min-w-0 lg:flex lg:h-full lg:min-h-0 lg:flex-col lg:overflow-hidden"> <div className="min-w-0 lg:flex lg:h-full lg:min-h-0 lg:flex-col lg:overflow-hidden">
<section <section
className={[ className={[
'min-w-0 rounded-xl border border-gray-200 bg-white p-2 shadow-sm dark:border-white/10 dark:bg-gray-900/60 sm:p-3', 'min-w-0 w-full rounded-xl border border-gray-200 bg-white p-2 shadow-sm dark:border-white/10 dark:bg-gray-900/60 sm:p-3',
imageExpanded imageExpanded
? 'lg:grid lg:h-full lg:min-h-0 lg:grid-rows-[minmax(0,1fr)_auto] lg:gap-3 lg:overflow-hidden' ? 'lg:grid lg:h-full lg:min-h-0 lg:grid-rows-[minmax(0,1fr)_auto] lg:gap-3 lg:overflow-hidden'
: 'lg:self-start' : 'lg:self-start'
@ -5666,6 +5666,7 @@ export default function TrainingTab(props: {
})} })}
</div> </div>
{ /* Rechte Seite */ }
<div <div
ref={mobileLabelsScrollRef} ref={mobileLabelsScrollRef}
className="min-h-0 h-full flex-1 overflow-y-auto overscroll-contain p-3 scroll-smooth" className="min-h-0 h-full flex-1 overflow-y-auto overscroll-contain p-3 scroll-smooth"

View File

@ -251,6 +251,8 @@ export type RecorderSettingsState = {
ffmpegPath?: string ffmpegPath?: string
autoAddToDownloadList?: boolean autoAddToDownloadList?: boolean
autoStartAddedDownloads?: boolean autoStartAddedDownloads?: boolean
enableConcurrentDownloadsLimit?: boolean
maxConcurrentDownloads?: number
useChaturbateApi?: boolean useChaturbateApi?: boolean
useMyFreeCamsWatcher?: boolean useMyFreeCamsWatcher?: boolean
autoDeleteSmallDownloads?: boolean autoDeleteSmallDownloads?: boolean
@ -283,6 +285,7 @@ export type JobEvent = {
modelImageUrl?: string modelImageUrl?: string
modelChatRoomUrl?: string modelChatRoomUrl?: string
ts?: number ts?: number
error?: string
postWorkKey?: string postWorkKey?: string
postWork?: { postWork?: {
@ -293,10 +296,11 @@ export type JobEvent = {
maxParallel?: number maxParallel?: number
} }
// ✅ neu für finished_postwork
file?: string file?: string
assetId?: string assetId?: string
queue?: FinishedPostworkQueue queue?: 'postwork' | 'enrich'
state?: FinishedPostworkState state?: 'queued' | 'running' | 'done' | 'error' | 'missing'
label?: string label?: string
position?: number position?: number
waiting?: number waiting?: number
@ -304,6 +308,55 @@ export type JobEvent = {
maxParallel?: number maxParallel?: number
} }
export type TaskStateEvent = {
type?: 'task_state'
generateAssetsRunning?: boolean
regenerateAssetsRunning?: boolean
checkVideosRunning?: boolean
cleanupRunning?: boolean
anyRunning?: boolean
ts?: number
}
export type AnalysisProgressEvent = {
type?: 'analysis_progress'
scope?: string
requestId?: string
analysisRequestId?: string
running?: boolean
phase?: 'starting' | 'running' | 'done' | 'error' | string
progress?: number
current?: number
total?: number
file?: string
sourceFile?: string
message?: string
error?: string
startedAtMs?: number
finishedAtMs?: number
durationMs?: number
ts?: number
}
export type BackgroundSplitProgress = {
phase: 'preparing' | 'splitting' | 'done' | 'error'
total: number
current: number
completed: number
segmentProgress?: number
overallProgress?: number
message?: string
segment?: {
index: number
label: string
start: number
end: number
duration: number
}
startedAtMs: number
sourceFile: string
}
export type StoredModel = { export type StoredModel = {
id: string id: string
input: string input: string