Compare commits

..

2 Commits

2 changed files with 101 additions and 39 deletions

View File

@ -179,7 +179,7 @@ fun DrawerMenu(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
"Version 1.0.0", "Version 1.1.0",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = AppColors.OnSurfaceTertiary, color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp fontSize = 12.sp
@ -202,7 +202,7 @@ fun DrawerMenu(
modifier = Modifier.size(12.dp) modifier = Modifier.size(12.dp)
) )
Text( Text(
"AI", "Gemini AI",
color = AppColors.Primary, color = AppColors.Primary,
fontSize = 11.sp, fontSize = 11.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold

View File

@ -3,6 +3,8 @@ package com.example.notesai.presentation.screens.ai
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -22,6 +24,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import com.example.notesai.config.APIKey import com.example.notesai.config.APIKey
import com.example.notesai.data.local.DataStoreManager import com.example.notesai.data.local.DataStoreManager
import com.example.notesai.data.model.* import com.example.notesai.data.model.*
@ -139,7 +142,10 @@ fun AIHelperScreen(
isGeneratingSummary = false isGeneratingSummary = false
} }
Box(modifier = Modifier.fillMaxSize()) { Box(
modifier = Modifier.fillMaxSize()
) {
// MAIN CONTENT - Layer 1 (paling bawah)
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -349,7 +355,8 @@ fun AIHelperScreen(
scrollState.animateScrollTo(scrollState.maxValue) scrollState.animateScrollTo(scrollState.maxValue)
// Generate summary dengan Gemini // Generate summary dengan Gemini
val response = generativeModel.generateContent(summaryPrompt) val response =
generativeModel.generateContent(summaryPrompt)
val summary = response.text ?: "Gagal membuat ringkasan" val summary = response.text ?: "Gagal membuat ringkasan"
// Add AI response // Add AI response
@ -462,7 +469,7 @@ fun AIHelperScreen(
} }
} }
// Input Area // Input Area - Layer 2 (di atas chat, tapi di bawah drawer)
Surface( Surface(
color = AppColors.Surface, color = AppColors.Surface,
shadowElevation = 8.dp, shadowElevation = 8.dp,
@ -509,7 +516,8 @@ fun AIHelperScreen(
delay(100) delay(100)
scrollState.animateScrollTo(scrollState.maxValue) scrollState.animateScrollTo(scrollState.maxValue)
val response = generativeModel.generateContent(summaryPrompt) val response =
generativeModel.generateContent(summaryPrompt)
val summary = response.text ?: "Gagal membuat ringkasan" val summary = response.text ?: "Gagal membuat ringkasan"
chatMessages = chatMessages + ChatMessage( chatMessages = chatMessages + ChatMessage(
@ -599,9 +607,11 @@ fun AIHelperScreen(
} }
} }
val fullPrompt = "$notesContext\n\nPertanyaan: $userPrompt\n\nBerikan jawaban dalam bahasa Indonesia yang natural dan membantu." val fullPrompt =
"$notesContext\n\nPertanyaan: $userPrompt\n\nBerikan jawaban dalam bahasa Indonesia yang natural dan membantu."
val result = generativeModel.generateContent(fullPrompt) val result = generativeModel.generateContent(fullPrompt)
val response = result.text ?: "Tidak ada respons dari AI" val response =
result.text ?: "Tidak ada respons dari AI"
chatMessages = chatMessages + ChatMessage( chatMessages = chatMessages + ChatMessage(
message = response, message = response,
@ -611,18 +621,44 @@ fun AIHelperScreen(
saveChatHistory() saveChatHistory()
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = when { errorMessage = when {
e.message?.contains("quota", ignoreCase = true) == true -> e.message?.contains(
"quota",
ignoreCase = true
) == true ->
"⚠️ Kuota API habis. Silakan coba lagi nanti atau hubungi developer." "⚠️ Kuota API habis. Silakan coba lagi nanti atau hubungi developer."
e.message?.contains("404", ignoreCase = true) == true ||
e.message?.contains("not found", ignoreCase = true) == true -> e.message?.contains(
"404",
ignoreCase = true
) == true ||
e.message?.contains(
"not found",
ignoreCase = true
) == true ->
"⚠️ Model AI tidak ditemukan. Silakan hubungi developer." "⚠️ Model AI tidak ditemukan. Silakan hubungi developer."
e.message?.contains("401", ignoreCase = true) == true ||
e.message?.contains("API key", ignoreCase = true) == true -> e.message?.contains(
"401",
ignoreCase = true
) == true ||
e.message?.contains(
"API key",
ignoreCase = true
) == true ->
"⚠️ API key tidak valid. Silakan hubungi developer." "⚠️ API key tidak valid. Silakan hubungi developer."
e.message?.contains("timeout", ignoreCase = true) == true ->
e.message?.contains(
"timeout",
ignoreCase = true
) == true ->
"⚠️ Koneksi timeout. Periksa koneksi internet Anda." "⚠️ Koneksi timeout. Periksa koneksi internet Anda."
e.message?.contains("network", ignoreCase = true) == true ->
e.message?.contains(
"network",
ignoreCase = true
) == true ->
"⚠️ Tidak ada koneksi internet. Silakan periksa koneksi Anda." "⚠️ Tidak ada koneksi internet. Silakan periksa koneksi Anda."
else -> else ->
"⚠️ Terjadi kesalahan: ${e.message?.take(100) ?: "Unknown error"}" "⚠️ Terjadi kesalahan: ${e.message?.take(100) ?: "Unknown error"}"
} }
@ -647,34 +683,60 @@ fun AIHelperScreen(
} }
} }
// Chat History Drawer // DRAWER - Layer 3 (paling atas, di luar Column)
AnimatedVisibility( AnimatedVisibility(
visible = showHistoryDrawer, visible = showHistoryDrawer,
enter = fadeIn() + slideInHorizontally(), enter = fadeIn() + slideInHorizontally(initialOffsetX = { -it }),
exit = fadeOut() + slideOutHorizontally() exit = fadeOut() + slideOutHorizontally(targetOffsetX = { -it }),
modifier = Modifier
.fillMaxSize()
.zIndex(10f) // Z-index lebih tinggi dari bottom bar
) { ) {
ChatHistoryDrawer( // Backdrop + Drawer
chatHistories = chatHistories, Box(
categories = categories, modifier = Modifier
notes = notes, .fillMaxSize()
selectedCategory = selectedCategory, .clickable(
onDismiss = { showHistoryDrawer = false }, interactionSource = remember { MutableInteractionSource() },
onHistoryClick = { loadChatHistory(it) }, indication = null
onDeleteHistory = { historyId -> ) { showHistoryDrawer = false }
scope.launch { .background(Color.Black.copy(alpha = 0.5f))
dataStoreManager.deleteChatHistory(historyId) ) {
} // Drawer Content
}, Box(
onCategorySelected = { category -> modifier = Modifier
selectedCategory = category .fillMaxHeight()
}, .fillMaxWidth(0.85f) // 85% dari lebar layar
onNewChat = { startNewChat() }, .align(Alignment.CenterStart)
onEditHistoryTitle = { historyId, newTitle -> .clickable(
scope.launch { interactionSource = remember { MutableInteractionSource() },
dataStoreManager.updateChatHistoryTitle(historyId, newTitle) indication = null
} ) { /* Prevent backdrop click */ }
) {
ChatHistoryDrawer(
chatHistories = chatHistories,
categories = categories,
notes = notes,
selectedCategory = selectedCategory,
onDismiss = { showHistoryDrawer = false },
onHistoryClick = { loadChatHistory(it) },
onDeleteHistory = { historyId ->
scope.launch {
dataStoreManager.deleteChatHistory(historyId)
}
},
onCategorySelected = { category ->
selectedCategory = category
},
onNewChat = { startNewChat() },
onEditHistoryTitle = { historyId, newTitle ->
scope.launch {
dataStoreManager.updateChatHistoryTitle(historyId, newTitle)
}
}
)
} }
) }
} }
} }
} }