....
This commit is contained in:
@@ -24,6 +24,7 @@ import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
import io.ktor.client.plugins.logging.Logging
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import model.AppConfig
|
||||
@@ -53,17 +54,22 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(20.dp)) {
|
||||
item {
|
||||
Text("거래 방식 선택", style = MaterialTheme.typography.h6)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), // 필요에 따라 너비 설정
|
||||
verticalAlignment = Alignment.CenterVertically // 상하 중앙 정렬 추가
|
||||
){
|
||||
Text("투자 방식", style = MaterialTheme.typography.subtitle1)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
RadioButton(selected = !config.isSimulation, onClick = { config = config.copy(isSimulation = false,) })
|
||||
Text("실전투자")
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Text("실전")
|
||||
Spacer(Modifier.width(10.dp))
|
||||
RadioButton(selected = config.isSimulation, onClick = { config = config.copy() })
|
||||
Text("모의투자")
|
||||
Text("모의")
|
||||
}
|
||||
Divider(Modifier.padding(vertical = 12.dp))
|
||||
|
||||
Divider(Modifier.padding(vertical = 10.dp))
|
||||
OutlinedTextField(
|
||||
value = config.htsId,
|
||||
onValueChange = { config = config.copy(htsId = it,) },
|
||||
@@ -73,94 +79,126 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
|
||||
)
|
||||
// 실전 3종 입력
|
||||
Text("실전투자 정보 (시세 조회 필수)", fontWeight = FontWeight.Bold)
|
||||
OutlinedTextField(value = config.realAccountNo, onValueChange = {
|
||||
config = config.copy(realAccountNo = it,)
|
||||
if(it.length >= 8) checkAndLoadConfig(it, true)
|
||||
}, label = { Text("실전 계좌번호") }, modifier = Modifier.fillMaxWidth())
|
||||
OutlinedTextField(value = config.realAppKey, onValueChange = { config = config.copy(realAppKey = it,) }, label = { Text("실전 App Key") }, modifier = Modifier.fillMaxWidth())
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), // 필요에 따라 너비 설정
|
||||
verticalAlignment = Alignment.CenterVertically // 상하 중앙 정렬 추가
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = config.realAccountNo, onValueChange = {
|
||||
config = config.copy(realAccountNo = it,)
|
||||
if (it.length >= 8) checkAndLoadConfig(it, true)
|
||||
}, label = { Text("실전 계좌번호") }, modifier = Modifier.weight(0.5f))
|
||||
OutlinedTextField(
|
||||
value = config.realAppKey,
|
||||
onValueChange = { config = config.copy(realAppKey = it,) },
|
||||
label = { Text("실전 App Key") },
|
||||
modifier = Modifier.weight(0.5f)
|
||||
)
|
||||
}
|
||||
OutlinedTextField(value = config.realSecretKey, onValueChange = { config = config.copy(realSecretKey = it,) }, label = { Text("실전 Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
// 모의 3종 입력
|
||||
Text("모의투자 정보", fontWeight = FontWeight.Bold)
|
||||
OutlinedTextField(value = config.vtsAccountNo, onValueChange = {
|
||||
config = config.copy(vtsAccountNo = it,)
|
||||
if(it.length >= 8) checkAndLoadConfig(it, false)
|
||||
}, label = { Text("모의 계좌번호") }, modifier = Modifier.fillMaxWidth())
|
||||
OutlinedTextField(value = config.vtsAppKey, onValueChange = { config = config.copy(vtsAppKey = it,) }, label = { Text("모의 App Key") }, modifier = Modifier.fillMaxWidth())
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), // 필요에 따라 너비 설정
|
||||
verticalAlignment = Alignment.CenterVertically // 상하 중앙 정렬 추가
|
||||
) {
|
||||
OutlinedTextField(value = config.vtsAccountNo, onValueChange = {
|
||||
config = config.copy(vtsAccountNo = it,)
|
||||
if (it.length >= 8) checkAndLoadConfig(it, false)
|
||||
}, label = { Text("모의 계좌번호") }, modifier = Modifier.weight(0.5f))
|
||||
OutlinedTextField(
|
||||
value = config.vtsAppKey,
|
||||
onValueChange = { config = config.copy(vtsAppKey = it,) },
|
||||
label = { Text("모의 App Key") },
|
||||
modifier = Modifier.weight(0.5f)
|
||||
)
|
||||
}
|
||||
OutlinedTextField(value = config.vtsSecretKey, onValueChange = { config = config.copy(vtsSecretKey = it,) }, label = { Text("모의 Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
|
||||
|
||||
Divider(Modifier.padding(vertical = 16.dp))
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text("정보 조회 Api Keyz", fontWeight = FontWeight.Bold)
|
||||
OutlinedTextField(value = config.nAppKey, onValueChange = { config = config.copy(nAppKey = it,) }, label = { Text("NAVER Client ID") }, modifier = Modifier.fillMaxWidth())
|
||||
OutlinedTextField(value = config.nSecretKey, onValueChange = { config = config.copy(nSecretKey = it,) }, label = { Text("NAVER Client Secret") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
|
||||
OutlinedTextField(value = config.dAppKey, onValueChange = { config = config.copy(dAppKey = it,) }, label = { Text("Dart ApiKey") }, modifier = Modifier.fillMaxWidth())
|
||||
|
||||
// AI 모델 경로 및 드래그 앤 드롭
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text("AI 모델 설정", fontWeight = FontWeight.Bold)
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(100.dp).border(1.dp, Color.Gray, 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) config = config.copy(modelPath = path,)
|
||||
}
|
||||
}),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(if(config.modelPath.isEmpty()) "GGUF 모델 파일을 여기로 드래그하세요" else config.modelPath, fontSize = 12.sp)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), // 필요에 따라 너비 설정
|
||||
verticalAlignment = Alignment.CenterVertically // 상하 중앙 정렬 추가
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier.weight(0.5f).height(60.dp).border(1.dp, Color.Gray, 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) config = config.copy(modelPath = path,)
|
||||
}
|
||||
}),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(if(config.modelPath.isEmpty()) "GGUF 모델 파일을 여기로 드래그하세요" else config.modelPath, fontSize = 12.sp)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.weight(0.5f).height(60.dp).border(1.dp, Color.Gray, RoundedCornerShape(8.dp))
|
||||
.onExternalDrag(onDrop = { state ->
|
||||
val data = state.dragData
|
||||
if (data is DragData.FilesList) {
|
||||
val embedModelPath = data.readFiles().firstOrNull()?.removePrefix("file:")
|
||||
if (embedModelPath?.endsWith(".gguf") == true) config = config.copy(embedModelPath = embedModelPath,)
|
||||
}
|
||||
}),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(if(config.embedModelPath.isEmpty()) "임베드용 GGUF 모델 파일을 여기로 드래그하세요" else config.embedModelPath, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(100.dp).border(1.dp, Color.Gray, RoundedCornerShape(8.dp))
|
||||
.onExternalDrag(onDrop = { state ->
|
||||
val data = state.dragData
|
||||
if (data is DragData.FilesList) {
|
||||
val embedModelPath = data.readFiles().firstOrNull()?.removePrefix("file:")
|
||||
if (embedModelPath?.endsWith(".gguf") == true) config = config.copy(embedModelPath = embedModelPath,)
|
||||
}
|
||||
}),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(if(config.embedModelPath.isEmpty()) "임베드용 GGUF 모델 파일을 여기로 드래그하세요" else config.embedModelPath, fontSize = 12.sp)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().height(50.dp),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
// isLoading = true
|
||||
// 1. KisSession.config 업데이트 및 DB 저장
|
||||
KisSession.config = config
|
||||
DatabaseFactory.saveConfig(config)
|
||||
DartCodeManager.updateCorpCodes(HttpClient(CIO) {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true // 기본값이 포함된 요청 바디를 정확히 전송하기 위해 필요
|
||||
})
|
||||
}
|
||||
// [수정] 모든 로그(Headers + Body)를 찍도록 설정
|
||||
install(Logging) {
|
||||
logger = Logger.DEFAULT
|
||||
level = LogLevel.BODY
|
||||
}
|
||||
})
|
||||
val authService = KisAuthService
|
||||
val tradeService = KisTradeService
|
||||
val authSuccess = authService.refreshAllTokens()
|
||||
val wsKeySuccess = tradeService.refreshWebsocketKey()
|
||||
var retryCount = 0
|
||||
val maxRetries = 3
|
||||
val retryDelay = 90_000L // 1분 30초
|
||||
var isAuthCompleted = false
|
||||
|
||||
if (authSuccess && wsKeySuccess) {
|
||||
statusMessage = "✅ 인증 성공! LLM 시작 중..."
|
||||
onAuthSuccess()
|
||||
} else {
|
||||
statusMessage = "❌ 인증 실패. 키 정보를 확인하세요."
|
||||
while (retryCount <= maxRetries && !isAuthCompleted) {
|
||||
if (retryCount > 0) {
|
||||
statusMessage = "⏳ 인증 재시도 중... (${retryCount}/${maxRetries}) - 1분 30초 후 재시작"
|
||||
delay(retryDelay)
|
||||
}
|
||||
|
||||
// 1. 설정값 저장
|
||||
KisSession.config = config
|
||||
DatabaseFactory.saveConfig(config)
|
||||
|
||||
// 2. 법인코드 업데이트
|
||||
DartCodeManager.updateCorpCodes(HttpClient(CIO) {
|
||||
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
|
||||
})
|
||||
|
||||
// 3. 토큰 및 웹소켓 키 갱신
|
||||
val authSuccess = KisAuthService.refreshAllTokens()
|
||||
val wsKeySuccess = KisTradeService.refreshWebsocketKey()
|
||||
|
||||
if (authSuccess && wsKeySuccess) {
|
||||
statusMessage = "✅ 인증 성공! LLM 시작 중..."
|
||||
isAuthCompleted = true
|
||||
onAuthSuccess()
|
||||
} else {
|
||||
retryCount++
|
||||
if (retryCount > maxRetries) {
|
||||
statusMessage = "❌ 인증 실패. 3회 재시도 후 중단되었습니다. 키 정보를 확인하세요."
|
||||
} else {
|
||||
statusMessage = "⚠️ 인증 실패. 잠시 후 자동으로 다시 시도합니다. (시도 $retryCount)"
|
||||
}
|
||||
}
|
||||
}
|
||||
// isLoading = false
|
||||
}
|
||||
}
|
||||
) { Text("설정 저장 및 실행") }
|
||||
|
||||
Reference in New Issue
Block a user