v1.3修复了第一次进入对话可能会闪退情况,修复了reasoner模型可能400问题

This commit is contained in:
lotus 2025-05-07 20:05:53 +08:00
parent addbabeb8a
commit 69dd27be52
3 changed files with 275 additions and 189 deletions

View File

@ -11,7 +11,7 @@ android {
minSdk 31
targetSdk 35
versionCode 1
versionName "1.0" //
versionName "1.3" //
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -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<ChatMessage> currentMessageListForSaving = new ArrayList<>();
private long currentConversationId = -1; // -1 表示新对话或未初始化的对话
private volatile boolean isSessionReady = false; // 标记会话主要是currentConversationId是否已准备好可用
private List<ChatMessage> 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<ChatMessage> messagesToSave = new ArrayList<>(currentMessageListForSaving); // Create a copy
Log.d(TAG, "正在保存当前对话 ID: " + currentConversationId + ",包含 " + currentMessageListForSaving.size() + " 条消息。");
final List<ChatMessage> 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<ChatRequest.Message> buildApiContextMessages(List<ChatMessage> sourceMessages) {
// 重构后的 buildApiContextMessages 方法
private List<ChatRequest.Message> buildApiContextMessages(List<ChatMessage> allMessagesInConversation, String targetModelName, String latestUserRawInput) {
Log.d(TAG, "buildApiContextMessages 调用 - 目标模型: " + targetModelName + ", 对话中消息总数: " + allMessagesInConversation.size() + ", 最新用户原始输入长度: " + (latestUserRawInput != null ? latestUserRawInput.length() : "null"));
List<ChatRequest.Message> 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<ChatRequest.Message> 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<apiMessages.size(); i++) {
ChatRequest.Message m = apiMessages.get(i);
Log.d(TAG, " -> 上下文["+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<ChatResponse>() {
@Override
public void onResponse(@NonNull Call<ChatResponse> call, @NonNull Response<ChatResponse> 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<ChatResponse> 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<ChatRequest.Message> 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<ChatRequest.Message> 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<ChatRequest.Message> 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();
}

View File

@ -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
}