feat: 配置页增加测试连接按钮,发送简单文本验证API是否可用

This commit is contained in:
xiaji
2026-04-05 21:52:11 +08:00
parent 90e1b2ce39
commit 0e05c77f55
3 changed files with 197 additions and 0 deletions

View File

@@ -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<std::thread::JoinHandle<Result<String, String>>>,
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::<String>());
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());

View File

@@ -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<EditText>(R.id.etHeaderKey)

View File

@@ -229,6 +229,37 @@
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">
<Button
android:id="@+id/btnTestConnection"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:text="测试连接"
android:textSize="14sp"
android:textColor="@color/white"
android:background="@drawable/button_primary_bg"
android:minWidth="0dp"
android:minHeight="0dp"
android:layout_marginEnd="8dp"/>
<TextView
android:id="@+id/tvTestStatus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:textSize="12sp"
android:textColor="@color/text_hint"
android:text="点击测试连接"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>