feat: 简化SecondActivity,支持3个模型配置

This commit is contained in:
xiaji
2026-05-08 23:22:06 +08:00
parent 8cc254ebc1
commit 1fef271a20
2 changed files with 165 additions and 834 deletions

View File

@@ -4,13 +4,11 @@ import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
@@ -20,10 +18,8 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.flomo_ai.ui.theme.ThemeManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
@@ -35,64 +31,32 @@ import org.json.JSONObject
class MainActivity : AppCompatActivity() {
private lateinit var inputEditText: EditText
private lateinit var configButton: Button
private lateinit var outputStatusLabel: TextView
private lateinit var outputTextView: EditText
private lateinit var promptSelector: Spinner
private lateinit var promptNameText: TextView
private lateinit var promptContentText: TextView
private lateinit var headerModelSelector: Spinner
// Data classes matching SecondActivity
data class HeaderConfig(val key: String, val value: String)
data class PromptConfig(val id: String, val title: String, val content: String, val expanded: Boolean = false)
data class ButtonConfig(val id: String, val label: String, val action: String, val apiUrl: String? = null, val apiMethod: String? = null, val apiBodyTemplate: String? = null, val expanded: Boolean = false)
data class LLMConfig(val name: String, val baseUrl: String, val apiKey: String, val model: String)
data class SettingsData(
val llmConfigs: List<LLMConfig>?,
val selectedLlmIndex: Int?,
val headerConfigs: List<HeaderConfig>?,
val promptConfigs: List<PromptConfig>?,
val buttonConfigs: List<ButtonConfig>?,
val llmConfig: LLMConfig? = null
)
private var llmConfigs = listOf<LLMConfig>()
private var selectedLlmIndex = 0
@SuppressLint("MissingInflatedId", "CutPasteId", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ThemeManager.applySavedTheme(this)
Log.d("MainActivity", "onCreate: Starting MainActivity")
setContentView(R.layout.activity_main)
Log.d("MainActivity", "onCreate: Layout set")
promptSelector = findViewById<Spinner>(R.id.promptSelector)
promptNameText = findViewById<TextView>(R.id.promptNameText)
promptContentText = findViewById<TextView>(R.id.promptContentText)
inputEditText = findViewById<EditText>(R.id.inputEditText)
val sendButton = findViewById<Button>(R.id.sendButton)
val stopButton = findViewById<Button>(R.id.stopButton)
outputStatusLabel = findViewById<TextView>(R.id.outputStatusLabel)
outputTextView = findViewById<EditText>(R.id.outputTextView)
val btnCopyResult = findViewById<Button>(R.id.btnCopyResult)
val headerTitle = findViewById<TextView>(R.id.headerTitle)
headerModelSelector = findViewById<Spinner>(R.id.headerModelSelector)
Log.d("MainActivity", "onCreate: Views initialized")
headerTitle.text = "AI优化"
loadModelsFromConfig()
// Initialize quick action buttons
initQuickButtons()
// Load prompts from configuration
loadPromptsFromConfig()
outputStatusLabel.text = "等待发送"
outputTextView.setText("")
// Setup prompt selector listener
promptSelector.setOnItemSelectedListener(object : android.widget.AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: android.widget.AdapterView<*>, view: android.view.View?, position: Int, id: Long) {
override fun onItemSelected(parent: android.widget.AdapterView<*>, view: View?, position: Int, id: Long) {
val selectedTitle = promptSelector.getItemAtPosition(position) as String
@Suppress("UNCHECKED_CAST")
@@ -112,8 +76,6 @@ class MainActivity : AppCompatActivity() {
} else {
promptContentText.visibility = View.GONE
}
Log.d("MainActivity", "Prompt selected: $selectedTitle, content: $content")
}
override fun onNothingSelected(parent: android.widget.AdapterView<*>) {
@@ -124,11 +86,11 @@ class MainActivity : AppCompatActivity() {
})
sendButton.setOnClickListener {
Log.d("MainActivity", "Send button clicked")
val inputText = inputEditText.text.toString()
if (inputText.isNotEmpty()) {
outputStatusLabel.text = "连接中…"
outputTextView.setText("正在生成...")
if (inputText.isEmpty()) {
Toast.makeText(this, "请输入内容", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val promptContent = promptContentText.text.toString()
val fullContent = if (promptContent.isNotEmpty() && promptContent != "无特殊指令") {
@@ -136,19 +98,74 @@ class MainActivity : AppCompatActivity() {
} else {
inputText
}
Log.d("MainActivity", "Full content to send: $fullContent")
// 加载配置并发送请求
loadLlmConfigsAndSend(fullContent)
}
btnCopyResult.setOnClickListener {
val textToCopy = outputTextView.text.toString()
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("优化结果", textToCopy)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "结果已复制到剪贴板", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
}
}
initQuickButtons()
}
private fun loadModelsFromConfig() {
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
if (json != null) {
try {
val settings = Gson().fromJson(json, SettingsData::class.java)
llmConfigs = settings.llmConfigs ?: emptyList()
selectedLlmIndex = settings.selectedLlmIndex ?: 0
} catch (e: Exception) {
Log.e("MainActivity", "Error loading config", e)
}
}
}
private fun loadLlmConfigsAndSend(content: String) {
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null) ?: return
try {
val settings = Gson().fromJson(json, SettingsData::class.java)
val configs = settings.llmConfigs ?: return
if (configs.isEmpty()) return
val config = if (selectedLlmIndex < configs.size) configs[selectedLlmIndex] else configs[0]
val baseUrl = config.baseUrl.ifEmpty { "https://api.openai.com/v1" }
val apiKey = config.apiKey
val model = config.model.ifEmpty { "gpt-4o" }
sendToLlm(baseUrl, apiKey, model, content)
} catch (e: Exception) {
Log.e("MainActivity", "Error loading LLM config", e)
outputTextView.setText("错误: ${e.message}")
}
}
private fun sendToLlm(baseUrl: String, apiKey: String, model: String, content: String) {
val outputStatusLabel = findViewById<TextView>(R.id.outputStatusLabel)
outputStatusLabel.text = "连接中..."
outputTextView.setText("正在生成...")
CoroutineScope(Dispatchers.IO).launch {
try {
val llmConfig = getSelectedModelConfig()
val baseUrl = llmConfig.baseUrl.ifEmpty { "https://open.bigmodel.cn/api/paas/v4" }
val apiKey = llmConfig.apiKey
val model = llmConfig.model.ifEmpty { "glm-4.7-flash" }
val messagesJson = JSONArray().apply {
put(JSONObject().apply {
put("role", "user")
put("content", fullContent)
put("content", content)
})
}
@@ -156,7 +173,6 @@ class MainActivity : AppCompatActivity() {
put("model", model)
put("messages", messagesJson)
put("max_tokens", 65536)
put("temperature", 1.0)
}
val client = OkHttpClient()
@@ -181,9 +197,9 @@ class MainActivity : AppCompatActivity() {
val choices = responseJson.getJSONArray("choices")
if (choices.length() > 0) {
val message = choices.getJSONObject(0).getJSONObject("message")
val content = message.getString("content")
val result = message.getString("content")
outputStatusLabel.text = "已完成"
outputTextView.setText(content)
outputTextView.setText(result)
} else {
outputStatusLabel.text = "发生错误"
outputTextView.setText("API 返回空结果")
@@ -198,160 +214,18 @@ class MainActivity : AppCompatActivity() {
outputStatusLabel.text = "发生错误"
outputTextView.setText("错误: ${e.message}")
}
Log.e("MainActivity", "Error calling API", e)
}
}
} else {
Log.w("MainActivity", "Input text is empty")
Toast.makeText(this, "请输入内容", Toast.LENGTH_SHORT).show()
}
}
stopButton.setOnClickListener {
Log.d("MainActivity", "Stop button clicked")
outputStatusLabel.text = "已停止"
Toast.makeText(this, "生成已停止", Toast.LENGTH_SHORT).show()
}
btnCopyResult.setOnClickListener {
Log.d("MainActivity", "btnCopyResult clicked")
val textToCopy = outputTextView.text.toString()
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("优化结果", textToCopy)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "结果已复制到剪贴板", Toast.LENGTH_SHORT).show()
Log.d("MainActivity", "Text copied to clipboard")
} else {
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
Log.w("MainActivity", "No text to copy")
}
}
val btnSaveNote = findViewById<Button>(R.id.btnSaveNote)
btnSaveNote.setOnClickListener {
Log.d("MainActivity", "btnSaveNote clicked")
val textToSave = outputTextView.text.toString()
if (textToSave.isNotEmpty() && textToSave != "发送消息后结果将在此显示") {
saveToNoteApi(textToSave)
}
private fun saveToNoteApi(content: String) {
try {
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
if (json == null) {
Toast.makeText(this, "请先配置笔记API", Toast.LENGTH_SHORT).show()
return
}
val settings = Gson().fromJson(json, NoteSettingsData::class.java)
val noteConfig = settings.noteApiConfig
if (noteConfig == null || noteConfig.apiUrl.isBlank()) {
Toast.makeText(this, "请先配置笔记API", Toast.LENGTH_SHORT).show()
return
}
outputStatusLabel.text = "提交中..."
CoroutineScope(Dispatchers.Main).launch {
try {
val result = submitToNoteApi(noteConfig.apiType, noteConfig.apiUrl, noteConfig.apiKey, content)
if (result) {
outputStatusLabel.text = "已提交"
Toast.makeText(this@MainActivity, "笔记已保存", Toast.LENGTH_SHORT).show()
inputEditText.text.clear()
} else {
outputStatusLabel.text = "提交失败"
Toast.makeText(this@MainActivity, "保存失败,请检查配置", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
outputStatusLabel.text = "提交失败"
Toast.makeText(this@MainActivity, "保存失败: ${e.message}", Toast.LENGTH_SHORT).show()
Log.e("MainActivity", "saveToNoteApi error", e)
}
}
} catch (e: Exception) {
Toast.makeText(this, "请先配置笔记API", Toast.LENGTH_SHORT).show()
Log.e("MainActivity", "saveToNoteApi error", e)
}
}
private suspend fun submitToNoteApi(apiType: String, apiUrl: String, apiKey: String, content: String): Boolean {
return withContext(Dispatchers.IO) {
try {
val client = OkHttpClient()
val requestBody = when (apiType) {
"Flomo" -> {
val json = JSONObject()
.put("content", content)
.put("content_type", "markdown")
json.toString().toRequestBody("application/json".toMediaType())
}
"Notion" -> {
val json = JSONObject()
.put("parent", JSONObject().put("database_id", apiKey))
.put("properties", JSONObject()
.put("Name", JSONObject()
.put("title", JSONArray()
.put(JSONObject().put("text", JSONObject().put("content", "AI优化结果")))
)
)
)
json.toString().toRequestBody("application/json".toMediaType())
}
else -> {
val json = JSONObject().put("content", content)
json.toString().toRequestBody("application/json".toMediaType())
}
}
val request = Request.Builder()
.url(apiUrl)
.addHeader("Content-Type", "application/json")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
response.isSuccessful
} catch (e: Exception) {
Log.e("MainActivity", "submitToNoteApi error", e)
false
}
}
}
data class NoteApiConfig(
val apiType: String,
val apiUrl: String,
val apiKey: String
)
data class NoteSettingsData(
val noteApiConfig: NoteApiConfig?
)
}
if (selectedIndex >= models.size) {
selectedIndex = 0
}
return models[selectedIndex]
}
private fun loadPromptsFromConfig() {
Log.d("MainActivity", "loadPromptsFromConfig: Starting")
try {
// Load shared preferences
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
val promptTitles = mutableListOf<String>()
val promptContents = mutableListOf<String>()
// Add default prompt
promptTitles.add("无系统提示词")
promptContents.add("无特殊指令")
@@ -366,16 +240,11 @@ class MainActivity : AppCompatActivity() {
promptContents.add(prompt.content)
}
}
Log.d("MainActivity", "Loaded ${promptConfigs.size} custom prompts from config")
} catch (e: Exception) {
Log.e("MainActivity", "Error loading prompts from config", e)
Log.e("MainActivity", "Error loading prompts", e)
}
} else {
Log.d("MainActivity", "No saved config found, using default prompts")
}
// Add default prompts if no custom prompts found
if (promptTitles.size == 1) {
promptTitles.add("翻译助手")
promptContents.add("将输入的文本翻译成指定语言")
@@ -383,7 +252,6 @@ class MainActivity : AppCompatActivity() {
promptContents.add("解释代码的功能和逻辑")
}
// Add quick action prompts
if (!promptTitles.contains("检查错别字")) {
promptTitles.add("检查错别字")
promptContents.add("请检查以下文本中的错别字并纠正:")
@@ -401,38 +269,19 @@ class MainActivity : AppCompatActivity() {
promptContents.add("请润色以下文本,使其更通顺流畅:")
}
// Store prompt contents in a map for easy access
val promptMap = mutableMapOf<String, String>()
for (i in promptTitles.indices) {
promptMap[promptTitles[i]] = promptContents[i]
}
// Save prompt map to use in onItemSelectedListener
promptSelector.tag = promptMap
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, promptTitles)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
promptSelector.adapter = adapter
promptSelector.setSelection(0)
Log.d("MainActivity", "loadPromptsFromConfig: Completed with ${promptTitles.size} prompts")
} catch (e: Exception) {
Log.e("MainActivity", "loadPromptsFromConfig: Error", e)
// Fallback to default prompts
val promptTitles = listOf("无系统提示词", "翻译助手", "代码解释")
val promptContents = listOf("无特殊指令", "将输入的文本翻译成指定语言", "解释代码的功能和逻辑")
val promptMap = mutableMapOf<String, String>()
for (i in promptTitles.indices) {
promptMap[promptTitles[i]] = promptContents[i]
}
promptSelector.tag = promptMap
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, promptTitles)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
promptSelector.adapter = adapter
promptSelector.setSelection(0)
Log.e("MainActivity", "loadPromptsFromConfig error", e)
}
}
@@ -442,21 +291,10 @@ class MainActivity : AppCompatActivity() {
val btnTranslate = findViewById<LinearLayout>(R.id.btnTranslate)
val btnPolishing = findViewById<LinearLayout>(R.id.btnPolishing)
btnCheckTypos.setOnClickListener {
selectPrompt("检查错别字")
}
btnSummarize.setOnClickListener {
selectPrompt("总结")
}
btnTranslate.setOnClickListener {
selectPrompt("翻译")
}
btnPolishing.setOnClickListener {
selectPrompt("润色")
}
btnCheckTypos.setOnClickListener { selectPrompt("检查错别字") }
btnSummarize.setOnClickListener { selectPrompt("总结") }
btnTranslate.setOnClickListener { selectPrompt("翻译") }
btnPolishing.setOnClickListener { selectPrompt("润色") }
}
private fun selectPrompt(promptName: String) {
@@ -480,153 +318,18 @@ class MainActivity : AppCompatActivity() {
}
}
private fun saveToNoteApi(content: String) {
try {
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
if (json == null) {
Toast.makeText(this, "请先配置笔记API", Toast.LENGTH_SHORT).show()
return
}
val settings = Gson().fromJson(json, NoteSettingsData::class.java)
val noteConfig = settings.noteApiConfig
if (noteConfig == null || noteConfig.apiUrl.isBlank()) {
Toast.makeText(this, "请先配置笔记API", Toast.LENGTH_SHORT).show()
return
}
outputStatusLabel.text = "提交中..."
CoroutineScope(Dispatchers.Main).launch {
try {
val result = submitToNoteApi(noteConfig.apiType, noteConfig.apiUrl, noteConfig.apiKey, content)
if (result) {
outputStatusLabel.text = "已提交"
Toast.makeText(this@MainActivity, "笔记已保存", Toast.LENGTH_SHORT).show()
} else {
outputStatusLabel.text = "提交失败"
Toast.makeText(this@MainActivity, "保存失败,请检查配置", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
outputStatusLabel.text = "提交失败"
Toast.makeText(this@MainActivity, "保存失败: ${e.message}", Toast.LENGTH_SHORT).show()
Log.e("MainActivity", "saveToNoteApi error", e)
}
}
} catch (e: Exception) {
Toast.makeText(this, "请先配置笔记API", Toast.LENGTH_SHORT).show()
Log.e("MainActivity", "saveToNoteApi error", e)
}
}
private suspend fun submitToNoteApi(apiType: String, apiUrl: String, apiKey: String, content: String): Boolean {
return withContext(Dispatchers.IO) {
try {
val client = OkHttpClient()
val (requestBody, useAuth) = when (apiType) {
"Flomo" -> {
val json = JSONObject()
.put("content", content)
.put("content_type", "markdown")
Pair(json.toString().toRequestBody("application/json".toMediaType()), false)
}
"Notion" -> {
val json = JSONObject()
.put("parent", JSONObject().put("database_id", apiKey))
.put("properties", JSONObject()
.put("Name", JSONObject()
.put("title", JSONArray()
.put(JSONObject().put("text", JSONObject().put("content", "AI优化结果")))
)
)
)
Pair(json.toString().toRequestBody("application/json".toMediaType()), true)
}
else -> {
val json = JSONObject().put("content", content)
Pair(json.toString().toRequestBody("application/json".toMediaType()), apiKey.isNotBlank())
}
}
val requestBuilder = Request.Builder()
.url(apiUrl)
.addHeader("Content-Type", "application/json")
.post(requestBody)
if (useAuth && apiKey.isNotBlank()) {
requestBuilder.addHeader("Authorization", "Bearer $apiKey")
}
val request = requestBuilder.build()
val response = client.newCall(request).execute()
response.isSuccessful
} catch (e: Exception) {
Log.e("MainActivity", "submitToNoteApi error", e)
false
}
}
private suspend fun submitToNoteApi(apiType: String, apiUrl: String, apiKey: String, content: String): Boolean {
return withContext(Dispatchers.IO) {
try {
val client = OkHttpClient()
val requestBody = when (apiType) {
"Flomo" -> {
val json = JSONObject()
.put("content", content)
.put("content_type", "markdown")
json.toString().toRequestBody("application/json".toMediaType())
}
"Notion" -> {
val json = JSONObject()
.put("parent", JSONObject().put("database_id", apiKey))
.put("properties", JSONObject()
.put("Name", JSONObject()
.put("title", JSONArray()
.put(JSONObject().put("text", JSONObject().put("content", "AI优化结果")))
)
)
)
json.toString().toRequestBody("application/json".toMediaType())
}
else -> {
val json = JSONObject().put("content", content)
json.toString().toRequestBody("application/json".toMediaType())
}
}
val request = Request.Builder()
.url(apiUrl)
.addHeader("Content-Type", "application/json")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
response.isSuccessful
} catch (e: Exception) {
Log.e("MainActivity", "submitToNoteApi error", e)
false
}
}
}
data class NoteApiConfig(
val apiType: String,
val apiUrl: String,
val apiKey: String
)
}
// Legacy APIConfig class for backward compatibility
data class APIConfig(
val id: Long,
val name: String,
val url: String,
val key: String,
val secretKey: String,
val model: String
data class LLMConfig(val name: String, val baseUrl: String, val apiKey: String, val model: String, val enabled: Boolean = true)
data class SettingsData(
val llmConfigs: List<LLMConfig>?,
val selectedLlmIndex: Int?,
val headerConfigs: List<HeaderConfig>?,
val promptConfigs: List<PromptConfig>?,
val buttonConfigs: List<ButtonConfig>?,
val noteApiConfig: NoteApiConfig?,
val llmConfig: LLMConfig? = null
)
data class HeaderConfig(val key: String, val value: String)
data class PromptConfig(val id: String, val title: String, val content: String, val expanded: Boolean = false)
data class ButtonConfig(val id: String, val label: String, val action: String, val apiUrl: String? = null, val apiMethod: String? = null, val apiBodyTemplate: String? = null, val expanded: Boolean = false)
data class NoteApiConfig(val apiType: String, val apiUrl: String, val apiKey: String)
}

View File

@@ -1,31 +1,18 @@
package com.example.flomo_ai
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.method.PasswordTransformationMethod
import android.util.Log
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import android.widget.RadioGroup
import android.widget.RadioButton
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.example.flomo_ai.ui.theme.ThemeManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -34,18 +21,9 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
import java.util.*
// Data classes for the new settings structure
data class HeaderConfig(val key: String, val value: String)
data class PromptConfig(val id: String, val title: String, val content: String, val expanded: Boolean = false)
data class ButtonConfig(val id: String, val label: String, val action: String, val apiUrl: String? = null, val apiMethod: String? = null, val apiBodyTemplate: String? = null, val expanded: Boolean = false)
class SecondActivity : AppCompatActivity() {
// View references - Model 1
private lateinit var etBaseUrl1: EditText
private lateinit var etApiKey1: EditText
private lateinit var btnToggleApiKey1: ImageButton
@@ -54,7 +32,6 @@ class SecondActivity : AppCompatActivity() {
private lateinit var btnTestConnection1: Button
private lateinit var tvTestStatus1: TextView
// View references - Model 2
private lateinit var etBaseUrl2: EditText
private lateinit var etApiKey2: EditText
private lateinit var btnToggleApiKey2: ImageButton
@@ -63,7 +40,6 @@ class SecondActivity : AppCompatActivity() {
private lateinit var btnTestConnection2: Button
private lateinit var tvTestStatus2: TextView
// View references - Model 3
private lateinit var etBaseUrl3: EditText
private lateinit var etApiKey3: EditText
private lateinit var btnToggleApiKey3: ImageButton
@@ -72,105 +48,23 @@ class SecondActivity : AppCompatActivity() {
private lateinit var btnTestConnection3: Button
private lateinit var tvTestStatus3: TextView
// Header view references
private lateinit var llHeadersList: LinearLayout
private lateinit var btnAddHeader: Button
private lateinit var layoutHeaderContent: LinearLayout
private lateinit var ivHeaderArrow: ImageView
private lateinit var layoutHeaderToggle: LinearLayout
// Prompt view references
private lateinit var llPromptList: LinearLayout
private lateinit var btnAddPrompt: Button
private lateinit var layoutPromptContent: LinearLayout
private lateinit var ivPromptArrow: ImageView
private lateinit var layoutPromptToggle: LinearLayout
// Theme view references
private lateinit var rgThemeMode: RadioGroup
private lateinit var rbThemeFollowSystem: RadioButton
private lateinit var rbThemeLight: RadioButton
private lateinit var rbThemeDark: RadioButton
// Note API view references
private lateinit var spNoteApiType: Spinner
private lateinit var etNoteApiUrl: EditText
private lateinit var etNoteApiKey: EditText
private lateinit var btnToggleNoteApiKey: ImageButton
// Data storage
private var llmConfigs = mutableListOf<LLMConfig>()
private var selectedLlmIndex = 0
private var headerConfigs = mutableListOf<HeaderConfig>()
private var promptConfigs = mutableListOf<PromptConfig>()
private var buttonConfigs = mutableListOf<ButtonConfig>()
// API config (for backward compatibility with existing API calls)
private lateinit var apiConfig: APIConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ThemeManager.applySavedTheme(this)
Log.d("SecondActivity", "onCreate: Starting SecondActivity")
try {
setContentView(R.layout.activity_second)
Log.d("SecondActivity", "onCreate: Layout set")
} catch (e: Exception) {
Log.e("SecondActivity", "onCreate: Error setting layout", e)
throw e
}
try {
// Initialize views
initViews()
Log.d("SecondActivity", "onCreate: Views initialized")
// Load existing configurations
loadConfigurations()
Log.d("SecondActivity", "onCreate: Configurations loaded")
// Setup UI based on loaded data
setupUI()
Log.d("SecondActivity", "onCreate: UI setup completed")
// Back button functionality
findViewById<ImageButton>(R.id.btnBack).setOnClickListener {
Log.d("SecondActivity", "Back button clicked")
finish()
}
// Home button functionality
findViewById<Button>(R.id.btnHome).setOnClickListener {
Log.d("SecondActivity", "Home button clicked")
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}
// Add header button
btnAddHeader.setOnClickListener {
Log.d("SecondActivity", "Add header button clicked")
addHeaderEntry()
}
// Add prompt button
btnAddPrompt.setOnClickListener {
Log.d("SecondActivity", "Add prompt button clicked")
addPromptEntry()
}
Log.d("SecondActivity", "onCreate: Completed successfully")
} catch (e: Exception) {
Log.e("SecondActivity", "onCreate: Error during initialization", e)
throw e
}
}
private fun initViews() {
Log.d("SecondActivity", "initViews: Starting")
try {
// Model 1
etBaseUrl1 = findViewById(R.id.etBaseUrl1)
etApiKey1 = findViewById(R.id.etApiKey1)
btnToggleApiKey1 = findViewById(R.id.btnToggleApiKey1)
@@ -179,7 +73,6 @@ class SecondActivity : AppCompatActivity() {
btnTestConnection1 = findViewById(R.id.btnTestConnection1)
tvTestStatus1 = findViewById(R.id.tvTestStatus1)
// Model 2
etBaseUrl2 = findViewById(R.id.etBaseUrl2)
etApiKey2 = findViewById(R.id.etApiKey2)
btnToggleApiKey2 = findViewById(R.id.btnToggleApiKey2)
@@ -188,7 +81,6 @@ class SecondActivity : AppCompatActivity() {
btnTestConnection2 = findViewById(R.id.btnTestConnection2)
tvTestStatus2 = findViewById(R.id.tvTestStatus2)
// Model 3
etBaseUrl3 = findViewById(R.id.etBaseUrl3)
etApiKey3 = findViewById(R.id.etApiKey3)
btnToggleApiKey3 = findViewById(R.id.btnToggleApiKey3)
@@ -197,115 +89,22 @@ class SecondActivity : AppCompatActivity() {
btnTestConnection3 = findViewById(R.id.btnTestConnection3)
tvTestStatus3 = findViewById(R.id.tvTestStatus3)
// Header Section
llHeadersList = findViewById(R.id.llHeadersList)
btnAddHeader = findViewById(R.id.btnAddHeader)
layoutHeaderContent = findViewById(R.id.layoutHeaderContent)
ivHeaderArrow = findViewById(R.id.ivHeaderArrow)
layoutHeaderToggle = findViewById(R.id.layoutHeaderToggle)
btnToggleApiKey1.setOnClickListener { toggleApiKeyVisibility(etApiKey1, btnToggleApiKey1) }
btnToggleApiKey2.setOnClickListener { toggleApiKeyVisibility(etApiKey2, btnToggleApiKey2) }
btnToggleApiKey3.setOnClickListener { toggleApiKeyVisibility(etApiKey3, btnToggleApiKey3) }
// Prompt Section
llPromptList = findViewById(R.id.llPromptList)
btnAddPrompt = findViewById(R.id.btnAddPrompt)
layoutPromptContent = findViewById(R.id.layoutPromptContent)
ivPromptArrow = findViewById(R.id.ivPromptArrow)
layoutPromptToggle = findViewById(R.id.layoutPromptToggle)
// Theme Section
rgThemeMode = findViewById(R.id.rgThemeMode)
rbThemeFollowSystem = findViewById(R.id.rbThemeFollowSystem)
rbThemeLight = findViewById(R.id.rbThemeLight)
rbThemeDark = findViewById(R.id.rbThemeDark)
// Note API Section
spNoteApiType = findViewById(R.id.spNoteApiType)
etNoteApiUrl = findViewById(R.id.etNoteApiUrl)
etNoteApiKey = findViewById(R.id.etNoteApiKey)
btnToggleNoteApiKey = findViewById(R.id.btnToggleNoteApiKey)
Log.d("SecondActivity", "initViews: All views found")
// Setup API key toggle for Model 1
btnToggleApiKey1.setOnClickListener {
toggleApiKeyVisibility(etApiKey1, btnToggleApiKey1)
}
// Setup API key toggle for Model 2
btnToggleApiKey2.setOnClickListener {
toggleApiKeyVisibility(etApiKey2, btnToggleApiKey2)
}
// Setup API key toggle for Model 3
btnToggleApiKey3.setOnClickListener {
toggleApiKeyVisibility(etApiKey3, btnToggleApiKey3)
}
// Setup Header Toggle (Fold/Unfold)
layoutHeaderToggle.setOnClickListener {
Log.d("SecondActivity", "Header toggle clicked")
val isExpanded = layoutHeaderContent.visibility == View.VISIBLE
if (isExpanded) {
layoutHeaderContent.visibility = View.GONE
ivHeaderArrow.rotation = 0f
} else {
layoutHeaderContent.visibility = View.VISIBLE
ivHeaderArrow.rotation = 90f
}
}
// Setup Prompt Toggle (Fold/Unfold)
layoutPromptToggle.setOnClickListener {
Log.d("SecondActivity", "Prompt toggle clicked")
val isExpanded = layoutPromptContent.visibility == View.VISIBLE
if (isExpanded) {
layoutPromptContent.visibility = View.GONE
ivPromptArrow.rotation = 0f
} else {
layoutPromptContent.visibility = View.VISIBLE
ivPromptArrow.rotation = 90f
}
}
// Setup Note API key toggle
btnToggleNoteApiKey.setOnClickListener {
val isPassword = etNoteApiKey.transformationMethod is PasswordTransformationMethod
etNoteApiKey.transformationMethod = if (isPassword) null else PasswordTransformationMethod()
etNoteApiKey.setSelection(etNoteApiKey.text.length)
if (isPassword) {
btnToggleNoteApiKey.setImageResource(android.R.drawable.ic_menu_view)
} else {
btnToggleNoteApiKey.setImageResource(android.R.drawable.ic_lock_idle_lock)
}
}
// Test connection buttons
btnTestConnection1.setOnClickListener { testConnection1() }
btnTestConnection2.setOnClickListener { testConnection2() }
btnTestConnection3.setOnClickListener { testConnection3() }
Log.d("SecondActivity", "initViews: Completed")
} catch (e: Exception) {
Log.e("SecondActivity", "initViews: Error finding views", e)
throw e
}
}
private fun loadConfigurations() {
Log.d("SecondActivity", "loadConfigurations: Starting")
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
Log.d("SecondActivity", "loadConfigurations: JSON loaded: ${json?.substring(0, minOf(json.length, 100))}")
if (json != null) {
try {
Log.d("SecondActivity", "loadConfigurations: Trying to parse as SettingsData")
val settings = Gson().fromJson(json, SettingsData::class.java)
Log.d("SecondActivity", "loadConfigurations: SettingsData parsed successfully")
headerConfigs = settings.headerConfigs?.toMutableList() ?: mutableListOf()
promptConfigs = settings.promptConfigs?.toMutableList() ?: mutableListOf()
buttonConfigs = settings.buttonConfigs?.toMutableList() ?: mutableListOf()
llmConfigs = settings.llmConfigs?.toMutableList() ?: mutableListOf()
selectedLlmIndex = settings.selectedLlmIndex ?: 0
@@ -330,47 +129,14 @@ class SecondActivity : AppCompatActivity() {
))
}
if (selectedLlmIndex >= llmConfigs.size) {
selectedLlmIndex = 0
}
loadConfigsToViews()
settings.noteApiConfig?.let { noteConfig ->
val apiTypes = listOf("Flomo", "Notion", "Joplin", "Custom")
val typeIndex = apiTypes.indexOf(noteConfig.apiType)
if (typeIndex >= 0) {
spNoteApiType.setSelection(typeIndex)
}
etNoteApiUrl.setText(noteConfig.apiUrl)
etNoteApiKey.setText(noteConfig.apiKey)
}
updateApiKeyVisibilityForAll()
} catch (e: Exception) {
Log.e("SecondActivity", "loadConfigurations: Error parsing SettingsData", e)
try {
Log.d("SecondActivity", "loadConfigurations: Trying to parse as List<APIConfig>")
val type = object : TypeToken<List<APIConfig>>() {}.type
val oldConfigs = Gson().fromJson<List<APIConfig>>(json, type)
if (oldConfigs.isNotEmpty()) {
val oldConfig = oldConfigs[0]
etBaseUrl1.setText(oldConfig.url)
etApiKey1.setText(oldConfig.key)
etModel1.setText(oldConfig.model)
updateApiKeyVisibilityForAll()
}
} catch (e2: Exception) {
Log.e("SecondActivity", "loadConfigurations: Error parsing List<APIConfig>", e2)
setDefaultConfigs()
}
}
} else {
Log.d("SecondActivity", "loadConfigurations: No saved config, using defaults")
setDefaultConfigs()
}
Log.d("SecondActivity", "loadConfigurations: Completed")
}
private fun loadConfigsToViews() {
@@ -427,61 +193,6 @@ class SecondActivity : AppCompatActivity() {
editText.setSelection(editText.text.length)
}
private fun setupUI() {
// Setup headers
llHeadersList.removeAllViews()
if (headerConfigs.isEmpty()) {
addHeaderEntry()
} else {
for (header in headerConfigs) {
addHeaderEntry(header.key, header.value)
}
}
// Setup prompts
llPromptList.removeAllViews()
if (promptConfigs.isEmpty()) {
addPromptEntry()
} else {
for (prompt in promptConfigs) {
addPromptEntry(prompt.title, prompt.content)
}
}
// Setup theme
setupTheme()
// Setup Note API spinner
val noteApiTypes = listOf("Flomo", "Notion", "Joplin", "Custom")
val noteApiAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, noteApiTypes)
noteApiAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spNoteApiType.adapter = noteApiAdapter
}
private fun setupTheme() {
val themeMode = ThemeManager.getThemeMode(this)
when (themeMode) {
ThemeManager.THEME_FOLLOW_SYSTEM -> rbThemeFollowSystem.isChecked = true
ThemeManager.THEME_LIGHT -> rbThemeLight.isChecked = true
ThemeManager.THEME_DARK -> rbThemeDark.isChecked = true
else -> rbThemeFollowSystem.isChecked = true
}
rgThemeMode.setOnCheckedChangeListener { _, checkedId ->
val newMode = when (checkedId) {
R.id.rbThemeFollowSystem -> ThemeManager.THEME_FOLLOW_SYSTEM
R.id.rbThemeLight -> ThemeManager.THEME_LIGHT
R.id.rbThemeDark -> ThemeManager.THEME_DARK
else -> ThemeManager.THEME_FOLLOW_SYSTEM
}
ThemeManager.setThemeMode(this, newMode)
Log.d("SecondActivity", "Theme mode changed to: ${ThemeManager.getThemeModeName(newMode)}")
recreate()
}
}
private fun toggleApiKeyVisibility(editText: EditText, button: ImageButton) {
val isPassword = editText.transformationMethod is PasswordTransformationMethod
editText.transformationMethod = if (isPassword) null else PasswordTransformationMethod()
@@ -595,46 +306,12 @@ class SecondActivity : AppCompatActivity() {
)
}
private fun addHeaderEntry(key: String = "", value: String = "") {
val view = layoutInflater.inflate(R.layout.header_entry, null)
val etKey = view.findViewById<EditText>(R.id.etHeaderKey)
val etValue = view.findViewById<EditText>(R.id.etHeaderValue)
val btnRemove = view.findViewById<ImageButton>(R.id.btnRemoveHeader)
etKey.setText(key)
etValue.setText(value)
btnRemove.setOnClickListener {
llHeadersList.removeView(view)
}
llHeadersList.addView(view)
}
private fun addPromptEntry(title: String = "", content: String = "") {
val view = layoutInflater.inflate(R.layout.prompt_entry, null)
val etTitle = view.findViewById<EditText>(R.id.etPromptTitle)
val etContent = view.findViewById<EditText>(R.id.etPromptContent)
val btnRemove = view.findViewById<ImageButton>(R.id.btnRemovePrompt)
etTitle.setText(title)
etContent.setText(content)
btnRemove.setOnClickListener {
llPromptList.removeView(view)
}
llPromptList.addView(view)
}
// Save all configurations when leaving or explicitly saving
override fun onPause() {
super.onPause()
saveConfigurations()
}
private fun saveConfigurations() {
// Update LLM configs from views
llmConfigs.clear()
llmConfigs.add(LLMConfig(
name = etModelName1.text.toString().ifEmpty { "默认配置" },
@@ -655,68 +332,20 @@ class SecondActivity : AppCompatActivity() {
model = etModel3.text.toString()
))
headerConfigs.clear()
for (i in 0 until llHeadersList.childCount) {
val view = llHeadersList.getChildAt(i)
val key = view.findViewById<EditText>(R.id.etHeaderKey).text.toString()
val value = view.findViewById<EditText>(R.id.etHeaderValue).text.toString()
if (key.isNotBlank() && value.isNotBlank()) {
headerConfigs.add(HeaderConfig(key, value))
}
}
promptConfigs.clear()
for (i in 0 until llPromptList.childCount) {
val view = llPromptList.getChildAt(i)
val title = view.findViewById<EditText>(R.id.etPromptTitle).text.toString()
val content = view.findViewById<EditText>(R.id.etPromptContent).text.toString()
if (title.isNotBlank() && content.isNotBlank()) {
promptConfigs.add(PromptConfig(id = "prompt_$i", title = title, content = content))
}
}
buttonConfigs.clear()
val noteApiConfig = NoteApiConfig(
apiType = spNoteApiType.selectedItem.toString(),
apiUrl = etNoteApiUrl.text.toString(),
apiKey = etNoteApiKey.text.toString()
)
val settingsData = SettingsData(
llmConfigs = llmConfigs,
selectedLlmIndex = selectedLlmIndex,
headerConfigs = headerConfigs,
promptConfigs = promptConfigs,
buttonConfigs = buttonConfigs,
noteApiConfig = noteApiConfig
headerConfigs = null,
promptConfigs = null,
buttonConfigs = null,
noteApiConfig = null
)
val json = Gson().toJson(settingsData)
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
sharedPrefs.edit().putString("configs", json).apply()
apiConfig = APIConfig(
System.currentTimeMillis(),
"llm-config",
etBaseUrl1.text.toString(),
etApiKey1.text.toString(),
"",
etModel1.text.toString()
)
}
// Legacy APIConfig class for backward compatibility with existing code
data class APIConfig(
val id: Long,
val name: String,
val url: String,
val key: String,
val secretKey: String,
val model: String
)
// New data classes for settings structure
data class LLMConfig(
val name: String,
val baseUrl: String,
@@ -725,14 +354,8 @@ class SecondActivity : AppCompatActivity() {
val enabled: Boolean = true
)
data class NoteApiConfig(
val apiType: String,
val apiUrl: String,
val apiKey: String
)
data class SettingsData(
val llmConfigs: List<LLMConfig>,
val llmConfigs: List<LLMConfig>?,
val selectedLlmIndex: Int?,
val headerConfigs: List<HeaderConfig>?,
val promptConfigs: List<PromptConfig>?,
@@ -740,4 +363,9 @@ class SecondActivity : AppCompatActivity() {
val noteApiConfig: NoteApiConfig?,
val llmConfig: LLMConfig? = null
)
data class HeaderConfig(val key: String, val value: String)
data class PromptConfig(val id: String, val title: String, val content: String, val expanded: Boolean = false)
data class ButtonConfig(val id: String, val label: String, val action: String, val apiUrl: String? = null, val apiMethod: String? = null, val apiBodyTemplate: String? = null, val expanded: Boolean = false)
data class NoteApiConfig(val apiType: String, val apiUrl: String, val apiKey: String)
}