llmemory 0.1.1 → 0.1.8

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -1
  3. data/exe/llmemory +8 -0
  4. data/lib/llmemory/cli/commands/base.rb +76 -0
  5. data/lib/llmemory/cli/commands/long_term/categories.rb +34 -0
  6. data/lib/llmemory/cli/commands/long_term/edges.rb +43 -0
  7. data/lib/llmemory/cli/commands/long_term/facts.rb +42 -0
  8. data/lib/llmemory/cli/commands/long_term/graph.rb +76 -0
  9. data/lib/llmemory/cli/commands/long_term/nodes.rb +42 -0
  10. data/lib/llmemory/cli/commands/long_term/resources.rb +42 -0
  11. data/lib/llmemory/cli/commands/long_term.rb +19 -0
  12. data/lib/llmemory/cli/commands/search.rb +86 -0
  13. data/lib/llmemory/cli/commands/short_term.rb +59 -0
  14. data/lib/llmemory/cli/commands/stats.rb +70 -0
  15. data/lib/llmemory/cli/commands/users.rb +27 -0
  16. data/lib/llmemory/cli.rb +76 -0
  17. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/application_controller.rb +83 -0
  18. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/graph_controller.rb +18 -0
  19. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/long_term_controller.rb +31 -0
  20. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/search_controller.rb +58 -0
  21. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/short_term_controller.rb +15 -0
  22. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/stats_controller.rb +21 -0
  23. data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/users_controller.rb +27 -0
  24. data/lib/llmemory/dashboard/app/views/layouts/application.html.erb +53 -0
  25. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/graph/index.html.erb +41 -0
  26. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/long_term/categories.html.erb +12 -0
  27. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/long_term/index.html.erb +42 -0
  28. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/search/index.html.erb +43 -0
  29. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/short_term/show.html.erb +25 -0
  30. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/stats/index.html.erb +32 -0
  31. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/users/index.html.erb +30 -0
  32. data/lib/llmemory/dashboard/app/views/llmemory/dashboard/users/show.html.erb +14 -0
  33. data/lib/llmemory/dashboard/config/routes.rb +12 -0
  34. data/lib/llmemory/dashboard/engine.rb +9 -0
  35. data/lib/llmemory/dashboard.rb +8 -0
  36. data/lib/llmemory/llm.rb +4 -3
  37. data/lib/llmemory/long_term/file_based/storages/active_record_storage.rb +24 -0
  38. data/lib/llmemory/long_term/file_based/storages/base.rb +16 -0
  39. data/lib/llmemory/long_term/file_based/storages/database_storage.rb +35 -0
  40. data/lib/llmemory/long_term/file_based/storages/file_storage.rb +21 -0
  41. data/lib/llmemory/long_term/file_based/storages/memory_storage.rb +23 -0
  42. data/lib/llmemory/long_term/graph_based/conflict_resolver.rb +2 -2
  43. data/lib/llmemory/long_term/graph_based/edge.rb +3 -3
  44. data/lib/llmemory/long_term/graph_based/knowledge_graph.rb +2 -2
  45. data/lib/llmemory/long_term/graph_based/memory.rb +2 -2
  46. data/lib/llmemory/long_term/graph_based/storages/active_record_storage.rb +28 -5
  47. data/lib/llmemory/long_term/graph_based/storages/base.rb +17 -1
  48. data/lib/llmemory/long_term/graph_based/storages/memory_storage.rb +24 -5
  49. data/lib/llmemory/memory.rb +7 -4
  50. data/lib/llmemory/short_term/stores/active_record_store.rb +8 -0
  51. data/lib/llmemory/short_term/stores/base.rb +8 -0
  52. data/lib/llmemory/short_term/stores/memory_store.rb +9 -0
  53. data/lib/llmemory/short_term/stores/postgres_store.rb +15 -0
  54. data/lib/llmemory/short_term/stores/redis_store.rb +10 -0
  55. data/lib/llmemory/version.rb +1 -1
  56. metadata +39 -8
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Llmemory
6
+ module Cli
7
+ module Commands
8
+ class Users < Base
9
+ def option_parser(parser)
10
+ parser.on("--store TYPE", "Short-term store: memory, redis, postgres, active_record") do |v|
11
+ @store_type = v
12
+ end
13
+ end
14
+
15
+ def execute(_argv, _opts)
16
+ store = short_term_store(@store_type)
17
+ users = store.list_users
18
+ if users.empty?
19
+ puts "No users with short-term memory found."
20
+ else
21
+ users.each { |u| puts u }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require_relative "cli/commands/base"
5
+ require_relative "cli/commands/users"
6
+ require_relative "cli/commands/short_term"
7
+ require_relative "cli/commands/long_term"
8
+ require_relative "cli/commands/stats"
9
+ require_relative "cli/commands/search"
10
+
11
+ module Llmemory
12
+ class CLI
13
+ def self.run(argv)
14
+ new.run(argv)
15
+ end
16
+
17
+ def run(argv)
18
+ argv = argv.dup
19
+ return print_help if argv.empty?
20
+ first = argv.first
21
+ if first == "--help" || first == "-h"
22
+ print_help
23
+ return
24
+ end
25
+ subcommand = argv.shift
26
+ command_class = find_command(subcommand)
27
+ if command_class
28
+ command_class.new.run(argv)
29
+ else
30
+ $stderr.puts "llmemory: unknown command '#{subcommand}'"
31
+ $stderr.puts "Run 'llmemory --help' for usage."
32
+ exit 1
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def find_command(name)
39
+ normalized = name.tr("-", "_").downcase
40
+ {
41
+ "users" => Cli::Commands::Users,
42
+ "short_term" => Cli::Commands::ShortTerm,
43
+ "facts" => Cli::Commands::LongTerm::Facts,
44
+ "categories" => Cli::Commands::LongTerm::Categories,
45
+ "resources" => Cli::Commands::LongTerm::Resources,
46
+ "nodes" => Cli::Commands::LongTerm::Nodes,
47
+ "edges" => Cli::Commands::LongTerm::Edges,
48
+ "graph" => Cli::Commands::LongTerm::Graph,
49
+ "search" => Cli::Commands::Search,
50
+ "stats" => Cli::Commands::Stats
51
+ }[normalized]
52
+ end
53
+
54
+ def print_help
55
+ puts <<~HELP
56
+ llmemory - Inspect llmemory storage
57
+
58
+ Usage: llmemory <command> [options] [arguments]
59
+
60
+ Commands:
61
+ users List users with memory
62
+ short-term USER_ID Inspect short-term memory (use --list-sessions to list sessions)
63
+ facts USER_ID List long-term facts (file-based)
64
+ categories USER_ID List long-term categories (file-based)
65
+ resources USER_ID List long-term resources (file-based)
66
+ nodes USER_ID List graph nodes (graph-based)
67
+ edges USER_ID List graph edges (graph-based)
68
+ graph USER_ID Export graph (--format dot|json)
69
+ search USER_ID "query" Search in memory
70
+ stats [USER_ID] Show statistics
71
+
72
+ Run 'llmemory <command> --help' for command-specific options.
73
+ HELP
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class ApplicationController < ActionController::Base
6
+ helper_method :short_term_store, :file_based_storage, :graph_based_storage
7
+
8
+ protected
9
+
10
+ def short_term_store
11
+ @short_term_store ||= build_short_term_store
12
+ end
13
+
14
+ def file_based_storage
15
+ @file_based_storage ||= build_file_based_storage
16
+ end
17
+
18
+ def graph_based_storage
19
+ @graph_based_storage ||= build_graph_based_storage
20
+ end
21
+
22
+ def long_term_type
23
+ Llmemory.configuration.long_term_type.to_s
24
+ end
25
+
26
+ def graph_based?
27
+ long_term_type == "graph_based"
28
+ end
29
+
30
+ def file_based?
31
+ long_term_type == "file_based"
32
+ end
33
+
34
+ private
35
+
36
+ def build_short_term_store
37
+ type = (Llmemory.configuration.short_term_store).to_s.to_sym
38
+ case type
39
+ when :memory then Llmemory::ShortTerm::Stores::MemoryStore.new
40
+ when :redis then Llmemory::ShortTerm::Stores::RedisStore.new
41
+ when :postgres then Llmemory::ShortTerm::Stores::PostgresStore.new
42
+ when :active_record, :activerecord
43
+ require "llmemory/short_term/stores/active_record_store"
44
+ Llmemory::ShortTerm::Stores::ActiveRecordStore.new
45
+ else
46
+ Llmemory::ShortTerm::Stores::MemoryStore.new
47
+ end
48
+ end
49
+
50
+ def build_file_based_storage
51
+ type = (Llmemory.configuration.long_term_store).to_s.to_sym
52
+ case type
53
+ when :memory then Llmemory::LongTerm::FileBased::Storages::MemoryStorage.new
54
+ when :file
55
+ Llmemory::LongTerm::FileBased::Storages::FileStorage.new(
56
+ base_path: Llmemory.configuration.long_term_storage_path
57
+ )
58
+ when :postgres, :database
59
+ Llmemory::LongTerm::FileBased::Storages::DatabaseStorage.new(
60
+ database_url: Llmemory.configuration.database_url
61
+ )
62
+ when :active_record, :activerecord
63
+ require "llmemory/long_term/file_based/storages/active_record_storage"
64
+ Llmemory::LongTerm::FileBased::Storages::ActiveRecordStorage.new
65
+ else
66
+ Llmemory::LongTerm::FileBased::Storages::MemoryStorage.new
67
+ end
68
+ end
69
+
70
+ def build_graph_based_storage
71
+ type = (Llmemory.configuration.long_term_store).to_s.to_sym
72
+ case type
73
+ when :memory then Llmemory::LongTerm::GraphBased::Storages::MemoryStorage.new
74
+ when :active_record, :activerecord
75
+ require "llmemory/long_term/graph_based/storages/active_record_storage"
76
+ Llmemory::LongTerm::GraphBased::Storages::ActiveRecordStorage.new
77
+ else
78
+ Llmemory::LongTerm::GraphBased::Storages::MemoryStorage.new
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class GraphController < ApplicationController
6
+ def index
7
+ @user_id = params[:user_id]
8
+ @nodes = graph_based_storage.list_nodes(@user_id, limit: 200)
9
+ @edges = graph_based_storage.list_edges(@user_id, limit: 300)
10
+ end
11
+
12
+ def show
13
+ index
14
+ render :index
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class LongTermController < ApplicationController
6
+ before_action :ensure_file_based
7
+
8
+ def index
9
+ @user_id = params[:user_id]
10
+ @items = file_based_storage.list_items(user_id: @user_id, limit: 100)
11
+ @resources = file_based_storage.list_resources(user_id: @user_id, limit: 50)
12
+ end
13
+
14
+ def categories
15
+ @user_id = params[:user_id]
16
+ @category_names = file_based_storage.list_categories(@user_id)
17
+ @categories_content = {}
18
+ @category_names.each do |name|
19
+ @categories_content[name] = file_based_storage.load_category(@user_id, name)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def ensure_file_based
26
+ return if file_based?
27
+ redirect_to root_path, alert: "Long-term file-based view only available when long_term_type is file_based."
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class SearchController < ApplicationController
6
+ def index
7
+ @query = params[:q].to_s.strip
8
+ @user_id = params[:user_id].presence
9
+
10
+ if @query.empty?
11
+ @short_term_results = []
12
+ @file_based_results = []
13
+ @graph_results = []
14
+ return
15
+ end
16
+
17
+ if @user_id.present?
18
+ @short_term_results = search_short_term(@user_id)
19
+ if file_based?
20
+ @file_based_results = file_based_storage.search_items(@user_id, @query) +
21
+ file_based_storage.search_resources(@user_id, @query).map { |r| r.merge(type: "resource") }
22
+ else
23
+ @file_based_results = []
24
+ end
25
+ if graph_based?
26
+ nodes = graph_based_storage.list_nodes(@user_id)
27
+ q = @query.downcase
28
+ @graph_results = nodes.select { |n| (n.respond_to?(:name) ? n.name : n[:name]).to_s.downcase.include?(q) }
29
+ else
30
+ @graph_results = []
31
+ end
32
+ else
33
+ @short_term_results = []
34
+ @file_based_results = []
35
+ @graph_results = []
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def search_short_term(user_id)
42
+ sessions = short_term_store.list_sessions(user_id: user_id)
43
+ results = []
44
+ sessions.each do |session_id|
45
+ state = short_term_store.load(user_id, session_id)
46
+ next unless state
47
+ messages = state[:messages] || state["messages"] || []
48
+ messages.each do |m|
49
+ content = (m[:content] || m["content"]).to_s
50
+ next unless content.downcase.include?(@query.downcase)
51
+ results << { session_id: session_id, role: m[:role] || m["role"], content: content }
52
+ end
53
+ end
54
+ results
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class ShortTermController < ApplicationController
6
+ def show
7
+ @user_id = params[:user_id]
8
+ @session_id = params[:session_id].presence || "default"
9
+ state = short_term_store.load(@user_id, @session_id)
10
+ @messages = state ? (state[:messages] || state["messages"] || []) : []
11
+ @sessions = short_term_store.list_sessions(user_id: @user_id)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class StatsController < ApplicationController
6
+ def index
7
+ @user_id = params[:user_id]
8
+ @sessions = short_term_store.list_sessions(user_id: @user_id)
9
+
10
+ if graph_based?
11
+ @nodes_count = graph_based_storage.count_nodes(@user_id)
12
+ @edges_count = graph_based_storage.count_edges(@user_id)
13
+ else
14
+ @items_count = file_based_storage.count_items(user_id: @user_id)
15
+ @categories_count = file_based_storage.list_categories(@user_id).size
16
+ @resources_count = file_based_storage.list_resources(user_id: @user_id).size
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmemory
4
+ module Dashboard
5
+ class UsersController < ApplicationController
6
+ def index
7
+ @users = short_term_store.list_users
8
+ @users = (@users + long_term_user_ids).uniq
9
+ end
10
+
11
+ def show
12
+ @user_id = params[:user_id]
13
+ @sessions = short_term_store.list_sessions(user_id: @user_id)
14
+ end
15
+
16
+ protected
17
+
18
+ def long_term_user_ids
19
+ if graph_based?
20
+ graph_based_storage.list_users
21
+ else
22
+ file_based_storage.list_users
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Llmemory Dashboard</title>
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; background: #f5f5f5; color: #333; line-height: 1.5; }
10
+ .header { background: #2c3e50; color: #ecf0f1; padding: 12px 20px; }
11
+ .header a { color: #ecf0f1; text-decoration: none; }
12
+ .header a:hover { text-decoration: underline; }
13
+ .header h1 { margin: 0; font-size: 1.25rem; font-weight: 600; }
14
+ .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
15
+ .card { background: #fff; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); padding: 20px; margin-bottom: 20px; }
16
+ table { width: 100%; border-collapse: collapse; }
17
+ th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #eee; }
18
+ th { background: #f8f9fa; font-weight: 600; }
19
+ tr:hover { background: #f8f9fa; }
20
+ a { color: #3498db; }
21
+ a:hover { color: #2980b9; }
22
+ .nav-links { margin-top: 10px; }
23
+ .nav-links a { margin-right: 16px; }
24
+ .empty { color: #999; font-style: italic; }
25
+ .message { padding: 8px 12px; margin: 8px 0; border-radius: 4px; }
26
+ .message.user { background: #e8f4fd; border-left: 4px solid #3498db; }
27
+ .message.assistant { background: #f0f9f0; border-left: 4px solid #27ae60; }
28
+ pre { background: #f4f4f4; padding: 12px; border-radius: 4px; overflow-x: auto; font-size: 13px; }
29
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; }
30
+ .stat-box { background: #f8f9fa; padding: 16px; border-radius: 6px; text-align: center; }
31
+ .stat-box .value { font-size: 1.5rem; font-weight: 700; color: #2c3e50; }
32
+ .stat-box .label { font-size: 0.875rem; color: #666; margin-top: 4px; }
33
+ form { margin: 16px 0; }
34
+ input[type=text] { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; width: 300px; }
35
+ input[type=submit] { padding: 8px 16px; background: #3498db; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
36
+ input[type=submit]:hover { background: #2980b9; }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <div class="header">
41
+ <h1><a href="<%= main_app.respond_to?(:root_path) ? main_app.root_path : '#' %>">Llmemory</a> Dashboard</h1>
42
+ <div class="nav-links">
43
+ <a href="<%= root_path %>">Users</a>
44
+ <a href="<%= search_path %>">Search</a>
45
+ </div>
46
+ </div>
47
+ <div class="container">
48
+ <% if flash[:alert] %><p class="message" style="background:#fde8e8;border-left-color:#e74c3c;"><%= flash[:alert] %></p><% end %>
49
+ <% if flash[:notice] %><p class="message" style="background:#e8fdf0;border-left-color:#27ae60;"><%= flash[:notice] %></p><% end %>
50
+ <%= yield %>
51
+ </div>
52
+ </body>
53
+ </html>
@@ -0,0 +1,41 @@
1
+ <div class="card">
2
+ <h2>Knowledge graph: <%= @user_id %></h2>
3
+ <h3>Nodes (<%= @nodes.size %>)</h3>
4
+ <% if @nodes.blank? %>
5
+ <p class="empty">No nodes.</p>
6
+ <% else %>
7
+ <table>
8
+ <thead>
9
+ <tr><th>ID</th><th>Type</th><th>Name</th></tr>
10
+ </thead>
11
+ <tbody>
12
+ <% @nodes.each do |n| %>
13
+ <tr>
14
+ <td><%= n.respond_to?(:id) ? n.id : n[:id] %></td>
15
+ <td><%= n.respond_to?(:entity_type) ? n.entity_type : n[:entity_type] %></td>
16
+ <td><%= n.respond_to?(:name) ? n.name : n[:name] %></td>
17
+ </tr>
18
+ <% end %>
19
+ </tbody>
20
+ </table>
21
+ <% end %>
22
+ <h3>Edges (<%= @edges.size %>)</h3>
23
+ <% if @edges.blank? %>
24
+ <p class="empty">No edges.</p>
25
+ <% else %>
26
+ <table>
27
+ <thead>
28
+ <tr><th>Subject</th><th>Predicate</th><th>Object</th></tr>
29
+ </thead>
30
+ <tbody>
31
+ <% @edges.each do |e| %>
32
+ <tr>
33
+ <td><%= e.respond_to?(:subject_id) ? e.subject_id : e[:subject_id] %></td>
34
+ <td><%= e.respond_to?(:predicate) ? e.predicate : e[:predicate] %></td>
35
+ <td><%= e.respond_to?(:target_id) ? e.target_id : e[:object_id] %></td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
40
+ <% end %>
41
+ </div>
@@ -0,0 +1,12 @@
1
+ <div class="card">
2
+ <h2>Categories: <%= @user_id %></h2>
3
+ <p><a href="<%= user_long_term_path(@user_id) %>">Back to long-term</a></p>
4
+ <% if @category_names.blank? %>
5
+ <p class="empty">No categories.</p>
6
+ <% else %>
7
+ <% @category_names.each do |name| %>
8
+ <h3><%= name %></h3>
9
+ <pre><%= ERB::Util.html_escape(@categories_content[name].to_s) %></pre>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,42 @@
1
+ <div class="card">
2
+ <h2>Long-term memory (file-based): <%= @user_id %></h2>
3
+ <p><a href="<%= user_long_term_categories_path(@user_id) %>">View categories</a></p>
4
+ <h3>Items (facts)</h3>
5
+ <% if @items.blank? %>
6
+ <p class="empty">No items.</p>
7
+ <% else %>
8
+ <table>
9
+ <thead>
10
+ <tr><th>Category</th><th>Content</th><th>Created</th></tr>
11
+ </thead>
12
+ <tbody>
13
+ <% @items.each do |i| %>
14
+ <tr>
15
+ <td><%= i[:category] || i["category"] %></td>
16
+ <td><%= truncate((i[:content] || i["content"]).to_s, length: 120) %></td>
17
+ <td><%= i[:created_at] || i["created_at"] %></td>
18
+ </tr>
19
+ <% end %>
20
+ </tbody>
21
+ </table>
22
+ <% end %>
23
+ <h3>Resources</h3>
24
+ <% if @resources.blank? %>
25
+ <p class="empty">No resources.</p>
26
+ <% else %>
27
+ <table>
28
+ <thead>
29
+ <tr><th>ID</th><th>Text (excerpt)</th><th>Created</th></tr>
30
+ </thead>
31
+ <tbody>
32
+ <% @resources.each do |r| %>
33
+ <tr>
34
+ <td><%= r[:id] || r["id"] %></td>
35
+ <td><%= truncate((r[:text] || r["text"]).to_s, length: 100) %></td>
36
+ <td><%= r[:created_at] || r["created_at"] %></td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
41
+ <% end %>
42
+ </div>
@@ -0,0 +1,43 @@
1
+ <div class="card">
2
+ <h2>Search</h2>
3
+ <%= form_tag search_path, method: :get do %>
4
+ <%= text_field_tag :q, @query, placeholder: "Query" %>
5
+ <%= submit_tag "Search" %>
6
+ <% end %>
7
+ <% if @query.present? && @user_id.blank? %>
8
+ <p class="empty">Specify a user (e.g. add ?user_id=USER_ID to the URL) to search within that user's memory.</p>
9
+ <% end %>
10
+ <% if @query.present? && @user_id.present? %>
11
+ <h3>Short-term</h3>
12
+ <% if @short_term_results.blank? %>
13
+ <p class="empty">No matches.</p>
14
+ <% else %>
15
+ <% @short_term_results.each do |r| %>
16
+ <div class="message">
17
+ <strong>[<%= r[:session_id] %>] <%= r[:role] %>:</strong>
18
+ <%= truncate(r[:content], length: 200) %>
19
+ </div>
20
+ <% end %>
21
+ <% end %>
22
+ <% if file_based? %>
23
+ <h3>Long-term (file)</h3>
24
+ <% if @file_based_results.blank? %>
25
+ <p class="empty">No matches.</p>
26
+ <% else %>
27
+ <% @file_based_results.each do |r| %>
28
+ <p><%= r[:type] == "resource" ? "[resource]" : "[#{r[:category]}]" %> <%= truncate(r[:content] || r[:text], length: 150) %></p>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
32
+ <% if graph_based? %>
33
+ <h3>Graph nodes</h3>
34
+ <% if @graph_results.blank? %>
35
+ <p class="empty">No matches.</p>
36
+ <% else %>
37
+ <% @graph_results.each do |n| %>
38
+ <p>[<%= n.respond_to?(:entity_type) ? n.entity_type : n[:entity_type] %>] <%= n.respond_to?(:name) ? n.name : n[:name] %></p>
39
+ <% end %>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>
43
+ </div>
@@ -0,0 +1,25 @@
1
+ <div class="card">
2
+ <h2>Short-term memory: <%= @user_id %></h2>
3
+ <p>
4
+ Session: <%= @session_id %>
5
+ <% if @sessions.any? %>
6
+ | Other sessions:
7
+ <% @sessions.each do |sess| %>
8
+ <% next if sess == @session_id %>
9
+ <a href="<%= user_short_term_path(@user_id) %>?session_id=<%= sess %>"><%= sess %></a>
10
+ <% end %>
11
+ <% end %>
12
+ </p>
13
+ <% if @messages.blank? %>
14
+ <p class="empty">No messages in this session.</p>
15
+ <% else %>
16
+ <% @messages.each do |m| %>
17
+ <% role = m[:role] || m["role"] %>
18
+ <% content = (m[:content] || m["content"]).to_s %>
19
+ <div class="message <%= role %>">
20
+ <strong><%= role %>:</strong>
21
+ <%= content.gsub(/\n/, "<br>").html_safe %>
22
+ </div>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="card">
2
+ <h2>Stats: <%= @user_id %></h2>
3
+ <div class="stats-grid">
4
+ <div class="stat-box">
5
+ <div class="value"><%= @sessions.size %></div>
6
+ <div class="label">Short-term sessions</div>
7
+ </div>
8
+ <% if graph_based? %>
9
+ <div class="stat-box">
10
+ <div class="value"><%= @nodes_count %></div>
11
+ <div class="label">Nodes</div>
12
+ </div>
13
+ <div class="stat-box">
14
+ <div class="value"><%= @edges_count %></div>
15
+ <div class="label">Edges</div>
16
+ </div>
17
+ <% else %>
18
+ <div class="stat-box">
19
+ <div class="value"><%= @items_count %></div>
20
+ <div class="label">Items</div>
21
+ </div>
22
+ <div class="stat-box">
23
+ <div class="value"><%= @categories_count %></div>
24
+ <div class="label">Categories</div>
25
+ </div>
26
+ <div class="stat-box">
27
+ <div class="value"><%= @resources_count %></div>
28
+ <div class="label">Resources</div>
29
+ </div>
30
+ <% end %>
31
+ </div>
32
+ </div>
@@ -0,0 +1,30 @@
1
+ <div class="card">
2
+ <h2>Users with memory</h2>
3
+ <% if @users.blank? %>
4
+ <p class="empty">No users found.</p>
5
+ <% else %>
6
+ <table>
7
+ <thead>
8
+ <tr><th>User ID</th><th>Actions</th></tr>
9
+ </thead>
10
+ <tbody>
11
+ <% @users.each do |user_id| %>
12
+ <tr>
13
+ <td><%= user_id %></td>
14
+ <td>
15
+ <a href="<%= user_path(user_id) %>">View</a>
16
+ <a href="<%= user_short_term_path(user_id) %>">Short-term</a>
17
+ <% if file_based? %>
18
+ <a href="<%= user_long_term_path(user_id) %>">Long-term</a>
19
+ <% end %>
20
+ <% if graph_based? %>
21
+ <a href="<%= user_graph_path(user_id) %>">Graph</a>
22
+ <% end %>
23
+ <a href="<%= user_stats_path(user_id) %>">Stats</a>
24
+ </td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
29
+ <% end %>
30
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="card">
2
+ <h2>User: <%= @user_id %></h2>
3
+ <p>
4
+ <a href="<%= user_short_term_path(@user_id) %>">Short-term memory</a>
5
+ <% if file_based? %>
6
+ | <a href="<%= user_long_term_path(@user_id) %>">Long-term (file)</a>
7
+ <% end %>
8
+ <% if graph_based? %>
9
+ | <a href="<%= user_graph_path(@user_id) %>">Graph</a>
10
+ <% end %>
11
+ | <a href="<%= user_stats_path(@user_id) %>">Stats</a>
12
+ </p>
13
+ <p>Sessions: <%= @sessions.join(", ") %></p>
14
+ </div>