Skip to content

Commit d8de718

Browse files
Modular AI, v3.1.1
1 parent f3280f3 commit d8de718

15 files changed

Lines changed: 234 additions & 259 deletions
-78 MB
Binary file not shown.
-30.7 MB
Binary file not shown.

app/src/main/java/com/thivyanstudios/hark/data/UserPreferencesRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class UserPreferencesRepository @Inject constructor(
6767
whisperTranslate = preferences[PreferenceKeys.WHISPER_TRANSLATE] ?: false,
6868
silenceThreshold = preferences[PreferenceKeys.SILENCE_THRESHOLD] ?: 0.005f,
6969
transcriptionFontSize = preferences[PreferenceKeys.TRANSCRIPTION_FONT_SIZE] ?: 22f,
70-
selectedModelId = preferences[PreferenceKeys.SELECTED_MODEL_ID] ?: "ggml-base-q8_0.bin"
70+
selectedModelId = preferences[PreferenceKeys.SELECTED_MODEL_ID] ?: "ggml-base-q8_0"
7171
)
7272
}
7373
.flowOn(ioDispatcher)

app/src/main/java/com/thivyanstudios/hark/data/WhisperModelManager.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,11 @@ class WhisperModelManager @Inject constructor(
3636

3737
val availableModels = listOf(
3838
WhisperModel(
39-
id = "ggml-base-q8_0.bin",
39+
id = "ggml-base-q8_0",
4040
name = "Base (Standard)",
4141
description = "Good balance of speed and accuracy. Best for most devices.",
42-
url = "", // Included in assets
43-
sizeBytes = 77_000_000L,
44-
isAsset = true
42+
url = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q8_0.bin",
43+
sizeBytes = 77_000_000L
4544
),
4645
WhisperModel(
4746
id = "ggml-tiny.en-q8_0",

app/src/main/java/com/thivyanstudios/hark/data/model/UserPreferences.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ data class UserPreferences(
1616
val whisperTranslate: Boolean = false,
1717
val silenceThreshold: Float = 0.005f,
1818
val transcriptionFontSize: Float = 22f,
19-
val selectedModelId: String = "ggml-base-q8_0.bin"
19+
val selectedModelId: String = "ggml-base-q8_0"
2020
)

app/src/main/java/com/thivyanstudios/hark/service/AudioStreamingService.kt

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class AudioStreamingService : Service(), AudioStreamingController {
8484
private lateinit var serviceScope: CoroutineScope
8585
private var disableHearingAidPriority = false
8686
private var lastTranscriptModeEnabled: Boolean? = null
87+
private var lastSelectedModelId: String? = null
8788

8889
private val _hearingAidConnected = MutableStateFlow(false)
8990
override val hearingAidConnected = _hearingAidConnected.asStateFlow()
@@ -192,6 +193,15 @@ class AudioStreamingService : Service(), AudioStreamingController {
192193
}
193194
lastTranscriptModeEnabled = newTranscriptMode
194195

196+
val newModelId = prefs.selectedModelId
197+
if (lastSelectedModelId != null && newModelId != lastSelectedModelId) {
198+
HarkLog.i(TAG, "Model selection changed: $newModelId, stopping stream")
199+
if (_isStreaming.value) {
200+
stopStreaming()
201+
}
202+
}
203+
lastSelectedModelId = newModelId
204+
195205
val gain = 10.0.pow(prefs.microphoneGain / 20.0).toFloat()
196206
audioEngine.setMicrophoneGain(gain)
197207
audioEngine.setNoiseSuppressionEnabled(prefs.noiseSuppressionEnabled)
@@ -219,6 +229,16 @@ class AudioStreamingService : Service(), AudioStreamingController {
219229
}
220230
.launchIn(serviceScope)
221231

232+
// Stop streaming if model library changes (download or delete)
233+
whisperModelManager.modelStoreUpdateTrigger
234+
.onEach {
235+
if (_isStreaming.value) {
236+
HarkLog.i(TAG, "Model library changed, stopping stream")
237+
stopStreaming()
238+
}
239+
}
240+
.launchIn(serviceScope)
241+
222242
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
223243
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Hark::AudioStreamingWakeLock")
224244
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
@@ -298,25 +318,10 @@ class AudioStreamingService : Service(), AudioStreamingController {
298318

299319
val modelFile = File(filesDir, model.fileName)
300320

301-
if (model.isAsset) {
302-
// Check if model exists and has a minimum expected size
303-
val minExpectedSize = model.sizeBytes
304-
305-
if (!modelFile.exists() || modelFile.length() < minExpectedSize) {
306-
HarkLog.i(TAG, "Copying or re-copying Whisper model from assets (exists=${modelFile.exists()}, size=${modelFile.length()})...")
307-
assets.open(model.fileName).use { input ->
308-
modelFile.outputStream().use { output ->
309-
input.copyTo(output)
310-
}
311-
}
312-
}
313-
} else {
314-
// It's a downloaded model
315-
if (!whisperModelManager.isModelDownloaded(model)) {
316-
HarkLog.e(TAG, "Selected model not downloaded: ${model.name}")
317-
audioEngine.sendError("Model not downloaded. Please go to Settings.")
318-
return@withContext
319-
}
321+
// It's a downloaded model
322+
if (!whisperModelManager.isModelDownloaded(model)) {
323+
HarkLog.i(TAG, "No model downloaded, skipping Whisper initialization.")
324+
return@withContext
320325
}
321326

322327
if (!modelFile.exists()) {

app/src/main/java/com/thivyanstudios/hark/ui/HarkAppContent.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ fun HarkAppContent(
7676
HomeScreen(
7777
isStreaming = uiState.isStreaming,
7878
onStreamButtonClick = onToggleStreaming,
79-
hapticFeedbackEnabled = uiState.hapticFeedbackEnabled
79+
hapticFeedbackEnabled = uiState.hapticFeedbackEnabled,
80+
isModelAvailable = uiState.isModelAvailable
8081
)
8182
}
8283
composable(Navigation.ROUTE_TRANSCRIBE) {
@@ -117,6 +118,7 @@ fun HarkAppContent(
117118
}
118119
},
119120
hapticFeedbackEnabled = uiState.hapticFeedbackEnabled,
121+
isModelAvailable = uiState.isModelAvailable,
120122
modifier = Modifier.align(Alignment.BottomCenter)
121123
)
122124
}

app/src/main/java/com/thivyanstudios/hark/ui/MainUiState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ data class MainUiState(
1111
val activeSoundEvents: List<SoundEvent> = emptyList(),
1212
val audioLevel: Float = 0f, // Normalized 0.0 to 1.0
1313
val isLoading: Boolean = true,
14+
val isModelAvailable: Boolean = false,
1415
val history: List<com.thivyanstudios.hark.data.local.TranscriptionEntity> = emptyList()
1516
)
1617

app/src/main/java/com/thivyanstudios/hark/ui/screens/BottomNavBar.kt

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ fun BottomNavBar(
5555
currentRoute: String,
5656
onNavigate: (String) -> Unit,
5757
hapticFeedbackEnabled: Boolean,
58+
isModelAvailable: Boolean,
5859
modifier: Modifier = Modifier
5960
) {
6061
val navigationBarsPadding = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
@@ -88,20 +89,22 @@ fun BottomNavBar(
8889
onClick = { onNavigate(Navigation.ROUTE_HOME) },
8990
hapticFeedbackEnabled = hapticFeedbackEnabled
9091
)
91-
AnimatedNavigationBarItem(
92-
icon = Icons.Rounded.RecordVoiceOver,
93-
labelResId = R.string.nav_transcribe,
94-
selected = currentRoute == Navigation.ROUTE_TRANSCRIBE,
95-
onClick = { onNavigate(Navigation.ROUTE_TRANSCRIBE) },
96-
hapticFeedbackEnabled = hapticFeedbackEnabled
97-
)
98-
AnimatedNavigationBarItem(
99-
icon = Icons.Rounded.History,
100-
labelResId = R.string.nav_history,
101-
selected = currentRoute == Navigation.ROUTE_HISTORY,
102-
onClick = { onNavigate(Navigation.ROUTE_HISTORY) },
103-
hapticFeedbackEnabled = hapticFeedbackEnabled
104-
)
92+
if (isModelAvailable) {
93+
AnimatedNavigationBarItem(
94+
icon = Icons.Rounded.RecordVoiceOver,
95+
labelResId = R.string.nav_transcribe,
96+
selected = currentRoute == Navigation.ROUTE_TRANSCRIBE,
97+
onClick = { onNavigate(Navigation.ROUTE_TRANSCRIBE) },
98+
hapticFeedbackEnabled = hapticFeedbackEnabled
99+
)
100+
AnimatedNavigationBarItem(
101+
icon = Icons.Rounded.History,
102+
labelResId = R.string.nav_history,
103+
selected = currentRoute == Navigation.ROUTE_HISTORY,
104+
onClick = { onNavigate(Navigation.ROUTE_HISTORY) },
105+
hapticFeedbackEnabled = hapticFeedbackEnabled
106+
)
107+
}
105108
AnimatedNavigationBarItem(
106109
icon = Icons.Rounded.Settings,
107110
labelResId = R.string.nav_settings,

app/src/main/java/com/thivyanstudios/hark/ui/screens/HomeScreen.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import kotlinx.coroutines.delay
3232
fun HomeScreen(
3333
isStreaming: Boolean,
3434
onStreamButtonClick: () -> Unit,
35-
hapticFeedbackEnabled: Boolean
35+
hapticFeedbackEnabled: Boolean,
36+
isModelAvailable: Boolean
3637
) {
3738
var isButtonEnabled by remember { mutableStateOf(true) }
3839

@@ -58,7 +59,7 @@ fun HomeScreen(
5859
)
5960

6061
val buttonColor by animateColorAsState(
61-
targetValue = if (isStreaming) Color(0xFF4CAF50) else Color(0xFFF44336),
62+
targetValue = if (isStreaming) Color(0xFF4CAF50) else Color(0xFFF44336),
6263
animationSpec = spring(stiffness = Spring.StiffnessLow),
6364
label = "ButtonColor"
6465
)
@@ -160,7 +161,7 @@ fun HomeScreen(
160161
// Activity Chip at the bottom - Simplified to be less "flickery"
161162
val navigationBarsPadding = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
162163

163-
if (isStreaming) {
164+
if (isStreaming && isModelAvailable) {
164165
Row(
165166
modifier = Modifier
166167
.align(Alignment.BottomCenter)

0 commit comments

Comments
 (0)