gaggle 0.2.2

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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +167 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/gaggle_manifest.js +4 -0
  6. data/app/assets/stylesheets/application.css +1 -0
  7. data/app/assets/stylesheets/gaggle/tailwind.css +1740 -0
  8. data/app/controllers/gaggle/application_controller.rb +23 -0
  9. data/app/controllers/gaggle/channels/messages_controller.rb +27 -0
  10. data/app/controllers/gaggle/channels_controller.rb +84 -0
  11. data/app/controllers/gaggle/gooses/sessions_controller.rb +23 -0
  12. data/app/controllers/gaggle/gooses_controller.rb +47 -0
  13. data/app/controllers/gaggle/messages_controller.rb +7 -0
  14. data/app/controllers/gaggle/overviews_controller.rb +5 -0
  15. data/app/controllers/gaggle/sessions_controller.rb +27 -0
  16. data/app/helpers/gaggle/application_helper.rb +4 -0
  17. data/app/javascript/controllers/gaggle/application.js +9 -0
  18. data/app/javascript/controllers/gaggle/chat_field_form_controller.js +7 -0
  19. data/app/javascript/controllers/gaggle/index.js +4 -0
  20. data/app/javascript/controllers/gaggle/local_timestamp_controller.js +21 -0
  21. data/app/javascript/controllers/gaggle/text_area_auto_expand_controller.js +27 -0
  22. data/app/javascript/controllers/gaggle/transition_controller.js +102 -0
  23. data/app/javascript/gaggle/application.js +3 -0
  24. data/app/javascript/gaggle/el-transition.js +64 -0
  25. data/app/jobs/gaggle/application_job.rb +4 -0
  26. data/app/mailers/gaggle/application_mailer.rb +6 -0
  27. data/app/models/gaggle/application_record.rb +8 -0
  28. data/app/models/gaggle/channel.rb +45 -0
  29. data/app/models/gaggle/channel_goose.rb +11 -0
  30. data/app/models/gaggle/current.rb +5 -0
  31. data/app/models/gaggle/goose/personality_defaults.rb +87 -0
  32. data/app/models/gaggle/goose.rb +94 -0
  33. data/app/models/gaggle/message.rb +61 -0
  34. data/app/models/gaggle/notification.rb +47 -0
  35. data/app/models/gaggle/session.rb +138 -0
  36. data/app/views/gaggle/application/_logo.html.erb +125 -0
  37. data/app/views/gaggle/application/_mobile_header.html.erb +15 -0
  38. data/app/views/gaggle/application/_sidebar.html.erb +259 -0
  39. data/app/views/gaggle/channels/_channel.html.erb +22 -0
  40. data/app/views/gaggle/channels/_form.html.erb +70 -0
  41. data/app/views/gaggle/channels/edit.html.erb +10 -0
  42. data/app/views/gaggle/channels/gooses/index.html.erb +12 -0
  43. data/app/views/gaggle/channels/index.json.jbuilder +3 -0
  44. data/app/views/gaggle/channels/messages/new.html.erb +2 -0
  45. data/app/views/gaggle/channels/new.html.erb +8 -0
  46. data/app/views/gaggle/channels/show.html.erb +98 -0
  47. data/app/views/gaggle/channels/show.json.jbuilder +5 -0
  48. data/app/views/gaggle/channels/update.turbo_stream.erb +1 -0
  49. data/app/views/gaggle/gooses/_form.html.erb +65 -0
  50. data/app/views/gaggle/gooses/edit.html.erb +2 -0
  51. data/app/views/gaggle/gooses/index.json.jbuilder +5 -0
  52. data/app/views/gaggle/gooses/new.html.erb +2 -0
  53. data/app/views/gaggle/gooses/sessions/index.html.erb +55 -0
  54. data/app/views/gaggle/gooses/show.html.erb +41 -0
  55. data/app/views/gaggle/messages/_message.html.erb +30 -0
  56. data/app/views/gaggle/overviews/show.html.erb +17 -0
  57. data/app/views/gaggle/sessions/show.html.erb +76 -0
  58. data/app/views/layouts/gaggle/application.html.erb +19 -0
  59. data/config/importmap.rb +7 -0
  60. data/config/routes.rb +19 -0
  61. data/config/utility_classes.yml +22 -0
  62. data/db/gaggle_migrate/20250214180303_create_gaggle_tables.rb +53 -0
  63. data/db/gaggle_migrate/20250220002655_add_delivered_at_to_notification.rb +5 -0
  64. data/db/gaggle_migrate/20250220004428_create_join_table_gaggle_channel_gaggle_goose.rb +8 -0
  65. data/lib/gaggle/engine.rb +48 -0
  66. data/lib/gaggle/version.rb +3 -0
  67. data/lib/gaggle.rb +8 -0
  68. data/lib/generators/gaggle/install/install_generator.rb +7 -0
  69. data/lib/generators/gaggle/install/templates/db/gaggle_schema.rb +59 -0
  70. data/lib/generators/gaggle/update/update_generator.rb +16 -0
  71. metadata +269 -0
@@ -0,0 +1,259 @@
1
+ <%# locals: (channels:) %>
2
+
3
+ <!-- Static Sidebar for Desktop -->
4
+ <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-72 lg:flex-col bg-white dark:bg-gray-900
5
+ shadow-lg shadow-gray-200/50 dark:shadow-gray-800/20 transition-all duration-300">
6
+ <div class="flex grow flex-col gap-y-6 overflow-y-auto border-r border-gray-200 dark:border-gray-800
7
+ px-6 py-4">
8
+ <!-- Logo -->
9
+ <div class="flex h-16 shrink-0 items-center">
10
+ <%= render 'logo', classes: 'h-10 w-auto text-indigo-600 dark:text-indigo-400' %>
11
+ </div>
12
+
13
+ <!-- Navigation -->
14
+ <nav class="flex flex-1 flex-col">
15
+ <ul role="list" class="flex flex-1 flex-col gap-y-8">
16
+ <!-- Channels Section -->
17
+ <li>
18
+ <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
19
+ Channels
20
+ </div>
21
+ <ul role="list" class="-mx-2 mt-3 space-y-1" data-channel-list-sidebar>
22
+ <%= render channels %>
23
+ <%= link_to new_channel_path,
24
+ class: "new-channel group flex gap-x-3 rounded-lg p-2 text-sm font-medium text-gray-700
25
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50 hover:text-indigo-700
26
+ dark:text-gray-200 dark:hover:text-indigo-300 transition-all duration-200" do %>
27
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
28
+ stroke="currentColor" class="size-5 shrink-0 text-indigo-500 dark:text-indigo-400
29
+ group-hover:text-indigo-600 dark:group-hover:text-indigo-300">
30
+ <path stroke-linecap="round" stroke-linejoin="round"
31
+ d="M12 10.5v6m3-3H9m4.06-7.19-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
32
+ </svg>
33
+ New Channel
34
+ <% end %>
35
+ </ul>
36
+ </li>
37
+
38
+ <!-- Your Gaggle Section -->
39
+ <li>
40
+ <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
41
+ Your Gaggle
42
+ </div>
43
+ <ul role="list" class="-mx-2 mt-3 space-y-2">
44
+ <% @geese.each do |goose| %>
45
+ <li>
46
+ <div class="group flex items-center gap-x-3 rounded-lg text-sm font-medium
47
+ text-gray-700 hover:bg-indigo-50 dark:hover:bg-indigo-900/50
48
+ hover:text-indigo-700 dark:text-gray-200 dark:hover:text-indigo-300
49
+ transition-all duration-200">
50
+ <!-- Link Part -->
51
+ <%= link_to goose,
52
+ class: "flex items-center gap-x-3 flex-1 p-4" do %>
53
+ <span class="flex size-5 shrink-0 items-center justify-center rounded-md
54
+ border border-indigo-200 dark:border-indigo-700/50
55
+ bg-indigo-50 dark:bg-indigo-900/30 text-[0.625rem] font-medium
56
+ text-indigo-600 dark:text-indigo-400">
57
+ <%= goose.name[0] %>
58
+ </span>
59
+ <span class="flex-1 truncate"><%= goose.name %></span>
60
+ <% end %>
61
+
62
+ <!-- Button Part -->
63
+ <% if session = goose.sessions.running.first %>
64
+ <%= button_to session, method: :delete, class: yass(btn: :danger),
65
+ data: { turbo_confirm: "Are you sure you want to stop this session?" } do %>
66
+ Stop
67
+ <% end %>
68
+ <% else %>
69
+ <%= button_to goose_sessions_path(goose), class: yass(btn: :success) do %>
70
+ Start
71
+ <% end %>
72
+ <% end %>
73
+ </div>
74
+ </li>
75
+ <% end %>
76
+ <%= link_to new_goose_path,
77
+ class: "group flex gap-x-3 rounded-lg p-2 text-sm font-medium text-gray-700
78
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50 hover:text-indigo-700
79
+ dark:text-gray-200 dark:hover:text-indigo-300 transition-all duration-200" do %>
80
+ <svg fill="currentColor" stroke="currentColor" xmlns="http://www.w3.org/2000/svg"
81
+ viewBox="0 0 111.4 122.88" class="size-5 shrink-0 text-indigo-500 dark:text-indigo-400
82
+ group-hover:text-indigo-600 dark:group-hover:text-indigo-300">
83
+ <path d="M100.75,9.6l1,2.81a2.94,2.94,0,0,0-1.61-.14,4.13,4.13,0,0,0-2,1.11,9.48,9.48,0,0,0-2.56,5.15l-.94,5.85-.05.17-.07.28a7.49,7.49,0,0,1-3.24-.75l-3.93-1.72a34.26,34.26,0,0,0-3.43,18c.84,12.21,6,16.43,7.35,26.9a36.62,36.62,0,0,1,.3,3.76c.22,8.28-1.6,12.23-8.22,17.32-2,1.56-6.2,3.79-10.28,6.15-3.71,2.16-7.34,4.48-9.55,5.64a48.29,48.29,0,0,1-4.51,2.13h0l.64,1.58a3.61,3.61,0,0,0,.94.77,27,27,0,0,0,2.81,1.07c3.58,1.37,7.5,2.74,11.63,4.11a14.84,14.84,0,0,1-4.41,1.33,15.23,15.23,0,0,1-.39,5c-4.22-1.34-7.27-2.52-8.43-.43l-5.4-5.23c-1.26-1.29-1.33-2.3-.43-3.08l.78-.57c.47-.34,1-.54,1-1.18a1.5,1.5,0,0,0-.14-.65l-1.25-1.73h0a22.4,22.4,0,0,1-9.41,1h0a11.53,11.53,0,0,1-4.28,3.66A11,11,0,0,0,46.92,111c4.68.42,8.31,2.63,11.56,5.4L54,117.82a12.49,12.49,0,0,1-.4,5.06c-4-2.12-6.35-2.43-8-.92l-5.75-5.29a2,2,0,0,1-.8-2.06c.23-1.11,1.76-1.31,2.5-2.51l-2.12-3c-.77-1.11-.46-.83.42-2A9.28,9.28,0,0,0,41.88,103h0c-10.62-3.71-13.08-11.6-19.5-20.21q-2.89-3.88-6-7.42a8,8,0,0,0,2.2-.22A12.82,12.82,0,0,0,23,79.77a9.14,9.14,0,0,0,6,1.15,10.16,10.16,0,0,0,5.77,4.21,15.44,15.44,0,0,0,8.94-.63c11-.51,18-3.35,21.58-8.14s3.63-11.6.69-19.77a1.31,1.31,0,0,0-1.68-.79,1.3,1.3,0,0,0-.79,1.67c2.64,7.32,2.72,13.23-.33,17.33s-9.48,6.64-19.76,7.08A1.53,1.53,0,0,0,43,82a13.21,13.21,0,0,1-7.59.66,7.9,7.9,0,0,1-4.69-3.76,1.32,1.32,0,0,0-1.42-.65,6.85,6.85,0,0,1-5-.69A11.18,11.18,0,0,1,20.36,73a1.31,1.31,0,0,0-1.6-.63,5.61,5.61,0,0,1-2.88.33,5.14,5.14,0,0,1-2.42-1.19,1.34,1.34,0,0,0-1.08-.29L9.82,68.62c-5.53-3-9-8.78-9.82-17.85l3,.92a6.21,6.21,0,0,1,.55-5.22,13.54,13.54,0,0,1,8.28-.12l-1.29-2.7a30.21,30.21,0,0,0,6.69,1.4c9.54.89,20.58-2.83,30.71-3.5,8.6-.57,15.23.25,19.17,3,1.52-5.44,2.95-16.92,4.36-30.13a21,21,0,0,1,1.41-6.77,12.43,12.43,0,0,1,4.8-5.51c5.65-4,15-1.8,19.76,2.78a14.74,14.74,0,0,1,3.3,4.72Z"/>
84
+ </svg>
85
+ New Goose
86
+ <% end %>
87
+ </ul>
88
+ </li>
89
+
90
+ <!-- Settings -->
91
+ <li class="-mx-6 mt-auto">
92
+ <%= link_to '#',
93
+ class: "flex items-center gap-x-4 px-6 py-3 text-sm font-medium text-gray-700
94
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50 hover:text-indigo-700
95
+ dark:text-gray-200 dark:hover:text-indigo-300 transition-all duration-200" do %>
96
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
97
+ stroke="currentColor" class="size-5 text-indigo-500 dark:text-indigo-400">
98
+ <path stroke-linecap="round" stroke-linejoin="round"
99
+ d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
100
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
101
+ </svg>
102
+ Settings
103
+ <% end %>
104
+ </li>
105
+ </ul>
106
+ </nav>
107
+ </div>
108
+ </div>
109
+
110
+ <div data-controller="transition"
111
+ data-transition-open-value="false"
112
+ class="contents"
113
+ >
114
+ <!-- Mobile Menu -->
115
+ <div class="lg:hidden hidden"
116
+ data-transition-target="listener"
117
+ data-transition-enter="transition-opacity ease-linear duration-300"
118
+ data-transition-enter-start="opacity-0"
119
+ data-transition-enter-end="opacity-100"
120
+ data-transition-leave="transition-opacity ease-linear duration-300"
121
+ data-transition-leave-start="opacity-100"
122
+ data-transition-leave-end="opacity-0"
123
+ >
124
+ <div class="fixed inset-0 z-50 bg-gray-900/90 dark:bg-black/90 transition-opacity duration-200"
125
+ aria-hidden="true" data-mobile-menu-target="overlay" data-action="click->transition#close"></div>
126
+
127
+ <div class="fixed inset-y-0 left-0 z-50 w-72 flex"
128
+ data-transition-target="listener"
129
+ data-transition-enter="transition ease-in-out duration-300 transform"
130
+ data-transition-enter-start="-translate-x-full"
131
+ data-transition-enter-end="translate-x-0"
132
+ data-transition-leave="transition ease-in-out duration-300 transform"
133
+ data-transition-leave-start="translate-x-0"
134
+ data-transition-leave-end="-translate-x-full"
135
+ >
136
+ <div class="relative flex w-full flex-1 flex-col bg-white dark:bg-gray-900
137
+ shadow-lg shadow-gray-200/50 dark:shadow-gray-800/20"
138
+ data-transition-target="listener"
139
+ data-transition-enter="ease-in-out duration-300"
140
+ data-transition-enter-start="opacity-0"
141
+ data-transition-enter-end="opacity-100"
142
+ data-transition-leave="ease-in-out duration-300"
143
+ data-transition-leave-start="opacity-100"
144
+ data-transition-leave-end="opacity-0">
145
+ <!-- Close Button -->
146
+ <div class="absolute right-0 top-0 flex w-16 justify-center pt-5">
147
+ <button type="button" class="cursor-pointer -m-2.5 p-2.5 text-indigo-600 dark:text-indigo-400
148
+ hover:text-indigo-700 dark:hover:text-indigo-300 transition-colors duration-200"
149
+ data-action="transition#close">
150
+ <span class="sr-only">Close sidebar</span>
151
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
152
+ stroke="currentColor" class="size-6">
153
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
154
+ </svg>
155
+ </button>
156
+ </div>
157
+
158
+ <!-- Sidebar Content -->
159
+ <div class="flex grow flex-col gap-y-6 overflow-y-auto px-6 py-4">
160
+ <div class="flex h-16 shrink-0 items-center">
161
+ <%= render 'logo', classes: 'h-10 w-auto text-indigo-600 dark:text-indigo-400' %>
162
+ </div>
163
+ <nav class="flex flex-1 flex-col">
164
+ <ul role="list" class="flex flex-1 flex-col gap-y-8">
165
+ <li>
166
+ <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
167
+ Channels
168
+ </div>
169
+ <ul role="list" class="-mx-2 mt-3 space-y-1">
170
+ <%= render channels %>
171
+ <%= link_to new_channel_path,
172
+ class: "new-channel group flex gap-x-3 rounded-lg p-2 text-sm font-medium text-gray-700
173
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50 hover:text-indigo-700
174
+ dark:text-gray-200 dark:hover:text-indigo-300 transition-all duration-200" do %>
175
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
176
+ stroke="currentColor" class="size-5 shrink-0 text-indigo-500 dark:text-indigo-400
177
+ group-hover:text-indigo-600 dark:group-hover:text-indigo-300">
178
+ <path stroke-linecap="round" stroke-linejoin="round"
179
+ d="M12 10.5v6m3-3H9m4.06-7.19-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
180
+ </svg>
181
+ New Channel
182
+ <% end %>
183
+ </ul>
184
+ </li>
185
+ <li>
186
+ <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
187
+ Your Gaggle
188
+ </div>
189
+ <ul role="list" class="-mx-2 mt-3 space-y-2">
190
+ <% @geese.each do |goose| %>
191
+ <li>
192
+ <div class="group flex items-center gap-x-3 rounded-lg text-sm font-medium
193
+ text-gray-700 hover:bg-indigo-50 dark:hover:bg-indigo-900/50
194
+ hover:text-indigo-700 dark:text-gray-200 dark:hover:text-indigo-300
195
+ transition-all duration-200">
196
+ <!-- Link Part -->
197
+ <%= link_to goose,
198
+ class: "flex items-center gap-x-3 flex-1 p-4" do %>
199
+ <span class="flex size-5 shrink-0 items-center justify-center rounded-md
200
+ border border-indigo-200 dark:border-indigo-700/50
201
+ bg-indigo-50 dark:bg-indigo-900/30 text-[0.625rem] font-medium
202
+ text-indigo-600 dark:text-indigo-400">
203
+ <%= goose.name[0] %>
204
+ </span>
205
+ <span class="flex-1 truncate"><%= goose.name %></span>
206
+ <% end %>
207
+
208
+ <!-- Button Part -->
209
+ <% if session = goose.sessions.running.first %>
210
+ <%= button_to session, method: :delete, class: yass(btn: :danger),
211
+ data: { turbo_confirm: "Are you sure you want to stop this session?" } do %>
212
+ Stop
213
+ <% end %>
214
+ <% else %>
215
+ <%= button_to goose_sessions_path(goose), class: yass(btn: :success) do %>
216
+ Start
217
+ <% end %>
218
+ <% end %>
219
+ </div>
220
+ </li>
221
+ <% end %>
222
+ <%= link_to new_goose_path,
223
+ class: "group flex gap-x-3 rounded-lg p-2 text-sm font-medium text-gray-700
224
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50 hover:text-indigo-700
225
+ dark:text-gray-200 dark:hover:text-indigo-300 transition-all duration-200" do %>
226
+ <svg fill="currentColor" stroke="currentColor" xmlns="http://www.w3.org/2000/svg"
227
+ viewBox="0 0 111.4 122.88" class="size-5 shrink-0 text-indigo-500 dark:text-indigo-400
228
+ group-hover:text-indigo-600 dark:group-hover:text-indigo-300">
229
+ <path d="M100.75,9.6l1,2.81a2.94,2.94,0,0,0-1.61-.14,4.13,4.13,0,0,0-2,1.11,9.48,9.48,0,0,0-2.56,5.15l-.94,5.85-.05.17-.07.28a7.49,7.49,0,0,1-3.24-.75l-3.93-1.72a34.26,34.26,0,0,0-3.43,18c.84,12.21,6,16.43,7.35,26.9a36.62,36.62,0,0,1,.3,3.76c.22,8.28-1.6,12.23-8.22,17.32-2,1.56-6.2,3.79-10.28,6.15-3.71,2.16-7.34,4.48-9.55,5.64a48.29,48.29,0,0,1-4.51,2.13h0l.64,1.58a3.61,3.61,0,0,0,.94.77,27,27,0,0,0,2.81,1.07c3.58,1.37,7.5,2.74,11.63,4.11a14.84,14.84,0,0,1-4.41,1.33,15.23,15.23,0,0,1-.39,5c-4.22-1.34-7.27-2.52-8.43-.43l-5.4-5.23c-1.26-1.29-1.33-2.3-.43-3.08l.78-.57c.47-.34,1-.54,1-1.18a1.5,1.5,0,0,0-.14-.65l-1.25-1.73h0a22.4,22.4,0,0,1-9.41,1h0a11.53,11.53,0,0,1-4.28,3.66A11,11,0,0,0,46.92,111c4.68.42,8.31,2.63,11.56,5.4L54,117.82a12.49,12.49,0,0,1-.4,5.06c-4-2.12-6.35-2.43-8-.92l-5.75-5.29a2,2,0,0,1-.8-2.06c.23-1.11,1.76-1.31,2.5-2.51l-2.12-3c-.77-1.11-.46-.83.42-2A9.28,9.28,0,0,0,41.88,103h0c-10.62-3.71-13.08-11.6-19.5-20.21q-2.89-3.88-6-7.42a8,8,0,0,0,2.2-.22A12.82,12.82,0,0,0,23,79.77a9.14,9.14,0,0,0,6,1.15,10.16,10.16,0,0,0,5.77,4.21,15.44,15.44,0,0,0,8.94-.63c11-.51,18-3.35,21.58-8.14s3.63-11.6.69-19.77a1.31,1.31,0,0,0-1.68-.79,1.3,1.3,0,0,0-.79,1.67c2.64,7.32,2.72,13.23-.33,17.33s-9.48,6.64-19.76,7.08A1.53,1.53,0,0,0,43,82a13.21,13.21,0,0,1-7.59.66,7.9,7.9,0,0,1-4.69-3.76,1.32,1.32,0,0,0-1.42-.65,6.85,6.85,0,0,1-5-.69A11.18,11.18,0,0,1,20.36,73a1.31,1.31,0,0,0-1.6-.63,5.61,5.61,0,0,1-2.88.33,5.14,5.14,0,0,1-2.42-1.19,1.34,1.34,0,0,0-1.08-.29L9.82,68.62c-5.53-3-9-8.78-9.82-17.85l3,.92a6.21,6.21,0,0,1,.55-5.22,13.54,13.54,0,0,1,8.28-.12l-1.29-2.7a30.21,30.21,0,0,0,6.69,1.4c9.54.89,20.58-2.83,30.71-3.5,8.6-.57,15.23.25,19.17,3,1.52-5.44,2.95-16.92,4.36-30.13a21,21,0,0,1,1.41-6.77,12.43,12.43,0,0,1,4.8-5.51c5.65-4,15-1.8,19.76,2.78a14.74,14.74,0,0,1,3.3,4.72Z"/>
230
+ </svg>
231
+ New Goose
232
+ <% end %>
233
+ </ul>
234
+ </li>
235
+ <li class="-mx-6 mt-auto">
236
+ <%= link_to '#',
237
+ class: "flex items-center gap-x-4 px-6 py-3 text-sm font-medium text-gray-700
238
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50 hover:text-indigo-700
239
+ dark:text-gray-200 dark:hover:text-indigo-300 transition-all duration-200" do %>
240
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
241
+ stroke="currentColor" class="size-5 text-indigo-500 dark:text-indigo-400">
242
+ <path stroke-linecap="round" stroke-linejoin="round"
243
+ d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
244
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
245
+ </svg>
246
+ Settings
247
+ <% end %>
248
+ </li>
249
+ </ul>
250
+ </nav>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ </div>
255
+
256
+ <main class="w-full h-full max-h-screen lg:pl-72 bg-gray-50 dark:bg-gray-950 overflow-y-auto">
257
+ <%= yield %>
258
+ </main>
259
+ </div>
@@ -0,0 +1,22 @@
1
+ <%# locals: (channel:) %>
2
+
3
+ <li class="<%= dom_id(channel, :sidebar) %>">
4
+ <%= link_to channel, class: "group flex gap-x-3 rounded-lg p-2 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" do %>
5
+ <svg
6
+ class="size-5 shrink-0 text-gray-500 dark:text-gray-400 group-hover:text-indigo-600 dark:group-hover:text-indigo-400"
7
+ fill="none"
8
+ viewBox="0 0 24 24"
9
+ stroke-width="1.5"
10
+ stroke="currentColor"
11
+ aria-hidden="true"
12
+ data-slot="icon"
13
+ >
14
+ <path
15
+ stroke-linecap="round"
16
+ stroke-linejoin="round"
17
+ d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
18
+ />
19
+ </svg>
20
+ <span class="<%= dom_id(channel, :title) %>"><%= channel.name %></span>
21
+ <% end %>
22
+ </li>
@@ -0,0 +1,70 @@
1
+ <%# locals: (channel:) %>
2
+
3
+ <%= form_with model: channel, class: "space-y-6 max-w-2xl mx-auto" do |form| %>
4
+ <% if @channel.errors.any? %>
5
+ <div id="error_explanation"
6
+ class="bg-red-50 dark:bg-red-900/40 border border-red-200 dark:border-red-800/50
7
+ text-red-700 dark:text-red-300 px-4 py-3 rounded-xl mb-4
8
+ shadow-sm shadow-red-200/50 dark:shadow-red-900/20 transition-all duration-200">
9
+ <h2 class="font-semibold text-sm tracking-wide"><%= pluralize(@channel.errors.count, "error") %> prevented saving:</h2>
10
+ <ul class="list-disc pl-5 mt-2 text-sm">
11
+ <% @channel.errors.full_messages.each do |msg| %>
12
+ <li><%= msg %></li>
13
+ <% end %>
14
+ </ul>
15
+ </div>
16
+ <% end %>
17
+
18
+ <!-- Name Field -->
19
+ <div class="space-y-2">
20
+ <%= form.label :name,
21
+ class: "block text-sm font-medium text-gray-900 dark:text-gray-100" %>
22
+ <%= form.text_field :name,
23
+ required: true,
24
+ placeholder: "Enter channel name",
25
+ class: "block w-full px-4 py-2.5 bg-white dark:bg-gray-800/50
26
+ border border-gray-300 dark:border-gray-700/50 rounded-lg
27
+ shadow-sm focus:outline-none focus:ring-2
28
+ focus:ring-indigo-500 dark:focus:ring-indigo-400
29
+ focus:border-transparent text-gray-900 dark:text-gray-100
30
+ placeholder:text-gray-400 dark:placeholder:text-gray-500
31
+ hover:border-indigo-400 dark:hover:border-indigo-600
32
+ transition-all duration-200" %>
33
+ </div>
34
+
35
+ <!-- Gaggle Members Assignment -->
36
+ <div class="space-y-3">
37
+ <h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wide">
38
+ Assign Gaggle Members
39
+ </h3>
40
+ <div class="grid gap-2 sm:grid-cols-2">
41
+ <%= form.collection_check_boxes :goose_ids, Gaggle::Goose.all, :id, :name do |b| %>
42
+ <label class="flex items-center gap-x-3 p-2 rounded-lg
43
+ bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700/50
44
+ hover:bg-indigo-50 dark:hover:bg-indigo-900/50
45
+ hover:border-indigo-300 dark:hover:border-indigo-700/50
46
+ transition-all duration-200 cursor-pointer shadow-sm">
47
+ <%= b.check_box class: "h-4 w-4 text-indigo-600 dark:text-indigo-400
48
+ border-gray-300 dark:border-gray-600
49
+ rounded focus:ring-indigo-500 dark:focus:ring-indigo-400
50
+ bg-white dark:bg-gray-800" %>
51
+ <span class="text-sm text-gray-700 dark:text-gray-200"><%= b.text %></span>
52
+ </label>
53
+ <% end %>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Submit Button -->
58
+ <div class="pt-2">
59
+ <%= form.submit class: "w-full flex justify-center py-2.5 px-6
60
+ border border-transparent rounded-lg
61
+ shadow-md text-sm font-medium text-white
62
+ bg-indigo-600 hover:bg-indigo-700
63
+ dark:bg-indigo-500 dark:hover:bg-indigo-600
64
+ focus:outline-none focus:ring-2
65
+ focus:ring-offset-2 dark:focus:ring-offset-gray-900
66
+ focus:ring-indigo-500 disabled:opacity-50
67
+ disabled:cursor-not-allowed
68
+ hover:shadow-lg transition-all duration-300 cursor-pointer" %>
69
+ </div>
70
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <%= render "mobile_header" %>
2
+
3
+ <div class="max-w-lg mx-auto p-6">
4
+ <div class="mb-8">
5
+ <h1 class="text-xl font-medium text-gray-900 dark:text-gray-100">Update Channel</h1>
6
+ <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">Modify the channel settings and member assignments.</p>
7
+ </div>
8
+
9
+ <%= render 'form', channel: @channel %>
10
+ </div>
@@ -0,0 +1,12 @@
1
+ <div class="max-w-2xl mx-auto p-6 bg-white shadow rounded-lg">
2
+ <%= form_with url: channel_gooses_path(@channel), scope: :goose, method: :post, class: "space-y-4" do |form| %>
3
+ <div class="mb-4">
4
+ <%= form.label :id, "Select Goose", class: "block text-sm font-medium text-gray-700" %>
5
+ <%= form.select :id, options_from_collection_for_select(@missing_gooses, :id, :name), { prompt: "Choose a Goose" }, { class: "mt-1 block w-full border-gray-300 rounded-md shadow-sm" } %>
6
+ </div>
7
+
8
+ <div>
9
+ <%= form.submit "Add Goose", class: yass(btn: :blue) %>
10
+ </div>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,3 @@
1
+ json.array! @channels do |channel|
2
+ json.(channel, :id, :name, :goose_ids)
3
+ end
@@ -0,0 +1,2 @@
1
+ <h1>New Message for Thread</h1>
2
+ <p>This is the new message view for channel with ID: <%= params[:channel_id] %></p>
@@ -0,0 +1,8 @@
1
+ <div class="max-w-lg mx-auto p-6">
2
+ <div class="mb-8">
3
+ <h1 class="text-xl font-medium text-gray-900 dark:text-gray-100">New Channel</h1>
4
+ <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">Create a new channel and assign members.</p>
5
+ </div>
6
+
7
+ <%= render 'form', channel: @channel %>
8
+ </div>
@@ -0,0 +1,98 @@
1
+ <%= turbo_stream_from @channel %>
2
+
3
+ <div class="flex flex-col h-screen bg-white dark:bg-gray-950 transition-colors duration-200">
4
+ <!-- Header -->
5
+ <div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800
6
+ bg-white dark:bg-gray-900 shadow-sm">
7
+ <%= render "mobile_header"%>
8
+ <h1 class="text-xl font-semibold text-gray-900 dark:text-gray-100 truncate flex-1">
9
+ <%= @channel.name %>
10
+ </h1>
11
+
12
+ <div class="flex space-x-1">
13
+ <%= link_to [:edit, @channel],
14
+ class: "p-2 text-gray-600 hover:text-yellow-600 dark:text-yellow-400
15
+ dark:hover:text-yellow-300 hover:bg-gray-100 dark:hover:bg-gray-800
16
+ rounded-lg transition-all duration-150",
17
+ title: "Edit channel" do %>
18
+ <svg xmlns="http://www.w3.org/2000/svg"
19
+ fill="none"
20
+ viewBox="0 0 24 24"
21
+ stroke-width="1.5"
22
+ stroke="currentColor"
23
+ class="w-5 h-5">
24
+ <path stroke-linecap="round"
25
+ stroke-linejoin="round"
26
+ d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" />
27
+ </svg>
28
+ <% end %>
29
+
30
+ <%= button_to @channel,
31
+ method: :delete,
32
+ class: "p-2 text-gray-600 hover:text-red-600 dark:text-gray-400
33
+ dark:hover:text-red-500 hover:bg-gray-100 dark:hover:bg-gray-800
34
+ rounded-lg transition-all duration-150 cursor-pointer",
35
+ data: { turbo_confirm: "Are you sure you want to delete this channel?" },
36
+ title: "Delete channel" do %>
37
+ <svg xmlns="http://www.w3.org/2000/svg"
38
+ fill="none"
39
+ viewBox="0 0 24 24"
40
+ stroke-width="1.5"
41
+ stroke="currentColor"
42
+ class="w-5 h-5">
43
+ <path stroke-linecap="round"
44
+ stroke-linejoin="round"
45
+ d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
46
+ </svg>
47
+ <% end %>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Messages -->
52
+ <div id="messages"
53
+ class="flex-1 overflow-y-auto p-6 space-y-4 bg-gray-50 dark:bg-gray-950
54
+ flex flex-col-reverse">
55
+ <%= render @channel.messages.reverse %>
56
+ </div>
57
+
58
+ <!-- Message Input -->
59
+ <div id="new-message"
60
+ class="p-4 border-t border-gray-200 dark:border-gray-800
61
+ bg-white dark:bg-gray-900 shadow-sm">
62
+ <%= form_with(model: [@channel, Gaggle::Message.new],
63
+ class: "relative") do |form| %>
64
+ <div class="relative flex items-center">
65
+ <%= form.text_area :content,
66
+ rows: 1,
67
+ autofocus: true,
68
+ placeholder: "Type your message...",
69
+ class: "w-full px-4 py-2.5 pr-20 rounded-lg border border-gray-300 dark:border-gray-700/50
70
+ bg-white dark:bg-gray-800/70 text-gray-900 dark:text-gray-100
71
+ placeholder-gray-400 dark:placeholder-gray-500
72
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
73
+ dark:focus:ring-indigo-400 focus:border-transparent
74
+ resize-none min-h-[2.5rem] max-h-32 transition-all duration-200
75
+ scrollbar-thin scrollbar-thumb-gray-300 dark:scrollbar-thumb-gray-600
76
+ scrollbar-track-transparent hover:border-gray-400 dark:hover:border-gray-600 resize-y",
77
+ data: {
78
+ controller: "text-area-auto-expand chat-field-form",
79
+ text_area_auto_expand_max_rows_value: 5,
80
+ action: "keydown.enter->chat-field-form#submitForm:prevent"
81
+ } %>
82
+ <%= form.label :content,
83
+ class: "absolute right-2 px-3 py-1 bg-indigo-600 hover:bg-indigo-700
84
+ dark:bg-indigo-500 dark:hover:bg-indigo-600
85
+ text-white text-sm font-medium rounded-full
86
+ focus:outline-none focus:ring-2 focus:ring-indigo-500
87
+ dark:focus:ring-indigo-400 focus:ring-offset-2
88
+ dark:focus:ring-offset-gray-900
89
+ disabled:opacity-50 disabled:cursor-not-allowed
90
+ transition-all duration-200" do %>
91
+ <% form.submit '', class: 'hidden' %>
92
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
93
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
94
+ </svg>
95
+ <% end %>
96
+ </div>
97
+ <% end %>
98
+ </div>
@@ -0,0 +1,5 @@
1
+ json.name @channel.name
2
+ json.messages @channel.messages do |message|
3
+ next if @notification && message.updated_at < @notification.message.created_at
4
+ json.(message, :id, :content, :user_name)
5
+ end
@@ -0,0 +1 @@
1
+ <%= turbo_stream.update ".#{dom_id(@channel, :title)}", @channel.name %>
@@ -0,0 +1,65 @@
1
+ <div class="max-w-2xl mx-auto p-6 bg-white dark:bg-gray-900 rounded-xl shadow-lg
2
+ shadow-gray-200/50 dark:shadow-gray-800/20 transition-all duration-300">
3
+ <%= form_with model: @goose, local: true, class: "space-y-6" do |f| %>
4
+ <!-- Name Field -->
5
+ <div class="space-y-2">
6
+ <%= f.label :name,
7
+ class: "block text-sm font-medium text-gray-900 dark:text-gray-100" %>
8
+ <%= f.text_field :name,
9
+ placeholder: "Enter name",
10
+ class: "block w-full px-4 py-2.5 bg-white dark:bg-gray-800/50
11
+ border border-gray-300 dark:border-gray-700/50
12
+ rounded-lg shadow-sm focus:outline-none focus:ring-2
13
+ focus:ring-indigo-500 dark:focus:ring-indigo-400
14
+ focus:border-transparent text-gray-900 dark:text-gray-100
15
+ placeholder:text-gray-400 dark:placeholder:text-gray-500
16
+ hover:border-gray-400 dark:hover:border-gray-600
17
+ transition-all duration-200" %>
18
+ </div>
19
+
20
+ <!-- Preset Prompts Dropdown -->
21
+ <div class="space-y-2">
22
+ <label for="preset_prompts"
23
+ class="block text-sm font-medium text-gray-900 dark:text-gray-100">
24
+ Preset Prompts
25
+ </label>
26
+ <%= select_tag :preset_prompts,
27
+ options_for_select(Gaggle::Goose.personalities.map { |personality| [personality[:name], personality[:prompt]] }),
28
+ prompt: 'Choose a preset prompt',
29
+ class: "block w-full px-4 py-2.5 bg-white dark:bg-gray-800/50
30
+ border border-gray-300 dark:border-gray-700/50
31
+ rounded-lg shadow-sm focus:outline-none focus:ring-2
32
+ focus:ring-indigo-500 dark:focus:ring-indigo-400
33
+ focus:border-transparent text-gray-900 dark:text-gray-100
34
+ appearance-none cursor-pointer
35
+ hover:border-gray-400 dark:hover:border-gray-600
36
+ transition-all duration-200",
37
+ onchange: "const target = document.getElementById('#{f.field_id(:prompt)}');
38
+ target.value = this.options[this.selectedIndex].value;
39
+ target.dispatchEvent(new Event('input'));" %>
40
+ </div>
41
+
42
+ <!-- Prompt Textarea -->
43
+ <div class="space-y-2">
44
+ <%= f.label :prompt,
45
+ class: "block text-sm font-medium text-gray-900 dark:text-gray-100" %>
46
+ <%= f.text_area :prompt,
47
+ rows: 10,
48
+ placeholder: "Enter your prompt here...",
49
+ class: "block w-full px-4 py-2.5 bg-white dark:bg-gray-800/50
50
+ border border-gray-300 dark:border-gray-700/50
51
+ rounded-lg shadow-sm focus:outline-none focus:ring-2
52
+ focus:ring-indigo-500 dark:focus:ring-indigo-400
53
+ focus:border-transparent text-gray-900 dark:text-gray-100
54
+ placeholder:text-gray-400 dark:placeholder:text-gray-500
55
+ resize-y hover:border-gray-400 dark:hover:border-gray-600",
56
+ data: { controller: "text-area-auto-expand" } %>
57
+ </div>
58
+
59
+ <!-- Submit Button -->
60
+ <div>
61
+ <%= f.submit "Save",
62
+ class: yass(btn: :success, add: 'w-full') %>
63
+ </div>
64
+ <% end %>
65
+ </div>
@@ -0,0 +1,2 @@
1
+ <%= render "mobile_header" %>
2
+ <%= render "form", goose: @goose %>
@@ -0,0 +1,5 @@
1
+ json.key_format! camelize: :lower
2
+
3
+ json.array! @gooses do |goose|
4
+ json.(goose, :id, :name)
5
+ end
@@ -0,0 +1,2 @@
1
+ <%= render "mobile_header" %>
2
+ <%= render "form", goose: @goose %>