Compare commits
2 Commits
edec3c4de0
...
56945a56d1
| Author | SHA1 | Date | |
|---|---|---|---|
| 56945a56d1 | |||
| 3234581163 |
@ -46,6 +46,12 @@ dependencies {
|
|||||||
// Coroutines
|
// Coroutines
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.1")
|
||||||
|
|
||||||
|
val langchain4jVersion = "0.31.0"
|
||||||
|
implementation("dev.langchain4j:langchain4j:$langchain4jVersion")
|
||||||
|
// llama.cpp 서버가 OpenAI API와 호환되므로 이 라이브러리를 사용합니다.
|
||||||
|
implementation("dev.langchain4j:langchain4j-open-ai:$langchain4jVersion")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compose.desktop {
|
compose.desktop {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import ui.SettingsScreen
|
|||||||
enum class AppScreen { Settings, Dashboard }
|
enum class AppScreen { Settings, Dashboard }
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
|
|
||||||
// 앱 실행 시 필요한 바이너리 경로 (실행 파일 위치)
|
// 앱 실행 시 필요한 바이너리 경로 (실행 파일 위치)
|
||||||
val binPath = "./src/main/resources/bin/llama-server"
|
val binPath = "./src/main/resources/bin/llama-server"
|
||||||
|
|
||||||
@ -40,7 +41,8 @@ fun main() = application {
|
|||||||
vtsAccountNo = it[ConfigTable.vtsAccountNo],
|
vtsAccountNo = it[ConfigTable.vtsAccountNo],
|
||||||
isSimulation = it[ConfigTable.isSimulation],
|
isSimulation = it[ConfigTable.isSimulation],
|
||||||
htsId = it[ConfigTable.htsId],
|
htsId = it[ConfigTable.htsId],
|
||||||
modelPath = it[ConfigTable.modelPath]
|
modelPath = it[ConfigTable.modelPath],
|
||||||
|
embedModelPath = it[ConfigTable.embedModelPath]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +64,10 @@ fun main() = application {
|
|||||||
|
|
||||||
// LLM 서버 시작 (설정된 모델 경로 사용)
|
// LLM 서버 시작 (설정된 모델 경로 사용)
|
||||||
if (config.modelPath.isNotEmpty()) {
|
if (config.modelPath.isNotEmpty()) {
|
||||||
LlamaServerManager.startServer(binPath, config.modelPath)
|
LlamaServerManager.startServer(binPath, config.modelPath,port = 8080)
|
||||||
|
}
|
||||||
|
if (config.embedModelPath.isNotEmpty()) {
|
||||||
|
LlamaServerManager.startServer(binPath, config.embedModelPath, port = 8081)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 대시보드로 화면 전환
|
// 대시보드로 화면 전환
|
||||||
|
|||||||
@ -27,6 +27,7 @@ object ConfigTable : Table("app_config") {
|
|||||||
val vtsAccountNo = varchar("vts_account_no", 20).default("")
|
val vtsAccountNo = varchar("vts_account_no", 20).default("")
|
||||||
val isSimulation = bool("is_simulation").default(true)
|
val isSimulation = bool("is_simulation").default(true)
|
||||||
val modelPath = varchar("model_path", 512).default("")
|
val modelPath = varchar("model_path", 512).default("")
|
||||||
|
val embedModelPath = varchar("embed_model_path", 512).default("")
|
||||||
val htsId = varchar("hts_id", 50).default("") // HTS ID 컬럼 추가
|
val htsId = varchar("hts_id", 50).default("") // HTS ID 컬럼 추가
|
||||||
override val primaryKey = PrimaryKey(id)
|
override val primaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
@ -61,6 +62,27 @@ object TradeLogTable : Table("trade_logs") {
|
|||||||
override val primaryKey = PrimaryKey(id)
|
override val primaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class VectorColumnType(private val dimension: Int) : ColumnType<String>() {
|
||||||
|
// [수정] H2에서 ARRAY는 내부 타입을 명시해야 합니다.
|
||||||
|
// VECTOR 대신 FLOAT8 ARRAY를 사용하면 벡터 연산 함수와 100% 호환됩니다.
|
||||||
|
override fun sqlType(): String = "FLOAT8 ARRAY"
|
||||||
|
|
||||||
|
override fun valueFromDB(value: Any): String = value.toString()
|
||||||
|
|
||||||
|
override fun notNullValueToDB(value: String): Any = value
|
||||||
|
}
|
||||||
|
|
||||||
|
object VectorStoreTable : Table("VECTOR_STORE") {
|
||||||
|
val id = integer("id").autoIncrement()
|
||||||
|
val content = text("content")
|
||||||
|
val metadata = text("metadata")
|
||||||
|
|
||||||
|
// 이제 FLOAT8 ARRAY 타입으로 컬럼이 생성됩니다.
|
||||||
|
val embedding = registerColumn<String>("embedding", VectorColumnType(1024))
|
||||||
|
|
||||||
|
override val primaryKey = PrimaryKey(id)
|
||||||
|
}
|
||||||
|
|
||||||
object DatabaseFactory {
|
object DatabaseFactory {
|
||||||
fun init() {
|
fun init() {
|
||||||
val dbPath = File("db/autotrade_db").absolutePath
|
val dbPath = File("db/autotrade_db").absolutePath
|
||||||
@ -71,10 +93,12 @@ object DatabaseFactory {
|
|||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
// 테이블 생성 (AutoTradeTable 포함)
|
// 테이블 생성 (AutoTradeTable 포함)
|
||||||
SchemaUtils.create(ConfigTable, TradeLogTable, AutoTradeTable)
|
SchemaUtils.createMissingTablesAndColumns(ConfigTable, TradeLogTable, AutoTradeTable,VectorStoreTable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- 자동매매(감시) 관련 함수 ---
|
// --- 자동매매(감시) 관련 함수 ---
|
||||||
|
|
||||||
|
|
||||||
@ -174,7 +198,8 @@ object DatabaseFactory {
|
|||||||
vtsAccountNo = it[ConfigTable.vtsAccountNo],
|
vtsAccountNo = it[ConfigTable.vtsAccountNo],
|
||||||
isSimulation = it[ConfigTable.isSimulation],
|
isSimulation = it[ConfigTable.isSimulation],
|
||||||
htsId = it[ConfigTable.htsId], // htsId 로드
|
htsId = it[ConfigTable.htsId], // htsId 로드
|
||||||
modelPath = it[ConfigTable.modelPath]
|
modelPath = it[ConfigTable.modelPath],
|
||||||
|
embedModelPath = it[ConfigTable.embedModelPath],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,6 +217,7 @@ object DatabaseFactory {
|
|||||||
it[isSimulation] = config.isSimulation
|
it[isSimulation] = config.isSimulation
|
||||||
it[htsId] = config.htsId
|
it[htsId] = config.htsId
|
||||||
it[modelPath] = config.modelPath
|
it[modelPath] = config.modelPath
|
||||||
|
it[embedModelPath] = config.embedModelPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,8 @@ data class AppConfig(
|
|||||||
|
|
||||||
var websocketToken: String = "",
|
var websocketToken: String = "",
|
||||||
val isSimulation: Boolean = true,
|
val isSimulation: Boolean = true,
|
||||||
val modelPath: String = "") {
|
val modelPath: String = "",
|
||||||
|
val embedModelPath: String = "") {
|
||||||
|
|
||||||
val accountNo : String
|
val accountNo : String
|
||||||
get() {
|
get() {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package network
|
|||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
@ -19,10 +20,15 @@ object AiService {
|
|||||||
coerceInputValues = true
|
coerceInputValues = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
install(HttpTimeout) {
|
||||||
|
requestTimeoutMillis = 60_000 // 전체 요청 대기 시간을 60초로 설정
|
||||||
|
connectTimeoutMillis = 10_000 // 서버 연결 대기 시간 10초
|
||||||
|
socketTimeoutMillis = 60_000 // 데이터 수신 대기 시간 60초
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val LLM_URL = "http://localhost:8080/completion"
|
// private const val LLM_URL = "http://localhost:8080/completion"
|
||||||
|
private const val LLM_URL = "http://127.0.0.1:8080/completion"
|
||||||
/**
|
/**
|
||||||
* 종목명, 현재가, 실시간 체결내역을 바탕으로 AI 분석 결과를 가져옵니다.
|
* 종목명, 현재가, 실시간 체결내역을 바탕으로 AI 분석 결과를 가져옵니다.
|
||||||
*/
|
*/
|
||||||
@ -38,26 +44,15 @@ object AiService {
|
|||||||
|
|
||||||
// Gemma에게 전달할 프롬프트 구성
|
// Gemma에게 전달할 프롬프트 구성
|
||||||
val prompt = """
|
val prompt = """
|
||||||
<start_of_turn>user
|
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
|
||||||
당신은 20년 경력의 전문 주식 트레이더이자 데이터 분석가입니다.
|
당신은 20년 경력의 주식 트레이더입니다. 데이터를 분석하여 짧고 단호하게 조언합니다.<|eot_id|><|start_header_id|>user<|end_header_id|>
|
||||||
다음 데이터를 바탕으로 해당 종목의 현재 '수급 상황'과 '단기 전망'을 분석하여 3줄 이내로 핵심만 말해주세요.
|
다음 데이터를 분석하여 '수급 상황'과 '단기 전망'을 3줄 이내로 요약하세요.
|
||||||
|
|
||||||
[종목 정보]
|
[종목] $stockName ($currentPrice)
|
||||||
- 종목명: $stockName
|
[최근 체결]
|
||||||
- 현재가: $currentPrice
|
$tradeSummary
|
||||||
|
<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
[최근 실시간 체결 내역]
|
""".trimIndent()
|
||||||
$tradeSummary
|
|
||||||
|
|
||||||
분석 기준:
|
|
||||||
1. 매수 체결 비중이 높은지, 매도 체결 비중이 높은지 판단하세요.
|
|
||||||
2. 대량 체결(고래)의 움직임이 있는지 확인하세요.
|
|
||||||
3. 단기적으로 진입하기에 적절한 시점인지 조언하세요.
|
|
||||||
|
|
||||||
답변은 한국어로, 친절하지만 단호한 전문가 말투를 사용하세요.<end_of_turn>
|
|
||||||
<start_of_turn>model
|
|
||||||
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val response = client.post(LLM_URL) {
|
val response = client.post(LLM_URL) {
|
||||||
@ -72,20 +67,53 @@ object AiService {
|
|||||||
"AI 서버 응답 오류: ${response.status}"
|
"AI 서버 응답 오류: ${response.status}"
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"분석 실패: 로컬 AI 서버(llama.cpp)가 실행 중인지 확인하세요. (${e.message})"
|
var msg = "분석 실패: 로컬 AI 서버(llama.cpp)가 실행 중인지 확인하세요. (${e.message})"
|
||||||
|
println(msg)
|
||||||
|
msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun getEmbedding(text: String): List<Double>? {
|
||||||
|
return try {
|
||||||
|
val response = client.post("http://127.0.0.1:8080/embedding") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(EmbeddingRequest(content = text))
|
||||||
|
}
|
||||||
|
if (response.status == HttpStatusCode.OK) {
|
||||||
|
val res: EmbeddingResponse = response.body()
|
||||||
|
res.embedding
|
||||||
|
} else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EmbeddingRequest(val content: String)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EmbeddingResponse(val embedding: List<Double>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* llama.cpp 서버 요청 데이터 구조
|
* llama.cpp 서버 요청 데이터 구조
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LlamaRequest(
|
data class LlamaRequest(
|
||||||
val prompt: String,
|
val prompt: String,
|
||||||
val n_predict: Int = 256,
|
val n_predict: Int = 256, // 답변 길이를 엄격히 제한
|
||||||
val temperature: Double = 0.7,
|
val temperature: Double = 0.4, // M3 Pro에서 더 일관된 답변을 위해 낮춤
|
||||||
val stop: List<String> = listOf("<|end_of_turn|>", "<end_of_turn>")
|
val stop: List<String> = listOf(
|
||||||
|
"<|eot_id|>",
|
||||||
|
"<|end_of_text|>",
|
||||||
|
"<|start_header_id|>",
|
||||||
|
"user",
|
||||||
|
"model"
|
||||||
|
) // [중요] AI가 멈춰야 할 지점들을 명확히 지정
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -120,7 +120,7 @@ class KisWebSocketManager {
|
|||||||
time = dataRows[1],
|
time = dataRows[1],
|
||||||
price = price,
|
price = price,
|
||||||
change = dataRows[4],
|
change = dataRows[4],
|
||||||
volume = dataRows[2],
|
volume = dataRows[12],
|
||||||
type = model.TradeType.NEUTRAL
|
type = model.TradeType.NEUTRAL
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,50 +4,64 @@ import java.io.File
|
|||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
object LlamaServerManager {
|
object LlamaServerManager {
|
||||||
private var process: Process? = null
|
// 포트별로 프로세스를 관리합니다.
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
private val processes = ConcurrentHashMap<Int, Process>()
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
init {
|
||||||
|
Runtime.getRuntime().addShutdownHook(Thread {
|
||||||
|
stopAll()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun startServer(binPath: String, modelPath: String) {
|
fun startServer(binPath: String, modelPath: String, port: Int, nGpuLayers: Int = 99) {
|
||||||
if (process != null || modelPath.isNullOrBlank()) return // 이미 실행 중이면 무시
|
// 이미 해당 포트에서 실행 중이거나 모델 경로가 비었으면 무시합니다.
|
||||||
|
if (processes.containsKey(port) || modelPath.isBlank()) return
|
||||||
|
|
||||||
val command = listOf(
|
val command = listOf(
|
||||||
binPath,
|
binPath,
|
||||||
"-m", modelPath,
|
"-m", modelPath,
|
||||||
"--port", "8080",
|
"--port", port.toString(),
|
||||||
"-c", "2048", // 컨텍스트 길이
|
"-c", if (port == 8081) "512" else "4096", // 임베딩용은 컨텍스트가 짧아도 충분합니다.
|
||||||
"-t", "4", // 인텔 맥 코어 수에 맞춰 스레드 제한 (부하 방지)
|
"-ngl", nGpuLayers.toString(),
|
||||||
"--embedding" // 나중에 유사도 분석 등을 위해 활성화
|
"-t", "6", // M3 Pro의 성능 코어를 고려하여 6~8개 권장
|
||||||
|
"--embedding" // 임베딩 기능을 활성화합니다.
|
||||||
)
|
)
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
val pb = ProcessBuilder(command)
|
val pb = ProcessBuilder(command)
|
||||||
// 실행 파일 권한 확인 (자동 부여)
|
|
||||||
|
pb.redirectErrorStream(true)
|
||||||
File(binPath).setExecutable(true)
|
File(binPath).setExecutable(true)
|
||||||
|
|
||||||
process = pb.start()
|
val process = pb.start()
|
||||||
println("✅ AI 서버 시작됨: http://localhost:8080")
|
processes[port] = process
|
||||||
|
println("✅ AI 서버 시작 시도 (Port: $port, Model: ${File(modelPath).name})")
|
||||||
|
|
||||||
// 서버 로그 모니터링 (에러 디버깅용)
|
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
||||||
val reader = BufferedReader(InputStreamReader(process?.inputStream))
|
|
||||||
var line: String?
|
var line: String?
|
||||||
while (reader.readLine().also { line = it } != null) {
|
while (reader.readLine().also { line = it } != null) {
|
||||||
// 서버 준비 완료 로그 확인용
|
// 로그 출력 (디버깅용)
|
||||||
if (line?.contains("HTTP server listening") == true) {
|
println("[Server $port] $line")
|
||||||
println("🚀 AI 모델 로딩 완료 및 대기 중")
|
if (line?.contains("server is listening") == true) {
|
||||||
|
println("🚀 AI 서버 준비 완료 (Port: $port)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("❌ AI 서버 실행 실패: ${e.message}")
|
println("❌ AI 서버 실행 실패 (Port: $port): ${e.message}")
|
||||||
|
processes.remove(port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopServer() {
|
fun stopAll() {
|
||||||
process?.destroy()
|
processes.forEach { (port, process) ->
|
||||||
process = null
|
process.destroy()
|
||||||
println("🛑 AI 서버 종료")
|
println("🛑 AI 서버 종료 (Port: $port)")
|
||||||
|
}
|
||||||
|
processes.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
75
src/main/kotlin/network/RagService.kt
Normal file
75
src/main/kotlin/network/RagService.kt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// src/main/kotlin/network/RagService.kt
|
||||||
|
|
||||||
|
import dev.langchain4j.data.segment.TextSegment
|
||||||
|
import dev.langchain4j.model.openai.OpenAiChatModel
|
||||||
|
import dev.langchain4j.model.openai.OpenAiEmbeddingModel
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
object RagService {
|
||||||
|
// 임베딩 모델 (8081) 및 채팅 모델 (8080) 설정
|
||||||
|
private val embeddingModel = OpenAiEmbeddingModel.builder()
|
||||||
|
.baseUrl("http://127.0.0.1:8081/v1")
|
||||||
|
.apiKey("unused")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val chatModel = OpenAiChatModel.builder()
|
||||||
|
.baseUrl("http://127.0.0.1:8080/v1")
|
||||||
|
.apiKey("unused")
|
||||||
|
.timeout(Duration.ofSeconds(60))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 텍스트를 임베딩하여 H2 DB에 저장합니다.
|
||||||
|
*/
|
||||||
|
fun ingest(text: String, meta: String = "") {
|
||||||
|
val embedding = embeddingModel.embed(text).content().vector()
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
VectorStoreTable.insert {
|
||||||
|
it[content] = text
|
||||||
|
it[metadata] = meta
|
||||||
|
// 벡터 데이터를 문자열 형태로 저장 (H2 포맷)
|
||||||
|
it[VectorStoreTable.embedding] = embedding.joinToString(",", "[", "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("💾 H2 벡터 저장 완료: ${text.take(15)}...")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 질문과 가장 유사한 정보를 H2에서 검색하여 AI 답변을 생성합니다.
|
||||||
|
*/
|
||||||
|
fun ask(question: String): String {
|
||||||
|
val queryVector = embeddingModel.embed(question).content().vector()
|
||||||
|
val vectorStr = queryVector.joinToString(",", "[", "]")
|
||||||
|
|
||||||
|
// H2의 VECTOR_COSINE_SIMILARITY 함수를 사용하여 검색
|
||||||
|
val context = transaction {
|
||||||
|
val query = "SELECT content FROM VECTOR_STORE " +
|
||||||
|
"ORDER BY VECTOR_COSINE_SIMILARITY(embedding, '$vectorStr') DESC " +
|
||||||
|
"LIMIT 3"
|
||||||
|
|
||||||
|
val results = mutableListOf<String>()
|
||||||
|
exec(query) { rs ->
|
||||||
|
while (rs.next()) {
|
||||||
|
results.add(rs.getString("content"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.joinToString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
val prompt = """
|
||||||
|
[참고 정보]
|
||||||
|
$context
|
||||||
|
|
||||||
|
[질문]
|
||||||
|
$question
|
||||||
|
|
||||||
|
위 정보를 참고하여 분석 결과를 말해주세요.
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
return chatModel.generate(prompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -98,6 +98,19 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
Text(if(config.modelPath.isEmpty()) "GGUF 모델 파일을 여기로 드래그하세요" else config.modelPath, fontSize = 12.sp)
|
Text(if(config.modelPath.isEmpty()) "GGUF 모델 파일을 여기로 드래그하세요" else config.modelPath, 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(24.dp))
|
||||||
|
|
||||||
|
|||||||
BIN
src/main/resources/bin/libggml-base.0.9.5.dylib
Executable file
BIN
src/main/resources/bin/libggml-base.0.9.5.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libggml-base.0.dylib
Symbolic link
1
src/main/resources/bin/libggml-base.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-base.0.9.5.dylib
|
||||||
1
src/main/resources/bin/libggml-base.dylib
Symbolic link
1
src/main/resources/bin/libggml-base.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-base.0.dylib
|
||||||
BIN
src/main/resources/bin/libggml-blas.0.9.5.dylib
Executable file
BIN
src/main/resources/bin/libggml-blas.0.9.5.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libggml-blas.0.dylib
Symbolic link
1
src/main/resources/bin/libggml-blas.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-blas.0.9.5.dylib
|
||||||
1
src/main/resources/bin/libggml-blas.dylib
Symbolic link
1
src/main/resources/bin/libggml-blas.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-blas.0.dylib
|
||||||
BIN
src/main/resources/bin/libggml-cpu.0.9.5.dylib
Executable file
BIN
src/main/resources/bin/libggml-cpu.0.9.5.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libggml-cpu.0.dylib
Symbolic link
1
src/main/resources/bin/libggml-cpu.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-cpu.0.9.5.dylib
|
||||||
1
src/main/resources/bin/libggml-cpu.dylib
Symbolic link
1
src/main/resources/bin/libggml-cpu.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-cpu.0.dylib
|
||||||
BIN
src/main/resources/bin/libggml-metal.0.9.5.dylib
Executable file
BIN
src/main/resources/bin/libggml-metal.0.9.5.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libggml-metal.0.dylib
Symbolic link
1
src/main/resources/bin/libggml-metal.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-metal.0.9.5.dylib
|
||||||
1
src/main/resources/bin/libggml-metal.dylib
Symbolic link
1
src/main/resources/bin/libggml-metal.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-metal.0.dylib
|
||||||
BIN
src/main/resources/bin/libggml-rpc.0.9.5.dylib
Executable file
BIN
src/main/resources/bin/libggml-rpc.0.9.5.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libggml-rpc.0.dylib
Symbolic link
1
src/main/resources/bin/libggml-rpc.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-rpc.0.9.5.dylib
|
||||||
1
src/main/resources/bin/libggml-rpc.dylib
Symbolic link
1
src/main/resources/bin/libggml-rpc.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml-rpc.0.dylib
|
||||||
BIN
src/main/resources/bin/libggml.0.9.5.dylib
Executable file
BIN
src/main/resources/bin/libggml.0.9.5.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libggml.0.dylib
Symbolic link
1
src/main/resources/bin/libggml.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml.0.9.5.dylib
|
||||||
1
src/main/resources/bin/libggml.dylib
Symbolic link
1
src/main/resources/bin/libggml.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libggml.0.dylib
|
||||||
BIN
src/main/resources/bin/libllama.0.0.7787.dylib
Executable file
BIN
src/main/resources/bin/libllama.0.0.7787.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libllama.0.dylib
Symbolic link
1
src/main/resources/bin/libllama.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libllama.0.0.7787.dylib
|
||||||
1
src/main/resources/bin/libllama.dylib
Symbolic link
1
src/main/resources/bin/libllama.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libllama.0.dylib
|
||||||
BIN
src/main/resources/bin/libmtmd.0.0.7787.dylib
Executable file
BIN
src/main/resources/bin/libmtmd.0.0.7787.dylib
Executable file
Binary file not shown.
1
src/main/resources/bin/libmtmd.0.dylib
Symbolic link
1
src/main/resources/bin/libmtmd.0.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libmtmd.0.0.7787.dylib
|
||||||
1
src/main/resources/bin/libmtmd.dylib
Symbolic link
1
src/main/resources/bin/libmtmd.dylib
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libmtmd.0.dylib
|
||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user