From 0e05c77f55ec62b5e9a1d7674ea79562c5448e2d Mon Sep 17 00:00:00 2001 From: xiaji Date: Sun, 5 Apr 2026 21:52:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AE=E9=A1=B5=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=B5=8B=E8=AF=95=E8=BF=9E=E6=8E=A5=E6=8C=89=E9=92=AE?= =?UTF-8?q?=EF=BC=8C=E5=8F=91=E9=80=81=E7=AE=80=E5=8D=95=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E9=AA=8C=E8=AF=81API=E6=98=AF=E5=90=A6=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flomo-ai-desktop/src/app.rs | 87 +++++++++++++++++++ .../com/example/flomo_ai/SecondActivity.kt | 79 +++++++++++++++++ .../src/main/res/layout/activity_second.xml | 31 +++++++ 3 files changed, 197 insertions(+) diff --git a/flomo-ai-desktop/src/app.rs b/flomo-ai-desktop/src/app.rs index bcbf7b6..23416d4 100644 --- a/flomo-ai-desktop/src/app.rs +++ b/flomo-ai-desktop/src/app.rs @@ -26,6 +26,10 @@ pub struct FlomoAiApp { new_prompt_title: String, new_prompt_content: String, + test_status: String, + test_is_loading: bool, + pending_test: Option>>, + current_page: Page, theme_dirty: bool, } @@ -54,6 +58,10 @@ impl FlomoAiApp { new_prompt_title: String::new(), new_prompt_content: String::new(), + test_status: "点击测试连接".to_string(), + test_is_loading: false, + pending_test: None, + current_page: Page::Main, theme_dirty: false, } @@ -84,6 +92,28 @@ impl FlomoAiApp { ctx.request_repaint(); } } + + if let Some(handle) = self.pending_test.take() { + if handle.is_finished() { + match handle.join() { + Ok(Ok(text)) => { + self.test_status = format!("连接成功: {}", text.chars().take(30).collect::()); + self.test_is_loading = false; + } + Ok(Err(e)) => { + self.test_status = format!("连接失败: {}", e); + self.test_is_loading = false; + } + Err(_) => { + self.test_status = "测试线程错误".to_string(); + self.test_is_loading = false; + } + } + } else { + self.pending_test = Some(handle); + ctx.request_repaint(); + } + } } fn send_request(&mut self, ctx: &egui::Context) { @@ -144,6 +174,38 @@ impl FlomoAiApp { self.settings.theme_config.mode = self.settings_selected_theme; let _ = save_settings(&self.settings); } + + fn test_connection(&mut self, ctx: &egui::Context) { + if self.test_is_loading { + return; + } + + if self.settings_base_url.is_empty() { + self.test_status = "错误: Base URL 不能为空".to_string(); + return; + } + + self.test_status = "测试中...".to_string(); + self.test_is_loading = true; + + let settings = self.settings.clone(); + let ctx_clone = ctx.clone(); + + let handle = std::thread::spawn(move || { + let mut test_settings = settings.clone(); + test_settings.llm_config.model = if settings.llm_config.model.is_empty() { + "gpt-4o".to_string() + } else { + settings.llm_config.model.clone() + }; + + let result = crate::api::call_llm(&test_settings, "你好,请回复OK".to_string(), None); + ctx_clone.request_repaint(); + result + }); + + self.pending_test = Some(handle); + } } impl eframe::App for FlomoAiApp { @@ -415,6 +477,31 @@ impl FlomoAiApp { ui.text_edit_singleline(&mut self.settings_model); ui.add_space(12.0); + // Test connection button + ui.horizontal(|ui| { + let btn_text = if self.test_is_loading { "测试中..." } else { "测试连接" }; + let btn = egui::Button::new(btn_text) + .fill(egui::Color32::from_rgb(100, 100, 255)) + .rounding(4.0) + .min_size(egui::vec2(80.0, 32.0)); + + if ui.add(btn).clicked() && !self.test_is_loading { + self.test_connection(ctx); + } + + let status_color = if self.test_is_loading { + egui::Color32::from_rgb(255, 165, 0) + } else if self.test_status.starts_with("连接成功") { + egui::Color32::from_rgb(0, 180, 0) + } else if self.test_status.starts_with("连接失败") || self.test_status.starts_with("错误") { + egui::Color32::RED + } else { + egui::Color32::GRAY + }; + + ui.label(egui::RichText::new(&self.test_status).size(11.0).color(status_color)); + }); + ui.separator(); ui.label(egui::RichText::new("主题").size(14.0).strong()); diff --git a/flomo-ai/app/src/main/java/com/example/flomo_ai/SecondActivity.kt b/flomo-ai/app/src/main/java/com/example/flomo_ai/SecondActivity.kt index 17ec87c..f36edc6 100644 --- a/flomo-ai/app/src/main/java/com/example/flomo_ai/SecondActivity.kt +++ b/flomo-ai/app/src/main/java/com/example/flomo_ai/SecondActivity.kt @@ -50,6 +50,8 @@ class SecondActivity : AppCompatActivity() { private lateinit var etApiKey: EditText private lateinit var btnToggleApiKey: ImageButton private lateinit var etModel: EditText + private lateinit var btnTestConnection: Button + private lateinit var tvTestStatus: TextView private lateinit var llHeadersList: LinearLayout private lateinit var btnAddHeader: Button private lateinit var layoutHeaderContent: LinearLayout @@ -151,6 +153,8 @@ class SecondActivity : AppCompatActivity() { etApiKey = findViewById(R.id.etApiKey) btnToggleApiKey = findViewById(R.id.btnToggleApiKey) etModel = findViewById(R.id.etModel) + btnTestConnection = findViewById(R.id.btnTestConnection) + tvTestStatus = findViewById(R.id.tvTestStatus) // Header Section llHeadersList = findViewById(R.id.llHeadersList) @@ -235,6 +239,11 @@ class SecondActivity : AppCompatActivity() { } } + // Test connection button + btnTestConnection.setOnClickListener { + testConnection() + } + Log.d("SecondActivity", "initViews: Completed") } catch (e: Exception) { Log.e("SecondActivity", "initViews: Error finding views", e) @@ -378,6 +387,76 @@ class SecondActivity : AppCompatActivity() { } } + private fun testConnection() { + val baseUrl = etBaseUrl.text.toString().trim() + val apiKey = etApiKey.text.toString().trim() + val model = etModel.text.toString().trim() + + if (baseUrl.isEmpty()) { + tvTestStatus.text = "错误: Base URL 不能为空" + tvTestStatus.setTextColor(ContextCompat.getColor(this, R.color.error)) + return + } + + tvTestStatus.text = "测试中..." + tvTestStatus.setTextColor(ContextCompat.getColor(this, R.color.warning)) + btnTestConnection.isEnabled = false + + CoroutineScope(Dispatchers.IO).launch { + try { + val messagesJson = org.json.JSONArray().apply { + put(org.json.JSONObject().apply { + put("role", "user") + put("content", "你好,请回复OK") + }) + } + + val requestBody = org.json.JSONObject().apply { + put("model", model) + put("messages", messagesJson) + put("max_tokens", 10) + } + + val client = OkHttpClient() + val request = Request.Builder() + .url("$baseUrl/chat/completions") + .addHeader("Content-Type", "application/json") + .addHeader("Authorization", "Bearer $apiKey") + .post(requestBody.toString().toRequestBody("application/json".toMediaType())) + .build() + + val response = client.newCall(request).execute() + + withContext(Dispatchers.Main) { + btnTestConnection.isEnabled = true + if (response.isSuccessful) { + val responseBody = response.body?.string() + val responseJson = org.json.JSONObject(responseBody ?: "") + val choices = responseJson.optJSONArray("choices") + if (choices != null && choices.length() > 0) { + val message = choices.getJSONObject(0).optJSONObject("message") + val content = message?.optString("content", "") ?: "" + tvTestStatus.text = "连接成功: ${content.take(20)}" + tvTestStatus.setTextColor(ContextCompat.getColor(this@SecondActivity, R.color.success)) + } else { + tvTestStatus.text = "连接成功,但返回格式异常" + tvTestStatus.setTextColor(ContextCompat.getColor(this@SecondActivity, R.color.warning)) + } + } else { + tvTestStatus.text = "连接失败: ${response.code} ${response.message}" + tvTestStatus.setTextColor(ContextCompat.getColor(this@SecondActivity, R.color.error)) + } + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + btnTestConnection.isEnabled = true + tvTestStatus.text = "连接失败: ${e.message}" + tvTestStatus.setTextColor(ContextCompat.getColor(this@SecondActivity, R.color.error)) + } + } + } + } + private fun addHeaderEntry(key: String = "", value: String = "") { val view = layoutInflater.inflate(R.layout.header_entry, null) val etKey = view.findViewById(R.id.etHeaderKey) diff --git a/flomo-ai/app/src/main/res/layout/activity_second.xml b/flomo-ai/app/src/main/res/layout/activity_second.xml index 32b9d08..629d6c8 100644 --- a/flomo-ai/app/src/main/res/layout/activity_second.xml +++ b/flomo-ai/app/src/main/res/layout/activity_second.xml @@ -229,6 +229,37 @@ android:textSize="16sp" /> + + + +