Загрузка данных
package com.example.monika
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.*
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.ai.client.generativeai.GenerativeModel
import com.google.ai.client.generativeai.type.content
import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONObject
import java.util.UUID
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
class MainActivity : ComponentActivity() {
// Вставь сюда свой рабочий API-ключ
private val API_KEY = "ВСТАВЬ_КЛЮЧ_СЮДА"
private var generativeModel: GenerativeModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
generativeModel = GenerativeModel(
modelName = "gemini-1.5-flash",
apiKey = API_KEY,
systemInstruction = content {
text("""
Ты — Моника. Строго соблюдай эти правила:
1. На вопрос "Как тебя зовут?" или похожий отвечай только: "Меня зовут Моника."
2. На вопрос "Кто тебя создал?" или похожий отвечай только: "Меня создал Оливер Тимербулатов. 13-летний разработчик."
3. На вопрос "На какой ты базе сделана?" или похожий отвечай только: "Извините, я не могу ответить на этот вопрос..."
4. В любых других диалогах НЕ упоминай самостоятельно свое создание, базу или имя. Отвечай как обычный собеседник.
""".trimIndent())
}
)
} catch (e: Exception) {
e.printStackTrace()
}
setContent {
val context = LocalContext.current
// Настройки темы
val themePrefs = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
var themeModeStr by remember { mutableStateOf(themePrefs.getString("theme", "SYSTEM") ?: "SYSTEM") }
val isSystemDark = isSystemInDarkTheme()
val isDarkTheme = when (themeModeStr) {
"LIGHT" -> false
"DARK" -> true
else -> isSystemDark
}
val currentColorScheme = if (isDarkTheme) darkColorScheme() else lightColorScheme()
MaterialTheme(colorScheme = currentColorScheme) {
val leftDrawerState = rememberDrawerState(DrawerValue.Closed)
val rightDrawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val uriHandler = LocalUriHandler.current
val clipboardManager = LocalClipboardManager.current
// Состояние чатов
val savedChats = remember { mutableStateListOf<ChatSession>().apply { addAll(ChatManager.loadChats(context)) } }
val messages = remember { mutableStateListOf<ChatMessage>() }
var currentChatId by remember { mutableStateOf<String?>(null) }
var inputText by remember { mutableStateOf("") }
var isThinking by remember { mutableStateOf(false) }
var showBottomSheet by remember { mutableStateOf(false) }
var showNewChatDialog by remember { mutableStateOf(false) }
var newChatName by remember { mutableStateOf("") }
// Редактирование сообщений
var showMessageMenuFor by remember { mutableStateOf<ChatMessage?>(null) }
var editMessageData by remember { mutableStateOf<ChatMessage?>(null) }
var editedText by remember { mutableStateOf("") }
// Цвета
val userBubbleColor = if (isDarkTheme) Color.DarkGray else Color.Gray
val userTextColor = Color.White
val monikaTextColor = MaterialTheme.colorScheme.onBackground
fun autoSaveCurrentChat() {
if (currentChatId != null) {
val index = savedChats.indexOfFirst { it.id == currentChatId }
if (index != -1) {
savedChats[index] = savedChats[index].copy(messages = messages.toList())
ChatManager.saveChats(context, savedChats)
}
}
}
// Автоматическая прокрутка вниз при добавлении сообщений
LaunchedEffect(messages.size) {
if (messages.isNotEmpty()) {
listState.animateScrollToItem(messages.size - 1)
}
}
// 1. Внешняя шторка (НАСТРОЙКИ СПРАВА - RTL)
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
ModalNavigationDrawer(
drawerState = rightDrawerState,
drawerContent = {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
ModalDrawerSheet(modifier = Modifier.width(300.dp)) {
Column(Modifier.padding(16.dp)) {
Box(modifier = Modifier.fillMaxWidth().height(100.dp), contentAlignment = Alignment.Center) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = "Профиль",
modifier = Modifier.size(64.dp),
tint = Color.Gray
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text("Профиль", fontWeight = FontWeight.Bold)
Text("Войти/Зарегистрироваться", fontSize = 10.sp, color = Color.Gray)
}
}
Text("Скоро....", color = Color.Red, fontSize = 24.sp, fontWeight = FontWeight.Bold, modifier = Modifier.rotate(-45f))
}
}
HorizontalDivider()
val themeDisplayText = when(themeModeStr) {
"LIGHT" -> "светлая"
"DARK" -> "темная"
else -> "системная"
}
Text(
text = "Смена темы: $themeDisplayText",
modifier = Modifier
.fillMaxWidth()
.clickable {
themeModeStr = when (themeModeStr) {
"SYSTEM" -> "LIGHT"
"LIGHT" -> "DARK"
"DARK" -> "SYSTEM"
else -> "SYSTEM"
}
themePrefs.edit().putString("theme", themeModeStr).apply()
}
.padding(16.dp)
)
Spacer(modifier = Modifier.weight(1f))
Text("Team Lazy Cat™ - Monika AI.\nVersion of Monika: Monika Beta 1.5 Patch 5 Panda 2", fontSize = 10.sp, color = Color.Gray, modifier = Modifier.padding(16.dp))
}
}
}
) {
// 2. Внутренняя шторка (МЕНЮ СЛЕВА - LTR)
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
ModalNavigationDrawer(
drawerState = leftDrawerState,
drawerContent = {
ModalDrawerSheet(modifier = Modifier.width(300.dp)) {
Text("Поиск чатов", modifier = Modifier.padding(16.dp).clickable { })
Text("Сохранить в новый чат", color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, modifier = Modifier.padding(16.dp).clickable { showNewChatDialog = true })
HorizontalDivider()
LazyColumn {
items(savedChats) { chat ->
Text(
text = chat.name,
fontWeight = if (chat.id == currentChatId) FontWeight.Bold else FontWeight.Normal,
color = if (chat.id == currentChatId) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.fillMaxWidth()
.clickable {
currentChatId = chat.id
messages.clear()
messages.addAll(chat.messages)
scope.launch { leftDrawerState.close() }
}
.padding(16.dp)
)
}
}
}
}
) {
// 3. ОСНОВНОЙ ЭКРАН С ЧАТОМ
Scaffold(
topBar = {
Row(Modifier.fillMaxWidth().padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Menu, "Меню", Modifier.clickable { scope.launch { leftDrawerState.open() } })
Text("Monika", fontWeight = FontWeight.Bold, fontSize = 20.sp)
Icon(Icons.Default.Settings, "Настройки", Modifier.clickable { scope.launch { rightDrawerState.open() } })
}
}
) { padding ->
Column(Modifier.fillMaxSize().padding(padding).background(MaterialTheme.colorScheme.background)) {
// Область сообщений
Box(Modifier.weight(1f).fillMaxWidth()) {
if (messages.isEmpty()) {
Column(Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally) {
Text("Здравствуйте!", fontSize = 42.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onBackground)
Text("Начнем общение?", fontSize = 18.sp, color = Color.Gray)
}
} else {
LazyColumn(state = listState, modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp)) {
items(messages) { msg ->
if (msg.isUser) {
Box(Modifier.fillMaxWidth().padding(vertical = 4.dp), contentAlignment = Alignment.CenterEnd) {
Box {
Text(
text = msg.text,
modifier = Modifier
.background(userBubbleColor, RoundedCornerShape(16.dp))
.combinedClickable(
onClick = {},
onLongClick = { showMessageMenuFor = msg }
)
.padding(12.dp),
color = userTextColor
)
DropdownMenu(
expanded = showMessageMenuFor == msg,
onDismissRequest = { showMessageMenuFor = null }
) {
DropdownMenuItem(
text = { Text("Скопировать") },
onClick = {
clipboardManager.setText(AnnotatedString(msg.text))
showMessageMenuFor = null
}
)
DropdownMenuItem(
text = { Text("Изменить") },
onClick = {
editedText = msg.text
editMessageData = msg
showMessageMenuFor = null
}
)
}
}
}
} else {
Column(Modifier.fillMaxWidth().padding(vertical = 4.dp), horizontalAlignment = Alignment.Start) {
if (msg.isError) {
val annotatedLinkString = buildAnnotatedString {
append("Сервера отключены либо ИИ не работает. За дополнительной информацией нажмите на ссылку ниже:\n")
pushStringAnnotation("URL", "https://max.ru/join/uRR4GZBf7zebhwD_NsJOFEiqlj6HKbOUpjsSWXXoKxI")
withStyle(SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
append("https://max.ru/join/uRR4GZBf7zebhwD_NsJOFEiqlj6HKbOUpjsSWXXoKxI")
}
pop()
}
ClickableText(
text = annotatedLinkString,
onClick = { offset ->
annotatedLinkString.getStringAnnotations("URL", offset, offset).firstOrNull()?.let { uriHandler.openUri(it.item) }
},
modifier = Modifier.padding(12.dp)
)
} else {
Text(
text = msg.text,
mo