Merge pull request #1965 from debpalash/fix/1856-dictation-step
fix(setup): stop the last onboarding step failing three times with no model
This commit is contained in:
@@ -9,6 +9,7 @@ the frozen-backend fallback mirror it for their toolchains.
|
||||
## [Unreleased]
|
||||
|
||||
**Highlights**
|
||||
- The last onboarding step offers to install a speech-to-text model instead of failing three times when none is installed (#1856)
|
||||
- A download that fails because the folder sits behind a mount point Windows will not cross now says so, and where to move it (#1957)
|
||||
- A GPU that is merely short on free memory is no longer told to reinstall its drivers (#1812) — thanks @michaelhuamanflores!
|
||||
- An error thrown by a browser extension is filtered on Safari and the macOS app too, not only on Chromium (#1901) — thanks @Chang-Jin-Lee!
|
||||
|
||||
@@ -26,6 +26,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { API, apiFetch } from '../api/client';
|
||||
import { asrMissingPayload, toastAsrModelMissing } from '../utils/asrModelMissing';
|
||||
import { useEffectiveDictationShortcut } from '../hooks/useEffectiveDictationShortcut';
|
||||
import { useDictationReadiness } from '../hooks/useDictationReadiness';
|
||||
import AsrModelChooser from './AsrModelChooser';
|
||||
import { Button } from '../ui';
|
||||
|
||||
// Shared status-pill base; per-state color/bg/border appended below. The gruvbox
|
||||
@@ -66,6 +68,12 @@ export default function DictationDemo({ embedded = false }) {
|
||||
const [hotkeyState, setHotkeyState] = useState('unknown'); // unknown | registered | verified
|
||||
const desktop = isTauri();
|
||||
const { info: shortcut } = useEffectiveDictationShortcut(desktop);
|
||||
// Transcribing a sample needs a speech-to-text model, and the mandatory-only
|
||||
// install path ships none. Rendering the cards regardless meant the final
|
||||
// onboarding step opened with one hard error per card and invited the user
|
||||
// to press a hotkey that could not work (#1856). Probed for the same reason
|
||||
// the demo already probes for its sample WAVs below.
|
||||
const readiness = useDictationReadiness();
|
||||
const [playingId, setPlayingId] = useState(null);
|
||||
const [transcripts, setTranscripts] = useState({}); // {scriptId: {state, text, error}}
|
||||
// null = probing, true/false once the demo assets are confirmed present.
|
||||
@@ -222,7 +230,11 @@ export default function DictationDemo({ embedded = false }) {
|
||||
// depend on the bundled WAVs, which installs don't always ship. Hiding
|
||||
// the whole panel left the wizard's "Try dictation" act completely
|
||||
// blank on every such install (#119/#124 follow-up, refined).
|
||||
const showScripts = assetsAvailable !== false;
|
||||
// `checking` still shows the cards: the probe resolves in well under a
|
||||
// second and flashing the install panel first would be worse than a brief
|
||||
// wait. Only a confirmed-missing model swaps them out.
|
||||
const asrMissing = readiness.phase === 'missing';
|
||||
const showScripts = assetsAvailable !== false && !asrMissing;
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -250,6 +262,18 @@ export default function DictationDemo({ embedded = false }) {
|
||||
|
||||
<audio ref={audioRef} onEnded={() => setPlayingId(null)} preload="none" />
|
||||
|
||||
{asrMissing && (
|
||||
<div className="flex flex-col gap-2 rounded-[8px] border border-border bg-[rgba(0,0,0,0.15)] px-[12px] py-[10px]">
|
||||
<p className="m-0 text-[11px] leading-[1.45] text-fg-muted">{t('asr_missing.message')}</p>
|
||||
<AsrModelChooser
|
||||
fallback={readiness.missing?.recommended}
|
||||
onInstall={readiness.install}
|
||||
onSelect={readiness.select}
|
||||
disabled={readiness.phase === 'installing'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showScripts && (
|
||||
<div className="dictation-demo__scripts grid grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-[10px]">
|
||||
{SCRIPTS.map((s) => {
|
||||
|
||||
@@ -4,6 +4,24 @@ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
|
||||
const { readiness, apiJson } = vi.hoisted(() => ({
|
||||
readiness: {
|
||||
phase: 'ready',
|
||||
missing: null,
|
||||
check: vi.fn().mockResolvedValue(true),
|
||||
install: vi.fn(),
|
||||
select: vi.fn(),
|
||||
},
|
||||
apiJson: vi.fn(),
|
||||
}));
|
||||
vi.mock('../hooks/useDictationReadiness', () => ({
|
||||
useDictationReadiness: () => readiness,
|
||||
}));
|
||||
vi.mock('../api/client', async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
apiJson: (...a) => apiJson(...a),
|
||||
}));
|
||||
|
||||
import DictationDemo from '../components/DictationDemo';
|
||||
|
||||
function withI18n(node) {
|
||||
@@ -15,6 +33,8 @@ describe('DictationDemo', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
originalFetch = global.fetch;
|
||||
readiness.phase = 'ready';
|
||||
readiness.missing = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -92,4 +112,41 @@ describe('DictationDemo', () => {
|
||||
const transcribeCall = calls.find((c) => String(c[0]).endsWith('/transcribe'));
|
||||
expect(transcribeCall[1]?.method).toBe('POST');
|
||||
});
|
||||
|
||||
// #1856: the mandatory-only install path ships no speech-to-text model, so
|
||||
// the final onboarding step opened with one hard error per script card and
|
||||
// told the user to press a hotkey that could not transcribe anything. The
|
||||
// step now offers the models instead of failing three times.
|
||||
it('offers the models instead of failing every card when none is installed', async () => {
|
||||
readiness.phase = 'missing';
|
||||
readiness.missing = { recommended: { repo_id: 'r/tiny', label: 'Tiny', size_gb: 0.1 } };
|
||||
apiJson.mockResolvedValue({
|
||||
models: [
|
||||
{
|
||||
id: 'sherpa-whisper-tiny',
|
||||
repo_id: 'r/tiny',
|
||||
label: 'Tiny',
|
||||
tag: 'offline',
|
||||
recommended: true,
|
||||
size_gb: 0.1,
|
||||
languages: '90+ languages',
|
||||
installed: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
render(withI18n(<DictationDemo />));
|
||||
|
||||
expect(await screen.findByTestId('asr-model-chooser')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Schedule a meeting with Pat/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the script cards while readiness is still being probed', () => {
|
||||
readiness.phase = 'checking';
|
||||
readiness.missing = null;
|
||||
render(withI18n(<DictationDemo />));
|
||||
|
||||
// A sub-second probe must not flash the install panel over the cards.
|
||||
expect(screen.getByText(/Schedule a meeting with Pat/)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('asr-model-chooser')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user