Commit graph

18 commits

Author SHA1 Message Date
Bruno Deanoz
31278f2440 Add Live Call UI overlay + wiring in ChatScreen
LiveCallOverlay — fullscreen composable: KaizenOrb (amplitude-reactive,
streaming/recording variants), duration timer, status text (connecting/
listening/speaking), animated subtitle (transcript/response), end call
button (red gradient circle with PhoneOff icon), interrupt on orb tap.

ChatScreen wiring: CallButton → startLiveCall() → binds LiveCallService
→ Foreground Service with AudioPipeline + LiveClient. Overlay renders
above chat content with fade animation. endLiveCall() unbinds + stops.

ChatInput: onCallClick callback propagated from CallButton to ChatScreen.

i18n: live_connecting/listening/ready/speaking/end/reconnecting in de+en.
KaizenIcons: added PhoneOff (Lucide).
2026-06-24 03:14:08 +02:00
Bruno Deanoz
7aefd7ba70 fix: migrate ClickableText to LinkAnnotation, add server version check
Replace deprecated ClickableText with LinkAnnotation.Url + Text (Compose
native URI handling). Add GET /api/v1/meta call after login and on each
app resume to detect API version skew — shows dismissible amber banner.
2026-06-23 14:51:20 +02:00
Bruno Deanoz
1899f2cff1 fix: voice messages show as compact pill instead of file attachment chip
Audio-only user messages (no text, only audio attachments) now render as
a clean "🎤 Sprachnachricht" pill instead of the ugly filename chip
(voice_1782170416740.m4a). Detects via attachments.all { kind == "audio" }
&& content.isBlank(). Regular file attachments are unaffected.
2026-06-23 01:24:56 +02:00
Bruno Deanoz
fb6bc981d1 feat: web search provider picker for Vertex models (Google vs Enterprise)
When search mode is active and a vertex: model is selected, a second pill
appears next to "Suche" with a dropdown to choose between Google Search
(default) and Enterprise Web Search grounding. The selection is sent as
webSearchProvider in the chat request body. Matches the web frontend's
OptionPill for webSearchProvider.

Only visible for Vertex models — OpenRouter uses agentive web_search
server-tool which has no provider toggle.
2026-06-23 01:23:03 +02:00
Bruno Deanoz
558b1be675 feat: sidebar redesign — glasier look, functional search, generate title, duplicate
Design: lower tintAlpha (0.80/0.86) for more blob bleed-through, accent-tinted
gradient borders + level1 shadow on active items, specular borders on search bar
and user card, accent gradient avatar, separator line in context menu.

Features: live search filter, "Titel generieren" (Sparkles, manual POST with
{ manual: true }), "Duplizieren" (copies all messages with new IDs + parentId
chain), titleGenerated field on ConversationSummary/Entity (Room v4).
2026-06-23 00:56:58 +02:00
Bruno Deanoz
9e1f30de1f feat: voice recording + TTS read-aloud for assistant messages
Audio recording:
- Mic button wired to MediaRecorder (AAC/M4A, 44.1kHz/128kbps)
- Tap to start, tap again to stop — auto-uploads as audio attachment
- RECORD_AUDIO permission with runtime request
- Recording state: red stop-square on mic button

TTS read-aloud:
- Volume button on assistant messages → POST /api/v1/audio-gen
- Gemini-TTS generates speech server-side, MediaPlayer streams it
- Tap again to stop playback, accent-colored icon when playing
- Graceful error handling (Toast on failure)

Supporting changes:
- KaizenApi.generateSpeech() for /api/v1/audio-gen endpoint
- KaizenIcons.Volume2 added (Lucide)
- DE/EN strings for recording, permission, TTS states
2026-06-23 00:27:02 +02:00
Bruno Deanoz
141d4de227 fix locale, add stop button, auto-logout on 401, migrate login shadows
- EmptyHero date respects system locale instead of hardcoded German
- Stop button (red square) replaces send/call during streaming, cancels stream cleanly
- Auto-logout on HTTP 401 (expired token) routes to login instead of error banner
- LoginScreen migrated from Modifier.shadow to kaizenShadow (design system pillar 4)
2026-06-23 00:02:30 +02:00
Bruno Deanoz
ba7ebafe08 feat: security settings — devices, password change, biometric toggle
Security section in Settings with three cards:
- Biometric toggle (persisted in SecureStore, respected by unlock flow)
- Signed-in devices list with remote revoke (GET/DELETE /api/v1/auth/tokens)
- Password change form (POST /api/v1/auth/change-password)

requestBiometricOrPassword() now checks the biometric toggle — when
disabled, always falls back to password dialog.
2026-06-22 13:44:01 +02:00
Bruno Deanoz
15f5781824 fix: close biometric bypass on locked conversations
Three paths in openConversation() called unlockAndOpen() without any
authentication when biometrics were unavailable. Sidebar ToggleLock
also sent PATCH { locked: false } without auth.

- Unified requestBiometricOrPassword() gates all unlock paths
- PasswordUnlockDialog as fallback when biometrics unavailable
- KaizenApi.requestUnlock() sends password to server for verification
- All unlock flows (open chat + sidebar toggle) now require auth
2026-06-22 13:27:55 +02:00
Bruno Deanoz
f6dc5db363 reasoning presets + sampling controls (functional, not mock)
Reasoning pill (Brain icon + dropdown): 8 presets matching web
(Sofort/Aus/Minimal/Niedrig/Standard/Mittel/Hoch/Maximal).
"Standard" = no override, "Sofort" = no reasoning + throughput.
Sends reasoningEffort + preferThroughput in ChatRequest.

Sampling pill (Tune icon + popover): per-parameter toggle +
slider for Temperature (0-2), Top-P (0-1), Top-K (1-100).
Each parameter independently on/off — only enabled ones are
sent. Accent highlight when any param is enabled.

Both controls are separate from mode pills (Bild/Suche/Video)
and work in chat + search modes, matching the web layout.

ChatRequest extended with temperature/topP/topK fields.
Old ChatSpeed enum no longer used for reasoning (kept for
backward compat with ServerConfig).
2026-06-22 00:19:14 +02:00
Bruno Deanoz
88559bf49e biometric unlock for locked conversations
Hardware-enforced via Android KeyStore + CryptoObject:
- AES key in TEE/StrongBox with setUserAuthenticationRequired(true)
- BiometricPrompt receives Cipher as CryptoObject, unlocked only
  after real biometric auth (fingerprint/face), not bypassable
- Key auto-invalidated on biometric enrollment change

Flow: tap locked chat → BiometricPrompt → on success request
server unlock token (POST /api/v1/auth/unlock) → fetch messages
with X-Unlock-Token header → display conversation.

Dependencies: androidx.biometric:biometric:1.4.0-alpha02
2026-06-21 23:56:57 +02:00
Bruno Deanoz
f3d8154052 sidebar redesign + markdown typography overhaul
Sidebar: increase glass opacity (0.92/0.88), flat conversation rows
instead of pill shapes, 3-dot context menu (rename/delete/pin/lock),
smaller profile avatar (30dp), logout integrated into user card.
Add KaizenApi.patchConversation + deleteConversation for CRUD.

Markdown: increase line-height from 23sp to 26sp (×1.625 ratio),
double paragraph spacing (6dp→12dp), generous heading margins
(18dp top, 8dp bottom), wider list item spacing — matches Gemini-
level readability.
2026-06-21 23:31:03 +02:00
Bruno Deanoz
09489318bf feat: attachment picker menu — camera, gallery, and file options
Replaced the generic file picker with a dropdown menu offering three
options: take photo (camera preview), pick from gallery (image/*), or
choose any file (*/*). Camera captures are compressed as JPEG and
uploaded like any other file. Upload logic extracted into shared
uploadPickedFile helper.
2026-06-21 18:45:41 +02:00
Bruno Deanoz
5c54b9b9e3 fix: rewrite ToolEventsBlock to match web frontend logic
Parse tool events into thinking/analyzing/inProgress/completed states
instead of showing all as generic "Tool" rows. Handles type "thinking"
(shows "Verarbeitet…"), "analyzing" (shows filename), "start"/"end"/
"error" with proper icons and elapsed time. Empty/thinking-only events
no longer show a confusing wrench icon.
2026-06-21 18:40:19 +02:00
Bruno Deanoz
76ab8bfde9 feat: reasoning, sources, and tool-call display
StreamConsumer.Incremental now returns StreamState (content, reasoning,
tools, query, sources) instead of just visible text. All sentinel sections
are parsed and structured:
- \x01 REASONING: captured as separate string
- \x02 SOURCES: parsed as List<SearchSource> (title, url, snippet)
- \x03 TOOL: parsed as List<ToolEvent> (type, name, args, result, elapsed)
- \x04 QUERY: captured as search query string

UI components:
- ReasoningBlock: collapsible "Gedankengang" with expand/collapse animation
- ToolEventsBlock: shows tool-call steps with status icons (start/end/error)
- SourcesBlock: web search sources as compact cards with domain extraction

Message model extended with reasoning, tools, query, sources fields.
KaizenApi.chat() returns Flow<StreamState> instead of Flow<String>.
ChatScreen wires all fields to Message during streaming.

Fixed: empty append() on Incremental no longer prematurely ends tool phase.
StreamConsumerTest rewritten to verify all parsed sections.
2026-06-21 15:35:46 +02:00
Bruno Deanoz
736bb4c206 feat: persistent settings + server sync + language selector
Settings now persist across app restarts via SecureStore:
- Theme (KaizenThemeId), appearance (Light/Dark/System), username, locale
- All loaded on SettingsViewModel init, saved on every change

Server sync: fetchMe() returns name + email from /api/v1/me,
populates SettingsViewModel on login and each foreground resume.

Language selector: System / Deutsch / English pill toggle in Settings.
Locale preference stored; runtime switching deferred to next iteration.

MeResponse expanded to parse name + email (was defaultModel only).
SettingsViewModel tests updated for store-less default state.
2026-06-21 15:26:14 +02:00
Bruno Deanoz
0237eae1e6 feat: i18n — extract all UI strings to resources (de + en)
German (default) in values/strings.xml, English in values-en/strings.xml.
Matches web app's next-intl de/en setup.

Migrated: greeting, hero headline, chat input, error messages, mode pills,
suggestion pills, sidebar (brand, search, dates, logout, settings),
model sheet, login screen (welcome, fields, errors, submit button),
conversation labels (locked, pinned).

ChatModels: Suggestion/ChatMode now use resource IDs instead of hardcoded strings.
greeting() is now @Composable (uses stringResource).
ModePillsRow: selected state uses resource ID (Int) instead of String.
Date labels (HEUTE/GESTERN/FRÜHER) localized via DateLabels data class.
2026-06-21 15:02:16 +02:00
a5a6dbff61 Initial commit - Kaizen App 2026-06-18 15:08:09 +02:00