htm 0.0.31 → 0.0.32
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.
- checksums.yaml +4 -4
- data/.irbrc +2 -3
- data/.rubocop.yml +184 -0
- data/CHANGELOG.md +46 -0
- data/README.md +2 -0
- data/Rakefile +93 -12
- data/db/migrate/00008_create_node_relationships.rb +54 -0
- data/db/migrate/00009_fix_node_relationships_column_types.rb +17 -0
- data/db/schema.sql +124 -1
- data/docs/api/database.md +35 -57
- data/docs/api/embedding-service.md +1 -1
- data/docs/api/index.md +26 -15
- data/docs/api/working-memory.md +8 -8
- data/docs/architecture/index.md +5 -7
- data/docs/architecture/overview.md +5 -8
- data/docs/assets/images/htm-architecture-overview.svg +1 -1
- data/docs/assets/images/htm-context-assembly-flow.svg +2 -2
- data/docs/assets/images/htm-layered-architecture.svg +3 -3
- data/docs/assets/images/two-tier-memory-architecture.svg +1 -1
- data/docs/database/README.md +1 -0
- data/docs/database_rake_tasks.md +20 -28
- data/docs/development/contributing.md +5 -5
- data/docs/development/index.md +4 -7
- data/docs/development/schema.md +71 -1
- data/docs/development/setup.md +40 -82
- data/docs/development/testing.md +1 -1
- data/docs/examples/file-loading.md +4 -4
- data/docs/examples/mcp-client.md +1 -1
- data/docs/getting-started/quick-start.md +4 -4
- data/docs/guides/adding-memories.md +14 -1
- data/docs/guides/configuration.md +5 -5
- data/docs/guides/context-assembly.md +4 -4
- data/docs/guides/file-loading.md +12 -12
- data/docs/guides/getting-started.md +2 -2
- data/docs/guides/long-term-memory.md +7 -27
- data/docs/guides/propositions.md +20 -19
- data/docs/guides/recalling-memories.md +5 -5
- data/docs/guides/tags.md +18 -13
- data/docs/multi_framework_support.md +1 -1
- data/docs/robots/hive-mind.md +1 -1
- data/docs/robots/multi-robot.md +2 -2
- data/docs/robots/robot-groups.md +1 -1
- data/docs/robots/two-tier-memory.md +72 -94
- data/docs/setup_local_database.md +8 -54
- data/docs/using_rake_tasks_in_your_app.md +6 -6
- data/examples/01_basic_usage.rb +1 -0
- data/examples/03_custom_llm_configuration.rb +1 -0
- data/examples/04_file_loader_usage.rb +1 -0
- data/examples/05_timeframe_demo.rb +1 -0
- data/examples/06_example_app/app.rb +1 -0
- data/examples/07_cli_app/htm_cli.rb +1 -0
- data/examples/09_mcp_client.rb +1 -0
- data/examples/10_telemetry/demo.rb +1 -0
- data/examples/11_robot_groups/multi_process.rb +1 -0
- data/examples/11_robot_groups/same_process.rb +1 -0
- data/examples/12_rails_app/.envrc +12 -0
- data/examples/12_rails_app/Gemfile +8 -3
- data/examples/12_rails_app/Gemfile.lock +94 -89
- data/examples/12_rails_app/README.md +70 -19
- data/examples/12_rails_app/app/controllers/application_controller.rb +6 -0
- data/examples/12_rails_app/app/controllers/chats_controller.rb +305 -0
- data/examples/12_rails_app/app/controllers/dashboard_controller.rb +3 -0
- data/examples/12_rails_app/app/controllers/files_controller.rb +17 -2
- data/examples/12_rails_app/app/controllers/home_controller.rb +8 -0
- data/examples/12_rails_app/app/controllers/memories_controller.rb +9 -4
- data/examples/12_rails_app/app/controllers/messages_controller.rb +214 -0
- data/examples/12_rails_app/app/controllers/robots_controller.rb +11 -1
- data/examples/12_rails_app/app/controllers/tags_controller.rb +14 -1
- data/examples/12_rails_app/app/javascript/application.js +1 -1
- data/examples/12_rails_app/app/models/application_record.rb +5 -0
- data/examples/12_rails_app/app/models/chat.rb +36 -0
- data/examples/12_rails_app/app/models/message.rb +5 -0
- data/examples/12_rails_app/app/models/model.rb +5 -0
- data/examples/12_rails_app/app/models/tool_call.rb +5 -0
- data/examples/12_rails_app/app/views/chats/index.html.erb +61 -0
- data/examples/12_rails_app/app/views/chats/show.html.erb +213 -0
- data/examples/12_rails_app/app/views/dashboard/index.html.erb +3 -0
- data/examples/12_rails_app/app/views/files/index.html.erb +10 -5
- data/examples/12_rails_app/app/views/files/new.html.erb +4 -2
- data/examples/12_rails_app/app/views/files/show.html.erb +19 -3
- data/examples/12_rails_app/app/views/home/index.html.erb +45 -0
- data/examples/12_rails_app/app/views/layouts/application.html.erb +20 -18
- data/examples/12_rails_app/app/views/memories/_memory_card.html.erb +1 -1
- data/examples/12_rails_app/app/views/memories/deleted.html.erb +3 -1
- data/examples/12_rails_app/app/views/memories/edit.html.erb +2 -0
- data/examples/12_rails_app/app/views/memories/index.html.erb +2 -0
- data/examples/12_rails_app/app/views/memories/new.html.erb +2 -0
- data/examples/12_rails_app/app/views/memories/show.html.erb +4 -2
- data/examples/12_rails_app/app/views/messages/_message.html.erb +20 -0
- data/examples/12_rails_app/app/views/robots/index.html.erb +2 -0
- data/examples/12_rails_app/app/views/robots/new.html.erb +2 -0
- data/examples/12_rails_app/app/views/robots/show.html.erb +2 -0
- data/examples/12_rails_app/app/views/search/index.html.erb +59 -8
- data/examples/12_rails_app/app/views/shared/_navbar.html.erb +75 -29
- data/examples/12_rails_app/app/views/tags/index.html.erb +2 -0
- data/examples/12_rails_app/app/views/tags/show.html.erb +3 -1
- data/examples/12_rails_app/config/application.rb +1 -1
- data/examples/12_rails_app/config/database.yml +9 -5
- data/examples/12_rails_app/config/importmap.rb +1 -1
- data/examples/12_rails_app/config/initializers/htm.rb +9 -2
- data/examples/12_rails_app/config/initializers/ruby_llm.rb +33 -0
- data/examples/12_rails_app/config/routes.rb +39 -23
- data/examples/12_rails_app/db/migrate/20250124000001_create_ruby_llm_tables.rb +34 -0
- data/examples/12_rails_app/db/migrate/20250124000002_create_models_table.rb +28 -0
- data/examples/12_rails_app/db/schema.rb +67 -0
- data/examples/examples_helper.rb +25 -0
- data/lib/htm/circuit_breaker.rb +5 -6
- data/lib/htm/config/builder.rb +12 -12
- data/lib/htm/config/database.rb +21 -27
- data/lib/htm/config/validator.rb +12 -18
- data/lib/htm/config.rb +76 -65
- data/lib/htm/database.rb +193 -199
- data/lib/htm/embedding_service.rb +4 -9
- data/lib/htm/integrations/sinatra.rb +7 -7
- data/lib/htm/job_adapter.rb +14 -21
- data/lib/htm/jobs/generate_embedding_job.rb +28 -44
- data/lib/htm/jobs/generate_propositions_job.rb +29 -55
- data/lib/htm/jobs/generate_relationships_job.rb +137 -0
- data/lib/htm/jobs/generate_tags_job.rb +45 -67
- data/lib/htm/loaders/markdown_loader.rb +65 -112
- data/lib/htm/long_term_memory/fulltext_search.rb +1 -1
- data/lib/htm/long_term_memory/hybrid_search.rb +300 -128
- data/lib/htm/long_term_memory/node_operations.rb +2 -2
- data/lib/htm/long_term_memory/relevance_scorer.rb +100 -68
- data/lib/htm/long_term_memory/tag_operations.rb +87 -120
- data/lib/htm/long_term_memory/vector_search.rb +1 -1
- data/lib/htm/long_term_memory.rb +2 -1
- data/lib/htm/mcp/cli.rb +59 -58
- data/lib/htm/mcp/server.rb +5 -6
- data/lib/htm/mcp/tools.rb +30 -36
- data/lib/htm/migration.rb +10 -10
- data/lib/htm/models/node.rb +2 -3
- data/lib/htm/models/node_relationship.rb +72 -0
- data/lib/htm/models/node_tag.rb +2 -2
- data/lib/htm/models/robot_node.rb +2 -2
- data/lib/htm/models/tag.rb +41 -28
- data/lib/htm/observability.rb +45 -51
- data/lib/htm/proposition_service.rb +3 -7
- data/lib/htm/query_cache.rb +13 -15
- data/lib/htm/railtie.rb +1 -2
- data/lib/htm/robot_group.rb +9 -9
- data/lib/htm/sequel_config.rb +1 -0
- data/lib/htm/sql_builder.rb +1 -1
- data/lib/htm/tag_service.rb +2 -6
- data/lib/htm/timeframe.rb +4 -5
- data/lib/htm/timeframe_extractor.rb +42 -83
- data/lib/htm/version.rb +1 -1
- data/lib/htm/workflows/remember_workflow.rb +112 -115
- data/lib/htm/working_memory.rb +21 -26
- data/lib/htm.rb +103 -116
- data/lib/tasks/db.rake +0 -2
- data/lib/tasks/doc.rake +14 -13
- data/lib/tasks/files.rake +5 -12
- data/lib/tasks/htm.rake +70 -71
- data/lib/tasks/jobs.rake +41 -47
- data/lib/tasks/tags.rake +3 -8
- metadata +25 -100
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<% content_for :body_attributes do %>data-no-auto-refresh<% end %>
|
|
2
|
+
|
|
1
3
|
<div class="space-y-6">
|
|
2
4
|
<!-- Back link -->
|
|
3
5
|
<div>
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
</svg>
|
|
42
44
|
Restore
|
|
43
45
|
<% end %>
|
|
44
|
-
<%= button_to memory_path(@memory.id, permanent: true), method: :delete, class: 'inline-flex items-center gap-2 rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-500 transition-colors'
|
|
46
|
+
<%= button_to memory_path(@memory.id, permanent: true), method: :delete, data: { confirm: 'Permanently delete this memory? This cannot be undone!' }, class: 'inline-flex items-center gap-2 rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-500 transition-colors' do %>
|
|
45
47
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
46
48
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
47
49
|
</svg>
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
</svg>
|
|
55
57
|
Edit
|
|
56
58
|
<% end %>
|
|
57
|
-
<%= button_to memory_path(@memory.id), method: :delete, class: 'inline-flex items-center gap-2 rounded-md bg-red-600/80 px-3 py-2 text-sm font-medium text-white hover:bg-red-600 transition-colors'
|
|
59
|
+
<%= button_to memory_path(@memory.id), method: :delete, data: { confirm: 'Move this memory to trash?' }, class: 'inline-flex items-center gap-2 rounded-md bg-red-600/80 px-3 py-2 text-sm font-medium text-white hover:bg-red-600 transition-colors' do %>
|
|
58
60
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
59
61
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
60
62
|
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<% is_user = message.role == 'user' %>
|
|
2
|
+
|
|
3
|
+
<div class="flex <%= is_user ? 'justify-end' : 'justify-start' %>">
|
|
4
|
+
<div class="max-w-[80%] <%= is_user ? 'order-2' : 'order-1' %>">
|
|
5
|
+
<div class="rounded-lg px-4 py-3 <%= is_user ? 'bg-indigo-600 text-white' : 'bg-gray-800 border border-gray-700 text-gray-100' %>">
|
|
6
|
+
<div class="prose prose-sm <%= is_user ? 'prose-invert' : 'prose-gray' %> max-w-none">
|
|
7
|
+
<p class="whitespace-pre-wrap break-words"><%= message.content %></p>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex items-center gap-2 mt-1 <%= is_user ? 'justify-end' : 'justify-start' %>">
|
|
11
|
+
<span class="text-xs text-gray-500">
|
|
12
|
+
<%= message.role.capitalize %>
|
|
13
|
+
</span>
|
|
14
|
+
<span class="text-xs text-gray-600">•</span>
|
|
15
|
+
<time class="text-xs text-gray-500 local-time" datetime="<%= message.created_at.utc.iso8601 %>">
|
|
16
|
+
<%= message.created_at.strftime('%H:%M') %>
|
|
17
|
+
</time>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
<%# Disable auto-refresh on search page - searches take time %>
|
|
2
|
+
<% content_for :body_attributes do %>data-no-auto-refresh<% end %>
|
|
3
|
+
|
|
1
4
|
<div class="space-y-6">
|
|
2
5
|
<!-- Header -->
|
|
3
6
|
<div>
|
|
@@ -7,7 +10,7 @@
|
|
|
7
10
|
|
|
8
11
|
<!-- Search Form -->
|
|
9
12
|
<div class="rounded-lg bg-gray-800 border border-gray-700 p-6">
|
|
10
|
-
<%= form_with url: search_path, method: :get, class: 'space-y-4',
|
|
13
|
+
<%= form_with url: search_path, method: :get, class: 'space-y-4', html: { id: 'search-form' } do %>
|
|
11
14
|
<div>
|
|
12
15
|
<label for="query" class="block text-sm font-medium text-gray-300 mb-2">Search Query</label>
|
|
13
16
|
<div class="flex gap-4">
|
|
@@ -19,7 +22,13 @@
|
|
|
19
22
|
placeholder="Enter your search query..."
|
|
20
23
|
class="flex-1 rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 focus:ring-indigo-500 focus:border-indigo-500"
|
|
21
24
|
/>
|
|
22
|
-
|
|
25
|
+
<button type="submit" id="search-btn" class="rounded-md bg-indigo-600 px-6 py-2 text-sm font-medium text-white hover:bg-indigo-500 cursor-pointer transition-colors inline-flex items-center gap-2">
|
|
26
|
+
<svg id="search-spinner" class="hidden animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
27
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
28
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
29
|
+
</svg>
|
|
30
|
+
<span id="search-btn-text">Search</span>
|
|
31
|
+
</button>
|
|
23
32
|
</div>
|
|
24
33
|
</div>
|
|
25
34
|
|
|
@@ -114,10 +123,23 @@
|
|
|
114
123
|
<ul class="space-y-3">
|
|
115
124
|
<% results.each_with_index do |memory, i| %>
|
|
116
125
|
<li class="text-sm">
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
<div class="flex items-start gap-2">
|
|
127
|
+
<span class="text-gray-500 font-mono text-xs flex-shrink-0"><%= i + 1 %>.</span>
|
|
128
|
+
<div class="min-w-0">
|
|
129
|
+
<%= link_to memory_path(memory['id']), class: 'text-gray-300 hover:text-white line-clamp-2 block' do %>
|
|
130
|
+
<%= truncate(memory['content'], length: 80) %>
|
|
131
|
+
<% end %>
|
|
132
|
+
<div class="mt-1 text-xs">
|
|
133
|
+
<% if strategy == :hybrid && memory['rrf_score'] %>
|
|
134
|
+
<span class="text-purple-400"><%= (memory['rrf_score'].to_f * 100).round(1) %>%</span>
|
|
135
|
+
<% elsif strategy == :vector && memory['similarity'] %>
|
|
136
|
+
<span class="text-blue-400"><%= (memory['similarity'].to_f * 100).round(1) %>%</span>
|
|
137
|
+
<% elsif strategy == :fulltext && memory['rank'] %>
|
|
138
|
+
<span class="text-green-400"><%= memory['rank'].to_f.round(2) %></span>
|
|
139
|
+
<% end %>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
121
143
|
</li>
|
|
122
144
|
<% end %>
|
|
123
145
|
</ul>
|
|
@@ -160,8 +182,17 @@
|
|
|
160
182
|
<div class="mt-2 flex items-center gap-4 text-xs text-gray-500">
|
|
161
183
|
<span class="font-mono text-gray-600">#<%= memory['id'] %></span>
|
|
162
184
|
<span><%= time_ago_in_words(memory['created_at'].is_a?(Time) ? memory['created_at'] : Time.parse(memory['created_at'].to_s)) %> ago</span>
|
|
163
|
-
|
|
164
|
-
|
|
185
|
+
<%# Display score based on search strategy %>
|
|
186
|
+
<% case @selected_strategy %>
|
|
187
|
+
<% when :hybrid %>
|
|
188
|
+
<span class="text-purple-400">RRF: <%= (memory['rrf_score'].to_f * 100).round(2) %>%</span>
|
|
189
|
+
<% if memory['sources'].is_a?(Array) && memory['sources'].any? %>
|
|
190
|
+
<span class="text-gray-600">via <%= memory['sources'].join(', ') %></span>
|
|
191
|
+
<% end %>
|
|
192
|
+
<% when :vector %>
|
|
193
|
+
<span class="text-blue-400">Similarity: <%= (memory['similarity'].to_f * 100).round(2) %>%</span>
|
|
194
|
+
<% when :fulltext %>
|
|
195
|
+
<span class="text-green-400">Score: <%= memory['rank'].to_f.round(4) %></span>
|
|
165
196
|
<% end %>
|
|
166
197
|
</div>
|
|
167
198
|
</div>
|
|
@@ -182,3 +213,23 @@
|
|
|
182
213
|
<% end %>
|
|
183
214
|
<% end %>
|
|
184
215
|
</div>
|
|
216
|
+
|
|
217
|
+
<script>
|
|
218
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
219
|
+
const form = document.getElementById('search-form');
|
|
220
|
+
const btn = document.getElementById('search-btn');
|
|
221
|
+
const spinner = document.getElementById('search-spinner');
|
|
222
|
+
const btnText = document.getElementById('search-btn-text');
|
|
223
|
+
|
|
224
|
+
if (form && btn) {
|
|
225
|
+
form.addEventListener('submit', () => {
|
|
226
|
+
// Show spinner and disable button
|
|
227
|
+
spinner.classList.remove('hidden');
|
|
228
|
+
btnText.textContent = 'Searching...';
|
|
229
|
+
btn.disabled = true;
|
|
230
|
+
btn.classList.add('opacity-75', 'cursor-wait');
|
|
231
|
+
btn.classList.remove('hover:bg-indigo-500');
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
</script>
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<%
|
|
2
|
+
# Determine current section
|
|
3
|
+
in_chat_section = controller_name.in?(['chats', 'messages'])
|
|
4
|
+
in_htm_section = controller_name.in?(['dashboard', 'memories', 'tags', 'robots', 'search', 'files'])
|
|
5
|
+
%>
|
|
6
|
+
|
|
1
7
|
<nav class="bg-gray-800 border-b border-gray-700">
|
|
2
8
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
3
9
|
<div class="flex h-16 items-center justify-between">
|
|
@@ -7,46 +13,86 @@
|
|
|
7
13
|
<svg class="h-8 w-8 text-indigo-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
8
14
|
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
|
|
9
15
|
</svg>
|
|
10
|
-
<span class="text-xl font-bold text-white">HTM
|
|
16
|
+
<span class="text-xl font-bold text-white">HTM Demo</span>
|
|
11
17
|
<% end %>
|
|
12
18
|
</div>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'search' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
25
|
-
<%= link_to 'Files', files_path,
|
|
26
|
-
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'files' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
19
|
+
|
|
20
|
+
<% if in_chat_section %>
|
|
21
|
+
<!-- Chat section navigation -->
|
|
22
|
+
<div class="hidden md:block">
|
|
23
|
+
<div class="ml-10 flex items-baseline space-x-4">
|
|
24
|
+
<span class="rounded-full bg-indigo-900/50 border border-indigo-500/30 px-3 py-1 text-xs font-medium text-indigo-400">Chat</span>
|
|
25
|
+
<%= link_to 'All Chats', app_root_path,
|
|
26
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'chats' && action_name == 'index' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
27
|
+
<%= link_to 'HTM Management', htm_root_path,
|
|
28
|
+
class: "rounded-md px-3 py-2 text-sm font-medium text-gray-400 hover:bg-gray-700 hover:text-white" %>
|
|
29
|
+
</div>
|
|
27
30
|
</div>
|
|
28
|
-
|
|
31
|
+
<% elsif in_htm_section %>
|
|
32
|
+
<!-- HTM section navigation -->
|
|
33
|
+
<div class="hidden md:block">
|
|
34
|
+
<div class="ml-10 flex items-baseline space-x-4">
|
|
35
|
+
<span class="rounded-full bg-purple-900/50 border border-purple-500/30 px-3 py-1 text-xs font-medium text-purple-400">HTM</span>
|
|
36
|
+
<%= link_to 'Dashboard', htm_root_path,
|
|
37
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'dashboard' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
38
|
+
<%= link_to 'Memories', memories_path,
|
|
39
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'memories' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
40
|
+
<%= link_to 'Tags', tags_path,
|
|
41
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'tags' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
42
|
+
<%= link_to 'Robots', robots_path,
|
|
43
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'robots' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
44
|
+
<%= link_to 'Search', search_path,
|
|
45
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'search' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
46
|
+
<%= link_to 'Files', files_path,
|
|
47
|
+
class: "rounded-md px-3 py-2 text-sm font-medium #{controller_name == 'files' ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
|
|
48
|
+
<%= link_to 'Chat', app_root_path,
|
|
49
|
+
class: "rounded-md px-3 py-2 text-sm font-medium text-gray-400 hover:bg-gray-700 hover:text-white" %>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<% else %>
|
|
53
|
+
<!-- Home page - show both section links -->
|
|
54
|
+
<div class="hidden md:block">
|
|
55
|
+
<div class="ml-10 flex items-baseline space-x-4">
|
|
56
|
+
<%= link_to 'Chat', app_root_path,
|
|
57
|
+
class: "rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
58
|
+
<%= link_to 'HTM Management', htm_root_path,
|
|
59
|
+
class: "rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
29
63
|
</div>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
|
|
65
|
+
<% if in_htm_section %>
|
|
66
|
+
<div class="flex items-center gap-4">
|
|
67
|
+
<div class="flex items-center gap-2 text-sm text-gray-400">
|
|
68
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
69
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
70
|
+
</svg>
|
|
71
|
+
<span>Robot: <strong class="text-indigo-400"><%= current_robot_name %></strong></span>
|
|
72
|
+
</div>
|
|
36
73
|
</div>
|
|
37
|
-
|
|
74
|
+
<% end %>
|
|
38
75
|
</div>
|
|
39
76
|
</div>
|
|
40
77
|
|
|
41
78
|
<!-- Mobile menu -->
|
|
42
79
|
<div class="md:hidden" data-controller="mobile-menu">
|
|
43
80
|
<div class="space-y-1 px-2 pb-3 pt-2 sm:px-3">
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
<% if in_chat_section %>
|
|
82
|
+
<%= link_to 'All Chats', app_root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
83
|
+
<%= link_to 'HTM Management', htm_root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white" %>
|
|
84
|
+
<% elsif in_htm_section %>
|
|
85
|
+
<%= link_to 'Dashboard', htm_root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
86
|
+
<%= link_to 'Memories', memories_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
87
|
+
<%= link_to 'Tags', tags_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
88
|
+
<%= link_to 'Robots', robots_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
89
|
+
<%= link_to 'Search', search_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
90
|
+
<%= link_to 'Files', files_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
91
|
+
<%= link_to 'Chat', app_root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white" %>
|
|
92
|
+
<% else %>
|
|
93
|
+
<%= link_to 'Chat', app_root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
94
|
+
<%= link_to 'HTM Management', htm_root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
|
|
95
|
+
<% end %>
|
|
50
96
|
</div>
|
|
51
97
|
</div>
|
|
52
98
|
</nav>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<% content_for :body_attributes do %>data-no-auto-refresh<% end %>
|
|
2
|
+
|
|
1
3
|
<div class="space-y-6">
|
|
2
4
|
<!-- Back link -->
|
|
3
5
|
<div>
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
<% prefix = parts[0..i].join(':') %>
|
|
35
37
|
<% matching_tag = HTM::Models::Tag.first(name: prefix) %>
|
|
36
38
|
<% if matching_tag %>
|
|
37
|
-
<%= link_to part, tag_path(matching_tag), class: "#{i == parts.length - 1 ? 'text-indigo-400' : 'text-gray-400 hover:text-white'}" %>
|
|
39
|
+
<%= link_to part, tag_path(matching_tag.id), class: "#{i == parts.length - 1 ? 'text-indigo-400' : 'text-gray-400 hover:text-white'}" %>
|
|
38
40
|
<% else %>
|
|
39
41
|
<span class="text-gray-500"><%= part %></span>
|
|
40
42
|
<% end %>
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
# Rails app database
|
|
2
|
-
#
|
|
1
|
+
# Rails app database for RubyLLM chat persistence
|
|
2
|
+
# HTM uses its own database (htm_examples)
|
|
3
3
|
|
|
4
4
|
default: &default
|
|
5
5
|
adapter: postgresql
|
|
6
6
|
encoding: unicode
|
|
7
|
-
pool: 5
|
|
7
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
8
8
|
|
|
9
9
|
development:
|
|
10
10
|
<<: *default
|
|
11
|
-
database:
|
|
11
|
+
database: chatbot_development
|
|
12
12
|
|
|
13
13
|
test:
|
|
14
14
|
<<: *default
|
|
15
|
-
database:
|
|
15
|
+
database: chatbot_test
|
|
16
|
+
|
|
17
|
+
examples:
|
|
18
|
+
<<: *default
|
|
19
|
+
database: chatbot_examples
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
pin "application"
|
|
4
|
-
|
|
4
|
+
# Turbo removed - causes issues with CDN-based setup, using vanilla JS instead
|
|
5
5
|
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
|
6
6
|
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
|
7
7
|
pin_all_from "app/javascript/controllers", under: "controllers"
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# Use fiber-based async jobs for better responsiveness
|
|
3
|
+
# Configure HTM for the Rails chatbot app
|
|
5
4
|
HTM.configure do |config|
|
|
5
|
+
# Use the examples database which contains the knowledge base
|
|
6
|
+
# This overrides HTM_ENV=development to use the populated database
|
|
7
|
+
config.database.url = ENV.fetch('HTM_CHATBOT_DATABASE_URL', 'postgresql://localhost:5432/htm_examples')
|
|
8
|
+
|
|
9
|
+
# Use fiber-based async jobs for better responsiveness
|
|
6
10
|
config.job.backend = :fiber
|
|
7
11
|
end
|
|
12
|
+
|
|
13
|
+
Rails.logger.info "HTM initialized for Rails application"
|
|
14
|
+
Rails.logger.info "HTM database: #{HTM.configuration.database_url}"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby_llm'
|
|
4
|
+
|
|
5
|
+
RubyLLM.configure do |config|
|
|
6
|
+
# Logging - use INFO level in production, DEBUG for troubleshooting
|
|
7
|
+
config.logger = Rails.logger
|
|
8
|
+
config.log_level = Rails.env.development? ? Logger::INFO : Logger::WARN
|
|
9
|
+
|
|
10
|
+
# Enable new acts_as API (required for acts_as_chat)
|
|
11
|
+
config.use_new_acts_as = true
|
|
12
|
+
|
|
13
|
+
# Model registry class for database storage
|
|
14
|
+
config.model_registry_class = 'Model'
|
|
15
|
+
|
|
16
|
+
# Default chat model
|
|
17
|
+
config.default_model = ENV.fetch('CHAT_MODEL', 'gemma3:latest')
|
|
18
|
+
|
|
19
|
+
# Cloud provider API keys
|
|
20
|
+
config.openai_api_key = ENV['OPENAI_API_KEY']
|
|
21
|
+
config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
|
|
22
|
+
config.xai_api_key = ENV['XAI_API_KEY']
|
|
23
|
+
|
|
24
|
+
# Ollama (local provider) - requires /v1 suffix for OpenAI-compatible API
|
|
25
|
+
ollama_base = ENV.fetch('OLLAMA_URL', 'http://localhost:11434')
|
|
26
|
+
config.ollama_api_base = ollama_base.end_with?('/v1') ? ollama_base : "#{ollama_base}/v1"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Include ActiveRecord support directly since railtie hook doesn't always fire correctly
|
|
30
|
+
Rails.application.config.to_prepare do
|
|
31
|
+
require 'ruby_llm/active_record/acts_as'
|
|
32
|
+
ActiveRecord::Base.include RubyLLM::ActiveRecord::ActsAs unless ActiveRecord::Base.respond_to?(:acts_as_chat)
|
|
33
|
+
end
|
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Rails.application.routes.draw do
|
|
4
|
-
|
|
4
|
+
# Landing page
|
|
5
|
+
root 'home#index'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
# Chatbot section
|
|
8
|
+
scope '/app' do
|
|
9
|
+
get '/', to: 'chats#index', as: :app_root
|
|
10
|
+
resources :chats, only: [:index, :show, :create, :update, :destroy] do
|
|
11
|
+
member do
|
|
12
|
+
patch :update_htm_settings
|
|
13
|
+
end
|
|
14
|
+
resources :messages, only: [:create]
|
|
12
15
|
end
|
|
16
|
+
get '/models', to: 'chats#models', as: :provider_models
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
# HTM Management section (existing functionality)
|
|
20
|
+
scope '/htm' do
|
|
21
|
+
get '/', to: 'dashboard#index', as: :htm_root
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
resources :memories do
|
|
24
|
+
collection do
|
|
25
|
+
get :deleted
|
|
26
|
+
end
|
|
27
|
+
member do
|
|
28
|
+
post :restore
|
|
29
|
+
end
|
|
20
30
|
end
|
|
21
|
-
end
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
get '/', action: :index
|
|
25
|
-
end
|
|
32
|
+
resources :tags, only: [:index, :show]
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
post :upload_directory
|
|
32
|
-
post :sync_all
|
|
34
|
+
resources :robots, only: [:index, :show, :new, :create] do
|
|
35
|
+
member do
|
|
36
|
+
post :switch
|
|
37
|
+
end
|
|
33
38
|
end
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
|
|
40
|
+
get '/search', to: 'search#index', as: :search
|
|
41
|
+
|
|
42
|
+
resources :files, only: [:index, :show, :new, :create, :destroy] do
|
|
43
|
+
collection do
|
|
44
|
+
post :load_directory
|
|
45
|
+
post :upload
|
|
46
|
+
post :upload_directory
|
|
47
|
+
post :sync_all
|
|
48
|
+
end
|
|
49
|
+
member do
|
|
50
|
+
post :sync
|
|
51
|
+
end
|
|
36
52
|
end
|
|
37
53
|
end
|
|
38
54
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRubyLlmTables < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :chats do |t|
|
|
6
|
+
t.string :model_id
|
|
7
|
+
t.text :instructions
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
create_table :messages do |t|
|
|
13
|
+
t.references :chat, null: false, foreign_key: true
|
|
14
|
+
t.string :role, null: false
|
|
15
|
+
t.text :content
|
|
16
|
+
t.string :model_id
|
|
17
|
+
t.integer :input_tokens
|
|
18
|
+
t.integer :output_tokens
|
|
19
|
+
|
|
20
|
+
t.timestamps
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
create_table :tool_calls do |t|
|
|
24
|
+
t.references :message, null: false, foreign_key: true
|
|
25
|
+
t.string :tool_call_id, null: false
|
|
26
|
+
t.string :name, null: false
|
|
27
|
+
t.jsonb :arguments, default: {}
|
|
28
|
+
|
|
29
|
+
t.timestamps
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
add_index :tool_calls, :tool_call_id
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateModelsTable < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :models do |t|
|
|
6
|
+
t.string :model_id, null: false
|
|
7
|
+
t.string :name, null: false
|
|
8
|
+
t.string :provider, null: false
|
|
9
|
+
t.string :family
|
|
10
|
+
t.datetime :model_created_at
|
|
11
|
+
t.integer :context_window
|
|
12
|
+
t.integer :max_output_tokens
|
|
13
|
+
t.date :knowledge_cutoff
|
|
14
|
+
t.jsonb :modalities, default: {}
|
|
15
|
+
t.jsonb :capabilities, default: []
|
|
16
|
+
t.jsonb :pricing, default: {}
|
|
17
|
+
t.jsonb :metadata, default: {}
|
|
18
|
+
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :models, [:provider, :model_id], unique: true
|
|
23
|
+
add_index :models, :provider
|
|
24
|
+
add_index :models, :family
|
|
25
|
+
add_index :models, :capabilities, using: :gin
|
|
26
|
+
add_index :models, :modalities, using: :gin
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
|
4
|
+
#
|
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
9
|
+
# migrations use external dependencies or application code.
|
|
10
|
+
#
|
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Schema[8.1].define(version: 2025_01_24_000002) do
|
|
14
|
+
# These are extensions that must be enabled in order to support this database
|
|
15
|
+
enable_extension "pg_catalog.plpgsql"
|
|
16
|
+
|
|
17
|
+
create_table "chats", force: :cascade do |t|
|
|
18
|
+
t.datetime "created_at", null: false
|
|
19
|
+
t.text "instructions"
|
|
20
|
+
t.string "model_id"
|
|
21
|
+
t.datetime "updated_at", null: false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
create_table "messages", force: :cascade do |t|
|
|
25
|
+
t.bigint "chat_id", null: false
|
|
26
|
+
t.text "content"
|
|
27
|
+
t.datetime "created_at", null: false
|
|
28
|
+
t.integer "input_tokens"
|
|
29
|
+
t.string "model_id"
|
|
30
|
+
t.integer "output_tokens"
|
|
31
|
+
t.string "role", null: false
|
|
32
|
+
t.datetime "updated_at", null: false
|
|
33
|
+
t.index ["chat_id"], name: "index_messages_on_chat_id"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
create_table "models", force: :cascade do |t|
|
|
37
|
+
t.integer "context_window"
|
|
38
|
+
t.datetime "created_at", null: false
|
|
39
|
+
t.string "family"
|
|
40
|
+
t.decimal "input_price_per_million", precision: 10, scale: 4
|
|
41
|
+
t.integer "max_tokens"
|
|
42
|
+
t.jsonb "metadata", default: {}
|
|
43
|
+
t.string "model_id", null: false
|
|
44
|
+
t.string "name"
|
|
45
|
+
t.decimal "output_price_per_million", precision: 10, scale: 4
|
|
46
|
+
t.string "provider"
|
|
47
|
+
t.boolean "supports_functions", default: false
|
|
48
|
+
t.boolean "supports_vision", default: false
|
|
49
|
+
t.datetime "updated_at", null: false
|
|
50
|
+
t.index ["model_id"], name: "index_models_on_model_id", unique: true
|
|
51
|
+
t.index ["provider"], name: "index_models_on_provider"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
create_table "tool_calls", force: :cascade do |t|
|
|
55
|
+
t.jsonb "arguments", default: {}
|
|
56
|
+
t.datetime "created_at", null: false
|
|
57
|
+
t.bigint "message_id", null: false
|
|
58
|
+
t.string "name", null: false
|
|
59
|
+
t.string "tool_call_id", null: false
|
|
60
|
+
t.datetime "updated_at", null: false
|
|
61
|
+
t.index ["message_id"], name: "index_tool_calls_on_message_id"
|
|
62
|
+
t.index ["tool_call_id"], name: "index_tool_calls_on_tool_call_id"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
add_foreign_key "messages", "chats"
|
|
66
|
+
add_foreign_key "tool_calls", "messages"
|
|
67
|
+
end
|