Use the parent clip last frame and new-segment join for extensions, not the stitched file's first frame. Show this account's library storage in Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-28 10:54:46 -05:00
co-authored by Cursor
parent 231c59a7a7
commit 0af3cddb14
16 changed files with 571 additions and 70 deletions
+78 -1
View File
@@ -5,11 +5,24 @@
<div class="flex items-start justify-between gap-3">
<div>
<h2 class="font-display text-2xl font-bold">Settings</h2>
<p class="mt-1 text-sm text-zinc-400">Library folders, passwords, and prompt-helper instruction sets for this account.</p>
<p class="mt-1 text-sm text-zinc-400">Library storage, folders, passwords, and prompt-helper instruction sets for this account.</p>
</div>
<button type="button" class="rounded-full border border-white/10 px-3 py-1 text-sm text-zinc-400 hover:text-white" @click="emit('close')">Close</button>
</div>
<section class="mt-6">
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Storage</h3>
<div class="mt-2 rounded-2xl border border-white/10 bg-zinc-950/50 px-4 py-3">
<p v-if="storageLoading" class="text-sm text-zinc-500">Measuring…</p>
<p v-else-if="storageError" class="text-sm text-zinc-500">{{ storageError }}</p>
<template v-else-if="storage">
<p class="text-sm text-zinc-200">{{ storageLine }}</p>
<p v-if="storageBreakdown" class="mt-1 text-xs text-zinc-500">{{ storageBreakdown }}</p>
<p class="mt-1 text-xs text-zinc-600">{{ storageNote }}</p>
</template>
</div>
</section>
<section v-if="authMode === 'password'" class="mt-6 space-y-3">
<h3 class="text-sm font-semibold uppercase tracking-wider text-zinc-500">Site login</h3>
<p class="text-sm text-zinc-400">Username and password are stored encrypted on this server. Changing them does not move the library.</p>
@@ -207,6 +220,56 @@ const loginUser = ref(props.username || '')
const loginCurrent = ref('')
const loginNext = ref('')
const drafts = reactive<Record<string, { name: string; protect: boolean; password: string; currentPassword: string }>>({})
const storageLoading = ref(true)
const storageError = ref('')
const storage = ref<{
libraryBytes: number
breakdown: { clips: number; stills: number; tmp: number; other: number }
volume: { ok: boolean; totalBytes: number | null; usedBytes: number | null; freeBytes: number | null; overlay: boolean }
} | null>(null)
function formatBytes(bytes: number) {
const n = Math.max(0, Number(bytes) || 0)
if (n < 1024) return `${Math.round(n)} B`
const units = ['KB', 'MB', 'GB', 'TB']
let value = n / 1024
let unit = 0
while (value >= 1024 && unit < units.length - 1) {
value /= 1024
unit += 1
}
const digits = value >= 10 || units[unit] === 'KB' ? 0 : 1
return `${value.toFixed(digits)} ${units[unit]}`
}
const storageLine = computed(() => {
if (!storage.value) return ''
const library = `Library ${formatBytes(storage.value.libraryBytes)}`
const volume = storage.value.volume
if (!volume.ok || volume.freeBytes == null || volume.totalBytes == null) {
return `${library} · free space unavailable`
}
const free = `${formatBytes(volume.freeBytes)} free of ${formatBytes(volume.totalBytes)}`
return volume.overlay ? `${library} · ${free} on the container disk` : `${library} · ${free}`
})
const storageBreakdown = computed(() => {
if (!storage.value) return ''
const { clips, stills, tmp } = storage.value.breakdown
const parts = [
clips ? `Clips ${formatBytes(clips)}` : '',
stills ? `Stills ${formatBytes(stills)}` : '',
tmp ? `Temp ${formatBytes(tmp)}` : ''
].filter(Boolean)
return parts.join(' · ')
})
const storageNote = computed(() => {
if (!storage.value) return ''
return storage.value.volume.overlay
? "This account's clips, stills, thumbs, and temp. Free space is the container disk, not a mounted volume."
: "This account's clips, stills, thumbs, and temp. Volume free space is shared."
})
function onKey(event: KeyboardEvent) {
if (event.key === 'Escape') emit('close')
@@ -215,6 +278,7 @@ function onKey(event: KeyboardEvent) {
onMounted(() => {
window.addEventListener('keydown', onKey)
void loadPresets()
void loadStorage()
})
onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
@@ -280,6 +344,19 @@ async function loadPresets() {
promptPresets.value = data.presets || []
}
async function loadStorage() {
storageLoading.value = true
storageError.value = ''
try {
storage.value = await $fetch('/api/settings/storage')
} catch (error: any) {
storage.value = null
storageError.value = error?.data?.statusMessage || error?.statusMessage || 'Could not read storage'
} finally {
storageLoading.value = false
}
}
async function addPreset() {
const name = newPresetName.value.trim()
const systemPrompt = newPresetPrompt.value.trim()