feat: 配置页增加测试连接按钮,发送简单文本验证API是否可用
This commit is contained in:
@@ -26,6 +26,10 @@ pub struct FlomoAiApp {
|
|||||||
new_prompt_title: String,
|
new_prompt_title: String,
|
||||||
new_prompt_content: String,
|
new_prompt_content: String,
|
||||||
|
|
||||||
|
test_status: String,
|
||||||
|
test_is_loading: bool,
|
||||||
|
pending_test: Option<std::thread::JoinHandle<Result<String, String>>>,
|
||||||
|
|
||||||
current_page: Page,
|
current_page: Page,
|
||||||
theme_dirty: bool,
|
theme_dirty: bool,
|
||||||
}
|
}
|
||||||
@@ -54,6 +58,10 @@ impl FlomoAiApp {
|
|||||||
new_prompt_title: String::new(),
|
new_prompt_title: String::new(),
|
||||||
new_prompt_content: String::new(),
|
new_prompt_content: String::new(),
|
||||||
|
|
||||||
|
test_status: "点击测试连接".to_string(),
|
||||||
|
test_is_loading: false,
|
||||||
|
pending_test: None,
|
||||||
|
|
||||||
current_page: Page::Main,
|
current_page: Page::Main,
|
||||||
theme_dirty: false,
|
theme_dirty: false,
|
||||||
}
|
}
|
||||||
@@ -84,6 +92,28 @@ impl FlomoAiApp {
|
|||||||
ctx.request_repaint();
|
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) {
|
fn send_request(&mut self, ctx: &egui::Context) {
|
||||||
@@ -144,6 +174,38 @@ impl FlomoAiApp {
|
|||||||
self.settings.theme_config.mode = self.settings_selected_theme;
|
self.settings.theme_config.mode = self.settings_selected_theme;
|
||||||
let _ = save_settings(&self.settings);
|
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 {
|
impl eframe::App for FlomoAiApp {
|
||||||
@@ -415,6 +477,31 @@ impl FlomoAiApp {
|
|||||||
ui.text_edit_singleline(&mut self.settings_model);
|
ui.text_edit_singleline(&mut self.settings_model);
|
||||||
ui.add_space(12.0);
|
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.separator();
|
||||||
|
|
||||||
ui.label(egui::RichText::new("主题").size(14.0).strong());
|
ui.label(egui::RichText::new("主题").size(14.0).strong());
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
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 btnTestConnection: Button
|
||||||
|
private lateinit var tvTestStatus: TextView
|
||||||
private lateinit var llHeadersList: LinearLayout
|
private lateinit var llHeadersList: LinearLayout
|
||||||
private lateinit var btnAddHeader: Button
|
private lateinit var btnAddHeader: Button
|
||||||
private lateinit var layoutHeaderContent: LinearLayout
|
private lateinit var layoutHeaderContent: LinearLayout
|
||||||
@@ -151,6 +153,8 @@ 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)
|
||||||
|
btnTestConnection = findViewById(R.id.btnTestConnection)
|
||||||
|
tvTestStatus = findViewById(R.id.tvTestStatus)
|
||||||
|
|
||||||
// Header Section
|
// Header Section
|
||||||
llHeadersList = findViewById(R.id.llHeadersList)
|
llHeadersList = findViewById(R.id.llHeadersList)
|
||||||
@@ -235,6 +239,11 @@ class SecondActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test connection button
|
||||||
|
btnTestConnection.setOnClickListener {
|
||||||
|
testConnection()
|
||||||
|
}
|
||||||
|
|
||||||
Log.d("SecondActivity", "initViews: Completed")
|
Log.d("SecondActivity", "initViews: Completed")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("SecondActivity", "initViews: Error finding views", e)
|
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 = "") {
|
private fun addHeaderEntry(key: String = "", value: String = "") {
|
||||||
val view = layoutInflater.inflate(R.layout.header_entry, null)
|
val view = layoutInflater.inflate(R.layout.header_entry, null)
|
||||||
val etKey = view.findViewById<EditText>(R.id.etHeaderKey)
|
val etKey = view.findViewById<EditText>(R.id.etHeaderKey)
|
||||||
|
|||||||
@@ -229,6 +229,37 @@
|
|||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
</LinearLayout>
|
</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>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user