unmagic-components 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -1
  3. data/README.md +506 -1
  4. data/app/assets/icons/lucide/LICENSE +43 -0
  5. data/app/assets/icons/lucide/arrow-down.svg +1 -0
  6. data/app/assets/icons/lucide/arrow-left.svg +1 -0
  7. data/app/assets/icons/lucide/arrow-up.svg +1 -0
  8. data/app/assets/icons/lucide/ban.svg +1 -0
  9. data/app/assets/icons/lucide/brain.svg +1 -0
  10. data/app/assets/icons/lucide/check.svg +1 -0
  11. data/app/assets/icons/lucide/chevron-down.svg +1 -0
  12. data/app/assets/icons/lucide/chevron-left.svg +1 -0
  13. data/app/assets/icons/lucide/chevron-right.svg +1 -0
  14. data/app/assets/icons/lucide/circle-check.svg +1 -0
  15. data/app/assets/icons/lucide/circle-dashed.svg +1 -0
  16. data/app/assets/icons/lucide/circle-question-mark.svg +1 -0
  17. data/app/assets/icons/lucide/circle-stop.svg +1 -0
  18. data/app/assets/icons/lucide/circle-x.svg +1 -0
  19. data/app/assets/icons/lucide/clock.svg +1 -0
  20. data/app/assets/icons/lucide/copy.svg +1 -0
  21. data/app/assets/icons/lucide/ellipsis-vertical.svg +1 -0
  22. data/app/assets/icons/lucide/file.svg +1 -0
  23. data/app/assets/icons/lucide/folder.svg +1 -0
  24. data/app/assets/icons/lucide/grip-vertical.svg +1 -0
  25. data/app/assets/icons/lucide/info.svg +1 -0
  26. data/app/assets/icons/lucide/lightbulb.svg +1 -0
  27. data/app/assets/icons/lucide/list-checks.svg +1 -0
  28. data/app/assets/icons/lucide/loader-circle.svg +1 -0
  29. data/app/assets/icons/lucide/messages-square.svg +1 -0
  30. data/app/assets/icons/lucide/paperclip.svg +1 -0
  31. data/app/assets/icons/lucide/pencil.svg +1 -0
  32. data/app/assets/icons/lucide/plus.svg +1 -0
  33. data/app/assets/icons/lucide/rotate-cw.svg +1 -0
  34. data/app/assets/icons/lucide/shield-alert.svg +1 -0
  35. data/app/assets/icons/lucide/timer.svg +1 -0
  36. data/app/assets/icons/lucide/triangle-alert.svg +1 -0
  37. data/app/assets/icons/lucide/wrench.svg +1 -0
  38. data/app/assets/icons/lucide/x.svg +1 -0
  39. data/app/assets/javascripts/unmagic/components/ai_chat.js +142 -0
  40. data/app/assets/javascripts/unmagic/components/autoscroll.js +103 -0
  41. data/app/assets/javascripts/unmagic/components/board.js +65 -0
  42. data/app/assets/javascripts/unmagic/components/dropzone.js +206 -0
  43. data/app/assets/javascripts/unmagic/components/elapsed.js +64 -0
  44. data/app/assets/javascripts/unmagic/components/optimistic.js +71 -0
  45. data/app/assets/javascripts/unmagic/components/placement.js +30 -0
  46. data/app/assets/javascripts/unmagic/components/slash_menu.js +186 -0
  47. data/app/assets/javascripts/unmagic/components/sortable.js +6 -0
  48. data/app/assets/javascripts/unmagic/components/sortable_item.js +122 -0
  49. data/app/assets/javascripts/unmagic/components/sortable_list.js +135 -0
  50. data/app/assets/javascripts/unmagic/components/sortable_param.js +14 -0
  51. data/app/assets/javascripts/unmagic/components/sortable_session.js +502 -0
  52. data/app/assets/javascripts/unmagic/components/streaming_markdown.js +269 -0
  53. data/app/assets/javascripts/unmagic/components/toasts.js +14 -4
  54. data/app/assets/javascripts/unmagic/components/toolbar.js +76 -0
  55. data/app/assets/javascripts/unmagic/components/upsert.js +4 -20
  56. data/app/assets/javascripts/unmagic/components.js +10 -0
  57. data/app/assets/tailwind/unmagic_components/engine.css +1515 -0
  58. data/lib/unmagic/components/action_view_helpers.rb +558 -3
  59. data/lib/unmagic/components/ai_chat/action_bar.rb +70 -0
  60. data/lib/unmagic/components/ai_chat/attachments.rb +119 -0
  61. data/lib/unmagic/components/ai_chat/branch_picker.rb +65 -0
  62. data/lib/unmagic/components/ai_chat/citation.rb +89 -0
  63. data/lib/unmagic/components/ai_chat/composer.rb +160 -0
  64. data/lib/unmagic/components/ai_chat/failure.rb +80 -0
  65. data/lib/unmagic/components/ai_chat/message.rb +142 -0
  66. data/lib/unmagic/components/ai_chat/payload.rb +93 -0
  67. data/lib/unmagic/components/ai_chat/permission.rb +143 -0
  68. data/lib/unmagic/components/ai_chat/plan.rb +91 -0
  69. data/lib/unmagic/components/ai_chat/proposal.rb +102 -0
  70. data/lib/unmagic/components/ai_chat/reasoning.rb +75 -0
  71. data/lib/unmagic/components/ai_chat/request.rb +206 -0
  72. data/lib/unmagic/components/ai_chat/section.rb +53 -0
  73. data/lib/unmagic/components/ai_chat/slash_menu.rb +68 -0
  74. data/lib/unmagic/components/ai_chat/tool_call.rb +181 -0
  75. data/lib/unmagic/components/ai_chat/transcript.rb +68 -0
  76. data/lib/unmagic/components/ai_chat/welcome.rb +73 -0
  77. data/lib/unmagic/components/ai_chat/workspace.rb +74 -0
  78. data/lib/unmagic/components/ai_chat.rb +31 -0
  79. data/lib/unmagic/components/avatar.rb +134 -0
  80. data/lib/unmagic/components/board.rb +182 -0
  81. data/lib/unmagic/components/browser/app/controllers/unmagic/components/browser/application_controller.rb +80 -0
  82. data/lib/unmagic/components/browser/app/controllers/unmagic/components/browser/demos_controller.rb +137 -0
  83. data/lib/unmagic/components/browser/app/controllers/unmagic/components/browser/pages_controller.rb +40 -0
  84. data/lib/unmagic/components/browser/app/helpers/unmagic/components/browser/application_helper.rb +52 -0
  85. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/board_store.rb +88 -0
  86. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/catalog.rb +65 -0
  87. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/pager.rb +11 -0
  88. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/profile.rb +25 -0
  89. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/reply.rb +72 -0
  90. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/thing.rb +31 -0
  91. data/lib/unmagic/components/browser/app/views/layouts/unmagic/components/browser/application.html.erb +65 -0
  92. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_code.html.erb +7 -0
  93. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_crumb.html.erb +7 -0
  94. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_example.html.erb +37 -0
  95. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_nav.html.erb +17 -0
  96. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/demos/edit_profile.html.erb +9 -0
  97. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_conversation.html.erb +101 -0
  98. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_live.html.erb +53 -0
  99. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_thumbnail.html.erb +7 -0
  100. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_welcome.html.erb +12 -0
  101. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_action_bar/_always.html.erb +6 -0
  102. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_action_bar/_hover.html.erb +12 -0
  103. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_action_bar/_thumbnail.html.erb +5 -0
  104. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_attachments/_dropzone.html.erb +17 -0
  105. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_attachments/_sent.html.erb +11 -0
  106. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_attachments/_thumbnail.html.erb +6 -0
  107. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_branch_picker/_positions.html.erb +5 -0
  108. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_branch_picker/_thumbnail.html.erb +3 -0
  109. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_citation/_compact.html.erb +10 -0
  110. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_citation/_quote.html.erb +11 -0
  111. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_citation/_thumbnail.html.erb +7 -0
  112. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_composer/_parts.html.erb +14 -0
  113. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_composer/_states.html.erb +8 -0
  114. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_composer/_thumbnail.html.erb +5 -0
  115. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_failure/_full.html.erb +11 -0
  116. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_failure/_plain.html.erb +3 -0
  117. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_failure/_thumbnail.html.erb +5 -0
  118. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_parts.html.erb +23 -0
  119. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_states.html.erb +9 -0
  120. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_thumbnail.html.erb +4 -0
  121. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_turns.html.erb +16 -0
  122. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_payload/_json.html.erb +4 -0
  123. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_payload/_text.html.erb +4 -0
  124. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_payload/_thumbnail.html.erb +3 -0
  125. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_permission/_answered.html.erb +12 -0
  126. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_permission/_thumbnail.html.erb +7 -0
  127. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_permission/_waiting.html.erb +12 -0
  128. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_plan/_empty.html.erb +3 -0
  129. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_plan/_states.html.erb +9 -0
  130. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_plan/_thumbnail.html.erb +7 -0
  131. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_proposal/_decided.html.erb +11 -0
  132. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_proposal/_inline.html.erb +18 -0
  133. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_proposal/_thumbnail.html.erb +6 -0
  134. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_reasoning/_settled.html.erb +6 -0
  135. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_reasoning/_thinking.html.erb +5 -0
  136. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_reasoning/_thumbnail.html.erb +3 -0
  137. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_answered.html.erb +10 -0
  138. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_choices.html.erb +13 -0
  139. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_form.html.erb +9 -0
  140. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_thumbnail.html.erb +8 -0
  141. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_slash_menu/_composer.html.erb +15 -0
  142. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_slash_menu/_thumbnail.html.erb +9 -0
  143. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_made.html.erb +12 -0
  144. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_run.html.erb +25 -0
  145. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_states.html.erb +22 -0
  146. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_thumbnail.html.erb +7 -0
  147. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_welcome/_suggestions.html.erb +11 -0
  148. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_welcome/_thumbnail.html.erb +6 -0
  149. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_workspace/_files.html.erb +12 -0
  150. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_workspace/_thumbnail.html.erb +7 -0
  151. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_group.html.erb +5 -0
  152. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_sizes.html.erb +8 -0
  153. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_thumbnail.html.erb +5 -0
  154. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_tints.html.erb +7 -0
  155. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/badge/_in_context.html.erb +14 -0
  156. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/badge/_thumbnail.html.erb +5 -0
  157. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/badge/_tones.html.erb +8 -0
  158. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/board/_handles.html.erb +13 -0
  159. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/board/_live.html.erb +34 -0
  160. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/board/_thumbnail.html.erb +10 -0
  161. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/button/_sizes.html.erb +12 -0
  162. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/button/_thumbnail.html.erb +4 -0
  163. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/button/_variants.html.erb +6 -0
  164. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/callout/_neutral.html.erb +3 -0
  165. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/callout/_thumbnail.html.erb +5 -0
  166. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/callout/_tones.html.erb +14 -0
  167. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_actions_and_footer.html.erb +7 -0
  168. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_flush_table.html.erb +6 -0
  169. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_link.html.erb +6 -0
  170. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_thumbnail.html.erb +6 -0
  171. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/confirm/_danger.html.erb +3 -0
  172. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/confirm/_plain.html.erb +2 -0
  173. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/confirm/_thumbnail.html.erb +7 -0
  174. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/copy_button/_from_element.html.erb +4 -0
  175. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/copy_button/_thumbnail.html.erb +4 -0
  176. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/copy_button/_with_label.html.erb +3 -0
  177. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/detail_list/_inline.html.erb +5 -0
  178. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/detail_list/_stacked.html.erb +7 -0
  179. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/detail_list/_thumbnail.html.erb +5 -0
  180. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/dialog/_dialog_tag.html.erb +8 -0
  181. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/dialog/_modal.html.erb +10 -0
  182. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/dialog/_thumbnail.html.erb +4 -0
  183. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/elapsed/_countdown.html.erb +3 -0
  184. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/elapsed/_running.html.erb +8 -0
  185. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/elapsed/_thumbnail.html.erb +4 -0
  186. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/empty_state/_default.html.erb +1 -0
  187. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/empty_state/_thumbnail.html.erb +3 -0
  188. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_autogrow.html.erb +4 -0
  189. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_control_classes.html.erb +54 -0
  190. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_fields.html.erb +28 -0
  191. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_states.html.erb +11 -0
  192. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_thumbnail.html.erb +8 -0
  193. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_uuid_field.html.erb +9 -0
  194. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/local_time/_absolute.html.erb +5 -0
  195. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/local_time/_relative.html.erb +14 -0
  196. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/local_time/_thumbnail.html.erb +14 -0
  197. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/menu/_actions.html.erb +10 -0
  198. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/menu/_labelled.html.erb +6 -0
  199. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/menu/_thumbnail.html.erb +14 -0
  200. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/page_header/_default.html.erb +7 -0
  201. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/page_header/_thumbnail.html.erb +5 -0
  202. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/page_header/_with_leading.html.erb +14 -0
  203. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_blocking_out.html.erb +25 -0
  204. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_card.html.erb +7 -0
  205. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_detail_list.html.erb +15 -0
  206. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_page_header.html.erb +7 -0
  207. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_text_and_button.html.erb +17 -0
  208. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_thumbnail.html.erb +10 -0
  209. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_connected.html.erb +17 -0
  210. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_grid.html.erb +20 -0
  211. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_handles.html.erb +22 -0
  212. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_thumbnail.html.erb +7 -0
  213. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/streaming_markdown/_reveal.html.erb +19 -0
  214. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/streaming_markdown/_stopped.html.erb +31 -0
  215. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/streaming_markdown/_thumbnail.html.erb +5 -0
  216. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_deferred.html.erb +6 -0
  217. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_empty.html.erb +5 -0
  218. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_sortable.html.erb +8 -0
  219. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_table_tag.html.erb +4 -0
  220. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_thumbnail.html.erb +4 -0
  221. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tabs/_links.html.erb +5 -0
  222. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tabs/_panels.html.erb +18 -0
  223. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tabs/_thumbnail.html.erb +5 -0
  224. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_above_dialog.html.erb +10 -0
  225. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_flash.html.erb +13 -0
  226. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_stream.html.erb +6 -0
  227. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_thumbnail.html.erb +10 -0
  228. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tooltip/_placement.html.erb +8 -0
  229. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tooltip/_term.html.erb +5 -0
  230. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tooltip/_thumbnail.html.erb +5 -0
  231. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/component.html.erb +58 -0
  232. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/installation.html.erb +47 -0
  233. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/overview.html.erb +49 -0
  234. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/theming.html.erb +52 -0
  235. data/lib/unmagic/components/browser/assets/browser.css +2 -0
  236. data/lib/unmagic/components/browser/catalog/ai_chat.rb +21 -0
  237. data/lib/unmagic/components/browser/catalog/ai_chat_action_bar.rb +15 -0
  238. data/lib/unmagic/components/browser/catalog/ai_chat_attachments.rb +15 -0
  239. data/lib/unmagic/components/browser/catalog/ai_chat_branch_picker.rb +12 -0
  240. data/lib/unmagic/components/browser/catalog/ai_chat_citation.rb +15 -0
  241. data/lib/unmagic/components/browser/catalog/ai_chat_composer.rb +16 -0
  242. data/lib/unmagic/components/browser/catalog/ai_chat_failure.rb +15 -0
  243. data/lib/unmagic/components/browser/catalog/ai_chat_message.rb +17 -0
  244. data/lib/unmagic/components/browser/catalog/ai_chat_payload.rb +16 -0
  245. data/lib/unmagic/components/browser/catalog/ai_chat_permission.rb +15 -0
  246. data/lib/unmagic/components/browser/catalog/ai_chat_plan.rb +14 -0
  247. data/lib/unmagic/components/browser/catalog/ai_chat_proposal.rb +15 -0
  248. data/lib/unmagic/components/browser/catalog/ai_chat_reasoning.rb +14 -0
  249. data/lib/unmagic/components/browser/catalog/ai_chat_request.rb +17 -0
  250. data/lib/unmagic/components/browser/catalog/ai_chat_slash_menu.rb +14 -0
  251. data/lib/unmagic/components/browser/catalog/ai_chat_tool_call.rb +17 -0
  252. data/lib/unmagic/components/browser/catalog/ai_chat_welcome.rb +13 -0
  253. data/lib/unmagic/components/browser/catalog/ai_chat_workspace.rb +12 -0
  254. data/lib/unmagic/components/browser/catalog/avatar.rb +15 -0
  255. data/lib/unmagic/components/browser/catalog/badge.rb +11 -0
  256. data/lib/unmagic/components/browser/catalog/board.rb +18 -0
  257. data/lib/unmagic/components/browser/catalog/button.rb +14 -0
  258. data/lib/unmagic/components/browser/catalog/callout.rb +13 -0
  259. data/lib/unmagic/components/browser/catalog/card.rb +16 -0
  260. data/lib/unmagic/components/browser/catalog/confirm.rb +14 -0
  261. data/lib/unmagic/components/browser/catalog/copy_button.rb +15 -0
  262. data/lib/unmagic/components/browser/catalog/detail_list.rb +14 -0
  263. data/lib/unmagic/components/browser/catalog/dialog.rb +15 -0
  264. data/lib/unmagic/components/browser/catalog/elapsed.rb +14 -0
  265. data/lib/unmagic/components/browser/catalog/empty_state.rb +11 -0
  266. data/lib/unmagic/components/browser/catalog/forms.rb +21 -0
  267. data/lib/unmagic/components/browser/catalog/local_time.rb +13 -0
  268. data/lib/unmagic/components/browser/catalog/menu.rb +15 -0
  269. data/lib/unmagic/components/browser/catalog/page_header.rb +13 -0
  270. data/lib/unmagic/components/browser/catalog/skeleton.rb +18 -0
  271. data/lib/unmagic/components/browser/catalog/sortable_list.rb +17 -0
  272. data/lib/unmagic/components/browser/catalog/streaming_markdown.rb +17 -0
  273. data/lib/unmagic/components/browser/catalog/table.rb +17 -0
  274. data/lib/unmagic/components/browser/catalog/tabs.rb +15 -0
  275. data/lib/unmagic/components/browser/catalog/toast.rb +17 -0
  276. data/lib/unmagic/components/browser/catalog/tooltip.rb +14 -0
  277. data/lib/unmagic/components/browser/config/routes.rb +41 -0
  278. data/lib/unmagic/components/browser/tailwind/browser.css +33 -0
  279. data/lib/unmagic/components/browser.rb +39 -0
  280. data/lib/unmagic/components/configuration.rb +31 -1
  281. data/lib/unmagic/components/duration.rb +35 -0
  282. data/lib/unmagic/components/elapsed.rb +46 -0
  283. data/lib/unmagic/components/engine.rb +9 -0
  284. data/lib/unmagic/components/icons.rb +34 -26
  285. data/lib/unmagic/components/modal.rb +1 -1
  286. data/lib/unmagic/components/page_header.rb +8 -0
  287. data/lib/unmagic/components/sortable.rb +104 -0
  288. data/lib/unmagic/components/streaming_markdown.rb +34 -0
  289. data/lib/unmagic/components/turbo_stream_actions.rb +16 -0
  290. data/lib/unmagic/components/version.rb +1 -1
  291. data/lib/unmagic/components.rb +30 -1
  292. metadata +308 -4
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ module Components
5
+ # A stretch of time as somebody would say it: 640ms, 2s, 3m 5s, 1h 4m 2s. Prose
6
+ # about a wait rather than a clock, so the larger units are left off until they
7
+ # are needed. Anything under a second is milliseconds, since most tool calls
8
+ # answer in a fraction of one and "0s" says nothing about which fraction.
9
+ #
10
+ # <unmagic-elapsed> writes the same words while a clock is running (elapsed.js),
11
+ # so a reading doesn't change shape the moment it settles. Change both together.
12
+ module Duration
13
+ def self.format(seconds)
14
+ return if seconds.nil?
15
+
16
+ if seconds.positive? && seconds < 1
17
+ return I18n.t("unmagic.components.elapsed.milliseconds", count: (seconds * 1000).round, default: "%{count}ms")
18
+ end
19
+
20
+ whole = seconds.round
21
+ hours, remainder = whole.divmod(3600)
22
+ minutes, secs = remainder.divmod(60)
23
+
24
+ if whole < 60
25
+ I18n.t("unmagic.components.elapsed.seconds", count: whole, default: "%{count}s")
26
+ elsif whole < 3600
27
+ I18n.t("unmagic.components.elapsed.minutes", minutes: minutes, seconds: secs, default: "%{minutes}m %{seconds}s")
28
+ else
29
+ I18n.t("unmagic.components.elapsed.hours", hours: hours, minutes: minutes, seconds: secs,
30
+ default: "%{hours}h %{minutes}m %{seconds}s")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ module Components
5
+ # A clock counting up from a moment the server named, or down to one. See
6
+ # ActionViewHelpers#elapsed_tag.
7
+ class Elapsed
8
+ DIRECTIONS = %i[up down].freeze
9
+
10
+ def initialize(view, time, direction:, **options)
11
+ unless DIRECTIONS.include?(direction)
12
+ raise ArgumentError, "unknown elapsed_tag direction #{direction.inspect} (expected one of #{DIRECTIONS.inspect})"
13
+ end
14
+
15
+ @view = view
16
+ @time = time.in_time_zone
17
+ @direction = direction
18
+ @options = options
19
+ end
20
+
21
+ # The server writes the reading the element would, so it is right before the
22
+ # script loads. Whole seconds, floored going up and ceiled going down: a
23
+ # stopwatch says how long it has been, not how long it nearly has been.
24
+ def render
25
+ attribute = @direction == :up ? :since : :until
26
+
27
+ view.content_tag("unmagic-elapsed", Duration.format(seconds), **@options,
28
+ attribute => @time.utc.iso8601(3),
29
+ title: I18n.l(@time, format: :long),
30
+ class: view.class_names("UnmagicElapsed", @options[:class]))
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :view
36
+
37
+ def seconds
38
+ if @direction == :up
39
+ [ (Time.current - @time).floor, 0 ].max
40
+ else
41
+ [ (@time - Time.current).ceil, 0 ].max
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -7,10 +7,19 @@ module Unmagic
7
7
  class Engine < ::Rails::Engine
8
8
  isolate_namespace Unmagic::Components
9
9
 
10
+ # Both hooks, because a helper test never touches ActionView::Base. The :action_view hook
11
+ # mixes into Base, which is what a template renders through — but ActionView::TestCase builds
12
+ # its own view class and calls the helper under test on the test case itself. So a host helper
13
+ # that composes one of these (badge, button_classes) works in the app and raises NoMethodError
14
+ # in its own spec, which is a confusing place to send someone.
10
15
  initializer "unmagic_components.helpers" do
11
16
  ActiveSupport.on_load(:action_view) do
12
17
  include Unmagic::Components::ActionViewHelpers
13
18
  end
19
+
20
+ ActiveSupport.on_load(:action_view_test_case) do
21
+ include Unmagic::Components::ActionViewHelpers
22
+ end
14
23
  end
15
24
 
16
25
  # turbo_stream.toast. turbo-rails runs this hook when its tag builder loads, so
@@ -1,39 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "unmagic_icon"
4
+
3
5
  module Unmagic
4
6
  module Components
5
- # The handful of glyphs the components draw for themselves — a close button, a
6
- # toast's tone, a copy button's confirmation. Inline SVG so the gem depends on no
7
- # icon library and no host helper. Paths are from Lucide (ISC licence).
7
+ # The glyphs the components draw for themselves — a close button, a toast's
8
+ # tone, a tool call's state. Rendered inline through unmagic-icon from a subset
9
+ # of Lucide the gem ships in app/assets/icons/lucide (ISC, see the LICENSE
10
+ # beside the files), so a host needs no icon set of its own: kp2 downloads
11
+ # Phosphor, not Lucide, and the components still draw.
12
+ #
13
+ # The library is read from the gem's own directory rather than looked up in
14
+ # unmagic-icon's registry, which only learns about engine icons when the host
15
+ # leaves unmagic-icon to initialise itself. Under Rails the same files are also
16
+ # registered as "unmagic_components:lucide", for a host that wants to draw them.
8
17
  module Icons
9
- PATHS = {
10
- x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
11
- check: '<path d="M20 6 9 17l-5-5"/>',
12
- circle_check: '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
13
- circle_x: '<circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/>',
14
- triangle_alert: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/>' \
15
- '<path d="M12 9v4"/><path d="M12 17h.01"/>',
16
- info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
17
- copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>' \
18
- '<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
19
- rotate: '<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/>',
20
- ellipsis_vertical: '<circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/>',
21
- arrow_left: '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
22
- chevron_down: '<path d="m6 9 6 6 6-6"/>'
23
- }.freeze
18
+ DIRECTORY = File.expand_path("../../../app/assets/icons/lucide", __dir__)
24
19
 
25
20
  # The glyph a tone leads with, where it has one.
26
21
  TONE_ICONS = { good: :circle_check, warn: :triangle_alert, bad: :circle_x, info: :info }.freeze
27
22
 
28
- def self.svg(view, name, **options)
29
- paths = PATHS.fetch(name) { raise ArgumentError, "unknown icon #{name.inspect}" }
23
+ class << self
24
+ # An icon by its Lucide name as a symbol, :circle_check for circle-check.
25
+ # Decorative unless the caller says otherwise, so aria-hidden is on by default.
26
+ def svg(view, name, **options)
27
+ library.find(name.to_s.tr("_", "-")).render(
28
+ "aria-hidden": "true", **options, class: view.class_names("UnmagicIcon", options[:class])
29
+ )
30
+ rescue Unmagic::Icon::IconNotFoundError
31
+ raise ArgumentError, "unknown icon #{name.inspect}"
32
+ end
33
+
34
+ def names
35
+ Dir[File.join(DIRECTORY, "*.svg")].map { |path| File.basename(path, ".svg").tr("-", "_").to_sym }.sort
36
+ end
37
+
38
+ private
30
39
 
31
- view.tag.svg(
32
- paths.html_safe, # rubocop:disable Rails/OutputSafety -- the constant markup above, never input
33
- xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor",
34
- "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true",
35
- **options, class: view.class_names("UnmagicIcon", options[:class])
36
- )
40
+ # The library's manifest-free lookup falls back to nothing, so an unknown
41
+ # name raises rather than drawing some other glyph.
42
+ def library
43
+ @library ||= Unmagic::Icon::Library.new(name: "unmagic_components:lucide", path: DIRECTORY)
44
+ end
37
45
  end
38
46
  end
39
47
  end
@@ -56,7 +56,7 @@ module Unmagic
56
56
 
57
57
  panel.footer do
58
58
  tag.button type: "button", class: Button.classes(:primary), data: { unmagic_modal_retry: "" } do
59
- safe_join [ Icons.svg(view, :rotate), I18n.t("unmagic.components.modal.retry", default: "Try again") ]
59
+ safe_join [ Icons.svg(view, :rotate_cw), I18n.t("unmagic.components.modal.retry", default: "Try again") ]
60
60
  end
61
61
  end
62
62
 
@@ -41,6 +41,14 @@ module Unmagic
41
41
  nil
42
42
  end
43
43
 
44
+ # Markup beside the title, as it is — the counterpart to leading, and the way to put badges
45
+ # there that something else has already rendered: a status partial, a row a helper returns.
46
+ # Those can't go through badge, which would wrap a badge in a badge.
47
+ def trailing(content = nil, &block)
48
+ @badges << (block ? view.capture(&block) : content)
49
+ nil
50
+ end
51
+
44
52
  # As a skeleton, anything given still renders for real, and the rest stands in
45
53
  # as shapes: a title bar, a description line (unless description: false) and
46
54
  # a button.
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+
5
+ module Unmagic
6
+ module Components
7
+ # Drag-and-drop ordering: a list whose items can be dragged, or moved with the
8
+ # keyboard, and dropped in a new place, here or in another list that shares its
9
+ # namespace. See ActionViewHelpers#sortable_list.
10
+ module Sortable
11
+ ORIENTATIONS = %i[vertical horizontal grid].freeze
12
+
13
+ # What the element says to a screen reader as an item is picked up, moved,
14
+ # dropped or put back. {item}, {list}, {position} and {count} are filled in by
15
+ # the element as it goes.
16
+ ANNOUNCEMENTS = {
17
+ instructions: "Press Space to pick up. While holding it, use the arrow keys to move it, Space to drop it, " \
18
+ "and Escape to cancel.",
19
+ picked: "Picked up {item}. Position {position} of {count} in {list}.",
20
+ moved: "{item} moved to position {position} of {count} in {list}.",
21
+ dropped: "Dropped {item} at position {position} of {count} in {list}.",
22
+ cancelled: "Cancelled. {item} is back at position {position} of {count} in {list}."
23
+ }.freeze
24
+
25
+ def self.announcements
26
+ ANNOUNCEMENTS.to_h do |key, default|
27
+ [ key, I18n.t("unmagic.components.sortable.#{key}", default: default) ]
28
+ end
29
+ end
30
+
31
+ # The <unmagic-sortable-list> and the items recorded into it.
32
+ class List
33
+ def initialize(view, namespace: nil, params: {}, url: nil, orientation: :vertical, label: nil, **options)
34
+ unless ORIENTATIONS.include?(orientation)
35
+ raise ArgumentError, "unknown sortable_list orientation #{orientation.inspect} (expected one of #{ORIENTATIONS.inspect})"
36
+ end
37
+
38
+ @view = view
39
+ @namespace = namespace
40
+ @params = params
41
+ @url = url.nil? ? Components.configuration.sortable_url.call(view) : url
42
+ @orientation = orientation
43
+ @label = label
44
+ @options = options
45
+ @items = []
46
+ end
47
+
48
+ # An item for a record (its key and rank come from config.sortable_item), or
49
+ # for key: and rank: given directly. label: is what a screen reader hears it
50
+ # called; without it, its text.
51
+ def item(record = nil, key: nil, rank: nil, label: nil, tag: "unmagic-sortable-item", **options, &block)
52
+ attributes = record ? Components.configuration.sortable_item.call(view, record) : {}
53
+ key ||= attributes[:key]
54
+ rank ||= attributes[:rank]
55
+ raise ArgumentError, "a sortable item needs a record or a key:" if key.blank?
56
+
57
+ rank = rank.to_s("F") if rank.is_a?(BigDecimal)
58
+ @items << view.content_tag(tag, block ? view.capture(&block) : nil, **options,
59
+ key: key,
60
+ "data-sortable-rank": rank,
61
+ "data-sortable-label": label,
62
+ class: view.class_names("UnmagicSortableItem", options[:class]))
63
+ nil
64
+ end
65
+
66
+ def render(extra = nil)
67
+ announcements = Sortable.announcements
68
+
69
+ view.content_tag("unmagic-sortable-list", **@options,
70
+ namespace: @namespace.presence,
71
+ url: @url.presence,
72
+ orientation: (@orientation unless @orientation == :vertical),
73
+ label: @label,
74
+ data: announcements.transform_keys { |key| "sortable_#{key}" }.merge(@options[:data] || {}),
75
+ class: view.class_names("UnmagicSortableList", "UnmagicSortableList--#{@orientation}", @options[:class])) do
76
+ view.safe_join [
77
+ *@params.map { |name, value| view.content_tag("unmagic-sortable-param", "", name: name, value: value) },
78
+ *@items,
79
+ extra
80
+ ].compact
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ attr_reader :view
87
+ end
88
+
89
+ # The grip an item is dragged by. With one in an item, only the grip starts a
90
+ # drag, so the rest of the item stays clickable, and it is the item's keyboard
91
+ # stop.
92
+ def self.handle(view, label: nil, **options)
93
+ label ||= I18n.t("unmagic.components.sortable.handle", default: "Drag to reorder")
94
+
95
+ view.tag.button(type: "button", **options,
96
+ "aria-label": label, title: label,
97
+ "data-sortable-handle": "",
98
+ class: view.class_names(Button.classes(:icon), "UnmagicSortableHandle", options[:class])) do
99
+ Icons.svg(view, :grip_vertical)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ module Components
5
+ # Server-rendered HTML, revealed at a steady pace as later renders of it are
6
+ # streamed in. See ActionViewHelpers#streaming_markdown_tag.
7
+ class StreamingMarkdown
8
+ def initialize(view, id:, final: false, streaming: false, **options)
9
+ raise ArgumentError, "streaming_markdown_tag needs an id: to stream into" if id.blank?
10
+
11
+ @view = view
12
+ @id = id
13
+ @final = final
14
+ @streaming = streaming
15
+ @options = options
16
+ end
17
+
18
+ # aria-busy only while the caller says a reply is live. It is what stops a
19
+ # transcript's live region announcing every flush, and a settled reply
20
+ # rendered busy would be skipped by a screen reader if the script never ran.
21
+ def render(content)
22
+ view.content_tag("unmagic-streaming-markdown", content, **@options,
23
+ id: @id,
24
+ final: ("" if @final),
25
+ "aria-busy": ("true" if @streaming && !@final),
26
+ class: view.class_names("UnmagicStreamingMarkdown", @options[:class]))
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :view
32
+ end
33
+ end
34
+ end
@@ -14,6 +14,22 @@ module Unmagic
14
14
  def toast(message, tone: :good)
15
15
  append Toast::TARGET, Toast.new(@view_context, message, tone: tone).template
16
16
  end
17
+
18
+ # Hands the whole of a reply rendered so far to the <unmagic-streaming-markdown>
19
+ # with that id, which paces its way toward it. Send the full render every
20
+ # time, not the new part: the element works out what is new.
21
+ #
22
+ # turbo_stream.stream_markdown "message_1_content", Markdown.render(message.content)
23
+ #
24
+ # From a model, the same action through Turbo::Broadcastable:
25
+ #
26
+ # broadcast_action_to chat, action: :stream_markdown, target: "#{dom_id(self)}_content",
27
+ # html: Markdown.render(content)
28
+ #
29
+ # Needs import "unmagic/components/streaming_markdown".
30
+ def stream_markdown(target, content = nil, **rendering, &block)
31
+ action :stream_markdown, target, content, **rendering, &block
32
+ end
17
33
  end
18
34
  end
19
35
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Unmagic
4
4
  module Components
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -38,10 +38,39 @@ require_relative "components/tabs"
38
38
  require_relative "components/copy_button"
39
39
  require_relative "components/autogrow"
40
40
  require_relative "components/uuid_input"
41
+ require_relative "components/duration"
42
+ require_relative "components/elapsed"
43
+ require_relative "components/streaming_markdown"
44
+ require_relative "components/avatar"
45
+ require_relative "components/sortable"
46
+ require_relative "components/board"
47
+ require_relative "components/ai_chat"
48
+ require_relative "components/ai_chat/payload"
49
+ require_relative "components/ai_chat/reasoning"
50
+ require_relative "components/ai_chat/message"
51
+ require_relative "components/ai_chat/transcript"
52
+ require_relative "components/ai_chat/tool_call"
53
+ require_relative "components/ai_chat/failure"
54
+ require_relative "components/ai_chat/section"
55
+ require_relative "components/ai_chat/plan"
56
+ require_relative "components/ai_chat/workspace"
57
+ require_relative "components/ai_chat/composer"
58
+ require_relative "components/ai_chat/request"
59
+ require_relative "components/ai_chat/permission"
60
+ require_relative "components/ai_chat/proposal"
61
+ require_relative "components/ai_chat/citation"
62
+ require_relative "components/ai_chat/welcome"
63
+ require_relative "components/ai_chat/attachments"
64
+ require_relative "components/ai_chat/slash_menu"
65
+ require_relative "components/ai_chat/action_bar"
66
+ require_relative "components/ai_chat/branch_picker"
41
67
  require_relative "components/action_view_helpers"
42
68
  # ActionView pulls in a partial Rails namespace, so this checks for the constant it
43
69
  # actually needs rather than for Rails.
44
- require_relative "components/engine" if defined?(Rails::Engine)
70
+ if defined?(Rails::Engine)
71
+ require_relative "components/engine"
72
+ require_relative "components/browser"
73
+ end
45
74
 
46
75
  module Unmagic
47
76
  module Components