feat(calendar): add runtime STT URL injection for production

- Add PUBLIC_STT_URL to hooks.server.ts runtime injection
- Update stt.ts to use runtime-injected URL with fallback
- Update .env.development to use production STT URL
- Update generate-env.mjs with STT URL mapping

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-28 15:25:39 +01:00
parent 3ff8d3833b
commit 7138236046
7 changed files with 78 additions and 6 deletions

View file

@ -222,6 +222,11 @@ CONTACTS_GOOGLE_REDIRECT_URI=http://localhost:5184/import?tab=google
CALENDAR_BACKEND_PORT=3014
CALENDAR_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/calendar
# Speech-to-Text Service (mana-stt)
# Production: https://stt-api.mana.how
# Local dev: http://localhost:3020
STT_URL=https://stt-api.mana.how
# ============================================
# STORAGE PROJECT (Cloud Drive)
# ============================================

View file

@ -11,6 +11,7 @@ const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
const PUBLIC_BACKEND_URL_CLIENT =
process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
const PUBLIC_STT_URL = process.env.PUBLIC_STT_URL || 'https://stt-api.mana.how';
export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
@ -20,6 +21,7 @@ export const handle: Handle = async ({ event, resolve }) => {
const envScript = `<script>
window.__PUBLIC_MANA_CORE_AUTH_URL__ = "${PUBLIC_MANA_CORE_AUTH_URL_CLIENT}";
window.__PUBLIC_BACKEND_URL__ = "${PUBLIC_BACKEND_URL_CLIENT}";
window.__PUBLIC_STT_URL__ = "${PUBLIC_STT_URL}";
</script>`;
return html.replace('<head>', `<head>${envScript}`);
},

View file

@ -4,7 +4,7 @@
import { authStore } from '$lib/stores/auth.svelte';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import type { TimeFormat, AllDayDisplayMode } from '$lib/stores/settings.svelte';
import type { TimeFormat, AllDayDisplayMode, SttLanguage } from '$lib/stores/settings.svelte';
import { calendarsStore } from '$lib/stores/calendars.svelte';
import { toast } from '$lib/stores/toast.svelte';
import {
@ -620,6 +620,48 @@
</SettingsCard>
</SettingsSection>
<!-- Spracheingabe -->
<SettingsSection title="Spracheingabe">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"
/>
</svg>
{/snippet}
<SettingsCard>
<div class="p-4 space-y-3">
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Sprache der Spracherkennung</span>
<span class="setting-description"
>Sprache für die Transkription von Sprachaufnahmen</span
>
</div>
<div class="button-group">
<button
class="group-button"
class:active={settingsStore.sttLanguage === 'de'}
onclick={() => settingsStore.set('sttLanguage', 'de' as SttLanguage)}
>
Deutsch
</button>
<button
class="group-button"
class:active={settingsStore.sttLanguage === 'auto'}
onclick={() => settingsStore.set('sttLanguage', 'auto' as SttLanguage)}
>
Automatisch
</button>
</div>
</div>
</div>
</SettingsCard>
</SettingsSection>
<!-- Geburtstage -->
<SettingsSection title="Geburtstage">
{#snippet icon()}

View file

@ -7,11 +7,18 @@
import { browser } from '$app/environment';
/**
* STT service URL - defaults to localhost for development
* STT service URL - uses runtime injection for production, env var for dev
*/
const STT_URL = browser
? import.meta.env.PUBLIC_STT_URL || 'http://localhost:3020'
: 'http://localhost:3020';
function getSttUrl(): string {
if (!browser) return 'http://localhost:3020';
// Check runtime-injected variable first (production Docker)
const runtimeUrl = (window as any).__PUBLIC_STT_URL__;
if (runtimeUrl) return runtimeUrl;
// Fall back to build-time env var or default
return import.meta.env.PUBLIC_STT_URL || 'https://stt-api.mana.how';
}
const STT_URL = getSttUrl();
export interface TranscriptionResult {
/** The transcribed text */

View file

@ -14,6 +14,7 @@ export type WeekStartDay = 0 | 1; // 0 = Sunday, 1 = Monday
export type TimeFormat = '24h' | '12h';
export type AllDayDisplayMode = 'header' | 'block'; // header = separate row, block = full day block in grid
export type WeekdayFormat = 'full' | 'short' | 'hidden';
export type SttLanguage = 'de' | 'auto'; // Speech-to-text language setting
export interface CalendarAppSettings {
// View settings
@ -64,6 +65,9 @@ export interface CalendarAppSettings {
// Event defaults
defaultEventDuration: number; // in minutes
defaultReminder: number; // in minutes before event
// Voice input settings
sttLanguage: SttLanguage; // Speech-to-text language ('de' or 'auto')
}
const DEFAULT_SETTINGS: CalendarAppSettings = {
@ -106,6 +110,8 @@ const DEFAULT_SETTINGS: CalendarAppSettings = {
// Event defaults
defaultEventDuration: 60,
defaultReminder: 15,
// Voice input defaults
sttLanguage: 'de',
};
const STORAGE_KEY = 'calendar-settings';
@ -275,6 +281,9 @@ export const settingsStore = {
get customDayCount() {
return settings.customDayCount;
},
get sttLanguage() {
return settings.sttLanguage;
},
get cloudSyncEnabled() {
return cloudSyncEnabled;
},

View file

@ -35,7 +35,12 @@ export interface AudioRecorder {
* Check if the browser supports audio recording
*/
export function isAudioRecordingSupported(): boolean {
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia && window.MediaRecorder);
return !!(
typeof navigator !== 'undefined' &&
navigator.mediaDevices &&
typeof navigator.mediaDevices.getUserMedia === 'function' &&
typeof MediaRecorder !== 'undefined'
);
}
/**

View file

@ -391,6 +391,8 @@ const APP_CONFIGS = [
// Cross-app integration: Contacts service for birthdays
PUBLIC_CONTACTS_API_URL: (env) => `http://localhost:${env.CONTACTS_BACKEND_PORT || '3015'}`,
PUBLIC_CONTACTS_WEB_URL: () => 'http://localhost:5184',
// Speech-to-Text Service
PUBLIC_STT_URL: (env) => env.STT_URL || 'http://localhost:3020',
},
},