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

View File

@ -8,7 +8,7 @@ import Tabs, { type TabItem } from './components/ui/Tabs'
import RecorderSettings from './components/ui/RecorderSettings'
import FinishedDownloads from './components/ui/FinishedDownloads'
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 ModelsTab from './components/ui/ModelsTab'
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>
}
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 = {
recordDir: 'records',
doneDir: 'records/done',

View File

@ -2274,15 +2274,18 @@ export default function FinishedDownloads({
showOnlyCorrupt
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 [galleryPageSize, setGalleryPageSize] = useState<number>(
DEFAULT_GALLERY_PAGE_SIZE
)
const lastGalleryScaleIndexRef = useRef(galleryCardScaleIndex)
const handleGalleryRecommendedPageSizeChange = useCallback((rawSize: number) => {
const rounded = Math.round(Number(rawSize))
if (!Number.isFinite(rounded) || rounded <= 0) return
@ -2290,23 +2293,16 @@ export default function FinishedDownloads({
const next = Math.max(1, Math.min(24, rounded))
setGalleryPageSize((current) => {
const scaleChanged = lastGalleryScaleIndexRef.current !== galleryCardScaleIndex
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
}
if (current === next) return current
return next
})
}, [galleryCardScaleIndex])
onPageSizeChange?.(next)
if (page !== 1) {
onPageChange(1)
}
}, [onPageSizeChange, onPageChange, page])
const effectivePageSize =
view === 'gallery'
@ -2314,12 +2310,12 @@ export default function FinishedDownloads({
: pageSize
const doneJobsPage =
globalFilterActive
shouldLoadAllDoneJobs
? (allDoneJobs ?? doneJobs)
: doneJobs
const doneTotalPage =
globalFilterActive
shouldLoadAllDoneJobs
? (allDoneJobs?.length ?? doneTotal)
: doneTotal
@ -2578,12 +2574,12 @@ export default function FinishedDownloads({
return visibleRows.slice(start, end)
}
if (view === 'gallery') {
return visibleRows.slice(0, effectivePageSize)
}
return visibleRows
}, [visibleRows, page, effectivePageSize, effectiveAllMode, view])
}, [visibleRows, page, effectivePageSize, effectiveAllMode])
const cardRows = mobileCardStackMode ? visibleRows : pageRows
const activeVisibleRows = pageRows
const emptyFolder =
!finishedDownloadsLoading &&
@ -2605,10 +2601,10 @@ export default function FinishedDownloads({
const allPageSelected = useMemo(() => {
return (
pageRows.length > 0 &&
pageRows.every((j) => selectionStore.isSelected(keyFor(j)))
activeVisibleRows.length > 0 &&
activeVisibleRows.every((j) => selectionStore.isSelected(keyFor(j)))
)
}, [pageRows, keyFor, selectionStore, selectionVersion])
}, [activeVisibleRows, keyFor, selectionStore, selectionVersion])
const hasSearchQuery = (searchQuery || '').trim() !== ''
const selectionActionsVisible = selectedCount > 0 && selectionActionsOpen
@ -2617,7 +2613,7 @@ export default function FinishedDownloads({
const selectedKeys = useMemo(() => {
const next = new Set<string>()
for (const j of pageRows) {
for (const j of activeVisibleRows) {
const k = keyFor(j)
if (selectionStore.isSelected(k)) {
next.add(k)
@ -2625,7 +2621,7 @@ export default function FinishedDownloads({
}
return next
}, [pageRows, keyFor, selectionStore, selectionVersion])
}, [activeVisibleRows, keyFor, selectionStore, selectionVersion])
// -----------------------------------------------------------------------------
// callbacks
@ -3801,7 +3797,7 @@ export default function FinishedDownloads({
// -----------------------------------------------------------------------------
useEffect(() => {
if (!globalFilterActive) {
if (!shouldLoadAllDoneJobs) {
setAllDoneJobs(null)
setAllDoneJobsLoading(false)
return
@ -3832,7 +3828,7 @@ export default function FinishedDownloads({
return () => {
cancelled = true
}
}, [globalFilterActive, loadAllDoneJobs])
}, [shouldLoadAllDoneJobs, loadAllDoneJobs])
useEffect(() => {
if (!showOnlyCorrupt) return
@ -5421,7 +5417,7 @@ export default function FinishedDownloads({
{view === 'cards' && (
<div className={isSmall ? ['relative z-0 pt-10', cardsMobileOffsetTopClass, cardsMobileOffsetBottomClass].filter(Boolean).join(' ') : ''}>
<FinishedDownloadsCardsView
rows={pageRows}
rows={cardRows}
selectionStore={selectionStore}
onToggleSelected={toggleSelected}
onToggleSelectAllPage={handleToggleSelectAllPage}
@ -5596,30 +5592,32 @@ export default function FinishedDownloads({
</main>
</div>
<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="shadow-2xl">
<Pagination
page={page}
pageSize={effectivePageSize}
totalItems={totalItemsForPagination}
onPageChange={(p) => {
flushSync(() => {
setInlinePlay(null)
setTeaserKey(null)
setHoverTeaserKey(null)
})
scrollFinishedListToTop()
onPageChange(p)
}}
showSummary={false}
prevLabel="Zurück"
nextLabel="Weiter"
className="mt-0"
/>
{!mobileCardStackMode ? (
<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="shadow-2xl">
<Pagination
page={page}
pageSize={effectivePageSize}
totalItems={totalItemsForPagination}
onPageChange={(p) => {
flushSync(() => {
setInlinePlay(null)
setTeaserKey(null)
setHoverTeaserKey(null)
})
scrollFinishedListToTop()
onPageChange(p)
}}
showSummary={false}
prevLabel="Zurück"
nextLabel="Weiter"
className="mt-0"
/>
</div>
</div>
</div>
</div>
) : null}
</>
)
}

View File

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

View File

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

View File

@ -809,7 +809,7 @@ function TrainingStageOverlay(props: {
aria-hidden="true"
draggable={false}
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',
backgroundVisible ? 'opacity-80' : 'opacity-0',
].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">
<section
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
? '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'
@ -5666,6 +5666,7 @@ export default function TrainingTab(props: {
})}
</div>
{ /* Rechte Seite */ }
<div
ref={mobileLabelsScrollRef}
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
autoAddToDownloadList?: boolean
autoStartAddedDownloads?: boolean
enableConcurrentDownloadsLimit?: boolean
maxConcurrentDownloads?: number
useChaturbateApi?: boolean
useMyFreeCamsWatcher?: boolean
autoDeleteSmallDownloads?: boolean
@ -283,6 +285,7 @@ export type JobEvent = {
modelImageUrl?: string
modelChatRoomUrl?: string
ts?: number
error?: string
postWorkKey?: string
postWork?: {
@ -293,10 +296,11 @@ export type JobEvent = {
maxParallel?: number
}
// ✅ neu für finished_postwork
file?: string
assetId?: string
queue?: FinishedPostworkQueue
state?: FinishedPostworkState
queue?: 'postwork' | 'enrich'
state?: 'queued' | 'running' | 'done' | 'error' | 'missing'
label?: string
position?: number
waiting?: number
@ -304,6 +308,55 @@ export type JobEvent = {
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 = {
id: string
input: string