diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts
index c3be7a608..5b901905b 100644
--- a/app/(chat)/api/chat/route.ts
+++ b/app/(chat)/api/chat/route.ts
@@ -250,6 +250,7 @@ import {
pruneTaskBoundaryContext,
pruneTaskBoundaryHistoryMessages,
} from "@/lib/ai/runtime/task-boundary-context-pruner";
+import { buildRecentDialogueMemory } from "@/lib/ai/runtime/recent-dialogue-memory";
import {
resolveAgentModelSlotDescriptor,
upsertAgentModelSlot,
@@ -1085,6 +1086,10 @@ export async function POST(request: Request) {
let confirmedPromptForConversationState: string | undefined;
let smartAutoHistoryMessagesFromDb: DBMessage[] = [];
let smartAutoMediaHistoryMessagesFromDb: DBMessage[] = [];
+ let recentDialogueMemory = buildRecentDialogueMemory({
+ messages: [],
+ currentText: requestTextContent,
+ });
let preferredAgentModelId: string | undefined;
let configuredAgentConversationModelId: string | undefined;
let agentModelSlots: AgentModelSlots = {};
@@ -1489,6 +1494,17 @@ export async function POST(request: Request) {
taskType: taskBoundaryDecision.taskType,
taskMode: taskBoundaryDecision.taskMode,
});
+ recentDialogueMemory = buildRecentDialogueMemory({
+ messages: loadedHistoryMessages,
+ currentText: smartAutoInputText,
+ maxEntries: taskBoundaryDecision.taskType === "chat" ? 6 : 4,
+ maxTotalChars: taskBoundaryDecision.taskType === "chat" ? 1000 : 700,
+ });
+ chatApiLogger.info("[RecentDialogueMemory] Smart auto recall:", {
+ entries: recentDialogueMemory.entries.length,
+ isRecallRequest: recentDialogueMemory.isRecallRequest,
+ taskType: taskBoundaryDecision.taskType,
+ });
autoTextForFallback = smartAutoInputText;
if (!requestWorkingState && !shouldIsolateDirectComposerContext) {
@@ -1563,23 +1579,12 @@ export async function POST(request: Request) {
resolutionConfidence: requestTargetResolution.confidence,
});
- // 🆕 从历史消息中提取对话文本(用于上下文理解)
- const historyMessages: { role: "user" | "assistant"; content: string }[] = [];
- for (const msg of smartAutoHistoryMessagesFromDb.slice(-10)) { // 最多取最近10条
- const parts = msg.parts as any[];
- if (parts && parts.length > 0) {
- // 提取文本内容
- const textParts = parts.filter((p: any) => p.type === 'text');
- const content = textParts.map((p: any) => p.text || '').join(' ').trim();
- if (content) {
- historyMessages.push({
- role: msg.role as "user" | "assistant",
- content: content.substring(0, 300), // 限制长度
- });
- }
- }
- }
- chatApiLogger.info("[Smart Auto Mode] History context:", historyMessages.length, "messages");
+ const historyMessages: { role: "user" | "assistant"; content: string }[] =
+ recentDialogueMemory.entries.map((entry) => ({
+ role: entry.role,
+ content: entry.content,
+ }));
+ chatApiLogger.info("[Smart Auto Mode] History context:", historyMessages.length, "memory entries");
// 2. 使用 LangGraph 驱动的自动模式工作流
const { runAutoAgentGraph } = await import("@/lib/ai/auto-agent-graph");
@@ -1612,8 +1617,15 @@ export async function POST(request: Request) {
workflowState: composedConversationState.threadState.workflowState,
memoryHints: composedConversationState.threadState.memoryHints,
contextSummary: composedConversationState.threadState.contextSummary,
+ recentDialogueMemory: recentDialogueMemory.contextSummary,
},
});
+ const agentGraphContext = [
+ composedConversationState.composedConversationMemory.contextSummary,
+ recentDialogueMemory.contextSummary,
+ ]
+ .filter(Boolean)
+ .join("\n");
const autoGraphResult = await withOperationTimeout(
runAutoAgentGraph({
text: smartAutoInputText,
@@ -1641,10 +1653,7 @@ export async function POST(request: Request) {
requestResolvedMediaInput.historyUserImageUrls.length,
currentUploadImageCount: requestResolvedMediaInput.currentMessageImageUrls.length,
historyMessages,
- context:
- composedConversationState.composedConversationMemory.contextSummary ||
- effectiveAutoContextText ||
- undefined,
+ context: agentGraphContext || effectiveAutoContextText || undefined,
preferredModelId: preferredAgentModelId,
threadState: composedConversationState.threadState,
}),
@@ -2747,6 +2756,21 @@ export async function POST(request: Request) {
purpose: "media",
hasCurrentImageInput: hasImageInputForConversationAction,
});
+ recentDialogueMemory = shouldIsolateDirectComposerContext
+ ? recentDialogueMemory
+ : buildRecentDialogueMemory({
+ messages: loadedMessagesForBoundary,
+ currentText: requestTextContent,
+ maxEntries: taskBoundaryDecision.taskType === "chat" ? 6 : 4,
+ maxTotalChars: taskBoundaryDecision.taskType === "chat" ? 1000 : 700,
+ });
+ if (recentDialogueMemory.entries.length > 0) {
+ chatApiLogger.info("[RecentDialogueMemory] Request recall:", {
+ entries: recentDialogueMemory.entries.length,
+ isRecallRequest: recentDialogueMemory.isRecallRequest,
+ taskType: taskBoundaryDecision.taskType,
+ });
+ }
if (!requestWorkingState && !shouldIsolateDirectComposerContext) {
requestWorkingState = await getConversationWorkingStateByChatId({
@@ -8591,16 +8615,33 @@ export async function POST(request: Request) {
resolvedRuntimeModelLookupId = actualModelId;
}
- const effectiveConversationSystemPrompt = shouldRunAgentConversation
- ? `${await buildAgentRoleIdentityInstruction({
+ const recentDialogueSystemContext = recentDialogueMemory.contextSummary
+ ? [
+ recentDialogueMemory.contextSummary,
+ ...recentDialogueMemory.memoryHints,
+ ].join("\n")
+ : "";
+ const baseConversationSystemPrompt = systemPrompt({
+ selectedChatModel: finalModelId,
+ requestHints,
+ });
+ const agentIdentityInstruction = shouldRunAgentConversation
+ ? await buildAgentRoleIdentityInstruction({
allowVideoGeneration: allowAgentVideoGeneration,
hasCanvasContext: Boolean(
effectiveContextPayload?.source === "canvas" ||
effectiveContextPayload?.selectedNodeIds?.length ||
effectiveContextPayload?.canvasImageNodes?.length,
),
- })}\n\n${systemPrompt({ selectedChatModel: finalModelId, requestHints })}`
- : systemPrompt({ selectedChatModel: finalModelId, requestHints });
+ })
+ : "";
+ const effectiveConversationSystemPrompt = [
+ agentIdentityInstruction,
+ recentDialogueSystemContext,
+ baseConversationSystemPrompt,
+ ]
+ .filter(Boolean)
+ .join("\n\n");
const activeRuntimeModelDescriptor = await resolveAgentModelSlotDescriptor(
resolvedRuntimeModelLookupId,
diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx
index dcc86638f..939931879 100644
--- a/components/canvas/CanvasChatPanel.tsx
+++ b/components/canvas/CanvasChatPanel.tsx
@@ -1636,130 +1636,6 @@ function CanvasAssistantTypingPlaceholder({ hint }: { hint?: string | null }) {
);
}
-function CanvasAgentProgressTimeline({
- progress,
-}: {
- progress?: AgentProgressStreamPayload | null;
-}) {
- const steps = buildAgentProgressNodeSteps(progress);
- const title = progress?.title?.trim() || getAgentProgressFallbackTitle(progress);
- const isFailed = progress?.stage === "failed";
- const isCompleted = progress?.stage === "completed";
- const isClarify = progress?.stage === "clarify";
-
- return (
-
-
-
-
- {isFailed ? (
-
- ) : isCompleted ? (
-
- ) : (
-
- )}
-
-
- {title}
-
-
-
-
-
- {steps.map((step, index) => {
- const isStepCompleted = step.state === "completed";
- const isStepCurrent = step.state === "current";
- const isStepFailed = step.state === "failed";
- const lineIsActive =
- isStepCompleted || isStepCurrent || isStepFailed;
-
- return (
-
-
-
- {isStepCompleted ? (
-
- ) : isStepFailed ? (
-
- ) : isStepCurrent ? (
-
- ) : (
-
- )}
- {isStepCurrent ? (
-
- ) : null}
-
-
- {step.label}
-
-
- {index < steps.length - 1 ? (
-
- ) : null}
-
- );
- })}
-
-
-
-
- );
-}
-
function CanvasComposerUploadProgressDialog({
progress,
}: {
@@ -13672,10 +13548,6 @@ export function CanvasChatPanel({
) : null}
- {isLoading && !shouldRenderImageLoading && (
-
- )}
-
diff --git a/lib/ai/runtime/recent-dialogue-memory.ts b/lib/ai/runtime/recent-dialogue-memory.ts
new file mode 100644
index 000000000..fa8dbd113
--- /dev/null
+++ b/lib/ai/runtime/recent-dialogue-memory.ts
@@ -0,0 +1,146 @@
+import type { DBMessage } from "@/lib/db/schema";
+
+export interface RecentDialogueMemoryEntry {
+ role: "user" | "assistant";
+ content: string;
+ messageId?: string;
+}
+
+export interface RecentDialogueMemory {
+ entries: RecentDialogueMemoryEntry[];
+ contextSummary: string;
+ memoryHints: string[];
+ isRecallRequest: boolean;
+}
+
+const USER_RECALL_PATTERN =
+ /(?:我|用户)?(?:刚才|刚刚|前面|之前|上面|上一(?:句|条|轮)|最近).{0,12}(?:说|发|问|讲|提|输入|发送).{0,12}(?:什么|啥|哪句|哪些|内容)|(?:我说了什么|我刚才说啥|我前面说啥|我之前说啥|我发了什么|我问了什么|记得我.{0,12}说|没有记忆)/i;
+
+const ASSISTANT_RECALL_PATTERN =
+ /(?:你|助手|ai|AI).{0,12}(?:刚才|刚刚|前面|之前|上面|上一(?:句|条|轮)|最近).{0,12}(?:说|回复|回答|讲).{0,12}(?:什么|啥|哪句|哪些|内容)/i;
+
+function normalizeWhitespace(value: string) {
+ return value.replace(/\s+/g, " ").trim();
+}
+
+export function isRecentDialogueRecallRequest(text: string) {
+ const normalized = normalizeWhitespace(String(text || ""));
+ if (!normalized) return false;
+ return USER_RECALL_PATTERN.test(normalized) || ASSISTANT_RECALL_PATTERN.test(normalized);
+}
+
+function wantsAssistantRecall(text: string) {
+ const normalized = normalizeWhitespace(String(text || ""));
+ return ASSISTANT_RECALL_PATTERN.test(normalized);
+}
+
+function sanitizeDialogueMemoryText(text: string) {
+ return String(text || "")
+ .replace(//g, "")
+ .replace(/!\[[^\]]*]\([^)]+\)/g, "")
+ .replace(/\[[^\]]*]\((?:https?:\/\/|\/)[^)]+\)/g, "")
+ .replace(/