| | |
| | | let progress = $state(0) |
| | | let failed = $state(false) |
| | | let downloadActive = $state(false) |
| | | let cancelling = $state(false) |
| | | let currentModelID = $state('') |
| | | |
| | | onMount(() => { |
| | | Events.On('model:download-progress', (ev: any) => { |
| | | const data = ev?.data |
| | | if (data?.percent != null) { |
| | | if (data?.modelID) { |
| | | currentModelID = data.modelID |
| | | } |
| | | downloadActive = true |
| | | cancelling = false |
| | | failed = false |
| | | statusText = t('onboarding.syncModel') |
| | | progress = data.percent |
| | |
| | | const data = ev?.data |
| | | if (data?.status === 'ready') { |
| | | downloadActive = false |
| | | cancelling = false |
| | | failed = false |
| | | statusText = t('onboarding.complete') |
| | | progress = 100 |
| | |
| | | } |
| | | } else if (data?.status === 'error') { |
| | | downloadActive = false |
| | | cancelling = false |
| | | statusText = t('onboarding.failed') |
| | | detailText = data?.error || '' |
| | | failed = true |
| | |
| | | |
| | | async function triggerDownload() { |
| | | try { |
| | | const current: any = await Call.ByName('voicesnap/services.EngineService.GetCurrentModelStatus') |
| | | currentModelID = typeof current?.modelID === 'string' ? current.modelID : '' |
| | | downloadActive = true |
| | | cancelling = false |
| | | failed = false |
| | | statusText = t('onboarding.syncModel') |
| | | detailText = '' |
| | |
| | | detailText = t('onboarding.extracting') |
| | | } catch (err: any) { |
| | | downloadActive = false |
| | | statusText = t('onboarding.failed') |
| | | detailText = err?.message || String(err) |
| | | cancelling = false |
| | | if (isCancelError(err)) { |
| | | statusText = t('onboarding.cancelled') |
| | | detailText = t('onboarding.cancelledDetail') |
| | | } else { |
| | | statusText = t('onboarding.failed') |
| | | detailText = err?.message || String(err) |
| | | } |
| | | failed = true |
| | | } |
| | | } |
| | | |
| | | function isCancelError(err: any): boolean { |
| | | const text = String(err?.message || err || '').toLowerCase() |
| | | return text.includes('context canceled') || text.includes('cancelled') || text.includes('canceled') |
| | | } |
| | | |
| | | async function cancelDownload() { |
| | | if (!downloadActive || cancelling) return |
| | | cancelling = true |
| | | statusText = t('onboarding.cancelling') |
| | | await Call.ByName('voicesnap/services.EngineService.CancelModelDownload', currentModelID) |
| | | } |
| | | </script> |
| | | |
| | |
| | | <!-- Progress bar --> |
| | | <div class="progress-track"> |
| | | <div class="progress-fill" style="width: {progress}%" class:failed></div> |
| | | </div> |
| | | |
| | | <div class="actions"> |
| | | {#if downloadActive} |
| | | <button class="action cancel" onclick={cancelDownload} disabled={cancelling}> |
| | | {cancelling ? t('onboarding.cancelling') : t('onboarding.cancel')} |
| | | </button> |
| | | {:else if failed} |
| | | <button class="action primary" onclick={triggerDownload}> |
| | | {t('onboarding.retry')} |
| | | </button> |
| | | {/if} |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | .progress-fill.failed { |
| | | background: var(--color-red); |
| | | } |
| | | |
| | | .actions { |
| | | margin-top: var(--spacing-lg); |
| | | min-height: 32px; |
| | | } |
| | | |
| | | .action { |
| | | min-width: 96px; |
| | | height: 32px; |
| | | padding: 0 14px; |
| | | border: none; |
| | | border-radius: var(--radius-sm); |
| | | color: white; |
| | | font-size: var(--font-size-sm); |
| | | font-weight: 500; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .action.primary { |
| | | background: var(--color-blue); |
| | | } |
| | | |
| | | .action.cancel { |
| | | background: var(--color-red); |
| | | } |
| | | |
| | | .action:disabled { |
| | | cursor: not-allowed; |
| | | opacity: 0.72; |
| | | } |
| | | </style> |