The message field of the AI chat, of comments and of notes: a card with a textarea that grows with the text
(up to max_rows, then it scrolls inside), a row of actions under it and a round send button. Enter sends the
form, Shift + Enter breaks the line, and Enter while an accent or a word is being composed does neither; the send
button is off while there is nothing to send, and over max_length the card turns red and a counter shows from
80% of the limit (DevactionUI.Composer hook).
It is a form: the LiveView gets event with %{name => text} (%{"prompt" => "..."} by default). LiveView
never overwrites a field that has focus, so after a send the server empties it with
push_event(socket, "composer:clear", %{id: "chat"}) (the id of the composer).
With busy the AI is answering: the field is read only and the send button becomes a stop button (its icon is
the stop slot, its name stop_label). A click on it, or Esc, sends stop_event to target when there is one,
and fires composer:stop on the card for a hook of your own.
edit_last_event is what ↑ in an empty field sends (to target when there is one): the LiveView answers with
push_event(socket, "composer:fill", %{id: "chat", text: last_message}), which puts the text in the field.
suggest_event turns on the / and @ menus. A / at the very start of the text, or an @ at the start of any
word, sends the event with %{"id" => id, "trigger" => "/" | "@", "query" => "what follows the sign"}, and
"trigger" => nil once the word is gone or Esc closed the list. The LiveView draws the list in the suggestions
slot (DevactionUI.Editor.suggest/1 is made for it) only while the trigger is open and filters it by the query;
the hook owns the keys of the open list: ↑ ↓ move the highlight, Enter or Tab choose the row and Esc closes. A
row with insert puts that text in place of the typed word; a row with event also tells the server.
The mode or model chip (Reviewer · strict) is a DevactionUI.Dropdown with variant={:chip} in the leading
slot, fixed so the card never cuts its menu.
With an upload (an allow_upload of the LiveView) a file pasted into the field or dropped on the card is
uploaded by LiveView, and Composer.file/1 chips in the header slot show each one with a button to remove it.
The form then needs a change_event, which LiveView requires of a form with a file input. Composer.button/1 is
a round action of the row under the field (leading on the left, trailing before the send button); given the
upload, it opens the file chooser.
Every text comes from the caller (label, send_label, stop_label, the labels of the buttons and chips), and
so do the icons, as slots.
Summary
Functions
An action of the row under the field; with an upload it is the label of the file input.
The chip of one file of the upload: its preview when it is an image, its name and progress when it is not.
Functions
@spec button(map()) :: Phoenix.LiveView.Rendered.t()
An action of the row under the field; with an upload it is the label of the file input.
Attributes
label(:string) (required) - the name of the button, also its tooltip.variant(:atom) - Defaults to:plain. Must be one of:plain, or:filled.upload(:any) - a@uploads.name: the button opens the file chooser of the composer. Defaults tonil.- Global attributes are accepted.
Slots
icon(required)
@spec composer(map()) :: Phoenix.LiveView.Rendered.t()
Attributes
id(:string) (required)name(:string) - the name of the field in the params ofevent. Defaults to"prompt".value(:string) - the text the field starts with. Defaults to"".label(:string) (required) - what the field is, for assistive technology.placeholder(:string) - Defaults tonil.event(:string) (required) - thephx-submitof the form.send_label(:string) (required) - the name of the send button.stop_label(:string) (required) - the name of the button while the AI answers (busy).change_event(:string) - thephx-changeof the form; anuploadneeds it. Defaults tonil.stop_event(:string) - the event the stop button and Esc send; leave it out to listen tocomposer:stopinstead. Defaults tonil.max_rows(:integer) - how many lines the field grows to before it scrolls. Defaults to8.max_length(:integer) - the longest text that can be sent; 0 for no limit. Defaults to0.busy(:boolean) - the AI is answering: stop instead of send. Defaults tofalse.suggest_event(:string) - sent when a/or an@is being typed (and when it is gone); leave it out for no menus. Defaults tonil.edit_last_event(:string) - sent when ↑ is pressed in an empty field, so the server can fill in the last message. Defaults tonil.size(:atom) - :sm is a one-line field with the actions at its end. Defaults to:md. Must be one of:md, or:sm.upload(:any) - a@uploads.nameto paste and drop files into. Defaults tonil.class(:any) - Defaults tonil.target(:any) - aphx-target(a@myself) when used inside a LiveComponent. Defaults tonil.- Global attributes are accepted.
Slots
header- above the field: the chips of the files,Composer.file/1for each entry.suggestions- the open/or@list, drawn by the server above the card.leading- actions at the start of the row under the field.trailing- actions before the send button.icon(required) - the arrow of the send button.stop(required) - the icon of the stop button.
@spec file(map()) :: Phoenix.LiveView.Rendered.t()
The chip of one file of the upload: its preview when it is an image, its name and progress when it is not.
Attributes
entry(:any) (required) - aPhoenix.LiveView.UploadEntry.remove_label(:string) (required) - the name of the remove button, for this file.kind(:string) - the tag of a file that is not an image (PDF); the extension of its name by default. Defaults tonil.detail(:string) - the line under the name of a file that is not an image (1.8 MB); its progress by default. Defaults tonil.cancel_event(:string) - sent withref, the entry to cancel. Defaults to"cancel-upload".target(:any) - aphx-target(a@myself) when used inside a LiveComponent. Defaults tonil.- Global attributes are accepted.
Slots
icon(required) - the cross of the remove button.