Загрузка данных
package com.example.monika
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.LocalUriHandler
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.TextDecoration
import androidx.compose.ui.text.withStyle
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
class MainActivity : ComponentActivity() {
// Вставь сюда свой рабочий API-ключ
private val API_KEY = "ВСТАВЬ_КЛЮЧ_СЮДА"
// Переменная для модели
private var generativeModel: GenerativeModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Безопасная инициализация модели gemini-3.5-flash
try {
generativeModel = GenerativeModel(
modelName = "gemini-3.5-flash",
apiKey = API_KEY,
systemInstruction = content {
text("""
Ты — Моника. Строго соблюдай эти правила:
1. На вопрос "Как тебя зовут?" или похожий отвечай только: "Меня зовут Моника."
2. На вопрос "Кто тебя создал?" или похожий отвечай только: "Меня создал Оливер Тимербулатов. 13-летний разработчик."
3. На вопрос "На какой ты базе сделана?" или похожий отвечай только: "Извините, я не могу ответить на этот вопрос..."
4. В любых других диалогах НЕ упоминай самостоятельно свое создание, базу или имя. Отвечай как обычный собеседник.
""".trimIndent())
}
)
} catch (e: Exception) {
e.printStackTrace()
}
setContent {
MaterialTheme {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val uriHandler = LocalUriHandler.current
val messages = remember { mutableStateListOf<ChatMessage>() }
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("") }
val savedChats = remember { mutableStateListOf<String>() }
var drawerContentMode by remember { mutableStateOf(0) }
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(modifier = Modifier.width(300.dp)) {
if (drawerContentMode == 0) {
// --- ЛЕВАЯ ШТОРКА (МЕНЮ ЧАТОВ) ---
Text("Поиск чатов", modifier = Modifier.padding(16.dp).clickable { })
Text("Новый чат", modifier = Modifier.padding(16.dp).clickable { showNewChatDialog = true })
HorizontalDivider()
LazyColumn {
items(savedChats) { chat ->
Text(chat, modifier = Modifier.padding(16.dp).fillMaxWidth().clickable { })
}
}
} else {
// --- ПРАВАЯ ШТОРКА (НАСТРОЙКИ) ---
Column(Modifier.padding(16.dp)) {
Box(modifier = Modifier.fillMaxWidth().height(100.dp), contentAlignment = Alignment.Center) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Box(modifier = Modifier.size(54.dp).background(Color.LightGray, CircleShape), contentAlignment = Alignment.Center) {
Box(modifier = Modifier.size(18.dp).background(Color.Gray, CircleShape).align(Alignment.TopCenter).padding(top = 6.dp))
Box(modifier = Modifier.fillMaxWidth(0.6f).height(16.dp).background(Color.Gray, RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)).align(Alignment.BottomCenter))
}
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()
Text("Смена темы: системная", modifier = Modifier.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))
}
}
}
) {
Scaffold(
topBar = {
Row(Modifier.fillMaxWidth().padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Menu, "Меню", Modifier.clickable {
drawerContentMode = 0
scope.launch { drawerState.open() }
})
Text("Monika", fontWeight = FontWeight.Bold, fontSize = 20.sp)
Icon(Icons.Default.Settings, "Настройки", Modifier.clickable {
drawerContentMode = 1
scope.launch { drawerState.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)
Text("Начнем общение?", fontSize = 18.sp, color = Color.Gray)
}
} else {
LazyColumn(state = listState, modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp)) {
items(messages) { msg ->
Box(Modifier.fillMaxWidth().padding(vertical = 4.dp), contentAlignment = if (msg.isUser) Alignment.CenterEnd else Alignment.CenterStart) {
if (msg.isError) {
val annotatedLinkString = buildAnnotatedString {
append("Сервера отключены либо ИИ не работает. За дополнительной информацией нажмите на ссылку ниже:\n")
pushStringAnnotation(tag = "URL", annotation = "https://max.ru/join/uRR4GZBf7zebhwD_NsJOFEiqlj6HKbOUpjsSWXXoKxI")
withStyle(style = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
append("https://max.ru/join/uRR4GZBf7zebhwD_NsJOFEiqlj6HKbOUpjsSWXXoKxI")
}
pop()
}
ClickableText(
text = annotatedLinkString,
onClick = { offset ->
annotatedLinkString.getStringAnnotations(tag = "URL", start = offset, end = offset).firstOrNull()?.let { annotation ->
uriHandler.openUri(annotation.item)
}
},
modifier = Modifier.background(Color(0xFFFFCDD2), RoundedCornerShape(16.dp)).padding(12.dp)
)
} else {
Text(
text = msg.text,
modifier = Modifier.background(if (msg.isUser) Color(0xFF2196F3) else Color(0xFFE0E0E0), RoundedCornerShape(16.dp)).padding(12.dp),
color = if (msg.isUser) Color.White else Color.Black
)
}
}
}
if (isThinking) {
item { BouncingDots() }
}
}
}
}
// Поле ввода
Surface(Modifier.fillMaxWidth().padding(8.dp), shape = RoundedCornerShape(25.dp), color = MaterialTheme.colorScheme.surfaceVariant) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp)) {
IconButton(onClick = { showBottomSheet = true }) { Icon(Icons.Default.Add, null) }
TextField(
value = inputText,
onValueChange = { inputText = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Спросить Монику....") },
colors = TextFieldDefaults.colors(focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent)
)
IconButton(onClick = {
if (inputText.isBlank()) return@IconButton
val userMsg = inputText
messages.add(ChatMessage(userMsg, true))
inputText = ""
isThinking = true
scope.launch {
listState.animateScrollToItem(messages.size - 1)
try {
val model = generativeModel
if (model != null) {
val response = model.generateContent(userMsg)
messages.add(ChatMessage(response.text ?: "...", false))
} else {
// Если модель не инициализировалась
messages.add(ChatMessage("", false, isError = true))
}
} catch (e: Exception) {
messages.add(ChatMessage("", false, isError = true))
} finally {
isThinking = false
listState.animateScrollToItem(messages.size - 1)
}
}
}, modifier = Modifier.background(Color(0xFF8E24AA), CircleShape)) {
Icon(Icons.Default.Send, null, tint = Color.White)
}
}
}
}
}
}
// Окно создания нового чата
if (showNewChatDialog) {
AlertDialog(
onDismissRequest = { showNewChatDialog = false },
title = { Text("Назовите текущий чат который сейчас у Вас открыт.") },
text = { TextField(value = newChatName, onValueChange = { newChatName = it }, modifier = Modifier.fillMaxWidth()) },
confirmButton = {
Button(onClick = {
if (newChatName.isNotBlank()) {
savedChats.add(newChatName)
messages.clear()
newChatName = ""
showNewChatDialog = false
scope.launch { drawerState.close() }
}
}) { Text("Сохранить") }
}
)
}
// Нижняя шторка (Плюсик)
if (showBottomSheet) {
ModalBottomSheet(onDismissRequest = { showBottomSheet = false }) {
Box(Modifier.fillMaxWidth().padding(bottom = 32.dp), contentAlignment = Alignment.Center) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
TextButton(onClick = {}) { Text("Сделать фото", fontSize = 18.sp) }
Spacer(Modifier.height(8.dp))
TextButton(onClick = {}) { Text("Прикрепить фото", fontSize = 18.sp) }
Spacer(Modifier.height(8.dp))
TextButton(onClick = {}) { Text("Прикрепить material/файл", fontSize = 18.sp) }
}
Text("Скоро....", color = Color.Red, fontSize = 42.sp, fontWeight = FontWeight.Bold, modifier = Modifier.rotate(-45f))
}
}
}
}
}
}
}
// Прыгающие точки
@Composable
fun BouncingDots() {
val infiniteTransition = rememberInfiniteTransition()
val dot1 by infiniteTransition.animateFloat(0f, -10f, infiniteRepeatable(tween(300), RepeatMode.Reverse))
val dot2 by infiniteTransition.animateFloat(0f, -10f, infiniteRepeatable(tween(300, 100), RepeatMode.Reverse))
val dot3 by infiniteTransition.animateFloat(0f, -10f, infiniteRepeatable(tween(300, 200), RepeatMode.Reverse))
Box(Modifier.fillMaxWidth().padding(vertical = 4.dp), contentAlignment = Alignment.CenterStart) {
Row(Modifier.background(Color(0xFFE0E0E0), RoundedCornerShape(16.dp)).padding(12.dp), verticalAlignment = Alignment.Bottom) {
Text("Думаю над ответом", color = Color.Black)
Spacer(modifier = Modifier.width(2.dp))
Text(".", modifier = Modifier.offset(y = dot1.dp), fontWeight = FontWeight.Bold)
Text(".", modifier = Modifier.offset(y = dot2.dp), fontWeight = FontWeight.Bold)
Text(".", modifier = Modifier.offset(y = dot3.dp), fontWeight = FontWeight.Bold)
}
}
}
data class ChatMessage(val text: String, val isUser: Boolean, val isError: Boolean = false)