Initial commit

This commit is contained in:
Алексей Будаев 2026-04-03 22:53:33 +08:00
commit cda6eb7ce0
680 changed files with 75081 additions and 0 deletions

View file

@ -0,0 +1,272 @@
package com.mistral.chat.ui
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.button.MaterialButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputEditText
import com.mistral.chat.R
import com.mistral.chat.api.MistralClient
import com.mistral.chat.data.Message
import com.mistral.chat.data.UserProfile
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: MessageAdapter
private lateinit var inputField: TextInputEditText
private lateinit var sendButton: MaterialButton
private lateinit var modelSelector: AutoCompleteTextView
private lateinit var toolbar: MaterialToolbar
private lateinit var progressIndicator: LinearProgressIndicator
private var client: MistralClient? = null
private val messages = mutableListOf<Message>()
private var availableModels: List<Pair<String, String>> = emptyList()
private lateinit var prefs: SharedPreferences
companion object {
private const val API_KEY = "YW0IjDBRLuyEBcgNjVeVUFlMI6fcZYLA"
private const val PREFS_NAME = "mistral_chat_prefs"
private const val KEY_USER_NAME = "user_name"
private const val KEY_USER_BIO = "user_bio"
private const val KEY_USER_PREFS = "user_preferences"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
client = MistralClient(API_KEY)
toolbar = findViewById(R.id.toolbar)
recyclerView = findViewById(R.id.recyclerView)
inputField = findViewById(R.id.inputField)
sendButton = findViewById(R.id.sendButton)
modelSelector = findViewById(R.id.modelSelector)
progressIndicator = findViewById(R.id.progressIndicator)
setupToolbar()
setupRecyclerView()
loadModels()
setupInput()
inputField.requestFocus()
inputField.postDelayed({
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(inputField, InputMethodManager.SHOW_IMPLICIT)
}, 100)
}
private fun setupToolbar() {
toolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_profile -> {
showProfileDialog()
true
}
R.id.action_clear -> {
showClearChatDialog()
true
}
R.id.action_about -> {
showAboutDialog()
true
}
else -> false
}
}
}
private fun setupRecyclerView() {
adapter = MessageAdapter(messages)
recyclerView.layoutManager = LinearLayoutManager(this).apply {
stackFromEnd = true
}
recyclerView.adapter = adapter
}
private fun setupInput() {
sendButton.setOnClickListener {
val userInput = inputField.text?.toString()?.trim()
if (userInput.isNullOrEmpty()) return@setOnClickListener
addMessage(Message(content = userInput, isUser = true))
inputField.text?.clear()
sendMessage(userInput)
}
inputField.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
toolbar.title = "Mistral Chat"
}
}
}
private fun loadModels() {
lifecycleScope.launch {
val result = client?.getModels()
result?.onSuccess { models ->
availableModels = models
runOnUiThread {
val modelNames = models.map { it.second }
val adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_dropdown_item_1line, modelNames)
modelSelector.setAdapter(adapter)
val codestralIndex = models.indexOfFirst { it.first.contains("codestral", ignoreCase = true) }
if (codestralIndex >= 0) {
modelSelector.setText(modelNames[codestralIndex], false)
} else if (modelNames.isNotEmpty()) {
modelSelector.setText(modelNames[0], false)
}
}
}?.onFailure {
availableModels = MistralClient.AVAILABLE_MODELS
runOnUiThread {
val modelNames = availableModels.map { it.second }
val adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_dropdown_item_1line, modelNames)
modelSelector.setAdapter(adapter)
val codestralIndex = availableModels.indexOfFirst { it.first.contains("codestral", ignoreCase = true) }
if (codestralIndex >= 0) {
modelSelector.setText(modelNames[codestralIndex], false)
} else {
modelSelector.setText(modelNames[0], false)
}
}
}
}
}
private fun addMessage(message: Message) {
messages.add(message)
adapter.notifyItemInserted(messages.size - 1)
recyclerView.scrollToPosition(messages.size - 1)
}
private fun sendMessage(userInput: String) {
val selectedModelName = modelSelector.text.toString()
val matchedModel = availableModels.find { it.second == selectedModelName }
val selectedModel = matchedModel?.first ?: "mistral-small-latest"
val userProfile = loadUserProfile()
val profileContext = if (!userProfile.isEmpty()) {
"\n[User Profile]\n${userProfile.toContextString()}\n"
} else ""
val apiMessages = messages.map { msg ->
Message(
content = msg.content,
isUser = msg.isUser
)
}.toMutableList()
if (profileContext.isNotEmpty() && apiMessages.isEmpty()) {
apiMessages.add(0, Message(content = profileContext, isUser = true))
}
sendButton.isEnabled = false
progressIndicator.isVisible = true
lifecycleScope.launch {
val result = client?.chat(selectedModel, apiMessages)
result?.onSuccess { (response, usedModel) ->
addMessage(Message(content = response, isUser = false, senderName = usedModel))
}?.onFailure { error ->
addMessage(Message(content = "Error: ${error.message}", isUser = false, senderName = "Error"))
}
sendButton.isEnabled = true
progressIndicator.isVisible = false
}
}
private fun loadUserProfile(): UserProfile {
return UserProfile(
name = prefs.getString(KEY_USER_NAME, "") ?: "",
bio = prefs.getString(KEY_USER_BIO, "") ?: "",
preferences = prefs.getString(KEY_USER_PREFS, "") ?: ""
)
}
private fun saveUserProfile(profile: UserProfile) {
prefs.edit()
.putString(KEY_USER_NAME, profile.name)
.putString(KEY_USER_BIO, profile.bio)
.putString(KEY_USER_PREFS, profile.preferences)
.apply()
}
private fun deleteUserProfile() {
prefs.edit()
.remove(KEY_USER_NAME)
.remove(KEY_USER_BIO)
.remove(KEY_USER_PREFS)
.apply()
}
private fun showProfileDialog() {
val profile = loadUserProfile()
val dialogView = layoutInflater.inflate(R.layout.dialog_profile, null)
val nameInput = dialogView.findViewById<TextInputEditText>(R.id.nameInput)
val bioInput = dialogView.findViewById<TextInputEditText>(R.id.bioInput)
val preferencesInput = dialogView.findViewById<TextInputEditText>(R.id.preferencesInput)
nameInput.setText(profile.name)
bioInput.setText(profile.bio)
preferencesInput.setText(profile.preferences)
AlertDialog.Builder(this)
.setView(dialogView)
.setPositiveButton(R.string.save) { _, _ ->
val newProfile = UserProfile(
name = nameInput.text?.toString() ?: "",
bio = bioInput.text?.toString() ?: "",
preferences = preferencesInput.text?.toString() ?: ""
)
saveUserProfile(newProfile)
toolbar.subtitle = if (newProfile.name.isNotBlank()) newProfile.name else null
}
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.delete) { _, _ ->
deleteUserProfile()
toolbar.subtitle = null
}
.show()
}
private fun showClearChatDialog() {
AlertDialog.Builder(this)
.setMessage(R.string.clear_chat_confirm)
.setPositiveButton(R.string.yes) { _, _ ->
messages.clear()
adapter.notifyDataSetChanged()
}
.setNegativeButton(R.string.no, null)
.show()
}
private fun showAboutDialog() {
AlertDialog.Builder(this)
.setMessage(R.string.about_text)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}