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
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Build OratioText

on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master ]
branches: [ develop ]
# pull_request:
# branches: [ master ]

jobs:
build-macos-arm64:
Expand Down
3 changes: 1 addition & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
- [x] Add a progress bar for the download of the Whisper model (with cancel option)
- [x] Add an icon
- [x] Build for Windows and macOS (see [BUILD.md](./BUILD.md))
- [ ] Add a help menu
- [x] Add language selection option (or detect automatically)
- [ ] Add an about menu (feedback, donate, etc)
- [ ] Build for Linux
- [ ] Add language selection option (or detect automatically)
- [ ] Add history of transcriptions
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "oratiotext"
version = "1.0.0"
version = "1.0.1"
description = "A cross-platform desktop application for converting speech to text using Whisper"
authors = ["kylethedeveloper"]
edition = "2021"
Expand Down
8 changes: 8 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ struct ModelInfo {
downloaded: bool,
}

#[tauri::command]
fn get_app_version(app: tauri::AppHandle) -> String {
app.package_info().version.to_string()
}

#[tauri::command]
fn get_system_info() -> SystemInfo {
use sysinfo::System;
Expand Down Expand Up @@ -94,6 +99,7 @@ async fn download_model(
async fn transcribe(
file_path: String,
model_name: String,
language: String,
state: State<'_, AppState>,
app: tauri::AppHandle,
) -> Result<TranscriptionResult, String> {
Expand Down Expand Up @@ -126,6 +132,7 @@ async fn transcribe(
let result = t
.transcribe_with_progress(
&file_path,
Some(language),
move |stage, percent| {
let _ = app_for_callback.emit(
"transcription-progress",
Expand Down Expand Up @@ -183,6 +190,7 @@ pub fn run() {
transcribe,
stop_transcription,
save_file,
get_app_version,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
11 changes: 10 additions & 1 deletion src-tauri/src/transcriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl Transcriber {
pub fn transcribe_with_progress<F>(
&self,
audio_path: &str,
language: Option<String>,
on_progress: F,
cancel_flag: Arc<AtomicBool>,
) -> Result<TranscriptionResult, Box<dyn std::error::Error>>
Expand Down Expand Up @@ -80,7 +81,15 @@ impl Transcriber {

// Configure whisper parameters
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
params.set_language(None);
if let Some(ref lang) = language {
if lang == "auto" {
params.set_language(Some("auto"));
} else {
params.set_language(Some(lang.as_str()));
}
} else {
params.set_language(Some("auto"));
}
params.set_print_progress(false);
params.set_print_realtime(false);
params.set_print_timestamps(false);
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
"productName": "OratioText",
"version": "1.0.0",
"version": "1.0.1",
"identifier": "com.oratiotext.app",
"build": {
"frontendDist": "../src"
Expand Down
88 changes: 84 additions & 4 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OratioText</title>
<link rel="stylesheet" href="styles.css" />
</head>

<body>
<div class="app">
<header class="app-header">
<button id="menu-toggle" class="theme-toggle" style="left: 0; right: auto;" title="Menu">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4 6l16 0" />
<path d="M4 12l16 0" />
<path d="M4 18l16 0" />
</svg>
</button>

<div id="nav-dropdown" class="nav-dropdown hidden">
<button class="nav-item active" data-page="home">Home</button>
<button class="nav-item" data-page="history">History</button>
<button class="nav-item" data-page="about">About</button>
</div>

<button id="theme-toggle" class="theme-toggle" title="Toggle light/dark theme">
<svg id="icon-moon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008" /></svg>
<svg id="icon-sun" class="hidden" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" /></svg>
<svg id="icon-moon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008" />
</svg>
<svg id="icon-sun" class="hidden" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />
<path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" />
</svg>
</button>
<div class="logo-area">
<img src="appicon.png" alt="OratioText" class="app-icon" />
Expand All @@ -20,7 +47,7 @@ <h1>OratioText</h1>
<p class="tagline">Speech to Text — runs entirely on your computer</p>
</header>

<main class="app-main">
<main class="app-main" id="page-home">
<!-- File Selection -->
<section class="card file-section">
<label class="section-label">Audio / Video File</label>
Expand Down Expand Up @@ -48,6 +75,39 @@ <h1>OratioText</h1>

<!-- Generate / Stop -->
<section class="action-section">
<div class="model-picker">
<select id="language-select">
<option value="auto" selected>Auto-Detect</option>
<option value="ar">Arabic</option>
<option value="zh">Chinese</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="en">English</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hi">Hindi</option>
<option value="hu">Hungarian</option>
<option value="id">Indonesian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="no">Norwegian</option>
<option value="pl">Polish</option>
<option value="pt">Portuguese</option>
<option value="ro">Romanian</option>
<option value="ru">Russian</option>
<option value="es">Spanish</option>
<option value="sv">Swedish</option>
<option value="th">Thai</option>
<option value="tr">Turkish</option>
<option value="uk">Ukrainian</option>
<option value="vi">Vietnamese</option>
</select>
</div>
<button id="generate-btn" class="btn btn-primary" disabled>
Generate Transcription
</button>
Expand Down Expand Up @@ -86,8 +146,28 @@ <h1>OratioText</h1>
<span id="language-text"></span>
</div>
</main>

<main class="app-main hidden" id="page-history">
<section class="card">
<label class="section-label">History</label>
<p class="placeholder-text">History is empty for now.</p>
</section>
</main>

<main class="app-main hidden" id="page-about">
<section class="card about-section">
<p id="app-version" style="color: var(--color-text-muted); font-size: 0.9em; margin-bottom: 16px;">Loading
version...</p>
<p style="margin-bottom: 24px;">OratioText is a speech to text application that runs entirely on your local
machine, ensuring
full privacy.<br />It uses the whisper.cpp models under the hood.</p>
<a href="https://github.com/kylethedeveloper/OratioText" target="_blank" class="github-link"
style="color: var(--color-primary); text-decoration: none; font-weight: 600;">GitHub Repository</a>
</section>
</main>
</div>

<script src="main.js"></script>
</body>
</html>

</html>
56 changes: 56 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const downloadProgress = document.getElementById("download-progress");
const progressFill = document.getElementById("progress-fill");
const progressText = document.getElementById("progress-text");
const modelInfo = document.getElementById("model-info");
const languageSelect = document.getElementById("language-select");
const generateBtn = document.getElementById("generate-btn");
const stopBtn = document.getElementById("stop-btn");
const timestampsToggle = document.getElementById("timestamps-toggle");
Expand All @@ -35,15 +36,38 @@ const statusText = document.getElementById("status-text");
const languageInfo = document.getElementById("language-info");
const languageText = document.getElementById("language-text");

// ---- Navigation Elements --------------------------------------------------
const menuToggle = document.getElementById("menu-toggle");
const navDropdown = document.getElementById("nav-dropdown");
const navItems = document.querySelectorAll(".nav-item");
const pages = {
home: document.getElementById("page-home"),
history: document.getElementById("page-history"),
about: document.getElementById("page-about"),
};

// ---- Initialization -------------------------------------------------------

document.addEventListener("DOMContentLoaded", async () => {
await loadModels();
await loadAppVersion();
setupEventListeners();
setupDownloadProgressListener();
setupTranscriptionProgressListener();
});

async function loadAppVersion() {
try {
const version = await invoke("get_app_version");
const appVersionEl = document.getElementById("app-version");
if (appVersionEl) {
appVersionEl.textContent = `Version ${version}`;
}
} catch (err) {
console.error("Failed to load app version:", err);
}
}

async function loadModels() {
try {
const [models, sysInfo] = await Promise.all([
Expand Down Expand Up @@ -99,6 +123,37 @@ function setupEventListeners() {
saveBtn.addEventListener("click", saveTranscription);
timestampsToggle.addEventListener("change", updateTranscriptionDisplay);
modelSelect.addEventListener("change", updateModelUI);

// Navigation events
menuToggle.addEventListener("click", () => {
navDropdown.classList.toggle("hidden");
});

document.addEventListener("click", (e) => {
if (!menuToggle.contains(e.target) && !navDropdown.contains(e.target)) {
navDropdown.classList.add("hidden");
}
});

navItems.forEach((btn) => {
btn.addEventListener("click", () => {
const targetPage = btn.dataset.page;
navDropdown.classList.add("hidden");

// Update active button
navItems.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");

// Show target page, hide others
Object.keys(pages).forEach((page) => {
if (page === targetPage) {
pages[page].classList.remove("hidden");
} else {
pages[page].classList.add("hidden");
}
});
});
});
}

function setupDownloadProgressListener() {
Expand Down Expand Up @@ -217,6 +272,7 @@ async function startTranscription() {
transcriptionResult = await invoke("transcribe", {
filePath: selectedFilePath,
modelName,
language: languageSelect.value
});

updateTranscriptionDisplay();
Expand Down
Loading
Loading