"use client"; // Per-engine digest configuration, serialized to a hidden JSON field — the // WorkersField idiom, for the same reason: `settings.digest.apps` is a keyed map // of optional fields, and a flat set of form inputs cannot express "this app // overrides only the model" without inventing a naming scheme per app. // // Only the fields an engine actually supports are rendered (DigestApp.fields), // so the metered lane never shows a `num_ctx` box it would ignore. // // A blank input means "unset" and is OMITTED from the JSON rather than written // as "" or 0 — sanitizeDigestApps drops empty values anyway, but writing them // would make an untouched app look configured, and `numCtx: 0` in particular // would read as a real (invalid) context size rather than as absence. import { useState } from "react"; import type { DigestAppDescriptor } from "yt-dlp-transcript-common/lib/digestApps"; import type { DigestAppConfig } from "yt-dlp-transcript-common/lib/digest"; import { DEFAULT_DIGEST_NUM_CTX } from "yt-dlp-transcript-common/lib/digest"; type Row = { bin: string; baseUrl: string; model: string; numCtx: string; temperature: string; }; function toRow(cfg: DigestAppConfig | undefined): Row { return { bin: cfg?.bin ?? "", baseUrl: cfg?.baseUrl ?? "", model: cfg?.model ?? "", numCtx: cfg?.numCtx != null ? String(cfg.numCtx) : "", temperature: cfg?.temperature != null ? String(cfg.temperature) : "", }; } function serialize(rows: Record): string { const out: Record = {}; for (const [id, row] of Object.entries(rows)) { const cfg: DigestAppConfig = {}; if (row.bin.trim()) cfg.bin = row.bin.trim(); if (row.baseUrl.trim()) cfg.baseUrl = row.baseUrl.trim(); if (row.model.trim()) cfg.model = row.model.trim(); const n = Number.parseInt(row.numCtx.trim(), 10); if (Number.isFinite(n) && n > 0) cfg.numCtx = n; const t = Number.parseFloat(row.temperature.trim()); if (Number.isFinite(t) && t >= 0) cfg.temperature = t; if (Object.keys(cfg).length > 0) out[id] = cfg; } return JSON.stringify(out); } export function DigestAppsField({ apps, initial, name, }: { apps: DigestAppDescriptor[]; initial: Record; name: string; }) { const [rows, setRows] = useState>(() => Object.fromEntries(apps.map((a) => [a.id, toRow(initial[a.id])])), ); const set = (id: string, key: keyof Row, value: string): void => setRows((prev) => ({ ...prev, [id]: { ...prev[id], [key]: value } })); return (
{apps.map((app) => { const row = rows[app.id] ?? toRow(undefined); return (
{app.label} {app.id} {app.metered ? "metered" : "local"}
{app.fields.model && ( )} {app.fields.numCtx && ( )} {app.fields.temperature && ( )} {app.fields.baseUrl && ( )} {app.fields.bin && ( )}
); })}
); } function Cell({ label, id, field, value, onChange, placeholder, hint, type = "text", }: { label: string; id: string; field: keyof Row; value: string; onChange: (id: string, key: keyof Row, value: string) => void; placeholder?: string; hint?: string; type?: string; }) { return ( ); }