AI提醒接收器改进,修改AiReminderReceiver.java,同步工作器改进,修改SyncWorker.java #41

Merged
pvexk5qol merged 1 commits from caoweiqiong_branch into master 4 weeks ago

@ -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";

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

@ -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<CozeResponse> 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<CozeResponse> 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<com.google.gson.JsonObject> 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<CozeResponse> 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<CozeResponse> 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<com.google.gson.JsonObject> 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<CozeResponse> 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<CozeResponse> 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<com.google.gson.JsonObject> 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;
}
}

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

@ -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("全天");

@ -14,6 +14,8 @@ import java.util.List;
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder> {
private List<ChatMessage> mMessages;
private java.text.SimpleDateFormat timeFormat = new java.text.SimpleDateFormat("MM-dd HH:mm", java.util.Locale.CHINA);
public ChatAdapter(List<ChatMessage> messages) {
this.mMessages = messages;
}
@ -36,6 +38,18 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
@Override
public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) {
ChatMessage msg = mMessages.get(position);
// 【新增手术:处理时间显示】
holder.tvTime.setVisibility(View.VISIBLE);
holder.tvTime.setText(timeFormat.format(new java.util.Date(msg.createdAt)));
// 如果想要更像微信(时间太近就不显示),可以加这个逻辑:
if (position > 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<ChatAdapter.ChatViewHolder
static class ChatViewHolder extends RecyclerView.ViewHolder {
View leftLayout, rightLayout;
TextView tvLeft, tvRight;
TextView tvLeft, tvRight, tvTime; // 新增 tvTime
ChatViewHolder(View v) {
super(v);
leftLayout = v.findViewById(R.id.ll_left_layout);
rightLayout = v.findViewById(R.id.ll_right_layout);
tvLeft = v.findViewById(R.id.tv_msg_left);
tvRight = v.findViewById(R.id.tv_msg_right);
tvTime = v.findViewById(R.id.tv_chat_time); // 绑定
}
}
}

@ -20,6 +20,9 @@ public class ChatFragment extends Fragment {
private List<ChatMessage> 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<net.micode.notes.tool.ai.CozeResponse> 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<net.micode.notes.tool.ai.CozeResponse> 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<com.google.gson.JsonObject> 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();
// ============================================================
// 【手术点 2JSON 解析脱壳逻辑】
// ============================================================
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<ChatMessage> 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();
}
}

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

@ -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: {

Loading…
Cancel
Save