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/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, }, /* 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", + }; + } + }, + });