llmemory 0.1.1 → 0.1.7
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/README.md +48 -1
- data/exe/llmemory +8 -0
- data/lib/llmemory/cli/commands/base.rb +76 -0
- data/lib/llmemory/cli/commands/long_term/categories.rb +34 -0
- data/lib/llmemory/cli/commands/long_term/edges.rb +43 -0
- data/lib/llmemory/cli/commands/long_term/facts.rb +42 -0
- data/lib/llmemory/cli/commands/long_term/graph.rb +76 -0
- data/lib/llmemory/cli/commands/long_term/nodes.rb +42 -0
- data/lib/llmemory/cli/commands/long_term/resources.rb +42 -0
- data/lib/llmemory/cli/commands/long_term.rb +19 -0
- data/lib/llmemory/cli/commands/search.rb +86 -0
- data/lib/llmemory/cli/commands/short_term.rb +59 -0
- data/lib/llmemory/cli/commands/stats.rb +70 -0
- data/lib/llmemory/cli/commands/users.rb +27 -0
- data/lib/llmemory/cli.rb +76 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/application_controller.rb +83 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/graph_controller.rb +18 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/long_term_controller.rb +31 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/search_controller.rb +58 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/short_term_controller.rb +15 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/stats_controller.rb +21 -0
- data/lib/llmemory/dashboard/app/controllers/llmemory/dashboard/users_controller.rb +27 -0
- data/lib/llmemory/dashboard/app/views/layouts/application.html.erb +53 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/graph/index.html.erb +41 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/long_term/categories.html.erb +12 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/long_term/index.html.erb +42 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/search/index.html.erb +43 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/short_term/show.html.erb +25 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/stats/index.html.erb +32 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/users/index.html.erb +30 -0
- data/lib/llmemory/dashboard/app/views/llmemory/dashboard/users/show.html.erb +14 -0
- data/lib/llmemory/dashboard/config/routes.rb +12 -0
- data/lib/llmemory/dashboard/engine.rb +9 -0
- data/lib/llmemory/dashboard.rb +8 -0
- data/lib/llmemory/llm.rb +4 -3
- data/lib/llmemory/long_term/file_based/storages/active_record_storage.rb +24 -0
- data/lib/llmemory/long_term/file_based/storages/base.rb +16 -0
- data/lib/llmemory/long_term/file_based/storages/database_storage.rb +35 -0
- data/lib/llmemory/long_term/file_based/storages/file_storage.rb +21 -0
- data/lib/llmemory/long_term/file_based/storages/memory_storage.rb +23 -0
- data/lib/llmemory/long_term/graph_based/storages/active_record_storage.rb +25 -2
- data/lib/llmemory/long_term/graph_based/storages/base.rb +17 -1
- data/lib/llmemory/long_term/graph_based/storages/memory_storage.rb +21 -2
- data/lib/llmemory/memory.rb +7 -4
- data/lib/llmemory/short_term/stores/active_record_store.rb +8 -0
- data/lib/llmemory/short_term/stores/base.rb +8 -0
- data/lib/llmemory/short_term/stores/memory_store.rb +9 -0
- data/lib/llmemory/short_term/stores/postgres_store.rb +15 -0
- data/lib/llmemory/short_term/stores/redis_store.rb +10 -0
- data/lib/llmemory/version.rb +1 -1
- 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
|
data/lib/llmemory/cli.rb
ADDED
|
@@ -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?(:object_id) ? e.object_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>
|