支持多个大模型配置,可添加、选择、切换模型
This commit is contained in:
@@ -40,17 +40,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private lateinit var promptSelector: Spinner
|
private lateinit var promptSelector: Spinner
|
||||||
private lateinit var promptNameText: TextView
|
private lateinit var promptNameText: TextView
|
||||||
private lateinit var promptContentText: TextView
|
private lateinit var promptContentText: TextView
|
||||||
|
private lateinit var headerModelSelector: Spinner
|
||||||
|
|
||||||
// Data classes matching SecondActivity
|
// Data classes matching SecondActivity
|
||||||
data class HeaderConfig(val key: String, val value: String)
|
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 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 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 baseUrl: String, val apiKey: String, val model: String)
|
data class LLMConfig(val name: String, val baseUrl: String, val apiKey: String, val model: String)
|
||||||
data class SettingsData(
|
data class SettingsData(
|
||||||
val llmConfig: LLMConfig?,
|
val llmConfigs: List<LLMConfig>?,
|
||||||
|
val selectedLlmIndex: Int?,
|
||||||
val headerConfigs: List<HeaderConfig>?,
|
val headerConfigs: List<HeaderConfig>?,
|
||||||
val promptConfigs: List<PromptConfig>?,
|
val promptConfigs: List<PromptConfig>?,
|
||||||
val buttonConfigs: List<ButtonConfig>?
|
val buttonConfigs: List<ButtonConfig>?,
|
||||||
|
val llmConfig: LLMConfig? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@SuppressLint("MissingInflatedId", "CutPasteId", "SetTextI18n")
|
@SuppressLint("MissingInflatedId", "CutPasteId", "SetTextI18n")
|
||||||
@@ -71,12 +74,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
outputTextView = findViewById<EditText>(R.id.outputTextView)
|
outputTextView = findViewById<EditText>(R.id.outputTextView)
|
||||||
val btnCopyResult = findViewById<Button>(R.id.btnCopyResult)
|
val btnCopyResult = findViewById<Button>(R.id.btnCopyResult)
|
||||||
val headerTitle = findViewById<TextView>(R.id.headerTitle)
|
val headerTitle = findViewById<TextView>(R.id.headerTitle)
|
||||||
val headerModelName = findViewById<TextView>(R.id.headerModelName)
|
headerModelSelector = findViewById<Spinner>(R.id.headerModelSelector)
|
||||||
Log.d("MainActivity", "onCreate: Views initialized")
|
Log.d("MainActivity", "onCreate: Views initialized")
|
||||||
|
|
||||||
headerTitle.text = "AI优化"
|
headerTitle.text = "AI优化"
|
||||||
val savedModel = loadModelFromConfig()
|
loadModelsFromConfig()
|
||||||
headerModelName.text = if (savedModel.isNotBlank()) savedModel else "GPT-4o"
|
|
||||||
|
|
||||||
// Initialize quick action buttons
|
// Initialize quick action buttons
|
||||||
initQuickButtons()
|
initQuickButtons()
|
||||||
@@ -129,21 +131,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
val llmConfig = getSelectedModelConfig()
|
||||||
val json = sharedPrefs.getString("configs", null)
|
val baseUrl = llmConfig.baseUrl.ifEmpty { "https://open.bigmodel.cn/api/paas/v4" }
|
||||||
|
val apiKey = llmConfig.apiKey
|
||||||
var baseUrl = "https://open.bigmodel.cn/api/paas/v4"
|
val model = llmConfig.model.ifEmpty { "glm-4.7-flash" }
|
||||||
var apiKey = ""
|
|
||||||
var model = "glm-4.7-flash"
|
|
||||||
|
|
||||||
if (json != null) {
|
|
||||||
val settings = Gson().fromJson(json, SettingsData::class.java)
|
|
||||||
settings.llmConfig?.let {
|
|
||||||
if (it.baseUrl.isNotBlank()) baseUrl = it.baseUrl
|
|
||||||
if (it.apiKey.isNotBlank()) apiKey = it.apiKey
|
|
||||||
if (it.model.isNotBlank()) model = it.model
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val messagesJson = JSONArray().apply {
|
val messagesJson = JSONArray().apply {
|
||||||
put(JSONObject().apply {
|
put(JSONObject().apply {
|
||||||
@@ -261,22 +252,110 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadModelFromConfig(): String {
|
private fun loadModelsFromConfig() {
|
||||||
return try {
|
try {
|
||||||
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
||||||
val json = sharedPrefs.getString("configs", null)
|
val json = sharedPrefs.getString("configs", null)
|
||||||
|
|
||||||
|
var models = mutableListOf<LLMConfig>()
|
||||||
|
var selectedIndex = 0
|
||||||
|
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
val settings = Gson().fromJson(json, SettingsData::class.java)
|
try {
|
||||||
settings.llmConfig?.model?.takeIf { it.isNotBlank() } ?: ""
|
val settings = Gson().fromJson(json, SettingsData::class.java)
|
||||||
} else {
|
models = settings.llmConfigs?.toMutableList() ?: mutableListOf()
|
||||||
""
|
selectedIndex = settings.selectedLlmIndex ?: 0
|
||||||
|
|
||||||
|
if (models.isEmpty()) {
|
||||||
|
settings.llmConfig?.let { legacy ->
|
||||||
|
models.add(LLMConfig(
|
||||||
|
name = "默认配置",
|
||||||
|
baseUrl = legacy.baseUrl,
|
||||||
|
apiKey = legacy.apiKey,
|
||||||
|
model = legacy.model
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "Error parsing settings, using defaults", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (models.isEmpty()) {
|
||||||
|
models.add(LLMConfig(
|
||||||
|
name = "默认配置",
|
||||||
|
baseUrl = "https://open.bigmodel.cn/api/paas/v4",
|
||||||
|
apiKey = "",
|
||||||
|
model = "glm-4.7-flash"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIndex >= models.size) {
|
||||||
|
selectedIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val names = models.map { it.name.ifEmpty { "未命名" } }
|
||||||
|
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, names)
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
headerModelSelector.adapter = adapter
|
||||||
|
headerModelSelector.setSelection(selectedIndex)
|
||||||
|
|
||||||
|
headerModelSelector.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: android.widget.AdapterView<*>, view: android.view.View?, position: Int, id: Long) {
|
||||||
|
Log.d("MainActivity", "Model selected: ${models[position].name}")
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: android.widget.AdapterView<*>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MainActivity", "Loaded ${models.size} model configs")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MainActivity", "Error loading model from config", e)
|
Log.e("MainActivity", "Error loading models from config", e)
|
||||||
""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSelectedModelConfig(): LLMConfig {
|
||||||
|
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
||||||
|
val json = sharedPrefs.getString("configs", null)
|
||||||
|
|
||||||
|
var models = mutableListOf<LLMConfig>()
|
||||||
|
var selectedIndex = headerModelSelector.selectedItemPosition
|
||||||
|
|
||||||
|
if (json != null) {
|
||||||
|
try {
|
||||||
|
val settings = Gson().fromJson(json, SettingsData::class.java)
|
||||||
|
models = settings.llmConfigs?.toMutableList() ?: mutableListOf()
|
||||||
|
|
||||||
|
if (models.isEmpty()) {
|
||||||
|
settings.llmConfig?.let { legacy ->
|
||||||
|
models.add(LLMConfig(
|
||||||
|
name = "默认配置",
|
||||||
|
baseUrl = legacy.baseUrl,
|
||||||
|
apiKey = legacy.apiKey,
|
||||||
|
model = legacy.model
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "Error parsing settings", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models.isEmpty()) {
|
||||||
|
return LLMConfig(
|
||||||
|
name = "默认配置",
|
||||||
|
baseUrl = "https://open.bigmodel.cn/api/paas/v4",
|
||||||
|
apiKey = "",
|
||||||
|
model = "glm-4.7-flash"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIndex >= models.size) {
|
||||||
|
selectedIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return models[selectedIndex]
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadPromptsFromConfig() {
|
private fun loadPromptsFromConfig() {
|
||||||
Log.d("MainActivity", "loadPromptsFromConfig: Starting")
|
Log.d("MainActivity", "loadPromptsFromConfig: Starting")
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -46,10 +46,14 @@ data class ButtonConfig(val id: String, val label: String, val action: String, v
|
|||||||
class SecondActivity : AppCompatActivity() {
|
class SecondActivity : AppCompatActivity() {
|
||||||
|
|
||||||
// View references
|
// View references
|
||||||
|
private lateinit var spModelSelector: Spinner
|
||||||
|
private lateinit var llModelList: LinearLayout
|
||||||
|
private lateinit var btnAddModel: Button
|
||||||
private lateinit var etBaseUrl: EditText
|
private lateinit var etBaseUrl: EditText
|
||||||
private lateinit var etApiKey: EditText
|
private lateinit var etApiKey: EditText
|
||||||
private lateinit var btnToggleApiKey: ImageButton
|
private lateinit var btnToggleApiKey: ImageButton
|
||||||
private lateinit var etModel: EditText
|
private lateinit var etModel: EditText
|
||||||
|
private lateinit var etModelName: EditText
|
||||||
private lateinit var btnTestConnection: Button
|
private lateinit var btnTestConnection: Button
|
||||||
private lateinit var tvTestStatus: TextView
|
private lateinit var tvTestStatus: TextView
|
||||||
private lateinit var llHeadersList: LinearLayout
|
private lateinit var llHeadersList: LinearLayout
|
||||||
@@ -78,6 +82,8 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
private lateinit var btnToggleNoteApiKey: ImageButton
|
private lateinit var btnToggleNoteApiKey: ImageButton
|
||||||
|
|
||||||
// Data storage
|
// Data storage
|
||||||
|
private var llmConfigs = mutableListOf<LLMConfig>()
|
||||||
|
private var selectedLlmIndex = 0
|
||||||
private var headerConfigs = mutableListOf<HeaderConfig>()
|
private var headerConfigs = mutableListOf<HeaderConfig>()
|
||||||
private var promptConfigs = mutableListOf<PromptConfig>()
|
private var promptConfigs = mutableListOf<PromptConfig>()
|
||||||
private var buttonConfigs = mutableListOf<ButtonConfig>()
|
private var buttonConfigs = mutableListOf<ButtonConfig>()
|
||||||
@@ -119,9 +125,7 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
// Home button functionality
|
// Home button functionality
|
||||||
findViewById<Button>(R.id.btnHome).setOnClickListener {
|
findViewById<Button>(R.id.btnHome).setOnClickListener {
|
||||||
Log.d("SecondActivity", "Home button clicked")
|
Log.d("SecondActivity", "Home button clicked")
|
||||||
// Create intent to go back to MainActivity
|
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
// Clear the activity stack to start fresh
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
@@ -153,6 +157,9 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
etApiKey = findViewById(R.id.etApiKey)
|
etApiKey = findViewById(R.id.etApiKey)
|
||||||
btnToggleApiKey = findViewById(R.id.btnToggleApiKey)
|
btnToggleApiKey = findViewById(R.id.btnToggleApiKey)
|
||||||
etModel = findViewById(R.id.etModel)
|
etModel = findViewById(R.id.etModel)
|
||||||
|
etModelName = findViewById(R.id.etModelName)
|
||||||
|
spModelSelector = findViewById(R.id.spModelSelector)
|
||||||
|
btnAddModel = findViewById(R.id.btnAddModel)
|
||||||
btnTestConnection = findViewById(R.id.btnTestConnection)
|
btnTestConnection = findViewById(R.id.btnTestConnection)
|
||||||
tvTestStatus = findViewById(R.id.tvTestStatus)
|
tvTestStatus = findViewById(R.id.tvTestStatus)
|
||||||
|
|
||||||
@@ -189,14 +196,12 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
Log.d("SecondActivity", "API key toggle clicked")
|
Log.d("SecondActivity", "API key toggle clicked")
|
||||||
val isPassword = etApiKey.transformationMethod is PasswordTransformationMethod
|
val isPassword = etApiKey.transformationMethod is PasswordTransformationMethod
|
||||||
etApiKey.transformationMethod = if (isPassword) null else PasswordTransformationMethod()
|
etApiKey.transformationMethod = if (isPassword) null else PasswordTransformationMethod()
|
||||||
// Move cursor to end
|
|
||||||
etApiKey.setSelection(etApiKey.text.length)
|
etApiKey.setSelection(etApiKey.text.length)
|
||||||
|
|
||||||
// Update icon based on state
|
|
||||||
if (isPassword) {
|
if (isPassword) {
|
||||||
btnToggleApiKey.setImageResource(android.R.drawable.ic_menu_view) // Show eye
|
btnToggleApiKey.setImageResource(android.R.drawable.ic_menu_view)
|
||||||
} else {
|
} else {
|
||||||
btnToggleApiKey.setImageResource(android.R.drawable.ic_lock_idle_lock) // Show lock
|
btnToggleApiKey.setImageResource(android.R.drawable.ic_lock_idle_lock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,10 +211,10 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
val isExpanded = layoutHeaderContent.visibility == View.VISIBLE
|
val isExpanded = layoutHeaderContent.visibility == View.VISIBLE
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
layoutHeaderContent.visibility = View.GONE
|
layoutHeaderContent.visibility = View.GONE
|
||||||
ivHeaderArrow.rotation = 0f // Point right
|
ivHeaderArrow.rotation = 0f
|
||||||
} else {
|
} else {
|
||||||
layoutHeaderContent.visibility = View.VISIBLE
|
layoutHeaderContent.visibility = View.VISIBLE
|
||||||
ivHeaderArrow.rotation = 90f // Point down
|
ivHeaderArrow.rotation = 90f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +224,10 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
val isExpanded = layoutPromptContent.visibility == View.VISIBLE
|
val isExpanded = layoutPromptContent.visibility == View.VISIBLE
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
layoutPromptContent.visibility = View.GONE
|
layoutPromptContent.visibility = View.GONE
|
||||||
ivPromptArrow.rotation = 0f // Point right
|
ivPromptArrow.rotation = 0f
|
||||||
} else {
|
} else {
|
||||||
layoutPromptContent.visibility = View.VISIBLE
|
layoutPromptContent.visibility = View.VISIBLE
|
||||||
ivPromptArrow.rotation = 90f // Point down
|
ivPromptArrow.rotation = 90f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,14 +258,12 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun loadConfigurations() {
|
private fun loadConfigurations() {
|
||||||
Log.d("SecondActivity", "loadConfigurations: Starting")
|
Log.d("SecondActivity", "loadConfigurations: Starting")
|
||||||
// Load shared preferences
|
|
||||||
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
||||||
val json = sharedPrefs.getString("configs", null)
|
val json = sharedPrefs.getString("configs", null)
|
||||||
Log.d("SecondActivity", "loadConfigurations: JSON loaded: ${json?.substring(0, minOf(json.length, 100))}")
|
Log.d("SecondActivity", "loadConfigurations: JSON loaded: ${json?.substring(0, minOf(json.length, 100))}")
|
||||||
|
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
try {
|
try {
|
||||||
// Try to load as new format first
|
|
||||||
Log.d("SecondActivity", "loadConfigurations: Trying to parse as SettingsData")
|
Log.d("SecondActivity", "loadConfigurations: Trying to parse as SettingsData")
|
||||||
val settings = Gson().fromJson(json, SettingsData::class.java)
|
val settings = Gson().fromJson(json, SettingsData::class.java)
|
||||||
Log.d("SecondActivity", "loadConfigurations: SettingsData parsed successfully")
|
Log.d("SecondActivity", "loadConfigurations: SettingsData parsed successfully")
|
||||||
@@ -268,12 +271,27 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
promptConfigs = settings.promptConfigs?.toMutableList() ?: mutableListOf()
|
promptConfigs = settings.promptConfigs?.toMutableList() ?: mutableListOf()
|
||||||
buttonConfigs = settings.buttonConfigs?.toMutableList() ?: mutableListOf()
|
buttonConfigs = settings.buttonConfigs?.toMutableList() ?: mutableListOf()
|
||||||
|
|
||||||
// Load LLM config
|
llmConfigs = settings.llmConfigs?.toMutableList() ?: mutableListOf()
|
||||||
etBaseUrl.setText(settings.llmConfig?.baseUrl ?: "https://api.openai.com/v1")
|
selectedLlmIndex = settings.selectedLlmIndex ?: 0
|
||||||
etApiKey.setText(settings.llmConfig?.apiKey ?: "")
|
|
||||||
etModel.setText(settings.llmConfig?.model ?: "gpt-4o")
|
if (llmConfigs.isEmpty()) {
|
||||||
|
val legacyBaseUrl = settings.llmConfig?.baseUrl ?: "https://api.openai.com/v1"
|
||||||
|
val legacyApiKey = settings.llmConfig?.apiKey ?: ""
|
||||||
|
val legacyModel = settings.llmConfig?.model ?: "gpt-4o"
|
||||||
|
llmConfigs.add(LLMConfig(
|
||||||
|
name = "默认配置",
|
||||||
|
baseUrl = legacyBaseUrl,
|
||||||
|
apiKey = legacyApiKey,
|
||||||
|
model = legacyModel
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedLlmIndex >= llmConfigs.size) {
|
||||||
|
selectedLlmIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSelectedModelToFields()
|
||||||
|
|
||||||
// Load Note API config
|
|
||||||
settings.noteApiConfig?.let { noteConfig ->
|
settings.noteApiConfig?.let { noteConfig ->
|
||||||
val apiTypes = listOf("Flomo", "Notion", "Joplin", "Custom")
|
val apiTypes = listOf("Flomo", "Notion", "Joplin", "Custom")
|
||||||
val typeIndex = apiTypes.indexOf(noteConfig.apiType)
|
val typeIndex = apiTypes.indexOf(noteConfig.apiType)
|
||||||
@@ -284,12 +302,10 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
etNoteApiKey.setText(noteConfig.apiKey)
|
etNoteApiKey.setText(noteConfig.apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update API key visibility based on whether it has text
|
|
||||||
updateApiKeyVisibility()
|
updateApiKeyVisibility()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("SecondActivity", "loadConfigurations: Error parsing SettingsData", e)
|
Log.e("SecondActivity", "loadConfigurations: Error parsing SettingsData", e)
|
||||||
// If new format fails, try to load old format for migration
|
|
||||||
try {
|
try {
|
||||||
Log.d("SecondActivity", "loadConfigurations: Trying to parse as List<APIConfig>")
|
Log.d("SecondActivity", "loadConfigurations: Trying to parse as List<APIConfig>")
|
||||||
val type = object : TypeToken<List<APIConfig>>() {}.type
|
val type = object : TypeToken<List<APIConfig>>() {}.type
|
||||||
@@ -303,14 +319,12 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
} catch (e2: Exception) {
|
} catch (e2: Exception) {
|
||||||
Log.e("SecondActivity", "loadConfigurations: Error parsing List<APIConfig>", e2)
|
Log.e("SecondActivity", "loadConfigurations: Error parsing List<APIConfig>", e2)
|
||||||
// If both fail, use defaults
|
|
||||||
etBaseUrl.setText("https://api.openai.com/v1")
|
etBaseUrl.setText("https://api.openai.com/v1")
|
||||||
etModel.setText("gpt-4o")
|
etModel.setText("gpt-4o")
|
||||||
updateApiKeyVisibility()
|
updateApiKeyVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No saved config, use defaults
|
|
||||||
Log.d("SecondActivity", "loadConfigurations: No saved config, using defaults")
|
Log.d("SecondActivity", "loadConfigurations: No saved config, using defaults")
|
||||||
etBaseUrl.setText("https://api.openai.com/v1")
|
etBaseUrl.setText("https://api.openai.com/v1")
|
||||||
etModel.setText("gpt-4o")
|
etModel.setText("gpt-4o")
|
||||||
@@ -319,18 +333,59 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
Log.d("SecondActivity", "loadConfigurations: Completed")
|
Log.d("SecondActivity", "loadConfigurations: Completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadSelectedModelToFields() {
|
||||||
|
if (llmConfigs.isNotEmpty() && selectedLlmIndex < llmConfigs.size) {
|
||||||
|
val config = llmConfigs[selectedLlmIndex]
|
||||||
|
etBaseUrl.setText(config.baseUrl)
|
||||||
|
etApiKey.setText(config.apiKey)
|
||||||
|
etModel.setText(config.model)
|
||||||
|
etModelName.setText(config.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateApiKeyVisibility() {
|
private fun updateApiKeyVisibility() {
|
||||||
val isEmpty = etApiKey.text.toString().isEmpty()
|
val isEmpty = etApiKey.text.toString().isEmpty()
|
||||||
etApiKey.transformationMethod = if (isEmpty) null else PasswordTransformationMethod()
|
etApiKey.transformationMethod = if (isEmpty) null else PasswordTransformationMethod()
|
||||||
// Keep cursor at end
|
|
||||||
etApiKey.setSelection(etApiKey.text.length)
|
etApiKey.setSelection(etApiKey.text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun refreshModelSelector() {
|
||||||
|
val names = llmConfigs.map { it.name.ifEmpty { "未命名" } }.toMutableList()
|
||||||
|
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, names)
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
spModelSelector.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupUI() {
|
private fun setupUI() {
|
||||||
|
refreshModelSelector()
|
||||||
|
|
||||||
|
spModelSelector.setOnItemSelectedListener(object : android.widget.AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: android.widget.AdapterView<*>, view: android.view.View?, position: Int, id: Long) {
|
||||||
|
selectedLlmIndex = position
|
||||||
|
loadSelectedModelToFields()
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: android.widget.AdapterView<*>) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
btnAddModel.setOnClickListener {
|
||||||
|
val newName = "新配置 ${llmConfigs.size + 1}"
|
||||||
|
val newConfig = LLMConfig(
|
||||||
|
name = newName,
|
||||||
|
baseUrl = etBaseUrl.text.toString().ifEmpty { "https://api.openai.com/v1" },
|
||||||
|
apiKey = "",
|
||||||
|
model = "gpt-4o"
|
||||||
|
)
|
||||||
|
llmConfigs.add(newConfig)
|
||||||
|
selectedLlmIndex = llmConfigs.size - 1
|
||||||
|
loadSelectedModelToFields()
|
||||||
|
refreshModelSelector()
|
||||||
|
spModelSelector.setSelection(selectedLlmIndex)
|
||||||
|
}
|
||||||
|
|
||||||
// Setup headers
|
// Setup headers
|
||||||
llHeadersList.removeAllViews()
|
llHeadersList.removeAllViews()
|
||||||
if (headerConfigs.isEmpty()) {
|
if (headerConfigs.isEmpty()) {
|
||||||
addHeaderEntry() // Add one empty entry by default
|
addHeaderEntry()
|
||||||
} else {
|
} else {
|
||||||
for (header in headerConfigs) {
|
for (header in headerConfigs) {
|
||||||
addHeaderEntry(header.key, header.value)
|
addHeaderEntry(header.key, header.value)
|
||||||
@@ -340,7 +395,7 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
// Setup prompts
|
// Setup prompts
|
||||||
llPromptList.removeAllViews()
|
llPromptList.removeAllViews()
|
||||||
if (promptConfigs.isEmpty()) {
|
if (promptConfigs.isEmpty()) {
|
||||||
addPromptEntry() // Add one empty entry by default
|
addPromptEntry()
|
||||||
} else {
|
} else {
|
||||||
for (prompt in promptConfigs) {
|
for (prompt in promptConfigs) {
|
||||||
addPromptEntry(prompt.title, prompt.content)
|
addPromptEntry(prompt.title, prompt.content)
|
||||||
@@ -358,10 +413,8 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTheme() {
|
private fun setupTheme() {
|
||||||
// Get saved theme mode
|
|
||||||
val themeMode = ThemeManager.getThemeMode(this)
|
val themeMode = ThemeManager.getThemeMode(this)
|
||||||
|
|
||||||
// Set the correct radio button
|
|
||||||
when (themeMode) {
|
when (themeMode) {
|
||||||
ThemeManager.THEME_FOLLOW_SYSTEM -> rbThemeFollowSystem.isChecked = true
|
ThemeManager.THEME_FOLLOW_SYSTEM -> rbThemeFollowSystem.isChecked = true
|
||||||
ThemeManager.THEME_LIGHT -> rbThemeLight.isChecked = true
|
ThemeManager.THEME_LIGHT -> rbThemeLight.isChecked = true
|
||||||
@@ -369,7 +422,6 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
else -> rbThemeFollowSystem.isChecked = true
|
else -> rbThemeFollowSystem.isChecked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up radio group listener
|
|
||||||
rgThemeMode.setOnCheckedChangeListener { _, checkedId ->
|
rgThemeMode.setOnCheckedChangeListener { _, checkedId ->
|
||||||
val newMode = when (checkedId) {
|
val newMode = when (checkedId) {
|
||||||
R.id.rbThemeFollowSystem -> ThemeManager.THEME_FOLLOW_SYSTEM
|
R.id.rbThemeFollowSystem -> ThemeManager.THEME_FOLLOW_SYSTEM
|
||||||
@@ -378,11 +430,8 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
else -> ThemeManager.THEME_FOLLOW_SYSTEM
|
else -> ThemeManager.THEME_FOLLOW_SYSTEM
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save and apply the new theme
|
|
||||||
ThemeManager.setThemeMode(this, newMode)
|
ThemeManager.setThemeMode(this, newMode)
|
||||||
Log.d("SecondActivity", "Theme mode changed to: ${ThemeManager.getThemeModeName(newMode)}")
|
Log.d("SecondActivity", "Theme mode changed to: ${ThemeManager.getThemeModeName(newMode)}")
|
||||||
|
|
||||||
// Recreate activity to apply theme changes
|
|
||||||
recreate()
|
recreate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,10 +467,15 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val client = OkHttpClient()
|
val client = OkHttpClient()
|
||||||
val request = Request.Builder()
|
val requestBuilder = Request.Builder()
|
||||||
.url("$baseUrl/chat/completions")
|
.url("$baseUrl/chat/completions")
|
||||||
.addHeader("Content-Type", "application/json")
|
.addHeader("Content-Type", "application/json")
|
||||||
.addHeader("Authorization", "Bearer $apiKey")
|
|
||||||
|
if (apiKey.isNotBlank()) {
|
||||||
|
requestBuilder.addHeader("Authorization", "Bearer $apiKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = requestBuilder
|
||||||
.post(requestBody.toString().toRequestBody("application/json".toMediaType()))
|
.post(requestBody.toString().toRequestBody("application/json".toMediaType()))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -496,7 +550,24 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun saveConfigurations() {
|
private fun saveConfigurations() {
|
||||||
// Update header configs from UI
|
if (llmConfigs.isNotEmpty() && selectedLlmIndex < llmConfigs.size) {
|
||||||
|
val currentConfig = llmConfigs[selectedLlmIndex]
|
||||||
|
llmConfigs[selectedLlmIndex] = currentConfig.copy(
|
||||||
|
name = etModelName.text.toString().ifEmpty { "未命名" },
|
||||||
|
baseUrl = etBaseUrl.text.toString(),
|
||||||
|
apiKey = etApiKey.text.toString(),
|
||||||
|
model = etModel.text.toString()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
llmConfigs.add(LLMConfig(
|
||||||
|
name = etModelName.text.toString().ifEmpty { "默认配置" },
|
||||||
|
baseUrl = etBaseUrl.text.toString(),
|
||||||
|
apiKey = etApiKey.text.toString(),
|
||||||
|
model = etModel.text.toString()
|
||||||
|
))
|
||||||
|
selectedLlmIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
headerConfigs.clear()
|
headerConfigs.clear()
|
||||||
for (i in 0 until llHeadersList.childCount) {
|
for (i in 0 until llHeadersList.childCount) {
|
||||||
val view = llHeadersList.getChildAt(i)
|
val view = llHeadersList.getChildAt(i)
|
||||||
@@ -507,7 +578,6 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update prompt configs from UI
|
|
||||||
promptConfigs.clear()
|
promptConfigs.clear()
|
||||||
for (i in 0 until llPromptList.childCount) {
|
for (i in 0 until llPromptList.childCount) {
|
||||||
val view = llPromptList.getChildAt(i)
|
val view = llPromptList.getChildAt(i)
|
||||||
@@ -518,27 +588,17 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Buttons are removed from this UI as per new design.
|
|
||||||
// We keep the list empty for data compatibility.
|
|
||||||
buttonConfigs.clear()
|
buttonConfigs.clear()
|
||||||
|
|
||||||
// Save LLM config
|
|
||||||
val llmConfig = LLMConfig(
|
|
||||||
baseUrl = etBaseUrl.text.toString(),
|
|
||||||
apiKey = etApiKey.text.toString(),
|
|
||||||
model = etModel.text.toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Save Note API config
|
|
||||||
val noteApiConfig = NoteApiConfig(
|
val noteApiConfig = NoteApiConfig(
|
||||||
apiType = spNoteApiType.selectedItem.toString(),
|
apiType = spNoteApiType.selectedItem.toString(),
|
||||||
apiUrl = etNoteApiUrl.text.toString(),
|
apiUrl = etNoteApiUrl.text.toString(),
|
||||||
apiKey = etNoteApiKey.text.toString()
|
apiKey = etNoteApiKey.text.toString()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Save everything
|
|
||||||
val settingsData = SettingsData(
|
val settingsData = SettingsData(
|
||||||
llmConfig = llmConfig,
|
llmConfigs = llmConfigs,
|
||||||
|
selectedLlmIndex = selectedLlmIndex,
|
||||||
headerConfigs = headerConfigs,
|
headerConfigs = headerConfigs,
|
||||||
promptConfigs = promptConfigs,
|
promptConfigs = promptConfigs,
|
||||||
buttonConfigs = buttonConfigs,
|
buttonConfigs = buttonConfigs,
|
||||||
@@ -549,7 +609,6 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
|
||||||
sharedPrefs.edit().putString("configs", json).apply()
|
sharedPrefs.edit().putString("configs", json).apply()
|
||||||
|
|
||||||
// Also update the legacy APIConfig for backward compatibility
|
|
||||||
apiConfig = APIConfig(
|
apiConfig = APIConfig(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
"llm-config",
|
"llm-config",
|
||||||
@@ -572,6 +631,7 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// New data classes for settings structure
|
// New data classes for settings structure
|
||||||
data class LLMConfig(
|
data class LLMConfig(
|
||||||
|
val name: String,
|
||||||
val baseUrl: String,
|
val baseUrl: String,
|
||||||
val apiKey: String,
|
val apiKey: String,
|
||||||
val model: String
|
val model: String
|
||||||
@@ -584,10 +644,12 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class SettingsData(
|
data class SettingsData(
|
||||||
val llmConfig: LLMConfig?,
|
val llmConfigs: List<LLMConfig>?,
|
||||||
|
val selectedLlmIndex: Int?,
|
||||||
val headerConfigs: List<HeaderConfig>?,
|
val headerConfigs: List<HeaderConfig>?,
|
||||||
val promptConfigs: List<PromptConfig>?,
|
val promptConfigs: List<PromptConfig>?,
|
||||||
val buttonConfigs: List<ButtonConfig>?,
|
val buttonConfigs: List<ButtonConfig>?,
|
||||||
val noteApiConfig: NoteApiConfig?
|
val noteApiConfig: NoteApiConfig?,
|
||||||
|
val llmConfig: LLMConfig? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -32,27 +32,12 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/text_primary"/>
|
android:textColor="@color/text_primary"/>
|
||||||
|
|
||||||
<LinearLayout
|
<Spinner
|
||||||
|
android:id="@+id/headerModelSelector"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_marginTop="2dp"
|
||||||
android:gravity="center_vertical"
|
android:spinnerMode="dropdown"/>
|
||||||
android:layout_marginTop="2dp">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="6dp"
|
|
||||||
android:layout_height="6dp"
|
|
||||||
android:background="@drawable/indicator_dot"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/headerModelName"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="GPT-4o"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="@color/primary"
|
|
||||||
android:layout_marginStart="5dp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -126,6 +126,64 @@
|
|||||||
android:textColor="@color/text_hint"
|
android:textColor="@color/text_hint"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<!-- Base URL -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="配置名称"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etModelName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edittext_border"
|
||||||
|
android:hint="默认配置"
|
||||||
|
android:inputType="text"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textColorHint="@color/text_hint"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 模型选择器 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spModelSelector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/edittext_border"
|
||||||
|
android:spinnerMode="dropdown"
|
||||||
|
android:padding="12dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnAddModel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="+ 添加"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:background="@drawable/button_primary_bg"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:minHeight="0dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Base URL -->
|
<!-- Base URL -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
Reference in New Issue
Block a user