From 20e4e24960f1f98f44a19e7231610507680ef9ec Mon Sep 17 00:00:00 2001 From: gy <2293314358@qq.com> Date: Sat, 31 Jan 2026 08:38:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=B7=A5=E4=BD=9C=E5=99=A8?= =?UTF-8?q?=E6=94=B9=E8=BF=9B=20(=20SyncWorker.java=20)=20AI=E6=8F=90?= =?UTF-8?q?=E9=86=92=E6=8E=A5=E6=94=B6=E5=99=A8=E6=94=B9=E8=BF=9B=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9AiReminderReceiver.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/net/micode/notes/data/Notes.java | 2 + .../net/micode/notes/data/NotesProvider.java | 12 + .../src/net/micode/notes/sync/SyncWorker.java | 269 ++++++++++++++++++ .../notes/tool/ai/AiReminderReceiver.java | 37 ++- .../net/micode/notes/ui/AgendaFragment.java | 9 + .../src/net/micode/notes/ui/ChatAdapter.java | 17 +- .../src/net/micode/notes/ui/ChatFragment.java | 192 ++++++++++++- .../net/micode/notes/ui/NoteEditActivity.java | 10 + .../micode/notes/ui/NotesListActivity.java | 11 +- 9 files changed, 533 insertions(+), 26 deletions(-) diff --git a/src/Notes-master/src/net/micode/notes/data/Notes.java b/src/Notes-master/src/net/micode/notes/data/Notes.java index cde268f..c799bd9 100644 --- a/src/Notes-master/src/net/micode/notes/data/Notes.java +++ b/src/Notes-master/src/net/micode/notes/data/Notes.java @@ -268,6 +268,8 @@ public class Notes { * 1: 日程提醒卡片 (Reminder Card) */ public static final String MSG_TYPE = "msg_type"; + public static final int MSG_TYPE_TEXT = 0; // 普通对话 + public static final int MSG_TYPE_REMINDER = 1; // [核心] 提醒卡片(UI显示金色气泡) public static final String CONTENT = "content"; public static final String CREATED_AT = "created_at"; diff --git a/src/Notes-master/src/net/micode/notes/data/NotesProvider.java b/src/Notes-master/src/net/micode/notes/data/NotesProvider.java index e5cebcd..cc8e5fb 100644 --- a/src/Notes-master/src/net/micode/notes/data/NotesProvider.java +++ b/src/Notes-master/src/net/micode/notes/data/NotesProvider.java @@ -50,6 +50,7 @@ public class NotesProvider extends ContentProvider { private static final int URI_SEARCH = 5; private static final int URI_SEARCH_SUGGEST = 6; private static final int URI_USER_ACCOUNT = 7; // 新增编号 + private static final int URI_CHAT_MESSAGES = 8; static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); @@ -61,6 +62,8 @@ public class NotesProvider extends ContentProvider { mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, "user_account", URI_USER_ACCOUNT); + // [核心新增]:让系统认识 content://micode_notes/chat_messages + mMatcher.addURI(Notes.AUTHORITY, "chat_messages", URI_CHAT_MESSAGES); } /** @@ -140,6 +143,9 @@ public class NotesProvider extends ContentProvider { Log.e(TAG, "got exception: " + ex.toString()); } break; + case URI_CHAT_MESSAGES: + c = db.query(NotesDatabaseHelper.TABLE.CHAT_MESSAGES, projection, selection, selectionArgs, null, null, sortOrder); + break; default: throw new IllegalArgumentException("Unknown URI " + uri); } @@ -168,6 +174,12 @@ public class NotesProvider extends ContentProvider { case URI_USER_ACCOUNT: insertedId = db.insert(TABLE.USER_ACCOUNT, null, values); break; + // [核心新增]:处理聊天消息的真正入库逻辑 + case URI_CHAT_MESSAGES: + insertedId = db.insert(NotesDatabaseHelper.TABLE.CHAT_MESSAGES, null, values); + // 插入成功后发送数据变更通知,让 ChatFragment 能够自动刷新 + getContext().getContentResolver().notifyChange(uri, null); + break; default: throw new IllegalArgumentException("Unknown URI " + uri); } diff --git a/src/Notes-master/src/net/micode/notes/sync/SyncWorker.java b/src/Notes-master/src/net/micode/notes/sync/SyncWorker.java index f1e55a6..6a3c398 100644 --- a/src/Notes-master/src/net/micode/notes/sync/SyncWorker.java +++ b/src/Notes-master/src/net/micode/notes/sync/SyncWorker.java @@ -15,11 +15,17 @@ import androidx.work.WorkerParameters; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.gson.JsonObject; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.model.CloudNote; +import net.micode.notes.tool.AiNotificationHelper; import net.micode.notes.tool.SyncMapper; +import net.micode.notes.tool.ai.AiDataSyncHelper; +import net.micode.notes.tool.ai.CozeClient; +import net.micode.notes.tool.ai.CozeRequest; +import net.micode.notes.tool.ai.CozeResponse; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -31,6 +37,8 @@ public class SyncWorker extends Worker { public static final int MODE_ALL = 0; // 默认:全量同步 public static final int MODE_PUSH = 1; // 仅上传 public static final int MODE_PULL = 2; // 仅拉取 + public static final int MODE_REMINDER = 3; + public static final int MODE_RANDOM_CHAT = 4; // 新增模式:随机/固定问候 public SyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); @@ -56,6 +64,7 @@ public class SyncWorker extends Worker { // --- PUSH 逻辑 (保持同步执行即可,因为它本身不依赖回调返回数据给UI) --- if (mode == MODE_PUSH || mode == MODE_ALL) { performPush(db, uid); + syncToCozeAgent(); } // --- PULL 逻辑 (异步变同步) --- @@ -72,6 +81,17 @@ public class SyncWorker extends Worker { } } + // 2. 在 doWork() 中增加判断 + if (mode == MODE_REMINDER) { + String title = getInputData().getString("reminder_title"); + requestAiReminderReply(title); + } + + if (mode == MODE_RANDOM_CHAT) { + String type = getInputData().getString("care_type"); + requestAiCareReply(type); + } + return isSuccess[0] ? Result.success() : Result.failure(); } catch (Exception e) { @@ -80,6 +100,240 @@ public class SyncWorker extends Worker { } } + private void requestAiCareReply(String careType) { + try { + // 1. 准备请求参数 (针对 Random 分支) + com.google.gson.JsonObject jsonPayload = new com.google.gson.JsonObject(); + jsonPayload.addProperty("intent", "random"); + + // 拟人化 Prompt 引导 + String prompt; + if ("morning".equals(careType)) { + prompt = "[System_Event]: 现在是早晨。请基于天气插件并结合我之前的笔记,给我一段充满活力的早安问候。"; + } else if ("night".equals(careType)) { + prompt = "[System_Event]: 现在是深夜。请根据我的生活习惯提示我早点休息,语气要温柔。"; + } else { + prompt = "[System_Event]: 现在是闲暇时刻。请从我的笔记库中挑一件有趣的事,或者分享一条今天的新闻来找我闲聊。"; + } + + jsonPayload.addProperty("payload", prompt); + jsonPayload.addProperty("current_time", AiDataSyncHelper.getCurrentTime()); + + // 2. 发起对话 + CozeRequest request = new CozeRequest(CozeClient.BOT_ID, "user_demo", jsonPayload.toString()); + retrofit2.Response response = CozeClient.getInstance() + .chat(CozeClient.getAuthToken(), request).execute(); + + String finalAnswer = null; + + if (response.isSuccessful() && response.body() != null && response.body().data != null) { + String chatId = response.body().data.id; + String convId = response.body().data.conversation_id; + + // 3. 轮询状态 + String status = ""; + int retries = 0; + while (!"completed".equals(status) && retries < 15) { + Thread.sleep(2000); + retrofit2.Response pollResp = CozeClient.getInstance() + .retrieveChat(CozeClient.getAuthToken(), chatId, convId).execute(); + if (pollResp.isSuccessful() && pollResp.body() != null && pollResp.body().data != null) { + status = pollResp.body().data.status; + } + retries++; + } + + // 4. 状态完成后获取消息列表 + if ("completed".equals(status)) { + retrofit2.Response msgListResp = CozeClient.getInstance() + .getMessageList(CozeClient.getAuthToken(), chatId, convId).execute(); + + if (msgListResp.isSuccessful() && msgListResp.body() != null) { + com.google.gson.JsonArray messages = msgListResp.body().getAsJsonArray("data"); + for (com.google.gson.JsonElement el : messages) { + com.google.gson.JsonObject m = el.getAsJsonObject(); + // 寻找 assistant 的真正 answer + if ("assistant".equals(m.get("role").getAsString()) && + "answer".equals(m.get("type").getAsString())) { + String rawContent = m.get("content").getAsString(); + finalAnswer = parseAiResponse(rawContent); // 强制脱壳 + break; + } + } + } + } + } + + // 5. 存储并通知用户 + if (finalAnswer != null && !finalAnswer.isEmpty()) { + // 存入聊天表 (msg_type=0 代表普通气泡) + saveChatMessageToLocal(finalAnswer); + // 弹出顶部通知 + AiNotificationHelper.sendAiNotification(getApplicationContext(), finalAnswer); + Log.d("AiCare", "Pushed care message: " + finalAnswer); + } + + } catch (Exception e) { + Log.e("AiCare", "Failed to get care reply", e); + } + } + + // 3. 实现提醒请求逻辑 + private void requestAiReminderReply(String agendaTitle) { + try { + // 1. 构造请求参数 + com.google.gson.JsonObject json = new com.google.gson.JsonObject(); + json.addProperty("intent", "reminder"); + json.addProperty("payload", "日程即将开始:" + agendaTitle); + json.addProperty("current_time", AiDataSyncHelper.getCurrentTime()); + + // 2. 发起对话 + CozeRequest request = new CozeRequest(CozeClient.BOT_ID, "user_demo", json.toString()); + retrofit2.Response response = CozeClient.getInstance() + .chat(CozeClient.getAuthToken(), request).execute(); + + // 声明变量,用于存放最终结果 + String finalAnswer = null; + + if (response.isSuccessful() && response.body() != null && response.body().data != null) { + String chatId = response.body().data.id; + String convId = response.body().data.conversation_id; + + // 3. 轮询状态直到完成 + String status = ""; + int retries = 0; + while (!"completed".equals(status) && retries < 15) { + Thread.sleep(2000); + retrofit2.Response pollResp = CozeClient.getInstance() + .retrieveChat(CozeClient.getAuthToken(), chatId, convId).execute(); + + if (pollResp.isSuccessful() && pollResp.body() != null && pollResp.body().data != null) { + status = pollResp.body().data.status; + } + retries++; + } + + // 4. [核心修复点]:状态完成后,请求消息列表并解析出 content + if ("completed".equals(status)) { + retrofit2.Response msgListResp = CozeClient.getInstance() + .getMessageList(CozeClient.getAuthToken(), chatId, convId).execute(); + + if (msgListResp.isSuccessful() && msgListResp.body() != null) { + com.google.gson.JsonArray messages = msgListResp.body().getAsJsonArray("data"); + + // 遍历寻找 AI 的回答内容 + for (com.google.gson.JsonElement el : messages) { + com.google.gson.JsonObject m = el.getAsJsonObject(); + if ("assistant".equals(m.get("role").getAsString()) && + "answer".equals(m.get("type").getAsString())) { + String rawContent = m.get("content").getAsString(); + finalAnswer = parseAiResponse(rawContent); // 强制脱壳 + break; + } + } + } + } + } + + // 5. 统一处理最终结果 + if (finalAnswer != null && !finalAnswer.isEmpty()) { + saveReminderToLocal(finalAnswer); + AiNotificationHelper.sendAiNotification(getApplicationContext(), finalAnswer); + Log.d("AiReminder", "Successfully got AI reminder: " + finalAnswer); + } else { + Log.w("AiReminder", "AI returned empty answer for reminder."); + } + + } catch (Exception e) { + Log.e("AiReminder", "Error in requestAiReminderReply", e); + } + } + + private void saveReminderToLocal(String content) { + android.content.ContentValues values = new android.content.ContentValues(); + values.put(Notes.ChatColumns.SENDER_TYPE, 1); // AI + values.put(Notes.ChatColumns.MSG_TYPE, 1); // [重要] 设为提醒卡片类型 + values.put(Notes.ChatColumns.CONTENT, content); + values.put(Notes.ChatColumns.CREATED_AT, System.currentTimeMillis()); + getApplicationContext().getContentResolver().insert( + android.net.Uri.parse("content://micode_notes/chat_messages"), values); + } + + // 在 SyncWorker 类中添加 + private void syncToCozeAgent() { + try { + com.google.gson.JsonObject payloadJson = new com.google.gson.JsonObject(); + payloadJson.addProperty("intent", "sync"); + payloadJson.addProperty("payload", AiDataSyncHelper.getSyncPayload(getApplicationContext())); + payloadJson.addProperty("current_time", AiDataSyncHelper.getCurrentTime()); + + CozeRequest request = new CozeRequest(CozeClient.BOT_ID, "user_demo", payloadJson.toString()); + + // 1. 发起对话 + retrofit2.Response response = CozeClient.getInstance() + .chat(CozeClient.getAuthToken(), request).execute(); + + if (response.isSuccessful() && response.body() != null && response.body().data != null) { + String chatId = response.body().data.id; + String convId = response.body().data.conversation_id; // [关键] 获取会话ID + + String status = ""; + int retries = 0; + while (!"completed".equals(status) && retries < 15) { + Thread.sleep(2000); + // [关键] 传入两个 ID 轮询 + retrofit2.Response pollResp = CozeClient.getInstance() + .retrieveChat(CozeClient.getAuthToken(), chatId, convId).execute(); + + if (pollResp.isSuccessful() && pollResp.body() != null && pollResp.body().data != null) { + status = pollResp.body().data.status; + } + retries++; + } + + // 2. 完成后抓取真正的“Answer” + if ("completed".equals(status)) { + retrofit2.Response msgListResp = CozeClient.getInstance() + .getMessageList(CozeClient.getAuthToken(), chatId, convId).execute(); + + if (msgListResp.isSuccessful() && msgListResp.body() != null) { + com.google.gson.JsonArray messages = msgListResp.body().getAsJsonArray("data"); + String finalAnswer = null; + + // 遍历寻找 role=assistant 且 type=answer 的内容 + for (com.google.gson.JsonElement el : messages) { + com.google.gson.JsonObject m = el.getAsJsonObject(); + if ("assistant".equals(m.get("role").getAsString()) && + "answer".equals(m.get("type").getAsString())) { + finalAnswer = m.get("content").getAsString(); + break; + } + } + + if (finalAnswer != null && !finalAnswer.isEmpty()) { + // 插入本地数据库并弹通知 + saveChatMessageToLocal(finalAnswer); + AiNotificationHelper.sendAiNotification(getApplicationContext(), finalAnswer); + } + } + } + } + } catch (Exception e) { + Log.e("CozeSync", "Sync Failed", e); + } + } + + // 辅助方法:将 AI 消息存入本地 chat_messages 表 + private void saveChatMessageToLocal(String content) { + android.content.ContentValues values = new android.content.ContentValues(); + values.put(net.micode.notes.data.Notes.ChatColumns.SENDER_TYPE, 1); // AI + values.put(net.micode.notes.data.Notes.ChatColumns.MSG_TYPE, 0); // 文本 + values.put(net.micode.notes.data.Notes.ChatColumns.CONTENT, content); + values.put(net.micode.notes.data.Notes.ChatColumns.CREATED_AT, System.currentTimeMillis()); + getApplicationContext().getContentResolver().insert( + android.net.Uri.parse("content://micode_notes/chat_messages"), values); + } + /** * [上行逻辑]:查询本地所有 sync_state = 1 的记录并上传 */ @@ -236,4 +490,19 @@ public class SyncWorker extends Worker { } return content; } + + private String parseAiResponse(String rawResponse) { + if (rawResponse == null || !rawResponse.trim().startsWith("{")) { + return rawResponse; // 如果不是JSON,直接返回原样数据 + } + try { + com.google.gson.JsonObject jsonObject = com.google.gson.JsonParser.parseString(rawResponse).getAsJsonObject(); + if (jsonObject.has("content")) { + return jsonObject.get("content").getAsString(); + } + } catch (Exception e) { + android.util.Log.e("AiSync", "JSON解析失败,保留原样", e); + } + return rawResponse; + } } \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/tool/ai/AiReminderReceiver.java b/src/Notes-master/src/net/micode/notes/tool/ai/AiReminderReceiver.java index a813615..7232170 100644 --- a/src/Notes-master/src/net/micode/notes/tool/ai/AiReminderReceiver.java +++ b/src/Notes-master/src/net/micode/notes/tool/ai/AiReminderReceiver.java @@ -12,22 +12,35 @@ import net.micode.notes.sync.SyncWorker; public class AiReminderReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (AiReminderScheduler.ACTION_AI_REMINDER.equals(intent.getAction())) { + String action = intent.getAction(); + if (action == null) return; + + Log.d("AiAssistant", "Receiver caught action: " + action); + + Data.Builder dataBuilder = new Data.Builder(); + + // 分支 1:处理 Step 4 的精准日程提醒 + if (AiReminderScheduler.ACTION_AI_REMINDER.equals(action)) { String title = intent.getStringExtra("title"); Log.d("AiReminder", "Alarm trigger for: " + title); - // 唤醒 WorkManager 执行一次性提醒请求 - // 借用 SyncWorker 里的逻辑,或者单独建一个业务逻辑 - Data inputData = new Data.Builder() - .putInt(SyncWorker.KEY_SYNC_MODE, 3) // 我们定义一个新模式:MODE_REMINDER = 3 - .putString("reminder_title", title) - .build(); - - OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(SyncWorker.class) - .setInputData(inputData) - .build(); + dataBuilder.putInt(SyncWorker.KEY_SYNC_MODE, SyncWorker.MODE_REMINDER); + dataBuilder.putString("reminder_title", title); + } + // 分支 2:处理 Step 5 的主动关怀闲聊 + else if (AiCareScheduler.ACTION_AI_CARE.equals(action)) { + String careType = intent.getStringExtra("care_type"); + Log.d("AiCare", "Care alarm trigger! Type: " + careType); - WorkManager.getInstance(context).enqueue(request); + dataBuilder.putInt(SyncWorker.KEY_SYNC_MODE, SyncWorker.MODE_RANDOM_CHAT); + dataBuilder.putString("care_type", careType); } + + // 统一封装并启动 WorkManager + OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(SyncWorker.class) + .setInputData(dataBuilder.build()) + .build(); + + WorkManager.getInstance(context).enqueue(request); } } \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/AgendaFragment.java b/src/Notes-master/src/net/micode/notes/ui/AgendaFragment.java index a85da6e..e54f84f 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AgendaFragment.java +++ b/src/Notes-master/src/net/micode/notes/ui/AgendaFragment.java @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import net.micode.notes.R; import net.micode.notes.data.Notes; +import net.micode.notes.tool.ai.AiReminderScheduler; import net.micode.notes.data.Notes.NoteColumns; import java.util.ArrayList; import java.util.Calendar; @@ -151,6 +152,14 @@ public class AgendaFragment extends Fragment { cv.put(NoteColumns.LOCAL_MODIFIED, 1); getContext().getContentResolver().insert(Notes.CONTENT_NOTE_URI, cv); + // [新增] 核心补丁:让 AI 助理感知到新日程 + try { + AiReminderScheduler.updateAllReminders(getContext()); + android.util.Log.d("AiReminder", "Scheduled from Agenda Quick Add"); + } catch (Exception e) { + e.printStackTrace(); + } + // 重置 UI et.setText(""); tvQuickTime.setText("全天"); diff --git a/src/Notes-master/src/net/micode/notes/ui/ChatAdapter.java b/src/Notes-master/src/net/micode/notes/ui/ChatAdapter.java index 7882ace..50864b5 100644 --- a/src/Notes-master/src/net/micode/notes/ui/ChatAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/ChatAdapter.java @@ -14,6 +14,8 @@ import java.util.List; public class ChatAdapter extends RecyclerView.Adapter { private List mMessages; + private java.text.SimpleDateFormat timeFormat = new java.text.SimpleDateFormat("MM-dd HH:mm", java.util.Locale.CHINA); + public ChatAdapter(List messages) { this.mMessages = messages; } @@ -36,6 +38,18 @@ public class ChatAdapter extends RecyclerView.Adapter 0) { + ChatMessage preMsg = mMessages.get(position - 1); + if (msg.createdAt - preMsg.createdAt < 5 * 60 * 1000) { // 5分钟内 + holder.tvTime.setVisibility(View.GONE); + } + } + int viewType = getItemViewType(position); // 隐藏所有,根据类型显示 @@ -65,13 +79,14 @@ public class ChatAdapter extends RecyclerView.Adapter mMessages = new ArrayList<>(); private EditText mInput; + private android.database.ContentObserver mChatObserver; + private static final android.net.Uri CHAT_URI = android.net.Uri.parse("content://micode_notes/chat_messages"); + @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_chat, container, false); @@ -31,19 +34,188 @@ public class ChatFragment extends Fragment { mRecyclerView.setAdapter(mAdapter); view.findViewById(R.id.btn_chat_send).setOnClickListener(v -> { - String text = mInput.getText().toString().trim(); - if (!text.isEmpty()) { - // 1. 本地插入用户消息 - ChatMessage userMsg = new ChatMessage(0, 0, text); - mMessages.add(userMsg); - mAdapter.notifyItemInserted(mMessages.size() - 1); - mRecyclerView.smoothScrollToPosition(mMessages.size() - 1); - mInput.setText(""); - - // TODO: Step 3 将在此处调用 CozeClient + final String text = mInput.getText().toString().trim(); + if (text.isEmpty()) return; + + // ============================================================ + // 【手术点 1:修改标题为“正在输入...”】 + // ============================================================ + if (getActivity() instanceof androidx.appcompat.app.AppCompatActivity) { + androidx.appcompat.app.ActionBar actionBar = ((androidx.appcompat.app.AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle("对方正在输入中......"); + } } + + // --- 第一部分:将用户消息存入数据库 (保持原逻辑) --- + new Thread(() -> { + android.content.ContentValues userValues = new android.content.ContentValues(); + userValues.put(net.micode.notes.data.Notes.ChatColumns.SENDER_TYPE, 0); + userValues.put(net.micode.notes.data.Notes.ChatColumns.MSG_TYPE, 0); + userValues.put(net.micode.notes.data.Notes.ChatColumns.CONTENT, text); + userValues.put(net.micode.notes.data.Notes.ChatColumns.CREATED_AT, System.currentTimeMillis()); + getContext().getContentResolver().insert(CHAT_URI, userValues); + }).start(); + + mInput.setText(""); + + // --- 第二部分:向 AI 发起请求 --- + new Thread(() -> { + try { + com.google.gson.JsonObject json = new com.google.gson.JsonObject(); + json.addProperty("intent", "chat"); + json.addProperty("payload", text); + json.addProperty("current_time", net.micode.notes.tool.ai.AiDataSyncHelper.getCurrentTime()); + + net.micode.notes.tool.ai.CozeRequest request = new net.micode.notes.tool.ai.CozeRequest( + net.micode.notes.tool.ai.CozeClient.BOT_ID, "user_demo", json.toString()); + + retrofit2.Response response = + net.micode.notes.tool.ai.CozeClient.getInstance().chat( + net.micode.notes.tool.ai.CozeClient.getAuthToken(), request).execute(); + + if (response.isSuccessful() && response.body() != null && response.body().data != null) { + String chatId = response.body().data.id; + String convId = response.body().data.conversation_id; + + String status = ""; + int retries = 0; + while (!"completed".equals(status) && retries < 30) { + Thread.sleep(2000); + retrofit2.Response poll = + net.micode.notes.tool.ai.CozeClient.getInstance().retrieveChat( + net.micode.notes.tool.ai.CozeClient.getAuthToken(), chatId, convId).execute(); + + if (poll.isSuccessful() && poll.body() != null && poll.body().data != null) { + status = poll.body().data.status; + if ("failed".equals(status)) break; + } + retries++; + } + + if ("completed".equals(status)) { + retrofit2.Response msgList = + net.micode.notes.tool.ai.CozeClient.getInstance().getMessageList( + net.micode.notes.tool.ai.CozeClient.getAuthToken(), chatId, convId).execute(); + + if (msgList.isSuccessful() && msgList.body() != null) { + com.google.gson.JsonArray dataArray = msgList.body().getAsJsonArray("data"); + for (com.google.gson.JsonElement el : dataArray) { + com.google.gson.JsonObject m = el.getAsJsonObject(); + + if ("assistant".equals(m.get("role").getAsString()) && + "answer".equals(m.get("type").getAsString())) { + + String rawAnswer = m.get("content").getAsString(); + + // ============================================================ + // 【手术点 2:JSON 解析脱壳逻辑】 + // ============================================================ + String finalAnswer = rawAnswer; + if (rawAnswer != null && rawAnswer.trim().startsWith("{")) { + try { + com.google.gson.JsonObject jo = com.google.gson.JsonParser.parseString(rawAnswer).getAsJsonObject(); + if (jo.has("content")) { + finalAnswer = jo.get("content").getAsString(); + } + } catch (Exception e) { + // 解析失败则保持 rawAnswer 原样 + } + } + + // 存入数据库 + android.content.ContentValues aiValues = new android.content.ContentValues(); + aiValues.put(net.micode.notes.data.Notes.ChatColumns.SENDER_TYPE, 1); + aiValues.put(net.micode.notes.data.Notes.ChatColumns.MSG_TYPE, 0); + aiValues.put(net.micode.notes.data.Notes.ChatColumns.CONTENT, finalAnswer); + aiValues.put(net.micode.notes.data.Notes.ChatColumns.CREATED_AT, System.currentTimeMillis()); + + getContext().getContentResolver().insert(CHAT_URI, aiValues); + break; + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // ============================================================ + // 【手术点 3:请求结束,恢复标题为“AI 助理”】 + // ============================================================ + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + if (getActivity() instanceof androidx.appcompat.app.AppCompatActivity) { + androidx.appcompat.app.ActionBar actionBar = ((androidx.appcompat.app.AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle("AI 助理"); + } + } + }); + } + } + }).start(); }); + // 1. 初次加载历史记录 + loadHistory(); + + // 2. 注册观察者:监听数据库变化 + mChatObserver = new android.database.ContentObserver(new android.os.Handler()) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + android.util.Log.d("ChatFragment", "Database changed, reloading..."); + loadHistory(); // 数据库一变就重新加载 + } + }; + getContext().getContentResolver().registerContentObserver(CHAT_URI, true, mChatObserver); + return view; } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mChatObserver != null) { + getContext().getContentResolver().unregisterContentObserver(mChatObserver); + } + } + + private void loadHistory() { + new Thread(() -> { + android.database.Cursor cursor = getContext().getContentResolver().query( + CHAT_URI, + null, null, null, "created_at ASC" // 按时间顺序排列 + ); + + List history = new ArrayList<>(); + if (cursor != null) { + while (cursor.moveToNext()) { + // 根据 Step 1 定义的列顺序取值 + int senderType = cursor.getInt(cursor.getColumnIndex(net.micode.notes.data.Notes.ChatColumns.SENDER_TYPE)); + int msgType = cursor.getInt(cursor.getColumnIndex(net.micode.notes.data.Notes.ChatColumns.MSG_TYPE)); + String content = cursor.getString(cursor.getColumnIndex(net.micode.notes.data.Notes.ChatColumns.CONTENT)); + long time = cursor.getLong(cursor.getColumnIndex(net.micode.notes.data.Notes.ChatColumns.CREATED_AT)); + + ChatMessage msg = new ChatMessage(senderType, msgType, content); + msg.createdAt = time; + history.add(msg); + } + cursor.close(); + } + + // 回到主线程更新 UI + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + mMessages.clear(); + mMessages.addAll(history); + mAdapter.notifyDataSetChanged(); + if (mMessages.size() > 0) { + mRecyclerView.scrollToPosition(mMessages.size() - 1); + } + }); + } + }).start(); + } } \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java index ec0ee41..3dc8567 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -78,6 +78,7 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser.TextAppearanceResources; import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; +import net.micode.notes.tool.ai.AiReminderScheduler; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; @@ -1612,6 +1613,15 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen saveImages(); setResult(RESULT_OK); + + // [新增] 刷新 AI 助理的闹钟计划 + try { + // 每次保存成功,都让调度器重新扫描一遍所有日程,更新系统闹钟 + AiReminderScheduler.updateAllReminders(this); + android.util.Log.d("AiAssistant", "Reminders scheduled successfully after save."); + } catch (Exception e) { + android.util.Log.e("AiAssistant", "Failed to schedule reminders", e); + } } return saved; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index 9928b28..7de394c 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -79,6 +79,7 @@ import net.micode.notes.tool.BackupUtils; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser; // import net.micode.notes.ui.NotesListItemAdapter.AppWidgetAttribute; +import net.micode.notes.tool.ai.AiCareScheduler; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; @@ -163,6 +164,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe * Insert an introduction when user firstly use this application */ setAppInfoFromRawRes(); + + AiCareScheduler.scheduleDailyCare(this); } @Override @@ -1442,19 +1445,21 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } }); break; - // [新增] 处理 Push case R.id.menu_push: - Toast.makeText(this, "正在上传数据到云端...", Toast.LENGTH_SHORT).show(); + // 手术点:更新提示语 + Toast.makeText(this, "正在备份数据并同步 AI 大脑...", Toast.LENGTH_SHORT).show(); - // 构造参数:MODE_PUSH + // [修复] 去掉前面的 androidx.work.Data,直接赋值 inputData = new androidx.work.Data.Builder() .putInt(net.micode.notes.sync.SyncWorker.KEY_SYNC_MODE, net.micode.notes.sync.SyncWorker.MODE_PUSH) .build(); + // [修复] 去掉前面的 androidx.work.OneTimeWorkRequest,直接赋值 syncRequest = new androidx.work.OneTimeWorkRequest.Builder(net.micode.notes.sync.SyncWorker.class) .setInputData(inputData) .build(); + // 提交任务 androidx.work.WorkManager.getInstance(this).enqueue(syncRequest); break; case R.id.menu_setting: { -- 2.34.1