Add Test & Download button for diarization model, clickable links
- Add diarize.download IPC handler that downloads the pyannote model and returns user-friendly error messages (missing license, bad token) - Add download_diarize_model Tauri command - Add "Test & Download Model" button in Speakers settings tab - Update instructions to list both required model licenses (speaker-diarization-3.1 AND segmentation-3.0) - Make all HuggingFace URLs clickable (opens in system browser) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { settings, saveSettings, type AppSettings } from '$lib/stores/settings';
|
||||
|
||||
interface Props {
|
||||
@@ -10,6 +12,32 @@
|
||||
|
||||
let localSettings = $state<AppSettings>({ ...$settings });
|
||||
let activeTab = $state<'transcription' | 'speakers' | 'ai' | 'local'>('transcription');
|
||||
let modelStatus = $state<'idle' | 'downloading' | 'success' | 'error'>('idle');
|
||||
let modelError = $state('');
|
||||
|
||||
async function testAndDownloadModel() {
|
||||
if (!localSettings.hf_token) {
|
||||
modelStatus = 'error';
|
||||
modelError = 'Please enter a HuggingFace token first.';
|
||||
return;
|
||||
}
|
||||
modelStatus = 'downloading';
|
||||
modelError = '';
|
||||
try {
|
||||
const result = await invoke<{ ok: boolean; error?: string }>('download_diarize_model', {
|
||||
hfToken: localSettings.hf_token,
|
||||
});
|
||||
if (result.ok) {
|
||||
modelStatus = 'success';
|
||||
} else {
|
||||
modelStatus = 'error';
|
||||
modelError = result.error || 'Unknown error';
|
||||
}
|
||||
} catch (err) {
|
||||
modelStatus = 'error';
|
||||
modelError = String(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Sync when settings store changes
|
||||
$effect(() => {
|
||||
@@ -86,18 +114,41 @@
|
||||
<input id="hf-token" type="password" bind:value={localSettings.hf_token} placeholder="hf_..." />
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<p class="info-title">Why is this needed?</p>
|
||||
<p>Speaker detection uses the <strong>pyannote.audio</strong> model, which is hosted on HuggingFace and requires accepting a license agreement.</p>
|
||||
<p class="info-title">How to get a token:</p>
|
||||
<p class="info-title">Setup (one-time)</p>
|
||||
<p>Speaker detection uses <strong>pyannote.audio</strong> models hosted on HuggingFace. You must accept the license for each model:</p>
|
||||
<ol>
|
||||
<li>Create a free account at <strong>huggingface.co</strong></li>
|
||||
<li>Go to <strong>huggingface.co/pyannote/speaker-diarization-3.1</strong> and accept the license</li>
|
||||
<li>Go to <strong>huggingface.co/settings/tokens</strong> and create a token with <em>read</em> access</li>
|
||||
<li>Paste the token above and click Save</li>
|
||||
<li>Create a free account at <!-- svelte-ignore a11y_no_static_element_interactions --><a class="ext-link" onclick={() => openUrl('https://huggingface.co/join')}>huggingface.co</a></li>
|
||||
<li>Accept the license on <strong>each</strong> of these pages:
|
||||
<ul>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<li><a class="ext-link" onclick={() => openUrl('https://huggingface.co/pyannote/speaker-diarization-3.1')}>pyannote/speaker-diarization-3.1</a></li>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<li><a class="ext-link" onclick={() => openUrl('https://huggingface.co/pyannote/segmentation-3.0')}>pyannote/segmentation-3.0</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<li>Create a token at <a class="ext-link" onclick={() => openUrl('https://huggingface.co/settings/tokens')}>huggingface.co/settings/tokens</a> (read access)</li>
|
||||
<li>Paste the token above and click <strong>Test & Download</strong></li>
|
||||
</ol>
|
||||
<p>The model will be downloaded automatically on first use (~100 MB).</p>
|
||||
</div>
|
||||
<div class="field checkbox">
|
||||
<button
|
||||
class="btn-download"
|
||||
onclick={testAndDownloadModel}
|
||||
disabled={modelStatus === 'downloading'}
|
||||
>
|
||||
{#if modelStatus === 'downloading'}
|
||||
Downloading model...
|
||||
{:else}
|
||||
Test & Download Model
|
||||
{/if}
|
||||
</button>
|
||||
{#if modelStatus === 'success'}
|
||||
<p class="status-success">Model downloaded successfully. Speaker detection is ready.</p>
|
||||
{/if}
|
||||
{#if modelStatus === 'error'}
|
||||
<p class="status-error">{modelError}</p>
|
||||
{/if}
|
||||
<div class="field checkbox" style="margin-top: 1rem;">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={localSettings.skip_diarization} />
|
||||
Skip speaker detection (faster, no speaker labels)
|
||||
@@ -303,6 +354,48 @@
|
||||
.info-box strong {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.ext-link {
|
||||
color: #e94560;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ext-link:hover {
|
||||
color: #ff6b81;
|
||||
}
|
||||
.info-box ul {
|
||||
margin: 0.25rem 0;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.btn-download {
|
||||
background: #0f3460;
|
||||
border: 1px solid #4a5568;
|
||||
color: #e0e0e0;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.btn-download:hover:not(:disabled) {
|
||||
background: #1a4a7a;
|
||||
border-color: #e94560;
|
||||
}
|
||||
.btn-download:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.status-success {
|
||||
color: #4ecdc4;
|
||||
font-size: 0.8rem;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
.status-error {
|
||||
color: #e94560;
|
||||
font-size: 0.8rem;
|
||||
margin: 0.25rem 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
Reference in New Issue
Block a user