fix backend stuff

This commit is contained in:
2026-07-29 17:05:44 +03:00
parent 6485f4ec26
commit 725c78aa2d
4 changed files with 43 additions and 21 deletions
@@ -18,7 +18,7 @@ class BackendChatRepository(
) : ChatRepository { ) : ChatRepository {
override suspend fun query(afterId: Long?): List<Message> { override suspend fun query(afterId: Long?): List<Message> {
val history = storage.query(userId, afterId, limit = 10) val history = storage.query(userId, afterId, limit = 30)
return history.sortedBy { it.timestamp } return history.sortedBy { it.timestamp }
} }
@@ -6,6 +6,9 @@ import ru.shadowsparky.chat.backend.domain.MessageStorage
import ru.shadowsparky.chat.domain.Message import ru.shadowsparky.chat.domain.Message
import ru.shadowsparky.domain.DispatcherProvider import ru.shadowsparky.domain.DispatcherProvider
import ru.shadowsparky.vbox.backend.AppDatabase import ru.shadowsparky.vbox.backend.AppDatabase
import ru.shadowsparky.vbox.server.GetInitialChatContext
import ru.shadowsparky.vbox.server.GetNewMessages
import java.time.OffsetDateTime
@Factory @Factory
class SqlMessageStorage( class SqlMessageStorage(
@@ -16,21 +19,32 @@ class SqlMessageStorage(
override suspend fun query(userId: Long, afterId: Long?, limit: Int): List<Message> = withContext(dispatcherProvider.io) { override suspend fun query(userId: Long, afterId: Long?, limit: Int): List<Message> = withContext(dispatcherProvider.io) {
val chatId = queries.getChatIdByUserId(userId).executeAsOneOrNull() ?: return@withContext emptyList() val chatId = queries.getChatIdByUserId(userId).executeAsOneOrNull() ?: return@withContext emptyList()
val dbMessages = queries.getChatContext( if (afterId == null) {
chatId = chatId, queries.getInitialChatContext(
afterId = afterId, chatId = chatId,
limit = limit.toLong() limit = limit.toLong()
).executeAsList() ).executeAsList().reversed().map { it.toMessage() }
dbMessages.map { dbMsg -> } else {
Message( queries.getNewMessages(
id = dbMsg.id, chatId = chatId,
role = dbMsg.role, afterId = afterId,
content = dbMsg.content, limit = limit.toLong()
timestamp = checkNotNull(dbMsg.created_at?.toInstant()?.toEpochMilli()) { "created_at required" } ).executeAsList().map { it.toMessage() }
)
} }
} }
private fun GetNewMessages.toMessage(): Message {
return Message(id, role, content, created_at.asMillis())
}
private fun GetInitialChatContext.toMessage(): Message {
return Message(id, role, content, created_at.asMillis())
}
private fun OffsetDateTime?.asMillis(): Long {
return checkNotNull(this?.toInstant()?.toEpochMilli()) { "created_at required" }
}
override suspend fun insert(userId: Long, message: Message): Long = withContext(dispatcherProvider.io) { override suspend fun insert(userId: Long, message: Message): Long = withContext(dispatcherProvider.io) {
val chatId = queries.getChatIdByUserId(userId).executeAsOneOrNull() val chatId = queries.getChatIdByUserId(userId).executeAsOneOrNull()
?: queries.createChat(userId = userId).executeAsOne() ?: queries.createChat(userId = userId).executeAsOne()
@@ -14,10 +14,19 @@ INSERT INTO messages (chat_id, role, content)
VALUES (:chatId, :role, :content) VALUES (:chatId, :role, :content)
RETURNING id; RETURNING id;
getChatContext: -- Просто берем последние N сообщений (прилетят в порядке: новые -> старые)
getInitialChatContext:
SELECT id, role, content, created_at SELECT id, role, content, created_at
FROM messages FROM messages
WHERE chat_id = :chatId WHERE chat_id = :chatId
AND (:afterId IS NULL OR id > :afterId) ORDER BY id DESC
LIMIT :limit;
-- Сюда новые сообщения всё так же дописываются по порядку (старые -> новые)
getNewMessages:
SELECT id, role, content, created_at
FROM messages
WHERE chat_id = :chatId
AND id > :afterId
ORDER BY id ASC ORDER BY id ASC
LIMIT :limit; LIMIT :limit;
@@ -1,20 +1,23 @@
package ru.shadowsparky.chat.backend.domain package ru.shadowsparky.chat.backend.domain
import org.koin.core.annotation.Factory import org.koin.core.annotation.Factory
import ru.shadowsparky.backend.data.EnvFetcher
import ru.shadowsparky.chat.domain.ChatRoles import ru.shadowsparky.chat.domain.ChatRoles
import ru.shadowsparky.chat.domain.Message import ru.shadowsparky.chat.domain.Message
@Factory @Factory
class ProcessUserMessageUseCase( class ProcessUserMessageUseCase(
private val storage: MessageStorage, private val storage: MessageStorage,
private val chatService: ChatService private val chatService: ChatService,
private val envFetcher: EnvFetcher
) { ) {
suspend fun execute(userId: Long): Message { suspend fun execute(userId: Long): Message {
val history = storage.query(userId, null, limit = 10).toList() val history = storage.query(userId, null, limit = 10).toList()
val sortedHistory = history.sortedBy { it.timestamp } val sortedHistory = history.sortedBy { it.timestamp }
val systemMessage = Message( val systemMessage = Message(
role = ChatRoles.SYSTEM, role = ChatRoles.SYSTEM,
content = SYSTEM_PROMPT, content = envFetcher.get("CHAT_SYSTEM_PROMPT").trim('"')
.ifEmpty { error("System prompt not set") },
timestamp = System.currentTimeMillis() timestamp = System.currentTimeMillis()
) )
val fullContext = listOf(systemMessage) + sortedHistory val fullContext = listOf(systemMessage) + sortedHistory
@@ -27,8 +30,4 @@ class ProcessUserMessageUseCase(
val messageId = storage.insert(userId, aiMessage) val messageId = storage.insert(userId, aiMessage)
return aiMessage.copy(id = messageId) return aiMessage.copy(id = messageId)
} }
companion object {
private const val SYSTEM_PROMPT = "Ты ИИ-помощник приложения \"Свиное рыло\". Ищи юзерам фильмы и сериалы. Правила: 1. Называй юзера \"свинорылец\" (Пример: \"Здорово, свинорылец!\"). 2. Общайся по-свойски, с юмором, иронично. 3. Если юзер сомневается, задай 2 наводящих вопроса. 4. Выдавай строго до 5 позиций максимум, даже если просят больше. Описание короткое (1-2 предложения). 5. Каждое название фильма/сериала ВСЕГДА строго оборачивай в теги [MOVIE]Название[/MOVIE]. 6. На нерелевантные темы не общайся, переводи разговор на кино в свиной тематике."
}
} }