htm 0.0.15 → 0.0.17

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/CHANGELOG.md +67 -0
  4. data/README.md +97 -1592
  5. data/bin/htm_mcp +31 -0
  6. data/config/database.yml +7 -4
  7. data/docs/getting-started/installation.md +31 -11
  8. data/docs/guides/mcp-server.md +456 -21
  9. data/docs/multi_framework_support.md +2 -2
  10. data/examples/mcp_client.rb +2 -2
  11. data/examples/rails_app/.gitignore +2 -0
  12. data/examples/rails_app/Gemfile +22 -0
  13. data/examples/rails_app/Gemfile.lock +438 -0
  14. data/examples/rails_app/Procfile.dev +1 -0
  15. data/examples/rails_app/README.md +98 -0
  16. data/examples/rails_app/Rakefile +5 -0
  17. data/examples/rails_app/app/assets/stylesheets/application.css +83 -0
  18. data/examples/rails_app/app/assets/stylesheets/inter-font.css +6 -0
  19. data/examples/rails_app/app/controllers/application_controller.rb +19 -0
  20. data/examples/rails_app/app/controllers/dashboard_controller.rb +27 -0
  21. data/examples/rails_app/app/controllers/files_controller.rb +205 -0
  22. data/examples/rails_app/app/controllers/memories_controller.rb +102 -0
  23. data/examples/rails_app/app/controllers/robots_controller.rb +44 -0
  24. data/examples/rails_app/app/controllers/search_controller.rb +46 -0
  25. data/examples/rails_app/app/controllers/tags_controller.rb +30 -0
  26. data/examples/rails_app/app/javascript/application.js +4 -0
  27. data/examples/rails_app/app/javascript/controllers/application.js +9 -0
  28. data/examples/rails_app/app/javascript/controllers/index.js +6 -0
  29. data/examples/rails_app/app/views/dashboard/index.html.erb +123 -0
  30. data/examples/rails_app/app/views/files/index.html.erb +108 -0
  31. data/examples/rails_app/app/views/files/new.html.erb +321 -0
  32. data/examples/rails_app/app/views/files/show.html.erb +130 -0
  33. data/examples/rails_app/app/views/layouts/application.html.erb +124 -0
  34. data/examples/rails_app/app/views/memories/_memory_card.html.erb +51 -0
  35. data/examples/rails_app/app/views/memories/deleted.html.erb +62 -0
  36. data/examples/rails_app/app/views/memories/edit.html.erb +35 -0
  37. data/examples/rails_app/app/views/memories/index.html.erb +81 -0
  38. data/examples/rails_app/app/views/memories/new.html.erb +71 -0
  39. data/examples/rails_app/app/views/memories/show.html.erb +126 -0
  40. data/examples/rails_app/app/views/robots/index.html.erb +106 -0
  41. data/examples/rails_app/app/views/robots/new.html.erb +36 -0
  42. data/examples/rails_app/app/views/robots/show.html.erb +79 -0
  43. data/examples/rails_app/app/views/search/index.html.erb +184 -0
  44. data/examples/rails_app/app/views/shared/_navbar.html.erb +52 -0
  45. data/examples/rails_app/app/views/shared/_stat_card.html.erb +52 -0
  46. data/examples/rails_app/app/views/tags/index.html.erb +131 -0
  47. data/examples/rails_app/app/views/tags/show.html.erb +67 -0
  48. data/examples/rails_app/bin/dev +8 -0
  49. data/examples/rails_app/bin/rails +4 -0
  50. data/examples/rails_app/bin/rake +4 -0
  51. data/examples/rails_app/config/application.rb +33 -0
  52. data/examples/rails_app/config/boot.rb +5 -0
  53. data/examples/rails_app/config/database.yml +15 -0
  54. data/examples/rails_app/config/environment.rb +5 -0
  55. data/examples/rails_app/config/importmap.rb +7 -0
  56. data/examples/rails_app/config/routes.rb +38 -0
  57. data/examples/rails_app/config/tailwind.config.js +35 -0
  58. data/examples/rails_app/config.ru +5 -0
  59. data/examples/rails_app/log/.keep +0 -0
  60. data/examples/rails_app/tmp/local_secret.txt +1 -0
  61. data/lib/htm/active_record_config.rb +2 -5
  62. data/lib/htm/configuration.rb +35 -2
  63. data/lib/htm/database.rb +3 -6
  64. data/lib/htm/mcp/cli.rb +333 -0
  65. data/lib/htm/mcp/group_tools.rb +476 -0
  66. data/lib/htm/mcp/resources.rb +89 -0
  67. data/lib/htm/mcp/server.rb +98 -0
  68. data/lib/htm/mcp/tools.rb +488 -0
  69. data/lib/htm/models/file_source.rb +5 -3
  70. data/lib/htm/railtie.rb +0 -4
  71. data/lib/htm/tasks.rb +7 -4
  72. data/lib/htm/version.rb +1 -1
  73. data/lib/tasks/htm.rake +6 -9
  74. metadata +59 -4
  75. data/bin/htm_mcp.rb +0 -621
@@ -0,0 +1,106 @@
1
+ <div class="space-y-6">
2
+ <!-- Header -->
3
+ <div class="flex items-center justify-between">
4
+ <div>
5
+ <h1 class="text-2xl font-bold text-white">Robots</h1>
6
+ <p class="mt-1 text-sm text-gray-400">Manage LLM agents in the hive mind</p>
7
+ </div>
8
+ <%= link_to new_robot_path, class: 'inline-flex items-center gap-2 rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 transition-colors' do %>
9
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
10
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
11
+ </svg>
12
+ New Robot
13
+ <% end %>
14
+ </div>
15
+
16
+ <!-- Info Box -->
17
+ <div class="rounded-lg bg-indigo-900/30 border border-indigo-500/30 p-4">
18
+ <div class="flex">
19
+ <div class="flex-shrink-0">
20
+ <svg class="h-5 w-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
22
+ </svg>
23
+ </div>
24
+ <div class="ml-3">
25
+ <p class="text-sm text-indigo-300">
26
+ <strong>Hive Mind Architecture:</strong> All robots share the same global memory store. Each robot can access memories created by any other robot, enabling collaborative knowledge sharing across agents.
27
+ </p>
28
+ <p class="text-sm text-indigo-300/70 mt-2">
29
+ <strong>Set as Active:</strong> Changes which robot is shown in the header. New memories you create will be associated with the active robot.
30
+ </p>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Current Robot -->
36
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
37
+ <div class="flex items-center justify-between">
38
+ <div class="flex items-center gap-3">
39
+ <div class="h-10 w-10 rounded-full bg-purple-900/50 flex items-center justify-center">
40
+ <svg class="h-5 w-5 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
41
+ <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" />
42
+ </svg>
43
+ </div>
44
+ <div>
45
+ <p class="text-sm text-gray-400">Currently active</p>
46
+ <p class="text-lg font-medium text-white"><%= current_robot_name %></p>
47
+ </div>
48
+ </div>
49
+ <span class="inline-flex items-center gap-1 rounded-full bg-green-900/50 border border-green-500/30 px-3 py-1 text-sm text-green-400">
50
+ <svg class="h-2 w-2" fill="currentColor" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>
51
+ Active
52
+ </span>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- Robots List -->
57
+ <% if @robots.any? %>
58
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
59
+ <% @robots.each do |robot| %>
60
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-5 hover:border-gray-600 transition-colors">
61
+ <div class="flex items-start justify-between">
62
+ <div class="flex items-center gap-3">
63
+ <div class="h-12 w-12 rounded-full bg-purple-900/50 flex items-center justify-center">
64
+ <svg class="h-6 w-6 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
65
+ <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" />
66
+ </svg>
67
+ </div>
68
+ <div>
69
+ <h3 class="text-lg font-medium text-white"><%= robot.name %></h3>
70
+ <p class="text-sm text-gray-500">Created <%= time_ago_in_words(robot.created_at) %> ago</p>
71
+ </div>
72
+ </div>
73
+ <% if robot.name == current_robot_name %>
74
+ <span class="inline-flex items-center rounded-full bg-green-900/50 border border-green-500/30 px-2 py-0.5 text-xs text-green-400">
75
+ Active
76
+ </span>
77
+ <% end %>
78
+ </div>
79
+
80
+ <div class="mt-4 pt-4 border-t border-gray-700 flex items-center justify-between">
81
+ <p class="text-sm text-gray-400"><%= robot.nodes.count %> memories</p>
82
+ <div class="flex items-center gap-2">
83
+ <%= link_to 'View', robot_path(robot), class: 'text-sm text-indigo-400 hover:text-indigo-300' %>
84
+ <% unless robot.name == current_robot_name %>
85
+ <%= button_to switch_robot_path(robot), method: :post, class: 'inline-flex items-center gap-1 text-sm text-green-400 hover:text-green-300 bg-transparent border-0 cursor-pointer', title: "Switch to #{robot.name} - new memories will be created under this robot" do %>
86
+ <svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
87
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
88
+ </svg>
89
+ Set as Active
90
+ <% end %>
91
+ <% end %>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <% end %>
96
+ </div>
97
+ <% else %>
98
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-8 text-center">
99
+ <svg class="mx-auto h-12 w-12 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
100
+ <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" />
101
+ </svg>
102
+ <h3 class="mt-4 text-lg font-medium text-white">No robots yet</h3>
103
+ <p class="mt-2 text-sm text-gray-400">Robots are created automatically when you add memories.</p>
104
+ </div>
105
+ <% end %>
106
+ </div>
@@ -0,0 +1,36 @@
1
+ <div class="space-y-6">
2
+ <!-- Back link -->
3
+ <div>
4
+ <%= link_to robots_path, class: 'inline-flex items-center gap-1 text-sm text-gray-400 hover:text-white transition-colors' do %>
5
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
6
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
7
+ </svg>
8
+ Back to Robots
9
+ <% end %>
10
+ </div>
11
+
12
+ <!-- Form -->
13
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-6">
14
+ <h1 class="text-xl font-bold text-white mb-6">Create New Robot</h1>
15
+
16
+ <%= form_with url: robots_path, method: :post, class: 'space-y-6' do %>
17
+ <div>
18
+ <label for="name" class="block text-sm font-medium text-gray-300 mb-2">Robot Name</label>
19
+ <input
20
+ type="text"
21
+ name="name"
22
+ id="name"
23
+ required
24
+ placeholder="e.g., assistant, researcher, coder"
25
+ class="w-full rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 focus:ring-indigo-500 focus:border-indigo-500"
26
+ />
27
+ <p class="mt-1 text-xs text-gray-500">A unique identifier for this robot. Use lowercase letters, numbers, and underscores.</p>
28
+ </div>
29
+
30
+ <div class="flex items-center gap-4 pt-4 border-t border-gray-700">
31
+ <%= submit_tag 'Create Robot', class: 'rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 cursor-pointer transition-colors' %>
32
+ <%= link_to 'Cancel', robots_path, class: 'rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600 transition-colors' %>
33
+ </div>
34
+ <% end %>
35
+ </div>
36
+ </div>
@@ -0,0 +1,79 @@
1
+ <div class="space-y-6">
2
+ <!-- Back link -->
3
+ <div>
4
+ <%= link_to robots_path, class: 'inline-flex items-center gap-1 text-sm text-gray-400 hover:text-white transition-colors' do %>
5
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
6
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
7
+ </svg>
8
+ Back to Robots
9
+ <% end %>
10
+ </div>
11
+
12
+ <!-- Robot Header -->
13
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-6">
14
+ <div class="flex items-start justify-between">
15
+ <div class="flex items-center gap-4">
16
+ <div class="h-16 w-16 rounded-full bg-purple-900/50 flex items-center justify-center">
17
+ <svg class="h-8 w-8 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
18
+ <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" />
19
+ </svg>
20
+ </div>
21
+ <div>
22
+ <h1 class="text-2xl font-bold text-white"><%= @robot.name %></h1>
23
+ <p class="text-sm text-gray-400">Created <%= @robot.created_at.strftime('%Y-%m-%d at %H:%M') %></p>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="flex items-center gap-3">
28
+ <% if @robot.name == current_robot_name %>
29
+ <span class="inline-flex items-center gap-1 rounded-full bg-green-900/50 border border-green-500/30 px-3 py-1 text-sm text-green-400">
30
+ <svg class="h-2 w-2" fill="currentColor" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>
31
+ Active
32
+ </span>
33
+ <% else %>
34
+ <%= button_to switch_robot_path(@robot), method: :post, class: 'inline-flex items-center gap-2 rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 transition-colors' do %>
35
+ Switch to this robot
36
+ <% end %>
37
+ <% end %>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Stats -->
42
+ <div class="mt-6 grid grid-cols-3 gap-4 pt-6 border-t border-gray-700">
43
+ <div>
44
+ <p class="text-2xl font-bold text-white"><%= @memory_count %></p>
45
+ <p class="text-sm text-gray-400">Total Memories</p>
46
+ </div>
47
+ <div>
48
+ <p class="text-2xl font-bold text-white"><%= @robot.nodes.where.not(embedding: nil).count %></p>
49
+ <p class="text-sm text-gray-400">With Embeddings</p>
50
+ </div>
51
+ <div>
52
+ <p class="text-2xl font-bold text-white"><%= @robot.nodes.joins(:tags).distinct.count('tags.id') %></p>
53
+ <p class="text-sm text-gray-400">Unique Tags</p>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <!-- Recent Memories -->
59
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-6">
60
+ <h2 class="text-lg font-medium text-white mb-4">Recent Memories</h2>
61
+ <% if @recent_memories.any? %>
62
+ <div class="space-y-3">
63
+ <% @recent_memories.each do |memory| %>
64
+ <%= link_to memory_path(memory), class: 'block rounded-md bg-gray-700/50 p-3 hover:bg-gray-700 transition-colors' do %>
65
+ <p class="text-sm text-gray-300 line-clamp-2"><%= memory.content %></p>
66
+ <div class="mt-2 flex items-center gap-2 text-xs text-gray-500">
67
+ <span><%= time_ago_in_words(memory.created_at) %> ago</span>
68
+ <% if memory.embedding.present? %>
69
+ <span class="text-green-500">embedded</span>
70
+ <% end %>
71
+ </div>
72
+ <% end %>
73
+ <% end %>
74
+ </div>
75
+ <% else %>
76
+ <p class="text-sm text-gray-500">No memories yet. Start by <%= link_to 'adding a memory', new_memory_path, class: 'text-indigo-400 hover:text-indigo-300' %>.</p>
77
+ <% end %>
78
+ </div>
79
+ </div>
@@ -0,0 +1,184 @@
1
+ <div class="space-y-6">
2
+ <!-- Header -->
3
+ <div>
4
+ <h1 class="text-2xl font-bold text-white">Semantic Search</h1>
5
+ <p class="mt-1 text-sm text-gray-400">Search memories using vector, full-text, or hybrid strategies</p>
6
+ </div>
7
+
8
+ <!-- Search Form -->
9
+ <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' do %>
11
+ <div>
12
+ <label for="query" class="block text-sm font-medium text-gray-300 mb-2">Search Query</label>
13
+ <div class="flex gap-4">
14
+ <input
15
+ type="text"
16
+ name="query"
17
+ id="query"
18
+ value="<%= @query %>"
19
+ placeholder="Enter your search query..."
20
+ 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
+ />
22
+ <%= submit_tag 'Search', class: 'rounded-md bg-indigo-600 px-6 py-2 text-sm font-medium text-white hover:bg-indigo-500 cursor-pointer transition-colors' %>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="grid grid-cols-1 sm:grid-cols-4 gap-4 pt-4 border-t border-gray-700">
27
+ <div>
28
+ <label for="strategy" class="block text-xs font-medium text-gray-400 mb-1">Strategy</label>
29
+ <select name="strategy" id="strategy" class="w-full rounded-md bg-gray-700 border-gray-600 text-white text-sm focus:ring-indigo-500 focus:border-indigo-500">
30
+ <option value="hybrid" <%= 'selected' if @selected_strategy == :hybrid %>>Hybrid</option>
31
+ <option value="vector" <%= 'selected' if @selected_strategy == :vector %>>Vector (Semantic)</option>
32
+ <option value="fulltext" <%= 'selected' if @selected_strategy == :fulltext %>>Full-text (Keyword)</option>
33
+ </select>
34
+ </div>
35
+
36
+ <div>
37
+ <label for="limit" class="block text-xs font-medium text-gray-400 mb-1">Results</label>
38
+ <select name="limit" id="limit" class="w-full rounded-md bg-gray-700 border-gray-600 text-white text-sm focus:ring-indigo-500 focus:border-indigo-500">
39
+ <option value="5" <%= 'selected' if @limit == 5 %>>5</option>
40
+ <option value="10" <%= 'selected' if @limit == 10 %>>10</option>
41
+ <option value="20" <%= 'selected' if @limit == 20 %>>20</option>
42
+ <option value="50" <%= 'selected' if @limit == 50 %>>50</option>
43
+ </select>
44
+ </div>
45
+
46
+ <div>
47
+ <label for="timeframe" class="block text-xs font-medium text-gray-400 mb-1">Timeframe</label>
48
+ <select name="timeframe" id="timeframe" class="w-full rounded-md bg-gray-700 border-gray-600 text-white text-sm focus:ring-indigo-500 focus:border-indigo-500">
49
+ <option value="all time" <%= 'selected' if @timeframe == 'all time' %>>All Time</option>
50
+ <option value="today" <%= 'selected' if @timeframe == 'today' %>>Today</option>
51
+ <option value="this week" <%= 'selected' if @timeframe == 'this week' %>>This Week</option>
52
+ <option value="this month" <%= 'selected' if @timeframe == 'this month' %>>This Month</option>
53
+ <option value="last year" <%= 'selected' if @timeframe == 'last year' %>>Last Year</option>
54
+ </select>
55
+ </div>
56
+
57
+ <div class="flex items-end">
58
+ <label class="flex items-center gap-2 cursor-pointer">
59
+ <input type="checkbox" name="compare" value="true" <%= 'checked' if params[:compare] == 'true' %> class="rounded bg-gray-700 border-gray-600 text-indigo-600 focus:ring-indigo-500">
60
+ <span class="text-sm text-gray-300">Compare all strategies</span>
61
+ </label>
62
+ </div>
63
+ </div>
64
+ <% end %>
65
+ </div>
66
+
67
+ <!-- Strategy Explanation -->
68
+ <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
69
+ <div class="rounded-lg bg-gray-800/50 border border-gray-700 p-4">
70
+ <div class="flex items-center gap-2 mb-2">
71
+ <div class="h-2 w-2 rounded-full bg-blue-400"></div>
72
+ <h3 class="text-sm font-medium text-white">Vector Search</h3>
73
+ </div>
74
+ <p class="text-xs text-gray-400">Uses semantic embeddings for conceptual similarity. Best for finding related content even with different wording.</p>
75
+ </div>
76
+ <div class="rounded-lg bg-gray-800/50 border border-gray-700 p-4">
77
+ <div class="flex items-center gap-2 mb-2">
78
+ <div class="h-2 w-2 rounded-full bg-green-400"></div>
79
+ <h3 class="text-sm font-medium text-white">Full-text Search</h3>
80
+ </div>
81
+ <p class="text-xs text-gray-400">Keyword matching with fuzzy trigram support. Best for exact terms, code, or specific phrases.</p>
82
+ </div>
83
+ <div class="rounded-lg bg-gray-800/50 border border-gray-700 p-4">
84
+ <div class="flex items-center gap-2 mb-2">
85
+ <div class="h-2 w-2 rounded-full bg-purple-400"></div>
86
+ <h3 class="text-sm font-medium text-white">Hybrid Search</h3>
87
+ </div>
88
+ <p class="text-xs text-gray-400">Combines vector and full-text with weighted scoring. Best overall approach for general queries.</p>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Results -->
93
+ <% if @query.present? %>
94
+ <% if params[:compare] == 'true' %>
95
+ <!-- Comparison View -->
96
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
97
+ <% @strategies.each do |strategy| %>
98
+ <div class="rounded-lg bg-gray-800 border border-gray-700 overflow-hidden">
99
+ <div class="px-4 py-3 bg-gray-800/50 border-b border-gray-700 flex items-center justify-between">
100
+ <div class="flex items-center gap-2">
101
+ <div class="h-2 w-2 rounded-full <%= strategy == :vector ? 'bg-blue-400' : strategy == :fulltext ? 'bg-green-400' : 'bg-purple-400' %>"></div>
102
+ <h3 class="font-medium text-white capitalize"><%= strategy %></h3>
103
+ </div>
104
+ <span class="text-xs text-gray-400"><%= @results["#{strategy}_time".to_sym] %>ms</span>
105
+ </div>
106
+ <div class="p-4">
107
+ <% if @errors[strategy].present? %>
108
+ <div class="rounded-md bg-red-900/30 border border-red-500/30 p-3">
109
+ <p class="text-sm text-red-400">Error: <%= @errors[strategy] %></p>
110
+ </div>
111
+ <% else %>
112
+ <% results = @results[strategy] || [] %>
113
+ <% if results.any? %>
114
+ <ul class="space-y-3">
115
+ <% results.each_with_index do |memory, i| %>
116
+ <li class="text-sm">
117
+ <span class="text-gray-500 font-mono text-xs"><%= i + 1 %>.</span>
118
+ <%= link_to memory_path(memory['id']), class: 'text-gray-300 hover:text-white line-clamp-2' do %>
119
+ <%= truncate(memory['content'], length: 80) %>
120
+ <% end %>
121
+ </li>
122
+ <% end %>
123
+ </ul>
124
+ <% else %>
125
+ <p class="text-sm text-gray-500">No results</p>
126
+ <% end %>
127
+ <% end %>
128
+ </div>
129
+ </div>
130
+ <% end %>
131
+ </div>
132
+ <% else %>
133
+ <!-- Single Strategy Results -->
134
+ <div class="rounded-lg bg-gray-800 border border-gray-700 overflow-hidden">
135
+ <div class="px-6 py-4 bg-gray-800/50 border-b border-gray-700 flex items-center justify-between">
136
+ <div>
137
+ <h2 class="text-lg font-medium text-white">Results</h2>
138
+ <p class="text-sm text-gray-400">
139
+ <%= (@results[@selected_strategy] || []).length %> results using <span class="capitalize"><%= @selected_strategy %></span> strategy
140
+ </p>
141
+ </div>
142
+ <span class="text-sm text-gray-400"><%= @results["#{@selected_strategy}_time".to_sym] %>ms</span>
143
+ </div>
144
+
145
+ <% results = @results[@selected_strategy] || [] %>
146
+ <% if results.any? %>
147
+ <ul class="divide-y divide-gray-700">
148
+ <% results.each_with_index do |memory, i| %>
149
+ <li class="p-4 hover:bg-gray-700/50 transition-colors">
150
+ <div class="flex items-start gap-4">
151
+ <span class="flex-shrink-0 w-6 h-6 rounded-full bg-gray-700 flex items-center justify-center text-xs text-gray-400 font-mono">
152
+ <%= i + 1 %>
153
+ </span>
154
+ <div class="flex-1 min-w-0">
155
+ <%= link_to memory_path(memory['id']), class: 'block group' do %>
156
+ <p class="text-sm text-gray-300 group-hover:text-white transition-colors">
157
+ <%= memory['content'] %>
158
+ </p>
159
+ <% end %>
160
+ <div class="mt-2 flex items-center gap-4 text-xs text-gray-500">
161
+ <span class="font-mono text-gray-600">#<%= memory['id'] %></span>
162
+ <span><%= time_ago_in_words(memory['created_at'].is_a?(Time) ? memory['created_at'] : Time.parse(memory['created_at'].to_s)) %> ago</span>
163
+ <% if memory['rank'] %>
164
+ <span>Rank: <%= memory['rank'].to_f.round(4) %></span>
165
+ <% end %>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </li>
170
+ <% end %>
171
+ </ul>
172
+ <% else %>
173
+ <div class="p-8 text-center">
174
+ <svg class="mx-auto h-12 w-12 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
175
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
176
+ </svg>
177
+ <h3 class="mt-4 text-lg font-medium text-white">No results found</h3>
178
+ <p class="mt-2 text-sm text-gray-400">Try a different query or adjust the timeframe.</p>
179
+ </div>
180
+ <% end %>
181
+ </div>
182
+ <% end %>
183
+ <% end %>
184
+ </div>
@@ -0,0 +1,52 @@
1
+ <nav class="bg-gray-800 border-b border-gray-700">
2
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
3
+ <div class="flex h-16 items-center justify-between">
4
+ <div class="flex items-center">
5
+ <div class="flex-shrink-0">
6
+ <%= link_to root_path, class: "flex items-center gap-2" do %>
7
+ <svg class="h-8 w-8 text-indigo-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
8
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
9
+ </svg>
10
+ <span class="text-xl font-bold text-white">HTM Explorer</span>
11
+ <% end %>
12
+ </div>
13
+ <div class="hidden md:block">
14
+ <div class="ml-10 flex items-baseline space-x-4">
15
+ <%= link_to 'Dashboard', root_path,
16
+ class: "rounded-md px-3 py-2 text-sm font-medium #{current_page?(root_path) ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white'}" %>
17
+ <%= link_to 'Memories', memories_path,
18
+ 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'}" %>
19
+ <%= link_to 'Tags', tags_path,
20
+ 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'}" %>
21
+ <%= link_to 'Robots', robots_path,
22
+ 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'}" %>
23
+ <%= link_to 'Search', search_path,
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'}" %>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <div class="flex items-center gap-4">
31
+ <div class="flex items-center gap-2 text-sm text-gray-400">
32
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
33
+ <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" />
34
+ </svg>
35
+ <span>Robot: <strong class="text-indigo-400"><%= current_robot_name %></strong></span>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Mobile menu -->
42
+ <div class="md:hidden" data-controller="mobile-menu">
43
+ <div class="space-y-1 px-2 pb-3 pt-2 sm:px-3">
44
+ <%= link_to 'Dashboard', root_path, class: "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" %>
45
+ <%= 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" %>
46
+ <%= 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" %>
47
+ <%= 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" %>
48
+ <%= 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" %>
49
+ <%= 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" %>
50
+ </div>
51
+ </div>
52
+ </nav>
@@ -0,0 +1,52 @@
1
+ <%
2
+ colors = {
3
+ 'indigo' => { bg: 'bg-indigo-900/50', border: 'border-indigo-500/30', text: 'text-indigo-400' },
4
+ 'green' => { bg: 'bg-green-900/50', border: 'border-green-500/30', text: 'text-green-400' },
5
+ 'red' => { bg: 'bg-red-900/50', border: 'border-red-500/30', text: 'text-red-400' },
6
+ 'yellow' => { bg: 'bg-yellow-900/50', border: 'border-yellow-500/30', text: 'text-yellow-400' },
7
+ 'purple' => { bg: 'bg-purple-900/50', border: 'border-purple-500/30', text: 'text-purple-400' },
8
+ 'blue' => { bg: 'bg-blue-900/50', border: 'border-blue-500/30', text: 'text-blue-400' }
9
+ }
10
+ c = colors[color] || colors['indigo']
11
+ %>
12
+
13
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-5">
14
+ <div class="flex items-center">
15
+ <div class="flex-shrink-0">
16
+ <div class="rounded-md <%= c[:bg] %> <%= c[:border] %> border p-3">
17
+ <% case icon %>
18
+ <% when 'memory' %>
19
+ <svg class="h-6 w-6 <%= c[:text] %>" fill="none" viewBox="0 0 24 24" stroke="currentColor">
20
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
21
+ </svg>
22
+ <% when 'vector' %>
23
+ <svg class="h-6 w-6 <%= c[:text] %>" fill="none" viewBox="0 0 24 24" stroke="currentColor">
24
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
25
+ </svg>
26
+ <% when 'trash' %>
27
+ <svg class="h-6 w-6 <%= c[:text] %>" fill="none" viewBox="0 0 24 24" stroke="currentColor">
28
+ <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" />
29
+ </svg>
30
+ <% when 'tag' %>
31
+ <svg class="h-6 w-6 <%= c[:text] %>" fill="none" viewBox="0 0 24 24" stroke="currentColor">
32
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
33
+ </svg>
34
+ <% when 'robot' %>
35
+ <svg class="h-6 w-6 <%= c[:text] %>" fill="none" viewBox="0 0 24 24" stroke="currentColor">
36
+ <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" />
37
+ </svg>
38
+ <% when 'file' %>
39
+ <svg class="h-6 w-6 <%= c[:text] %>" fill="none" viewBox="0 0 24 24" stroke="currentColor">
40
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
41
+ </svg>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+ <div class="ml-5 w-0 flex-1">
46
+ <dl>
47
+ <dt class="truncate text-sm font-medium text-gray-400"><%= title %></dt>
48
+ <dd class="text-2xl font-semibold text-white"><%= number_with_delimiter(value) %></dd>
49
+ </dl>
50
+ </div>
51
+ </div>
52
+ </div>