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.
- checksums.yaml +4 -4
- data/.envrc +1 -0
- data/CHANGELOG.md +67 -0
- data/README.md +97 -1592
- data/bin/htm_mcp +31 -0
- data/config/database.yml +7 -4
- data/docs/getting-started/installation.md +31 -11
- data/docs/guides/mcp-server.md +456 -21
- data/docs/multi_framework_support.md +2 -2
- data/examples/mcp_client.rb +2 -2
- data/examples/rails_app/.gitignore +2 -0
- data/examples/rails_app/Gemfile +22 -0
- data/examples/rails_app/Gemfile.lock +438 -0
- data/examples/rails_app/Procfile.dev +1 -0
- data/examples/rails_app/README.md +98 -0
- data/examples/rails_app/Rakefile +5 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +83 -0
- data/examples/rails_app/app/assets/stylesheets/inter-font.css +6 -0
- data/examples/rails_app/app/controllers/application_controller.rb +19 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +27 -0
- data/examples/rails_app/app/controllers/files_controller.rb +205 -0
- data/examples/rails_app/app/controllers/memories_controller.rb +102 -0
- data/examples/rails_app/app/controllers/robots_controller.rb +44 -0
- data/examples/rails_app/app/controllers/search_controller.rb +46 -0
- data/examples/rails_app/app/controllers/tags_controller.rb +30 -0
- data/examples/rails_app/app/javascript/application.js +4 -0
- data/examples/rails_app/app/javascript/controllers/application.js +9 -0
- data/examples/rails_app/app/javascript/controllers/index.js +6 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +123 -0
- data/examples/rails_app/app/views/files/index.html.erb +108 -0
- data/examples/rails_app/app/views/files/new.html.erb +321 -0
- data/examples/rails_app/app/views/files/show.html.erb +130 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +124 -0
- data/examples/rails_app/app/views/memories/_memory_card.html.erb +51 -0
- data/examples/rails_app/app/views/memories/deleted.html.erb +62 -0
- data/examples/rails_app/app/views/memories/edit.html.erb +35 -0
- data/examples/rails_app/app/views/memories/index.html.erb +81 -0
- data/examples/rails_app/app/views/memories/new.html.erb +71 -0
- data/examples/rails_app/app/views/memories/show.html.erb +126 -0
- data/examples/rails_app/app/views/robots/index.html.erb +106 -0
- data/examples/rails_app/app/views/robots/new.html.erb +36 -0
- data/examples/rails_app/app/views/robots/show.html.erb +79 -0
- data/examples/rails_app/app/views/search/index.html.erb +184 -0
- data/examples/rails_app/app/views/shared/_navbar.html.erb +52 -0
- data/examples/rails_app/app/views/shared/_stat_card.html.erb +52 -0
- data/examples/rails_app/app/views/tags/index.html.erb +131 -0
- data/examples/rails_app/app/views/tags/show.html.erb +67 -0
- data/examples/rails_app/bin/dev +8 -0
- data/examples/rails_app/bin/rails +4 -0
- data/examples/rails_app/bin/rake +4 -0
- data/examples/rails_app/config/application.rb +33 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +15 -0
- data/examples/rails_app/config/environment.rb +5 -0
- data/examples/rails_app/config/importmap.rb +7 -0
- data/examples/rails_app/config/routes.rb +38 -0
- data/examples/rails_app/config/tailwind.config.js +35 -0
- data/examples/rails_app/config.ru +5 -0
- data/examples/rails_app/log/.keep +0 -0
- data/examples/rails_app/tmp/local_secret.txt +1 -0
- data/lib/htm/active_record_config.rb +2 -5
- data/lib/htm/configuration.rb +35 -2
- data/lib/htm/database.rb +3 -6
- data/lib/htm/mcp/cli.rb +333 -0
- data/lib/htm/mcp/group_tools.rb +476 -0
- data/lib/htm/mcp/resources.rb +89 -0
- data/lib/htm/mcp/server.rb +98 -0
- data/lib/htm/mcp/tools.rb +488 -0
- data/lib/htm/models/file_source.rb +5 -3
- data/lib/htm/railtie.rb +0 -4
- data/lib/htm/tasks.rb +7 -4
- data/lib/htm/version.rb +1 -1
- data/lib/tasks/htm.rake +6 -9
- metadata +59 -4
- data/bin/htm_mcp.rb +0 -621
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="h-full bg-gray-900">
|
|
3
|
+
<head>
|
|
4
|
+
<title>HTM Memory Explorer</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<meta name="turbo-cache-control" content="no-preview">
|
|
7
|
+
<meta name="turbo-refresh-method" content="morph">
|
|
8
|
+
<meta name="turbo-refresh-scroll" content="preserve">
|
|
9
|
+
<%= csrf_meta_tags %>
|
|
10
|
+
<%= csp_meta_tag %>
|
|
11
|
+
|
|
12
|
+
<%# Tailwind CSS via CDN - perfect for demo apps %>
|
|
13
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
14
|
+
<script>
|
|
15
|
+
tailwind.config = {
|
|
16
|
+
darkMode: 'class',
|
|
17
|
+
theme: {
|
|
18
|
+
extend: {
|
|
19
|
+
colors: {
|
|
20
|
+
htm: {
|
|
21
|
+
50: '#f0f9ff',
|
|
22
|
+
100: '#e0f2fe',
|
|
23
|
+
200: '#bae6fd',
|
|
24
|
+
300: '#7dd3fc',
|
|
25
|
+
400: '#38bdf8',
|
|
26
|
+
500: '#0ea5e9',
|
|
27
|
+
600: '#0284c7',
|
|
28
|
+
700: '#0369a1',
|
|
29
|
+
800: '#075985',
|
|
30
|
+
900: '#0c4a6e',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<%# Google Fonts %>
|
|
39
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
40
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
41
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
42
|
+
|
|
43
|
+
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
|
44
|
+
|
|
45
|
+
<%# Hotwire via CDN - no build step needed %>
|
|
46
|
+
<script type="module">
|
|
47
|
+
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/+esm'
|
|
48
|
+
|
|
49
|
+
// Live reload: auto-refresh every 3 seconds using Turbo morphing
|
|
50
|
+
// This preserves scroll position and smoothly updates only changed elements
|
|
51
|
+
let autoRefreshEnabled = true;
|
|
52
|
+
let formFieldFocused = false;
|
|
53
|
+
let refreshInterval = 3000;
|
|
54
|
+
|
|
55
|
+
function autoRefresh() {
|
|
56
|
+
// Don't refresh if form field is focused (would interrupt typing)
|
|
57
|
+
if (autoRefreshEnabled && document.visibilityState === 'visible' && !formFieldFocused) {
|
|
58
|
+
Turbo.visit(window.location.href, { action: 'replace' });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Start auto-refresh after page loads
|
|
63
|
+
document.addEventListener('turbo:load', () => {
|
|
64
|
+
setInterval(autoRefresh, refreshInterval);
|
|
65
|
+
|
|
66
|
+
// Pause refresh when any form field is focused
|
|
67
|
+
document.addEventListener('focusin', (e) => {
|
|
68
|
+
if (e.target.matches('input, textarea, select')) {
|
|
69
|
+
formFieldFocused = true;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
document.addEventListener('focusout', (e) => {
|
|
74
|
+
if (e.target.matches('input, textarea, select')) {
|
|
75
|
+
formFieldFocused = false;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Pause refresh when page is hidden
|
|
81
|
+
document.addEventListener('visibilitychange', () => {
|
|
82
|
+
// Refresh immediately when tab becomes visible again
|
|
83
|
+
if (document.visibilityState === 'visible') {
|
|
84
|
+
autoRefresh();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Allow toggling from console: window.toggleAutoRefresh()
|
|
89
|
+
window.toggleAutoRefresh = () => {
|
|
90
|
+
autoRefreshEnabled = !autoRefreshEnabled;
|
|
91
|
+
console.log('Auto-refresh:', autoRefreshEnabled ? 'ON' : 'OFF');
|
|
92
|
+
return autoRefreshEnabled;
|
|
93
|
+
};
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<style>
|
|
97
|
+
body { font-family: 'Inter', sans-serif; }
|
|
98
|
+
code, pre, .font-mono { font-family: 'JetBrains Mono', monospace; }
|
|
99
|
+
</style>
|
|
100
|
+
</head>
|
|
101
|
+
|
|
102
|
+
<body class="h-full bg-gray-900 text-gray-100">
|
|
103
|
+
<div class="min-h-full">
|
|
104
|
+
<%= render 'shared/navbar' %>
|
|
105
|
+
|
|
106
|
+
<main class="py-6">
|
|
107
|
+
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
108
|
+
<% if flash[:notice] %>
|
|
109
|
+
<div class="mb-4 rounded-md bg-green-900/50 border border-green-500/50 p-4">
|
|
110
|
+
<p class="text-sm text-green-400"><%= flash[:notice] %></p>
|
|
111
|
+
</div>
|
|
112
|
+
<% end %>
|
|
113
|
+
<% if flash[:alert] %>
|
|
114
|
+
<div class="mb-4 rounded-md bg-red-900/50 border border-red-500/50 p-4">
|
|
115
|
+
<p class="text-sm text-red-400"><%= flash[:alert] %></p>
|
|
116
|
+
</div>
|
|
117
|
+
<% end %>
|
|
118
|
+
|
|
119
|
+
<%= yield %>
|
|
120
|
+
</div>
|
|
121
|
+
</main>
|
|
122
|
+
</div>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<div class="rounded-lg bg-gray-800 border border-gray-700 p-5 hover:border-gray-600 transition-colors">
|
|
2
|
+
<div class="flex items-start justify-between gap-4">
|
|
3
|
+
<div class="flex-1 min-w-0">
|
|
4
|
+
<%= link_to memory_path(memory), class: 'block group' do %>
|
|
5
|
+
<p class="text-sm text-gray-300 group-hover:text-white transition-colors line-clamp-3">
|
|
6
|
+
<%= memory.content %>
|
|
7
|
+
</p>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<div class="mt-3 flex flex-wrap items-center gap-2">
|
|
11
|
+
<% memory.tags.each do |tag| %>
|
|
12
|
+
<%= link_to memories_path(tag: tag.name), class: 'inline-flex items-center rounded-full bg-indigo-900/50 border border-indigo-500/30 px-2 py-0.5 text-xs text-indigo-300 hover:bg-indigo-800/50 transition-colors' do %>
|
|
13
|
+
<%= tag.name %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="mt-3 flex items-center gap-4 text-xs text-gray-500">
|
|
19
|
+
<span class="font-mono text-gray-600">#<%= memory.id %></span>
|
|
20
|
+
<span><%= time_ago_in_words(memory.created_at) %> ago</span>
|
|
21
|
+
<% if memory.embedding.present? %>
|
|
22
|
+
<span class="inline-flex items-center gap-1 text-green-500">
|
|
23
|
+
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>
|
|
24
|
+
embedded
|
|
25
|
+
</span>
|
|
26
|
+
<% else %>
|
|
27
|
+
<span class="inline-flex items-center gap-1 text-yellow-500">
|
|
28
|
+
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>
|
|
29
|
+
pending
|
|
30
|
+
</span>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% if memory.metadata.present? && memory.metadata.any? %>
|
|
33
|
+
<span class="text-gray-500">+ metadata</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="flex-shrink-0 flex items-center gap-2">
|
|
39
|
+
<%= link_to edit_memory_path(memory), class: 'rounded p-1.5 text-gray-400 hover:text-white hover:bg-gray-700 transition-colors', title: 'Edit' do %>
|
|
40
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
41
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
42
|
+
</svg>
|
|
43
|
+
<% end %>
|
|
44
|
+
<%= button_to memory_path(memory), method: :delete, class: 'rounded p-1.5 text-gray-400 hover:text-red-400 hover:bg-gray-700 transition-colors', title: 'Delete', data: { turbo_confirm: 'Move this memory to trash?' } do %>
|
|
45
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
46
|
+
<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
|
+
</svg>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
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">Trash</h1>
|
|
6
|
+
<p class="mt-1 text-sm text-gray-400">Deleted memories that can be restored</p>
|
|
7
|
+
</div>
|
|
8
|
+
<%= link_to memories_path, class: 'inline-flex items-center gap-2 rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600 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="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
11
|
+
</svg>
|
|
12
|
+
Back to Memories
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Deleted Memories List -->
|
|
17
|
+
<% if @memories.any? %>
|
|
18
|
+
<div class="space-y-4">
|
|
19
|
+
<% @memories.each do |memory| %>
|
|
20
|
+
<div class="rounded-lg bg-gray-800 border border-red-900/50 p-5">
|
|
21
|
+
<div class="flex items-start justify-between gap-4">
|
|
22
|
+
<div class="flex-1 min-w-0">
|
|
23
|
+
<%= link_to memory_path(memory), class: 'block group' do %>
|
|
24
|
+
<p class="text-sm text-gray-400 group-hover:text-gray-300 transition-colors line-clamp-3">
|
|
25
|
+
<%= memory.content %>
|
|
26
|
+
</p>
|
|
27
|
+
<% end %>
|
|
28
|
+
|
|
29
|
+
<div class="mt-3 flex items-center gap-4 text-xs text-gray-500">
|
|
30
|
+
<span>Deleted <%= time_ago_in_words(memory.deleted_at) %> ago</span>
|
|
31
|
+
<span>Created <%= time_ago_in_words(memory.created_at) %> ago</span>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="flex-shrink-0 flex items-center gap-2">
|
|
36
|
+
<%= button_to restore_memory_path(memory), method: :post, class: 'inline-flex items-center gap-1 rounded-md bg-green-600/80 px-3 py-1.5 text-xs font-medium text-white hover:bg-green-600 transition-colors' do %>
|
|
37
|
+
<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
38
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
39
|
+
</svg>
|
|
40
|
+
Restore
|
|
41
|
+
<% end %>
|
|
42
|
+
<%= button_to memory_path(memory, permanent: true), method: :delete, class: 'inline-flex items-center gap-1 rounded-md bg-red-600/80 px-3 py-1.5 text-xs font-medium text-white hover:bg-red-600 transition-colors', data: { turbo_confirm: 'Permanently delete? This cannot be undone!' } do %>
|
|
43
|
+
<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
45
|
+
</svg>
|
|
46
|
+
Delete Forever
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
</div>
|
|
53
|
+
<% else %>
|
|
54
|
+
<div class="rounded-lg bg-gray-800 border border-gray-700 p-8 text-center">
|
|
55
|
+
<svg class="mx-auto h-12 w-12 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
56
|
+
<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" />
|
|
57
|
+
</svg>
|
|
58
|
+
<h3 class="mt-4 text-lg font-medium text-white">Trash is empty</h3>
|
|
59
|
+
<p class="mt-2 text-sm text-gray-400">Deleted memories will appear here.</p>
|
|
60
|
+
</div>
|
|
61
|
+
<% end %>
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<div class="space-y-6">
|
|
2
|
+
<!-- Back link -->
|
|
3
|
+
<div>
|
|
4
|
+
<%= link_to memory_path(@memory), 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 Memory
|
|
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">Edit Memory</h1>
|
|
15
|
+
|
|
16
|
+
<%= form_with url: memory_path(@memory), method: :patch, class: 'space-y-6' do %>
|
|
17
|
+
<div>
|
|
18
|
+
<label for="content" class="block text-sm font-medium text-gray-300 mb-2">Content</label>
|
|
19
|
+
<textarea
|
|
20
|
+
name="content"
|
|
21
|
+
id="content"
|
|
22
|
+
rows="6"
|
|
23
|
+
required
|
|
24
|
+
class="w-full rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 focus:ring-indigo-500 focus:border-indigo-500"
|
|
25
|
+
><%= @memory.content %></textarea>
|
|
26
|
+
<p class="mt-1 text-xs text-gray-500">Editing content will regenerate embeddings.</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="flex items-center gap-4 pt-4 border-t border-gray-700">
|
|
30
|
+
<%= submit_tag 'Update Memory', class: 'rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 cursor-pointer transition-colors' %>
|
|
31
|
+
<%= link_to 'Cancel', memory_path(@memory), class: 'rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600 transition-colors' %>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
@@ -0,0 +1,81 @@
|
|
|
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">Memories</h1>
|
|
6
|
+
<p class="mt-1 text-sm text-gray-400">Browse and manage stored memories</p>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex gap-3">
|
|
9
|
+
<%= link_to deleted_memories_path, class: 'inline-flex items-center gap-2 rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600 transition-colors' do %>
|
|
10
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
11
|
+
<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" />
|
|
12
|
+
</svg>
|
|
13
|
+
Trash
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= link_to new_memory_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 %>
|
|
16
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
17
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
18
|
+
</svg>
|
|
19
|
+
Add Memory
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Filters -->
|
|
25
|
+
<div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
|
|
26
|
+
<%= form_with url: memories_path, method: :get, class: 'flex flex-wrap gap-4' do %>
|
|
27
|
+
<div class="flex-1 min-w-[200px]">
|
|
28
|
+
<label class="block text-xs font-medium text-gray-400 mb-1">Search</label>
|
|
29
|
+
<%= text_field_tag :search, params[:search], placeholder: 'Search memories...', class: 'w-full rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 text-sm focus:ring-indigo-500 focus:border-indigo-500' %>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="w-48">
|
|
32
|
+
<label class="block text-xs font-medium text-gray-400 mb-1">Filter by Tag</label>
|
|
33
|
+
<%= text_field_tag :tag, params[:tag], placeholder: 'e.g. database:postgresql', class: 'w-full rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 text-sm focus:ring-indigo-500 focus:border-indigo-500' %>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="flex items-end">
|
|
36
|
+
<%= submit_tag 'Filter', class: 'rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 cursor-pointer' %>
|
|
37
|
+
<% if params[:search].present? || params[:tag].present? %>
|
|
38
|
+
<%= link_to 'Clear', memories_path, class: 'ml-2 rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600' %>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Memories List -->
|
|
45
|
+
<% if @memories.any? %>
|
|
46
|
+
<div class="space-y-4">
|
|
47
|
+
<% @memories.each do |memory| %>
|
|
48
|
+
<%= render 'memory_card', memory: memory %>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Pagination -->
|
|
53
|
+
<% if @total_pages && @total_pages > 1 %>
|
|
54
|
+
<div class="flex justify-center gap-2">
|
|
55
|
+
<% if @page > 1 %>
|
|
56
|
+
<%= link_to "← Previous", memories_path(page: @page - 1, search: params[:search], tag: params[:tag]), class: 'rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600' %>
|
|
57
|
+
<% end %>
|
|
58
|
+
<span class="rounded-md bg-gray-800 px-4 py-2 text-sm text-gray-300">
|
|
59
|
+
Page <%= @page %> of <%= @total_pages %>
|
|
60
|
+
</span>
|
|
61
|
+
<% if @page < @total_pages %>
|
|
62
|
+
<%= link_to "Next →", memories_path(page: @page + 1, search: params[:search], tag: params[:tag]), class: 'rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600' %>
|
|
63
|
+
<% end %>
|
|
64
|
+
</div>
|
|
65
|
+
<% end %>
|
|
66
|
+
<% else %>
|
|
67
|
+
<div class="rounded-lg bg-gray-800 border border-gray-700 p-8 text-center">
|
|
68
|
+
<svg class="mx-auto h-12 w-12 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
69
|
+
<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" />
|
|
70
|
+
</svg>
|
|
71
|
+
<h3 class="mt-4 text-lg font-medium text-white">No memories found</h3>
|
|
72
|
+
<p class="mt-2 text-sm text-gray-400">
|
|
73
|
+
<% if params[:search].present? || params[:tag].present? %>
|
|
74
|
+
Try adjusting your filters or <%= link_to 'clear them', memories_path, class: 'text-indigo-400 hover:text-indigo-300' %>.
|
|
75
|
+
<% else %>
|
|
76
|
+
Get started by <%= link_to 'adding a new memory', new_memory_path, class: 'text-indigo-400 hover:text-indigo-300' %>.
|
|
77
|
+
<% end %>
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<div class="space-y-6">
|
|
2
|
+
<!-- Back link -->
|
|
3
|
+
<div>
|
|
4
|
+
<%= link_to memories_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 Memories
|
|
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">Add New Memory</h1>
|
|
15
|
+
|
|
16
|
+
<%= form_with url: memories_path, method: :post, class: 'space-y-6' do %>
|
|
17
|
+
<div>
|
|
18
|
+
<label for="content" class="block text-sm font-medium text-gray-300 mb-2">Content</label>
|
|
19
|
+
<textarea
|
|
20
|
+
name="content"
|
|
21
|
+
id="content"
|
|
22
|
+
rows="6"
|
|
23
|
+
required
|
|
24
|
+
placeholder="Enter the memory content..."
|
|
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
|
+
></textarea>
|
|
27
|
+
<p class="mt-1 text-xs text-gray-500">The main content of the memory. Will be embedded for semantic search.</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div>
|
|
31
|
+
<label for="tags" class="block text-sm font-medium text-gray-300 mb-2">Tags (optional)</label>
|
|
32
|
+
<input
|
|
33
|
+
type="text"
|
|
34
|
+
name="tags"
|
|
35
|
+
id="tags"
|
|
36
|
+
placeholder="database:postgresql, ai:embeddings"
|
|
37
|
+
class="w-full rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 focus:ring-indigo-500 focus:border-indigo-500"
|
|
38
|
+
/>
|
|
39
|
+
<p class="mt-1 text-xs text-gray-500">Comma-separated hierarchical tags. Use colons for namespaces (e.g., database:postgresql).</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div>
|
|
43
|
+
<label for="metadata" class="block text-sm font-medium text-gray-300 mb-2">Metadata (optional)</label>
|
|
44
|
+
<textarea
|
|
45
|
+
name="metadata"
|
|
46
|
+
id="metadata"
|
|
47
|
+
rows="3"
|
|
48
|
+
placeholder='{"source": "manual", "priority": "high"}'
|
|
49
|
+
class="w-full rounded-md bg-gray-700 border-gray-600 text-white placeholder-gray-400 font-mono text-sm focus:ring-indigo-500 focus:border-indigo-500"
|
|
50
|
+
></textarea>
|
|
51
|
+
<p class="mt-1 text-xs text-gray-500">Optional JSON metadata. Can be used to filter searches.</p>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="flex items-center gap-4 pt-4 border-t border-gray-700">
|
|
55
|
+
<%= submit_tag 'Save Memory', class: 'rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 cursor-pointer transition-colors' %>
|
|
56
|
+
<%= link_to 'Cancel', memories_path, class: 'rounded-md bg-gray-700 px-4 py-2 text-sm font-medium text-white hover:bg-gray-600 transition-colors' %>
|
|
57
|
+
</div>
|
|
58
|
+
<% end %>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Tips -->
|
|
62
|
+
<div class="rounded-lg bg-gray-800/50 border border-gray-700 p-4">
|
|
63
|
+
<h3 class="text-sm font-medium text-gray-300 mb-2">Tips for good memories</h3>
|
|
64
|
+
<ul class="text-xs text-gray-400 space-y-1 list-disc list-inside">
|
|
65
|
+
<li>Be specific and self-contained - each memory should make sense on its own</li>
|
|
66
|
+
<li>Use hierarchical tags for organization (e.g., project:myapp:authentication)</li>
|
|
67
|
+
<li>Include relevant context like dates, names, and technical details</li>
|
|
68
|
+
<li>Embeddings and tags are generated asynchronously after saving</li>
|
|
69
|
+
</ul>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<div class="space-y-6">
|
|
2
|
+
<!-- Back link -->
|
|
3
|
+
<div>
|
|
4
|
+
<%= link_to memories_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 Memories
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<!-- Memory Detail -->
|
|
13
|
+
<div class="rounded-lg bg-gray-800 border border-gray-700 overflow-hidden">
|
|
14
|
+
<div class="p-6">
|
|
15
|
+
<div class="flex items-start justify-between gap-4">
|
|
16
|
+
<div class="flex-1">
|
|
17
|
+
<% if @memory.deleted? %>
|
|
18
|
+
<div class="mb-4 rounded-md bg-red-900/50 border border-red-500/50 p-3">
|
|
19
|
+
<p class="text-sm text-red-400">This memory is in trash. Deleted <%= time_ago_in_words(@memory.deleted_at) %> ago.</p>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
|
|
23
|
+
<p class="text-lg text-gray-200 whitespace-pre-wrap"><%= @memory.content %></p>
|
|
24
|
+
|
|
25
|
+
<% if @memory.tags.any? %>
|
|
26
|
+
<div class="mt-4 flex flex-wrap gap-2">
|
|
27
|
+
<% @memory.tags.each do |tag| %>
|
|
28
|
+
<%= link_to memories_path(tag: tag.name), class: 'inline-flex items-center rounded-full bg-indigo-900/50 border border-indigo-500/30 px-3 py-1 text-sm text-indigo-300 hover:bg-indigo-800/50 transition-colors' do %>
|
|
29
|
+
<%= tag.name %>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="flex items-center gap-2">
|
|
37
|
+
<% if @memory.deleted? %>
|
|
38
|
+
<%= button_to restore_memory_path(@memory), method: :post, class: 'inline-flex items-center gap-2 rounded-md bg-green-600 px-3 py-2 text-sm font-medium text-white hover:bg-green-500 transition-colors' do %>
|
|
39
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
40
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
41
|
+
</svg>
|
|
42
|
+
Restore
|
|
43
|
+
<% end %>
|
|
44
|
+
<%= button_to memory_path(@memory, 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', data: { turbo_confirm: 'Permanently delete this memory? This cannot be undone!' } do %>
|
|
45
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
46
|
+
<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
|
+
</svg>
|
|
48
|
+
Delete Forever
|
|
49
|
+
<% end %>
|
|
50
|
+
<% else %>
|
|
51
|
+
<%= link_to edit_memory_path(@memory), class: 'inline-flex items-center gap-2 rounded-md bg-gray-700 px-3 py-2 text-sm font-medium text-white hover:bg-gray-600 transition-colors' do %>
|
|
52
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
54
|
+
</svg>
|
|
55
|
+
Edit
|
|
56
|
+
<% end %>
|
|
57
|
+
<%= button_to memory_path(@memory), 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', data: { turbo_confirm: 'Move this memory to trash?' } do %>
|
|
58
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
59
|
+
<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
|
+
</svg>
|
|
61
|
+
Delete
|
|
62
|
+
<% end %>
|
|
63
|
+
<% end %>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Metadata -->
|
|
69
|
+
<div class="border-t border-gray-700 bg-gray-800/50 px-6 py-4">
|
|
70
|
+
<dl class="grid grid-cols-2 gap-4 sm:grid-cols-4 text-sm">
|
|
71
|
+
<div>
|
|
72
|
+
<dt class="text-gray-500">ID</dt>
|
|
73
|
+
<dd class="mt-1 text-gray-300 font-mono"><%= @memory.id %></dd>
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<dt class="text-gray-500">Created</dt>
|
|
77
|
+
<dd class="mt-1 text-gray-300"><%= @memory.created_at.strftime('%Y-%m-%d %H:%M') %></dd>
|
|
78
|
+
</div>
|
|
79
|
+
<div>
|
|
80
|
+
<dt class="text-gray-500">Embedding</dt>
|
|
81
|
+
<dd class="mt-1">
|
|
82
|
+
<% if @memory.embedding.present? %>
|
|
83
|
+
<span class="inline-flex items-center gap-1 text-green-400">
|
|
84
|
+
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>
|
|
85
|
+
<%= @memory.embedding.is_a?(Array) ? @memory.embedding.size : 'N/A' %> dimensions
|
|
86
|
+
</span>
|
|
87
|
+
<% else %>
|
|
88
|
+
<span class="inline-flex items-center gap-1 text-yellow-400">
|
|
89
|
+
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>
|
|
90
|
+
Pending
|
|
91
|
+
</span>
|
|
92
|
+
<% end %>
|
|
93
|
+
</dd>
|
|
94
|
+
</div>
|
|
95
|
+
<div>
|
|
96
|
+
<dt class="text-gray-500">Content Hash</dt>
|
|
97
|
+
<dd class="mt-1 text-gray-300 font-mono text-xs truncate" title="<%= @memory.content_hash %>"><%= @memory.content_hash&.first(12) %>...</dd>
|
|
98
|
+
</div>
|
|
99
|
+
</dl>
|
|
100
|
+
|
|
101
|
+
<% if @memory.metadata.present? && @memory.metadata.any? %>
|
|
102
|
+
<div class="mt-4 pt-4 border-t border-gray-700">
|
|
103
|
+
<dt class="text-gray-500 text-sm mb-2">Metadata</dt>
|
|
104
|
+
<dd class="rounded-md bg-gray-900 p-3">
|
|
105
|
+
<pre class="text-xs text-gray-300 overflow-x-auto"><%= JSON.pretty_generate(@memory.metadata) %></pre>
|
|
106
|
+
</dd>
|
|
107
|
+
</div>
|
|
108
|
+
<% end %>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Related Memories -->
|
|
113
|
+
<% if @related.any? %>
|
|
114
|
+
<div class="rounded-lg bg-gray-800 border border-gray-700 p-6">
|
|
115
|
+
<h2 class="text-lg font-medium text-white mb-4">Related Memories</h2>
|
|
116
|
+
<div class="space-y-3">
|
|
117
|
+
<% @related.each do |memory| %>
|
|
118
|
+
<%= link_to memory_path(memory['id']), class: 'block rounded-md bg-gray-700/50 p-3 hover:bg-gray-700 transition-colors' do %>
|
|
119
|
+
<p class="text-sm text-gray-300 line-clamp-2"><%= memory['content'] %></p>
|
|
120
|
+
<p class="mt-1 text-xs text-gray-500"><%= time_ago_in_words(memory['created_at'].is_a?(Time) ? memory['created_at'] : Time.parse(memory['created_at'].to_s)) %> ago</p>
|
|
121
|
+
<% end %>
|
|
122
|
+
<% end %>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<% end %>
|
|
126
|
+
</div>
|