raif 1.0.0 → 1.2.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +346 -43
  3. data/app/assets/builds/raif.css +26 -1
  4. data/app/assets/stylesheets/raif/admin/stats.scss +12 -0
  5. data/app/assets/stylesheets/raif/loader.scss +27 -1
  6. data/app/controllers/raif/admin/application_controller.rb +14 -0
  7. data/app/controllers/raif/admin/stats/tasks_controller.rb +25 -0
  8. data/app/controllers/raif/admin/stats_controller.rb +19 -0
  9. data/app/controllers/raif/admin/tasks_controller.rb +18 -2
  10. data/app/controllers/raif/conversations_controller.rb +5 -1
  11. data/app/models/raif/agent.rb +11 -9
  12. data/app/models/raif/agents/native_tool_calling_agent.rb +11 -1
  13. data/app/models/raif/agents/re_act_agent.rb +6 -0
  14. data/app/models/raif/concerns/has_available_model_tools.rb +1 -1
  15. data/app/models/raif/concerns/json_schema_definition.rb +28 -0
  16. data/app/models/raif/concerns/llm_response_parsing.rb +42 -14
  17. data/app/models/raif/concerns/llm_temperature.rb +17 -0
  18. data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +51 -0
  19. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +56 -0
  20. data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +70 -0
  21. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +37 -0
  22. data/app/models/raif/concerns/llms/message_formatting.rb +42 -0
  23. data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +138 -0
  24. data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +41 -0
  25. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +26 -0
  26. data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +43 -0
  27. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +42 -0
  28. data/app/models/raif/conversation.rb +28 -7
  29. data/app/models/raif/conversation_entry.rb +40 -8
  30. data/app/models/raif/embedding_model.rb +22 -0
  31. data/app/models/raif/embedding_models/bedrock.rb +34 -0
  32. data/app/models/raif/embedding_models/open_ai.rb +40 -0
  33. data/app/models/raif/llm.rb +108 -9
  34. data/app/models/raif/llms/anthropic.rb +72 -57
  35. data/app/models/raif/llms/bedrock.rb +165 -0
  36. data/app/models/raif/llms/open_ai_base.rb +66 -0
  37. data/app/models/raif/llms/open_ai_completions.rb +100 -0
  38. data/app/models/raif/llms/open_ai_responses.rb +144 -0
  39. data/app/models/raif/llms/open_router.rb +88 -0
  40. data/app/models/raif/model_completion.rb +23 -2
  41. data/app/models/raif/model_file_input.rb +113 -0
  42. data/app/models/raif/model_image_input.rb +4 -0
  43. data/app/models/raif/model_tool.rb +82 -52
  44. data/app/models/raif/model_tool_invocation.rb +8 -6
  45. data/app/models/raif/model_tools/agent_final_answer.rb +18 -27
  46. data/app/models/raif/model_tools/fetch_url.rb +27 -36
  47. data/app/models/raif/model_tools/provider_managed/base.rb +9 -0
  48. data/app/models/raif/model_tools/provider_managed/code_execution.rb +5 -0
  49. data/app/models/raif/model_tools/provider_managed/image_generation.rb +5 -0
  50. data/app/models/raif/model_tools/provider_managed/web_search.rb +5 -0
  51. data/app/models/raif/model_tools/wikipedia_search.rb +46 -55
  52. data/app/models/raif/streaming_responses/anthropic.rb +63 -0
  53. data/app/models/raif/streaming_responses/bedrock.rb +89 -0
  54. data/app/models/raif/streaming_responses/open_ai_completions.rb +76 -0
  55. data/app/models/raif/streaming_responses/open_ai_responses.rb +54 -0
  56. data/app/models/raif/task.rb +71 -16
  57. data/app/views/layouts/raif/admin.html.erb +10 -0
  58. data/app/views/raif/admin/agents/show.html.erb +3 -1
  59. data/app/views/raif/admin/conversations/_conversation.html.erb +1 -1
  60. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +48 -0
  61. data/app/views/raif/admin/conversations/show.html.erb +4 -2
  62. data/app/views/raif/admin/model_completions/_model_completion.html.erb +8 -0
  63. data/app/views/raif/admin/model_completions/index.html.erb +2 -0
  64. data/app/views/raif/admin/model_completions/show.html.erb +58 -3
  65. data/app/views/raif/admin/stats/index.html.erb +128 -0
  66. data/app/views/raif/admin/stats/tasks/index.html.erb +45 -0
  67. data/app/views/raif/admin/tasks/_task.html.erb +5 -4
  68. data/app/views/raif/admin/tasks/index.html.erb +20 -2
  69. data/app/views/raif/admin/tasks/show.html.erb +3 -1
  70. data/app/views/raif/conversation_entries/_citations.html.erb +9 -0
  71. data/app/views/raif/conversation_entries/_conversation_entry.html.erb +22 -14
  72. data/app/views/raif/conversation_entries/_form.html.erb +1 -1
  73. data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -4
  74. data/app/views/raif/conversation_entries/_message.html.erb +14 -3
  75. data/config/locales/admin.en.yml +16 -0
  76. data/config/locales/en.yml +47 -3
  77. data/config/routes.rb +6 -0
  78. data/db/migrate/20250224234252_create_raif_tables.rb +1 -1
  79. data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +7 -0
  80. data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +14 -0
  81. data/db/migrate/20250424232946_add_created_at_indexes.rb +11 -0
  82. data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +14 -0
  83. data/db/migrate/20250507155314_add_retry_count_to_raif_model_completions.rb +7 -0
  84. data/db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb +14 -0
  85. data/db/migrate/20250603140622_add_citations_to_raif_model_completions.rb +13 -0
  86. data/db/migrate/20250603202013_add_stream_response_to_raif_model_completions.rb +7 -0
  87. data/lib/generators/raif/agent/agent_generator.rb +22 -12
  88. data/lib/generators/raif/agent/templates/agent.rb.tt +3 -3
  89. data/lib/generators/raif/agent/templates/application_agent.rb.tt +7 -0
  90. data/lib/generators/raif/conversation/conversation_generator.rb +10 -0
  91. data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +7 -0
  92. data/lib/generators/raif/conversation/templates/conversation.rb.tt +16 -14
  93. data/lib/generators/raif/install/templates/initializer.rb +62 -6
  94. data/lib/generators/raif/model_tool/model_tool_generator.rb +0 -5
  95. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +69 -56
  96. data/lib/generators/raif/task/templates/task.rb.tt +34 -23
  97. data/lib/raif/configuration.rb +63 -4
  98. data/lib/raif/embedding_model_registry.rb +83 -0
  99. data/lib/raif/engine.rb +56 -7
  100. data/lib/raif/errors/{open_ai/api_error.rb → invalid_model_file_input_error.rb} +1 -3
  101. data/lib/raif/errors/{anthropic/api_error.rb → invalid_model_image_input_error.rb} +1 -3
  102. data/lib/raif/errors/streaming_error.rb +18 -0
  103. data/lib/raif/errors/unsupported_feature_error.rb +8 -0
  104. data/lib/raif/errors.rb +4 -2
  105. data/lib/raif/json_schema_builder.rb +104 -0
  106. data/lib/raif/llm_registry.rb +315 -0
  107. data/lib/raif/migration_checker.rb +74 -0
  108. data/lib/raif/utils/html_fragment_processor.rb +169 -0
  109. data/lib/raif/utils.rb +1 -0
  110. data/lib/raif/version.rb +1 -1
  111. data/lib/raif.rb +7 -32
  112. data/lib/tasks/raif_tasks.rake +9 -4
  113. metadata +62 -12
  114. data/app/models/raif/llms/bedrock_claude.rb +0 -134
  115. data/app/models/raif/llms/open_ai.rb +0 -259
  116. data/lib/raif/default_llms.rb +0 -37
@@ -32,6 +32,16 @@
32
32
  <div class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
33
33
  <div class="position-sticky pt-3">
34
34
  <ul class="nav flex-column">
35
+ <li class="nav-item">
36
+ <a class="nav-link <%= current_page?(raif.admin_stats_path) ? "active" : "" %>" href="<%= raif.admin_stats_path %>">
37
+ <span class="d-inline-block me-2">
38
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bar-chart" viewBox="0 0 16 16">
39
+ <path d="M4 11H2v3h2v-3zm5-4H7v7h2V7zm5-5v12h-2V2h2zm-2-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1h-2zM6 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm-5 4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3z" />
40
+ </svg>
41
+ </span>
42
+ <%= t("raif.admin.common.stats") %>
43
+ </a>
44
+ </li>
35
45
  <li class="nav-item">
36
46
  <a class="nav-link <%= current_page?(raif.admin_tasks_path) ? "active" : "" %>" href="<%= raif.admin_tasks_path %>">
37
47
  <span class="d-inline-block me-2">
@@ -18,7 +18,9 @@
18
18
  </div>
19
19
  <div class="row mb-3">
20
20
  <div class="col-md-3"><strong><%= t("raif.admin.common.creator") %>:</strong></div>
21
- <div class="col-md-9"><%= @agent.creator_type %> #<%= @agent.creator_id %></div>
21
+ <div class="col-md-9">
22
+ <%= @agent.creator.try(:raif_display_name) || "#{@agent.creator_type} ##{@agent.creator_id}" %>
23
+ </div>
22
24
  </div>
23
25
  <div class="row mb-3">
24
26
  <div class="col-md-3"><strong><%= t("raif.admin.common.status") %>:</strong></div>
@@ -1,7 +1,7 @@
1
1
  <tr id="<%= dom_id(conversation) %>" class="raif-conversation">
2
2
  <td><%= link_to "##{conversation.id}", raif.admin_conversation_path(conversation) %></td>
3
3
  <td><small class="text-muted"><%= conversation.created_at.rfc822 %></small></td>
4
- <td><%= conversation.creator_type %> #<%= conversation.creator_id %></td>
4
+ <td><%= conversation.creator.try(:raif_display_name) || "#{conversation.creator_type} ##{conversation.creator_id}" %></td>
5
5
  <td><%= conversation.type %></td>
6
6
  <td><%= conversation.conversation_entries_count %></td>
7
7
  </tr>
@@ -21,6 +21,7 @@
21
21
  <%= link_to t("raif.admin.common.model_completion"), raif.admin_model_completion_path(entry.raif_model_completion) %>
22
22
  <% end %>
23
23
  </div>
24
+
24
25
  <div class="mb-3">
25
26
  <strong><%= t("raif.admin.common.user_message") %>:</strong>
26
27
  <pre class="mt-2"><%= entry.user_message %></pre>
@@ -31,4 +32,51 @@
31
32
  <pre class="mt-2"><%= entry.model_response_message %></pre>
32
33
  </div>
33
34
  <% end %>
35
+
36
+ <% if entry.raif_model_tool_invocations.any? %>
37
+ <div class="mb-3">
38
+ <strong><%= t("raif.admin.common.model_tool_invocations") %>:</strong>
39
+ <div class="ms-3 mt-2">
40
+ <% entry.raif_model_tool_invocations.each do |invocation| %>
41
+ <div class="card mb-2">
42
+ <div class="card-body p-3">
43
+ <div class="d-flex w-100 justify-content-between align-items-start">
44
+ <div>
45
+ <h6 class="mb-1">
46
+ <%= link_to invocation.tool_name, raif.admin_model_tool_invocation_path(invocation) %>
47
+ </h6>
48
+ <% if invocation.completed_at? %>
49
+ <span class="badge bg-success"><%= t("raif.admin.common.completed") %></span>
50
+ <% elsif invocation.failed_at? %>
51
+ <span class="badge bg-danger"><%= t("raif.admin.common.failed") %></span>
52
+ <% else %>
53
+ <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
54
+ <% end %>
55
+ </div>
56
+ <small class="text-muted"><%= invocation.created_at.rfc822 %></small>
57
+ </div>
58
+ <div class="mt-2">
59
+ <strong><%= t("raif.admin.common.arguments") %>:</strong>
60
+ <pre class="pre-wrap mt-1"><%= begin
61
+ JSON.pretty_generate(invocation.tool_arguments)
62
+ rescue StandardError
63
+ invocation.tool_arguments
64
+ end %></pre>
65
+ </div>
66
+ <% if invocation.result.present? %>
67
+ <div class="mt-2">
68
+ <strong><%= t("raif.admin.common.result") %>:</strong>
69
+ <pre class="pre-wrap mt-1"><%= begin
70
+ JSON.pretty_generate(invocation.result)
71
+ rescue StandardError
72
+ invocation.result
73
+ end %></pre>
74
+ </div>
75
+ <% end %>
76
+ </div>
77
+ </div>
78
+ <% end %>
79
+ </div>
80
+ </div>
81
+ <% end %>
34
82
  </div>
@@ -14,7 +14,9 @@
14
14
  </div>
15
15
  <div class="row mb-3">
16
16
  <div class="col-md-3"><strong><%= t("raif.admin.common.creator") %>:</strong></div>
17
- <div class="col-md-9"><%= @conversation.creator_type %> #<%= @conversation.creator_id %></div>
17
+ <div class="col-md-9">
18
+ <%= @conversation.creator.try(:raif_display_name) || "#{@conversation.creator_type} ##{@conversation.creator_id}" %>
19
+ </div>
18
20
  </div>
19
21
  <div class="row mb-3">
20
22
  <div class="col-md-3"><strong><%= t("raif.admin.common.type") %>:</strong></div>
@@ -47,7 +49,7 @@
47
49
  </div>
48
50
  <div class="card-body">
49
51
  <div class="list-group">
50
- <%= render collection: @conversation.entries.order(created_at: :asc).includes(:raif_model_completion),
52
+ <%= render collection: @conversation.entries.order(created_at: :asc).includes(:raif_model_completion, :raif_model_tool_invocations),
51
53
  partial: "raif/admin/conversations/conversation_entry",
52
54
  as: :entry %>
53
55
  </div>
@@ -5,5 +5,13 @@
5
5
  <td><%= model_completion.llm_model_key %></td>
6
6
  <td><%= model_completion.response_format %></td>
7
7
  <td><%= model_completion.total_tokens ? number_with_delimiter(model_completion.total_tokens) : "-" %></td>
8
+ <td><%= model_completion.total_cost ? number_to_currency(model_completion.total_cost, precision: 6) : "-" %></td>
9
+ <td>
10
+ <% if model_completion.citations.present? %>
11
+ <span class="badge bg-info"><%= model_completion.citations.length %></span>
12
+ <% else %>
13
+ <span class="text-muted">-</span>
14
+ <% end %>
15
+ </td>
8
16
  <td><small class="text-muted"><%= truncate(model_completion.raw_response, length: 100) %></small></td>
9
17
  </tr>
@@ -13,6 +13,8 @@
13
13
  <th><%= t("raif.admin.common.model") %></th>
14
14
  <th><%= t("raif.admin.common.response_format") %></th>
15
15
  <th><%= t("raif.admin.common.total_tokens") %></th>
16
+ <th><%= t("raif.admin.common.total_cost") %></th>
17
+ <th><%= t("raif.admin.common.citations") %></th>
16
18
  <th><%= t("raif.admin.common.response") %></th>
17
19
  </tr>
18
20
  </thead>
@@ -34,15 +34,42 @@
34
34
  </div>
35
35
  <div class="row mb-3">
36
36
  <div class="col-md-3"><strong><%= t("raif.admin.common.prompt_tokens") %>:</strong></div>
37
- <div class="col-md-9"><%= @model_completion.prompt_tokens ? number_with_delimiter(@model_completion.prompt_tokens) : "-" %></div>
37
+ <div class="col-md-9">
38
+ <% if @model_completion.prompt_tokens %>
39
+ <%= number_with_delimiter(@model_completion.prompt_tokens) %>
40
+ <% if @model_completion.prompt_token_cost %>
41
+ <small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@model_completion.prompt_token_cost, precision: 6) %>)</small>
42
+ <% end %>
43
+ <% else %>
44
+ -
45
+ <% end %>
46
+ </div>
38
47
  </div>
39
48
  <div class="row mb-3">
40
49
  <div class="col-md-3"><strong><%= t("raif.admin.common.completion_tokens") %>:</strong></div>
41
- <div class="col-md-9"><%= @model_completion.completion_tokens ? number_with_delimiter(@model_completion.completion_tokens) : "-" %></div>
50
+ <div class="col-md-9">
51
+ <% if @model_completion.completion_tokens %>
52
+ <%= number_with_delimiter(@model_completion.completion_tokens) %>
53
+ <% if @model_completion.output_token_cost %>
54
+ <small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@model_completion.output_token_cost, precision: 6) %>)</small>
55
+ <% end %>
56
+ <% else %>
57
+ -
58
+ <% end %>
59
+ </div>
42
60
  </div>
43
61
  <div class="row mb-3">
44
62
  <div class="col-md-3"><strong><%= t("raif.admin.common.total_tokens") %>:</strong></div>
45
- <div class="col-md-9"><%= @model_completion.total_tokens ? number_with_delimiter(@model_completion.total_tokens) : "-" %></div>
63
+ <div class="col-md-9">
64
+ <% if @model_completion.total_tokens %>
65
+ <%= number_with_delimiter(@model_completion.total_tokens) %>
66
+ <% if @model_completion.total_cost %>
67
+ <small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@model_completion.total_cost, precision: 6) %>)</small>
68
+ <% end %>
69
+ <% else %>
70
+ -
71
+ <% end %>
72
+ </div>
46
73
  </div>
47
74
  </div>
48
75
  </div>
@@ -115,3 +142,31 @@
115
142
  <% end %>
116
143
  </div>
117
144
  </div>
145
+
146
+ <div class="card mb-4">
147
+ <div class="card-header">
148
+ <h5 class="mb-0"><%= t("raif.admin.common.citations") %></h5>
149
+ </div>
150
+ <div class="card-body">
151
+ <% if @model_completion.citations.present? %>
152
+ <% @model_completion.citations.each_with_index do |citation, index| %>
153
+ <div class="mb-3 p-3 border rounded">
154
+ <div class="d-flex align-items-start">
155
+ <span class="badge bg-primary me-3"><%= index + 1 %></span>
156
+ <div class="flex-grow-1">
157
+ <h6 class="mb-1">
158
+ <a href="<%= citation["url"] %>" target="_blank" rel="noopener" class="text-decoration-none">
159
+ <%= citation["title"] %>
160
+ <i class="bi bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
161
+ </a>
162
+ </h6>
163
+ <small class="text-muted"><%= citation["url"] %></small>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ <% end %>
168
+ <% else %>
169
+ <p class="text-muted mb-0"><%= t("raif.admin.common.no_citations") %></p>
170
+ <% end %>
171
+ </div>
172
+ </div>
@@ -0,0 +1,128 @@
1
+ <div class="d-flex justify-content-between align-items-center my-4">
2
+ <h1 class="mb-0"><%= t("raif.admin.common.stats") %></h1>
3
+
4
+ <div class="period-filter">
5
+ <%= form_tag raif.admin_stats_path, method: :get, id: "period_filter_form", class: "d-flex align-items-center" do %>
6
+ <%= select_tag :period,
7
+ options_for_select(
8
+ [
9
+ [t("raif.admin.common.period_day"), "day"],
10
+ [t("raif.admin.common.period_week"), "week"],
11
+ [t("raif.admin.common.period_month"), "month"],
12
+ [t("raif.admin.common.period_all"), "all"]
13
+ ],
14
+ @selected_period
15
+ ),
16
+ class: "form-select form-select-sm me-2" %>
17
+ <%= submit_tag t("raif.admin.common.update"), class: "btn btn-sm btn-primary" %>
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="row">
23
+ <div class="col-12">
24
+ <div class="card shadow-sm mb-4">
25
+ <div class="card-body">
26
+ <div class="row g-4">
27
+ <!-- Model Completions -->
28
+ <div class="col-md-4 col-lg-4">
29
+ <div class="stats-card p-3 border rounded shadow-sm h-100">
30
+ <div class="d-flex align-items-center mb-2">
31
+ <span class="stats-icon bg-primary-subtle text-primary rounded p-2 me-3">
32
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-robot" viewBox="0 0 16 16">
33
+ <path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5ZM3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.58 26.58 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.933.933 0 0 1-.765.935c-.845.147-2.34.346-4.235.346-1.895 0-3.39-.2-4.235-.346A.933.933 0 0 1 3 9.219V8.062Zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a24.767 24.767 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25.286 25.286 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135Z" />
34
+ <path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2V1.866ZM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5Z" />
35
+ </svg>
36
+ </span>
37
+ <div>
38
+ <h6 class="mb-0 text-muted"><%= t("raif.admin.common.model_completions") %></h6>
39
+ </div>
40
+ </div>
41
+ <div class="d-flex justify-content-between align-items-center">
42
+ <h3 class="fw-bold mb-0"><%= number_with_delimiter(@model_completion_count) %></h3>
43
+ <p class="text-muted mt-2 mb-0"><small><%= t("raif.admin.common.est_cost") %>: <%= number_to_currency(@model_completion_total_cost, precision: 6) %></small></p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <!-- Tasks -->
49
+ <div class="col-md-4 col-lg-4">
50
+ <div class="stats-card p-3 border rounded shadow-sm h-100">
51
+ <div class="d-flex align-items-center mb-2">
52
+ <span class="stats-icon bg-success-subtle text-success rounded p-2 me-3">
53
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-list-check" viewBox="0 0 16 16">
54
+ <path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z" />
55
+ </svg>
56
+ </span>
57
+ <div>
58
+ <h6 class="mb-0 text-muted"><%= t("raif.admin.common.tasks") %></h6>
59
+ </div>
60
+ </div>
61
+ <div class="d-flex justify-content-between align-items-center">
62
+ <h3 class="fw-bold mb-0"><%= number_with_delimiter(@task_count) %></h3>
63
+ <div class="mt-2">
64
+ <%= link_to raif.admin_stats_tasks_path(period: @selected_period) do %>
65
+ <%= t("raif.admin.common.details") %> &raquo;
66
+ <% end %>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Agents -->
73
+ <div class="col-md-4 col-lg-4">
74
+ <div class="stats-card p-3 border rounded shadow-sm h-100">
75
+ <div class="d-flex align-items-center mb-2">
76
+ <span class="stats-icon bg-danger-subtle text-danger rounded p-2 me-3">
77
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-cpu" viewBox="0 0 16 16">
78
+ <path d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z" />
79
+ </svg>
80
+ </span>
81
+ <div>
82
+ <h6 class="mb-0 text-muted"><%= t("raif.admin.common.agents") %></h6>
83
+ </div>
84
+ </div>
85
+ <h3 class="fw-bold mb-0"><%= number_with_delimiter(@agent_count) %></h3>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Conversations -->
90
+ <div class="col-md-4 col-lg-4">
91
+ <div class="stats-card p-3 border rounded shadow-sm h-100">
92
+ <div class="d-flex align-items-center mb-2">
93
+ <span class="stats-icon bg-info-subtle text-info rounded p-2 me-3">
94
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
95
+ <path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
96
+ <path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-.079.186.074.394.273.362a21.673 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272a1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z" />
97
+ </svg>
98
+ </span>
99
+ <div>
100
+ <h6 class="mb-0 text-muted"><%= t("raif.admin.common.conversations") %></h6>
101
+ </div>
102
+ </div>
103
+ <h3 class="fw-bold mb-0"><%= number_with_delimiter(@conversation_count) %></h3>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Conversation Entries -->
108
+ <div class="col-md-4 col-lg-4">
109
+ <div class="stats-card p-3 border rounded shadow-sm h-100">
110
+ <div class="d-flex align-items-center mb-2">
111
+ <span class="stats-icon bg-warning-subtle text-warning rounded p-2 me-3">
112
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-chat-text" viewBox="0 0 16 16">
113
+ <path d="M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z" />
114
+ <path d="M4 5.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zM4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8zm0 2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5z" />
115
+ </svg>
116
+ </span>
117
+ <div>
118
+ <h6 class="mb-0 text-muted"><%= t("raif.admin.common.conversation_entries") %></h6>
119
+ </div>
120
+ </div>
121
+ <h3 class="fw-bold mb-0"><%= number_with_delimiter(@conversation_entry_count) %></h3>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
@@ -0,0 +1,45 @@
1
+ <%= link_to raif.admin_stats_path do %>
2
+ &laquo; <%= t("raif.admin.stats.tasks.back_to_stats") %>
3
+ <% end %>
4
+
5
+ <div class="d-flex justify-content-between align-items-center my-4">
6
+ <h1 class="mb-0"><%= t("raif.admin.stats.tasks.title") %></h1>
7
+
8
+ <div class="period-filter">
9
+ <%= form_tag raif.admin_stats_tasks_path, method: :get, id: "period_filter_form", class: "d-flex align-items-center" do %>
10
+ <%= select_tag :period,
11
+ options_for_select(
12
+ [
13
+ [t("raif.admin.common.period_day"), "day"],
14
+ [t("raif.admin.common.period_week"), "week"],
15
+ [t("raif.admin.common.period_month"), "month"],
16
+ [t("raif.admin.common.period_all"), "all"]
17
+ ],
18
+ @selected_period
19
+ ),
20
+ class: "form-select form-select-sm me-2" %>
21
+ <%= submit_tag t("raif.admin.common.update"), class: "btn btn-sm btn-primary" %>
22
+ <% end %>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="table-responsive">
27
+ <table class="table table-striped table-hover">
28
+ <thead class="table-light">
29
+ <tr>
30
+ <th><%= t("raif.admin.common.type") %></th>
31
+ <th><%= t("raif.admin.common.count") %></th>
32
+ <th><%= t("raif.admin.common.est_cost") %></th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ <% @task_counts_by_type.each do |type, count| %>
37
+ <tr>
38
+ <td><%= type %></td>
39
+ <td><%= number_with_delimiter(count) %></td>
40
+ <td><%= number_to_currency(@task_costs_by_type[type] || 0, precision: 6) %></td>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
45
+ </div>
@@ -2,14 +2,15 @@
2
2
  <td><%= link_to "##{task.id}", raif.admin_task_path(task) %></td>
3
3
  <td><%= task.type %></td>
4
4
  <td><small class="text-muted"><%= task.created_at.rfc822 %></small></td>
5
- <td><%= task.creator_type %> #<%= task.creator_id %></td>
5
+ <td><%= task.creator.try(:raif_display_name) || "#{task.creator_type} ##{task.creator_id}" %></td>
6
6
  <td><%= task.llm_model_key %></td>
7
7
  <td>
8
- <% if task.completed_at? %>
8
+ <% case task.status %>
9
+ <% when :completed %>
9
10
  <span class="badge bg-success"><%= t("raif.admin.common.completed") %></span>
10
- <% elsif task.failed_at? %>
11
+ <% when :failed %>
11
12
  <span class="badge bg-danger"><%= t("raif.admin.common.failed") %></span>
12
- <% elsif task.started_at? %>
13
+ <% when :in_progress %>
13
14
  <span class="badge bg-warning text-dark"><%= t("raif.admin.common.in_progress") %></span>
14
15
  <% else %>
15
16
  <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
@@ -4,10 +4,28 @@
4
4
  <div class="col-12">
5
5
  <%= form_tag raif.admin_tasks_path, method: :get, class: "mb-4" do %>
6
6
  <div class="row align-items-end">
7
- <div class="col-md-6">
7
+ <div class="col-md-4">
8
8
  <div class="form-group">
9
+ <label for="task_types"><%= t("raif.admin.common.type") %></label>
9
10
  <%= select_tag :task_types,
10
- options_for_select(@task_types.map{|type| [type, type] }, @selected_types),
11
+ options_for_select([["All", "all"]] + @task_types.map{|type| [type, type] }, @selected_type),
12
+ { class: "form-select" } %>
13
+ </div>
14
+ </div>
15
+ <div class="col-md-4">
16
+ <div class="form-group">
17
+ <label for="task_statuses"><%= t("raif.admin.common.status") %></label>
18
+ <%= select_tag :task_statuses,
19
+ options_for_select(
20
+ [
21
+ [t("raif.admin.common.all"), :all],
22
+ [t("raif.admin.common.completed"), :completed],
23
+ [t("raif.admin.common.failed"), :failed],
24
+ [t("raif.admin.common.in_progress"), :in_progress],
25
+ [t("raif.admin.common.pending"), :pending]
26
+ ],
27
+ @selected_statuses
28
+ ),
11
29
  { class: "form-select" } %>
12
30
  </div>
13
31
  </div>
@@ -14,7 +14,9 @@
14
14
  </div>
15
15
  <div class="row mb-3">
16
16
  <div class="col-md-3"><strong><%= t("raif.admin.common.creator") %>:</strong></div>
17
- <div class="col-md-9"><%= @task.creator_type %> #<%= @task.creator_id %></div>
17
+ <div class="col-md-9">
18
+ <%= @task.creator.try(:raif_display_name) || "#{@task.creator_type} ##{@task.creator_id}" %>
19
+ </div>
18
20
  </div>
19
21
  <div class="row mb-3">
20
22
  <div class="col-md-3"><strong><%= t("raif.admin.common.model") %>:</strong></div>
@@ -0,0 +1,9 @@
1
+ <div class="conversation-entry-citations">
2
+ <h6><%= t("raif.common.sources") %></h6>
3
+ <% conversation_entry.citations.each do |citation| %>
4
+ <%= link_to citation["url"], target: "_blank", rel: "noopener noreferrer", class: "d-flex align-items-center" do %>
5
+ <%= image_tag "https://www.google.com/s2/favicons?sz=16&domain=#{citation["url"]}", class: "me-1" %>
6
+ <%= citation["title"] %>
7
+ <% end %>
8
+ <% end %>
9
+ </div>
@@ -1,26 +1,34 @@
1
- <div id="<%= dom_id(conversation_entry) %>" class="my-2">
1
+ <div id="<%= dom_id(conversation_entry) %>" class="my-2 conversation-entry">
2
2
  <%= render "raif/conversation_entries/message",
3
3
  conversation_entry: conversation_entry,
4
4
  content: conversation_entry.user_message,
5
- message_type: :user %>
5
+ message_type: :user if conversation_entry.user_message.present? %>
6
6
 
7
- <% if conversation_entry.completed? || conversation_entry.generating_response? %>
8
- <%= render "raif/conversation_entries/message",
9
- conversation_entry: conversation_entry,
10
- content: conversation_entry.generating_response? ? content_tag(:span, "", class: "raif-loader") : conversation_entry.model_response_message,
11
- message_type: :model_response %>
12
-
13
- <% conversation_entry.raif_model_tool_invocations.select(&:renderable?).each do |ti| %>
14
- <div class="mb-4 container">
15
- <%= render ti, conversation_entry: conversation_entry %>
16
- </div>
17
- <% end %>
18
- <% elsif conversation_entry.failed? %>
7
+ <% if conversation_entry.failed? %>
19
8
  <div class="mb-4 container">
20
9
  <%= render "raif/conversation_entries/message",
21
10
  conversation_entry: conversation_entry,
22
11
  content: t("raif.common.there_was_an_error_generating_this_response"),
23
12
  message_type: :model_response %>
24
13
  </div>
14
+ <% elsif conversation_entry.generating_response? %>
15
+ <%= render "raif/conversation_entries/message",
16
+ conversation_entry: conversation_entry,
17
+ content: if conversation_entry.model_response_message.present?
18
+ conversation_entry.model_response_message + content_tag(:span, "", class: "raif-streaming-cursor")
19
+ else
20
+ content_tag(:span, "", class: "raif-loader")
21
+ end,
22
+ message_type: :model_response %>
23
+
24
+ <% elsif conversation_entry.completed? %>
25
+ <%= render "raif/conversation_entries/message",
26
+ conversation_entry: conversation_entry,
27
+ content: conversation_entry.model_response_message,
28
+ message_type: :model_response if conversation_entry.model_response_message.present? %>
29
+
30
+ <% conversation_entry.raif_model_tool_invocations.select(&:renderable?).each do |ti| %>
31
+ <%= render ti, conversation_entry: conversation_entry %>
32
+ <% end %>
25
33
  <% end %>
26
34
  </div>
@@ -10,7 +10,7 @@
10
10
  <% end %>
11
11
  <% end %>
12
12
 
13
- <div class="d-flex">
13
+ <div class="d-flex px-2">
14
14
  <%= f.text_field :user_message,
15
15
  class: "form-control me-2",
16
16
  placeholder: conversation_entry.raif_user_tool_invocation&.message_input_placeholder.presence || t("raif.common.type_your_message"),
@@ -1,4 +1,4 @@
1
- <hr class="mb-2 mx-n5">
2
-
3
- <%= render "raif/conversations/available_user_tools", conversation: conversation %>
4
- <%= render "raif/conversation_entries/form", conversation: conversation, conversation_entry: conversation_entry %>
1
+ <div class="border-top pt-2">
2
+ <%= render "raif/conversations/available_user_tools", conversation: conversation %>
3
+ <%= render "raif/conversation_entries/form", conversation: conversation, conversation_entry: conversation_entry %>
4
+ </div>
@@ -1,12 +1,23 @@
1
- <div class="d-flex mb-2 chat-message">
2
- <div class="d-flex w-100 <%= "justify-content-end" if message_type == :user %>">
1
+ <div class="d-flex my-2 px-3 <%= "justify-content-end" if message_type == :user %>">
2
+ <div class="d-flex <%= "chat-message-content" if message_type == :user %> <%= "received-message-content" if message_type == :model_response %>">
3
3
  <% if message_type == :model_response %>
4
4
  <%= render "raif/conversation_entries/model_response_avatar", conversation_entry: local_assigns[:conversation_entry] %>
5
5
  <% end %>
6
6
 
7
7
  <% if content.present? %>
8
8
  <div class="mb-1 rounded-2 p-3 <%= message_type == :user ? "bg-primary text-white" : "border" %>">
9
- <%= simple_format content %>
9
+ <% case local_assigns[:conversation_entry]&.response_format %>
10
+ <% when "text" %>
11
+ <%= simple_format content %>
12
+ <% when "html" %>
13
+ <%= sanitize content %>
14
+ <% else %>
15
+ <%= content %>
16
+ <% end %>
17
+
18
+ <% if message_type == :model_response && local_assigns[:conversation_entry]&.citations.present? %>
19
+ <%= render "raif/conversation_entries/citations", conversation_entry: conversation_entry %>
20
+ <% end %>
10
21
  </div>
11
22
  <% end %>
12
23