feat: 简化SecondActivity,支持3个模型配置
This commit is contained in:
@@ -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,234 +86,146 @@ class MainActivity : AppCompatActivity() {
|
||||
})
|
||||
|
||||
sendButton.setOnClickListener {
|
||||
Log.d("MainActivity", "Send button clicked")
|
||||
val inputText = inputEditText.text.toString()
|
||||
if (inputText.isNotEmpty()) {
|
||||
outputStatusLabel.text = "连接中…"
|
||||
outputTextView.setText("正在生成...")
|
||||
|
||||
val promptContent = promptContentText.text.toString()
|
||||
val fullContent = if (promptContent.isNotEmpty() && promptContent != "无特殊指令") {
|
||||
"$promptContent$inputText"
|
||||
} else {
|
||||
inputText
|
||||
}
|
||||
Log.d("MainActivity", "Full content to send: $fullContent")
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
val requestBody = JSONObject().apply {
|
||||
put("model", model)
|
||||
put("messages", messagesJson)
|
||||
put("max_tokens", 65536)
|
||||
put("temperature", 1.0)
|
||||
}
|
||||
|
||||
val client = OkHttpClient()
|
||||
val requestBuilder = Request.Builder()
|
||||
.url("$baseUrl/chat/completions")
|
||||
.addHeader("Content-Type", "application/json")
|
||||
|
||||
if (apiKey.isNotBlank()) {
|
||||
requestBuilder.addHeader("Authorization", "Bearer $apiKey")
|
||||
}
|
||||
|
||||
val request = requestBuilder
|
||||
.post(requestBody.toString().toRequestBody("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
val response = client.newCall(request).execute()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (response.isSuccessful) {
|
||||
val responseBody = response.body?.string()
|
||||
val responseJson = JSONObject(responseBody ?: "")
|
||||
val choices = responseJson.getJSONArray("choices")
|
||||
if (choices.length() > 0) {
|
||||
val message = choices.getJSONObject(0).getJSONObject("message")
|
||||
val content = message.getString("content")
|
||||
outputStatusLabel.text = "已完成"
|
||||
outputTextView.setText(content)
|
||||
} else {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("API 返回空结果")
|
||||
}
|
||||
} else {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("API 错误: ${response.code} ${response.message}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("错误: ${e.message}")
|
||||
}
|
||||
Log.e("MainActivity", "Error calling API", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w("MainActivity", "Input text is empty")
|
||||
if (inputText.isEmpty()) {
|
||||
Toast.makeText(this, "请输入内容", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
}
|
||||
|
||||
stopButton.setOnClickListener {
|
||||
Log.d("MainActivity", "Stop button clicked")
|
||||
outputStatusLabel.text = "已停止"
|
||||
Toast.makeText(this, "生成已停止", Toast.LENGTH_SHORT).show()
|
||||
val promptContent = promptContentText.text.toString()
|
||||
val fullContent = if (promptContent.isNotEmpty() && promptContent != "无特殊指令") {
|
||||
"$promptContent$inputText"
|
||||
} else {
|
||||
inputText
|
||||
}
|
||||
|
||||
// 加载配置并发送请求
|
||||
loadLlmConfigsAndSend(fullContent)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
initQuickButtons()
|
||||
}
|
||||
|
||||
private fun loadModelsFromConfig() {
|
||||
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
||||
val json = sharedPrefs.getString("configs", null)
|
||||
|
||||
private fun saveToNoteApi(content: String) {
|
||||
if (json != null) {
|
||||
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, 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 messagesJson = JSONArray().apply {
|
||||
put(JSONObject().apply {
|
||||
put("role", "user")
|
||||
put("content", content)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
val requestBody = JSONObject().apply {
|
||||
put("model", model)
|
||||
put("messages", messagesJson)
|
||||
put("max_tokens", 65536)
|
||||
}
|
||||
|
||||
outputStatusLabel.text = "提交中..."
|
||||
val client = OkHttpClient()
|
||||
val requestBuilder = Request.Builder()
|
||||
.url("$baseUrl/chat/completions")
|
||||
.addHeader("Content-Type", "application/json")
|
||||
|
||||
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()
|
||||
if (apiKey.isNotBlank()) {
|
||||
requestBuilder.addHeader("Authorization", "Bearer $apiKey")
|
||||
}
|
||||
|
||||
val request = requestBuilder
|
||||
.post(requestBody.toString().toRequestBody("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
val response = client.newCall(request).execute()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (response.isSuccessful) {
|
||||
val responseBody = response.body?.string()
|
||||
val responseJson = JSONObject(responseBody ?: "")
|
||||
val choices = responseJson.getJSONArray("choices")
|
||||
if (choices.length() > 0) {
|
||||
val message = choices.getJSONObject(0).getJSONObject("message")
|
||||
val result = message.getString("content")
|
||||
outputStatusLabel.text = "已完成"
|
||||
outputTextView.setText(result)
|
||||
} else {
|
||||
outputStatusLabel.text = "提交失败"
|
||||
Toast.makeText(this@MainActivity, "保存失败,请检查配置", Toast.LENGTH_SHORT).show()
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("API 返回空结果")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
outputStatusLabel.text = "提交失败"
|
||||
Toast.makeText(this@MainActivity, "保存失败: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
Log.e("MainActivity", "saveToNoteApi error", e)
|
||||
} else {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("API 错误: ${response.code} ${response.message}")
|
||||
}
|
||||
}
|
||||
} 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
|
||||
withContext(Dispatchers.Main) {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("错误: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -479,154 +317,19 @@ class MainActivity : AppCompatActivity() {
|
||||
findViewById<Button>(R.id.sendButton).performClick()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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,240 +48,63 @@ 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
|
||||
}
|
||||
setContentView(R.layout.activity_second)
|
||||
|
||||
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
|
||||
initViews()
|
||||
loadConfigurations()
|
||||
|
||||
findViewById<ImageButton>(R.id.btnBack).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
etModel1 = findViewById(R.id.etModel1)
|
||||
etModelName1 = findViewById(R.id.etModelName1)
|
||||
btnTestConnection1 = findViewById(R.id.btnTestConnection1)
|
||||
tvTestStatus1 = findViewById(R.id.tvTestStatus1)
|
||||
etBaseUrl1 = findViewById(R.id.etBaseUrl1)
|
||||
etApiKey1 = findViewById(R.id.etApiKey1)
|
||||
btnToggleApiKey1 = findViewById(R.id.btnToggleApiKey1)
|
||||
etModel1 = findViewById(R.id.etModel1)
|
||||
etModelName1 = findViewById(R.id.etModelName1)
|
||||
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)
|
||||
etModel2 = findViewById(R.id.etModel2)
|
||||
etModelName2 = findViewById(R.id.etModelName2)
|
||||
btnTestConnection2 = findViewById(R.id.btnTestConnection2)
|
||||
tvTestStatus2 = findViewById(R.id.tvTestStatus2)
|
||||
etBaseUrl2 = findViewById(R.id.etBaseUrl2)
|
||||
etApiKey2 = findViewById(R.id.etApiKey2)
|
||||
btnToggleApiKey2 = findViewById(R.id.btnToggleApiKey2)
|
||||
etModel2 = findViewById(R.id.etModel2)
|
||||
etModelName2 = findViewById(R.id.etModelName2)
|
||||
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)
|
||||
etModel3 = findViewById(R.id.etModel3)
|
||||
etModelName3 = findViewById(R.id.etModelName3)
|
||||
btnTestConnection3 = findViewById(R.id.btnTestConnection3)
|
||||
tvTestStatus3 = findViewById(R.id.tvTestStatus3)
|
||||
etBaseUrl3 = findViewById(R.id.etBaseUrl3)
|
||||
etApiKey3 = findViewById(R.id.etApiKey3)
|
||||
btnToggleApiKey3 = findViewById(R.id.btnToggleApiKey3)
|
||||
etModel3 = findViewById(R.id.etModel3)
|
||||
etModelName3 = findViewById(R.id.etModelName3)
|
||||
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)
|
||||
|
||||
// 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")
|
||||
btnToggleApiKey1.setOnClickListener { toggleApiKeyVisibility(etApiKey1, btnToggleApiKey1) }
|
||||
btnToggleApiKey2.setOnClickListener { toggleApiKeyVisibility(etApiKey2, btnToggleApiKey2) }
|
||||
btnToggleApiKey3.setOnClickListener { toggleApiKeyVisibility(etApiKey3, btnToggleApiKey3) }
|
||||
|
||||
// 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
|
||||
}
|
||||
btnTestConnection1.setOnClickListener { testConnection1() }
|
||||
btnTestConnection2.setOnClickListener { testConnection2() }
|
||||
btnTestConnection3.setOnClickListener { testConnection3() }
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
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,
|
||||
@@ -724,15 +353,9 @@ class SecondActivity : AppCompatActivity() {
|
||||
val model: String,
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user