package ui import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.DragData import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.onExternalDrag import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import model.AppConfig import network.KisAuthService import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.transactions.transaction import javax.swing.JFileChooser import javax.swing.filechooser.FileNameExtensionFilter import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog // 파일 선택기용 @OptIn(ExperimentalComposeUiApi::class) @Composable fun SettingsScreen( initialConfig: AppConfig, // 모델 경로가 포함된 확장된 AppConfig 필요 onAuthSuccess: (AppConfig, String) -> Unit ) { val scope = rememberCoroutineScope() val authService = remember { KisAuthService() } // 화면 입력 상태값 var appKey by remember { mutableStateOf(initialConfig.appKey) } var secretKey by remember { mutableStateOf(initialConfig.secretKey) } var accountNo by remember { mutableStateOf(initialConfig.accountNo) } var isSimulation by remember { mutableStateOf(initialConfig.isSimulation) } var modelPath by remember { mutableStateOf(initialConfig.modelPath ?: "") } // AI 모델 경로 var statusMessage by remember { mutableStateOf("설정 정보를 입력하세요.") } var isLoading by remember { mutableStateOf(false) } LazyColumn(modifier = Modifier.fillMaxSize().padding(24.dp)) { item { Text("API 및 계좌 설정", style = MaterialTheme.typography.h6) OutlinedTextField(value = appKey, onValueChange = { appKey = it }, label = { Text("App Key") }, modifier = Modifier.fillMaxWidth()) OutlinedTextField(value = secretKey, onValueChange = { secretKey = it }, label = { Text("Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation()) OutlinedTextField(value = accountNo, onValueChange = { accountNo = it }, label = { Text("계좌번호") }, modifier = Modifier.fillMaxWidth()) Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = isSimulation, onCheckedChange = { isSimulation = it }) Text("모의투자 서버 사용") } Divider(Modifier.padding(vertical = 16.dp)) // --- 추가된 AI 모델 설정 섹션 --- Text("AI 모델 설정 (Gemma-2-9b)", style = MaterialTheme.typography.h6) Spacer(modifier = Modifier.height(8.dp)) Row(verticalAlignment = Alignment.CenterVertically) { OutlinedTextField( value = modelPath, onValueChange = { modelPath = it }, label = { Text("GGUF 모델 경로") }, modifier = Modifier.weight(1f), placeholder = { Text("파일을 선택하거나 드래그하세요") } ) IconButton(onClick = { val chooser = JFileChooser().apply { fileFilter = FileNameExtensionFilter("GGUF 모델", "gguf") } if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { modelPath = chooser.selectedFile.absolutePath } }) { Icon(Icons.Default.FolderOpen, contentDescription = "파일 선택") } } // 드래그 앤 드롭 영역 Box( modifier = Modifier .fillMaxWidth() .height(80.dp) .padding(top = 8.dp) .border(1.dp, Color.LightGray, RoundedCornerShape(8.dp)) .onExternalDrag(onDrop = { state -> val data = state.dragData if (data is DragData.FilesList) { val path = data.readFiles().firstOrNull()?.removePrefix("file:") if (path?.endsWith(".gguf") == true) modelPath = path } }), contentAlignment = Alignment.Center ) { Text("여기에 .gguf 파일을 드래그하여 놓으세요", fontSize = 12.sp, color = Color.Gray) } Spacer(modifier = Modifier.height(24.dp)) // 저장 및 접속 버튼 Button( modifier = Modifier.fillMaxWidth().height(50.dp), enabled = !isLoading, onClick = { isLoading = true scope.launch { // 1. 새로운 설정 객체 생성 (순서 주의: isSimulation 다음 modelPath) val config = AppConfig( appKey = appKey.trim(), secretKey = secretKey.trim(), accountNo = accountNo.trim(), isSimulation = isSimulation, modelPath = modelPath ) transaction { ConfigTable.deleteAll() ConfigTable.insert { it[ConfigTable.appKey] = config.appKey it[ConfigTable.secretKey] = config.secretKey it[ConfigTable.accountNo] = config.accountNo it[ConfigTable.isSimulation] = config.isSimulation it[ConfigTable.modelPath] = config.modelPath } } statusMessage = "인증 토큰 발급 시도 중..." authService.fetchAccessToken(appKey, secretKey, isSimulation) .onSuccess { response -> statusMessage = "✅ 인증 성공!" onAuthSuccess(config, response.access_token) }.onFailure { statusMessage = "❌ 인증 실패(정보 저장됨): ${it.localizedMessage}" } isLoading = false } } ) { if (isLoading) { // [수정된 프로그래스 바] size -> Modifier.size CircularProgressIndicator( modifier = Modifier.size(20.dp), color = Color.White, strokeWidth = 2.dp ) } else { Text("설정 저장 및 접속 시작") } } Spacer(modifier = Modifier.height(16.dp)) Text(statusMessage, color = if (statusMessage.contains("✅")) Color.Green else Color.Gray) } } }