Compare commits
3 Commits
262920b8b3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac1baa9b0f | ||
|
|
a753a3369c | ||
|
|
bfd34c8919 |
@@ -22,3 +22,6 @@ lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
rustflags = ["-C", "link-args=-Wl,--subsystem,windows"]
|
||||
|
||||
@@ -31,14 +31,15 @@ pub struct FlomoAiApp {
|
||||
input_text: String,
|
||||
selected_prompt_index: usize,
|
||||
char_count: usize,
|
||||
|
||||
|
||||
model_displays: Vec<ModelDisplay>,
|
||||
pending_results: Option<std::thread::JoinHandle<Vec<ModelResult>>>,
|
||||
|
||||
pending_tests: Vec<(usize, std::thread::JoinHandle<Result<String, String>>)>,
|
||||
|
||||
settings_selected_theme: ThemeMode,
|
||||
new_prompt_title: String,
|
||||
new_prompt_content: String,
|
||||
|
||||
|
||||
current_page: Page,
|
||||
theme_dirty: bool,
|
||||
}
|
||||
@@ -65,6 +66,7 @@ impl FlomoAiApp {
|
||||
char_count: 0,
|
||||
model_displays,
|
||||
pending_results: None,
|
||||
pending_tests: Vec::new(),
|
||||
settings_selected_theme: ThemeMode::Light,
|
||||
new_prompt_title: String::new(),
|
||||
new_prompt_content: String::new(),
|
||||
@@ -73,6 +75,28 @@ impl FlomoAiApp {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_test_results(&mut self, ctx: &egui::Context) {
|
||||
for (index, handle) in self.pending_tests.drain(..) {
|
||||
match handle.join() {
|
||||
Ok(Ok(response)) => {
|
||||
let msg = if response.len() > 30 {
|
||||
format!("成功: {}...", &response[..30])
|
||||
} else {
|
||||
format!("成功: {}", response)
|
||||
};
|
||||
self.model_displays[index].test_status = msg;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
self.model_displays[index].test_status = format!("失败: {}", e);
|
||||
}
|
||||
Err(_) => {
|
||||
self.model_displays[index].test_status = "测试线程错误".to_string();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_results(&mut self, ctx: &egui::Context) {
|
||||
if let Some(handle) = self.pending_results.take() {
|
||||
if handle.is_finished() {
|
||||
@@ -184,6 +208,7 @@ impl FlomoAiApp {
|
||||
impl eframe::App for FlomoAiApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.poll_results(ctx);
|
||||
self.poll_test_results(ctx);
|
||||
|
||||
if self.theme_dirty {
|
||||
let theme = AppTheme::from_mode(self.settings.theme_config.mode);
|
||||
@@ -287,79 +312,77 @@ impl FlomoAiApp {
|
||||
ui.label(egui::RichText::new("各模型返回结果").size(11.0).color(egui::Color32::GRAY));
|
||||
ui.add_space(6.0);
|
||||
|
||||
let available_width = ui.available_width();
|
||||
let column_width = (available_width - 16.0) / 3.0;
|
||||
let total_width = ui.available_width();
|
||||
let spacing = 8.0;
|
||||
let column_width = (total_width - spacing * 2.0) / 3.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_height(200.0);
|
||||
for (i, display) in self.model_displays.iter().enumerate() {
|
||||
if i > 0 {
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
ui.set_min_width(column_width);
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.set_width(total_width);
|
||||
|
||||
for d in self.model_displays.iter() {
|
||||
egui::Frame::none()
|
||||
.fill(ui.style().visuals.widgets.inactive.bg_fill)
|
||||
.fill(if d.enabled {
|
||||
ui.style().visuals.widgets.inactive.bg_fill
|
||||
} else {
|
||||
egui::Color32::from_rgb(235, 235, 240)
|
||||
})
|
||||
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(180, 180, 200)))
|
||||
.rounding(8.0)
|
||||
.inner_margin(egui::Margin::same(10.0))
|
||||
.inner_margin(egui::Margin::same(8.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_min_width(column_width - 20.0);
|
||||
ui.set_min_width(column_width - 16.0);
|
||||
|
||||
let enabled_color = if display.enabled {
|
||||
let enabled_color = if d.enabled {
|
||||
egui::Color32::from_rgb(80, 80, 220)
|
||||
} else {
|
||||
egui::Color32::GRAY
|
||||
};
|
||||
|
||||
let enabled_dot = if display.enabled { "●" } else { "○" };
|
||||
ui.label(egui::RichText::new(format!("{} {}", enabled_dot, display.name))
|
||||
|
||||
let enabled_dot = if d.enabled { "●" } else { "○" };
|
||||
ui.label(egui::RichText::new(format!("{} {}", enabled_dot, d.name))
|
||||
.size(13.0).strong().color(enabled_color));
|
||||
|
||||
ui.label(egui::RichText::new(&display.model).size(10.0).color(egui::Color32::GRAY));
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.label(egui::RichText::new(&d.model).size(10.0).color(egui::Color32::GRAY));
|
||||
|
||||
ui.add_space(4.0);
|
||||
ui.add_sized([ui.available_width(), 1.0], egui::Separator::default());
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
let status_color = match &display.status {
|
||||
|
||||
let status_color = match &d.status {
|
||||
ModelStatus::Waiting => egui::Color32::GRAY,
|
||||
ModelStatus::Loading => egui::Color32::from_rgb(255, 140, 0),
|
||||
ModelStatus::Completed => egui::Color32::from_rgb(0, 160, 0),
|
||||
ModelStatus::Error(_) => egui::Color32::RED,
|
||||
};
|
||||
|
||||
ui.label(egui::RichText::new(match &display.status {
|
||||
|
||||
ui.label(egui::RichText::new(match &d.status {
|
||||
ModelStatus::Waiting => "● 就绪",
|
||||
ModelStatus::Loading => "◐ 生成中...",
|
||||
ModelStatus::Completed => "✓ 完成",
|
||||
ModelStatus::Error(_) => "✗ 错误",
|
||||
}).size(10.0).color(status_color));
|
||||
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
if display.result.is_empty() {
|
||||
|
||||
if d.result.is_empty() {
|
||||
ui.label(egui::RichText::new("等待结果...").size(11.0).color(egui::Color32::GRAY));
|
||||
} else {
|
||||
let mut result_text = display.result.clone();
|
||||
let mut result_text = d.result.clone();
|
||||
ui.add_sized(
|
||||
[ui.available_width(), 120.0],
|
||||
[ui.available_width(), 100.0],
|
||||
egui::TextEdit::multiline(&mut result_text)
|
||||
.desired_rows(5)
|
||||
.desired_rows(4)
|
||||
.frame(false),
|
||||
);
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button("复制").clicked() && !display.result.is_empty() {
|
||||
self.copy_to_clipboard(&display.result);
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
if ui.small_button("复制").clicked() && !d.result.is_empty() {
|
||||
self.copy_to_clipboard(&d.result);
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(spacing);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -380,75 +403,77 @@ impl FlomoAiApp {
|
||||
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
for i in 0..self.model_displays.len() {
|
||||
let enabled = self.model_displays[i].enabled;
|
||||
let name = self.model_displays[i].name.clone();
|
||||
let model = self.model_displays[i].model.clone();
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
self.model_displays[i].enabled ^= ui.checkbox(&mut self.model_displays[i].enabled, "").changed();
|
||||
ui.label(egui::RichText::new(&format!("模型 {} 配置", i + 1)).size(14.0).strong());
|
||||
ui.checkbox(&mut self.model_displays[i].enabled, "");
|
||||
let label = if self.model_displays[i].enabled {
|
||||
format!("{} ({})", self.model_displays[i].name, self.model_displays[i].model)
|
||||
} else {
|
||||
format!("{} (已禁用)", self.model_displays[i].name)
|
||||
};
|
||||
ui.label(egui::RichText::new(&label).size(14.0).strong());
|
||||
});
|
||||
|
||||
if !enabled {
|
||||
ui.label(egui::RichText::new("已禁用").size(11.0).color(egui::Color32::GRAY));
|
||||
} else {
|
||||
ui.label("名称");
|
||||
ui.text_edit_singleline(&mut self.model_displays[i].name);
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
let mut base_url = self.settings.llm_configs.models.get(i)
|
||||
.map(|m| m.base_url.clone())
|
||||
.unwrap_or_default();
|
||||
ui.label("API Base URL");
|
||||
ui.text_edit_singleline(&mut base_url);
|
||||
if i < self.settings.llm_configs.models.len() {
|
||||
self.settings.llm_configs.models[i].base_url = base_url.clone();
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
let mut api_key = self.settings.llm_configs.models.get(i)
|
||||
.map(|m| m.api_key.clone())
|
||||
.unwrap_or_default();
|
||||
ui.label("API Key");
|
||||
ui.text_edit_singleline(&mut api_key);
|
||||
if i < self.settings.llm_configs.models.len() {
|
||||
self.settings.llm_configs.models[i].api_key = api_key.clone();
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.label("Model");
|
||||
ui.text_edit_singleline(&mut self.model_displays[i].model);
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
let test_status = self.model_displays[i].test_status.clone();
|
||||
let test_btn = egui::Button::new("测试")
|
||||
.fill(egui::Color32::from_rgb(100, 100, 255))
|
||||
.rounding(4.0)
|
||||
.min_size(egui::vec2(60.0, 28.0));
|
||||
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.label("名称");
|
||||
ui.text_edit_singleline(&mut self.model_displays[i].name);
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
let mut base_url = self.settings.llm_configs.models.get(i)
|
||||
.map(|m| m.base_url.clone())
|
||||
.unwrap_or_default();
|
||||
ui.label("API Base URL");
|
||||
ui.text_edit_singleline(&mut base_url);
|
||||
if i < self.settings.llm_configs.models.len() {
|
||||
self.settings.llm_configs.models[i].base_url = base_url.clone();
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
let mut api_key = self.settings.llm_configs.models.get(i)
|
||||
.map(|m| m.api_key.clone())
|
||||
.unwrap_or_default();
|
||||
ui.label("API Key");
|
||||
ui.text_edit_singleline(&mut api_key);
|
||||
if i < self.settings.llm_configs.models.len() {
|
||||
self.settings.llm_configs.models[i].api_key = api_key.clone();
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.label("Model");
|
||||
ui.text_edit_singleline(&mut self.model_displays[i].model);
|
||||
if i < self.settings.llm_configs.models.len() {
|
||||
self.settings.llm_configs.models[i].model = self.model_displays[i].model.clone();
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
let test_status = self.model_displays[i].test_status.clone();
|
||||
let test_btn = egui::Button::new("测试")
|
||||
.fill(egui::Color32::from_rgb(100, 100, 255))
|
||||
.rounding(4.0)
|
||||
.min_size(egui::vec2(60.0, 28.0));
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add(test_btn).clicked() {
|
||||
if let Some(config) = self.settings.llm_configs.models.get(i).cloned() {
|
||||
self.model_displays[i].test_status = "测试中...".to_string();
|
||||
let headers = self.settings.header_configs.clone();
|
||||
let ctx_clone = ctx.clone();
|
||||
let display_index = i;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let result = test_single_llm(&config, &headers);
|
||||
ctx_clone.request_repaint();
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
test_single_llm(&config, &headers)
|
||||
});
|
||||
self.pending_tests.push((i, handle));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ui.label(egui::RichText::new(&test_status).size(11.0).color(egui::Color32::GRAY));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if i < self.model_displays.len() - 1 {
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
@@ -31,13 +32,19 @@ import org.json.JSONObject
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var inputEditText: EditText
|
||||
private lateinit var outputTextView: EditText
|
||||
private lateinit var tvResult1: TextView
|
||||
private lateinit var tvResult2: TextView
|
||||
private lateinit var tvResult3: TextView
|
||||
private lateinit var tvStatus1: TextView
|
||||
private lateinit var tvStatus2: TextView
|
||||
private lateinit var tvStatus3: TextView
|
||||
private lateinit var promptSelector: Spinner
|
||||
private lateinit var promptNameText: TextView
|
||||
private lateinit var promptContentText: TextView
|
||||
|
||||
|
||||
private var llmConfigs = listOf<LLMConfig>()
|
||||
private var selectedLlmIndex = 0
|
||||
private val results = mutableMapOf<Int, String>()
|
||||
|
||||
@SuppressLint("MissingInflatedId", "CutPasteId", "SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -51,6 +58,30 @@ class MainActivity : AppCompatActivity() {
|
||||
inputEditText = findViewById<EditText>(R.id.inputEditText)
|
||||
val sendButton = findViewById<Button>(R.id.sendButton)
|
||||
val btnCopyResult = findViewById<Button>(R.id.btnCopyResult)
|
||||
val configButton = findViewById<Button>(R.id.configButton)
|
||||
val modelSelector = findViewById<Spinner>(R.id.headerModelSelector)
|
||||
|
||||
// 初始化三栏结果视图
|
||||
tvResult1 = findViewById(R.id.tvResult1)
|
||||
tvResult2 = findViewById(R.id.tvResult2)
|
||||
tvResult3 = findViewById(R.id.tvResult3)
|
||||
tvStatus1 = findViewById(R.id.tvStatus1)
|
||||
tvStatus2 = findViewById(R.id.tvStatus2)
|
||||
tvStatus3 = findViewById(R.id.tvStatus3)
|
||||
|
||||
// 配置按钮点击事件
|
||||
configButton.setOnClickListener {
|
||||
val intent = Intent(this, SecondActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
// 模型选择器
|
||||
modelSelector.setOnItemSelectedListener(object : android.widget.AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: android.widget.AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||
selectedLlmIndex = position
|
||||
}
|
||||
override fun onNothingSelected(parent: android.widget.AdapterView<*>) {}
|
||||
})
|
||||
|
||||
loadModelsFromConfig()
|
||||
loadPromptsFromConfig()
|
||||
@@ -104,8 +135,13 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
btnCopyResult.setOnClickListener {
|
||||
val textToCopy = outputTextView.text.toString()
|
||||
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
|
||||
val sb = StringBuilder()
|
||||
if (results.containsKey(0)) sb.append("模型1:\n${results[0]}\n\n")
|
||||
if (results.containsKey(1)) sb.append("模型2:\n${results[1]}\n\n")
|
||||
if (results.containsKey(2)) sb.append("模型3:\n${results[2]}\n\n")
|
||||
|
||||
val textToCopy = sb.toString().trim()
|
||||
if (textToCopy.isNotEmpty()) {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("优化结果", textToCopy)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
@@ -114,14 +150,19 @@ class MainActivity : AppCompatActivity() {
|
||||
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
initQuickButtons()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
loadModelsFromConfig()
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -131,34 +172,74 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.e("MainActivity", "Error loading config", e)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新模型选择器
|
||||
val modelSelector = findViewById<Spinner>(R.id.headerModelSelector)
|
||||
val modelNames = llmConfigs.filter { it.enabled }.map { it.name }.toMutableList()
|
||||
if (modelNames.isEmpty()) {
|
||||
modelNames.add("未配置模型")
|
||||
}
|
||||
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, modelNames)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
modelSelector.adapter = adapter
|
||||
if (selectedLlmIndex < modelNames.size) {
|
||||
modelSelector.setSelection(selectedLlmIndex)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 找出所有启用的模型
|
||||
val enabledConfigs = configs.filter { it.enabled && it.baseUrl.isNotEmpty() && it.model.isNotEmpty() }
|
||||
|
||||
if (enabledConfigs.isEmpty()) {
|
||||
Toast.makeText(this, "没有启用的大模型配置", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化所有结果显示
|
||||
results.clear()
|
||||
tvStatus1.text = "等待..."
|
||||
tvStatus2.text = "等待..."
|
||||
tvStatus3.text = "等待..."
|
||||
tvResult1.text = "等待结果..."
|
||||
tvResult2.text = "等待结果..."
|
||||
tvResult3.text = "等待结果..."
|
||||
|
||||
// 为每个启用的模型启动请求
|
||||
enabledConfigs.forEachIndexed { index, config ->
|
||||
sendToLlm(config.baseUrl, config.apiKey, config.model, content, index, enabledConfigs.size)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("MainActivity", "Error loading LLM config", e)
|
||||
outputTextView.setText("错误: ${e.message}")
|
||||
Toast.makeText(this, "配置加载错误: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendToLlm(baseUrl: String, apiKey: String, model: String, content: String) {
|
||||
val outputStatusLabel = findViewById<TextView>(R.id.outputStatusLabel)
|
||||
outputStatusLabel.text = "连接中..."
|
||||
outputTextView.setText("正在生成...")
|
||||
private fun sendToLlm(baseUrl: String, apiKey: String, model: String, content: String, index: Int, total: Int) {
|
||||
val statusView = when (index) {
|
||||
0 -> tvStatus1
|
||||
1 -> tvStatus2
|
||||
2 -> tvStatus3
|
||||
else -> tvStatus1
|
||||
}
|
||||
val resultView = when (index) {
|
||||
0 -> tvResult1
|
||||
1 -> tvResult2
|
||||
2 -> tvResult3
|
||||
else -> tvResult1
|
||||
}
|
||||
|
||||
statusView.text = "生成中..."
|
||||
resultView.text = "正在生成..."
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
@@ -168,28 +249,28 @@ class MainActivity : AppCompatActivity() {
|
||||
put("content", content)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
val requestBody = JSONObject().apply {
|
||||
put("model", model)
|
||||
put("messages", messagesJson)
|
||||
put("max_tokens", 65536)
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
@@ -198,21 +279,22 @@ class MainActivity : AppCompatActivity() {
|
||||
if (choices.length() > 0) {
|
||||
val message = choices.getJSONObject(0).getJSONObject("message")
|
||||
val result = message.getString("content")
|
||||
outputStatusLabel.text = "已完成"
|
||||
outputTextView.setText(result)
|
||||
statusView.text = "完成"
|
||||
resultView.text = result
|
||||
results[index] = result
|
||||
} else {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("API 返回空结果")
|
||||
statusView.text = "错误"
|
||||
resultView.text = "API 返回空结果"
|
||||
}
|
||||
} else {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("API 错误: ${response.code} ${response.message}")
|
||||
statusView.text = "错误"
|
||||
resultView.text = "API 错误: ${response.code} ${response.message}"
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
outputStatusLabel.text = "发生错误"
|
||||
outputTextView.setText("错误: ${e.message}")
|
||||
statusView.text = "错误"
|
||||
resultView.text = "错误: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,22 @@ package com.example.flomo_ai
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.text.method.PasswordTransformationMethod
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.flomo_ai.ui.theme.ThemeManager
|
||||
@@ -24,31 +33,42 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
class SecondActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var cbModel1Enabled: CheckBox
|
||||
private lateinit var etModelName1: EditText
|
||||
private lateinit var etBaseUrl1: EditText
|
||||
private lateinit var etApiKey1: EditText
|
||||
private lateinit var btnToggleApiKey1: ImageButton
|
||||
private lateinit var etModel1: EditText
|
||||
private lateinit var etModelName1: EditText
|
||||
private lateinit var btnTestConnection1: Button
|
||||
private lateinit var tvTestStatus1: TextView
|
||||
|
||||
private lateinit var cbModel2Enabled: CheckBox
|
||||
private lateinit var etModelName2: EditText
|
||||
private lateinit var etBaseUrl2: EditText
|
||||
private lateinit var etApiKey2: EditText
|
||||
private lateinit var btnToggleApiKey2: ImageButton
|
||||
private lateinit var etModel2: EditText
|
||||
private lateinit var etModelName2: EditText
|
||||
private lateinit var btnTestConnection2: Button
|
||||
private lateinit var tvTestStatus2: TextView
|
||||
|
||||
private lateinit var cbModel3Enabled: CheckBox
|
||||
private lateinit var etModelName3: EditText
|
||||
private lateinit var etBaseUrl3: EditText
|
||||
private lateinit var etApiKey3: EditText
|
||||
private lateinit var btnToggleApiKey3: ImageButton
|
||||
private lateinit var etModel3: EditText
|
||||
private lateinit var etModelName3: EditText
|
||||
private lateinit var btnTestConnection3: Button
|
||||
private lateinit var tvTestStatus3: TextView
|
||||
|
||||
private lateinit var promptContainer: LinearLayout
|
||||
private lateinit var btnAddPrompt: Button
|
||||
private lateinit var rgTheme: RadioGroup
|
||||
private lateinit var rbThemeLight: RadioButton
|
||||
private lateinit var rbThemeDark: RadioButton
|
||||
private lateinit var rbThemeAuto: RadioButton
|
||||
|
||||
private var llmConfigs = mutableListOf<LLMConfig>()
|
||||
private var promptConfigs = mutableListOf<PromptConfig>()
|
||||
private var selectedLlmIndex = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -58,37 +78,67 @@ class SecondActivity : AppCompatActivity() {
|
||||
|
||||
initViews()
|
||||
loadConfigurations()
|
||||
|
||||
updatePromptList()
|
||||
|
||||
findViewById<ImageButton>(R.id.btnBack).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initViews() {
|
||||
cbModel1Enabled = findViewById(R.id.cbModel1Enabled)
|
||||
etModelName1 = findViewById(R.id.etModelName1)
|
||||
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)
|
||||
|
||||
cbModel2Enabled = findViewById(R.id.cbModel2Enabled)
|
||||
etModelName2 = findViewById(R.id.etModelName2)
|
||||
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)
|
||||
|
||||
cbModel3Enabled = findViewById(R.id.cbModel3Enabled)
|
||||
etModelName3 = findViewById(R.id.etModelName3)
|
||||
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)
|
||||
|
||||
promptContainer = findViewById(R.id.promptContainer)
|
||||
btnAddPrompt = findViewById(R.id.btnAddPrompt)
|
||||
rgTheme = findViewById(R.id.rgTheme)
|
||||
rbThemeLight = findViewById(R.id.rbThemeLight)
|
||||
rbThemeDark = findViewById(R.id.rbThemeDark)
|
||||
rbThemeAuto = findViewById(R.id.rbThemeAuto)
|
||||
|
||||
// 设置当前主题
|
||||
when (ThemeManager.getThemeMode(this)) {
|
||||
ThemeManager.THEME_LIGHT -> rbThemeLight.isChecked = true
|
||||
ThemeManager.THEME_DARK -> rbThemeDark.isChecked = true
|
||||
else -> rbThemeAuto.isChecked = true
|
||||
}
|
||||
|
||||
// 主题切换监听
|
||||
rgTheme.setOnCheckedChangeListener { _, checkedId ->
|
||||
val mode = when (checkedId) {
|
||||
R.id.rbThemeLight -> ThemeManager.THEME_LIGHT
|
||||
R.id.rbThemeDark -> ThemeManager.THEME_DARK
|
||||
else -> ThemeManager.THEME_FOLLOW_SYSTEM
|
||||
}
|
||||
ThemeManager.setThemeMode(this, mode)
|
||||
}
|
||||
|
||||
btnAddPrompt.setOnClickListener { showAddPromptDialog() }
|
||||
|
||||
btnToggleApiKey1.setOnClickListener { toggleApiKeyVisibility(etApiKey1, btnToggleApiKey1) }
|
||||
btnToggleApiKey2.setOnClickListener { toggleApiKeyVisibility(etApiKey2, btnToggleApiKey2) }
|
||||
btnToggleApiKey3.setOnClickListener { toggleApiKeyVisibility(etApiKey3, btnToggleApiKey3) }
|
||||
@@ -101,36 +151,45 @@ class SecondActivity : AppCompatActivity() {
|
||||
private fun loadConfigurations() {
|
||||
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?.toMutableList() ?: mutableListOf()
|
||||
promptConfigs = settings.promptConfigs?.toMutableList() ?: mutableListOf()
|
||||
selectedLlmIndex = settings.selectedLlmIndex ?: 0
|
||||
|
||||
|
||||
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
|
||||
))
|
||||
}
|
||||
|
||||
while (llmConfigs.size < 3) {
|
||||
llmConfigs.add(LLMConfig(
|
||||
name = "配置 ${llmConfigs.size + 1}",
|
||||
name = "模型1",
|
||||
baseUrl = "https://api.openai.com/v1",
|
||||
apiKey = "",
|
||||
model = "gpt-4o"
|
||||
model = "gpt-4o",
|
||||
enabled = true
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
while (llmConfigs.size < 3) {
|
||||
llmConfigs.add(LLMConfig(
|
||||
name = "模型${llmConfigs.size + 1}",
|
||||
baseUrl = "https://api.openai.com/v1",
|
||||
apiKey = "",
|
||||
model = "gpt-4o",
|
||||
enabled = false
|
||||
))
|
||||
}
|
||||
|
||||
// 加载默认提示词
|
||||
if (promptConfigs.isEmpty()) {
|
||||
promptConfigs.add(PromptConfig("default-1", "翻译助手", "将输入的文本翻译成指定语言"))
|
||||
promptConfigs.add(PromptConfig("default-2", "代码解释", "解释代码的功能和逻辑"))
|
||||
promptConfigs.add(PromptConfig("quick-1", "检查错别字", "请检查以下文本中的错别字并纠正:"))
|
||||
promptConfigs.add(PromptConfig("quick-2", "总结", "请用简洁的语言总结以下文本的主要内容:"))
|
||||
promptConfigs.add(PromptConfig("quick-3", "翻译", "请翻译以下文本:"))
|
||||
promptConfigs.add(PromptConfig("quick-4", "润色", "请润色以下文本,使其更通顺流畅:"))
|
||||
}
|
||||
|
||||
loadConfigsToViews()
|
||||
updateApiKeyVisibilityForAll()
|
||||
} catch (e: Exception) {
|
||||
setDefaultConfigs()
|
||||
}
|
||||
@@ -139,65 +198,104 @@ class SecondActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePromptList() {
|
||||
promptContainer.removeAllViews()
|
||||
for (i in promptConfigs.indices) {
|
||||
val prompt = promptConfigs[i]
|
||||
val itemView = LayoutInflater.from(this).inflate(R.layout.item_prompt_config, promptContainer, false)
|
||||
|
||||
val tvTitle = itemView.findViewById<TextView>(R.id.tvPromptTitle)
|
||||
val tvContent = itemView.findViewById<TextView>(R.id.tvPromptContent)
|
||||
val btnDelete = itemView.findViewById<Button>(R.id.btnDeletePrompt)
|
||||
|
||||
tvTitle.text = prompt.title
|
||||
tvContent.text = prompt.content
|
||||
|
||||
val index = i
|
||||
btnDelete.setOnClickListener {
|
||||
promptConfigs.removeAt(index)
|
||||
updatePromptList()
|
||||
}
|
||||
|
||||
promptContainer.addView(itemView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAddPromptDialog() {
|
||||
val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_add_prompt, null)
|
||||
val etTitle = dialogView.findViewById<EditText>(R.id.etPromptTitle)
|
||||
val etContent = dialogView.findViewById<EditText>(R.id.etPromptContent)
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("添加提示词")
|
||||
.setView(dialogView)
|
||||
.setPositiveButton("添加") { _, _ ->
|
||||
val title = etTitle.text.toString().trim()
|
||||
val content = etContent.text.toString().trim()
|
||||
if (title.isNotEmpty() && content.isNotEmpty()) {
|
||||
promptConfigs.add(PromptConfig(
|
||||
id = "custom-${promptConfigs.size}",
|
||||
title = title,
|
||||
content = content
|
||||
))
|
||||
updatePromptList()
|
||||
}
|
||||
}
|
||||
.setNegativeButton("取消", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun loadConfigsToViews() {
|
||||
if (llmConfigs.size > 0) {
|
||||
val config1 = llmConfigs[0]
|
||||
cbModel1Enabled.isChecked = config1.enabled
|
||||
etModelName1.setText(config1.name)
|
||||
etBaseUrl1.setText(config1.baseUrl)
|
||||
etApiKey1.setText(config1.apiKey)
|
||||
etModel1.setText(config1.model)
|
||||
etModelName1.setText(config1.name)
|
||||
}
|
||||
|
||||
|
||||
if (llmConfigs.size > 1) {
|
||||
val config2 = llmConfigs[1]
|
||||
cbModel2Enabled.isChecked = config2.enabled
|
||||
etModelName2.setText(config2.name)
|
||||
etBaseUrl2.setText(config2.baseUrl)
|
||||
etApiKey2.setText(config2.apiKey)
|
||||
etModel2.setText(config2.model)
|
||||
etModelName2.setText(config2.name)
|
||||
}
|
||||
|
||||
|
||||
if (llmConfigs.size > 2) {
|
||||
val config3 = llmConfigs[2]
|
||||
cbModel3Enabled.isChecked = config3.enabled
|
||||
etModelName3.setText(config3.name)
|
||||
etBaseUrl3.setText(config3.baseUrl)
|
||||
etApiKey3.setText(config3.apiKey)
|
||||
etModel3.setText(config3.model)
|
||||
etModelName3.setText(config3.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDefaultConfigs() {
|
||||
cbModel1Enabled.isChecked = true
|
||||
etModelName1.setText("模型1")
|
||||
etBaseUrl1.setText("https://api.openai.com/v1")
|
||||
etModel1.setText("gpt-4o")
|
||||
etModelName1.setText("默认配置")
|
||||
|
||||
|
||||
cbModel2Enabled.isChecked = false
|
||||
etModelName2.setText("模型2")
|
||||
etBaseUrl2.setText("https://api.openai.com/v1")
|
||||
etModel2.setText("gpt-4o")
|
||||
etModelName2.setText("配置2")
|
||||
|
||||
|
||||
cbModel3Enabled.isChecked = false
|
||||
etModelName3.setText("模型3")
|
||||
etBaseUrl3.setText("https://api.openai.com/v1")
|
||||
etModel3.setText("gpt-4o")
|
||||
etModelName3.setText("配置3")
|
||||
|
||||
updateApiKeyVisibilityForAll()
|
||||
}
|
||||
|
||||
private fun updateApiKeyVisibilityForAll() {
|
||||
updateApiKeyVisibility(etApiKey1, btnToggleApiKey1)
|
||||
updateApiKeyVisibility(etApiKey2, btnToggleApiKey2)
|
||||
updateApiKeyVisibility(etApiKey3, btnToggleApiKey3)
|
||||
}
|
||||
|
||||
private fun updateApiKeyVisibility(editText: EditText, button: ImageButton) {
|
||||
val isEmpty = editText.text.toString().isEmpty()
|
||||
editText.transformationMethod = if (isEmpty) null else PasswordTransformationMethod()
|
||||
editText.setSelection(editText.text.length)
|
||||
}
|
||||
|
||||
private fun toggleApiKeyVisibility(editText: EditText, button: ImageButton) {
|
||||
val isPassword = editText.transformationMethod is PasswordTransformationMethod
|
||||
editText.transformationMethod = if (isPassword) null else PasswordTransformationMethod()
|
||||
editText.setSelection(editText.text.length)
|
||||
|
||||
|
||||
if (isPassword) {
|
||||
button.setImageResource(android.R.drawable.ic_menu_view)
|
||||
} else {
|
||||
@@ -211,11 +309,11 @@ class SecondActivity : AppCompatActivity() {
|
||||
statusView.setTextColor(ContextCompat.getColor(this, R.color.error))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
statusView.text = "测试中..."
|
||||
statusView.setTextColor(ContextCompat.getColor(this, R.color.warning))
|
||||
button.isEnabled = false
|
||||
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val messagesJson = org.json.JSONArray().apply {
|
||||
@@ -224,28 +322,28 @@ class SecondActivity : AppCompatActivity() {
|
||||
put("content", "你好,请回复OK")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
val requestBody = org.json.JSONObject().apply {
|
||||
put("model", model)
|
||||
put("messages", messagesJson)
|
||||
put("max_tokens", 10)
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
button.isEnabled = true
|
||||
if (response.isSuccessful) {
|
||||
@@ -314,29 +412,32 @@ class SecondActivity : AppCompatActivity() {
|
||||
private fun saveConfigurations() {
|
||||
llmConfigs.clear()
|
||||
llmConfigs.add(LLMConfig(
|
||||
name = etModelName1.text.toString().ifEmpty { "默认配置" },
|
||||
name = etModelName1.text.toString().ifEmpty { "模型1" },
|
||||
baseUrl = etBaseUrl1.text.toString(),
|
||||
apiKey = etApiKey1.text.toString(),
|
||||
model = etModel1.text.toString()
|
||||
model = etModel1.text.toString(),
|
||||
enabled = cbModel1Enabled.isChecked
|
||||
))
|
||||
llmConfigs.add(LLMConfig(
|
||||
name = etModelName2.text.toString().ifEmpty { "配置2" },
|
||||
name = etModelName2.text.toString().ifEmpty { "模型2" },
|
||||
baseUrl = etBaseUrl2.text.toString(),
|
||||
apiKey = etApiKey2.text.toString(),
|
||||
model = etModel2.text.toString()
|
||||
model = etModel2.text.toString(),
|
||||
enabled = cbModel2Enabled.isChecked
|
||||
))
|
||||
llmConfigs.add(LLMConfig(
|
||||
name = etModelName3.text.toString().ifEmpty { "配置3" },
|
||||
name = etModelName3.text.toString().ifEmpty { "模型3" },
|
||||
baseUrl = etBaseUrl3.text.toString(),
|
||||
apiKey = etApiKey3.text.toString(),
|
||||
model = etModel3.text.toString()
|
||||
model = etModel3.text.toString(),
|
||||
enabled = cbModel3Enabled.isChecked
|
||||
))
|
||||
|
||||
val settingsData = SettingsData(
|
||||
llmConfigs = llmConfigs,
|
||||
selectedLlmIndex = selectedLlmIndex,
|
||||
headerConfigs = null,
|
||||
promptConfigs = null,
|
||||
promptConfigs = promptConfigs,
|
||||
buttonConfigs = null,
|
||||
noteApiConfig = null
|
||||
)
|
||||
@@ -354,6 +455,8 @@ class SecondActivity : AppCompatActivity() {
|
||||
val enabled: Boolean = true
|
||||
)
|
||||
|
||||
data class PromptConfig(val id: String, val title: String, val content: String)
|
||||
|
||||
data class SettingsData(
|
||||
val llmConfigs: List<LLMConfig>?,
|
||||
val selectedLlmIndex: Int?,
|
||||
@@ -365,7 +468,6 @@ class SecondActivity : AppCompatActivity() {
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -69,7 +69,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- 提示词选择区:左侧标签+下拉框,右侧快捷按钮 -->
|
||||
<!-- 提示词选择区 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -175,7 +175,7 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 提示词详情:显示名称和内容 -->
|
||||
<!-- 提示词详情 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -212,162 +212,236 @@
|
||||
android:text="无特殊指令"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 大模型返回结果 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="大模型返回结果"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textAllCaps="true"
|
||||
android:letterSpacing="0.15"
|
||||
android:layout_marginBottom="14dp"/>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/result_card_bg"
|
||||
android:padding="16dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/primary"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/outputStatusLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待发送"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/primary"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
android:background="@drawable/input_bg"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="14dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/outputTextView"
|
||||
android:id="@+id/inputEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minLines="3"
|
||||
android:minLines="2"
|
||||
android:maxLines="5"
|
||||
android:hint="输入待发送内容…"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:textSize="14sp"
|
||||
android:text="发送消息后结果将在此显示"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:editable="true"
|
||||
android:gravity="top"
|
||||
android:background="@android:drawable/edit_text"/>
|
||||
android:textColorHint="@color/text_hint"
|
||||
android:gravity="top|start"
|
||||
android:background="@android:color/transparent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="12dp">
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCharCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="0/4000"
|
||||
android:textSize="11sp"
|
||||
android:textColor="@color/text_hint"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCopyResult"
|
||||
android:layout_width="0dp"
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:layout_weight="1"
|
||||
android:text="复制结果"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="@drawable/button_secondary_bg"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveNote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_weight="1"
|
||||
android:text="提交笔记"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="@drawable/button_secondary_bg"
|
||||
android:text="发送"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@drawable/send_button_bg"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 三栏结果显示 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="各模型返回结果"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textAllCaps="true"
|
||||
android:letterSpacing="0.15"
|
||||
android:layout_marginBottom="10dp"/>
|
||||
|
||||
<!-- 模型1 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/result_card_bg"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvModel1Name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="● 模型1"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/primary"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatus1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待..."
|
||||
android:textSize="11sp"
|
||||
android:textColor="@color/text_hint"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/divider"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvResult1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待结果..."
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:minLines="3"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 模型2 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/result_card_bg"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvModel2Name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="○ 模型2"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatus2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待..."
|
||||
android:textSize="11sp"
|
||||
android:textColor="@color/text_hint"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/divider"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvResult2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待结果..."
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:minLines="3"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 模型3 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/result_card_bg"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvModel3Name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="○ 模型3"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatus3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待..."
|
||||
android:textSize="11sp"
|
||||
android:textColor="@color/text_hint"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/divider"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvResult3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="等待结果..."
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:minLines="3"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 复制结果按钮 -->
|
||||
<Button
|
||||
android:id="@+id/btnCopyResult"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:text="复制所有结果"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="@drawable/button_secondary_bg"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/surface"
|
||||
android:padding="20dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/inputEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minLines="3"
|
||||
android:maxLines="10"
|
||||
android:background="@drawable/input_bg"
|
||||
android:hint="输入待发送内容…"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textColorHint="@color/text_hint"
|
||||
android:gravity="top|start"
|
||||
android:scrollbars="vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/stopButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:text="停止生成"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/stop_generate"
|
||||
android:background="@drawable/stop_button_bg"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCharCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0/4000"
|
||||
android:textSize="11sp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:layout_marginEnd="12dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:text="➤"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@drawable/send_button_bg"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
File diff suppressed because it is too large
Load Diff
46
flomo-ai/app/src/main/res/layout/dialog_add_prompt.xml
Normal file
46
flomo-ai/app/src/main/res/layout/dialog_add_prompt.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="标题"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/text_hint"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPromptTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:hint="例如:翻译助手"
|
||||
android:textSize="14sp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="内容"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/text_hint"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPromptContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:hint="提示词内容..."
|
||||
android:textSize="14sp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="top"
|
||||
android:inputType="textMultiLine"/>
|
||||
|
||||
</LinearLayout>
|
||||
46
flomo-ai/app/src/main/res/layout/item_prompt_config.xml
Normal file
46
flomo-ai/app/src/main/res/layout/item_prompt_config.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPromptTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeletePrompt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:text="删除"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/error"
|
||||
android:background="@drawable/button_secondary_bg"
|
||||
android:minHeight="0dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPromptContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user