mirror of
https://github.com/we-promise/sure.git
synced 2026-05-31 16:29:03 +00:00
* fix(ai-chat): disable submit on empty input instead of surfacing 'Content missing' (#1697) Empty-input clicks on the chat send button posted the form, which then failed Message's `validates :content, presence: true` and surfaced `Content missing` to the user. The right shape per ChatGPT / Claude UX is to prevent the submission entirely until the input contains non-whitespace content. Add a `submit` target on the icon button and have the existing chat Stimulus controller: - Initialise the button to `disabled` when no `message_hint` is set. - Toggle disabled on every input event (re-using the existing `autoResize` handler) based on `input.value.trim().length > 0`. - Pre-clear disabled when a sample question is injected. - Short-circuit the Enter-key submit path on empty content so keyboard users hit the same gate. Closes #1697 * fix(ai-chat): drop server-rendered disabled attr, keep JS-driven gate (#1697) Codex review (P1) + @JSONbored + @jjmata called out that rendering the submit button with `disabled: message_hint.blank?` would lock the form out for users without working JS (asset failure, exception during Stimulus init, etc.). Server-side validation already catches empty submits with a real error message — server-disabling the button on top of that turns a soft fail into a hard one. Remove the server-render `disabled:` attribute. The chat Stimulus controller still runs `#updateSubmitState()` on connect, on every input event, and after sample-question injection, and `handleInputKeyDown` still short-circuits empty Enter submits. With JS the UX is identical; without JS the form keeps its fallback path. --------- Co-authored-by: jeffrey701 <jeffrey701@users.noreply.github.com>
33 lines
1.5 KiB
Plaintext
33 lines
1.5 KiB
Plaintext
<%# locals: (chat: nil, message_hint: nil) %>
|
|
|
|
<div id="chat-form" class="space-y-2">
|
|
<% model = chat && chat.persisted? ? [chat, Message.new] : Chat.new %>
|
|
|
|
<%= form_with model: model,
|
|
class: "flex lg:flex-col gap-2 bg-container px-2 py-1.5 rounded-full lg:rounded-lg shadow-border-xs",
|
|
data: { chat_target: "form" } do |f| %>
|
|
|
|
<%# In the future, this will be a dropdown with different AI models %>
|
|
<%= f.hidden_field :ai_model, value: default_ai_model %>
|
|
|
|
<%= f.text_area :content, placeholder: t(".placeholder"), value: message_hint,
|
|
class: "w-full border-0 focus:ring-0 text-sm resize-none px-1 bg-transparent",
|
|
data: { chat_target: "input", action: "input->chat#autoResize keydown->chat#handleInputKeyDown" },
|
|
rows: 1 %>
|
|
|
|
<div class="flex items-center justify-between gap-1">
|
|
<div class="items-center gap-1 hidden lg:flex">
|
|
<%# These are disabled for now, but in the future, will all open specific menus with their own context and search %>
|
|
<% ["plus", "command", "at-sign", "mouse-pointer-click"].each do |icon| %>
|
|
<%= icon(icon, as_button: true, disabled: true, class: "cursor-not-allowed", title: "Coming soon") %>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%= icon("arrow-up", as_button: true, type: "submit",
|
|
data: { chat_target: "submit" }) %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<p class="text-xs text-secondary"><%= t(".disclaimer") %></p>
|
|
</div>
|