diff --git a/src/frontend/client/src/components/Chat/AiMessageBubble.tsx b/src/frontend/client/src/components/Chat/AiMessageBubble.tsx index 185fe6de6..222e8f4ae 100644 --- a/src/frontend/client/src/components/Chat/AiMessageBubble.tsx +++ b/src/frontend/client/src/components/Chat/AiMessageBubble.tsx @@ -118,6 +118,7 @@ function UploadedFileList({ files, conversationId }: { files: any[]; conversatio conversationId={conversationId} fileId={file.file_id} altText={file.name || file.file_name} + initialUrl={file.filepath || file.file_path || file.file_url} /> ))} diff --git a/src/frontend/client/src/components/Chat/Messages/Content/MessageImage.tsx b/src/frontend/client/src/components/Chat/Messages/Content/MessageImage.tsx index d670a4f1a..61bbf042d 100644 --- a/src/frontend/client/src/components/Chat/Messages/Content/MessageImage.tsx +++ b/src/frontend/client/src/components/Chat/Messages/Content/MessageImage.tsx @@ -17,37 +17,51 @@ export function MessageImage({ conversationId, fileId, altText, + initialUrl, }: { conversationId?: string; fileId?: string; altText?: string; + /** Link the client already holds from the upload, if any. */ + initialUrl?: string; }) { - const [url, setUrl] = useState(null); + // Stored links carry the internal storage host, which the browser can't + // reach — same swap the download card does. + const toReachable = (u?: string | null) => + u ? u.replace(/https?:\/\/[^/]+/, __APP_ENV__.BASE_URL) : null; + + const [url, setUrl] = useState(toReachable(initialUrl)); const [failed, setFailed] = useState(false); useEffect(() => { let cancelled = false; - // A just-sent message reaches here before the backend has assigned the - // conversation its id. That's "not yet", not "gone" — keep waiting, or the - // placeholder flashes up on every image the user sends. + // Nothing to ask for, and nothing to show either. if (!conversationId || !fileId) { + if (!initialUrl) { + return; + } + setUrl(toReachable(initialUrl)); return; } setFailed(false); + // The upload link renders straight away — a message the user just sent + // isn't in the database yet, so asking the backend for a link would come + // back empty and read as a dead image. The re-issued link replaces it when + // it arrives, which is what makes an old conversation work. getAttachmentUrl(conversationId, fileId).then((fresh) => { if (cancelled) { return; } if (fresh) { - setUrl(fresh); - } else { + setUrl(toReachable(fresh)); + } else if (!initialUrl) { setFailed(true); } }); return () => { cancelled = true; }; - }, [conversationId, fileId]); + }, [conversationId, fileId, initialUrl]); // Debug aid: the resolved link and the ids it was resolved from are on the // wrapper, so a broken picture can be traced from devtools without digging diff --git a/src/frontend/client/src/pages/appChat/components/MessageUser.tsx b/src/frontend/client/src/pages/appChat/components/MessageUser.tsx index b94573b66..fd82b02e7 100644 --- a/src/frontend/client/src/pages/appChat/components/MessageUser.tsx +++ b/src/frontend/client/src/pages/appChat/components/MessageUser.tsx @@ -100,6 +100,7 @@ export default function MessageUser({ useName, data, showButton, disabledSearch conversationId={data.chat_id || chatId} fileId={file.file_id} altText={file.file_name || file.name} + initialUrl={file.file_url || file.filepath} /> ))}