Transcript export (.txt/.srt) and model storage manager (M6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0084e0435a
commit
8f2da530e2
9 files changed
+743
-12
No files matched your search
@@ -0,0 +1,107 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
deleteModelFromCache,
|
||||
getModelStorageInfo,
|
||||
type ModelStorageInfo,
|
||||
} from "../lib/modelCache";
|
||||
import { MODELS, getModel } from "../lib/models";
|
||||
|
||||
interface StorageManagerProps {
|
||||
/** Downloaded-badge set from App — changes re-measure an open panel. */
|
||||
downloaded: ReadonlySet<string>;
|
||||
/** True while transcribing/live — weights may be mid-read, so no deletes. */
|
||||
disabled: boolean;
|
||||
/** Called after a delete so App refreshes the downloaded badges (D-005). */
|
||||
onChanged: () => void;
|
||||
}
|
||||
|
||||
const MODEL_IDS = MODELS.map((model) => model.id);
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(2)} GB`;
|
||||
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(1)} MB`;
|
||||
if (bytes >= 1e3) return `${(bytes / 1e3).toFixed(0)} kB`;
|
||||
return `${bytes} B`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage management panel (M6): lists models with cached weights and their
|
||||
* exact on-disk size, with per-model delete. Collapsed by default — measuring
|
||||
* reads every cached response body, so it only runs when the panel is opened
|
||||
* (and again when the downloaded set changes or a model is deleted).
|
||||
*/
|
||||
export function StorageManager({ downloaded, disabled, onChanged }: StorageManagerProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [info, setInfo] = useState<ModelStorageInfo[] | null>(null);
|
||||
const [deleting, setDeleting] = useState<string | null>(null);
|
||||
|
||||
const measure = useCallback(() => {
|
||||
getModelStorageInfo(MODEL_IDS).then(setInfo, () => setInfo([]));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) measure();
|
||||
}, [open, downloaded, measure]);
|
||||
|
||||
async function handleDelete(modelId: string) {
|
||||
setDeleting(modelId);
|
||||
try {
|
||||
await deleteModelFromCache(modelId);
|
||||
measure();
|
||||
// Refresh the picker's downloaded/offline badges too.
|
||||
onChanged();
|
||||
} finally {
|
||||
setDeleting(null);
|
||||
}
|
||||
}
|
||||
|
||||
const totalBytes = (info ?? []).reduce((sum, entry) => sum + entry.bytes, 0);
|
||||
|
||||
return (
|
||||
<details
|
||||
className="storage-manager"
|
||||
onToggle={(event) => setOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary>
|
||||
Storage
|
||||
{open && info !== null && info.length > 0 && (
|
||||
<span className="storage-total"> — {formatBytes(totalBytes)} used</span>
|
||||
)}
|
||||
</summary>
|
||||
<p className="storage-hint">
|
||||
Model weights cached in this browser. Deleting frees the space; the
|
||||
model simply re-downloads the next time you use it.
|
||||
</p>
|
||||
{info === null ? (
|
||||
<p className="storage-hint">Measuring…</p>
|
||||
) : info.length === 0 ? (
|
||||
<p className="storage-hint">No models downloaded yet.</p>
|
||||
) : (
|
||||
<ul className="storage-list">
|
||||
{info.map((entry) => (
|
||||
<li key={entry.modelId} className="storage-row">
|
||||
<div className="storage-model">
|
||||
<span className="storage-name">
|
||||
{getModel(entry.modelId)?.displayName ?? entry.modelId}
|
||||
</span>
|
||||
<code className="storage-id">{entry.modelId}</code>
|
||||
</div>
|
||||
<span className="storage-size">
|
||||
{formatBytes(entry.bytes)} · {entry.fileCount}{" "}
|
||||
{entry.fileCount === 1 ? "file" : "files"}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="storage-delete"
|
||||
onClick={() => void handleDelete(entry.modelId)}
|
||||
disabled={disabled || deleting !== null}
|
||||
>
|
||||
{deleting === entry.modelId ? "Deleting…" : "Delete"}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</details>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user