diff --git a/app/src/main/java/com/app/assistant/config/SpeechConfig.kt b/app/src/main/java/com/app/assistant/config/SpeechConfig.kt
index 3eced24..cfd5ade 100644
--- a/app/src/main/java/com/app/assistant/config/SpeechConfig.kt
+++ b/app/src/main/java/com/app/assistant/config/SpeechConfig.kt
@@ -21,7 +21,10 @@ object SpeechConfig {
// Edge TTS Configurations
object EdgeTts {
const val WSS_URL = "wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1"
- val SUBSCRIPTION_KEY = BuildConfig.EDGE_TTS_SUBSCRIPTION_KEY
+ fun getSubscriptionKey(settingsRepository: SettingsRepository): String {
+ val key = settingsRepository.getEdgeTtsSubscriptionKey()
+ return if (key.isNullOrBlank()) BuildConfig.EDGE_TTS_SUBSCRIPTION_KEY else key
+ }
const val VOICE = "en-GB-SoniaNeural"
const val OUTPUT_FORMAT = "audio-24khz-48kbitrate-mono-mp3"
}
diff --git a/app/src/main/java/com/app/assistant/repository/SettingsRepository.kt b/app/src/main/java/com/app/assistant/repository/SettingsRepository.kt
index 6604634..b406d9b 100644
--- a/app/src/main/java/com/app/assistant/repository/SettingsRepository.kt
+++ b/app/src/main/java/com/app/assistant/repository/SettingsRepository.kt
@@ -35,6 +35,7 @@ class SettingsRepository(
fun saveKeys(
youtubeApiKey: String,
chatApiKey: String,
+ edgeTtsSubscriptionKey: String = "",
) {
with(securedPreferences.edit()) {
if (youtubeApiKey.isNotBlank()) {
@@ -43,6 +44,9 @@ class SettingsRepository(
if (chatApiKey.isNotBlank()) {
putString("chat_api_key", chatApiKey)
}
+ if (edgeTtsSubscriptionKey.isNotBlank()) {
+ putString("edge_tts_subscription_key", edgeTtsSubscriptionKey)
+ }
apply()
}
}
@@ -51,6 +55,8 @@ class SettingsRepository(
fun getChatApiKey(): String? = securedPreferences.getString("chat_api_key", null)
+ fun getEdgeTtsSubscriptionKey(): String? = securedPreferences.getString("edge_tts_subscription_key", null)
+
fun getLlmProvider(): String = sharedPreferences.getString("llm_provider", "GROQ") ?: "GROQ"
fun setLlmProvider(provider: String) {
diff --git a/app/src/main/java/com/app/assistant/tts/EdgeTtsApiManager.kt b/app/src/main/java/com/app/assistant/tts/EdgeTtsApiManager.kt
index f164cb8..3ad1da9 100644
--- a/app/src/main/java/com/app/assistant/tts/EdgeTtsApiManager.kt
+++ b/app/src/main/java/com/app/assistant/tts/EdgeTtsApiManager.kt
@@ -320,7 +320,7 @@ class EdgeTtsApiManager(
val connectionId = UUID.randomUUID().toString().replace("-", "").uppercase()
val secMsGec = generateSecMsGec()
val url = "${SpeechConfig.EdgeTts.WSS_URL}" +
- "?Ocp-Apim-Subscription-Key=${SpeechConfig.EdgeTts.SUBSCRIPTION_KEY}" +
+ "?Ocp-Apim-Subscription-Key=${SpeechConfig.EdgeTts.getSubscriptionKey(settingsRepository)}" +
"&ConnectionId=$connectionId" +
"&Sec-MS-GEC=$secMsGec" +
"&Sec-MS-GEC-Version=1-130.0.2849.68"
@@ -493,7 +493,7 @@ class EdgeTtsApiManager(
var ticks = unixTime
ticks -= ticks % 300
val winTicks = (ticks + 11644473600L) * 10000000L
- val strToHash = "${winTicks}${SpeechConfig.EdgeTts.SUBSCRIPTION_KEY}"
+ val strToHash = "${winTicks}${SpeechConfig.EdgeTts.getSubscriptionKey(settingsRepository)}"
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(strToHash.toByteArray(Charsets.UTF_8))
return digest.joinToString("") { "%02X".format(it) }
diff --git a/app/src/main/java/com/app/assistant/ui/screen/SettingsScreen.kt b/app/src/main/java/com/app/assistant/ui/screen/SettingsScreen.kt
index f461b50..968e0e8 100644
--- a/app/src/main/java/com/app/assistant/ui/screen/SettingsScreen.kt
+++ b/app/src/main/java/com/app/assistant/ui/screen/SettingsScreen.kt
@@ -95,6 +95,7 @@ fun SettingsScreen(
onLanguageSelected: (String) -> Unit,
) {
val initialYoutubeKey = remember { settingsViewModel.loadYoutubeKey() }
+ val initialEdgeTtsKey = remember { settingsViewModel.loadEdgeTtsSubscriptionKey() }
val initialChatKey = remember { settingsViewModel.loadChatKey() }
val initialProvider = remember { settingsViewModel.loadLlmProvider() }
val initialModel = remember { settingsViewModel.loadLlmModel() }
@@ -128,6 +129,7 @@ fun SettingsScreen(
SettingsScreenContent(
settingsViewModel = settingsViewModel,
initialYoutubeKey = initialYoutubeKey,
+ initialEdgeTtsKey = initialEdgeTtsKey,
initialChatKey = initialChatKey,
initialProvider = initialProvider,
initialModel = initialModel,
@@ -183,6 +185,7 @@ fun SettingsScreen(
fun SettingsScreenContent(
settingsViewModel: SettingsViewModel,
initialYoutubeKey: String,
+ initialEdgeTtsKey: String,
initialChatKey: String,
initialProvider: LlmProvider,
initialModel: String,
@@ -228,6 +231,7 @@ fun SettingsScreenContent(
onDeleteTranslationModel: (String) -> Unit
) {
var apiKey1 by rememberSaveable { mutableStateOf(initialYoutubeKey) }
+ var edgeTtsKey by rememberSaveable { mutableStateOf(initialEdgeTtsKey) }
var apiKey2 by rememberSaveable { mutableStateOf(initialChatKey) }
var provider by rememberSaveable { mutableStateOf(initialProvider) }
var model by rememberSaveable { mutableStateOf(initialModel) }
@@ -949,6 +953,20 @@ fun SettingsScreenContent(
description = stringResource(id = R.string.api_tts_desc) + " (Configured: ${SpeechConfig.ACTIVE_TTS_PROVIDER})"
)
+ Spacer(modifier = Modifier.height(8.dp))
+
+ OutlinedTextField(
+ value = edgeTtsKey,
+ onValueChange = {
+ edgeTtsKey = it
+ settingsViewModel.updateEdgeTtsSubscriptionKey(it)
+ },
+ label = { Text(stringResource(id = R.string.edge_tts_subscription_key)) },
+ visualTransformation = androidx.compose.ui.text.input.PasswordVisualTransformation(),
+ shape = RoundedCornerShape(12.dp),
+ modifier = Modifier.fillMaxWidth()
+ )
+
Spacer(modifier = Modifier.height(12.dp))
ModelDownloadCard(
@@ -1763,6 +1781,7 @@ fun SettingsScreenContentPreview() {
SettingsScreenContent(
settingsViewModel = dummyViewModel,
initialYoutubeKey = "AIzaSy...",
+ initialEdgeTtsKey = "EdgeKey...",
initialChatKey = "gsk_...",
initialProvider = LlmProvider.GROQ,
initialModel = LlmProvider.GROQ.defaultModel,
@@ -1822,6 +1841,7 @@ fun SettingsScreenContentCustomPreview() {
SettingsScreenContent(
settingsViewModel = dummyViewModel,
initialYoutubeKey = "",
+ initialEdgeTtsKey = "",
initialChatKey = "",
initialProvider = LlmProvider.CUSTOM,
initialModel = "my-custom-model",
diff --git a/app/src/main/java/com/app/assistant/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/app/assistant/viewmodel/SettingsViewModel.kt
index 6d101ef..b89e1e7 100644
--- a/app/src/main/java/com/app/assistant/viewmodel/SettingsViewModel.kt
+++ b/app/src/main/java/com/app/assistant/viewmodel/SettingsViewModel.kt
@@ -130,6 +130,11 @@ class SettingsViewModel(
val key = settingsRepository.getChatApiKey()
return if (key.isNullOrBlank()) BuildConfig.GROQ_API_KEY else key
}
+
+ fun loadEdgeTtsSubscriptionKey(): String {
+ val key = settingsRepository.getEdgeTtsSubscriptionKey()
+ return if (key.isNullOrBlank()) BuildConfig.EDGE_TTS_SUBSCRIPTION_KEY else key
+ }
fun loadLlmProvider(): LlmProvider {
val name = settingsRepository.getLlmProvider()
@@ -241,6 +246,7 @@ class SettingsViewModel(
fun saveSettings(
youtubeApiKey: String,
chatApiKey: String,
+ edgeTtsSubscriptionKey: String,
provider: LlmProvider,
model: String,
customUrl: String,
@@ -254,7 +260,7 @@ class SettingsViewModel(
sttMode: com.app.assistant.speech.SttMode,
ttsMode: com.app.assistant.tts.TtsMode
) {
- settingsRepository.saveKeys(youtubeApiKey, chatApiKey)
+ settingsRepository.saveKeys(youtubeApiKey, chatApiKey, edgeTtsSubscriptionKey)
settingsRepository.setLlmProvider(provider.name)
settingsRepository.setLlmModel(model)
settingsRepository.setLlmCustomUrl(customUrl)
@@ -270,11 +276,27 @@ class SettingsViewModel(
}
fun updateYoutubeApiKey(key: String) {
- settingsRepository.saveKeys(youtubeApiKey = key, chatApiKey = settingsRepository.getChatApiKey() ?: "")
+ settingsRepository.saveKeys(
+ youtubeApiKey = key,
+ chatApiKey = settingsRepository.getChatApiKey() ?: "",
+ edgeTtsSubscriptionKey = settingsRepository.getEdgeTtsSubscriptionKey() ?: ""
+ )
}
fun updateChatApiKey(key: String) {
- settingsRepository.saveKeys(youtubeApiKey = settingsRepository.getYoutubeApiKey() ?: "", chatApiKey = key)
+ settingsRepository.saveKeys(
+ youtubeApiKey = settingsRepository.getYoutubeApiKey() ?: "",
+ chatApiKey = key,
+ edgeTtsSubscriptionKey = settingsRepository.getEdgeTtsSubscriptionKey() ?: ""
+ )
+ }
+
+ fun updateEdgeTtsSubscriptionKey(key: String) {
+ settingsRepository.saveKeys(
+ youtubeApiKey = settingsRepository.getYoutubeApiKey() ?: "",
+ chatApiKey = settingsRepository.getChatApiKey() ?: "",
+ edgeTtsSubscriptionKey = key
+ )
}
fun updateLlmProvider(provider: LlmProvider) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 02bd29b..906b22c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,6 +2,7 @@
AssistantSettingsYouTube API Key
+ Edge TTS Subscription KeyChat API KeyLLM ProviderModel Name
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..448eeb5
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,717 @@
+
+
+
+
+
+ AI Assistant - Secure API Key QR Generator
+
+
+
+
+
+
+
+
+
+
+
+
+
AI Assistant Configuration
+
Securely generate an API keys QR code completely in your browser. Disconnect from the internet anytime for maximum privacy.
+
+
+
+
+
+
+
+ Configure API Keys
+
+
+
+
+
+ Client-Side Security
+ No data is transmitted over the network. The script relies on the local library, making it safe to save as a local file and run fully offline.
+
+
+
+
+
+
+
+
+
+
+ QR Code Preview
+
+
+
+
+
+
+
Fill in one or more API keys and click "Generate QR" to preview
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}();
\ No newline at end of file