From f4509d9f4396469b71926e14d95333cfa58e0707 Mon Sep 17 00:00:00 2001 From: ducnmm Date: Wed, 25 Mar 2026 11:31:20 +0700 Subject: [PATCH 1/4] fix: sync researcher lockfile specifier (workspace:*) --- apps/researcher/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/researcher/package.json b/apps/researcher/package.json index 0217aa35..72f75325 100644 --- a/apps/researcher/package.json +++ b/apps/researcher/package.json @@ -24,7 +24,7 @@ "@ai-sdk/openai": "^3.0.41", "@ai-sdk/provider": "^3.0.3", "@ai-sdk/react": "3.0.39", - "@mysten-incubation/memwal": "^0.0.1-dev.0", + "@mysten-incubation/memwal": "workspace:*", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", "@codemirror/state": "^6.5.0", From b6ddd6269abe3a723737abd381a1fb0e8179c6c4 Mon Sep 17 00:00:00 2001 From: ducnmm Date: Thu, 26 Mar 2026 01:41:20 +0700 Subject: [PATCH 2/4] feat(chatbot): add saveMemory AI tool for explicit memory saving --- apps/chatbot/app/(chat)/api/chat/route.ts | 3 ++ apps/chatbot/lib/ai/prompts.ts | 7 +++- apps/chatbot/lib/ai/tools/save-memory.ts | 48 +++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 apps/chatbot/lib/ai/tools/save-memory.ts diff --git a/apps/chatbot/app/(chat)/api/chat/route.ts b/apps/chatbot/app/(chat)/api/chat/route.ts index b5115664..c4f6a14b 100644 --- a/apps/chatbot/app/(chat)/api/chat/route.ts +++ b/apps/chatbot/app/(chat)/api/chat/route.ts @@ -21,6 +21,7 @@ import { getLanguageModel, getMemWalModel } from "@/lib/ai/providers"; import { createDocument } from "@/lib/ai/tools/create-document"; import { getWeather } from "@/lib/ai/tools/get-weather"; import { requestSuggestions } from "@/lib/ai/tools/request-suggestions"; +import { saveMemory } from "@/lib/ai/tools/save-memory"; import { updateDocument } from "@/lib/ai/tools/update-document"; import { isProductionEnvironment } from "@/lib/constants"; import { @@ -170,6 +171,7 @@ export async function POST(request: Request) { "createDocument", "updateDocument", "requestSuggestions", + "saveMemory", ], providerOptions: isReasoningModel ? { @@ -183,6 +185,7 @@ export async function POST(request: Request) { createDocument: createDocument({ session, dataStream }), updateDocument: updateDocument({ session, dataStream }), requestSuggestions: requestSuggestions({ session, dataStream }), + saveMemory: saveMemory({ memwalKey, memwalAccountId }), }, experimental_telemetry: { isEnabled: isProductionEnvironment, diff --git a/apps/chatbot/lib/ai/prompts.ts b/apps/chatbot/lib/ai/prompts.ts index a68e59c3..a6c67f96 100644 --- a/apps/chatbot/lib/ai/prompts.ts +++ b/apps/chatbot/lib/ai/prompts.ts @@ -39,7 +39,12 @@ Do not update document right after creating it. Wait for user feedback or reques export const regularPrompt = `You are a friendly assistant! Keep your responses concise and helpful. -When asked to write, create, or help with something, just do it directly. Don't ask clarifying questions unless absolutely necessary - make reasonable assumptions and proceed with the task.`; +When asked to write, create, or help with something, just do it directly. Don't ask clarifying questions unless absolutely necessary - make reasonable assumptions and proceed with the task. + +You have access to the user's personal memory system powered by MemWal. Memories are automatically recalled and injected as context during conversations. + +Memory Tool: +- saveMemory({text}) - Save information to the user's personal memory on the blockchain. ONLY call this when the user EXPLICITLY asks to save or remember something. Do NOT call it proactively.`; export type RequestHints = { latitude: Geo["latitude"]; diff --git a/apps/chatbot/lib/ai/tools/save-memory.ts b/apps/chatbot/lib/ai/tools/save-memory.ts new file mode 100644 index 00000000..3cc728a6 --- /dev/null +++ b/apps/chatbot/lib/ai/tools/save-memory.ts @@ -0,0 +1,48 @@ +import { tool } from "ai"; +import { z } from "zod"; +import { MemWal } from "@mysten-incubation/memwal"; + +export const saveMemory = ({ + memwalKey, + memwalAccountId, +}: { + memwalKey?: string; + memwalAccountId?: string; +}) => + tool({ + description: + "Save information to the user's personal memory on the blockchain. ONLY use this tool when the user EXPLICITLY asks you to save or remember something (e.g., 'remember this', 'save this', 'lưu lại', 'nhớ giùm'). Do NOT use this tool proactively. Save the FULL, DETAILED content — do not summarize or shorten it.", + inputSchema: z.object({ + text: z + .string() + .describe( + "The full, detailed text to save to memory. Include all relevant details — do not summarize." + ), + }), + execute: async ({ text }) => { + const key = memwalKey || process.env.MEMWAL_KEY; + const accountId = memwalAccountId || process.env.MEMWAL_ACCOUNT_ID; + const serverUrl = process.env.MEMWAL_SERVER_URL || "http://localhost:8000"; + + if (!key || !accountId) { + return { + saved: false, + text, + error: "MemWal not configured — MEMWAL_KEY or MEMWAL_ACCOUNT_ID missing", + }; + } + + try { + const memwal = MemWal.create({ key, accountId, serverUrl }); + await memwal.remember(text); + return { saved: true, text }; + } catch (error) { + console.error("[Tool] saveMemory error:", error); + return { + saved: false, + text, + error: error instanceof Error ? error.message : "Failed to save memory", + }; + } + }, + }); From 101e99944219003e3f880906be22d705c10db547 Mon Sep 17 00:00:00 2001 From: ducnmm Date: Thu, 26 Mar 2026 01:52:00 +0700 Subject: [PATCH 3/4] fix(researcher): revert memwal dep to npm version --- apps/researcher/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/researcher/package.json b/apps/researcher/package.json index 72f75325..0217aa35 100644 --- a/apps/researcher/package.json +++ b/apps/researcher/package.json @@ -24,7 +24,7 @@ "@ai-sdk/openai": "^3.0.41", "@ai-sdk/provider": "^3.0.3", "@ai-sdk/react": "3.0.39", - "@mysten-incubation/memwal": "workspace:*", + "@mysten-incubation/memwal": "^0.0.1-dev.0", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", "@codemirror/state": "^6.5.0", From 94870602438f968895fc61db8f4bbd881eb3a411 Mon Sep 17 00:00:00 2001 From: ducnmm Date: Thu, 26 Mar 2026 09:18:07 +0700 Subject: [PATCH 4/4] feat(chatbot): add loading indicator + increase rate limit to 30/hr --- apps/chatbot/components/message.tsx | 22 +++++++++++++++++++++- apps/chatbot/components/messages.tsx | 15 +++++++++------ apps/chatbot/lib/ai/entitlements.ts | 4 ++-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/chatbot/components/message.tsx b/apps/chatbot/components/message.tsx index bc6d268b..eb7ddfa8 100644 --- a/apps/chatbot/components/message.tsx +++ b/apps/chatbot/components/message.tsx @@ -66,7 +66,9 @@ const PurePreviewMessage = ({ > {message.role === "assistant" && (
- +
(p.type === "text" && p.text?.trim()) || ["tool-getWeather", "tool-createDocument", "tool-updateDocument", "tool-requestSuggestions"].includes(p.type)) ? "animate-pulse" : ""}> + +
)} @@ -104,6 +106,24 @@ const PurePreviewMessage = ({ )} + {/* Show "Thinking..." when assistant message is loading with no content */} + {message.role === "assistant" && + isLoading && + !message.parts?.some( + (p) => + (p.type === "text" && p.text?.trim()) || + ["tool-getWeather", "tool-createDocument", "tool-updateDocument", "tool-requestSuggestions"].includes(p.type) + ) && ( +
+ Thinking + + . + . + . + +
+ )} + {message.parts?.map((part, index) => { const { type } = part; const key = `message-${message.id}-part-${index}`; diff --git a/apps/chatbot/components/messages.tsx b/apps/chatbot/components/messages.tsx index c9581adb..9fb344eb 100644 --- a/apps/chatbot/components/messages.tsx +++ b/apps/chatbot/components/messages.tsx @@ -75,12 +75,15 @@ function PureMessages({ /> ))} - {status === "submitted" && - !messages.some((msg) => - msg.parts?.some( - (part) => "state" in part && part.state === "approval-responded" - ) - ) && } + {(status === "submitted" || status === "streaming") && + (() => { + const lastMsg = messages[messages.length - 1]; + // Show ThinkingMessage when: + // 1. Status is "submitted" (request sent, no response yet) AND last message is from user + // 2. Status is "streaming" but no assistant message exists yet + const lastIsUser = !lastMsg || lastMsg.role === "user"; + return lastIsUser; + })() && }
= { * For users without an account */ guest: { - maxMessagesPerHour: 10, + maxMessagesPerHour: 30, }, /* * For users with an account */ regular: { - maxMessagesPerHour: 10, + maxMessagesPerHour: 30, }, /*