From 40a78ea0fc72bd9c9774967dbf1983630f83b164 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Fri, 27 Feb 2026 11:51:31 +0800 Subject: [PATCH] fix: image gen result persistence with fallback for temp message IDs, bump v0.17.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: assistant messages use temporary IDs (temp-assistant-{timestamp}) in the frontend, which don't match real DB message IDs. The PUT update by message_id always found 0 rows, so results were never persisted. Fix — fallback search by session + prompt: - db.ts: updateMessageContent now returns changes count; new updateMessageBySessionAndHint() finds the latest assistant message containing image-gen-request with matching prompt text and updates it - /api/chat/messages PUT: tries message_id first, falls back to session_id + prompt_hint search; returns 404 if neither finds a match; response includes updated_message_id and fallback_used for debugging - ImageGenConfirmation: always sends session_id and prompt_hint in PUT request, regardless of whether messageId is a real or temp ID Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 4 +- package.json | 2 +- src/app/api/chat/messages/route.ts | 42 +++++++++++++++++--- src/components/chat/ImageGenConfirmation.tsx | 6 ++- src/lib/db.ts | 27 ++++++++++++- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72f3c79..509eb9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codepilot", - "version": "0.17.2", + "version": "0.17.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codepilot", - "version": "0.17.2", + "version": "0.17.3", "dependencies": { "@ai-sdk/anthropic": "^3.0.47", "@ai-sdk/google": "^3.0.31", diff --git a/package.json b/package.json index 8695091..9d49cce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codepilot", - "version": "0.17.2", + "version": "0.17.3", "private": true, "author": { "name": "op7418", diff --git a/src/app/api/chat/messages/route.ts b/src/app/api/chat/messages/route.ts index 7057c77..acca080 100644 --- a/src/app/api/chat/messages/route.ts +++ b/src/app/api/chat/messages/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { addMessage, updateMessageContent, getSession } from '@/lib/db'; +import { addMessage, updateMessageContent, updateMessageBySessionAndHint, getSession } from '@/lib/db'; export const runtime = 'nodejs'; @@ -42,24 +42,54 @@ export async function POST(request: NextRequest) { * PUT /api/chat/messages * Update the content of an existing message. * Used to replace image-gen-request with image-gen-result after generation. + * + * Tries message_id first. If no rows updated (temp ID), falls back to + * searching by session_id + prompt_hint for the real DB message. */ export async function PUT(request: NextRequest) { try { const body = await request.json(); - const { message_id, content } = body as { + const { message_id, content, session_id, prompt_hint } = body as { message_id: string; content: string; + session_id?: string; + prompt_hint?: string; }; - if (!message_id || !content) { + if (!content) { return NextResponse.json( - { error: 'message_id and content are required' }, + { error: 'content is required' }, { status: 400 }, ); } - updateMessageContent(message_id, content); - return NextResponse.json({ ok: true }); + // Try direct update by message_id + let changes = 0; + let updatedMessageId = message_id; + let fallbackUsed = false; + + if (message_id) { + changes = updateMessageContent(message_id, content); + } + + // Fallback: search by session + prompt hint + if (changes === 0 && session_id && prompt_hint) { + const result = updateMessageBySessionAndHint(session_id, prompt_hint, content); + changes = result.changes; + if (result.messageId) { + updatedMessageId = result.messageId; + fallbackUsed = true; + } + } + + if (changes === 0) { + return NextResponse.json( + { error: 'Message not found' }, + { status: 404 }, + ); + } + + return NextResponse.json({ ok: true, updated_message_id: updatedMessageId, fallback_used: fallbackUsed }); } catch (error) { const msg = error instanceof Error ? error.message : 'Failed to update message'; return NextResponse.json({ error: msg }, { status: 500 }); diff --git a/src/components/chat/ImageGenConfirmation.tsx b/src/components/chat/ImageGenConfirmation.tsx index ebeb9e9..be59efe 100644 --- a/src/components/chat/ImageGenConfirmation.tsx +++ b/src/components/chat/ImageGenConfirmation.tsx @@ -143,7 +143,7 @@ export function ImageGenConfirmation({ } // Persist result to DB by replacing image-gen-request with image-gen-result - if (messageId) { + { const resultBlock = JSON.stringify({ status: 'completed', prompt, @@ -158,8 +158,10 @@ export function ImageGenConfirmation({ method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - message_id: messageId, + message_id: messageId || '', content: '```image-gen-result\n' + resultBlock + '\n```', + session_id: sessionId, + prompt_hint: initialPrompt, }), }).catch(() => {}); } diff --git a/src/lib/db.ts b/src/lib/db.ts index a5e3f13..dd0b1d6 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -565,9 +565,32 @@ export function addMessage( return db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as Message; } -export function updateMessageContent(messageId: string, content: string): void { +export function updateMessageContent(messageId: string, content: string): number { const db = getDb(); - db.prepare('UPDATE messages SET content = ? WHERE id = ?').run(content, messageId); + const result = db.prepare('UPDATE messages SET content = ? WHERE id = ?').run(content, messageId); + return result.changes; +} + +/** + * Find the most recent assistant message in a session that contains a given text snippet, + * update its content, and return the real message ID. Used as fallback when the frontend + * only has a temporary message ID. + */ +export function updateMessageBySessionAndHint( + sessionId: string, + promptHint: string, + content: string, +): { changes: number; messageId?: string } { + const db = getDb(); + // Find the latest assistant message containing the prompt hint within an image-gen-request block + const row = db.prepare( + "SELECT id FROM messages WHERE session_id = ? AND role = 'assistant' AND content LIKE '%image-gen-request%' AND content LIKE ? ORDER BY created_at DESC LIMIT 1" + ).get(sessionId, `%${promptHint.slice(0, 60)}%`) as { id: string } | undefined; + + if (!row) return { changes: 0 }; + + const result = db.prepare('UPDATE messages SET content = ? WHERE id = ?').run(content, row.id); + return { changes: result.changes, messageId: row.id }; } export function clearSessionMessages(sessionId: string): void {