From 765e4fa4e51edb4c1c5b497baec9475bd8f081f6 Mon Sep 17 00:00:00 2001 From: Edu Depetris Date: Wed, 10 Jun 2026 12:00:41 -0300 Subject: [PATCH] Fix duplicate DOM ids in chat_ui tool-call partials The chat_ui tool-call partial was rendered once per tool call used the parent message id, which caused duplicated ids when a single assistant message contained multiple tool calls (when parellel tool calling happens). Use tool_call.id as the element id source so each rendered tool-call row has a unique id. Also update id prefixes to keep tool_call and tool_result ids distinct from message ids. --- .../tailwind/views/messages/tool_calls/_default.html.erb.tt | 2 +- .../tailwind/views/messages/tool_results/_default.html.erb.tt | 2 +- .../templates/views/messages/tool_calls/_default.html.erb.tt | 2 +- .../templates/views/messages/tool_results/_default.html.erb.tt | 2 +- lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt | 2 +- lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt | 2 +- spec/ruby_llm/generators/chat_ui_generator_spec.rb | 3 +++ spec/ruby_llm/generators/tool_generator_spec.rb | 2 ++ 8 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt b/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt index 4b95fd321..fdc2c69e6 100644 --- a/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +++ b/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt @@ -1,4 +1,4 @@ -
+
Tool Call diff --git a/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt b/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt index 6ae566d66..3f617683c 100644 --- a/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +++ b/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt @@ -5,7 +5,7 @@ title: "Tool Result Error", error_message: error_message %> <%% else %> -
+
Tool diff --git a/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt b/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt index 4a0dc365f..e6085a910 100644 --- a/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +++ b/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt @@ -1,4 +1,4 @@ -
Tool Call
<%%= tool_call.name %>(<%%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)
diff --git a/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt b/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt index c492ec235..3273620d5 100644 --- a/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +++ b/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt @@ -5,7 +5,7 @@ title: "Tool Result Error", error_message: error_message %> <%% else %> -
Tool
<%%= tool.content.presence || "(no output)" %>
diff --git a/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt b/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt index c7646b606..508e228aa 100644 --- a/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +++ b/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt @@ -2,7 +2,7 @@ <%% if tool_call_error.present? %> <%%= render "messages/error", message: tool_calls, title: "Tool Call Error", error_message: tool_call_error %> <%% else %> -
<%= tool_display_name %> Call
<%%= tool_call.name %>(<%%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)
diff --git a/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt b/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt index e38eb0a06..f81e30f8a 100644 --- a/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +++ b/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt @@ -2,7 +2,7 @@ <%% if error_message.present? %> <%%= render "messages/error", message: tool, title: "Tool Result Error", error_message: error_message %> <%% else %> -
<%= tool_display_name %> Result
<%%= tool.content.presence || "(no output)" %>
diff --git a/spec/ruby_llm/generators/chat_ui_generator_spec.rb b/spec/ruby_llm/generators/chat_ui_generator_spec.rb index 23ec8cc9f..5bcdb4b4c 100644 --- a/spec/ruby_llm/generators/chat_ui_generator_spec.rb +++ b/spec/ruby_llm/generators/chat_ui_generator_spec.rb @@ -70,8 +70,11 @@ def expect_generated_view_set( # rubocop:disable Metrics/AbcSize tool_calls_partial = File.read(File.join(base_path, 'messages/_tool_calls.html.erb')) expect(tool_calls_partial).to include('tool_calls: tool_calls, tool_call: tool_call') expect(tool_calls_partial).to include('local_assigns[:message]') + tool_calls_default = File.read(File.join(base_path, 'messages/tool_calls/_default.html.erb')) + expect(tool_calls_default).to include('message_tool_call_<%= tool_call.id %>') tool_results_default = File.read(File.join(base_path, 'messages/tool_results/_default.html.erb')) expect(tool_results_default).to include('tool.tool_error_message') + expect(tool_results_default).to include('message_tool_result_<%= tool.id %>') chat_form = File.read(File.join(base_path, 'chats/_form.html.erb')) expect(chat_form).to include('@chat_models.map') expect(chat_form).to include('[model.label, model.id]') diff --git a/spec/ruby_llm/generators/tool_generator_spec.rb b/spec/ruby_llm/generators/tool_generator_spec.rb index 0f8b1bc51..97763e977 100644 --- a/spec/ruby_llm/generators/tool_generator_spec.rb +++ b/spec/ruby_llm/generators/tool_generator_spec.rb @@ -52,6 +52,7 @@ def run_rails_generate(*args) expect(tool_call_partial).to include('message: tool_calls') expect(tool_call_partial).to include('Weather Call') expect(tool_call_partial).to include('tool_call.arguments.map') + expect(tool_call_partial).to include('message_tool_call_<%= tool_call.id %>') expect(tool_call_partial).not_to include('render "messages/tool_calls/default"') expect(tool_call_partial).not_to include('<%%') @@ -59,6 +60,7 @@ def run_rails_generate(*args) expect(tool_result_partial).to include('tool.tool_error_message') expect(tool_result_partial).to include('Weather Result') expect(tool_result_partial).to include('tool.content.presence || "(no output)"') + expect(tool_result_partial).to include('message_tool_result_<%= tool.id %>') expect(tool_result_partial).not_to include('render "messages/tool_results/default", tool: tool') expect(tool_result_partial).not_to include('<%%') end