From 5b5cba7030b7f4dd519868fe310bb4fce893bf91 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 30 Jun 2026 03:52:02 +0800
Subject: [PATCH] Add Windows QA command launcher

---
 privatevoice.src/frontend/src/components/onboarding/OnboardingView.svelte |  109 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 95 insertions(+), 14 deletions(-)

diff --git a/privatevoice.src/frontend/src/components/onboarding/OnboardingView.svelte b/privatevoice.src/frontend/src/components/onboarding/OnboardingView.svelte
index 566b118..2b3a727 100755
--- a/privatevoice.src/frontend/src/components/onboarding/OnboardingView.svelte
+++ b/privatevoice.src/frontend/src/components/onboarding/OnboardingView.svelte
@@ -9,11 +9,21 @@
   let detailText = $state('')
   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 downloadedMB = Math.floor((data.downloaded || 0) / 1024 / 1024)
         const totalMB = Math.floor((data.total || 0) / 1024 / 1024)
@@ -24,13 +34,21 @@
     Events.On('engine:status', (ev: any) => {
       const data = ev?.data
       if (data?.status === 'ready') {
+        downloadActive = false
+        cancelling = false
+        failed = false
         statusText = t('onboarding.complete')
         progress = 100
       } else if (data?.status === 'need_model') {
-        statusText = t('onboarding.failed')
-        detailText = data?.error || ''
-        failed = true
+        const error = typeof data?.error === 'string' ? data.error.trim() : ''
+        if (error && !downloadActive) {
+          statusText = t('onboarding.failed')
+          detailText = error
+          failed = true
+        }
       } else if (data?.status === 'error') {
+        downloadActive = false
+        cancelling = false
         statusText = t('onboarding.failed')
         detailText = data?.error || ''
         failed = true
@@ -42,20 +60,41 @@
 
   async function triggerDownload() {
     try {
-      const config: any = await Call.ByName('voicesnap/services.ConfigService.GetConfig')
-      if (config) {
-        await Call.ByName('voicesnap/services.EngineService.DownloadModel',
-          config.ModelDownloadUrl,
-          config.FallbackModelDownloadUrl
-        )
-        statusText = t('onboarding.optimizing')
-        detailText = t('onboarding.extracting')
-      }
+      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 = ''
+      await Call.ByName('voicesnap/services.EngineService.DownloadCurrentModel')
+      downloadActive = false
+      statusText = t('onboarding.optimizing')
+      detailText = t('onboarding.extracting')
     } catch (err: any) {
-      statusText = t('onboarding.failed')
-      detailText = err?.message || String(err)
+      downloadActive = false
+      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>
 
@@ -76,6 +115,18 @@
     <!-- 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>
@@ -152,4 +203,34 @@
   .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>

--
Gitblit v1.9.3