raif 1.0.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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +678 -0
  4. data/Rakefile +20 -0
  5. data/app/assets/builds/raif.css +74 -0
  6. data/app/assets/builds/raif_admin.css +266 -0
  7. data/app/assets/config/raif_manifest.js +1 -0
  8. data/app/assets/javascript/raif/controllers/conversations_controller.js +11 -0
  9. data/app/assets/javascript/raif/stream_actions/raif_scroll_to_bottom.js +12 -0
  10. data/app/assets/javascript/raif.js +10 -0
  11. data/app/assets/stylesheets/raif/admin/conversation.scss +64 -0
  12. data/app/assets/stylesheets/raif/loader.scss +85 -0
  13. data/app/assets/stylesheets/raif.scss +1 -0
  14. data/app/assets/stylesheets/raif_admin.scss +299 -0
  15. data/app/controllers/raif/admin/agents_controller.rb +17 -0
  16. data/app/controllers/raif/admin/application_controller.rb +20 -0
  17. data/app/controllers/raif/admin/conversations_controller.rb +17 -0
  18. data/app/controllers/raif/admin/model_completions_controller.rb +17 -0
  19. data/app/controllers/raif/admin/model_tool_invocations_controller.rb +17 -0
  20. data/app/controllers/raif/admin/tasks_controller.rb +23 -0
  21. data/app/controllers/raif/application_controller.rb +20 -0
  22. data/app/controllers/raif/conversation_entries_controller.rb +60 -0
  23. data/app/controllers/raif/conversations_controller.rb +58 -0
  24. data/app/helpers/raif/application_helper.rb +7 -0
  25. data/app/helpers/raif/shared/conversations_helper.rb +13 -0
  26. data/app/jobs/raif/application_job.rb +8 -0
  27. data/app/jobs/raif/conversation_entry_job.rb +30 -0
  28. data/app/models/raif/agent.rb +133 -0
  29. data/app/models/raif/agents/native_tool_calling_agent.rb +127 -0
  30. data/app/models/raif/agents/re_act_agent.rb +121 -0
  31. data/app/models/raif/agents/re_act_step.rb +33 -0
  32. data/app/models/raif/application_record.rb +14 -0
  33. data/app/models/raif/concerns/boolean_timestamp.rb +69 -0
  34. data/app/models/raif/concerns/has_available_model_tools.rb +13 -0
  35. data/app/models/raif/concerns/has_llm.rb +19 -0
  36. data/app/models/raif/concerns/has_requested_language.rb +20 -0
  37. data/app/models/raif/concerns/invokes_model_tools.rb +13 -0
  38. data/app/models/raif/concerns/llm_response_parsing.rb +44 -0
  39. data/app/models/raif/conversation.rb +67 -0
  40. data/app/models/raif/conversation_entry.rb +85 -0
  41. data/app/models/raif/llm.rb +88 -0
  42. data/app/models/raif/llms/anthropic.rb +120 -0
  43. data/app/models/raif/llms/bedrock_claude.rb +134 -0
  44. data/app/models/raif/llms/open_ai.rb +259 -0
  45. data/app/models/raif/model_completion.rb +28 -0
  46. data/app/models/raif/model_tool.rb +69 -0
  47. data/app/models/raif/model_tool_invocation.rb +43 -0
  48. data/app/models/raif/model_tools/agent_final_answer.rb +46 -0
  49. data/app/models/raif/model_tools/fetch_url.rb +57 -0
  50. data/app/models/raif/model_tools/wikipedia_search.rb +78 -0
  51. data/app/models/raif/task.rb +137 -0
  52. data/app/models/raif/user_tool_invocation.rb +29 -0
  53. data/app/views/layouts/raif/admin.html.erb +98 -0
  54. data/app/views/raif/admin/agents/_agent.html.erb +18 -0
  55. data/app/views/raif/admin/agents/_conversation_message.html.erb +15 -0
  56. data/app/views/raif/admin/agents/index.html.erb +33 -0
  57. data/app/views/raif/admin/agents/show.html.erb +131 -0
  58. data/app/views/raif/admin/conversations/_conversation.html.erb +7 -0
  59. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +34 -0
  60. data/app/views/raif/admin/conversations/index.html.erb +32 -0
  61. data/app/views/raif/admin/conversations/show.html.erb +56 -0
  62. data/app/views/raif/admin/model_completions/_model_completion.html.erb +9 -0
  63. data/app/views/raif/admin/model_completions/index.html.erb +34 -0
  64. data/app/views/raif/admin/model_completions/show.html.erb +117 -0
  65. data/app/views/raif/admin/model_tool_invocations/_model_tool_invocation.html.erb +16 -0
  66. data/app/views/raif/admin/model_tool_invocations/index.html.erb +33 -0
  67. data/app/views/raif/admin/model_tool_invocations/show.html.erb +66 -0
  68. data/app/views/raif/admin/tasks/_task.html.erb +19 -0
  69. data/app/views/raif/admin/tasks/index.html.erb +49 -0
  70. data/app/views/raif/admin/tasks/show.html.erb +176 -0
  71. data/app/views/raif/conversation_entries/_conversation_entry.html.erb +26 -0
  72. data/app/views/raif/conversation_entries/_form.html.erb +25 -0
  73. data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -0
  74. data/app/views/raif/conversation_entries/_form_with_user_tool_invocation.html.erb +18 -0
  75. data/app/views/raif/conversation_entries/_message.html.erb +17 -0
  76. data/app/views/raif/conversation_entries/_model_response_avatar.html.erb +1 -0
  77. data/app/views/raif/conversation_entries/_user_avatar.html.erb +1 -0
  78. data/app/views/raif/conversation_entries/create.turbo_stream.erb +11 -0
  79. data/app/views/raif/conversation_entries/new.turbo_stream.erb +6 -0
  80. data/app/views/raif/conversations/_available_user_tools.html.erb +11 -0
  81. data/app/views/raif/conversations/_full_conversation.html.erb +15 -0
  82. data/app/views/raif/conversations/show.html.erb +1 -0
  83. data/config/i18n-tasks.yml +181 -0
  84. data/config/importmap.rb +6 -0
  85. data/config/initializers/pagy.rb +14 -0
  86. data/config/locales/admin.en.yml +91 -0
  87. data/config/locales/en.yml +50 -0
  88. data/config/routes.rb +22 -0
  89. data/db/migrate/20250224234252_create_raif_tables.rb +114 -0
  90. data/lib/generators/raif/agent/agent_generator.rb +22 -0
  91. data/lib/generators/raif/agent/templates/agent.rb.tt +28 -0
  92. data/lib/generators/raif/conversation/conversation_generator.rb +27 -0
  93. data/lib/generators/raif/conversation/templates/conversation.rb.tt +37 -0
  94. data/lib/generators/raif/install/install_generator.rb +31 -0
  95. data/lib/generators/raif/install/templates/initializer.rb +81 -0
  96. data/lib/generators/raif/model_tool/model_tool_generator.rb +27 -0
  97. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +74 -0
  98. data/lib/generators/raif/task/task_generator.rb +28 -0
  99. data/lib/generators/raif/task/templates/application_task.rb.tt +7 -0
  100. data/lib/generators/raif/task/templates/task.rb.tt +52 -0
  101. data/lib/generators/raif/views_generator.rb +22 -0
  102. data/lib/raif/configuration.rb +82 -0
  103. data/lib/raif/default_llms.rb +37 -0
  104. data/lib/raif/engine.rb +86 -0
  105. data/lib/raif/errors/action_not_authorized_error.rb +8 -0
  106. data/lib/raif/errors/anthropic/api_error.rb +10 -0
  107. data/lib/raif/errors/invalid_config_error.rb +8 -0
  108. data/lib/raif/errors/invalid_conversation_type_error.rb +8 -0
  109. data/lib/raif/errors/invalid_user_tool_type_error.rb +8 -0
  110. data/lib/raif/errors/open_ai/api_error.rb +10 -0
  111. data/lib/raif/errors/open_ai/json_schema_error.rb +10 -0
  112. data/lib/raif/errors.rb +9 -0
  113. data/lib/raif/languages.rb +33 -0
  114. data/lib/raif/rspec.rb +7 -0
  115. data/lib/raif/utils/html_to_markdown_converter.rb +7 -0
  116. data/lib/raif/utils/readable_content_extractor.rb +61 -0
  117. data/lib/raif/utils.rb +6 -0
  118. data/lib/raif/version.rb +5 -0
  119. data/lib/raif.rb +65 -0
  120. data/lib/tasks/raif_tasks.rake +6 -0
  121. metadata +294 -0
@@ -0,0 +1,299 @@
1
+ // Variables
2
+ $primary-color: #3b82f6;
3
+ $secondary-color: #1e3a8a;
4
+ $accent-color: #0ea5e9;
5
+ $danger-color: #ef4444;
6
+ $warning-color: #f59e0b;
7
+ $light-color: #f0f9ff;
8
+ $border-color: #e2e8f0;
9
+ $text-color: #525f7f;
10
+ $text-muted: #8898aa;
11
+ $shadow-color: rgba(50, 50, 93, 0.1);
12
+ $radius: 4px;
13
+ $transition: all 0.2s ease;
14
+
15
+ // Import conversation styles
16
+ @import "raif/admin/conversation";
17
+
18
+ // Base styles
19
+ body.raif-admin {
20
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ color: $text-color;
22
+ background-color: #f8fafc;
23
+ line-height: 1.5;
24
+ font-size: 0.9375rem;
25
+ }
26
+
27
+ .raif-admin {
28
+
29
+ h1,
30
+ h2,
31
+ h3,
32
+ h4,
33
+ h5,
34
+ h6 {
35
+ color: $secondary-color;
36
+ font-weight: 600;
37
+ }
38
+
39
+ a {
40
+ color: $primary-color;
41
+ text-decoration: none;
42
+ transition: $transition;
43
+
44
+ &:hover {
45
+ color: darken($primary-color, 10%);
46
+ }
47
+ }
48
+
49
+ // Layout
50
+ .navbar {
51
+ box-shadow: 0 2px 5px 0 $shadow-color;
52
+ padding: 0.75rem 1.5rem;
53
+
54
+ &.navbar-dark {
55
+ background: linear-gradient(135deg, $secondary-color, darken($secondary-color, 10%)) !important;
56
+ }
57
+
58
+ .navbar-brand {
59
+ font-weight: 700;
60
+ letter-spacing: 0.5px;
61
+ }
62
+ }
63
+
64
+ .sidebar {
65
+ box-shadow: inset -1px 0 0 $border-color;
66
+ background-color: white !important;
67
+ padding-top: 1rem;
68
+
69
+ .nav-link {
70
+ color: $text-color;
71
+ border-radius: $radius;
72
+ margin: 0.25rem 0.75rem;
73
+ padding: 0.5rem 0.75rem;
74
+
75
+ &:hover {
76
+ background-color: $light-color;
77
+ }
78
+
79
+ &.active {
80
+ background-color: rgba($primary-color, 0.1);
81
+ color: $primary-color;
82
+ font-weight: 500;
83
+ }
84
+ }
85
+ }
86
+
87
+ main {
88
+ padding-top: 1.5rem;
89
+ padding-bottom: 3rem;
90
+ }
91
+
92
+ // Cards
93
+ .card {
94
+ border: none;
95
+ border-radius: $radius;
96
+ box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
97
+ margin-bottom: 1.5rem;
98
+ overflow: hidden;
99
+
100
+ .card-header {
101
+ background-color: white;
102
+ border-bottom: 1px solid $border-color;
103
+ padding: 1rem 1.25rem;
104
+
105
+ h5 {
106
+ margin-bottom: 0;
107
+ font-size: 1rem;
108
+ font-weight: 600;
109
+ }
110
+ }
111
+
112
+ .card-body {
113
+ padding: 1.25rem;
114
+ }
115
+
116
+ &.conversation-card {
117
+ .card-body {
118
+ background-color: #f8f9fa;
119
+ }
120
+ }
121
+ }
122
+
123
+ // Tables
124
+ .table {
125
+ margin-bottom: 0;
126
+
127
+ th {
128
+ font-weight: 600;
129
+ color: $secondary-color;
130
+ border-top: none;
131
+ padding: 1rem 0.75rem;
132
+ font-size: 0.8125rem;
133
+ text-transform: uppercase;
134
+ letter-spacing: 0.5px;
135
+ }
136
+
137
+ td {
138
+ padding: 1rem 0.75rem;
139
+ vertical-align: middle;
140
+ border-color: $border-color;
141
+ }
142
+
143
+ &.table-hover tbody tr:hover {
144
+ background-color: $light-color;
145
+ }
146
+
147
+ &.table-striped tbody tr:nth-of-type(odd) {
148
+ background-color: rgba($light-color, 0.5);
149
+ }
150
+ }
151
+
152
+ .table-responsive {
153
+ border-radius: $radius;
154
+ box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
155
+ background-color: white;
156
+ overflow: hidden;
157
+ }
158
+
159
+ // Badges
160
+ .badge {
161
+ font-weight: 500;
162
+ padding: 0.4em 0.65em;
163
+ font-size: 0.75em;
164
+ border-radius: 50rem;
165
+
166
+ &.bg-success {
167
+ background-color: $accent-color !important;
168
+ }
169
+
170
+ &.bg-danger {
171
+ background-color: $danger-color !important;
172
+ }
173
+
174
+ &.bg-warning {
175
+ background-color: $warning-color !important;
176
+ color: white !important;
177
+ }
178
+
179
+ &.bg-secondary {
180
+ background-color: $text-muted !important;
181
+ }
182
+ }
183
+
184
+ // Buttons
185
+ .btn {
186
+ font-weight: 500;
187
+ padding: 0.5rem 1rem;
188
+ border-radius: $radius;
189
+ transition: $transition;
190
+ box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
191
+
192
+ &.btn-outline-secondary {
193
+ color: $text-color;
194
+ border-color: $border-color;
195
+
196
+ &:hover {
197
+ background-color: $light-color;
198
+ color: $secondary-color;
199
+ border-color: $border-color;
200
+ }
201
+ }
202
+ }
203
+
204
+ // Pre and code blocks
205
+ pre {
206
+ background-color: $light-color;
207
+ border-radius: $radius;
208
+ padding: 1rem;
209
+ margin-bottom: 0;
210
+ font-size: 0.875rem;
211
+ color: $secondary-color;
212
+ border: 1px solid $border-color;
213
+ }
214
+
215
+ .pre-wrap {
216
+ white-space: pre-wrap;
217
+ }
218
+
219
+ // List groups
220
+ .list-group-item {
221
+ border-color: $border-color;
222
+ padding: 1rem;
223
+
224
+ &:first-child {
225
+ border-top-left-radius: $radius;
226
+ border-top-right-radius: $radius;
227
+ }
228
+
229
+ &:last-child {
230
+ border-bottom-left-radius: $radius;
231
+ border-bottom-right-radius: $radius;
232
+ }
233
+ }
234
+
235
+ // Pagination
236
+ .pagination {
237
+ .page-link {
238
+ color: $text-color;
239
+ border-color: $border-color;
240
+ padding: 0.5rem 0.75rem;
241
+
242
+ &:hover {
243
+ background-color: $light-color;
244
+ border-color: $border-color;
245
+ color: $secondary-color;
246
+ }
247
+ }
248
+
249
+ .page-item.active .page-link {
250
+ background-color: $primary-color;
251
+ border-color: $primary-color;
252
+ }
253
+ }
254
+
255
+ // Detail rows
256
+ .row.mb-3 {
257
+ .col-md-3 {
258
+ strong {
259
+ color: $secondary-color;
260
+ }
261
+ }
262
+ }
263
+
264
+ // Alerts
265
+ .alert {
266
+ border: none;
267
+ border-radius: $radius;
268
+ box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
269
+
270
+ &.alert-info {
271
+ background-color: rgba($primary-color, 0.1);
272
+ color: $primary-color;
273
+ }
274
+ }
275
+
276
+ // Text utilities
277
+ .text-muted {
278
+ color: $text-muted !important;
279
+ }
280
+
281
+ // Responsive adjustments
282
+ @media (max-width: 767.98px) {
283
+ .sidebar {
284
+ position: static;
285
+ height: auto;
286
+ padding-top: 0;
287
+
288
+ .nav-link {
289
+ margin: 0;
290
+ border-radius: 0;
291
+ }
292
+ }
293
+
294
+ main {
295
+ padding-top: 1rem;
296
+ }
297
+ }
298
+
299
+ } // end .raif-admin
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Admin
5
+ class AgentsController < Raif::Admin::ApplicationController
6
+ include Pagy::Backend
7
+
8
+ def index
9
+ @pagy, @agents = pagy(Raif::Agent.order(created_at: :desc))
10
+ end
11
+
12
+ def show
13
+ @agent = Raif::Agent.find(params[:id])
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Admin
5
+ class ApplicationController < Raif::ApplicationController
6
+ include Pagy::Backend
7
+
8
+ layout "raif/admin"
9
+
10
+ private
11
+
12
+ def authorize_raif_action
13
+ unless instance_exec(&Raif.config.authorize_admin_controller_action)
14
+ raise Raif::Errors::ActionNotAuthorizedError, "Admin action not authorized"
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Admin
5
+ class ConversationsController < Raif::Admin::ApplicationController
6
+ include Pagy::Backend
7
+
8
+ def index
9
+ @pagy, @conversations = pagy(Raif::Conversation.order(created_at: :desc))
10
+ end
11
+
12
+ def show
13
+ @conversation = Raif::Conversation.find(params[:id])
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Admin
5
+ class ModelCompletionsController < Raif::Admin::ApplicationController
6
+ include Pagy::Backend
7
+
8
+ def index
9
+ @pagy, @model_completions = pagy(Raif::ModelCompletion.order(created_at: :desc))
10
+ end
11
+
12
+ def show
13
+ @model_completion = Raif::ModelCompletion.find(params[:id])
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Admin
5
+ class ModelToolInvocationsController < Raif::Admin::ApplicationController
6
+ include Pagy::Backend
7
+
8
+ def index
9
+ @pagy, @model_tool_invocations = pagy(Raif::ModelToolInvocation.order(created_at: :desc))
10
+ end
11
+
12
+ def show
13
+ @model_tool_invocation = Raif::ModelToolInvocation.find(params[:id])
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Admin
5
+ class TasksController < Raif::Admin::ApplicationController
6
+ include Pagy::Backend
7
+
8
+ def index
9
+ @task_types = Raif::Task.distinct.pluck(:type)
10
+ @selected_types = params[:task_types] || []
11
+
12
+ tasks = Raif::Task.order(created_at: :desc)
13
+ tasks = tasks.where(type: @selected_types) if @selected_types.present?
14
+
15
+ @pagy, @tasks = pagy(tasks)
16
+ end
17
+
18
+ def show
19
+ @task = Raif::Task.includes(:raif_model_completion).find(params[:id])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ class ApplicationController < ::ApplicationController
5
+
6
+ before_action :authorize_raif_action
7
+
8
+ def raif_current_user
9
+ send(Raif.config.current_user_method) if respond_to?(Raif.config.current_user_method)
10
+ end
11
+
12
+ private
13
+
14
+ def authorize_raif_action
15
+ unless instance_exec(&Raif.config.authorize_controller_action)
16
+ raise Raif::Errors::ActionNotAuthorizedError, "#{self.class.name}##{action_name} not authorized"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::ConversationEntriesController < Raif::ApplicationController
4
+ before_action :set_conversation
5
+
6
+ def new
7
+ @conversation_entry = @conversation.entries.new
8
+
9
+ @form_partial = if params[:user_tool_type]
10
+ @conversation_entry.raif_user_tool_invocation = user_tool_type.new
11
+ "raif/conversation_entries/form_with_user_tool_invocation"
12
+ else
13
+ "raif/conversation_entries/form_with_available_tools"
14
+ end
15
+ end
16
+
17
+ def create
18
+ user_tool_invocation = if params[:user_tool_type].present?
19
+ user_tool_params = params[:conversation_entry].delete(:raif_user_tool_invocation_attributes)
20
+ user_tool_type.new(user_tool_params.permit(user_tool_type.tool_params))
21
+ end
22
+
23
+ @conversation_entry = @conversation.entries.new(conversation_entry_params)
24
+ @conversation_entry.raif_user_tool_invocation = user_tool_invocation
25
+ @conversation_entry.creator = current_user
26
+
27
+ if @conversation_entry.save
28
+ Raif::ConversationEntryJob.perform_later(conversation_entry: @conversation_entry)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def set_conversation
35
+ @conversation = Raif::Conversation.find(params[:conversation_id])
36
+ end
37
+
38
+ def conversation_entry_params
39
+ params.require(:conversation_entry).permit(:user_message)
40
+ end
41
+
42
+ def user_tool_type
43
+ @user_tool_type ||= begin
44
+ unless Raif.config.user_tool_types.include?(params[:user_tool_type])
45
+ raise Raif::Errors::InvalidUserToolTypeError,
46
+ "Invalid Raif user tool type - not in Raif.config.user_tool_types: #{params[:user_tool_type]}"
47
+ end
48
+
49
+ user_tool_type = params[:user_tool_type].constantize
50
+
51
+ unless user_tool_type.ancestors.include?(Raif::UserToolInvocation)
52
+ raise Raif::Errors::InvalidUserToolTypeError,
53
+ "Invalid Raif user tool type - not a descendant of Raif::UserToolInvocation: #{params[:user_tool_type]}"
54
+ end
55
+
56
+ user_tool_type
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::ConversationsController < Raif::ApplicationController
4
+ before_action :validate_conversation_type
5
+
6
+ def show
7
+ @conversations = conversations_scope
8
+
9
+ @conversation = if params[:id] == "latest"
10
+ if @conversations.any?
11
+ @conversations.first
12
+ else
13
+ conversation = build_new_conversation
14
+ conversation.save!
15
+ conversation
16
+ end
17
+ else
18
+ @conversations.find(params[:id])
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def build_new_conversation
25
+ raif_conversation_type.new(creator: raif_current_user)
26
+ end
27
+
28
+ def conversations_scope
29
+ raif_conversation_type.newest_first.where(creator: raif_current_user)
30
+ end
31
+
32
+ def conversation_type_param
33
+ params[:conversation_type].presence || "Raif::Conversation"
34
+ end
35
+
36
+ def validate_conversation_type
37
+ head :bad_request unless Raif.config.conversation_types.include?(conversation_type_param)
38
+ end
39
+
40
+ def raif_conversation_type
41
+ @raif_conversation_type ||= begin
42
+ unless Raif.config.conversation_types.include?(conversation_type_param)
43
+ raise Raif::Errors::InvalidConversationTypeError,
44
+ "Invalid Raif conversation type - not in Raif.config.conversation_types: #{conversation_type_param}"
45
+ end
46
+
47
+ conversation_type = conversation_type_param.constantize
48
+
49
+ unless conversation_type == Raif::Conversation || conversation_type.ancestors.include?(Raif::Conversation)
50
+ raise Raif::Errors::InvalidConversationTypeError,
51
+ "Invalid Raif conversation type - not a descendant of Raif::Conversation: #{conversation_type_param}"
52
+ end
53
+
54
+ conversation_type
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module ApplicationHelper
5
+ include Pagy::Frontend
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Shared
5
+ module ConversationsHelper
6
+
7
+ def raif_conversation(conversation)
8
+ render "raif/conversations/full_conversation", conversation: conversation
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ class ApplicationJob < ::ApplicationJob
5
+ include ActionView::RecordIdentifier
6
+
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ class ConversationEntryJob < ApplicationJob
5
+
6
+ before_enqueue do |job|
7
+ conversation_entry = job.arguments.first[:conversation_entry]
8
+ conversation_entry.update_columns(started_at: Time.current)
9
+ end
10
+
11
+ def perform(conversation_entry:)
12
+ conversation = conversation_entry.raif_conversation
13
+ conversation_entry.process_entry!
14
+ conversation_entry.broadcast_replace_to conversation
15
+
16
+ Turbo::StreamsChannel.broadcast_action_to(
17
+ conversation,
18
+ action: :raif_scroll_to_bottom,
19
+ target: dom_id(conversation, :entries)
20
+ )
21
+ rescue StandardError => e
22
+ logger.error "Error processing conversation entry: #{e.message}"
23
+ logger.error e.backtrace.join("\n")
24
+
25
+ conversation_entry.failed!
26
+ conversation_entry.broadcast_replace_to conversation
27
+ end
28
+
29
+ end
30
+ end