settings defaults. logging. misc
This commit is contained in:
parent
6f94f60b15
commit
0b254654ef
|
|
@ -46,3 +46,6 @@ bin/
|
||||||
|
|
||||||
### STT model ###
|
### STT model ###
|
||||||
/src/main/resources/STT-model/
|
/src/main/resources/STT-model/
|
||||||
|
/src/main/resources/Semantic-model/
|
||||||
|
/logs/
|
||||||
|
/user.properties
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ dependencies {
|
||||||
// Core
|
// Core
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
|
|
||||||
|
// ConstrainLayout
|
||||||
|
implementation("tech.annexflow.compose:constraintlayout-compose-multiplatform:0.4.0")
|
||||||
|
|
||||||
// Material Design
|
// Material Design
|
||||||
implementation(compose.materialIconsExtended)
|
implementation(compose.materialIconsExtended)
|
||||||
|
|
||||||
|
|
@ -37,15 +40,10 @@ dependencies {
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
||||||
|
|
||||||
// Voyager
|
// Voyager
|
||||||
// Navigator
|
|
||||||
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
|
||||||
// Screen Model
|
|
||||||
implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion")
|
||||||
// BottomSheetNavigator
|
|
||||||
implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion")
|
||||||
// TabNavigator
|
|
||||||
implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion")
|
||||||
// Transitions
|
|
||||||
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
||||||
|
|
||||||
// Deep Java Library
|
// Deep Java Library
|
||||||
|
|
@ -57,8 +55,9 @@ dependencies {
|
||||||
// Semantic Kernel
|
// Semantic Kernel
|
||||||
implementation("com.microsoft.semantic-kernel:semantickernel-api:1.3.0")
|
implementation("com.microsoft.semantic-kernel:semantickernel-api:1.3.0")
|
||||||
|
|
||||||
// SLF4J
|
// Logging
|
||||||
implementation("org.slf4j:slf4j-api:2.0.16")
|
implementation ("io.github.oshai:kotlin-logging-jvm:7.0.0")
|
||||||
|
implementation ("ch.qos.logback:logback-classic:1.5.11")
|
||||||
}
|
}
|
||||||
|
|
||||||
compose.desktop {
|
compose.desktop {
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,44 @@
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
|
||||||
import androidx.compose.material.icons.filled.Stop
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import ui.screens.Home
|
||||||
import processing.SemanticSimilarity
|
import ui.TopBar
|
||||||
import screens.Home
|
import java.io.File
|
||||||
import screens.Settings
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
|
||||||
|
val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
val testSentence = "This is a test sentence for semantic similarity"
|
logger.info { "\n================== NEW APPLICATION SESSION ==================" }
|
||||||
val testCommands = listOf("This is a test", "This sentence is completely different", "Testing semantic similarity between sentence", "An unrelated statement")
|
checkLogsDir()
|
||||||
|
|
||||||
val semanticTester = SemanticSimilarity()
|
|
||||||
semanticTester.compareSentenceToList(testSentence, testCommands)
|
|
||||||
|
|
||||||
Window(onCloseRequest = ::exitApplication) {
|
Window(onCloseRequest = ::exitApplication) {
|
||||||
Navigator(Home()) { navigator ->
|
MaterialTheme {
|
||||||
Scaffold(
|
Navigator(Home()) { navigator ->
|
||||||
topBar = { topBar(navigator.size) },
|
Scaffold(
|
||||||
content = { CurrentScreen() },
|
topBar = { TopBar.topBar(navigator.size) },
|
||||||
bottomBar = { }
|
content = { CurrentScreen() },
|
||||||
)
|
bottomBar = { }
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.info { "Application started" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
fun checkLogsDir() {
|
||||||
fun topBar(screenCount: Int) {
|
val dir = File(Paths.get("logs/").absolutePathString())
|
||||||
Row {
|
if (!dir.exists()) {
|
||||||
if (screenCount > 1) {
|
dir.createNewFile()
|
||||||
backButton()
|
logger.info { "Folder Created: logs/" }
|
||||||
}
|
|
||||||
captureButton()
|
|
||||||
Settings.settingsButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun backButton() {
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
|
|
||||||
Button(onClick = { navigator.pop() }) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ArrowBackIosNew,
|
|
||||||
contentDescription = "Go Back"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val captureDuration = Settings.userProperties.getProperty("capture_duration").toInt()
|
|
||||||
@Composable
|
|
||||||
fun getCaptureIcon(capturing: Boolean) = if (!capturing) Icons.Filled.PlayArrow else Icons.Filled.Stop
|
|
||||||
@Composable
|
|
||||||
fun getCaptureDescription(capturing: Boolean) = if (!capturing) "Start" else "Stop"
|
|
||||||
fun getCaptureOnClick(mic: Microphone) = if (!mic.capturing) mic.startCapture(captureDuration) else mic.stopCapture()
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
@Composable
|
|
||||||
fun captureButton() {
|
|
||||||
val mic = Microphone()
|
|
||||||
var isCapturing by rememberSaveable { mutableStateOf(mic.capturing) }
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
getCaptureOnClick(mic)
|
|
||||||
isCapturing = !isCapturing
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = getCaptureIcon(isCapturing),
|
|
||||||
contentDescription = getCaptureDescription(isCapturing) + " audio capture"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: If folders (Semantic-model/, STT-model/) don't exist on start-up install
|
||||||
|
// Install Semantic-model by downloading, using djl-converter (look into using pip module from Java), move to resources
|
||||||
|
// Install STT-model by downloading, move to resources
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import javax.sound.sampled.AudioSystem
|
||||||
import javax.sound.sampled.DataLine
|
import javax.sound.sampled.DataLine
|
||||||
import javax.sound.sampled.TargetDataLine
|
import javax.sound.sampled.TargetDataLine
|
||||||
import processing.STT
|
import processing.STT
|
||||||
|
import ui.Capture.Companion.captureLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Documentation
|
* TODO: Documentation
|
||||||
|
|
@ -25,6 +26,7 @@ class Microphone {
|
||||||
*/
|
*/
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun startCapture(captureDuration: Int) {
|
fun startCapture(captureDuration: Int) {
|
||||||
|
captureLogger.info { "\n================== NEW CAPTURE SESSION ==================" }
|
||||||
capturing = true
|
capturing = true
|
||||||
source.open(audioFormat, source.bufferSize)
|
source.open(audioFormat, source.bufferSize)
|
||||||
source.start()
|
source.start()
|
||||||
|
|
@ -36,8 +38,8 @@ class Microphone {
|
||||||
while (capturing) {
|
while (capturing) {
|
||||||
val audioBytes = source.read(buffer, 0, buffer.size)
|
val audioBytes = source.read(buffer, 0, buffer.size)
|
||||||
val result = stt.parseBuffer(buffer, audioBytes)
|
val result = stt.parseBuffer(buffer, audioBytes)
|
||||||
println("Captured Speech: $result")
|
captureLogger.info { ("Captured Speech: $result") }
|
||||||
//TODO: Pass onto semantic similarity
|
//TODO: Pass onto processing
|
||||||
}
|
}
|
||||||
stt.closeModel()
|
stt.closeModel()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.Properties
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
|
||||||
|
class UserSettings: Properties() {
|
||||||
|
init {
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.createNewFile()
|
||||||
|
defaults()
|
||||||
|
save()
|
||||||
|
logger.info { "File Created: user.settings initialized with defaults" }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val stream = FileInputStream(file)
|
||||||
|
load(stream)
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaults() {
|
||||||
|
setProperty("capture_duration", "5")
|
||||||
|
setProperty("ai_name", "Mia")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
val stream = FileOutputStream(file)
|
||||||
|
store(stream, null)
|
||||||
|
stream.flush()
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val file = File(
|
||||||
|
Paths.get("user.settings").absolutePathString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package processing
|
package processing
|
||||||
|
|
||||||
|
import logger
|
||||||
import org.vosk.Model
|
import org.vosk.Model
|
||||||
import org.vosk.Recognizer
|
import org.vosk.Recognizer
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
@ -12,33 +13,37 @@ class STT {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private val model = Model(getModelPath())
|
fun parseBuffer(buffer: ByteArray, audioBytes: Int): String? = if (recognizer.acceptWaveForm(buffer, audioBytes)) recognizer.result else recognizer.partialResult
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private val recognizer = Recognizer(model, 16000.0f)
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
fun parseBuffer(buffer: ByteArray, audioBytes: Int): String? {
|
|
||||||
return if (recognizer.acceptWaveForm(buffer, audioBytes)) {
|
|
||||||
recognizer.result
|
|
||||||
} else {
|
|
||||||
recognizer.partialResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeModel() {
|
fun closeModel() {
|
||||||
recognizer.close()
|
recognizer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
*
|
/**
|
||||||
*/
|
*
|
||||||
private fun getModelPath() = Paths.get(
|
*/
|
||||||
javaClass.getResource("/STT-model")!!.toURI()
|
private val model = getModel()
|
||||||
).absolutePathString()
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private val recognizer = getRecognizer()
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private fun getModelPath() = Paths.get(
|
||||||
|
javaClass.getResource("/STT-model")!!.toURI()
|
||||||
|
).absolutePathString()
|
||||||
|
|
||||||
|
private fun getModel(): Model {
|
||||||
|
logger.info { "STT model initializing..." }
|
||||||
|
return Model(getModelPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRecognizer(): Recognizer {
|
||||||
|
logger.info { "STT model initialized" }
|
||||||
|
return Recognizer(model, 16000.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,73 +1,77 @@
|
||||||
package processing
|
package processing
|
||||||
|
|
||||||
import ai.djl.Application
|
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer
|
||||||
import ai.djl.Model
|
import ai.djl.inference.Predictor
|
||||||
import ai.djl.huggingface.tokenizers.Encoding
|
|
||||||
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
|
|
||||||
import ai.djl.huggingface.translator.TextEmbeddingTranslator
|
|
||||||
import ai.djl.huggingface.translator.TextEmbeddingTranslatorFactory
|
|
||||||
import ai.djl.huggingface.zoo.HfModelZoo
|
|
||||||
import ai.djl.ndarray.NDArray
|
import ai.djl.ndarray.NDArray
|
||||||
import ai.djl.ndarray.NDList
|
import ai.djl.ndarray.NDList
|
||||||
import ai.djl.ndarray.NDManager
|
import ai.djl.ndarray.NDManager
|
||||||
import ai.djl.repository.zoo.Criteria
|
import ai.djl.repository.zoo.Criteria
|
||||||
import ai.djl.training.util.ProgressBar
|
import ai.djl.training.util.ProgressBar
|
||||||
import ai.djl.translate.DeferredTranslatorFactory
|
import logger
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
// Model generated using the djl-converter utility. Then moved to resources before build
|
||||||
|
|
||||||
class SemanticSimilarity {
|
class SemanticSimilarity {
|
||||||
val manager = NDManager.newBaseManager()
|
|
||||||
val model = getCriteria().loadModel().newPredictor()
|
|
||||||
val tokenizer = HuggingFaceTokenizer.newInstance("sentence-transformers/all-mpnet-base-v2")
|
|
||||||
|
|
||||||
fun getCriteria(): Criteria<NDList, NDList> = Criteria.builder()
|
|
||||||
.setTypes(NDList::class.java, NDList::class.java)
|
|
||||||
.optModelPath(Paths.get(
|
|
||||||
javaClass.getResource("/Semantic-model")!!.toURI()
|
|
||||||
))
|
|
||||||
.optProgress(ProgressBar())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun compareSentenceToList(sentence: String, list: List<String>) {
|
|
||||||
val sentenceEmbedding = generateEmbedding(sentence)
|
|
||||||
val similarities = list.map { string ->
|
|
||||||
Pair(
|
|
||||||
string,
|
|
||||||
cosineSimilarity(sentenceEmbedding, generateEmbedding(string))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
similarities.forEach { similarity ->
|
|
||||||
println("Similarity between '$sentence' and '${similarity.first}': ${similarity.second}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun generateEmbedding(input: String): NDArray {
|
|
||||||
val inputList = tokenizer.encode(input).toNDList(manager, false)
|
|
||||||
val inputIds = inputList[0].expandDims(0)
|
|
||||||
val attentionMask = inputList[1].expandDims(0)
|
|
||||||
|
|
||||||
return model.predict(
|
|
||||||
NDList(inputIds, attentionMask)
|
|
||||||
)[0].mean(intArrayOf(1)).normalize(2.0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val manager = NDManager.newBaseManager()
|
||||||
|
val model = getModel()
|
||||||
|
val tokenizer = getTokenizer()
|
||||||
|
|
||||||
|
fun getModel(): Predictor<NDList, NDList> {
|
||||||
|
logger.info { "Semantic similarity model initialized" }
|
||||||
|
return getCriteria().loadModel().newPredictor()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTokenizer(): HuggingFaceTokenizer {
|
||||||
|
logger.info { "Semantic similarity tokenizer initialized" }
|
||||||
|
return HuggingFaceTokenizer.newInstance("sentence-transformers/all-mpnet-base-v2")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCriteria(): Criteria<NDList, NDList> = Criteria.builder()
|
||||||
|
.setTypes(NDList::class.java, NDList::class.java)
|
||||||
|
.optModelPath(Paths.get(
|
||||||
|
javaClass.getResource("/Semantic-model")!!.toURI()
|
||||||
|
))
|
||||||
|
.optProgress(ProgressBar())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun compareSentenceToList(sentence: String, list: List<String>): List<SimilarityScore> {
|
||||||
|
val sentenceEmbedding = generateEmbedding(sentence)
|
||||||
|
val similarities = list.map { string ->
|
||||||
|
SimilarityScore(
|
||||||
|
string,
|
||||||
|
cosineSimilarity(sentenceEmbedding, generateEmbedding(string))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return similarities
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun generateEmbedding(input: String): NDArray {
|
||||||
|
val inputList = tokenizer.encode(input).toNDList(manager, false)
|
||||||
|
val inputIds = inputList[0].expandDims(0)
|
||||||
|
val attentionMask = inputList[1].expandDims(0)
|
||||||
|
|
||||||
|
return model.predict(
|
||||||
|
NDList(inputIds, attentionMask)
|
||||||
|
)[0].mean(intArrayOf(1)).normalize(2.0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
fun cosineSimilarity(input: NDArray, command: NDArray): Float {
|
fun cosineSimilarity(input: NDArray, command: NDArray): Float {
|
||||||
val inputVec = input.squeeze(0)
|
val inputVec = input.squeeze(0)
|
||||||
val commandVec = command.squeeze(0)
|
val commandVec = command.squeeze(0)
|
||||||
val dotProduct = input.matMul(commandVec).getFloat()
|
val dotProduct = input.matMul(commandVec).getFloat()
|
||||||
val normInput = calculateNorm(inputVec)
|
val normInput = inputVec.norm().getFloat()
|
||||||
val normCommand = calculateNorm(commandVec)
|
val normCommand = commandVec.norm().getFloat()
|
||||||
|
|
||||||
return dotProduct / (normInput * normCommand)
|
return dotProduct / (normInput * normCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateNorm(array: NDArray) = sqrt(array.dot(array).getFloat())
|
data class SimilarityScore (
|
||||||
|
val sentence: String,
|
||||||
|
val score: Float,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package screens
|
|
||||||
|
|
||||||
import Microphone
|
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
|
||||||
import androidx.compose.material.icons.filled.Stop
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
|
|
||||||
class Home: Screen {
|
|
||||||
@Composable
|
|
||||||
@Preview
|
|
||||||
override fun Content() {
|
|
||||||
MaterialTheme {
|
|
||||||
Row {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
package screens
|
|
||||||
|
|
||||||
class Profiles {
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.util.Properties
|
|
||||||
|
|
||||||
class Settings: Screen {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun Content() {
|
|
||||||
Column {
|
|
||||||
userProperties.iterator().forEach { property ->
|
|
||||||
Row { //TODO: Make look nice
|
|
||||||
Text("${property.key}")
|
|
||||||
Text("${property.value}") //TODO: This should be a different input element dependent on expected dtype (maybe use prefixes to determine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val userProperties = getProperties()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun settingsButton() {
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
Button(onClick = {
|
|
||||||
navigator.push(Settings())
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Settings,
|
|
||||||
contentDescription = "screens.Settings"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getProperties(): Properties {
|
|
||||||
val properties = Properties()
|
|
||||||
properties.load(
|
|
||||||
FileInputStream(
|
|
||||||
javaClass.getResource("/user.properties")!!.file
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return properties
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import Microphone
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.Slider
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material.icons.filled.Stop
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import logger
|
||||||
|
import ui.screens.Settings
|
||||||
|
import ui.screens.Settings.Companion.userSettings
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class Capture {
|
||||||
|
companion object {
|
||||||
|
val captureLogger = KotlinLogging.logger("capture_logger")
|
||||||
|
|
||||||
|
|
||||||
|
fun getCaptureDuration() = userSettings.getProperty("capture_duration").toInt()
|
||||||
|
fun getCaptureOnClick(mic: Microphone) = if (!mic.capturing) mic.startCapture(getCaptureDuration()) else mic.stopCapture()
|
||||||
|
fun getCaptureIcon(capturing: Boolean) = if (!capturing) Icons.Filled.PlayArrow else Icons.Filled.Stop
|
||||||
|
fun getCaptureDescription(capturing: Boolean) = if (!capturing) "Start" else "Stop"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun captureContext(modifier: Modifier) {
|
||||||
|
Column(modifier) {
|
||||||
|
captureButton()
|
||||||
|
lastCapture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
@Composable
|
||||||
|
fun captureButton() {
|
||||||
|
val mic = Microphone()
|
||||||
|
var isCapturing by rememberSaveable { mutableStateOf(mic.capturing) }
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
getCaptureOnClick(mic)
|
||||||
|
isCapturing = !isCapturing
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = getCaptureIcon(isCapturing),
|
||||||
|
contentDescription = getCaptureDescription(isCapturing) + " audio capture"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun captureDurationSlider() {
|
||||||
|
var sliderPosition by remember { mutableStateOf(getCaptureDuration().toFloat()) }
|
||||||
|
Text("Capture Duration")
|
||||||
|
Slider(
|
||||||
|
valueRange = 1.0f..20.0f,
|
||||||
|
value = sliderPosition,
|
||||||
|
onValueChange = {
|
||||||
|
sliderPosition = it.roundToInt().toFloat()
|
||||||
|
userSettings.setProperty("capture_duration", "${it.roundToInt()}")
|
||||||
|
userSettings.save()
|
||||||
|
logger.info { "User Setting Changed: capture_duration = ${it.roundToInt()}" }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text("$sliderPosition seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun captureLog() {} //TODO: Display capture.{date}.log to screen
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun lastCapture() {} //TODO: Display capture.{date}.log last added line
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import ui.screens.Settings
|
||||||
|
|
||||||
|
class TopBar {
|
||||||
|
companion object {
|
||||||
|
@Composable
|
||||||
|
fun topBar(screenCount: Int) {
|
||||||
|
ConstraintLayout(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
val (back, capture, settings) = createRefs()
|
||||||
|
|
||||||
|
if (screenCount > 1) {
|
||||||
|
backButton(Modifier.constrainAs(back) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
start.linkTo(parent.start, margin = 10.dp)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
Capture.captureContext(Modifier.constrainAs(capture) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
centerHorizontallyTo(parent)
|
||||||
|
})
|
||||||
|
Settings.settingsButton(Modifier.constrainAs(settings) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
end.linkTo(parent.end, margin = 10.dp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun backButton(modifier: Modifier) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = { navigator.pop()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ArrowBackIosNew,
|
||||||
|
contentDescription = "Go Back"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import ui.Capture
|
||||||
|
|
||||||
|
class Home: Screen {
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
override fun Content() {
|
||||||
|
Capture.captureLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package ui.screens
|
||||||
|
|
||||||
|
class Profiles {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ui.screens
|
||||||
|
|
||||||
|
import UserSettings
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import ui.Capture
|
||||||
|
|
||||||
|
class Settings: Screen {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
Column {
|
||||||
|
userSettings.forEach { setting ->
|
||||||
|
Spacer(Modifier.padding(10.dp))
|
||||||
|
setting(setting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val userSettings = UserSettings()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun settingsButton(modifier: Modifier) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
Button(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = {
|
||||||
|
navigator.push(Settings())
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Settings,
|
||||||
|
contentDescription = "Settings"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun setting(setting: Map.Entry<Any?, Any?>) {
|
||||||
|
when(setting.key) {
|
||||||
|
"capture_duration" -> Capture.captureDurationSlider()
|
||||||
|
"ai_name" -> null //TODO: TextField
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ import com.sun.jna.platform.win32.WinDef
|
||||||
import com.sun.jna.win32.W32APIOptions
|
import com.sun.jna.win32.W32APIOptions
|
||||||
import java.awt.Point
|
import java.awt.Point
|
||||||
import java.awt.Robot
|
import java.awt.Robot
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
|
|
||||||
class WindowController { //TODO: Class that controls program window
|
class WindowController { //TODO: Class that controls program window
|
||||||
private val os = OS.valueOf(System.getProperty("os.name"))
|
private val os = OS.valueOf(System.getProperty("os.name"))
|
||||||
|
|
@ -21,17 +20,17 @@ class WindowController { //TODO: Class that controls program window
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendKeyPresses(keyCodes: Array<Int>) {
|
fun sendKeyPresses(keyCodes: Array<Int>) {
|
||||||
keyCodes.iterator().forEach { keyCode ->
|
keyCodes.forEach { keyCode ->
|
||||||
inputController.keyPress(keyCode)
|
inputController.keyPress(keyCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun sendKeyReleases(keyCodes: Array<Int>) {
|
fun sendKeyReleases(keyCodes: Array<Int>) {
|
||||||
keyCodes.iterator().forEach { keyCode ->
|
keyCodes.forEach { keyCode ->
|
||||||
inputController.keyRelease(keyCode)
|
inputController.keyRelease(keyCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun sendKeyStrokes(keyCodes: Array<Int>, delay: Int?) { //TODO: Create KeyList Class
|
fun sendKeyStrokes(keyCodes: Array<Int>, delay: Int?) { //TODO: Create KeyList Class
|
||||||
keyCodes.iterator().forEach { keyCode ->
|
keyCodes.forEach { keyCode ->
|
||||||
sendKeyPresses(arrayOf(keyCode))
|
sendKeyPresses(arrayOf(keyCode))
|
||||||
inputController.delay(delay!!)
|
inputController.delay(delay!!)
|
||||||
sendKeyReleases(arrayOf(keyCode))
|
sendKeyReleases(arrayOf(keyCode))
|
||||||
|
|
@ -39,7 +38,7 @@ class WindowController { //TODO: Class that controls program window
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMultiKeyStrokes(commands: Array<Array<Int>>, delay: Int?) { //TODO: Create MultiKeyList Class
|
fun sendMultiKeyStrokes(commands: Array<Array<Int>>, delay: Int?) { //TODO: Create MultiKeyList Class
|
||||||
commands.iterator().forEach { command ->
|
commands.forEach { command ->
|
||||||
sendMousePresses(command)
|
sendMousePresses(command)
|
||||||
inputController.delay(delay!!)
|
inputController.delay(delay!!)
|
||||||
sendMouseReleases(command)
|
sendMouseReleases(command)
|
||||||
|
|
@ -47,12 +46,12 @@ class WindowController { //TODO: Class that controls program window
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMousePresses(buttonCodes: Array<Int>) { //TODO: Create ButtonList Class
|
fun sendMousePresses(buttonCodes: Array<Int>) { //TODO: Create ButtonList Class
|
||||||
buttonCodes.iterator().forEach { buttonCode ->
|
buttonCodes.forEach { buttonCode ->
|
||||||
inputController.mousePress(buttonCode)
|
inputController.mousePress(buttonCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun sendMouseReleases(buttonCodes: Array<Int>) {
|
fun sendMouseReleases(buttonCodes: Array<Int>) {
|
||||||
buttonCodes.iterator().forEach { buttonCode ->
|
buttonCodes.forEach { buttonCode ->
|
||||||
inputController.mouseRelease(buttonCode)
|
inputController.mouseRelease(buttonCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +59,7 @@ class WindowController { //TODO: Class that controls program window
|
||||||
inputController.mouseMove(location.x, location.y)
|
inputController.mouseMove(location.x, location.y)
|
||||||
}
|
}
|
||||||
fun sendMouseStrokes(buttonCodes: Array<Int>, delay: Int?) {
|
fun sendMouseStrokes(buttonCodes: Array<Int>, delay: Int?) {
|
||||||
buttonCodes.iterator().forEach { buttonCode ->
|
buttonCodes.forEach { buttonCode ->
|
||||||
sendMousePresses(arrayOf(buttonCode))
|
sendMousePresses(arrayOf(buttonCode))
|
||||||
inputController.delay(delay!!)
|
inputController.delay(delay!!)
|
||||||
sendMouseReleases(arrayOf(buttonCode))
|
sendMouseReleases(arrayOf(buttonCode))
|
||||||
|
|
@ -68,12 +67,12 @@ class WindowController { //TODO: Class that controls program window
|
||||||
}
|
}
|
||||||
fun sendMouseStrokesAtLocation(buttonCodes: Array<Int>, delay: Int?, location: Point) {
|
fun sendMouseStrokesAtLocation(buttonCodes: Array<Int>, delay: Int?, location: Point) {
|
||||||
sendMouseLocation(location)
|
sendMouseLocation(location)
|
||||||
buttonCodes.iterator().forEach { buttonCode ->
|
buttonCodes.forEach { buttonCode ->
|
||||||
sendMouseStrokes(arrayOf(buttonCode), delay)
|
sendMouseStrokes(arrayOf(buttonCode), delay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, delay: Int?) {
|
fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, delay: Int?) {
|
||||||
commands.iterator().forEach { command ->
|
commands.forEach { command ->
|
||||||
sendMouseLocation(command.first)
|
sendMouseLocation(command.first)
|
||||||
sendMouseStrokes(command.second, delay)
|
sendMouseStrokes(command.second, delay)
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +88,7 @@ class WindowController { //TODO: Class that controls program window
|
||||||
}
|
}
|
||||||
private fun activateLinuxWindow(windowName: String) {
|
private fun activateLinuxWindow(windowName: String) {
|
||||||
val xorgFlag = System.getenv("DISPLAY")
|
val xorgFlag = System.getenv("DISPLAY")
|
||||||
val hyprlandFlag = System.getenv("HYPRLAND_CMD") //TODO: User override of default checks, and add sway compatibility
|
val hyprlandFlag = System.getenv("HYPRLAND_CMD") //TODO: User override of default checks (can specify what platform), and add sway compatibility
|
||||||
|
|
||||||
if (hyprlandFlag != null) {
|
if (hyprlandFlag != null) {
|
||||||
val command = arrayOf("hyprctl", "dispatch", "focuswindow", windowName) //TODO: need to check both class regex and title regex (look at hyprland wiki)
|
val command = arrayOf("hyprctl", "dispatch", "focuswindow", windowName) //TODO: need to check both class regex and title regex (look at hyprland wiki)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<configuration>
|
||||||
|
<!-- Define the log levels -->
|
||||||
|
<logger name="capture_logger" level="DEBUG">
|
||||||
|
<appender-ref ref="CAPTURE_LOGS_FILE" />
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="APPLICATION_LOGS_FILE" />
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!-- Define the specific class appender -->
|
||||||
|
<appender name="CAPTURE_LOGS_FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>logs/capture.log</file>
|
||||||
|
<append>true</append>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Define the appender for other logs -->
|
||||||
|
<appender name="APPLICATION_LOGS_FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>logs/application.log</file>
|
||||||
|
<append>true</append>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Define the STDOUT appender -->
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
capture_duration=5
|
|
||||||
Loading…
Reference in New Issue