Compare commits

..

1 Commits

Author SHA1 Message Date
Gitea Actions
76d6aa4d3e chore: bump version to 2.0.19 [skip ci] 2026-04-11 03:09:49 +00:00
15 changed files with 57 additions and 106 deletions

View File

@@ -73,15 +73,8 @@ class APIServer:
original_state_cb = self.controller.on_state_changed
def on_state_changed(state: str, message: str):
# Isolate the upstream callback so a failure there (e.g. a
# broken stdout pipe in main_headless) cannot propagate into
# _set_state and tear down engine init / reload_engine /
# apply_settings request handling.
if original_state_cb:
try:
original_state_cb(state, message)
except Exception:
pass
self._broadcast_control({"type": "state_changed", "state": state, "message": message})
self.controller.on_state_changed = on_state_changed
@@ -280,7 +273,6 @@ class APIServer:
data = resp.json()
ctrl.config.set('remote.auth_token', data.get('token', ''))
ctrl.config.set('remote.server_url', req.server_url)
ctrl.config.set('remote.email', req.email)
return {"status": "ok", "token": data.get('token', '')}
else:
raise HTTPException(status_code=resp.status_code, detail=resp.text)

View File

@@ -276,6 +276,7 @@ class AppController:
self.current_model_size = model
self.current_device_config = device_config
user_name = self.config.get('user.name', 'User')
continuous_mode = self.config.get('transcription.continuous_mode', False)
if continuous_mode:
@@ -292,6 +293,7 @@ class AppController:
if remote_mode in ('managed', 'byok'):
self.transcription_engine = DeepgramTranscriptionEngine(
config=self.config,
user_name=user_name,
input_device_index=audio_device,
)
self.transcription_engine.set_callbacks(
@@ -341,7 +343,7 @@ class AppController:
initial_prompt=self.config.get('transcription.initial_prompt', ''),
no_log_file=self.config.get('transcription.no_log_file', True),
input_device_index=audio_device,
app_config=self.config,
user_name=user_name,
)
self.transcription_engine.set_callbacks(
realtime_callback=self._on_realtime_transcription,

View File

@@ -75,16 +75,10 @@ def main():
# Create controller and initialize
controller = AppController(config=config)
# Wire a state callback that prints state events for the parent
# process to read. Stdout writes can fail with EINVAL on Windows
# when the parent stops reading the sidecar pipe; swallow those
# so the engine state machine isn't taken down by a logging path.
# Wire a state callback that prints the ready event
def on_state_changed(state, message):
event = {"event": "state", "state": state, "message": message}
try:
print(json.dumps(event), flush=True)
except (OSError, ValueError):
pass
controller.on_state_changed = on_state_changed

View File

@@ -36,16 +36,18 @@ class DeepgramTranscriptionEngine:
# Construction / configuration
# ------------------------------------------------------------------ #
def __init__(self, config, input_device_index: Optional[int] = None):
def __init__(self, config, user_name: str = "User", input_device_index: Optional[int] = None):
"""
Initialise the engine from a :class:`client.config.Config` object.
Args:
config: Application ``Config`` instance.
user_name: Display name attached to transcriptions.
input_device_index: Index of the audio input device to use
(``None`` for the system default).
"""
self.config = config
self.user_name = user_name
self.input_device_index = input_device_index
# Mode: 'managed' (proxy) or 'byok' (direct Deepgram)
@@ -452,7 +454,7 @@ class DeepgramTranscriptionEngine:
text=text,
is_final=is_final,
timestamp=datetime.now(),
user_name=self.config.get('user.name', 'User'),
user_name=self.user_name,
)
if is_final:
if self.final_callback:
@@ -503,7 +505,7 @@ class DeepgramTranscriptionEngine:
text=transcript,
is_final=is_final,
timestamp=datetime.now(),
user_name=self.config.get('user.name', 'User'),
user_name=self.user_name,
)
if is_final:
if self.final_callback:
@@ -534,6 +536,10 @@ class DeepgramTranscriptionEngine:
pass
self._ws = None
def set_user_name(self, user_name: str):
"""Update the user name attached to future transcriptions."""
self.user_name = user_name
def is_recording_active(self) -> bool:
"""Return ``True`` if audio is currently being captured."""
return self._is_recording

View File

@@ -58,8 +58,8 @@ class RealtimeTranscriptionEngine:
no_log_file: bool = True,
# Audio device
input_device_index: Optional[int] = None,
# App config (for reading user.name at transcription time)
app_config=None
# User name
user_name: str = ""
):
"""
Initialize RealtimeSTT transcription engine.
@@ -82,7 +82,7 @@ class RealtimeTranscriptionEngine:
initial_prompt: Optional prompt to guide transcription
no_log_file: Disable RealtimeSTT logging
input_device_index: Audio input device index
app_config: App Config object for reading user.name dynamically
user_name: User name for transcriptions
"""
self.model = model
self.language = language
@@ -100,7 +100,7 @@ class RealtimeTranscriptionEngine:
self.enable_realtime = enable_realtime_transcription
self.realtime_model = realtime_model
self.realtime_processing_pause = realtime_processing_pause
self.app_config = app_config
self.user_name = user_name
# Callbacks
self.realtime_callback: Optional[Callable[[TranscriptionResult], None]] = None
@@ -162,11 +162,6 @@ class RealtimeTranscriptionEngine:
self.realtime_callback = realtime_callback
self.final_callback = final_callback
def _get_user_name(self) -> str:
if self.app_config:
return self.app_config.get('user.name', '')
return ''
def _on_realtime_transcription(self, text: str):
"""Internal callback for realtime transcriptions."""
if self.realtime_callback and text.strip():
@@ -174,7 +169,7 @@ class RealtimeTranscriptionEngine:
text=text,
is_final=False,
timestamp=datetime.now(),
user_name=self._get_user_name()
user_name=self.user_name
)
self.realtime_callback(result)
@@ -185,7 +180,7 @@ class RealtimeTranscriptionEngine:
text=text,
is_final=True,
timestamp=datetime.now(),
user_name=self._get_user_name()
user_name=self.user_name
)
self.final_callback(result)
@@ -411,6 +406,10 @@ class RealtimeTranscriptionEngine:
if self.is_recording:
print("VAD settings updated. Restart transcription to apply changes.")
def set_user_name(self, user_name: str):
"""Set the user name for transcriptions."""
self.user_name = user_name
def __repr__(self) -> str:
return f"RealtimeTranscriptionEngine(model={self.model}, device={self.device}, running={self.is_recording})"

View File

@@ -72,7 +72,6 @@ remote:
mode: byok # local | managed | byok
server_url: "https://transcribe.shadowdao.com" # Proxy server URL for managed mode
auth_token: "" # JWT stored after login (managed mode)
email: "" # Email of the logged-in managed-mode account (for UI display)
byok_api_key: "" # Deepgram API key for BYOK mode
deepgram_model: nova-2 # Deepgram model to use
language: en-US # Language code

View File

@@ -401,6 +401,7 @@ class MainWindow(QMainWindow):
# Use Deepgram-based remote transcription
self.transcription_engine = DeepgramTranscriptionEngine(
config=self.config,
user_name=user_name,
input_device_index=audio_device
)
self.transcription_engine.set_callbacks(
@@ -430,7 +431,7 @@ class MainWindow(QMainWindow):
initial_prompt=self.config.get('transcription.initial_prompt', ''),
no_log_file=self.config.get('transcription.no_log_file', True),
input_device_index=audio_device,
app_config=self.config
user_name=user_name
)
# Set up callbacks for transcription results

View File

@@ -90,7 +90,7 @@ class TranscriptionCLI:
initial_prompt=self.config.get('transcription.initial_prompt', ''),
no_log_file=True,
input_device_index=audio_device,
app_config=self.config
user_name=user_name
)
# Set up callbacks

View File

@@ -1,7 +1,7 @@
{
"name": "local-transcription",
"private": true,
"version": "2.0.20",
"version": "2.0.19",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@@ -1,6 +1,6 @@
[project]
name = "local-transcription"
version = "1.0.15"
version = "1.0.12"
description = "A standalone desktop application for real-time speech-to-text transcription using Whisper models"
readme = "README.md"
requires-python = ">=3.9"

View File

@@ -1,6 +1,6 @@
[package]
name = "local-transcription"
version = "2.0.20"
version = "2.0.19"
description = "Real-time speech-to-text transcription for streamers"
authors = ["Local Transcription Contributors"]
edition = "2021"

View File

@@ -1,6 +1,6 @@
{
"productName": "Local Transcription",
"version": "2.0.20",
"version": "2.0.19",
"identifier": "net.anhonesthost.local-transcription",
"build": {
"frontendDist": "../dist",

View File

@@ -44,7 +44,6 @@
let byokApiKey = $state("");
let managedEmail = $state("");
let managedPassword = $state("");
let managedLoggedIn = $state(false);
let autoCheckUpdates = $state(true);
let isCloudMode = $derived(remoteMode === "managed" || remoteMode === "byok");
@@ -132,8 +131,6 @@
remoteMode = cfg.remote.mode;
remoteServerUrl = cfg.remote.server_url;
byokApiKey = cfg.remote.byok_api_key ?? "";
managedEmail = cfg.remote.email ?? "";
managedLoggedIn = !!(cfg.remote.auth_token && cfg.remote.email);
autoCheckUpdates = cfg.updates.auto_check;
});
@@ -260,37 +257,15 @@
const MANAGED_SERVER_URL = "https://transcribe.shadowdao.com";
let loginMessage = $state("");
async function handleManagedLogin() {
loginMessage = "";
try {
await backendStore.apiPost("/api/login", {
email: managedEmail,
password: managedPassword,
server_url: remoteServerUrl || MANAGED_SERVER_URL,
});
loginMessage = "Logged in successfully!";
managedPassword = "";
managedLoggedIn = true;
await configStore.fetchConfig();
} catch (err) {
console.error("Login failed:", err);
loginMessage = "Login failed. Check your email and password.";
}
}
async function handleManagedLogout() {
try {
await configStore.updateConfig({
remote: { auth_token: "", email: "" },
});
managedLoggedIn = false;
managedPassword = "";
loginMessage = "";
} catch (err) {
console.error("Logout failed:", err);
loginMessage = `Error: ${err}`;
}
}
@@ -509,15 +484,6 @@
{/if}
{#if remoteMode === "managed"}
<div class="managed-auth">
{#if managedLoggedIn}
<p style="font-size: 13px; margin: 0 0 8px;">
<span style="color: var(--accent-green, #4CAF50);">✓ Logged in</span>
as <strong>{managedEmail}</strong>
</p>
<div class="auth-buttons">
<button onclick={handleManagedLogout}>Log out</button>
</div>
{:else}
<div class="field">
<label for="managed-email">Email</label>
<input
@@ -541,12 +507,6 @@
<p style="font-size: 11px; color: var(--text-muted); margin-top: 8px;">
Don't have an account? <a href="https://transcribe.shadowdao.com/register.html" target="_blank" rel="noopener" style="color: var(--accent-blue);">Sign up here</a>
</p>
{/if}
{#if loginMessage}
<p style="font-size: 12px; margin-top: 6px; color: {loginMessage.startsWith('Logged') ? 'var(--accent-green, #4CAF50)' : 'var(--accent-red, #f44336)'};">
{loginMessage}
</p>
{/if}
</div>
{/if}
</section>

View File

@@ -65,7 +65,6 @@ export interface AppConfig {
mode: string;
server_url: string;
auth_token: string;
email: string;
byok_api_key: string;
deepgram_model: string;
language: string;
@@ -132,7 +131,6 @@ function getDefaultConfig(): AppConfig {
mode: "byok",
server_url: "",
auth_token: "",
email: "",
byok_api_key: "",
deepgram_model: "nova-2",
language: "en-US",

View File

@@ -1,7 +1,7 @@
"""Version information for Local Transcription."""
__version__ = "2.0.20"
__version_info__ = (2, 0, 20)
__version__ = "2.0.19"
__version_info__ = (2, 0, 19)
# Version history:
# 1.4.0 - Auto-update feature: