console_agent 0.1.0 → 0.3.0
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 +129 -143
- data/app/controllers/console_agent/application_controller.rb +21 -0
- data/app/controllers/console_agent/sessions_controller.rb +16 -0
- data/app/helpers/console_agent/sessions_helper.rb +42 -0
- data/app/models/console_agent/session.rb +23 -0
- data/app/views/console_agent/sessions/index.html.erb +57 -0
- data/app/views/console_agent/sessions/show.html.erb +56 -0
- data/app/views/layouts/console_agent/application.html.erb +83 -0
- data/config/routes.rb +4 -0
- data/lib/console_agent/configuration.rb +12 -5
- data/lib/console_agent/console_methods.rb +167 -4
- data/lib/console_agent/context_builder.rb +34 -125
- data/lib/console_agent/engine.rb +5 -0
- data/lib/console_agent/executor.rb +81 -1
- data/lib/console_agent/repl.rb +363 -42
- data/lib/console_agent/session_logger.rb +79 -0
- data/lib/console_agent/storage/base.rb +27 -0
- data/lib/console_agent/storage/file_storage.rb +63 -0
- data/lib/console_agent/tools/memory_tools.rb +136 -0
- data/lib/console_agent/tools/registry.rb +228 -2
- data/lib/console_agent/version.rb +1 -1
- data/lib/console_agent.rb +143 -3
- data/lib/generators/console_agent/templates/initializer.rb +14 -6
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 661ab1a997f36d2b10ea9649aa41367aca039c859c3e2afa01c3cbf8b8dd9345
|
|
4
|
+
data.tar.gz: 5cfe513d78a1785b7199fb0ebbbc70e825092e49e4051caf390e3ccb7ccf23c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d7fd63a81886c7abbf4f82db677b6b4bb9151760c378901306e789b3619d7ca31b743f7f4a14a9a28335e775877243b0c4ff8b8bd6cc6acee0c8a02c270eccc
|
|
7
|
+
data.tar.gz: 3e0308bd89e0421a4a29dbe8c7b7be6afd584a504559a456f830e1c9a268517d131d6dcf00417347f5f3ea80a434bf9211e0515771fc3e43686f0117bf401f9b
|
data/README.md
CHANGED
|
@@ -1,48 +1,40 @@
|
|
|
1
1
|
# ConsoleAgent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Claude Code, embedded in your Rails console.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Ask questions in plain English. The AI explores your schema, models, and source code on its own, then writes and runs the Ruby code for you.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Add to your Gemfile:
|
|
7
|
+
## Install
|
|
10
8
|
|
|
11
9
|
```ruby
|
|
10
|
+
# Gemfile
|
|
12
11
|
gem 'console_agent', group: :development
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
Run the install generator:
|
|
16
|
-
|
|
17
14
|
```bash
|
|
18
15
|
bundle install
|
|
19
16
|
rails generate console_agent:install
|
|
20
17
|
```
|
|
21
18
|
|
|
22
|
-
Set your API key
|
|
19
|
+
Set your API key in the generated initializer or as an env var (`ANTHROPIC_API_KEY`):
|
|
23
20
|
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
```ruby
|
|
22
|
+
# config/initializers/console_agent.rb
|
|
23
|
+
ConsoleAgent.configure do |config|
|
|
24
|
+
config.api_key = 'sk-ant-...'
|
|
25
|
+
end
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
To set up session logging (OPTIONAL), create the table from the console:
|
|
30
29
|
|
|
31
30
|
```ruby
|
|
32
|
-
|
|
31
|
+
ConsoleAgent.setup!
|
|
32
|
+
# => ConsoleAgent: created console_agent_sessions table.
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| Command | Description |
|
|
38
|
-
|---------|-------------|
|
|
39
|
-
| `ai "query"` | Ask a question, review generated code, confirm before executing |
|
|
40
|
-
| `ai! "query"` | Ask a question and enter interactive mode for follow-ups |
|
|
41
|
-
| `ai!` | Enter interactive mode (type `exit` to leave) |
|
|
42
|
-
| `ai? "query"` | Explain only — shows code but never executes |
|
|
43
|
-
| `ai_status` | Print current configuration summary |
|
|
35
|
+
To reset the table (e.g. after upgrading), run `ConsoleAgent.teardown!` then `ConsoleAgent.setup!`.
|
|
44
36
|
|
|
45
|
-
|
|
37
|
+
## Usage
|
|
46
38
|
|
|
47
39
|
```
|
|
48
40
|
irb> ai "find the 5 most recent orders over $100"
|
|
@@ -51,184 +43,178 @@ irb> ai "find the 5 most recent orders over $100"
|
|
|
51
43
|
12 tables: users, orders, line_items, products...
|
|
52
44
|
-> describe_table("orders")
|
|
53
45
|
8 columns
|
|
54
|
-
-> describe_model("Order")
|
|
55
|
-
4 associations, 2 validations
|
|
56
|
-
|
|
57
|
-
You can query recent high-value orders like this:
|
|
58
46
|
|
|
59
47
|
Order.where("total > ?", 100).order(created_at: :desc).limit(5)
|
|
60
48
|
|
|
61
|
-
[tokens in: 1,240 | out: 85 | total: 1,325]
|
|
62
49
|
Execute? [y/N/edit] y
|
|
63
50
|
=> [#<Order id: 4821, ...>, ...]
|
|
64
51
|
```
|
|
65
52
|
|
|
66
|
-
|
|
53
|
+
The AI calls tools behind the scenes to learn your app — schema, models, associations, source code — so it writes accurate queries without you providing any context.
|
|
54
|
+
|
|
55
|
+
### Commands
|
|
56
|
+
|
|
57
|
+
| Command | What it does |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `ai "query"` | One-shot: ask, review code, confirm |
|
|
60
|
+
| `ai! "query"` | Interactive: ask and keep chatting |
|
|
61
|
+
| `ai? "query"` | Explain only, never executes |
|
|
62
|
+
|
|
63
|
+
### Multi-Step Plans
|
|
64
|
+
|
|
65
|
+
For complex tasks, the AI builds a plan and executes it step by step:
|
|
67
66
|
|
|
68
67
|
```
|
|
69
|
-
|
|
70
|
-
ConsoleAgent interactive mode. Type 'exit' or 'quit' to leave.
|
|
71
|
-
ai> show me all tables
|
|
68
|
+
ai> get the most recent salesforce token and count events via the API
|
|
72
69
|
Thinking...
|
|
73
|
-
->
|
|
74
|
-
|
|
70
|
+
-> describe_table("oauth2_tokens")
|
|
71
|
+
28 columns
|
|
72
|
+
-> read_file("lib/salesforce_api.rb")
|
|
73
|
+
202 lines
|
|
74
|
+
|
|
75
|
+
Plan (2 steps):
|
|
76
|
+
1. Find the most recent active Salesforce OAuth2 token
|
|
77
|
+
token = Oauth2Token.where(provider: "salesforce", active: true)
|
|
78
|
+
.order(updated_at: :desc).first
|
|
79
|
+
2. Query event count via SOQL
|
|
80
|
+
api = SalesforceApi.new(step1)
|
|
81
|
+
api.query("SELECT COUNT(Id) FROM Event")
|
|
82
|
+
|
|
83
|
+
Accept plan? [y/N/a(uto)] a
|
|
84
|
+
Step 1/2: Find the most recent active Salesforce OAuth2 token
|
|
75
85
|
...
|
|
76
|
-
|
|
86
|
+
=> #<Oauth2Token id: 1, provider: "salesforce", ...>
|
|
87
|
+
|
|
88
|
+
Step 2/2: Query event count via SOQL
|
|
77
89
|
...
|
|
78
|
-
|
|
79
|
-
[session totals — in: 3,200 | out: 410 | total: 3,610]
|
|
80
|
-
Left ConsoleAgent interactive mode.
|
|
90
|
+
=> [{"expr0"=>42}]
|
|
81
91
|
```
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
Each step's return value is available to later steps as `step1`, `step2`, etc.
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Model: claude-opus-4-6
|
|
90
|
-
API key: sk-ant-...a3b4
|
|
91
|
-
Context mode: smart
|
|
92
|
-
Max tokens: 4096
|
|
93
|
-
Temperature: 0.2
|
|
94
|
-
Timeout: 30s
|
|
95
|
-
Max tool rounds:10
|
|
96
|
-
Auto-execute: false
|
|
97
|
-
Debug: false
|
|
98
|
-
```
|
|
95
|
+
Plan prompt options:
|
|
96
|
+
- **y** — accept, then confirm each step one at a time
|
|
97
|
+
- **a** — accept and auto-run all steps (stays in manual mode for future queries)
|
|
98
|
+
- **N** — decline; you're asked what to change and the AI revises
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
### Memories
|
|
101
101
|
|
|
102
|
-
The
|
|
102
|
+
The AI remembers what it learns about your codebase across sessions:
|
|
103
103
|
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
```
|
|
105
|
+
ai> how does sharding work?
|
|
106
|
+
-> read_file("config/initializers/sharding.rb")
|
|
107
|
+
-> save_memory("Sharding architecture")
|
|
108
|
+
Memory saved
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
This app uses database-per-shard. User.count returns the current shard only.
|
|
111
|
+
```
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
# config.model = 'claude-opus-4-6'
|
|
113
|
+
Next time, it already knows — no re-reading files, fewer tokens.
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
# :smart - (default) LLM uses tools to fetch schema/model/code on demand
|
|
117
|
-
# :full - sends all schema, models, and routes every time
|
|
118
|
-
config.context_mode = :smart
|
|
115
|
+
### Interactive Mode
|
|
119
116
|
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
```
|
|
118
|
+
irb> ai!
|
|
119
|
+
ConsoleAgent interactive mode. Type 'exit' to leave.
|
|
120
|
+
Auto-execute: OFF (Shift-Tab or /auto to toggle)
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
ai> show me all tables
|
|
123
|
+
...
|
|
124
|
+
ai> count orders by status
|
|
125
|
+
...
|
|
126
|
+
ai> /auto
|
|
127
|
+
Auto-execute: ON
|
|
128
|
+
ai> delete cancelled orders older than 90 days
|
|
129
|
+
...
|
|
130
|
+
ai> exit
|
|
131
|
+
```
|
|
125
132
|
|
|
126
|
-
|
|
127
|
-
config.auto_execute = false
|
|
133
|
+
Toggle `/auto` to skip confirmation prompts. `/debug` shows raw API traffic. `/usage` shows token stats.
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
config.max_tool_rounds = 10
|
|
135
|
+
### Sessions
|
|
131
136
|
|
|
132
|
-
|
|
133
|
-
config.timeout = 30
|
|
137
|
+
Sessions are saved automatically when session logging is enabled. You can name, list, and resume them.
|
|
134
138
|
|
|
135
|
-
# Debug mode: prints full API requests/responses and tool calls
|
|
136
|
-
# config.debug = true
|
|
137
|
-
end
|
|
138
139
|
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
ConsoleAgent
|
|
145
|
-
ENV['OPENAI_API_KEY'] = 'sk-...'
|
|
140
|
+
ai> /name sf_user_123_calendar
|
|
141
|
+
Session named: sf_user_123_calendar
|
|
142
|
+
ai> exit
|
|
143
|
+
Session #42 saved.
|
|
144
|
+
Resume with: ai_resume "sf_user_123_calendar"
|
|
145
|
+
Left ConsoleAgent interactive mode.
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
List recent sessions:
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
```
|
|
151
|
+
irb> ai_sessions
|
|
152
|
+
[Sessions — showing 3]
|
|
151
153
|
|
|
152
|
-
|
|
154
|
+
#42 sf_user_123_calendar find user 123 with calendar issues
|
|
155
|
+
[interactive] 5m ago 2340 tokens
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- **list_files** / **read_file** / **search_code** — browse app source code
|
|
157
|
+
#41 count all active users
|
|
158
|
+
[one_shot] 1h ago 850 tokens
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
#40 debug_payments explain payment flow
|
|
161
|
+
[interactive] 2h ago 4100 tokens
|
|
159
162
|
|
|
160
|
-
|
|
163
|
+
Use ai_resume(id_or_name) to resume a session.
|
|
164
|
+
```
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
Resume a session by name or ID — previous output is replayed, then you continue where you left off:
|
|
163
167
|
|
|
164
|
-
```ruby
|
|
165
|
-
ConsoleAgent.configure { |c| c.context_mode = :full }
|
|
166
168
|
```
|
|
169
|
+
irb> ai_resume "sf_user_123_calendar"
|
|
170
|
+
--- Replaying previous session output ---
|
|
171
|
+
ai> find user 123 with calendar issues
|
|
172
|
+
...previous output...
|
|
173
|
+
--- End of previous output ---
|
|
167
174
|
|
|
168
|
-
|
|
175
|
+
ConsoleAgent interactive mode (sf_user_123_calendar). Type 'exit' to leave.
|
|
176
|
+
ai> now check their calendar sync status
|
|
177
|
+
...
|
|
178
|
+
```
|
|
169
179
|
|
|
170
|
-
|
|
171
|
-
|------|-------------|
|
|
172
|
-
| `list_tables` | All database table names |
|
|
173
|
-
| `describe_table` | Columns, types, and indexes for one table |
|
|
174
|
-
| `list_models` | All model names with association names |
|
|
175
|
-
| `describe_model` | Associations, validations, scopes for one model |
|
|
176
|
-
| `list_files` | Ruby files in a directory |
|
|
177
|
-
| `read_file` | Read a source file (capped at 200 lines) |
|
|
178
|
-
| `search_code` | Grep for a pattern across Ruby files |
|
|
180
|
+
Name or rename a session after the fact:
|
|
179
181
|
|
|
180
|
-
|
|
182
|
+
```
|
|
183
|
+
irb> ai_name 41, "active_user_count"
|
|
184
|
+
Session #41 named: active_user_count
|
|
185
|
+
```
|
|
181
186
|
|
|
182
|
-
|
|
187
|
+
Filter sessions by search term:
|
|
183
188
|
|
|
184
|
-
|
|
189
|
+
```
|
|
190
|
+
irb> ai_sessions 20, search: "salesforce"
|
|
191
|
+
```
|
|
185
192
|
|
|
186
|
-
|
|
193
|
+
If you have an existing `console_agent_sessions` table, run `ConsoleAgent.migrate!` to add the `name` column.
|
|
187
194
|
|
|
188
|
-
|
|
195
|
+
## Configuration
|
|
189
196
|
|
|
190
|
-
|
|
197
|
+
All settings live in `config/initializers/console_agent.rb` and can be changed at runtime:
|
|
191
198
|
|
|
192
199
|
```ruby
|
|
193
200
|
ConsoleAgent.configure do |config|
|
|
194
|
-
config.provider = :openai
|
|
195
|
-
config.
|
|
201
|
+
config.provider = :anthropic # or :openai
|
|
202
|
+
config.auto_execute = false # true to skip confirmations
|
|
203
|
+
config.max_tokens = 4096 # max tokens per LLM response
|
|
204
|
+
config.max_tool_rounds = 10 # max tool calls per query
|
|
205
|
+
config.session_logging = true # log sessions to DB (run ConsoleAgent.setup!)
|
|
196
206
|
end
|
|
197
207
|
```
|
|
198
208
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
If your Rails app runs in Docker, mount the gem source as a volume.
|
|
202
|
-
|
|
203
|
-
In `docker-compose.yml`:
|
|
204
|
-
|
|
205
|
-
```yaml
|
|
206
|
-
volumes:
|
|
207
|
-
- /path/to/console_agent:/console_agent
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
In `Gemfile`:
|
|
209
|
+
For the admin UI, mount the engine:
|
|
211
210
|
|
|
212
211
|
```ruby
|
|
213
|
-
|
|
212
|
+
mount ConsoleAgent::Engine => '/console_agent'
|
|
214
213
|
```
|
|
215
214
|
|
|
216
|
-
For Docker builds (where the gem source isn't available yet), add a stub to your Dockerfile before `bundle install`:
|
|
217
|
-
|
|
218
|
-
```dockerfile
|
|
219
|
-
RUN mkdir -p /console_agent/lib/console_agent && \
|
|
220
|
-
echo "module ConsoleAgent; VERSION = '0.1.0'; end" > /console_agent/lib/console_agent/version.rb && \
|
|
221
|
-
echo "require 'console_agent/version'" > /console_agent/lib/console_agent.rb && \
|
|
222
|
-
printf "require_relative 'lib/console_agent/version'\nGem::Specification.new do |s|\n s.name = 'console_agent'\n s.version = ConsoleAgent::VERSION\n s.summary = 'stub'\n s.authors = ['x']\n s.files = ['lib/console_agent.rb']\n s.add_dependency 'rails', '>= 5.0'\n s.add_dependency 'faraday', '>= 1.0'\nend\n" > /console_agent/console_agent.gemspec
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
The volume mount overwrites the stub at runtime with the real source.
|
|
226
|
-
|
|
227
215
|
## Requirements
|
|
228
216
|
|
|
229
|
-
- Ruby >= 2.5
|
|
230
|
-
- Rails >= 5.0
|
|
231
|
-
- Faraday >= 1.0
|
|
217
|
+
- Ruby >= 2.5, Rails >= 5.0, Faraday >= 1.0
|
|
232
218
|
|
|
233
219
|
## License
|
|
234
220
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
protect_from_forgery with: :exception
|
|
4
|
+
|
|
5
|
+
before_action :authenticate!
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def authenticate!
|
|
10
|
+
username = ConsoleAgent.configuration.admin_username
|
|
11
|
+
password = ConsoleAgent.configuration.admin_password
|
|
12
|
+
|
|
13
|
+
return unless username && password
|
|
14
|
+
|
|
15
|
+
authenticate_or_request_with_http_basic('ConsoleAgent Admin') do |u, p|
|
|
16
|
+
ActiveSupport::SecurityUtils.secure_compare(u, username) &
|
|
17
|
+
ActiveSupport::SecurityUtils.secure_compare(p, password)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
class SessionsController < ApplicationController
|
|
3
|
+
PER_PAGE = 50
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@page = [params[:page].to_i, 1].max
|
|
7
|
+
@total = Session.count
|
|
8
|
+
@total_pages = (@total / PER_PAGE.to_f).ceil
|
|
9
|
+
@sessions = Session.recent.offset((@page - 1) * PER_PAGE).limit(PER_PAGE)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@session = Session.find(params[:id])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
module SessionsHelper
|
|
3
|
+
# Convert ANSI escape codes to HTML spans for terminal-style rendering
|
|
4
|
+
def ansi_to_html(text)
|
|
5
|
+
return '' if text.nil? || text.empty?
|
|
6
|
+
|
|
7
|
+
color_map = {
|
|
8
|
+
'30' => '#000', '31' => '#e74c3c', '32' => '#2ecc71', '33' => '#f39c12',
|
|
9
|
+
'34' => '#3498db', '35' => '#9b59b6', '36' => '#1abc9c', '37' => '#ecf0f1',
|
|
10
|
+
'90' => '#888', '91' => '#ff6b6b', '92' => '#69db7c', '93' => '#ffd43b',
|
|
11
|
+
'94' => '#74c0fc', '95' => '#da77f2', '96' => '#63e6be', '97' => '#fff'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
escaped = h(text).to_str
|
|
15
|
+
|
|
16
|
+
# Process ANSI codes: colors, bold, dim, reset
|
|
17
|
+
escaped.gsub!(/\e\[([0-9;]+)m/) do
|
|
18
|
+
codes = $1.split(';')
|
|
19
|
+
if codes.include?('0') || $1 == '0'
|
|
20
|
+
'</span>'
|
|
21
|
+
else
|
|
22
|
+
styles = []
|
|
23
|
+
codes.each do |code|
|
|
24
|
+
case code
|
|
25
|
+
when '1' then styles << 'font-weight:bold'
|
|
26
|
+
when '2' then styles << 'opacity:0.6'
|
|
27
|
+
when '4' then styles << 'text-decoration:underline'
|
|
28
|
+
else
|
|
29
|
+
styles << "color:#{color_map[code]}" if color_map[code]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
styles.empty? ? '' : "<span style=\"#{styles.join(';')}\">"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Clean up any remaining escape sequences
|
|
37
|
+
escaped.gsub!(/\e\[[0-9;]*[A-Za-z]/, '')
|
|
38
|
+
|
|
39
|
+
escaped.html_safe
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
class Session < ActiveRecord::Base
|
|
3
|
+
self.table_name = 'console_agent_sessions'
|
|
4
|
+
|
|
5
|
+
validates :query, presence: true
|
|
6
|
+
validates :conversation, presence: true
|
|
7
|
+
validates :mode, presence: true, inclusion: { in: %w[one_shot interactive explain] }
|
|
8
|
+
|
|
9
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
10
|
+
scope :named, ->(name) { where(name: name) }
|
|
11
|
+
scope :search, ->(q) { where("name LIKE ? OR query LIKE ?", "%#{q}%", "%#{q}%") }
|
|
12
|
+
|
|
13
|
+
def self.connection
|
|
14
|
+
klass = ConsoleAgent.configuration.connection_class
|
|
15
|
+
if klass
|
|
16
|
+
klass = Object.const_get(klass) if klass.is_a?(String)
|
|
17
|
+
klass.connection
|
|
18
|
+
else
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|
2
|
+
<h2 style="font-size: 20px;">Sessions</h2>
|
|
3
|
+
<span class="text-muted" style="font-size: 14px;"><%= @total %> total</span>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% if @sessions.empty? %>
|
|
7
|
+
<div class="meta-card">
|
|
8
|
+
<p class="text-muted">No sessions recorded yet.</p>
|
|
9
|
+
</div>
|
|
10
|
+
<% else %>
|
|
11
|
+
<table>
|
|
12
|
+
<thead>
|
|
13
|
+
<tr>
|
|
14
|
+
<th>Time</th>
|
|
15
|
+
<th>User</th>
|
|
16
|
+
<th>Name</th>
|
|
17
|
+
<th>Query</th>
|
|
18
|
+
<th>Mode</th>
|
|
19
|
+
<th>Tokens</th>
|
|
20
|
+
<th>Executed?</th>
|
|
21
|
+
<th>Duration</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
<% @sessions.each do |session| %>
|
|
26
|
+
<tr>
|
|
27
|
+
<td class="mono"><%= session.created_at.strftime('%Y-%m-%d %H:%M') %></td>
|
|
28
|
+
<td><%= session.user_name %></td>
|
|
29
|
+
<td><%= session.name.present? ? session.name : '-' %></td>
|
|
30
|
+
<td><a href="<%= console_agent.session_path(session) %>"><%= truncate(session.query, length: 80) %></a></td>
|
|
31
|
+
<td><span class="badge badge-<%= session.mode %>"><%= session.mode %></span></td>
|
|
32
|
+
<td class="mono"><%= session.input_tokens + session.output_tokens %></td>
|
|
33
|
+
<td><%= session.executed? ? '<span class="check">Yes</span>'.html_safe : '<span class="cross">No</span>'.html_safe %></td>
|
|
34
|
+
<td class="mono"><%= session.duration_ms ? "#{session.duration_ms}ms" : '-' %></td>
|
|
35
|
+
</tr>
|
|
36
|
+
<% end %>
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
|
|
40
|
+
<% if @total_pages > 1 %>
|
|
41
|
+
<div class="pagination">
|
|
42
|
+
<% if @page > 1 %>
|
|
43
|
+
<a href="<%= console_agent.sessions_path(page: @page - 1) %>">← Newer</a>
|
|
44
|
+
<% else %>
|
|
45
|
+
<span class="disabled">← Newer</span>
|
|
46
|
+
<% end %>
|
|
47
|
+
|
|
48
|
+
<span class="page-info">Page <%= @page %> of <%= @total_pages %></span>
|
|
49
|
+
|
|
50
|
+
<% if @page < @total_pages %>
|
|
51
|
+
<a href="<%= console_agent.sessions_path(page: @page + 1) %>">Older →</a>
|
|
52
|
+
<% else %>
|
|
53
|
+
<span class="disabled">Older →</span>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
<% end %>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<a href="<%= console_agent.sessions_path %>" class="back-link">← Back to sessions</a>
|
|
2
|
+
|
|
3
|
+
<div class="meta-card">
|
|
4
|
+
<div class="meta-grid">
|
|
5
|
+
<div class="meta-item">
|
|
6
|
+
<label>Query</label>
|
|
7
|
+
<span><%= @session.query %></span>
|
|
8
|
+
</div>
|
|
9
|
+
<% if @session.name.present? %>
|
|
10
|
+
<div class="meta-item">
|
|
11
|
+
<label>Name</label>
|
|
12
|
+
<span><%= @session.name %></span>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
15
|
+
<div class="meta-item">
|
|
16
|
+
<label>Mode</label>
|
|
17
|
+
<span class="badge badge-<%= @session.mode %>"><%= @session.mode %></span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="meta-item">
|
|
20
|
+
<label>User</label>
|
|
21
|
+
<span><%= @session.user_name || '-' %></span>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="meta-item">
|
|
24
|
+
<label>Provider / Model</label>
|
|
25
|
+
<span><%= @session.provider %> / <%= @session.model %></span>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="meta-item">
|
|
28
|
+
<label>Tokens (in / out)</label>
|
|
29
|
+
<span class="mono"><%= @session.input_tokens %> / <%= @session.output_tokens %></span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="meta-item">
|
|
32
|
+
<label>Duration</label>
|
|
33
|
+
<span class="mono"><%= @session.duration_ms ? "#{@session.duration_ms}ms" : '-' %></span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="meta-item">
|
|
36
|
+
<label>Executed?</label>
|
|
37
|
+
<span><%= @session.executed? ? '<span class="check">Yes</span>'.html_safe : '<span class="cross">No</span>'.html_safe %></span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="meta-item">
|
|
40
|
+
<label>Time</label>
|
|
41
|
+
<span><%= @session.created_at.strftime('%Y-%m-%d %H:%M:%S') %></span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<h3 style="font-size: 16px; margin-bottom: 12px;">Console Output</h3>
|
|
47
|
+
|
|
48
|
+
<% if @session.console_output.present? %>
|
|
49
|
+
<div class="terminal">
|
|
50
|
+
<pre><%= ansi_to_html(@session.console_output) %></pre>
|
|
51
|
+
</div>
|
|
52
|
+
<% else %>
|
|
53
|
+
<div class="meta-card">
|
|
54
|
+
<p class="text-muted">No console output recorded for this session.</p>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|