settings defaults. logging. misc
This commit is contained in:
parent
6f94f60b15
commit
0b254654ef
|
|
@ -46,3 +46,6 @@ bin/
|
|||
|
||||
### STT model ###
|
||||
/src/main/resources/STT-model/
|
||||
/src/main/resources/Semantic-model/
|
||||
/logs/
|
||||
/user.properties
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ dependencies {
|
|||
// Core
|
||||
implementation(compose.desktop.currentOs)
|
||||
|
||||
// ConstrainLayout
|
||||
implementation("tech.annexflow.compose:constraintlayout-compose-multiplatform:0.4.0")
|
||||
|
||||
// Material Design
|
||||
implementation(compose.materialIconsExtended)
|
||||
|
||||
|
|
@ -37,15 +40,10 @@ dependencies {
|
|||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
||||
|
||||
// Voyager
|
||||
// Navigator
|
||||
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
|
||||
// Screen Model
|
||||
implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion")
|
||||
// BottomSheetNavigator
|
||||
implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion")
|
||||
// TabNavigator
|
||||
implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion")
|
||||
// Transitions
|
||||
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
||||
|
||||
// Deep Java Library
|
||||
|
|
@ -57,8 +55,9 @@ dependencies {
|
|||
// Semantic Kernel
|
||||
implementation("com.microsoft.semantic-kernel:semantickernel-api:1.3.0")
|
||||
|
||||
// SLF4J
|
||||
implementation("org.slf4j:slf4j-api:2.0.16")
|
||||
// Logging
|
||||
implementation ("io.github.oshai:kotlin-logging-jvm:7.0.0")
|
||||
implementation ("ch.qos.logback:logback-classic:1.5.11")
|
||||
}
|
||||
|
||||
compose.desktop {
|
||||
|
|
|
|||
|
|
@ -1,92 +1,44 @@
|
|||
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.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.application
|
||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import processing.SemanticSimilarity
|
||||
import screens.Home
|
||||
import screens.Settings
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import ui.screens.Home
|
||||
import ui.TopBar
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
val logger = KotlinLogging.logger {}
|
||||
|
||||
fun main() = application {
|
||||
val testSentence = "This is a test sentence for semantic similarity"
|
||||
val testCommands = listOf("This is a test", "This sentence is completely different", "Testing semantic similarity between sentence", "An unrelated statement")
|
||||
|
||||
val semanticTester = SemanticSimilarity()
|
||||
semanticTester.compareSentenceToList(testSentence, testCommands)
|
||||
|
||||
logger.info { "\n================== NEW APPLICATION SESSION ==================" }
|
||||
checkLogsDir()
|
||||
Window(onCloseRequest = ::exitApplication) {
|
||||
Navigator(Home()) { navigator ->
|
||||
Scaffold(
|
||||
topBar = { topBar(navigator.size) },
|
||||
content = { CurrentScreen() },
|
||||
bottomBar = { }
|
||||
)
|
||||
MaterialTheme {
|
||||
Navigator(Home()) { navigator ->
|
||||
Scaffold(
|
||||
topBar = { TopBar.topBar(navigator.size) },
|
||||
content = { CurrentScreen() },
|
||||
bottomBar = { }
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info { "Application started" }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun topBar(screenCount: Int) {
|
||||
Row {
|
||||
if (screenCount > 1) {
|
||||
backButton()
|
||||
}
|
||||
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"
|
||||
)
|
||||
fun checkLogsDir() {
|
||||
val dir = File(Paths.get("logs/").absolutePathString())
|
||||
if (!dir.exists()) {
|
||||
dir.createNewFile()
|
||||
logger.info { "Folder Created: logs/" }
|
||||
}
|
||||
}
|
||||
|
||||
//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.TargetDataLine
|
||||
import processing.STT
|
||||
import ui.Capture.Companion.captureLogger
|
||||
|
||||
/**
|
||||
* TODO: Documentation
|
||||
|
|
@ -25,6 +26,7 @@ class Microphone {
|
|||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun startCapture(captureDuration: Int) {
|
||||
captureLogger.info { "\n================== NEW CAPTURE SESSION ==================" }
|
||||
capturing = true
|
||||
source.open(audioFormat, source.bufferSize)
|
||||
source.start()
|
||||
|
|
@ -36,8 +38,8 @@ class Microphone {
|
|||
while (capturing) {
|
||||
val audioBytes = source.read(buffer, 0, buffer.size)
|
||||
val result = stt.parseBuffer(buffer, audioBytes)
|
||||
println("Captured Speech: $result")
|
||||
//TODO: Pass onto semantic similarity
|
||||
captureLogger.info { ("Captured Speech: $result") }
|
||||
//TODO: Pass onto processing
|
||||
}
|
||||
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
|
||||
|
||||
import logger
|
||||
import org.vosk.Model
|
||||
import org.vosk.Recognizer
|
||||
import java.nio.file.Paths
|
||||
|
|
@ -12,33 +13,37 @@ class STT {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
private val model = Model(getModelPath())
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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 parseBuffer(buffer: ByteArray, audioBytes: Int): String? = if (recognizer.acceptWaveForm(buffer, audioBytes)) recognizer.result else recognizer.partialResult
|
||||
|
||||
fun closeModel() {
|
||||
recognizer.close()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private fun getModelPath() = Paths.get(
|
||||
javaClass.getResource("/STT-model")!!.toURI()
|
||||
).absolutePathString()
|
||||
companion object {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private val model = getModel()
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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
|
||||
|
||||
import ai.djl.Application
|
||||
import ai.djl.Model
|
||||
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.huggingface.tokenizers.HuggingFaceTokenizer
|
||||
import ai.djl.inference.Predictor
|
||||
import ai.djl.ndarray.NDArray
|
||||
import ai.djl.ndarray.NDList
|
||||
import ai.djl.ndarray.NDManager
|
||||
import ai.djl.repository.zoo.Criteria
|
||||
import ai.djl.training.util.ProgressBar
|
||||
import ai.djl.translate.DeferredTranslatorFactory
|
||||
import logger
|
||||
import java.nio.file.Paths
|
||||
import kotlin.math.sqrt
|
||||
|
||||
// Model generated using the djl-converter utility. Then moved to resources before build
|
||||
|
||||
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 {
|
||||
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 {
|
||||
val inputVec = input.squeeze(0)
|
||||
val commandVec = command.squeeze(0)
|
||||
val dotProduct = input.matMul(commandVec).getFloat()
|
||||
val normInput = calculateNorm(inputVec)
|
||||
val normCommand = calculateNorm(commandVec)
|
||||
val normInput = inputVec.norm().getFloat()
|
||||
val normCommand = commandVec.norm().getFloat()
|
||||
|
||||
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 java.awt.Point
|
||||
import java.awt.Robot
|
||||
import java.awt.event.KeyEvent
|
||||
|
||||
class WindowController { //TODO: Class that controls program window
|
||||
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>) {
|
||||
keyCodes.iterator().forEach { keyCode ->
|
||||
keyCodes.forEach { keyCode ->
|
||||
inputController.keyPress(keyCode)
|
||||
}
|
||||
}
|
||||
fun sendKeyReleases(keyCodes: Array<Int>) {
|
||||
keyCodes.iterator().forEach { keyCode ->
|
||||
keyCodes.forEach { keyCode ->
|
||||
inputController.keyRelease(keyCode)
|
||||
}
|
||||
}
|
||||
fun sendKeyStrokes(keyCodes: Array<Int>, delay: Int?) { //TODO: Create KeyList Class
|
||||
keyCodes.iterator().forEach { keyCode ->
|
||||
keyCodes.forEach { keyCode ->
|
||||
sendKeyPresses(arrayOf(keyCode))
|
||||
inputController.delay(delay!!)
|
||||
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
|
||||
commands.iterator().forEach { command ->
|
||||
commands.forEach { command ->
|
||||
sendMousePresses(command)
|
||||
inputController.delay(delay!!)
|
||||
sendMouseReleases(command)
|
||||
|
|
@ -47,12 +46,12 @@ class WindowController { //TODO: Class that controls program window
|
|||
}
|
||||
|
||||
fun sendMousePresses(buttonCodes: Array<Int>) { //TODO: Create ButtonList Class
|
||||
buttonCodes.iterator().forEach { buttonCode ->
|
||||
buttonCodes.forEach { buttonCode ->
|
||||
inputController.mousePress(buttonCode)
|
||||
}
|
||||
}
|
||||
fun sendMouseReleases(buttonCodes: Array<Int>) {
|
||||
buttonCodes.iterator().forEach { buttonCode ->
|
||||
buttonCodes.forEach { buttonCode ->
|
||||
inputController.mouseRelease(buttonCode)
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +59,7 @@ class WindowController { //TODO: Class that controls program window
|
|||
inputController.mouseMove(location.x, location.y)
|
||||
}
|
||||
fun sendMouseStrokes(buttonCodes: Array<Int>, delay: Int?) {
|
||||
buttonCodes.iterator().forEach { buttonCode ->
|
||||
buttonCodes.forEach { buttonCode ->
|
||||
sendMousePresses(arrayOf(buttonCode))
|
||||
inputController.delay(delay!!)
|
||||
sendMouseReleases(arrayOf(buttonCode))
|
||||
|
|
@ -68,12 +67,12 @@ class WindowController { //TODO: Class that controls program window
|
|||
}
|
||||
fun sendMouseStrokesAtLocation(buttonCodes: Array<Int>, delay: Int?, location: Point) {
|
||||
sendMouseLocation(location)
|
||||
buttonCodes.iterator().forEach { buttonCode ->
|
||||
buttonCodes.forEach { buttonCode ->
|
||||
sendMouseStrokes(arrayOf(buttonCode), delay)
|
||||
}
|
||||
}
|
||||
fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, delay: Int?) {
|
||||
commands.iterator().forEach { command ->
|
||||
commands.forEach { command ->
|
||||
sendMouseLocation(command.first)
|
||||
sendMouseStrokes(command.second, delay)
|
||||
}
|
||||
|
|
@ -89,7 +88,7 @@ class WindowController { //TODO: Class that controls program window
|
|||
}
|
||||
private fun activateLinuxWindow(windowName: String) {
|
||||
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) {
|
||||
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