aeno 0.0.3

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 (140) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +230 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/stylesheets/aeno/application.css +1 -0
  6. data/app/assets/stylesheets/aeno/base.css +43 -0
  7. data/app/assets/stylesheets/aeno/reset.css +397 -0
  8. data/app/assets/stylesheets/aeno/source.css +15 -0
  9. data/app/assets/stylesheets/aeno/theme.css +6 -0
  10. data/app/assets/stylesheets/aeno/themes/slate.css +163 -0
  11. data/app/assets/stylesheets/aeno/themes/zinc.css +163 -0
  12. data/app/assets/stylesheets/aeno/utilities.css +23 -0
  13. data/app/components/aeno/application_view_component.rb +219 -0
  14. data/app/components/aeno/blocks/component_preview/component.html.erb +7 -0
  15. data/app/components/aeno/blocks/component_preview/component.rb +10 -0
  16. data/app/components/aeno/blocks/component_preview/styles.css +10 -0
  17. data/app/components/aeno/form_builder.rb +87 -0
  18. data/app/components/aeno/pages/showcase/index/component.html.erb +53 -0
  19. data/app/components/aeno/pages/showcase/index/component.rb +7 -0
  20. data/app/components/aeno/pages/showcase/index/styles.css +27 -0
  21. data/app/components/aeno/pages/showcase/placeholder/component.html.erb +7 -0
  22. data/app/components/aeno/pages/showcase/placeholder/component.rb +10 -0
  23. data/app/components/aeno/pages/showcase/show/component.html.erb +38 -0
  24. data/app/components/aeno/pages/showcase/show/component.rb +48 -0
  25. data/app/components/aeno/primitives/button/component.rb +66 -0
  26. data/app/components/aeno/primitives/button/controller.js +7 -0
  27. data/app/components/aeno/primitives/button/styles.css +153 -0
  28. data/app/components/aeno/primitives/card/component.html.erb +3 -0
  29. data/app/components/aeno/primitives/card/component.rb +42 -0
  30. data/app/components/aeno/primitives/card/styles.css +28 -0
  31. data/app/components/aeno/primitives/conversation/component.html.erb +28 -0
  32. data/app/components/aeno/primitives/conversation/component.rb +15 -0
  33. data/app/components/aeno/primitives/conversation/controller.js +18 -0
  34. data/app/components/aeno/primitives/conversation/message/component.html.erb +24 -0
  35. data/app/components/aeno/primitives/conversation/message/component.rb +35 -0
  36. data/app/components/aeno/primitives/conversation/streaming_indicator/component.html.erb +21 -0
  37. data/app/components/aeno/primitives/conversation/streaming_indicator/component.rb +18 -0
  38. data/app/components/aeno/primitives/conversation/styles.css +221 -0
  39. data/app/components/aeno/primitives/conversation/user_message_box/component.html.erb +1 -0
  40. data/app/components/aeno/primitives/conversation/user_message_box/component.rb +4 -0
  41. data/app/components/aeno/primitives/drawer/component.html.erb +43 -0
  42. data/app/components/aeno/primitives/drawer/component.rb +33 -0
  43. data/app/components/aeno/primitives/drawer/controller.js +104 -0
  44. data/app/components/aeno/primitives/drawer/styles.css +90 -0
  45. data/app/components/aeno/primitives/dropdown/checkbox.rb +22 -0
  46. data/app/components/aeno/primitives/dropdown/component.html.erb +38 -0
  47. data/app/components/aeno/primitives/dropdown/component.rb +53 -0
  48. data/app/components/aeno/primitives/dropdown/controller.js +153 -0
  49. data/app/components/aeno/primitives/dropdown/item.rb +29 -0
  50. data/app/components/aeno/primitives/dropdown/label.rb +7 -0
  51. data/app/components/aeno/primitives/dropdown/radio_group.rb +16 -0
  52. data/app/components/aeno/primitives/dropdown/radio_item.rb +24 -0
  53. data/app/components/aeno/primitives/dropdown/separator.rb +7 -0
  54. data/app/components/aeno/primitives/dropdown/styles.css +155 -0
  55. data/app/components/aeno/primitives/empty/component.html.erb +15 -0
  56. data/app/components/aeno/primitives/empty/component.rb +18 -0
  57. data/app/components/aeno/primitives/empty/styles.css +40 -0
  58. data/app/components/aeno/primitives/input_attachments/component.html.erb +60 -0
  59. data/app/components/aeno/primitives/input_attachments/component.rb +52 -0
  60. data/app/components/aeno/primitives/input_attachments/controller.js +357 -0
  61. data/app/components/aeno/primitives/input_attachments/styles.css +102 -0
  62. data/app/components/aeno/primitives/input_color/component.html.erb +24 -0
  63. data/app/components/aeno/primitives/input_color/component.rb +42 -0
  64. data/app/components/aeno/primitives/input_color/styles.css +64 -0
  65. data/app/components/aeno/primitives/input_password/component.html.erb +43 -0
  66. data/app/components/aeno/primitives/input_password/component.rb +20 -0
  67. data/app/components/aeno/primitives/input_password/controller.js +17 -0
  68. data/app/components/aeno/primitives/input_password/styles.css +61 -0
  69. data/app/components/aeno/primitives/input_select/component.html.erb +43 -0
  70. data/app/components/aeno/primitives/input_select/component.rb +21 -0
  71. data/app/components/aeno/primitives/input_select/option.rb +14 -0
  72. data/app/components/aeno/primitives/input_select/styles.css +30 -0
  73. data/app/components/aeno/primitives/input_slider/component.html.erb +33 -0
  74. data/app/components/aeno/primitives/input_slider/component.rb +35 -0
  75. data/app/components/aeno/primitives/input_slider/styles.css +74 -0
  76. data/app/components/aeno/primitives/input_tagging/component.html.erb +73 -0
  77. data/app/components/aeno/primitives/input_tagging/component.rb +40 -0
  78. data/app/components/aeno/primitives/input_tagging/controller.js +326 -0
  79. data/app/components/aeno/primitives/input_tagging/styles.css +148 -0
  80. data/app/components/aeno/primitives/input_text/component.html.erb +25 -0
  81. data/app/components/aeno/primitives/input_text/component.rb +20 -0
  82. data/app/components/aeno/primitives/input_text/styles.css +38 -0
  83. data/app/components/aeno/primitives/input_text_area/component.html.erb +23 -0
  84. data/app/components/aeno/primitives/input_text_area/component.rb +19 -0
  85. data/app/components/aeno/primitives/input_text_area/styles.css +30 -0
  86. data/app/components/aeno/primitives/input_text_area_ai/component.html.erb +51 -0
  87. data/app/components/aeno/primitives/input_text_area_ai/component.rb +47 -0
  88. data/app/components/aeno/primitives/input_text_area_ai/controller.js +198 -0
  89. data/app/components/aeno/primitives/input_text_area_ai/styles.css +91 -0
  90. data/app/components/aeno/primitives/input_wrapper/component.html.erb +20 -0
  91. data/app/components/aeno/primitives/input_wrapper/component.rb +31 -0
  92. data/app/components/aeno/primitives/input_wrapper/styles.css +72 -0
  93. data/app/components/aeno/primitives/layouts/agentic/component.html.erb +4 -0
  94. data/app/components/aeno/primitives/layouts/agentic/component.rb +9 -0
  95. data/app/components/aeno/primitives/layouts/agentic/styles.css +23 -0
  96. data/app/components/aeno/primitives/layouts/app/aside.rb +9 -0
  97. data/app/components/aeno/primitives/layouts/app/component.html.erb +14 -0
  98. data/app/components/aeno/primitives/layouts/app/component.rb +11 -0
  99. data/app/components/aeno/primitives/layouts/app/sidebar.rb +9 -0
  100. data/app/components/aeno/primitives/layouts/app/styles.css +46 -0
  101. data/app/components/aeno/primitives/page/component.html.erb +24 -0
  102. data/app/components/aeno/primitives/page/component.rb +23 -0
  103. data/app/components/aeno/primitives/page/styles.css +55 -0
  104. data/app/components/aeno/primitives/sidebar/component.html.erb +25 -0
  105. data/app/components/aeno/primitives/sidebar/component.rb +14 -0
  106. data/app/components/aeno/primitives/sidebar/footer.rb +7 -0
  107. data/app/components/aeno/primitives/sidebar/group.rb +18 -0
  108. data/app/components/aeno/primitives/sidebar/header.rb +7 -0
  109. data/app/components/aeno/primitives/sidebar/item.rb +19 -0
  110. data/app/components/aeno/primitives/sidebar/styles.css +95 -0
  111. data/app/components/aeno/primitives/spinner/component.rb +36 -0
  112. data/app/components/aeno/primitives/spinner/styles.css +81 -0
  113. data/app/components/aeno/primitives/table/cell.rb +7 -0
  114. data/app/components/aeno/primitives/table/column.rb +7 -0
  115. data/app/components/aeno/primitives/table/component.html.erb +8 -0
  116. data/app/components/aeno/primitives/table/component.rb +14 -0
  117. data/app/components/aeno/primitives/table/header.rb +13 -0
  118. data/app/components/aeno/primitives/table/row.rb +11 -0
  119. data/app/components/aeno/primitives/table/styles.css +39 -0
  120. data/app/controllers/aeno/application_controller.rb +15 -0
  121. data/app/controllers/aeno/showcase_controller.rb +40 -0
  122. data/app/controllers/aeno/theme_controller.rb +10 -0
  123. data/app/helpers/aeno/application_helper.rb +28 -0
  124. data/app/javascript/aeno/application.js +3 -0
  125. data/app/javascript/aeno/controllers/application.js +5 -0
  126. data/app/javascript/aeno/controllers/index.js +5 -0
  127. data/app/javascript/aeno/controllers/loader.js +62 -0
  128. data/app/jobs/aeno/application_job.rb +4 -0
  129. data/app/models/aeno/application_record.rb +5 -0
  130. data/app/views/layouts/aeno/application.html.erb +55 -0
  131. data/config/importmap.rb +20 -0
  132. data/config/routes.rb +5 -0
  133. data/lib/aeno/configuration.rb +56 -0
  134. data/lib/aeno/engine.rb +43 -0
  135. data/lib/aeno/engine_helpers.rb +44 -0
  136. data/lib/aeno/theme.rb +326 -0
  137. data/lib/aeno/version.rb +3 -0
  138. data/lib/aeno.rb +11 -0
  139. data/lib/tasks/aeno_tasks.rake +39 -0
  140. metadata +310 -0
@@ -0,0 +1,48 @@
1
+ module Aeno::Pages::Showcase::Show
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ option :component_class
4
+
5
+ def title
6
+ component_class.examples_title || component_name
7
+ end
8
+
9
+ def description
10
+ component_class.examples_description
11
+ end
12
+
13
+ def examples
14
+ component_class.examples_list
15
+ end
16
+
17
+ def props
18
+ component_class.props
19
+ end
20
+
21
+ # Returns :primitive or :block
22
+ def component_type
23
+ if component_class.name.include?("::Primitives::")
24
+ :primitive
25
+ elsif component_class.name.include?("::Blocks::")
26
+ :block
27
+ end
28
+ end
29
+
30
+ # e.g., "button" from Aeno::Primitives::Button::Component
31
+ def component_name
32
+ component_class.name
33
+ .sub(/^Aeno::(Primitives|Blocks)::/, "")
34
+ .sub(/::Component$/, "")
35
+ .underscore
36
+ .tr("_", "-")
37
+ end
38
+
39
+ def render_component(**props)
40
+ case component_type
41
+ when :primitive
42
+ helpers.ui(component_name, **props)
43
+ when :block
44
+ helpers.block(component_name, **props)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,66 @@
1
+ module Aeno::Primitives::Button
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :variant, description: "Button style variant",
4
+ values: [:default, :secondary, :destructive, :ghost, :outline, :link],
5
+ default: -> { :default }
6
+ prop :size, description: "Button size",
7
+ values: [:xsmall, :small, :large, :icon],
8
+ optional: true
9
+ prop :label, description: "Button text label", optional: true
10
+ prop :icon, description: "Lucide icon name", optional: true
11
+ prop :href, description: "Link URL (renders as <a>)", optional: true
12
+ prop :disabled, description: "Disable the button", default: -> { false }
13
+ prop :full, description: "Full width button", default: -> { false }
14
+
15
+ option(:type, optional: true)
16
+ option(:method, optional: true)
17
+ option(:data, default: -> { {} })
18
+ option(:target, optional: true)
19
+
20
+ examples("Button", description: "Clickable actions and navigation links") do |b|
21
+ b.example(:default, title: "Default") do |e|
22
+ e.preview label: "Click me"
23
+ end
24
+
25
+ b.example(:variants, title: "Variants", description: "Available style variants") do |e|
26
+ e.preview variant: :default, label: "Default"
27
+ e.preview variant: :secondary, label: "Secondary"
28
+ e.preview variant: :destructive, label: "Destructive"
29
+ e.preview variant: :outline, label: "Outline"
30
+ e.preview variant: :ghost, label: "Ghost"
31
+ e.preview variant: :link, label: "Link"
32
+ end
33
+
34
+ b.example(:sizes, title: "Sizes", description: "Available sizes") do |e|
35
+ e.preview size: :xsmall, label: "XSmall"
36
+ e.preview size: :small, label: "Small"
37
+ e.preview label: "Default"
38
+ e.preview size: :large, label: "Large"
39
+ end
40
+
41
+ b.example(:icons, title: "With Icons", description: "Buttons with icons") do |e|
42
+ e.preview icon: "settings", label: "Settings"
43
+ e.preview icon: "download", label: "Download", variant: :secondary
44
+ e.preview icon: "trash-2", label: "Delete", variant: :destructive
45
+ e.preview icon: "settings", size: :icon
46
+ end
47
+
48
+ b.example(:states, title: "States") do |e|
49
+ e.preview label: "Disabled", disabled: true
50
+ e.preview label: "Full Width", full: true
51
+ end
52
+ end
53
+
54
+ def button_classes
55
+ classes(variant:, size:, disabled:, full:)
56
+ end
57
+
58
+ def call
59
+ action_tag(href: href, method: method, data: merged_data, class: button_classes, target: target, disabled: disabled) do
60
+ (icon ? lucide_icon(icon, class: "flex-shrink-0 icon") : "".html_safe) +
61
+ ui("spinner", size: :sm, variant: :white, css: "spinner hidden") +
62
+ (label ? content_tag(:span, label, class: "truncate flex-shrink") : "".html_safe)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ console.log("button connected");
6
+ }
7
+ }
@@ -0,0 +1,153 @@
1
+ /* Button component styles */
2
+ .cp-button {
3
+ border-radius: var(--ui-rounded-button, 0.375rem);
4
+ padding: 0.625rem 0.875rem;
5
+ font-weight: 600;
6
+ display: inline-flex;
7
+ align-items: center;
8
+ gap: 0.25rem;
9
+ cursor: pointer;
10
+ transition:
11
+ color 0.15s ease,
12
+ background-color 0.15s ease,
13
+ border-color 0.15s ease;
14
+
15
+ &:focus-visible {
16
+ outline: 2px solid var(--ui-ring, #3b82f6);
17
+ outline-offset: 2px;
18
+ }
19
+
20
+ & > span {
21
+ overflow: hidden;
22
+ text-overflow: ellipsis;
23
+ white-space: nowrap;
24
+ flex-shrink: 1;
25
+ }
26
+
27
+ & > svg {
28
+ flex-shrink: 0;
29
+ width: 1rem;
30
+ height: 1rem;
31
+ }
32
+
33
+ & .spinner {
34
+ display: none;
35
+ }
36
+
37
+ &.loading {
38
+ opacity: 0.5;
39
+ pointer-events: none;
40
+
41
+ & .icon {
42
+ display: none;
43
+ }
44
+ & .spinner {
45
+ display: flex;
46
+ }
47
+ }
48
+
49
+ /* Variants */
50
+ &--default {
51
+ background-color: var(--ui-button-bg, #475569);
52
+ color: var(--ui-button-foreground, #ffffff);
53
+
54
+ &:hover {
55
+ background-color: var(--ui-button-hover, #334155);
56
+ }
57
+ }
58
+
59
+ &--secondary {
60
+ background-color: var(--ui-button-secondary-bg, #e2e8f0);
61
+ color: var(--ui-button-secondary-foreground, #1e293b);
62
+
63
+ &:hover {
64
+ background-color: var(--ui-button-secondary-hover, #cbd5e1);
65
+ }
66
+ }
67
+
68
+ &--destructive {
69
+ background-color: var(--ui-button-destructive-bg, #ef4444);
70
+ color: var(--ui-button-destructive-foreground, #ffffff);
71
+
72
+ &:hover {
73
+ background-color: var(--ui-button-destructive-hover, #dc2626);
74
+ }
75
+ }
76
+
77
+ &--outline {
78
+ border: 1px solid var(--ui-button-border, #e2e8f0);
79
+ background-color: var(--ui-background, #ffffff);
80
+ color: var(--ui-button-outline-foreground, #1e293b);
81
+
82
+ &:hover {
83
+ background-color: var(--ui-button-outline-hover, #f1f5f9);
84
+ }
85
+ }
86
+
87
+ &--ghost {
88
+ background-color: transparent;
89
+ color: var(--ui-button-ghost-foreground, #1e293b);
90
+
91
+ &:hover {
92
+ background-color: var(--ui-button-ghost-hover, #f1f5f9);
93
+ }
94
+ }
95
+
96
+ &--link {
97
+ background-color: transparent;
98
+ color: var(--ui-link, #3b82f6);
99
+ text-underline-offset: 4px;
100
+
101
+ &:hover {
102
+ text-decoration: underline;
103
+ color: var(--ui-link-hover, #2563eb);
104
+ }
105
+ }
106
+
107
+ /* Sizes */
108
+ &--xsmall {
109
+ padding: 0.25rem 0.625rem;
110
+ font-size: 0.75rem;
111
+ border-radius: var(--ui-rounded-button-sm, 0.25rem);
112
+ gap: 0.25rem;
113
+
114
+ & > svg {
115
+ width: 0.75rem;
116
+ height: 0.75rem;
117
+ }
118
+ }
119
+
120
+ &--small {
121
+ padding: 0.375rem 0.75rem;
122
+ font-size: 0.875rem;
123
+ border-radius: var(--ui-rounded-button-sm, 0.25rem);
124
+
125
+ & > svg {
126
+ width: 1rem;
127
+ height: 1rem;
128
+ }
129
+ }
130
+
131
+ &--large {
132
+ padding: 0.75rem 1.5rem;
133
+ font-size: 1.125rem;
134
+ border-radius: var(--ui-rounded-button-lg, 0.5rem);
135
+ gap: 0.5rem;
136
+
137
+ & > svg {
138
+ width: 1.25rem;
139
+ height: 1.25rem;
140
+ }
141
+ }
142
+
143
+ /* States */
144
+ &--disabled {
145
+ pointer-events: none;
146
+ opacity: 0.5;
147
+ }
148
+
149
+ &--full {
150
+ width: 100%;
151
+ justify-content: center;
152
+ }
153
+ }
@@ -0,0 +1,3 @@
1
+ <%= tag.div class: card_classes, style: merged_style do %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,42 @@
1
+ module Aeno::Primitives::Card
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :variant, description: "Card style variant",
4
+ values: [:default, :ghost, :elevated],
5
+ default: -> { :default }
6
+ prop :pad, description: "Padding level (1-4, each = 1rem)", values: [1, 2, 3, 4], optional: true
7
+ prop :centered, description: "Center content with flexbox", default: -> { false }
8
+
9
+ examples("Card", description: "Container for grouping related content") do |b|
10
+ b.example(:default, title: "Default") do |e|
11
+ e.preview
12
+ end
13
+
14
+ b.example(:variants, title: "Variants", description: "Available style variants") do |e|
15
+ e.preview variant: :default
16
+ e.preview variant: :ghost
17
+ e.preview variant: :elevated
18
+ end
19
+
20
+ b.example(:padding, title: "Padding", description: "Padding levels") do |e|
21
+ e.preview pad: 1
22
+ e.preview pad: 2
23
+ e.preview pad: 3
24
+ e.preview pad: 4
25
+ end
26
+
27
+ b.example(:centered, title: "Centered", description: "Centered content") do |e|
28
+ e.preview centered: true
29
+ end
30
+ end
31
+
32
+ def card_classes
33
+ [
34
+ class_for,
35
+ (class_for(:centered) if centered),
36
+ (class_for(variant) if variant != :default),
37
+ ("pad-#{pad}" if pad),
38
+ css
39
+ ].compact.join(" ")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ /* Card component styles */
2
+ .cp-card {
3
+ background-color: var(--ui-card-bg);
4
+ color: var(--ui-card-fg);
5
+ border: 1px solid var(--ui-card-border);
6
+ border-radius: var(--ui-card-radius);
7
+ box-shadow: var(--ui-card-shadow);
8
+
9
+ /* Layout */
10
+ &--centered {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ }
15
+
16
+ /* Variants */
17
+ &--ghost {
18
+ border-color: transparent;
19
+ box-shadow: none;
20
+ background-color: transparent;
21
+ }
22
+
23
+ &--elevated {
24
+ box-shadow:
25
+ 0 10px 15px -3px rgb(0 0 0 / 0.1),
26
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
27
+ }
28
+ }
@@ -0,0 +1,28 @@
1
+ <div class="cp-conversation" data-controller="<%= controller_name %>">
2
+ <div class="cp-conversation__header">
3
+ <h2 class="cp-conversation__title"><%= title || "Chat" %></h2>
4
+ </div>
5
+
6
+ <div class="cp-conversation__messages" data-<%= controller_name %>-target="messagesContainer" id="messages">
7
+ <% if messages.any? %>
8
+ <% messages.each do |message| %>
9
+ <%= message %>
10
+ <% end %>
11
+ <% else %>
12
+ <div class="cp-conversation__empty">
13
+ <div class="cp-conversation__empty-content">
14
+ <p class="cp-conversation__empty-title">Start a conversation</p>
15
+ <p class="cp-conversation__empty-subtitle">Ask me anything!</p>
16
+ </div>
17
+ </div>
18
+ <% end %>
19
+
20
+ <div id="streaming-indicator" class="cp-conversation__streaming-hidden">
21
+ <%= ui("conversation/streaming-indicator") %>
22
+ </div>
23
+
24
+ <div data-<%= controller_name %>-target="scrollAnchor"></div>
25
+ </div>
26
+
27
+ <%= user_message_box %>
28
+ </div>
@@ -0,0 +1,15 @@
1
+ module Aeno::Primitives::Conversation
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :title, description: "Chat title", optional: true
4
+ prop :streaming, description: "Show streaming indicator", default: -> { false }
5
+
6
+ renders_many :messages, Message::Component
7
+ renders_one :user_message_box, UserMessageBox::Component
8
+
9
+ examples("Conversation", description: "Chat conversation interface") do |b|
10
+ b.example(:default, title: "Default") do |e|
11
+ e.preview title: "Chat"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["messagesContainer", "scrollAnchor"];
5
+
6
+ connect() {
7
+ console.log("conversation connected");
8
+
9
+ // Auto-scroll to bottom on initial load
10
+ this.scrollToBottom();
11
+ }
12
+
13
+ scrollToBottom() {
14
+ if (this.hasScrollAnchorTarget) {
15
+ this.scrollAnchorTarget.scrollIntoView({ behavior: "smooth" });
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,24 @@
1
+ <div class="cp-conversation-message <%= role_class %>">
2
+ <div class="cp-conversation-message__container">
3
+ <div class="cp-conversation-message__row">
4
+ <div class="cp-conversation-message__avatar-wrapper">
5
+ <div class="cp-conversation-message__avatar <%= avatar_class %>">
6
+ <%= avatar_label %>
7
+ </div>
8
+ </div>
9
+ <div class="cp-conversation-message__content">
10
+ <div class="cp-conversation-message__role">
11
+ <%= role_label %>
12
+ </div>
13
+ <div class="cp-conversation-message__body">
14
+ <%= content %>
15
+ </div>
16
+ <% if timestamp %>
17
+ <div class="cp-conversation-message__timestamp">
18
+ <%= timestamp %>
19
+ </div>
20
+ <% end %>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
@@ -0,0 +1,35 @@
1
+ module Aeno::Primitives::Conversation::Message
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :role, description: "Message role", values: [:user, :assistant, :error, :system], default: -> { "assistant" }
4
+ prop :content, description: "Message content", default: -> { "" }
5
+ prop :timestamp, description: "Message timestamp", optional: true
6
+
7
+ def role_class
8
+ "cp-conversation-message--#{role}"
9
+ end
10
+
11
+ def avatar_class
12
+ "cp-conversation-message__avatar--#{role}"
13
+ end
14
+
15
+ def role_label
16
+ case role.to_s
17
+ when "user" then "You"
18
+ when "assistant" then "Assistant"
19
+ when "error" then "Error"
20
+ when "system" then "System"
21
+ else role.to_s.capitalize
22
+ end
23
+ end
24
+
25
+ def avatar_label
26
+ case role.to_s
27
+ when "user" then "U"
28
+ when "assistant" then "A"
29
+ when "error" then "!"
30
+ when "system" then "S"
31
+ else "?"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ <div class="cp-conversation-streaming">
2
+ <div class="cp-conversation-streaming__container">
3
+ <div class="cp-conversation-streaming__row">
4
+ <div class="cp-conversation-streaming__avatar-wrapper">
5
+ <div class="cp-conversation-streaming__avatar <%= avatar_class %>">
6
+ <%= avatar_label %>
7
+ </div>
8
+ </div>
9
+ <div class="cp-conversation-streaming__content">
10
+ <div class="cp-conversation-streaming__label">
11
+ <%= label %>
12
+ </div>
13
+ <div class="cp-conversation-streaming__dots">
14
+ <div class="cp-conversation-streaming__dot" style="animation-delay: 0ms;"></div>
15
+ <div class="cp-conversation-streaming__dot" style="animation-delay: 150ms;"></div>
16
+ <div class="cp-conversation-streaming__dot" style="animation-delay: 300ms;"></div>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
@@ -0,0 +1,18 @@
1
+ module Aeno::Primitives::Conversation::StreamingIndicator
2
+ class Component < ::Aeno::ApplicationViewComponent
3
+ prop :role, description: "Role being streamed", default: -> { "assistant" }
4
+ prop :label, description: "Label text", default: -> { "Assistant" }
5
+
6
+ def avatar_class
7
+ "cp-conversation-streaming__avatar--#{role}"
8
+ end
9
+
10
+ def avatar_label
11
+ case role.to_s
12
+ when "user" then "U"
13
+ when "assistant" then "A"
14
+ else role.to_s[0].upcase
15
+ end
16
+ end
17
+ end
18
+ end