diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 12efc21ab1..40ec12dd9e 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1259,7 +1259,7 @@ async def chat_completion( target_model_id = entry['model_id'] assistant_message_id = entry['message_id'] if assistant_message_id: - history_messages[assistant_message_id] = { + assistant_message = { 'id': assistant_message_id, 'parentId': user_message_id, 'childrenIds': [], @@ -1269,6 +1269,11 @@ async def chat_completion( 'model': target_model_id, 'timestamp': int(time.time()), } + # Preserve the side-by-side column index so duplicate + # models don't collapse into one another on reload. + if entry.get('modelIdx') is not None: + assistant_message['modelIdx'] = entry['modelIdx'] + history_messages[assistant_message_id] = assistant_message await Chats.insert_new_chat( chat_id, @@ -1481,19 +1486,24 @@ async def chat_completion( target_model_id = entry['model_id'] assistant_message_id = entry['message_id'] if assistant_message_id: + assistant_message = { + 'id': assistant_message_id, + 'parentId': user_message_id, + 'childrenIds': [], + 'role': 'assistant', + 'content': '', + 'done': False, + 'model': target_model_id, + 'timestamp': int(time.time()), + } + # Preserve the side-by-side column index so duplicate + # models don't collapse into one another on reload. + if entry.get('modelIdx') is not None: + assistant_message['modelIdx'] = entry['modelIdx'] await Chats.upsert_message_to_chat_by_id_and_message_id( chat_id, assistant_message_id, - { - 'id': assistant_message_id, - 'parentId': user_message_id, - 'childrenIds': [], - 'role': 'assistant', - 'content': '', - 'done': False, - 'model': target_model_id, - 'timestamp': int(time.time()), - }, + assistant_message, ) await publish_event( request, diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index f1bc26ffd9..5d657e92f8 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -2777,9 +2777,11 @@ : selectedModels; // Create response messages for each selected model - // Build message_ids list: [{model_id, message_id}, ...] + // Build message_ids list: [{model_id, message_id, modelIdx}, ...] // Uses an array instead of a dict to support duplicate model IDs in side-by-side chat. - const messageIdsList: Array<{ model_id: string; message_id: string }> = []; + // modelIdx identifies each side-by-side column so the backend can persist it; without + // it, duplicate models collapse into one another when the chat is reloaded. + const messageIdsList: Array<{ model_id: string; message_id: string; modelIdx: number }> = []; for (const [_modelIdx, modelId] of selectedModelIds.entries()) { const model = $models.filter((m) => m.id === modelId).at(0); @@ -2811,7 +2813,11 @@ } responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId; - messageIdsList.push({ model_id: modelId, message_id: responseMessageId }); + messageIdsList.push({ + model_id: modelId, + message_id: responseMessageId, + modelIdx: modelIdx ? modelIdx : _modelIdx + }); } } history = history; @@ -2890,7 +2896,11 @@ primaryResponseMessageId, _chatId, { - messageIdsList: selectedModelIds.length > 1 ? messageIdsList : undefined, + // Always forward the message_ids list (not just for multi-model sends) so the + // backend persists each response's modelIdx — including single-column + // regenerations in a duplicate-model chat, which would otherwise lose their + // column identity and collapse on reload. + messageIdsList: messageIdsList.length > 0 ? messageIdsList : undefined, regenerationPrompt } );