From 69dd27be524ea43fdbdaa02504d9c3c1e921f51d Mon Sep 17 00:00:00 2001 From: lotus Date: Wed, 7 May 2025 20:05:53 +0800 Subject: [PATCH] =?UTF-8?q?v1.3=E4=BF=AE=E5=A4=8D=E4=BA=86=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E6=AC=A1=E8=BF=9B=E5=85=A5=E5=AF=B9=E8=AF=9D=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E4=BC=9A=E9=97=AA=E9=80=80=E6=83=85=E5=86=B5=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86reasoner=E6=A8=A1=E5=9E=8B=E5=8F=AF?= =?UTF-8?q?=E8=83=BD400=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../com/lotus_lab/jetchat/MainActivity.java | 456 +++++++++++------- .../jetchat/data/ConversationDao.java | 6 +- 3 files changed, 275 insertions(+), 189 deletions(-) diff --git a/build.gradle b/build.gradle index c15ca17..28f701c 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ android { minSdk 31 targetSdk 35 versionCode 1 - versionName "1.0" // 你可以按需修改这个版本名 + versionName "1.3" // 你可以按需修改这个版本名 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/src/main/java/com/lotus_lab/jetchat/MainActivity.java b/src/main/java/com/lotus_lab/jetchat/MainActivity.java index 91ac588..ab3dd0c 100644 --- a/src/main/java/com/lotus_lab/jetchat/MainActivity.java +++ b/src/main/java/com/lotus_lab/jetchat/MainActivity.java @@ -6,8 +6,8 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.text.TextUtils; // Import TextUtils -import android.util.Log; +import android.text.TextUtils; // 确保导入 +import android.util.Log; // 确保导入 import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -32,9 +32,9 @@ import com.lotus_lab.jetchat.network.DeepSeekApiService; import com.lotus_lab.jetchat.ui.ChatAdapter; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -// import java.util.UUID; // UUID not directly used in this version +import java.util.ArrayList; // 确保导入 +import java.util.Collections; // 确保导入 +import java.util.List; // 确保导入 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -45,7 +45,7 @@ import retrofit2.Response; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; - private static final int MAX_CONTEXT_MESSAGES = 10; // Max messages to send as context + private static final int MAX_CONTEXT_MESSAGES = 10; // 作为上下文发送的最大消息数 private RecyclerView recyclerViewChat; private EditText editTextMessage; @@ -58,17 +58,18 @@ public class MainActivity extends AppCompatActivity { private ExecutorService databaseExecutor; private Handler mainThreadHandler; - private long currentConversationId = -1; - private List currentMessageListForSaving = new ArrayList<>(); + private long currentConversationId = -1; // -1 表示新对话或未初始化的对话 + private volatile boolean isSessionReady = false; // 标记会话(主要是currentConversationId)是否已准备好可用 + private List currentMessageListForSaving = new ArrayList<>(); // 存储当前对话的消息 private SharedPreferences sharedPreferences; private String apiKey; - // private String modelName; // Will be replaced by modelName1 and modelName2 - private String modelName1; // For the primary model - private String modelName2; // For the secondary/comparison model (can be empty) + private String modelName1; // 主模型 + private String modelName2; // 次模型 (可选) private String apiBaseUrl; - private int activeApiCallCount = 0; // Counter for active API calls + // private int activeApiCallCount = 0; // 旧的计数器,现在使用 outstandingApiRequests + private int outstandingApiRequests = 0; // 用于跟踪并发API请求的数量 @Override protected void onCreate(Bundle savedInstanceState) { @@ -76,7 +77,6 @@ public class MainActivity extends AppCompatActivity { setContentView(R.layout.activity_main); sharedPreferences = getSharedPreferences(SettingsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); - // loadApiSettings(); // Moved to onResume and before sending message for freshness mainThreadHandler = new Handler(Looper.getMainLooper()); recyclerViewChat = findViewById(R.id.recyclerViewChat); @@ -84,14 +84,13 @@ public class MainActivity extends AppCompatActivity { buttonSend = findViewById(R.id.buttonSend); progressBar = findViewById(R.id.progressBar); - // apiService = ApiClient.getClient(this).create(DeepSeekApiService.class); // Initialize in onResume database = AppDatabase.getDatabase(this); databaseExecutor = Executors.newSingleThreadExecutor(); chatAdapter = new ChatAdapter(new ArrayList<>()); recyclerViewChat.setLayoutManager(new LinearLayoutManager(this)); recyclerViewChat.setAdapter(chatAdapter); - loadChatHistory(); // This will start a new conversation session + loadChatHistory(); // 这将启动一个新的会话 buttonSend.setOnClickListener(v -> sendMessage()); } @@ -100,85 +99,82 @@ public class MainActivity extends AppCompatActivity { protected void onResume() { super.onResume(); String newBaseUrlSetting = sharedPreferences.getString(SettingsActivity.KEY_API_BASE_URL, SettingsActivity.DEFAULT_API_BASE_URL); - // Check if apiBaseUrl needs update or if apiService needs reinitialization if (apiBaseUrl == null || !apiBaseUrl.equals(newBaseUrlSetting) || apiService == null) { - Log.d(TAG, "Base URL changed, was null, or apiService was null. Old: " + apiBaseUrl + ", New: " + newBaseUrlSetting); - ApiClient.resetClient(); // Reset if your client caches the base URL - // Load settings again as base URL might have changed - loadApiSettings(); + Log.d(TAG, "API基础URL已更改、为空或apiService为空。旧: " + apiBaseUrl + ", 新: " + newBaseUrlSetting); + ApiClient.resetClient(); + loadApiSettings(); // 基础URL更改后重新加载设置 apiService = ApiClient.getClient(this).create(DeepSeekApiService.class); } else { - // Still load settings in case other things like API key or model changed - loadApiSettings(); + loadApiSettings(); // 即使基础URL未变,其他设置(如模型)也可能已更改 } } private void loadApiSettings() { apiKey = sharedPreferences.getString(SettingsActivity.KEY_API_KEY, ""); apiBaseUrl = sharedPreferences.getString(SettingsActivity.KEY_API_BASE_URL, SettingsActivity.DEFAULT_API_BASE_URL); - // Load both model names modelName1 = sharedPreferences.getString(SettingsActivity.KEY_SELECTED_MODEL_1, SettingsActivity.DEFAULT_MODEL_1); modelName2 = sharedPreferences.getString(SettingsActivity.KEY_SELECTED_MODEL_2, SettingsActivity.DEFAULT_MODEL_2); - Log.d(TAG, "Settings loaded: API Key (ends with): ..." + (apiKey.length() > 4 ? apiKey.substring(apiKey.length() - 4) : apiKey) + - ", Model1=" + modelName1 + ", Model2=" + modelName2 + ", BaseURL=" + apiBaseUrl); + Log.d(TAG, "设置已加载: API密钥 (结尾): ..." + (apiKey.length() > 4 ? apiKey.substring(apiKey.length() - 4) : apiKey) + + ", 模型1=" + modelName1 + ", 模型2=" + modelName2 + ", 基础URL=" + apiBaseUrl); String placeholderApiKey = ""; try { placeholderApiKey = getString(R.string.default_api_key_placeholder); } catch (Exception e) { - Log.e(TAG, "getString(R.string.default_api_key_placeholder) failed.", e); + Log.e(TAG, "getString(R.string.default_api_key_placeholder) 失败。", e); } if (apiKey.isEmpty() || apiKey.equals(placeholderApiKey)) { - Log.w(TAG, "API Key is not set or is placeholder."); - // Consider showing a persistent warning or guiding user to settings + Log.w(TAG, "API密钥未设置或为占位符。"); } if (TextUtils.isEmpty(modelName1) && TextUtils.isEmpty(modelName2)) { - Log.w(TAG, "Neither Model 1 nor Model 2 is configured."); - // This case should ideally be handled by SettingsActivity ensuring model1 has a default + Log.w(TAG, "模型1和模型2均未配置。"); } } private void refreshConversation() { - Log.d(TAG, "refreshConversation called. Current active conversationId: " + currentConversationId); - saveCurrentConversationIfNeeded(); // Save previous before starting new - startNewConversationSession(); + Log.d(TAG, "refreshConversation 调用。当前活动 conversationId: " + currentConversationId); + saveCurrentConversationIfNeeded(); + startNewConversationSession(); // 这会重置 currentConversationId 并清空列表/UI - chatAdapter.clearMessages(); - currentMessageListForSaving.clear(); + // chatAdapter.clearMessages(); // 已在 startNewConversationSession 的回调中处理 + // currentMessageListForSaving.clear(); // 已在 startNewConversationSession 的回调中处理 Toast.makeText(this, "新对话已开始", Toast.LENGTH_SHORT).show(); - setLoadingState(false); // Ensure loading is off for new conversation + setLoadingState(false); editTextMessage.setText(""); editTextMessage.requestFocus(); scrollToBottom(false); } private void startNewConversationSession() { - // Creates a new conversation entry in the DB and sets currentConversationId + isSessionReady = false; // 在获取到新的 conversationId 之前,会话尚未就绪 + Log.d(TAG, "startNewConversationSession: isSessionReady 设置为 false"); Conversation newSessionConversation = new Conversation(System.currentTimeMillis(), "新对话开始...", System.currentTimeMillis()); databaseExecutor.execute(() -> { currentConversationId = database.conversationDao().insertConversation(newSessionConversation); - Log.i(TAG, "New conversation session started with ID: " + currentConversationId); + isSessionReady = true; // 获取到新的 ID 后,会话就绪 + Log.i(TAG, "新对话会话已启动,ID: " + currentConversationId + ", isSessionReady 设置为 true"); mainThreadHandler.post(() -> { currentMessageListForSaving.clear(); if (chatAdapter != null) chatAdapter.clearMessages(); + // 可选:通知用户会话已准备好 }); }); } private void saveCurrentConversationIfNeeded() { if (currentConversationId != -1 && !currentMessageListForSaving.isEmpty()) { - Log.d(TAG, "Saving current conversation ID: " + currentConversationId + " with " + currentMessageListForSaving.size() + " messages."); - final List messagesToSave = new ArrayList<>(currentMessageListForSaving); // Create a copy + Log.d(TAG, "正在保存当前对话 ID: " + currentConversationId + ",包含 " + currentMessageListForSaving.size() + " 条消息。"); + final List messagesToSave = new ArrayList<>(currentMessageListForSaving); final long conversationIdToSave = currentConversationId; databaseExecutor.execute(() -> { - if (messagesToSave.isEmpty()) return; // Should not happen if check above is done + if (messagesToSave.isEmpty()) return; ChatMessage lastMessage = messagesToSave.get(messagesToSave.size() - 1); String preview = lastMessage.content; - if (preview.length() > 100) { // Increased preview length + if (preview.length() > 100) { preview = preview.substring(0, 100) + "..."; } Conversation conversationToUpdate = database.conversationDao().getConversationById(conversationIdToSave); @@ -186,21 +182,32 @@ public class MainActivity extends AppCompatActivity { conversationToUpdate.lastMessagePreview = preview; conversationToUpdate.lastMessageTimestamp = lastMessage.timestamp; database.conversationDao().updateConversation(conversationToUpdate); - Log.d(TAG, "Conversation meta updated for ID: " + conversationIdToSave); + Log.d(TAG, "对话元数据已更新,ID: " + conversationIdToSave); } else { - Log.e(TAG, "Failed to find conversation to update with ID: " + conversationIdToSave); + Log.e(TAG, "未能找到要更新的对话,ID: " + conversationIdToSave); + } + }); + } else if (currentConversationId != -1 && currentMessageListForSaving.isEmpty()) { + final long emptyConvIdToDelete = currentConversationId; + Log.d(TAG, "当前对话 ID " + emptyConvIdToDelete + " 为空。计划删除并准备新会话。"); + databaseExecutor.execute(() -> { + int rowsDeleted = database.conversationDao().deleteConversationById(emptyConvIdToDelete); + if (rowsDeleted > 0) { + Log.d(TAG, "ID为 " + emptyConvIdToDelete + " 的空对话外壳已成功删除。"); + mainThreadHandler.post(() -> { + if (this.currentConversationId == emptyConvIdToDelete) { + Log.i(TAG, "活动对话 " + emptyConvIdToDelete + " 是一个空对话并已删除。正在启动新的对话会话。"); + startNewConversationSession(); + } else { + Log.w(TAG, "空对话 " + emptyConvIdToDelete + " 已删除,但活动对话ID已更改为 " + this.currentConversationId + "。未从此删除路径启动新会话。"); + } + }); + } else { + Log.w(TAG, "未能删除ID为 " + emptyConvIdToDelete + " 的空对话外壳(可能已被删除或从未存在)。"); } }); } else { - Log.d(TAG, "No active conversation or no messages to save for convId: " + currentConversationId); - if (currentConversationId != -1 && currentMessageListForSaving.isEmpty()) { - // Optionally delete empty conversation shells if they are created but never used - final long emptyConvId = currentConversationId; - databaseExecutor.execute(() -> { - Log.d(TAG, "Attempting to delete empty conversation shell with ID: " + emptyConvId); - database.conversationDao().deleteConversationById(emptyConvId); - }); - } + Log.d(TAG, "没有活动的对话 (ID: " + currentConversationId + ") 或没有消息可保存。"); } } @@ -209,120 +216,187 @@ public class MainActivity extends AppCompatActivity { startActivity(intent); } - - private List buildApiContextMessages(List sourceMessages) { + // 重构后的 buildApiContextMessages 方法 + private List buildApiContextMessages(List allMessagesInConversation, String targetModelName, String latestUserRawInput) { + Log.d(TAG, "buildApiContextMessages 调用 - 目标模型: " + targetModelName + ", 对话中消息总数: " + allMessagesInConversation.size() + ", 最新用户原始输入长度: " + (latestUserRawInput != null ? latestUserRawInput.length() : "null")); List apiMessages = new ArrayList<>(); - // Optional: Add a system prompt if desired for your models - // apiMessages.add(new ChatRequest.Message("system", "You are a helpful assistant.")); - int startIndex = Math.max(0, sourceMessages.size() - MAX_CONTEXT_MESSAGES); - for (int i = startIndex; i < sourceMessages.size(); i++) { - ChatMessage chatMsg = sourceMessages.get(i); - // Ensure only user and assistant messages are part of the context - if ("user".equals(chatMsg.role) || "assistant".equals(chatMsg.role)) { - apiMessages.add(new ChatRequest.Message(chatMsg.role, chatMsg.content)); + boolean isReasonerTypeModel = "deepseek-reasoner".equalsIgnoreCase(targetModelName); + + if (isReasonerTypeModel) { + if (!TextUtils.isEmpty(latestUserRawInput)) { + apiMessages.add(new ChatRequest.Message("user", latestUserRawInput)); + Log.d(TAG, "对于 " + targetModelName + ", 仅发送当前用户原始输入: \"" + latestUserRawInput.substring(0, Math.min(latestUserRawInput.length(), 50)) + "...\""); + } else { + Log.w(TAG, "对于 " + targetModelName + ", latestUserRawInput 为空。无法发送消息。"); + } + } else { + List tempContext = new ArrayList<>(); + String lastRoleSent = null; + + for (int i = allMessagesInConversation.size() - 1; i >= 0 && tempContext.size() < MAX_CONTEXT_MESSAGES; i--) { + ChatMessage historicalMsg = allMessagesInConversation.get(i); + String currentMsgRole = historicalMsg.role; + String currentMsgContent = historicalMsg.content; + + if ("user".equals(currentMsgRole) || "assistant".equals(currentMsgRole)) { + if (lastRoleSent == null || !lastRoleSent.equals(currentMsgRole)) { + // 插入到列表的开头,以保持原始顺序,避免后续反转 + tempContext.add(0, new ChatRequest.Message(currentMsgRole, currentMsgContent)); + lastRoleSent = currentMsgRole; + } else { + Log.d(TAG, "为保持角色交替跳过消息: 角色=" + currentMsgRole + ", 内容=" + currentMsgContent.substring(0, Math.min(currentMsgContent.length(), 30)) + "..."); + } + } + } + // Collections.reverse(tempContext); // 由于我们是使用 add(0, ...) 倒序构建的,所以不再需要反转 + apiMessages.addAll(tempContext); + + // 验证和最终调整 + if (!apiMessages.isEmpty()) { + // 检查是否有连续角色 (理论上构建逻辑应避免此问题) + for (int i = 0; i < apiMessages.size() - 1; i++) { + if (apiMessages.get(i).role.equals(apiMessages.get(i + 1).role)) { + Log.e(TAG, "为 " + targetModelName + " 构建的上下文存在连续相同角色!角色: " + apiMessages.get(i).role + "。正在尝试修正..."); + // 这是一个简化的修正:如果出现连续,则清空并只用最新用户输入 + apiMessages.clear(); + if (!TextUtils.isEmpty(latestUserRawInput)) { + apiMessages.add(new ChatRequest.Message("user", latestUserRawInput)); + } + Log.e(TAG,"上下文修正后大小: " + apiMessages.size()); + break; // 跳出检查循环 + } + } + + // 确保如果发送了多条消息,最后一条是 "user" (因为是用户触发的) + // 并且这条 "user" 消息是用户刚刚输入的那个 + if (!apiMessages.isEmpty() && !"user".equals(apiMessages.get(apiMessages.size() - 1).role) && !TextUtils.isEmpty(latestUserRawInput)) { + Log.w(TAG, "为 " + targetModelName + " 构建的上下文未以用户消息结尾。正在追加当前用户输入。"); + apiMessages.add(new ChatRequest.Message("user", latestUserRawInput)); + } else if (!apiMessages.isEmpty() && "user".equals(apiMessages.get(apiMessages.size() - 1).role) && !apiMessages.get(apiMessages.size()-1).content.equals(latestUserRawInput) && !TextUtils.isEmpty(latestUserRawInput)){ + // 如果最后是user,但内容不是最新的用户输入,也替换/追加 (确保最新用户输入优先) + Log.w(TAG, "为 " + targetModelName + " 构建的上下文最后是用户消息,但内容不是最新输入。正在用最新用户输入替换/追加。"); + // 简单处理:移除最后一条,添加新的 (这可能不总是最佳,但确保最新用户输入存在) + apiMessages.remove(apiMessages.size()-1); + apiMessages.add(new ChatRequest.Message("user", latestUserRawInput)); + } + + + } else if (!TextUtils.isEmpty(latestUserRawInput)) { + // 如果历史上下文为空,但用户有输入 + apiMessages.add(new ChatRequest.Message("user", latestUserRawInput)); + Log.w(TAG, "为 " + targetModelName + " 构建的历史上下文为空,仅发送当前用户输入。"); + } + + // 最后的保险:如果经历了所有逻辑,apiMessages 还是空的,但有用户输入,就加上用户的输入 + if (apiMessages.isEmpty() && !TextUtils.isEmpty(latestUserRawInput)){ + apiMessages.add(new ChatRequest.Message("user", latestUserRawInput)); + Log.d(TAG, "对于 " + targetModelName + ", 上下文为空,作为最后手段添加当前用户输入。"); } } - // If after filtering, the context is empty but we have a user message (e.g., first message of conversation) - // This case should be covered if saveMessageAndUpdateUi adds the user message to sourceMessages before this method is called. - if (apiMessages.isEmpty() && !sourceMessages.isEmpty()) { - ChatMessage lastMessageInSource = sourceMessages.get(sourceMessages.size() -1); - if ("user".equals(lastMessageInSource.role)) { - Log.w(TAG, "API context was empty after filtering, adding current user message directly."); - apiMessages.add(new ChatRequest.Message(lastMessageInSource.role, lastMessageInSource.content)); - } + + Log.d(TAG, "为 " + targetModelName + " 构建的最终API上下文消息数量: " + apiMessages.size()); + for(int i=0; i 上下文["+i+"]: 角色: " + m.role + ", 内容: " + m.content.substring(0, Math.min(m.content.length(), 70)) + "..."); } return apiMessages; } - private synchronized void incrementActiveApiCallCount() { - activeApiCallCount++; - if (activeApiCallCount == 1) { // Only set loading true when the first call starts - mainThreadHandler.post(() -> setLoadingState(true)); - } - Log.d(TAG, "Incremented active API calls: " + activeApiCallCount); + + private synchronized void incrementOutstandingApiRequests() { + outstandingApiRequests++; + Log.d(TAG, "增加待处理API请求计数: " + outstandingApiRequests); + // setLoadingState(true) 由 sendMessage 在确认至少有一个请求后调用 } - private synchronized void decrementActiveApiCallCountAndCheckLoadingState() { - if (activeApiCallCount > 0) { - activeApiCallCount--; + private synchronized void decrementOutstandingApiRequests() { + if (outstandingApiRequests > 0) { + outstandingApiRequests--; } - Log.d(TAG, "Decremented active API calls: " + activeApiCallCount); - if (activeApiCallCount == 0) { + Log.d(TAG, "减少待处理API请求计数: " + outstandingApiRequests); + if (outstandingApiRequests == 0) { mainThreadHandler.post(() -> setLoadingState(false)); } } private void makeApiCall(ChatRequest request, final String modelIdentifier) { if (apiService == null) { - handleApiError("API Service not initialized. Please check settings or restart."); - decrementActiveApiCallCountAndCheckLoadingState(); // Ensure counter is decremented + handleApiError("API服务未初始化。请检查设置或重启应用。", modelIdentifier); + decrementOutstandingApiRequests(); // 确保计数器减少 return; } String authHeader = "Bearer " + apiKey; - Log.d(TAG, "Making API call to model: " + modelIdentifier + " with request: " + request.toString()); + Log.d(TAG, "正在向模型发起API调用: " + modelIdentifier); + // 不在此处记录整个请求体,因为它可能很大。buildApiContextMessages 已记录上下文。 apiService.getChatCompletion(authHeader, request).enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful() && response.body() != null && response.body().choices != null && !response.body().choices.isEmpty()) { - ChatResponse.Message assistantApiMessage = response.body().choices.get(0).message; - if (assistantApiMessage != null && assistantApiMessage.content != null && assistantApiMessage.role != null) { - String responseContent = "[Model: " + modelIdentifier + "]\n" + assistantApiMessage.content.trim(); - ChatMessage assistantMessage = new ChatMessage( - currentConversationId, - assistantApiMessage.role, // Usually "assistant" - responseContent, - System.currentTimeMillis() - ); - saveMessageAndUpdateUi(assistantMessage); - } else { - handleApiError("API response format error from " + modelIdentifier + ": Assistant message or role is null."); - } - } else { - String errorBodyString = "Could not read error body."; - if (response.errorBody() != null) { - try { - errorBodyString = response.errorBody().string(); - } catch (IOException e) { - Log.e(TAG, "Error reading errorBody from " + modelIdentifier, e); + try { + if (response.isSuccessful() && response.body() != null && response.body().choices != null && !response.body().choices.isEmpty()) { + ChatResponse.Message assistantApiMessage = response.body().choices.get(0).message; + if (assistantApiMessage != null && assistantApiMessage.content != null && assistantApiMessage.role != null) { + String responseContent = "[模型: " + modelIdentifier + "]\n" + assistantApiMessage.content.trim(); + ChatMessage assistantMessage = new ChatMessage( + currentConversationId, + assistantApiMessage.role, + responseContent, + System.currentTimeMillis() + ); + saveMessageAndUpdateUi(assistantMessage); + } else { + handleApiError("来自 " + modelIdentifier + " 的API响应格式错误: 助手消息或角色为空。", modelIdentifier); } + } else { + String errorBodyString = "无法读取错误详情。"; + if (response.errorBody() != null) { + try { + errorBodyString = response.errorBody().string(); + } catch (IOException e) { + Log.e(TAG, "从 " + modelIdentifier + " 读取errorBody失败", e); + } + } + handleApiError("模型 " + modelIdentifier + " 的API请求失败: " + response.code() + " - " + response.message() + "\n详情: " + errorBodyString, modelIdentifier); } - handleApiError("API request failed for " + modelIdentifier + ": " + response.code() + " - " + response.message() + "\nDetails: " + errorBodyString); + } finally { + decrementOutstandingApiRequests(); } - decrementActiveApiCallCountAndCheckLoadingState(); } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - handleApiError("Network request failed for " + modelIdentifier + ": " + t.getMessage()); - Log.e(TAG, "API Call Failed for " + modelIdentifier, t); - decrementActiveApiCallCountAndCheckLoadingState(); + try { + handleApiError("模型 " + modelIdentifier + " 的网络请求失败: " + t.getMessage(), modelIdentifier); + Log.e(TAG, "模型 " + modelIdentifier + " 的API调用失败", t); + } finally { + decrementOutstandingApiRequests(); + } } }); } - + // 修改后的 sendMessage 方法 private void sendMessage() { - loadApiSettings(); // Ensure settings are current + loadApiSettings(); // 确保设置最新 - if (currentConversationId == -1) { - Toast.makeText(this, "无法发送消息:当前会话无效", Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Cannot send message, currentConversationId is -1. Attempting to start a new session."); - startNewConversationSession(); - Toast.makeText(this, "正在初始化新会话,请稍后重试发送", Toast.LENGTH_LONG).show(); - return; - } - - String messageText = editTextMessage.getText().toString().trim(); - if (messageText.isEmpty()) { + String messageTextFromInput = editTextMessage.getText().toString().trim(); + if (messageTextFromInput.isEmpty()) { Toast.makeText(this, "消息不能为空", Toast.LENGTH_SHORT).show(); return; } - String placeholderApiKey = ""; - try { placeholderApiKey = getString(R.string.default_api_key_placeholder); } catch (Exception e) { /* already logged */ } + if (!isSessionReady || currentConversationId <= 0) { + Toast.makeText(this, "会话正在初始化,请稍候...", Toast.LENGTH_SHORT).show(); + Log.w(TAG, "sendMessage 调用,但会话未就绪或 currentConversationId 无效。isSessionReady: " + isSessionReady + ", currentConvId: " + currentConversationId); + if (!isSessionReady && (currentConversationId <=0) ) { // 如果会话ID也无效,则尝试启动新会话 + startNewConversationSession(); + } + return; + } + String placeholderApiKey = ""; + try {placeholderApiKey = getString(R.string.default_api_key_placeholder);} catch (Exception e) {/*ignore*/} if (apiKey.isEmpty() || apiKey.equals(placeholderApiKey)) { Toast.makeText(this, getString(R.string.error_api_key_not_set), Toast.LENGTH_LONG).show(); return; @@ -331,107 +405,118 @@ public class MainActivity extends AppCompatActivity { Toast.makeText(this, getString(R.string.error_base_url_not_set), Toast.LENGTH_LONG).show(); return; } - // Model 1 is mandatory (or should have a default from settings) - if (TextUtils.isEmpty(modelName1)) { - Toast.makeText(this, "主模型 (模型1) 未配置,请检查设置。", Toast.LENGTH_LONG).show(); + + if (TextUtils.isEmpty(modelName1) && TextUtils.isEmpty(modelName2)) { + Toast.makeText(this, "请至少在设置中配置一个模型。", Toast.LENGTH_LONG).show(); + // setLoadingState(false); // 如果之前没有设置 true,这里不需要 return; } - - ChatMessage userMessage = new ChatMessage(currentConversationId, "user", messageText, System.currentTimeMillis()); - // Save user message and update UI immediately. - // The saveMessageAndUpdateUi method adds to currentMessageListForSaving. + ChatMessage userMessage = new ChatMessage(currentConversationId, "user", messageTextFromInput, System.currentTimeMillis()); saveMessageAndUpdateUi(userMessage); - editTextMessage.setText(""); // Clear input field + editTextMessage.setText(""); - // Build context messages *after* user message is added to currentMessageListForSaving - List apiContextMessages = buildApiContextMessages(new ArrayList<>(currentMessageListForSaving)); + outstandingApiRequests = 0; // 重置计数器 - // Reset active call count before making new calls - // This should be handled carefully if sendMessage can be called rapidly. - // For simplicity, assuming one "send operation" at a time. - // The increment/decrement logic handles the loading state. - - boolean callMade = false; - - // Send to Model 1 if (!TextUtils.isEmpty(modelName1)) { - Log.d(TAG, "Preparing to send message to Model 1: " + modelName1); - ChatRequest request1 = new ChatRequest(modelName1, apiContextMessages); - incrementActiveApiCallCount(); // Increment before making the call - makeApiCall(request1, modelName1); - callMade = true; + List contextForModel1 = buildApiContextMessages( + new ArrayList<>(currentMessageListForSaving), modelName1, messageTextFromInput + ); + if (!contextForModel1.isEmpty()) { + incrementOutstandingApiRequests(); // 增加计数 + Log.d(TAG, "正在向模型1发送消息: " + modelName1); + ChatRequest request1 = new ChatRequest(modelName1, contextForModel1); + makeApiCall(request1, modelName1); + } else { + Log.w(TAG, "为模型1 (" + modelName1 + ") 构建的上下文为空或无效。跳过API调用。"); + } } - // Send to Model 2 if configured if (!TextUtils.isEmpty(modelName2)) { - Log.d(TAG, "Preparing to send message to Model 2: " + modelName2); - // It's crucial that apiContextMessages is the same for both calls if comparing responses - ChatRequest request2 = new ChatRequest(modelName2, apiContextMessages); - incrementActiveApiCallCount(); // Increment before making the call - makeApiCall(request2, modelName2); - callMade = true; + List contextForModel2 = buildApiContextMessages( + new ArrayList<>(currentMessageListForSaving), modelName2, messageTextFromInput + ); + if (!contextForModel2.isEmpty()) { + incrementOutstandingApiRequests(); // 增加计数 + Log.d(TAG, "正在向模型2发送消息: " + modelName2); + ChatRequest request2 = new ChatRequest(modelName2, contextForModel2); + makeApiCall(request2, modelName2); + } else { + Log.w(TAG, "为模型2 (" + modelName2 + ") 构建的上下文为空或无效。跳过API调用。"); + } } - if (!callMade) { - // This case should ideally not be reached if modelName1 is always set. - handleApiError("没有配置任何模型,请在设置中选择模型。"); - // No need to call setLoadingState(false) here, as it wasn't set to true + if (outstandingApiRequests > 0) { + setLoadingState(true); + } else { + Log.w(TAG, "没有API调用需要发起。outstandingApiRequests = 0"); + Toast.makeText(this, "未能为所选模型构建有效的请求。", Toast.LENGTH_LONG).show(); + setLoadingState(false); // 确保如果没有请求发出,加载状态是关闭的 } } private void saveMessageAndUpdateUi(ChatMessage message) { - if (message.conversationId == -1 && currentConversationId != -1) { + if (message.conversationId <= 0 && currentConversationId > 0) { // 确保使用有效的 currentConversationId message.conversationId = currentConversationId; - } else if (message.conversationId == -1 && currentConversationId == -1) { - Log.e(TAG, "Message cannot be saved. No active conversation session (convId: -1)."); + } else if (message.conversationId <= 0 && currentConversationId <= 0) { + Log.e(TAG, "消息无法保存。没有活动的对话会话 (convId: " + currentConversationId +")。"); Toast.makeText(this, "错误:无法保存消息,会话无效。", Toast.LENGTH_LONG).show(); - // Optionally, try to start a new session, but this might lead to lost messages if not handled carefully - // startNewConversationSession(); + if (!isSessionReady) startNewConversationSession(); // 如果会话未就绪,尝试启动 return; } final ChatMessage messageToSave = message; databaseExecutor.execute(() -> { long insertedMessageId = database.chatMessageDao().insert(messageToSave); - messageToSave.id = (int) insertedMessageId; // Update the object with the DB-generated ID + messageToSave.id = (int) insertedMessageId; mainThreadHandler.post(() -> { if (chatAdapter != null) { chatAdapter.addMessage(messageToSave); } - // Add to in-memory list only if it's not already there (e.g. if called for user message then assistant) - // A better check might be needed if messages could be added out of order or duplicated. - // For now, assuming it's added sequentially. - if (!currentMessageListForSaving.contains(messageToSave)) { // Basic check to avoid duplicates + // 确保不重复添加同一个消息对象到内存列表 + // contains 可能需要 ChatMessage 实现 equals 和 hashCode + boolean found = false; + for(ChatMessage cm : currentMessageListForSaving){ + if(cm.id == messageToSave.id && messageToSave.id != 0){ // 如果ID已分配且相同 + found = true; + break; + } + } + if(!found) { // 或者如果ID为0(新消息尚未插入DB时),基于内容和时间戳等简单比较 currentMessageListForSaving.add(messageToSave); } + scrollToBottom(true); - Log.d(TAG, "Message saved to DB and UI. ConvID: " + messageToSave.conversationId + + Log.d(TAG, "消息已保存至数据库和UI。ConvID: " + messageToSave.conversationId + ", MsgID: " + messageToSave.id + - ", Role: " + messageToSave.role + + ", 角色: " + messageToSave.role + ". Adapter count: " + (chatAdapter != null ? chatAdapter.getItemCount() : "null")); }); }); } private void loadChatHistory() { - Log.d(TAG, "loadChatHistory called - starting new conversation session for MainActivity."); - startNewConversationSession(); // MainActivity always starts a new conversation on create - setLoadingState(false); // Ensure loading is off initially + Log.d(TAG, "loadChatHistory 调用 - 为MainActivity启动新的对话会话。"); + startNewConversationSession(); + setLoadingState(false); } - private void handleApiError(String errorMessage) { + // 更新 handleApiError 以接受 modelIdentifier + private void handleApiError(String errorMessage, String modelIdentifier) { mainThreadHandler.post(() -> { - Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show(); - Log.e(TAG, "API_ERROR: " + errorMessage); - // Decrement counter is handled by makeApiCall's onFailure, - // but if error happens before makeApiCall, ensure loading state is reset. - // For errors like "No model configured", loading state might not have been set. - // if (activeApiCallCount == 0) setLoadingState(false); + String fullErrorMessage = "模型 [" + modelIdentifier + "] 错误: " + errorMessage; + Toast.makeText(MainActivity.this, fullErrorMessage, Toast.LENGTH_LONG).show(); + Log.e(TAG, "API_ERROR ("+modelIdentifier+"): " + errorMessage); + // setLoadingState(false) 由 decrementOutstandingApiRequests 处理 }); } + // 过载一个不带modelIdentifier的,用于通用错误 + private void handleApiError(String errorMessage) { + handleApiError(errorMessage, "N/A"); + } + private void scrollToBottom(boolean smooth) { int itemCount = chatAdapter != null ? chatAdapter.getItemCount() : 0; @@ -445,7 +530,7 @@ public class MainActivity extends AppCompatActivity { } private void setLoadingState(boolean isLoading) { - Log.d(TAG, "Setting loading state to: " + isLoading); + Log.d(TAG, "设置加载状态为: " + isLoading); if (isLoading) { progressBar.setVisibility(View.VISIBLE); buttonSend.setEnabled(false); @@ -461,7 +546,6 @@ public class MainActivity extends AppCompatActivity { public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_main, menu); - // Removed direct listeners as they are handled in onOptionsItemSelected return true; } @@ -485,14 +569,14 @@ public class MainActivity extends AppCompatActivity { @Override protected void onStop() { super.onStop(); - Log.d(TAG, "onStop called. Saving current conversation if needed."); + Log.d(TAG, "onStop 调用。如果需要,保存当前对话。"); saveCurrentConversationIfNeeded(); } @Override protected void onDestroy() { - Log.d(TAG, "onDestroy called. Saving current conversation if needed before shutdown."); - saveCurrentConversationIfNeeded(); // Ensure last save attempt + Log.d(TAG, "onDestroy 调用。关闭前如果需要,保存当前对话。"); + saveCurrentConversationIfNeeded(); if (databaseExecutor != null && !databaseExecutor.isShutdown()) { databaseExecutor.shutdown(); } diff --git a/src/main/java/com/lotus_lab/jetchat/data/ConversationDao.java b/src/main/java/com/lotus_lab/jetchat/data/ConversationDao.java index fa9181b..f155fa2 100644 --- a/src/main/java/com/lotus_lab/jetchat/data/ConversationDao.java +++ b/src/main/java/com/lotus_lab/jetchat/data/ConversationDao.java @@ -30,10 +30,12 @@ public interface ConversationDao { Conversation getConversationById(long conversationId); // (可选) 删除单个对话 (会级联删除其下的 ChatMessage) + // 修改返回类型为 int,以返回删除的行数 @Query("DELETE FROM conversations WHERE id = :conversationId") - void deleteConversationById(long conversationId); + int deleteConversationById(long conversationId); // <--- 修改此处 // (可选) 清空所有对话记录 (也会级联删除所有 ChatMessage) + // 如果也需要知道删除了多少行,也可以将此方法返回类型改为 int @Query("DELETE FROM conversations") - void deleteAllConversations(); + void deleteAllConversations(); // 如果需要,也可以改为返回 int } \ No newline at end of file