Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/ui/sources/components/ui/lists/MeterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ export const MeterBar = React.memo<MeterBarProps>((props) => {
animatedFill.setValue(fill);
return;
}
// Transfers report progress every ~100ms and each report restarts this animation. A decelerating
// curve then lurches and stalls on every report; a linear glide spanning a few reports reads as
// one continuous motion.
const animation = Animated.timing(animatedFill, {
toValue: fill,
duration: motionTokens.durationMs.fast,
easing: motionTokens.easing.standard,
duration: motionTokens.durationMs.slow,
easing: motionTokens.easing.linear,
useNativeDriver: Platform.OS !== 'web',
});
animation.start();
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/ca.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8785,6 +8785,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} de ${total} · ${filesDone} de ${filesTotal} ${filesTotal === 1 ? 'fitxer' : 'fitxers'}`,
progressAccessibilityLabel: "Progrés de la descàrrega",
verifyingDetail: "Verificant",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Verificat ${verified} de ${total} · ${filesDone} de ${filesTotal} ${filesTotal === 1 ? 'fitxer' : 'fitxers'}`,
verifyingAccessibilityLabel: "Progrés de la verificació",
failedDetail: "Ha fallat",
failedSubtitle: "No s'ha pogut preparar el model. Toca per tornar-ho a provar.",
failedResumableSubtitle: ({ size }: { size: string }) => `La descàrrega no s'ha completat. Es conserven ${size}; toca per reprendre-la.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8853,6 +8853,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} von ${total} · ${filesDone} von ${filesTotal} ${filesTotal === 1 ? 'Datei' : 'Dateien'}`,
progressAccessibilityLabel: "Download-Fortschritt",
verifyingDetail: "Wird geprüft",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`${verified} von ${total} geprüft · ${filesDone} von ${filesTotal} ${filesTotal === 1 ? 'Datei' : 'Dateien'}`,
verifyingAccessibilityLabel: "Prüffortschritt",
failedDetail: "Fehlgeschlagen",
failedSubtitle: "Das Modell konnte nicht vorbereitet werden. Tippe, um es erneut zu versuchen.",
failedResumableSubtitle: ({ size }: { size: string }) => `Der Download wurde nicht abgeschlossen. ${size} bleiben erhalten – tippe zum Fortsetzen.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8885,6 +8885,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} of ${total} · ${filesDone} of ${filesTotal} ${filesTotal === 1 ? 'file' : 'files'}`,
progressAccessibilityLabel: "Download progress",
verifyingDetail: "Verifying",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Verified ${verified} of ${total} · ${filesDone} of ${filesTotal} ${filesTotal === 1 ? 'file' : 'files'}`,
verifyingAccessibilityLabel: "Verification progress",
failedDetail: "Failed",
failedSubtitle: "Couldn't get the model ready. Tap to try again.",
failedResumableSubtitle: ({ size }: { size: string }) => `Download didn't finish. ${size} is saved, so tap to resume.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9336,6 +9336,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} de ${total} · ${filesDone} de ${filesTotal} ${filesTotal === 1 ? 'archivo' : 'archivos'}`,
progressAccessibilityLabel: "Progreso de la descarga",
verifyingDetail: "Verificando",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Verificado ${verified} de ${total} · ${filesDone} de ${filesTotal} ${filesTotal === 1 ? 'archivo' : 'archivos'}`,
verifyingAccessibilityLabel: "Progreso de la verificación",
failedDetail: "Falló",
failedSubtitle: "No se pudo preparar el modelo. Toca para volver a intentarlo.",
failedResumableSubtitle: ({ size }: { size: string }) => `La descarga no terminó. Se conservan ${size}; toca para reanudarla.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8840,6 +8840,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} sur ${total} · ${filesDone} ${filesDone <= 1 ? 'fichier' : 'fichiers'} sur ${filesTotal}`,
progressAccessibilityLabel: "Progression du téléchargement",
verifyingDetail: "Vérification",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`${verified} sur ${total} vérifiés · ${filesDone} ${filesDone <= 1 ? 'fichier' : 'fichiers'} sur ${filesTotal}`,
verifyingAccessibilityLabel: "Progression de la vérification",
failedDetail: "Échec",
failedSubtitle: "Impossible de préparer le modèle. Touche pour réessayer.",
failedResumableSubtitle: ({ size }: { size: string }) => `Le téléchargement n'est pas terminé. ${size} sont conservés ; touche pour reprendre.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9657,6 +9657,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} di ${total} · ${filesDone} di ${filesTotal} file`,
progressAccessibilityLabel: "Avanzamento del download",
verifyingDetail: "Verifica in corso",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Verificati ${verified} di ${total} · ${filesDone} di ${filesTotal} file`,
verifyingAccessibilityLabel: "Avanzamento della verifica",
failedDetail: "Non riuscito",
failedSubtitle: "Impossibile preparare il modello. Tocca per riprovare.",
failedResumableSubtitle: ({ size }: { size: string }) => `Il download non è stato completato. ${size} sono stati conservati: tocca per riprendere.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9564,6 +9564,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} / ${total} · ${filesDone} / ${filesTotal} ファイル`,
progressAccessibilityLabel: "ダウンロードの進行状況",
verifyingDetail: "検証中",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`検証済み ${verified} / ${total} · ${filesDone} / ${filesTotal} ファイル`,
verifyingAccessibilityLabel: "検証の進行状況",
failedDetail: "失敗",
failedSubtitle: "モデルを準備できませんでした。タップしてもう一度お試しください。",
failedResumableSubtitle: ({ size }: { size: string }) => `ダウンロードが完了しませんでした。${size} は保存されています。タップして再開できます。`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9334,6 +9334,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} z ${total} · ${filesDone} z ${filesTotal} ${filesTotal === 1 ? 'pliku' : 'plików'}`,
progressAccessibilityLabel: "Postęp pobierania",
verifyingDetail: "Weryfikacja",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Zweryfikowano ${verified} z ${total} · ${filesDone} z ${filesTotal} ${filesTotal === 1 ? 'pliku' : 'plików'}`,
verifyingAccessibilityLabel: "Postęp weryfikacji",
failedDetail: "Niepowodzenie",
failedSubtitle: "Nie udało się przygotować modelu. Dotknij, aby spróbować ponownie.",
failedResumableSubtitle: ({ size }: { size: string }) => `Pobieranie nie zostało ukończone. Zachowano ${size} — dotknij, aby wznowić.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9445,6 +9445,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} de ${total} · ${filesDone} de ${filesTotal} ${filesTotal === 1 ? 'arquivo' : 'arquivos'}`,
progressAccessibilityLabel: "Progresso do download",
verifyingDetail: "Verificando",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Verificado ${verified} de ${total} · ${filesDone} de ${filesTotal} ${filesTotal === 1 ? 'arquivo' : 'arquivos'}`,
verifyingAccessibilityLabel: "Progresso da verificação",
failedDetail: "Falhou",
failedSubtitle: "Não foi possível preparar o modelo. Toque para tentar novamente.",
failedResumableSubtitle: ({ size }: { size: string }) => `O download não terminou. ${size} foram mantidos; toque para retomar.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9308,6 +9308,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} из ${total} · ${filesDone} из ${filesTotal} ${plural({ count: filesTotal, one: 'файла', few: 'файлов', many: 'файлов' })}`,
progressAccessibilityLabel: "Ход загрузки",
verifyingDetail: "Проверка",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`Проверено ${verified} из ${total} · ${filesDone} из ${filesTotal} ${plural({ count: filesTotal, one: 'файла', few: 'файлов', many: 'файлов' })}`,
verifyingAccessibilityLabel: "Ход проверки",
failedDetail: "Ошибка",
failedSubtitle: "Не удалось подготовить модель. Нажмите, чтобы повторить.",
failedResumableSubtitle: ({ size }: { size: string }) => `Загрузка не завершилась. ${size} сохранено — нажмите, чтобы продолжить.`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/zh-Hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9031,6 +9031,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} / ${total} · ${filesDone} / ${filesTotal} 个文件`,
progressAccessibilityLabel: "下载进度",
verifyingDetail: "正在校验",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`已校验 ${verified} / ${total} · ${filesDone} / ${filesTotal} 个文件`,
verifyingAccessibilityLabel: "校验进度",
failedDetail: "失败",
failedSubtitle: "无法准备模型。轻点以重试。",
failedResumableSubtitle: ({ size }: { size: string }) => `下载未完成。已保留 ${size},轻点即可继续。`,
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/sources/text/translations/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7644,6 +7644,10 @@ settingsSession: {
progressWithFiles: ({ loaded, total, filesDone, filesTotal }: { loaded: string; total: string; filesDone: number; filesTotal: number }) =>
`${loaded} / ${total} · ${filesDone} / ${filesTotal} 個檔案`,
progressAccessibilityLabel: "下載進度",
verifyingDetail: "正在驗證",
verifyingProgress: ({ verified, total, filesDone, filesTotal }: { verified: string; total: string; filesDone: number; filesTotal: number }) =>
`已驗證 ${verified} / ${total} · ${filesDone} / ${filesTotal} 個檔案`,
verifyingAccessibilityLabel: "驗證進度",
failedDetail: "失敗",
failedSubtitle: "無法準備模型。輕點以重試。",
failedResumableSubtitle: ({ size }: { size: string }) => `下載未完成。已保留 ${size},輕點即可繼續。`,
Expand Down
13 changes: 11 additions & 2 deletions apps/ui/sources/voice/downloads/downloadProgress.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { formatDownloadProgressDetail, parseDownloadProgress } from './downloadP

describe('parseDownloadProgress', () => {
it('parses number fractions as percent', () => {
expect(parseDownloadProgress(0.25)).toEqual({ percent: 25, label: null, bytes: null, files: null });
expect(parseDownloadProgress(0.25)).toEqual({ percent: 25, label: null, bytes: null, files: null, verifiedBytes: null });
});

it('parses percent numbers directly', () => {
expect(parseDownloadProgress(42)).toEqual({ percent: 42, label: null, bytes: null, files: null });
expect(parseDownloadProgress(42)).toEqual({ percent: 42, label: null, bytes: null, files: null, verifiedBytes: null });
});

it('parses loaded/total into percent and byte counts', () => {
Expand All @@ -17,6 +17,7 @@ describe('parseDownloadProgress', () => {
label: null,
bytes: { loaded: 50, total: 200 },
files: null,
verifiedBytes: null,
});
});

Expand All @@ -27,6 +28,12 @@ describe('parseDownloadProgress', () => {
});
});

it('parses verified bytes within the transfer size', () => {
expect(parseDownloadProgress({ loaded: 200, total: 200, verified: 150 }).verifiedBytes).toBe(150);
expect(parseDownloadProgress({ loaded: 200, total: 200, verified: 900 }).verifiedBytes).toBe(200);
expect(parseDownloadProgress({ loaded: 200, total: 200 }).verifiedBytes).toBeNull();
});

it('ignores file counts that cannot describe a transfer', () => {
expect(parseDownloadProgress({ loaded: 1, total: 2, filesDone: 0, filesTotal: 0 }).files).toBeNull();
expect(parseDownloadProgress({ loaded: 1, total: 2, filesDone: Number.NaN, filesTotal: 4 }).files).toBeNull();
Expand All @@ -38,6 +45,7 @@ describe('parseDownloadProgress', () => {
label: 'model_quantized.onnx',
bytes: null,
files: null,
verifiedBytes: null,
});
});

Expand All @@ -47,6 +55,7 @@ describe('parseDownloadProgress', () => {
label: 'af_heart.bin',
bytes: null,
files: null,
verifiedBytes: null,
});
});
});
Expand Down
14 changes: 9 additions & 5 deletions apps/ui/sources/voice/downloads/downloadProgress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type DownloadProgressInfo = {
bytes: { loaded: number; total: number } | null;
/** Completed and total files, when the source reports a multi-file transfer. */
files: { done: number; total: number } | null;
/** Bytes already checked against their expected hash, when the source verifies what it downloads. */
verifiedBytes: number | null;
};

function clampPercent(x: number): number {
Expand Down Expand Up @@ -35,10 +37,10 @@ export function parseDownloadProgress(progress: unknown): DownloadProgressInfo {
if (typeof progress === 'number' && Number.isFinite(progress)) {
const isFraction = progress >= 0 && progress <= 1;
const percent = clampPercent(Math.floor((isFraction ? progress * 100 : progress) as number));
return { percent, label: null, bytes: null, files: null };
return { percent, label: null, bytes: null, files: null, verifiedBytes: null };
}

if (!progress || typeof progress !== 'object') return { percent: null, label: null, bytes: null, files: null };
if (!progress || typeof progress !== 'object') return { percent: null, label: null, bytes: null, files: null, verifiedBytes: null };

const obj = progress as Record<string, unknown>;

Expand All @@ -58,17 +60,19 @@ export function parseDownloadProgress(progress: unknown): DownloadProgressInfo {
const total = obj?.total;
if (isFiniteNumber(loaded) && isFiniteNumber(total) && total > 0) {
const percent = clampPercent(Math.floor((loaded / total) * 100));
return { percent, label, bytes: { loaded: Math.min(Math.max(0, loaded), total), total }, files };
const verified = obj?.verified;
const verifiedBytes = isFiniteNumber(verified) ? Math.min(Math.max(0, verified), total) : null;
return { percent, label, bytes: { loaded: Math.min(Math.max(0, loaded), total), total }, files, verifiedBytes };
}

const rawProgress = obj?.progress;
if (isFiniteNumber(rawProgress)) {
const isFraction = rawProgress >= 0 && rawProgress <= 1;
const percent = clampPercent(Math.floor((isFraction ? rawProgress * 100 : rawProgress) as number));
return { percent, label, bytes: null, files };
return { percent, label, bytes: null, files, verifiedBytes: null };
}

return { percent: null, label, bytes: null, files };
return { percent: null, label, bytes: null, files, verifiedBytes: null };
}

export function formatDownloadProgressDetail(progress: unknown, opts?: { prefix?: string }): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,12 @@ describe('native model pack file downloads', () => {
if (platform === 'ios') expect(harness.nativeOptions[0]?.sessionType).toBe(1);
expect(harness.files.get(harness.fileUri)).toEqual(harness.bytes);
expect(harness.files.has(harness.metaUri)).toBe(true);
// Bytes arrive while the file transfers; it counts as done only once verified.
// Bytes arrive while the file transfers, then hashing reports verified bytes; it counts as done only once verified.
expect(progress).toEqual([
{ loaded: 2, total: 4, filesDone: 0, filesTotal: 1 },
{ loaded: 4, total: 4, filesDone: 0, filesTotal: 1 },
{ loaded: 4, total: 4, filesDone: 1, filesTotal: 1 },
{ loaded: 2, total: 4, verified: 0, filesDone: 0, filesTotal: 1 },
{ loaded: 4, total: 4, verified: 0, filesDone: 0, filesTotal: 1 },
{ loaded: 4, total: 4, verified: 4, filesDone: 0, filesTotal: 1 },
{ loaded: 4, total: 4, verified: 4, filesDone: 1, filesTotal: 1 },
]);
expect(harness.lifecycle).toMatchObject({ subscribed: false, retainedNativeTask: false });
});
Expand Down
Loading
Loading