Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 29 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,5 +236,33 @@
"calendar_weekday_th": "Th",
"calendar_weekday_fr": "Fr",
"calendar_weekday_sa": "Sa",
"calendar_weekday_su": "Su"
"calendar_weekday_su": "Su",

"wizard_welcome_title": "Welcome to Birda GUI",
"wizard_welcome_subtitle": "Let's get you set up in a few quick steps.",
"wizard_welcome_getStarted": "Get Started",
"wizard_welcome_skip": "Skip setup",

"wizard_cli_title": "birda CLI",
"wizard_cli_subtitle": "Birda GUI needs the birda command-line tool to analyze audio files.",
"wizard_cli_checking": "Checking for birda...",
"wizard_cli_found": "birda found at {path}",
"wizard_cli_notFound": "birda was not found on your system.",
"wizard_cli_notFoundHint": "birda should have been installed automatically. You can set the path manually or download it.",
"wizard_cli_download": "Download birda",
"wizard_cli_setPath": "Set path manually",

"wizard_model_title": "Install a Model",
"wizard_model_subtitle": "You need at least one model to analyze bird sounds. Choose one to install.",
"wizard_model_installing": "Installing {name}...",
"wizard_model_installed": "installed",
"wizard_model_hasModels": "You have {count} model(s) installed. You can install more later from Settings.",

"wizard_language_title": "Species Language",
"wizard_language_subtitle": "Choose the language for bird species names shown in detection results.",

"wizard_finish": "Finish Setup",
"wizard_next": "Next",
"wizard_back": "Back",
"wizard_stepOf": "Step {current} of {total}"
}
1 change: 1 addition & 0 deletions shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export interface AppSettings {
species_language: string;
ui_language: string;
theme: 'system' | 'light' | 'dark';
setup_completed: boolean;
}

// === Catalog Stats ===
Expand Down
28 changes: 25 additions & 3 deletions src/main/birda/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function listAvailable(): Promise<AvailableModel[]> {
return payload.models ?? [];
}

export async function installModel(name: string): Promise<string> {
export async function installModel(name: string, onProgress?: (line: string) => void): Promise<string> {
const birdaPath = await findBirda();
return new Promise((resolve, reject) => {
const proc = spawn(birdaPath, ['models', 'install', name], {
Expand All @@ -47,13 +47,31 @@ export async function installModel(name: string): Promise<string> {

let stdout = '';
let stderr = '';
let stdoutRemainder = '';
let stderrRemainder = '';

const reportProgress = (chunk: string, remainder: string): string => {
if (!onProgress) return remainder + chunk;
const text = remainder + chunk;
const parts = text.split('\n');
const newRemainder = parts.pop() ?? '';
for (const line of parts) {
const trimmed = line.trim();
if (trimmed) onProgress(trimmed);
}
return newRemainder;
};

proc.stdout.on('data', (data: Buffer) => {
stdout += data.toString();
const text = data.toString();
stdout += text;
stdoutRemainder = reportProgress(text, stdoutRemainder);
});

proc.stderr.on('data', (data: Buffer) => {
stderr += data.toString();
const text = data.toString();
stderr += text;
stderrRemainder = reportProgress(text, stderrRemainder);
});

// Auto-accept the license prompt; the GUI shows its own acceptance dialog
Expand All @@ -62,6 +80,10 @@ export async function installModel(name: string): Promise<string> {
proc.stdin.end();

proc.on('close', (code) => {
if (onProgress) {
if (stdoutRemainder.trim()) onProgress(stdoutRemainder.trim());
if (stderrRemainder.trim()) onProgress(stderrRemainder.trim());
}
if (code !== 0) {
reject(new Error(`Model install failed: ${stderr || stdout}`));
} else {
Expand Down
5 changes: 5 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@
{
label: 'Help',
submenu: [
{
label: 'Setup Wizard...',
click: () => mainWindow?.webContents.send('menu:setup-wizard'),
},
{ type: 'separator' },
{
label: 'About Birda GUI',
click: () => {
Expand Down Expand Up @@ -193,7 +198,7 @@
// Read saved language preference
let language = 'en';
try {
const settingsRaw = await fs.promises.readFile(

Check warning on line 201 in src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Type Check & Build

Found readFile from package "fs" with non literal argument at index 0
path.join(app.getPath('userData'), 'birda-gui-settings.json'),
'utf-8',
);
Expand Down
9 changes: 7 additions & 2 deletions src/main/ipc/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ export function registerModelHandlers(): void {
return listAvailable();
});

ipcMain.handle('birda:models-install', async (_event, name: string) => {
return installModel(name);
ipcMain.handle('birda:models-install', async (event, name: string) => {
const sender = event.sender;
return installModel(name, (line) => {
if (!sender.isDestroyed()) {
sender.send('birda:models-install-progress', line);
}
});
});

ipcMain.handle('birda:models-info', async (_event, name: string) => {
Expand Down
1 change: 1 addition & 0 deletions src/main/ipc/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ async function loadSettings(): Promise<AppSettings> {
species_language: 'en',
ui_language: 'en',
theme: 'system',
setup_completed: false,
};

try {
Expand Down
2 changes: 2 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ const ALLOWED_INVOKE_CHANNELS = new Set([

const ALLOWED_RECEIVE_CHANNELS = new Set([
'birda:analysis-progress',
'birda:models-install-progress',
'app:log',
'menu:open-file',
'menu:open-folder',
'menu:switch-tab',
'menu:focus-search',
'menu:toggle-log',
'menu:setup-wizard',
]);

contextBridge.exposeInMainWorld('birda', {
Expand Down
77 changes: 58 additions & 19 deletions src/renderer/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import StatusBar from '$lib/components/StatusBar.svelte';
import ProgressPanel from '$lib/components/ProgressPanel.svelte';
import LogPanel from '$lib/components/LogPanel.svelte';
import SetupWizard from '$lib/components/SetupWizard.svelte';
import AnalysisPage from './pages/AnalysisPage.svelte';
import DetectionsPage from './pages/DetectionsPage.svelte';
import MapPage from './pages/MapPage.svelte';
Expand All @@ -25,11 +26,33 @@
offAnalysisProgress,
onLog,
offLog,
onSetupWizard,
offSetupWizard,
} from '$lib/utils/ipc';
import { setupMenuListeners } from '$lib/utils/shortcuts';
import { onMount, onDestroy } from 'svelte';

let cleanupMenu: (() => void) | null = null;
let showWizard = $state<boolean | null>(null); // null = loading, true/false = resolved

async function handleWizardComplete() {
try {
const settings = await getSettings();
appState.theme = settings.theme;
appState.analysisConfidence = settings.default_confidence;
if (settings.default_model) {
appState.selectedModel = settings.default_model;
}
} catch {
// proceed with existing state
}
try {
appState.catalogStats = await getCatalogStats();
} catch {
// DB may not be ready
}
showWizard = false;
}

async function handleStop() {
try {
Expand Down Expand Up @@ -103,8 +126,10 @@
if (settings.default_model) {
appState.selectedModel = settings.default_model;
}
showWizard = !settings.setup_completed;
} catch {
// Failed to load settings
// Failed to load settings — show wizard as fallback
showWizard = true;
}

try {
Expand All @@ -114,6 +139,10 @@
}
})();

onSetupWizard(() => {
showWizard = true;
});

cleanupMenu = setupMenuListeners({
onOpenFile: (path: string) => {
appState.sourcePath = path;
Expand Down Expand Up @@ -144,28 +173,38 @@
onDestroy(() => {
offAnalysisProgress();
offLog();
offSetupWizard();
cleanupMenu?.();
});
</script>

<main class="bg-base-100 text-base-content flex h-screen select-none">
<Sidebar />
{#if showWizard === null}
<!-- Loading settings, show nothing -->
<main class="bg-base-100 flex h-screen select-none"></main>
{:else if showWizard}
<main class="bg-base-100 text-base-content h-screen select-none">
<SetupWizard oncomplete={handleWizardComplete} />
</main>
{:else}
<main class="bg-base-100 text-base-content flex h-screen select-none">
<Sidebar />

<div class="flex flex-1 flex-col overflow-hidden">
<div class="flex flex-1 flex-col overflow-hidden">
{#if appState.activeTab === 'analysis'}
<AnalysisPage onstart={handleStartAnalysis} onstop={handleStop} />
{:else if appState.activeTab === 'detections'}
<DetectionsPage />
{:else if appState.activeTab === 'map'}
<MapPage />
{:else if appState.activeTab === 'settings'}
<SettingsPage />
{/if}
<div class="flex flex-1 flex-col overflow-hidden">
{#if appState.activeTab === 'analysis'}
<AnalysisPage onstart={handleStartAnalysis} onstop={handleStop} />
{:else if appState.activeTab === 'detections'}
<DetectionsPage />
{:else if appState.activeTab === 'map'}
<MapPage />
{:else if appState.activeTab === 'settings'}
<SettingsPage />
{/if}
</div>

<ProgressPanel />
<LogPanel />
<StatusBar />
</div>

<ProgressPanel />
<LogPanel />
<StatusBar />
</div>
</main>
</main>
{/if}
1 change: 1 addition & 0 deletions src/renderer/src/lib/components/SettingsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
species_language: 'en',
ui_language: 'en',
theme: 'system',
setup_completed: true,
});

const freqOptions = [
Expand Down
Loading
Loading