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
36 changes: 21 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,20 @@ precedence on NVIDIA systems.

`large-v3-turbo` is the modern speed/quality sweet spot at the top end (≈8× faster
decoding than `large-v3`); pick it if accuracy matters more than leaving the CPU idle.
Drop the file in `resources/whisper/models/` and set its filename in Settings, or pass
it to the script: `pwsh ./scripts/setup-whisper.ps1 -Model ggml-large-v3-turbo-q5_0.bin`.
Drop the file in `resources/whisper/models/`; Parley discovers installed `.bin` files
and lists each one under **Settings → Transcription → Model**. You can also pass it to
the setup script: `pwsh ./scripts/setup-whisper.ps1 -Model ggml-large-v3-turbo-q5_0.bin`.

### Or: use a remote transcription server

If you'd rather not transcribe on this machine, run a compatible server elsewhere
and set **Settings → Transcription → Remote transcription URL** (e.g.
`http://192.168.1.10:8765`). When set, Parley skips the bundled engine entirely.
If you'd rather not transcribe on this machine, run a compatible server elsewhere,
select **Settings → Transcription → Model → External server**, and enter its URL
(e.g. `http://192.168.1.10:8765`). Parley can test reachability but does not manage
the remote process. When selected, it skips local model loading entirely.

The same section lists Automatic, installed Nemotron, and every installed Whisper
model. Local selections can be started, stopped to release memory, or restarted while
no meeting is active. Starting a meeting automatically reloads a stopped local model.

> ⚠️ **Platform note:** the bundled-engine path is currently hard-coded to the Windows
> layout (`bin/Release/whisper-server.exe`). On macOS/Linux, use the **remote URL**
Expand Down Expand Up @@ -369,8 +375,8 @@ Nemotron model or Python/CUDA runtime.
3. **Settings** (gear icon): save one **LLM connection** per provider (name, base
URL, model, optional API key) — a local llama-server / LM Studio / Ollama, or a
cloud URL. Mark one **active** (★), **Test** each, and set the analysis interval
and transcription options. Switch which connection a meeting uses from the
**LLM connection dropdown in the header** (before you start the meeting).
and choose/manage the transcription model. Switch which LLM connection a meeting
uses from the **LLM connection dropdown in the header** (before you start the meeting).
4. Check the footer for the installed version and selected **Voice-to-text** model.
Local model weights begin loading when Parley opens; if the footer still says
*Loading local model…*, starting a meeting waits only for the remaining load time.
Expand Down Expand Up @@ -414,23 +420,23 @@ pre-meeting context followed by every timestamped transcript line.

- **"The local transcription engine isn't installed" on Start.** You haven't fetched
the whisper engine yet — run **`task setup:whisper`** (or `scripts/setup-whisper.ps1`),
or set a remote transcription URL in Settings. Parley shows the reason in a red banner
or select an External server in Settings. Parley shows the reason in a red banner
and writes full details to **`parley.log`** in your app-data folder (Windows:
`%AppData%\Parley\`). For a packaged build, the `resources/whisper/` folder must sit
next to the `.exe`.
- **The installer says no NVIDIA GPU, but `nvidia-smi -L` shows one.** Install
Parley **v0.1.3 or newer**. Older installers ran the 64-bit NVIDIA utility through
a redirected 32-bit shell, which could incorrectly report no GPU.
- **Nemotron was not selected on an NVIDIA system.** The footer shows the backend
Parley actually selected. Check
- **Automatic did not select Nemotron on an NVIDIA system.** The footer shows the
backend Parley actually selected. Check
`%AppData%\Parley\nemotron-server.log`. Parley requires a complete
`resources\nemotron` installation with a `.ready` marker and falls back to CPU
Whisper when the model cannot load. On an installed per-user copy, close Parley
and resume provisioning from 64-bit PowerShell (the script reuses files already
present):
Nemotron installation with a `.ready` marker and falls back to CPU Whisper when
the model cannot load. An explicitly selected Nemotron reports the failure instead
of silently switching models. On an installed per-user copy, close Parley and
resume provisioning from 64-bit PowerShell; the script reuses files already present:

```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\Programs\Parley\resources\nemotron\setup.ps1" -InstallRoot "$env:LOCALAPPDATA\Programs\Parley\resources\nemotron"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\Programs\Parley\resources\nemotron\setup.ps1" -InstallRoot "$env:LOCALAPPDATA\Parley\nemotron"
```
- **"No mic" with a mic selected.** The badge now reflects whether a microphone source
actually started. If it still says *No mic*, that device failed to open (wrong device,
Expand Down
4 changes: 3 additions & 1 deletion frontend/bindings/github.com/tomvokac/parley/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export type {
AnalysisStatusEvent,
LoadedSession,
RuntimeInfo,
StatusEvent
StatusEvent,
TranscriptionConfig,
TranscriptionModelOption
} from "./models.js";
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,15 @@ export interface Settings {
"hasAPIKey": boolean;
"captureSources": CaptureSource[] | null;

/**
* SttEngine selects how transcription is provided: "auto", "nemotron",
* "whisper", or "external". Auto preserves the original GPU-first fallback.
*/
"sttEngine": string;

/**
* SttBaseURL, when set, points transcription at a remote /inference-compatible
* server (e.g. http://host:8765) instead of launching a local engine.
* server (e.g. http://host:8765). It is used only when SttEngine is "external".
*/
"sttBaseURL": string;

Expand Down
46 changes: 46 additions & 0 deletions frontend/bindings/github.com/tomvokac/parley/meetingservice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export function AddLiveNote(scope: string, text: string): $CancellablePromise<st
return $Call.ByID(1344950533, scope, text);
}

/**
* ConfigureTranscription validates and persists a model selection, then
* reconciles the idle runtime. Local loading continues asynchronously.
*/
export function ConfigureTranscription(config: $models.TranscriptionConfig): $CancellablePromise<void> {
return $Call.ByID(713856948, config);
}

/**
* DeleteSession permanently removes a saved meeting (not the one in progress).
*/
Expand Down Expand Up @@ -85,6 +93,14 @@ export function ListSessions(): $CancellablePromise<store$0.Session[] | null> {
return $Call.ByID(1774945289);
}

/**
* ListTranscriptionModels discovers the selectable local installations and the
* always-available external-server choice.
*/
export function ListTranscriptionModels(): $CancellablePromise<$models.TranscriptionModelOption[] | null> {
return $Call.ByID(1026157818);
}

/**
* LoadSession returns a saved meeting's full state for display.
*/
Expand All @@ -99,6 +115,13 @@ export function RenameSession(id: number, title: string): $CancellablePromise<vo
return $Call.ByID(3925232594, id, title);
}

/**
* RestartTranscriptionModel replaces the current local process with a fresh load.
*/
export function RestartTranscriptionModel(): $CancellablePromise<void> {
return $Call.ByID(3040477430);
}

/**
* Resume continues a previously saved meeting, appending new transcript/analysis
* to it. Pass the session id returned by ListSessions.
Expand All @@ -115,9 +138,32 @@ export function Start(): $CancellablePromise<void> {
return $Call.ByID(4124033072);
}

/**
* StartTranscriptionModel loads the selected local model without starting a meeting.
*/
export function StartTranscriptionModel(): $CancellablePromise<void> {
return $Call.ByID(2331669113);
}

/**
* Stop ends the session, flushing the final audio and closing recordings.
*/
export function Stop(): $CancellablePromise<void> {
return $Call.ByID(252224180);
}

/**
* StopTranscriptionModel releases the selected local model while Parley is idle.
*/
export function StopTranscriptionModel(): $CancellablePromise<void> {
return $Call.ByID(207813981);
}

/**
* TestExternalTranscription verifies that an external HTTP server is reachable.
* Any HTTP response counts as reachable; the inference path is exercised by the
* first real transcription request.
*/
export function TestExternalTranscription(baseURL: string): $CancellablePromise<void> {
return $Call.ByID(1170675977, baseURL);
}
34 changes: 33 additions & 1 deletion frontend/bindings/github.com/tomvokac/parley/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,18 @@ export interface LoadedSession {
export interface RuntimeInfo {
"appVersion": string;
"transcriptionModel": string;
"transcriptionModelID": string;

/**
* loading | ready | error
* local | external
*/
"transcriptionKind": string;

/**
* stopped | loading | ready | configured | error
*/
"transcriptionStatus": string;
"transcriptionMessage": string;
}

/**
Expand All @@ -61,3 +68,28 @@ export interface StatusEvent {
"micAvailable": boolean;
"activeSources": string[] | null;
}

/**
* TranscriptionConfig is the user-facing selection saved by the model manager.
* ModelID is one of auto, nemotron, whisper:<filename>, or external.
*/
export interface TranscriptionConfig {
"modelID": string;
"externalURL": string;
}

/**
* TranscriptionModelOption describes one selectable transcription provider.
*/
export interface TranscriptionModelOption {
"id": string;
"label": string;

/**
* automatic | local | external
*/
"kind": string;
"detail": string;
"available": boolean;
"unavailableReason": string;
}
23 changes: 21 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Events } from "@wailsio/runtime";
import {
AlertTriangle,
AudioLines,
CheckCircle2,
Download,
History,
Loader2,
Expand Down Expand Up @@ -152,7 +153,10 @@ function App() {
const [runtimeInfo, setRuntimeInfo] = useState<RuntimeInfo>({
appVersion: "",
transcriptionModel: "Loading local model…",
transcriptionModelID: "auto",
transcriptionKind: "local",
transcriptionStatus: "loading",
transcriptionMessage: "Loading the selected transcription model…",
});
// Session-scoped suggestion pins/dismissals, keyed by normalized text. The
// `analysis` listener is registered once, so it reads these via refs to avoid a
Expand Down Expand Up @@ -265,6 +269,7 @@ function App() {
...info,
transcriptionModel: "Model status unavailable",
transcriptionStatus: "error",
transcriptionMessage: "Parley could not read the transcription model status.",
}))
);

Expand Down Expand Up @@ -708,11 +713,20 @@ function App() {
</span>
<span
className="flex min-w-0 items-center gap-1.5"
title={`Voice-to-text model: ${runtimeInfo.transcriptionModel}`}
title={`Voice-to-text model: ${runtimeInfo.transcriptionModel}${runtimeInfo.transcriptionMessage ? ` — ${runtimeInfo.transcriptionMessage}` : ""}`}
>
{runtimeInfo.transcriptionStatus === "loading" && (
<Loader2 className="h-3 w-3 shrink-0 animate-spin" />
)}
{(runtimeInfo.transcriptionStatus === "ready" || runtimeInfo.transcriptionStatus === "configured") && (
<CheckCircle2 className="h-3 w-3 shrink-0 text-emerald-400" />
)}
{runtimeInfo.transcriptionStatus === "error" && (
<AlertTriangle className="h-3 w-3 shrink-0 text-destructive" />
)}
{runtimeInfo.transcriptionStatus === "stopped" && (
<Square className="h-2.5 w-2.5 shrink-0" />
)}
<span className="shrink-0">Voice-to-text:</span>
<span className="truncate text-foreground/75">
{runtimeInfo.transcriptionModel}
Expand All @@ -721,7 +735,12 @@ function App() {
</footer>

<AudioDialog open={audioOpen} onOpenChange={setAudioOpen} />
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
<SettingsDialog
open={settingsOpen}
onOpenChange={setSettingsOpen}
meetingActive={active}
runtimeInfo={runtimeInfo}
/>
<ContextDialog open={contextOpen} onOpenChange={setContextOpen} />
<SessionsDialog
open={sessionsOpen}
Expand Down
Loading
Loading