v1.3修复了第一次进入对话可能会闪退情况,修复了reasoner模型可能400问题
This commit is contained in:
parent
addbabeb8a
commit
69dd27be52
@ -11,7 +11,7 @@ android {
|
||||
minSdk 31
|
||||
targetSdk 35
|
||||
versionCode 1
|
||||
versionName "1.0" // 你可以按需修改这个版本名
|
||||
versionName "1.3" // 你可以按需修改这个版本名
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user