Composer
The box a person types into, and the one button at the end of it: Send, or Stop while a turn runs. Only the button changes with the turn's state, so a half-typed draft always survives.
Every state
Idle, running, stopping, and waiting on a question above. Enter sends, Shift+Enter makes a new line, and Enter does nothing without Send.
<div class="mx-auto flex max-w-2xl flex-col gap-4">
<% %i[idle running stopping waiting].each do |state| %>
<%= form_with url: "#", id: "composer_#{state}", namespace: "composer_#{state}" do |form| %>
<%= ai_chat_composer form: form, field: :content, state: state, placeholder: "#{state.to_s.capitalize}…", rows: 1,
stop_form: "composer_states_stop" %>
<% end %>
<% end %>
</div>With controls
attach and actions put controls in the box.
<div class="mx-auto max-w-2xl">
<%= form_with url: "#", id: "composer_parts", namespace: "composer_parts" do |form| %>
<%= ai_chat_composer form: form, field: :content, placeholder: "Message the assistant…", label: "Start chat" do |composer| %>
<% composer.attach do %>
<button type="button" class="<%= button_classes(:icon) %>" aria-label="Attach a file" title="Attach a file">
<%= Unmagic::Components::Icons.svg(self, :paperclip) %>
</button>
<% end %>
<% composer.actions do %>
<%= select_tag :model, options_for_select([ "Fast", "Thorough" ]), class: control_classes(:select, size: :small) %>
<% end %>
<% end %>
<% end %>
</div>