rails-llm 0.1.0 → 0.2.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/Gemfile +0 -1
- data/README.md +5 -1
- data/app/assets/javascripts/rails_llm/application.js +46 -5
- data/app/assets/stylesheets/rails_llm/application.css +642 -0
- data/app/controllers/rails_llm/agents_controller.rb +4 -1
- data/app/views/layouts/rails_llm/application.html.erb +12 -381
- data/app/views/rails_llm/agents/_message.html.erb +1 -0
- data/app/views/rails_llm/agents/index.html.erb +2 -1
- data/app/views/rails_llm/agents/show.html.erb +4 -3
- data/lib/rails_llm/stream.rb +4 -2
- data/lib/rails_llm/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efa61c893aa2e1e5c832f40e5bcb8ce69d8b50818da940b8f6de356e3548f682
|
|
4
|
+
data.tar.gz: 0d953138a349b41eaa8280e725a61952af83ec61b1012440e086dafae7ed113e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0525225b76f8b987c1bd3d1cb2bfe7874f7ee6a5f388508866dd1ad79879828e906af6f5b173d2758e637cd52f6c8e8a1f21277b8ef156af6262d455b3a571f5
|
|
7
|
+
data.tar.gz: 34f65efc1bc37e9a1156e6070b4c329ddbe1c37566ee1f8ebb3ffbc5c1a223d80a67af175b86c065a957edfdc25d9996e4993540e0ee397cd22a8b604875c03d
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -73,7 +73,7 @@ export DEEPSEEK_API_KEY=...
|
|
|
73
73
|
|
|
74
74
|
**5. Profit**
|
|
75
75
|
|
|
76
|
-
Open your browser:
|
|
76
|
+
Open your browser:
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
open http://localhost:3000/ai/agents
|
|
@@ -122,6 +122,10 @@ agent.ask("Tell me a story") { |chunk| print chunk } # streaming
|
|
|
122
122
|
| POST | `/ai/agents` | Create a new agent |
|
|
123
123
|
| POST | `/ai/agents/:id/ask` | Send a message |)
|
|
124
124
|
|
|
125
|
+
#### Screenshot
|
|
126
|
+
|
|
127
|
+

|
|
128
|
+
|
|
125
129
|
## License
|
|
126
130
|
|
|
127
131
|
[BSD Zero Clause](LICENSE)
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
;(function() {
|
|
2
2
|
const View = (messages) => {
|
|
3
|
-
const append = (role, content) => {
|
|
3
|
+
const append = (role, label, content) => {
|
|
4
4
|
const node = document.createElement("div")
|
|
5
5
|
node.className = `message ${role}`
|
|
6
|
-
|
|
6
|
+
const labelDiv = document.createElement("div")
|
|
7
|
+
labelDiv.className = "role-label"
|
|
8
|
+
labelDiv.textContent = `${label}:`
|
|
9
|
+
node.appendChild(labelDiv)
|
|
10
|
+
if (content) {
|
|
11
|
+
const contentDiv = document.createElement("div")
|
|
12
|
+
contentDiv.textContent = content
|
|
13
|
+
node.appendChild(contentDiv)
|
|
14
|
+
}
|
|
7
15
|
messages.appendChild(node)
|
|
8
16
|
messages.scrollTop = messages.scrollHeight
|
|
9
17
|
return node
|
|
@@ -28,11 +36,31 @@
|
|
|
28
36
|
messages,
|
|
29
37
|
appendToolCall,
|
|
30
38
|
appendAssistant() {
|
|
31
|
-
const node =
|
|
39
|
+
const node = document.createElement("div")
|
|
40
|
+
node.className = "message assistant"
|
|
41
|
+
const labelDiv = document.createElement("div")
|
|
42
|
+
labelDiv.className = "role-label"
|
|
43
|
+
labelDiv.textContent = "Robot:"
|
|
44
|
+
node.appendChild(labelDiv)
|
|
45
|
+
const contentDiv = document.createElement("div")
|
|
46
|
+
node.appendChild(contentDiv)
|
|
47
|
+
messages.appendChild(node)
|
|
48
|
+
messages.scrollTop = messages.scrollHeight
|
|
32
49
|
return node
|
|
33
50
|
},
|
|
34
51
|
appendUser(content) {
|
|
35
|
-
|
|
52
|
+
const node = document.createElement("div")
|
|
53
|
+
node.className = "message user"
|
|
54
|
+
const labelDiv = document.createElement("div")
|
|
55
|
+
labelDiv.className = "role-label"
|
|
56
|
+
labelDiv.textContent = "You:"
|
|
57
|
+
node.appendChild(labelDiv)
|
|
58
|
+
const contentDiv = document.createElement("div")
|
|
59
|
+
contentDiv.textContent = content
|
|
60
|
+
node.appendChild(contentDiv)
|
|
61
|
+
messages.appendChild(node)
|
|
62
|
+
messages.scrollTop = messages.scrollHeight
|
|
63
|
+
return node
|
|
36
64
|
},
|
|
37
65
|
clearEmptyState() {
|
|
38
66
|
const emptyState = messages.querySelector(".empty-state")
|
|
@@ -46,6 +74,16 @@
|
|
|
46
74
|
},
|
|
47
75
|
hideCursor(node) {
|
|
48
76
|
node.classList.remove("streaming-cursor")
|
|
77
|
+
},
|
|
78
|
+
updateCounters(totalTokens, messageCount) {
|
|
79
|
+
const tokenEl = document.getElementById("token-count")
|
|
80
|
+
const msgEl = document.getElementById("message-count")
|
|
81
|
+
if (tokenEl && totalTokens != null) {
|
|
82
|
+
tokenEl.textContent = `${totalTokens} tokens`
|
|
83
|
+
}
|
|
84
|
+
if (msgEl && messageCount != null) {
|
|
85
|
+
msgEl.textContent = `${messageCount} messages`
|
|
86
|
+
}
|
|
49
87
|
}
|
|
50
88
|
}
|
|
51
89
|
}
|
|
@@ -89,10 +127,13 @@
|
|
|
89
127
|
const onEvent = (event) => {
|
|
90
128
|
if (event.type == "done") {
|
|
91
129
|
state.done = true
|
|
130
|
+
view.updateCounters(event.total_tokens, event.message_count)
|
|
92
131
|
return
|
|
93
132
|
}
|
|
94
133
|
if (event.type == "content") {
|
|
95
|
-
ensureAssistantNode()
|
|
134
|
+
const node = ensureAssistantNode()
|
|
135
|
+
const contentDiv = node.querySelector(":scope > div:last-child")
|
|
136
|
+
if (contentDiv) contentDiv.innerHTML = event.content || ""
|
|
96
137
|
view.focusBottom()
|
|
97
138
|
return
|
|
98
139
|
}
|
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* rails-llm chat interface stylesheet
|
|
3
|
+
*
|
|
4
|
+
* Terminal-core design: dark, monospace, high contrast.
|
|
5
|
+
* Inspired by the REPL — messages read like command and output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* ==========================================================================
|
|
9
|
+
Design tokens
|
|
10
|
+
========================================================================== */
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
--color-bg: #000000;
|
|
14
|
+
--color-surface: #000000;
|
|
15
|
+
--color-sidebar: #0d0d0d;
|
|
16
|
+
--color-sidebar-hover: #1a1a1a;
|
|
17
|
+
--color-sidebar-text: #555555;
|
|
18
|
+
--color-sidebar-text-active: #e5e5e5;
|
|
19
|
+
--color-accent: #22c55e;
|
|
20
|
+
--color-accent-dim: rgba(34, 197, 94, 0.12);
|
|
21
|
+
--color-accent-hover: #4ade80;
|
|
22
|
+
--color-text: #e5e5e5;
|
|
23
|
+
--color-text-secondary: #888888;
|
|
24
|
+
--color-text-muted: #444444;
|
|
25
|
+
--color-border: #1a1a1a;
|
|
26
|
+
--color-prompt: #22c55e;
|
|
27
|
+
--color-output: #e5e5e5;
|
|
28
|
+
--color-code-bg: #0a0a0a;
|
|
29
|
+
--color-code-text: #e5e5e5;
|
|
30
|
+
--color-reasoning-text: #666666;
|
|
31
|
+
--color-tool-text: #666666;
|
|
32
|
+
--color-runtime-bg: #0a0a0a;
|
|
33
|
+
--font-mono: "SF Mono", "Fira Code", "Fira Mono", "JetBrains Mono",
|
|
34
|
+
"Cascadia Code", "Menlo", "Consolas", monospace;
|
|
35
|
+
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
36
|
+
"Helvetica Neue", Arial, sans-serif;
|
|
37
|
+
--sidebar-width: 260px;
|
|
38
|
+
--transition-fast: 0.15s ease;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ==========================================================================
|
|
42
|
+
Reset
|
|
43
|
+
========================================================================== */
|
|
44
|
+
|
|
45
|
+
*, *::before, *::after {
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
body {
|
|
52
|
+
font-family: var(--font-ui);
|
|
53
|
+
background: var(--color-bg);
|
|
54
|
+
color: var(--color-text);
|
|
55
|
+
height: 100vh;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ==========================================================================
|
|
59
|
+
Layout
|
|
60
|
+
========================================================================== */
|
|
61
|
+
|
|
62
|
+
.layout {
|
|
63
|
+
display: flex;
|
|
64
|
+
height: 100vh;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ==========================================================================
|
|
68
|
+
Sidebar
|
|
69
|
+
========================================================================== */
|
|
70
|
+
|
|
71
|
+
.sidebar {
|
|
72
|
+
width: var(--sidebar-width);
|
|
73
|
+
background: var(--color-sidebar);
|
|
74
|
+
color: var(--color-sidebar-text);
|
|
75
|
+
padding: 20px 16px 12px;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
gap: 6px;
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
border-right: 1px solid var(--color-border);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sidebar .brand {
|
|
84
|
+
margin-bottom: 24px;
|
|
85
|
+
padding-bottom: 16px;
|
|
86
|
+
border-bottom: 1px solid var(--color-border);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.sidebar .brand .product-sub {
|
|
90
|
+
font-size: 10px;
|
|
91
|
+
color: var(--color-accent);
|
|
92
|
+
font-weight: 500;
|
|
93
|
+
font-family: var(--font-mono);
|
|
94
|
+
letter-spacing: 0.5px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.sidebar .new-agent {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
background: transparent;
|
|
102
|
+
color: var(--color-sidebar-text);
|
|
103
|
+
border: 1px solid var(--color-border);
|
|
104
|
+
padding: 8px 12px;
|
|
105
|
+
font-size: 12px;
|
|
106
|
+
font-family: var(--font-mono);
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
transition: all var(--transition-fast);
|
|
110
|
+
margin-bottom: 8px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.sidebar .new-agent:hover {
|
|
114
|
+
background: var(--color-sidebar-hover);
|
|
115
|
+
border-color: var(--color-accent);
|
|
116
|
+
color: var(--color-sidebar-text-active);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.sidebar .new-agent .plus {
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
font-weight: 300;
|
|
122
|
+
line-height: 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.sidebar nav {
|
|
126
|
+
flex: 1;
|
|
127
|
+
overflow-y: auto;
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
gap: 2px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.sidebar .agent-link {
|
|
134
|
+
display: block;
|
|
135
|
+
padding: 6px 12px;
|
|
136
|
+
color: var(--color-sidebar-text);
|
|
137
|
+
text-decoration: none;
|
|
138
|
+
font-size: 12px;
|
|
139
|
+
font-family: var(--font-mono);
|
|
140
|
+
transition: all var(--transition-fast);
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
text-overflow: ellipsis;
|
|
143
|
+
white-space: nowrap;
|
|
144
|
+
line-height: 1.5;
|
|
145
|
+
border-left: 2px solid transparent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.sidebar .agent-link:hover {
|
|
149
|
+
color: var(--color-sidebar-text-active);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.sidebar .agent-link.active {
|
|
153
|
+
color: var(--color-sidebar-text-active);
|
|
154
|
+
border-left-color: var(--color-accent);
|
|
155
|
+
padding-left: 10px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.sidebar .powered-by {
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
gap: 8px;
|
|
163
|
+
padding: 8px 12px;
|
|
164
|
+
margin-top: 8px;
|
|
165
|
+
border-top: 1px solid var(--color-border);
|
|
166
|
+
font-size: 11px;
|
|
167
|
+
font-family: var(--font-mono);
|
|
168
|
+
color: var(--color-text-secondary);
|
|
169
|
+
text-decoration: none;
|
|
170
|
+
transition: all var(--transition-fast);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.sidebar .powered-by::before {
|
|
174
|
+
content: "";
|
|
175
|
+
width: 6px;
|
|
176
|
+
height: 6px;
|
|
177
|
+
background: var(--color-accent);
|
|
178
|
+
flex-shrink: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.sidebar .powered-by:hover {
|
|
182
|
+
color: var(--color-accent);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.sidebar .powered-by:hover::before {
|
|
186
|
+
background: var(--color-accent-hover);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ==========================================================================
|
|
190
|
+
Main content area
|
|
191
|
+
========================================================================== */
|
|
192
|
+
|
|
193
|
+
.main {
|
|
194
|
+
flex: 1;
|
|
195
|
+
display: flex;
|
|
196
|
+
flex-direction: column;
|
|
197
|
+
min-width: 0;
|
|
198
|
+
background: var(--color-bg);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ==========================================================================
|
|
202
|
+
Messages container
|
|
203
|
+
========================================================================== */
|
|
204
|
+
|
|
205
|
+
.messages {
|
|
206
|
+
flex: 1;
|
|
207
|
+
overflow-y: auto;
|
|
208
|
+
padding: 24px 32px;
|
|
209
|
+
display: flex;
|
|
210
|
+
flex-direction: column;
|
|
211
|
+
gap: 20px;
|
|
212
|
+
scroll-behavior: smooth;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ==========================================================================
|
|
216
|
+
Individual messages (REPL style)
|
|
217
|
+
========================================================================== */
|
|
218
|
+
|
|
219
|
+
.message {
|
|
220
|
+
max-width: 800px;
|
|
221
|
+
width: 100%;
|
|
222
|
+
line-height: 1.6;
|
|
223
|
+
font-size: 14px;
|
|
224
|
+
font-family: var(--font-mono);
|
|
225
|
+
color: var(--color-output);
|
|
226
|
+
animation: messageFadeIn 0.15s ease;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@keyframes messageFadeIn {
|
|
230
|
+
from { opacity: 0; }
|
|
231
|
+
to { opacity: 1; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.message .role-label {
|
|
235
|
+
font-size: 13px;
|
|
236
|
+
font-weight: 600;
|
|
237
|
+
margin-bottom: 2px;
|
|
238
|
+
user-select: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.message.user .role-label {
|
|
242
|
+
color: var(--color-accent);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.message.assistant .role-label {
|
|
246
|
+
color: var(--color-text-muted);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* ==========================================================================
|
|
250
|
+
Message content (markdown rendering)
|
|
251
|
+
========================================================================== */
|
|
252
|
+
|
|
253
|
+
.message.assistant > :first-child { margin-top: 0; }
|
|
254
|
+
.message.assistant > :last-child { margin-bottom: 0; }
|
|
255
|
+
|
|
256
|
+
.message.assistant p {
|
|
257
|
+
line-height: 1.6;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.message.assistant p + p,
|
|
261
|
+
.message.assistant ul,
|
|
262
|
+
.message.assistant ol,
|
|
263
|
+
.message.assistant pre,
|
|
264
|
+
.message.assistant blockquote,
|
|
265
|
+
.message.assistant table {
|
|
266
|
+
margin-top: 10px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.message.assistant h1,
|
|
270
|
+
.message.assistant h2,
|
|
271
|
+
.message.assistant h3,
|
|
272
|
+
.message.assistant h4,
|
|
273
|
+
.message.assistant h5,
|
|
274
|
+
.message.assistant h6 {
|
|
275
|
+
line-height: 1.3;
|
|
276
|
+
color: var(--color-text);
|
|
277
|
+
margin: 14px 0 6px;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.message.assistant h1,
|
|
282
|
+
.message.assistant h2 { font-size: 16px; }
|
|
283
|
+
.message.assistant h3 { font-size: 15px; }
|
|
284
|
+
.message.assistant h4,
|
|
285
|
+
.message.assistant h5,
|
|
286
|
+
.message.assistant h6 { font-size: 14px; }
|
|
287
|
+
|
|
288
|
+
.message.assistant h1:first-child,
|
|
289
|
+
.message.assistant h2:first-child,
|
|
290
|
+
.message.assistant h3:first-child {
|
|
291
|
+
margin-top: 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.message.assistant ul,
|
|
295
|
+
.message.assistant ol {
|
|
296
|
+
padding-left: 24px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.message.assistant li + li {
|
|
300
|
+
margin-top: 3px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.message.assistant li p {
|
|
304
|
+
margin: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.message.assistant a {
|
|
308
|
+
color: var(--color-accent);
|
|
309
|
+
text-decoration: underline;
|
|
310
|
+
text-underline-offset: 2px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.message.assistant a:hover {
|
|
314
|
+
color: var(--color-accent-hover);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.message.assistant code {
|
|
318
|
+
font-family: var(--font-mono);
|
|
319
|
+
font-size: 13px;
|
|
320
|
+
color: var(--color-accent);
|
|
321
|
+
word-break: break-word;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.message.assistant pre {
|
|
325
|
+
overflow-x: auto;
|
|
326
|
+
padding: 12px 14px;
|
|
327
|
+
background: var(--color-code-bg);
|
|
328
|
+
color: var(--color-code-text);
|
|
329
|
+
font-size: 13px;
|
|
330
|
+
line-height: 1.5;
|
|
331
|
+
border: 1px solid var(--color-border);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.message.assistant pre code {
|
|
335
|
+
display: block;
|
|
336
|
+
background: transparent;
|
|
337
|
+
color: inherit;
|
|
338
|
+
padding: 0;
|
|
339
|
+
white-space: pre;
|
|
340
|
+
tab-size: 2;
|
|
341
|
+
word-break: normal;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Syntax highlighting tokens */
|
|
345
|
+
.message.assistant pre code .c,
|
|
346
|
+
.message.assistant pre code .c1,
|
|
347
|
+
.message.assistant pre code .cm { color: #666666; }
|
|
348
|
+
|
|
349
|
+
.message.assistant pre code .k,
|
|
350
|
+
.message.assistant pre code .kd,
|
|
351
|
+
.message.assistant pre code .kn,
|
|
352
|
+
.message.assistant pre code .kp { color: #f472b6; }
|
|
353
|
+
|
|
354
|
+
.message.assistant pre code .s,
|
|
355
|
+
.message.assistant pre code .s1,
|
|
356
|
+
.message.assistant pre code .s2 { color: #86efac; }
|
|
357
|
+
|
|
358
|
+
.message.assistant pre code .nf,
|
|
359
|
+
.message.assistant pre code .nc,
|
|
360
|
+
.message.assistant pre code .no { color: #7dd3fc; }
|
|
361
|
+
|
|
362
|
+
.message.assistant pre code .mi,
|
|
363
|
+
.message.assistant pre code .mf { color: #fdba74; }
|
|
364
|
+
|
|
365
|
+
.message.assistant pre code .na { color: #a78bfa; }
|
|
366
|
+
|
|
367
|
+
.message.assistant pre code .nb { color: #f5f5f4; }
|
|
368
|
+
|
|
369
|
+
.message.assistant blockquote {
|
|
370
|
+
border-left: 2px solid var(--color-accent);
|
|
371
|
+
padding-left: 12px;
|
|
372
|
+
color: var(--color-text-secondary);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.message.assistant table {
|
|
376
|
+
width: 100%;
|
|
377
|
+
border-collapse: collapse;
|
|
378
|
+
font-size: 13px;
|
|
379
|
+
overflow-x: auto;
|
|
380
|
+
display: block;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.message.assistant th,
|
|
384
|
+
.message.assistant td {
|
|
385
|
+
border: 1px solid var(--color-border);
|
|
386
|
+
padding: 6px 10px;
|
|
387
|
+
text-align: left;
|
|
388
|
+
vertical-align: top;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.message.assistant th {
|
|
392
|
+
font-weight: 600;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.message.assistant img {
|
|
396
|
+
max-width: 100%;
|
|
397
|
+
height: auto;
|
|
398
|
+
margin: 8px 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.message.assistant hr {
|
|
402
|
+
border: none;
|
|
403
|
+
border-top: 1px solid var(--color-border);
|
|
404
|
+
margin: 16px 0;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* ==========================================================================
|
|
408
|
+
Reasoning panel
|
|
409
|
+
========================================================================== */
|
|
410
|
+
|
|
411
|
+
.reasoning {
|
|
412
|
+
max-width: 800px;
|
|
413
|
+
width: 100%;
|
|
414
|
+
padding: 0 0 0 16px;
|
|
415
|
+
margin-top: -12px;
|
|
416
|
+
font-size: 13px;
|
|
417
|
+
font-family: var(--font-mono);
|
|
418
|
+
color: var(--color-reasoning-text);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.reasoning summary {
|
|
422
|
+
cursor: pointer;
|
|
423
|
+
font-weight: 500;
|
|
424
|
+
color: var(--color-reasoning-text);
|
|
425
|
+
user-select: none;
|
|
426
|
+
font-size: 11px;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.reasoning summary:hover {
|
|
430
|
+
color: var(--color-text-secondary);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.reasoning .content {
|
|
434
|
+
margin-top: 6px;
|
|
435
|
+
font-size: 13px;
|
|
436
|
+
line-height: 1.5;
|
|
437
|
+
color: var(--color-reasoning-text);
|
|
438
|
+
white-space: pre-wrap;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* ==========================================================================
|
|
442
|
+
Tool call panels
|
|
443
|
+
========================================================================== */
|
|
444
|
+
|
|
445
|
+
.tool-call {
|
|
446
|
+
max-width: 800px;
|
|
447
|
+
width: 100%;
|
|
448
|
+
padding: 0 0 0 16px;
|
|
449
|
+
margin-top: -12px;
|
|
450
|
+
font-size: 13px;
|
|
451
|
+
font-family: var(--font-mono);
|
|
452
|
+
color: var(--color-tool-text);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.tool-call summary {
|
|
456
|
+
cursor: pointer;
|
|
457
|
+
font-weight: 500;
|
|
458
|
+
color: var(--color-tool-text);
|
|
459
|
+
user-select: none;
|
|
460
|
+
font-size: 11px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.tool-call summary:hover {
|
|
464
|
+
color: var(--color-text-secondary);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.tool-call .tool-name {
|
|
468
|
+
color: var(--color-accent);
|
|
469
|
+
font-weight: 600;
|
|
470
|
+
font-size: 12px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.tool-call pre {
|
|
474
|
+
margin-top: 6px;
|
|
475
|
+
background: var(--color-code-bg);
|
|
476
|
+
color: var(--color-code-text);
|
|
477
|
+
padding: 10px 12px;
|
|
478
|
+
font-size: 12px;
|
|
479
|
+
overflow-x: auto;
|
|
480
|
+
font-family: var(--font-mono);
|
|
481
|
+
border: 1px solid var(--color-border);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/* ==========================================================================
|
|
485
|
+
Input area
|
|
486
|
+
========================================================================== */
|
|
487
|
+
|
|
488
|
+
.input-area {
|
|
489
|
+
border-top: 1px solid var(--color-border);
|
|
490
|
+
padding: 12px 32px;
|
|
491
|
+
background: var(--color-bg);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.input-area form {
|
|
495
|
+
display: flex;
|
|
496
|
+
gap: 0;
|
|
497
|
+
max-width: 800px;
|
|
498
|
+
margin: 0 auto;
|
|
499
|
+
align-items: stretch;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.input-area .prompt {
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
color: var(--color-prompt);
|
|
506
|
+
font-family: var(--font-mono);
|
|
507
|
+
font-size: 14px;
|
|
508
|
+
padding-right: 10px;
|
|
509
|
+
user-select: none;
|
|
510
|
+
flex-shrink: 0;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.input-area input[type="text"] {
|
|
514
|
+
flex: 1;
|
|
515
|
+
padding: 8px 0;
|
|
516
|
+
border: none;
|
|
517
|
+
font-size: 14px;
|
|
518
|
+
font-family: var(--font-mono);
|
|
519
|
+
outline: none;
|
|
520
|
+
background: transparent;
|
|
521
|
+
color: var(--color-text);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.input-area input[type="text"]::placeholder {
|
|
525
|
+
color: var(--color-text-muted);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.input-area button,
|
|
529
|
+
.input-area input[type="submit"] {
|
|
530
|
+
background: transparent;
|
|
531
|
+
color: var(--color-accent);
|
|
532
|
+
border: 1px solid var(--color-accent);
|
|
533
|
+
padding: 8px 16px;
|
|
534
|
+
font-size: 12px;
|
|
535
|
+
font-family: var(--font-mono);
|
|
536
|
+
cursor: pointer;
|
|
537
|
+
transition: all var(--transition-fast);
|
|
538
|
+
flex-shrink: 0;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.input-area button:hover,
|
|
542
|
+
.input-area input[type="submit"]:hover {
|
|
543
|
+
background: var(--color-accent-dim);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.input-area button:disabled,
|
|
547
|
+
.input-area input[type="submit"]:disabled {
|
|
548
|
+
opacity: 0.3;
|
|
549
|
+
cursor: not-allowed;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* ==========================================================================
|
|
553
|
+
Empty state
|
|
554
|
+
========================================================================== */
|
|
555
|
+
|
|
556
|
+
.empty-state {
|
|
557
|
+
flex: 1;
|
|
558
|
+
display: flex;
|
|
559
|
+
flex-direction: column;
|
|
560
|
+
align-items: center;
|
|
561
|
+
justify-content: center;
|
|
562
|
+
gap: 12px;
|
|
563
|
+
color: #ffffff;
|
|
564
|
+
text-align: center;
|
|
565
|
+
padding: 40px 20px;
|
|
566
|
+
font-family: var(--font-mono);
|
|
567
|
+
font-size: 13px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.empty-state .icon {
|
|
571
|
+
font-size: 32px;
|
|
572
|
+
opacity: 0.5;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.empty-state p {
|
|
576
|
+
font-size: 13px;
|
|
577
|
+
max-width: 300px;
|
|
578
|
+
line-height: 1.5;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* ==========================================================================
|
|
582
|
+
Runtime bar
|
|
583
|
+
========================================================================== */
|
|
584
|
+
|
|
585
|
+
.runtime-bar {
|
|
586
|
+
display: flex;
|
|
587
|
+
align-items: center;
|
|
588
|
+
gap: 16px;
|
|
589
|
+
padding: 4px 32px;
|
|
590
|
+
background: var(--color-runtime-bg);
|
|
591
|
+
border-top: 1px solid var(--color-border);
|
|
592
|
+
font-size: 10px;
|
|
593
|
+
color: var(--color-text-muted);
|
|
594
|
+
font-family: var(--font-mono);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.runtime-bar .dot {
|
|
598
|
+
width: 5px;
|
|
599
|
+
height: 5px;
|
|
600
|
+
border-radius: 0;
|
|
601
|
+
background: var(--color-accent);
|
|
602
|
+
display: inline-block;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.runtime-bar .dot.idle {
|
|
606
|
+
background: var(--color-text-muted);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.runtime-bar span {
|
|
610
|
+
white-space: nowrap;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/* ==========================================================================
|
|
614
|
+
Typewriter cursor
|
|
615
|
+
========================================================================== */
|
|
616
|
+
|
|
617
|
+
.streaming-cursor::after {
|
|
618
|
+
content: "";
|
|
619
|
+
animation: typewriter 0.6s step-end infinite;
|
|
620
|
+
display: inline-block;
|
|
621
|
+
width: 1px;
|
|
622
|
+
height: 1em;
|
|
623
|
+
background: var(--color-accent);
|
|
624
|
+
margin-left: 2px;
|
|
625
|
+
vertical-align: text-bottom;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
@keyframes typewriter {
|
|
629
|
+
0%, 100% { opacity: 1; }
|
|
630
|
+
50% { opacity: 0; }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/* ==========================================================================
|
|
634
|
+
Responsive
|
|
635
|
+
========================================================================== */
|
|
636
|
+
|
|
637
|
+
@media (max-width: 768px) {
|
|
638
|
+
.sidebar { display: none; }
|
|
639
|
+
.messages { padding: 16px; }
|
|
640
|
+
.input-area { padding: 12px 16px; }
|
|
641
|
+
.message { max-width: 100%; font-size: 13px; }
|
|
642
|
+
}
|
|
@@ -32,7 +32,10 @@ module RailsLLM
|
|
|
32
32
|
response.headers["X-Accel-Buffering"] = "no"
|
|
33
33
|
stream = Stream.new(response.stream)
|
|
34
34
|
@agent.ask(prompt, stream:)
|
|
35
|
-
stream.finish
|
|
35
|
+
stream.finish(
|
|
36
|
+
total_tokens: @agent.usage.total_tokens,
|
|
37
|
+
message_count: messages.size
|
|
38
|
+
)
|
|
36
39
|
ensure
|
|
37
40
|
response.stream.close
|
|
38
41
|
end
|
|
@@ -1,398 +1,25 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>llm
|
|
4
|
+
<title>rails-llm</title>
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<%= csrf_meta_tags %>
|
|
7
7
|
<%= csp_meta_tag %>
|
|
8
|
+
<%= stylesheet_link_tag "rails_llm/application", media: "all" %>
|
|
8
9
|
<%= javascript_include_tag "rails_llm/application", defer: true %>
|
|
9
10
|
<%= yield :head %>
|
|
10
|
-
<style>
|
|
11
|
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
12
|
-
body {
|
|
13
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
14
|
-
Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
15
|
-
background: #f8f9fa;
|
|
16
|
-
color: #1a1a2e;
|
|
17
|
-
height: 100vh;
|
|
18
|
-
}
|
|
19
|
-
.layout { display: flex; height: 100vh; }
|
|
20
|
-
.sidebar {
|
|
21
|
-
width: 260px;
|
|
22
|
-
background: #1a1a2e;
|
|
23
|
-
color: #c8d6e5;
|
|
24
|
-
padding: 20px 16px;
|
|
25
|
-
display: flex;
|
|
26
|
-
flex-direction: column;
|
|
27
|
-
gap: 6px;
|
|
28
|
-
}
|
|
29
|
-
.sidebar .brand {
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
gap: 10px;
|
|
33
|
-
margin-bottom: 20px;
|
|
34
|
-
padding-bottom: 16px;
|
|
35
|
-
border-bottom: 1px solid #2d2d4a;
|
|
36
|
-
}
|
|
37
|
-
.sidebar .brand img {
|
|
38
|
-
width: 32px; height: 32px;
|
|
39
|
-
border-radius: 6px;
|
|
40
|
-
}
|
|
41
|
-
.sidebar .brand h1 {
|
|
42
|
-
font-size: 16px;
|
|
43
|
-
font-weight: 600;
|
|
44
|
-
color: #fff;
|
|
45
|
-
}
|
|
46
|
-
.sidebar .brand span {
|
|
47
|
-
font-size: 11px;
|
|
48
|
-
color: #00d68f;
|
|
49
|
-
font-weight: 500;
|
|
50
|
-
text-transform: uppercase;
|
|
51
|
-
letter-spacing: 0.5px;
|
|
52
|
-
}
|
|
53
|
-
.sidebar .new-agent {
|
|
54
|
-
background: transparent;
|
|
55
|
-
color: #c8d6e5;
|
|
56
|
-
border: 1px dashed #2d2d4a;
|
|
57
|
-
border-radius: 8px;
|
|
58
|
-
padding: 10px;
|
|
59
|
-
text-align: center;
|
|
60
|
-
font-size: 13px;
|
|
61
|
-
font-weight: 500;
|
|
62
|
-
text-decoration: none;
|
|
63
|
-
transition: all 0.15s;
|
|
64
|
-
margin-bottom: 12px;
|
|
65
|
-
}
|
|
66
|
-
.sidebar .new-agent:hover {
|
|
67
|
-
background: #2d2d4a;
|
|
68
|
-
border-color: #00d68f;
|
|
69
|
-
color: #fff;
|
|
70
|
-
}
|
|
71
|
-
.sidebar .agent-link {
|
|
72
|
-
display: block;
|
|
73
|
-
padding: 8px 12px;
|
|
74
|
-
border-radius: 6px;
|
|
75
|
-
color: #8899b4;
|
|
76
|
-
text-decoration: none;
|
|
77
|
-
font-size: 13px;
|
|
78
|
-
transition: all 0.12s;
|
|
79
|
-
overflow: hidden;
|
|
80
|
-
text-overflow: ellipsis;
|
|
81
|
-
white-space: nowrap;
|
|
82
|
-
}
|
|
83
|
-
.sidebar .agent-link:hover {
|
|
84
|
-
background: #2d2d4a;
|
|
85
|
-
color: #e0e6ed;
|
|
86
|
-
}
|
|
87
|
-
.sidebar .agent-link.active {
|
|
88
|
-
background: #2d2d4a;
|
|
89
|
-
color: #00d68f;
|
|
90
|
-
font-weight: 500;
|
|
91
|
-
border-left: 3px solid #00d68f;
|
|
92
|
-
}
|
|
93
|
-
.main {
|
|
94
|
-
flex: 1;
|
|
95
|
-
display: flex;
|
|
96
|
-
flex-direction: column;
|
|
97
|
-
min-width: 0;
|
|
98
|
-
}
|
|
99
|
-
.messages {
|
|
100
|
-
flex: 1;
|
|
101
|
-
overflow-y: auto;
|
|
102
|
-
padding: 32px 24px;
|
|
103
|
-
display: flex;
|
|
104
|
-
flex-direction: column;
|
|
105
|
-
gap: 20px;
|
|
106
|
-
scroll-behavior: smooth;
|
|
107
|
-
}
|
|
108
|
-
.message {
|
|
109
|
-
max-width: 760px;
|
|
110
|
-
padding: 14px 18px;
|
|
111
|
-
border-radius: 12px;
|
|
112
|
-
line-height: 1.6;
|
|
113
|
-
font-size: 15px;
|
|
114
|
-
animation: fadeIn 0.2s ease;
|
|
115
|
-
}
|
|
116
|
-
@keyframes fadeIn {
|
|
117
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
118
|
-
to { opacity: 1; transform: translateY(0); }
|
|
119
|
-
}
|
|
120
|
-
.message.user {
|
|
121
|
-
background: #1a1a2e;
|
|
122
|
-
color: #fff;
|
|
123
|
-
align-self: flex-end;
|
|
124
|
-
border-bottom-right-radius: 4px;
|
|
125
|
-
}
|
|
126
|
-
.message.assistant {
|
|
127
|
-
background: #fff;
|
|
128
|
-
border: 1px solid #e8ecf1;
|
|
129
|
-
align-self: flex-start;
|
|
130
|
-
border-bottom-left-radius: 4px;
|
|
131
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
|
132
|
-
}
|
|
133
|
-
.message.assistant > :first-child { margin-top: 0; }
|
|
134
|
-
.message.assistant > :last-child { margin-bottom: 0; }
|
|
135
|
-
.message.assistant p + p,
|
|
136
|
-
.message.assistant ul,
|
|
137
|
-
.message.assistant ol,
|
|
138
|
-
.message.assistant pre,
|
|
139
|
-
.message.assistant blockquote,
|
|
140
|
-
.message.assistant table { margin-top: 12px; }
|
|
141
|
-
.message.assistant h1,
|
|
142
|
-
.message.assistant h2,
|
|
143
|
-
.message.assistant h3,
|
|
144
|
-
.message.assistant h4,
|
|
145
|
-
.message.assistant h5,
|
|
146
|
-
.message.assistant h6 {
|
|
147
|
-
line-height: 1.25;
|
|
148
|
-
color: #101828;
|
|
149
|
-
margin: 14px 0 8px;
|
|
150
|
-
}
|
|
151
|
-
.message.assistant h1,
|
|
152
|
-
.message.assistant h2 { font-size: 17px; }
|
|
153
|
-
.message.assistant h3 { font-size: 16px; }
|
|
154
|
-
.message.assistant h4,
|
|
155
|
-
.message.assistant h5,
|
|
156
|
-
.message.assistant h6 { font-size: 15px; }
|
|
157
|
-
.message.assistant ul,
|
|
158
|
-
.message.assistant ol {
|
|
159
|
-
padding-left: 22px;
|
|
160
|
-
}
|
|
161
|
-
.message.assistant li + li {
|
|
162
|
-
margin-top: 4px;
|
|
163
|
-
}
|
|
164
|
-
.message.assistant a {
|
|
165
|
-
color: #0f766e;
|
|
166
|
-
text-decoration: underline;
|
|
167
|
-
text-underline-offset: 2px;
|
|
168
|
-
word-break: break-word;
|
|
169
|
-
}
|
|
170
|
-
.message.assistant code {
|
|
171
|
-
font-family: "SF Mono", "Fira Code", monospace;
|
|
172
|
-
font-size: 13px;
|
|
173
|
-
background: #f3f5f7;
|
|
174
|
-
color: #0f172a;
|
|
175
|
-
padding: 2px 5px;
|
|
176
|
-
border-radius: 5px;
|
|
177
|
-
}
|
|
178
|
-
.message.assistant pre {
|
|
179
|
-
overflow-x: auto;
|
|
180
|
-
padding: 12px 14px;
|
|
181
|
-
border-radius: 8px;
|
|
182
|
-
background: #0f172a;
|
|
183
|
-
color: #e2e8f0;
|
|
184
|
-
font-size: 13px;
|
|
185
|
-
line-height: 1.55;
|
|
186
|
-
}
|
|
187
|
-
.message.assistant pre code {
|
|
188
|
-
display: block;
|
|
189
|
-
background: transparent;
|
|
190
|
-
color: inherit;
|
|
191
|
-
padding: 0;
|
|
192
|
-
border-radius: 0;
|
|
193
|
-
white-space: pre;
|
|
194
|
-
tab-size: 2;
|
|
195
|
-
}
|
|
196
|
-
.message.assistant pre code[class*="language-"] .c,
|
|
197
|
-
.message.assistant pre code[class*="language-"] .cm,
|
|
198
|
-
.message.assistant pre code[class*="language-"] .c1 {
|
|
199
|
-
color: #94a3b8;
|
|
200
|
-
}
|
|
201
|
-
.message.assistant pre code[class*="language-"] .k,
|
|
202
|
-
.message.assistant pre code[class*="language-"] .kd,
|
|
203
|
-
.message.assistant pre code[class*="language-"] .kn {
|
|
204
|
-
color: #f472b6;
|
|
205
|
-
}
|
|
206
|
-
.message.assistant pre code[class*="language-"] .s,
|
|
207
|
-
.message.assistant pre code[class*="language-"] .s1,
|
|
208
|
-
.message.assistant pre code[class*="language-"] .s2 {
|
|
209
|
-
color: #86efac;
|
|
210
|
-
}
|
|
211
|
-
.message.assistant pre code[class*="language-"] .nf,
|
|
212
|
-
.message.assistant pre code[class*="language-"] .nc {
|
|
213
|
-
color: #7dd3fc;
|
|
214
|
-
}
|
|
215
|
-
.message.assistant pre code[class*="language-"] .mi,
|
|
216
|
-
.message.assistant pre code[class*="language-"] .mf {
|
|
217
|
-
color: #fdba74;
|
|
218
|
-
}
|
|
219
|
-
.message.assistant blockquote {
|
|
220
|
-
border-left: 3px solid #00d68f;
|
|
221
|
-
padding-left: 12px;
|
|
222
|
-
color: #52606d;
|
|
223
|
-
}
|
|
224
|
-
.message.assistant table {
|
|
225
|
-
width: 100%;
|
|
226
|
-
border-collapse: collapse;
|
|
227
|
-
font-size: 14px;
|
|
228
|
-
}
|
|
229
|
-
.message.assistant th,
|
|
230
|
-
.message.assistant td {
|
|
231
|
-
border: 1px solid #e5e7eb;
|
|
232
|
-
padding: 8px 10px;
|
|
233
|
-
text-align: left;
|
|
234
|
-
vertical-align: top;
|
|
235
|
-
}
|
|
236
|
-
.message.assistant th {
|
|
237
|
-
background: #f8fafc;
|
|
238
|
-
font-weight: 600;
|
|
239
|
-
}
|
|
240
|
-
.message.assistant img {
|
|
241
|
-
max-width: 100%;
|
|
242
|
-
height: auto;
|
|
243
|
-
border-radius: 8px;
|
|
244
|
-
}
|
|
245
|
-
.reasoning {
|
|
246
|
-
max-width: 760px;
|
|
247
|
-
align-self: flex-start;
|
|
248
|
-
font-size: 13px;
|
|
249
|
-
color: #6b7a8f;
|
|
250
|
-
background: #f0f2f5;
|
|
251
|
-
border-radius: 8px;
|
|
252
|
-
padding: 10px 14px;
|
|
253
|
-
margin-top: -12px;
|
|
254
|
-
border-left: 3px solid #00d68f;
|
|
255
|
-
}
|
|
256
|
-
.reasoning summary {
|
|
257
|
-
cursor: pointer;
|
|
258
|
-
font-weight: 500;
|
|
259
|
-
color: #4a5a6f;
|
|
260
|
-
user-select: none;
|
|
261
|
-
}
|
|
262
|
-
.reasoning .content {
|
|
263
|
-
margin-top: 8px;
|
|
264
|
-
font-size: 13px;
|
|
265
|
-
line-height: 1.5;
|
|
266
|
-
color: #5a6a7f;
|
|
267
|
-
white-space: pre-wrap;
|
|
268
|
-
}
|
|
269
|
-
.tool-call {
|
|
270
|
-
max-width: 760px;
|
|
271
|
-
align-self: flex-start;
|
|
272
|
-
font-size: 13px;
|
|
273
|
-
background: #f8f9fc;
|
|
274
|
-
border: 1px solid #e8ecf1;
|
|
275
|
-
border-radius: 8px;
|
|
276
|
-
padding: 10px 14px;
|
|
277
|
-
margin-top: -12px;
|
|
278
|
-
}
|
|
279
|
-
.tool-call summary {
|
|
280
|
-
cursor: pointer;
|
|
281
|
-
font-weight: 500;
|
|
282
|
-
color: #4a5a6f;
|
|
283
|
-
user-select: none;
|
|
284
|
-
}
|
|
285
|
-
.tool-call .tool-name {
|
|
286
|
-
color: #00d68f;
|
|
287
|
-
font-weight: 600;
|
|
288
|
-
font-family: "SF Mono", "Fira Code", "Fira Mono", monospace;
|
|
289
|
-
font-size: 12px;
|
|
290
|
-
}
|
|
291
|
-
.tool-call pre {
|
|
292
|
-
margin-top: 8px;
|
|
293
|
-
background: #1a1a2e;
|
|
294
|
-
color: #e0e6ed;
|
|
295
|
-
padding: 10px 12px;
|
|
296
|
-
border-radius: 6px;
|
|
297
|
-
font-size: 12px;
|
|
298
|
-
overflow-x: auto;
|
|
299
|
-
font-family: "SF Mono", "Fira Code", monospace;
|
|
300
|
-
}
|
|
301
|
-
.input-area {
|
|
302
|
-
border-top: 1px solid #e8ecf1;
|
|
303
|
-
padding: 16px 24px;
|
|
304
|
-
background: #fff;
|
|
305
|
-
}
|
|
306
|
-
.input-area form {
|
|
307
|
-
display: flex;
|
|
308
|
-
gap: 8px;
|
|
309
|
-
max-width: 800px;
|
|
310
|
-
margin: 0 auto;
|
|
311
|
-
}
|
|
312
|
-
.input-area input[type="text"] {
|
|
313
|
-
flex: 1;
|
|
314
|
-
padding: 12px 18px;
|
|
315
|
-
border: 1px solid #e0e4e8;
|
|
316
|
-
border-radius: 10px;
|
|
317
|
-
font-size: 15px;
|
|
318
|
-
outline: none;
|
|
319
|
-
transition: border 0.15s, box-shadow 0.15s;
|
|
320
|
-
}
|
|
321
|
-
.input-area input[type="text"]:focus {
|
|
322
|
-
border-color: #00d68f;
|
|
323
|
-
box-shadow: 0 0 0 3px rgba(0,214,143,0.12);
|
|
324
|
-
}
|
|
325
|
-
.input-area button {
|
|
326
|
-
background: #1a1a2e;
|
|
327
|
-
color: #fff;
|
|
328
|
-
border: none;
|
|
329
|
-
border-radius: 10px;
|
|
330
|
-
padding: 12px 24px;
|
|
331
|
-
font-size: 15px;
|
|
332
|
-
font-weight: 500;
|
|
333
|
-
cursor: pointer;
|
|
334
|
-
transition: background 0.15s;
|
|
335
|
-
}
|
|
336
|
-
.input-area button:hover { background: #2d2d4a; }
|
|
337
|
-
.input-area button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
338
|
-
.empty-state {
|
|
339
|
-
flex: 1;
|
|
340
|
-
display: flex;
|
|
341
|
-
flex-direction: column;
|
|
342
|
-
align-items: center;
|
|
343
|
-
justify-content: center;
|
|
344
|
-
gap: 12px;
|
|
345
|
-
color: #8899b4;
|
|
346
|
-
}
|
|
347
|
-
.empty-state .icon { font-size: 48px; opacity: 0.4; }
|
|
348
|
-
.empty-state p { font-size: 16px; }
|
|
349
|
-
.runtime-bar {
|
|
350
|
-
display: flex;
|
|
351
|
-
align-items: center;
|
|
352
|
-
gap: 16px;
|
|
353
|
-
padding: 6px 24px;
|
|
354
|
-
background: #f0f2f5;
|
|
355
|
-
border-top: 1px solid #e0e4e8;
|
|
356
|
-
font-size: 11px;
|
|
357
|
-
color: #8899b4;
|
|
358
|
-
font-family: "SF Mono", "Fira Code", monospace;
|
|
359
|
-
}
|
|
360
|
-
.runtime-bar .dot {
|
|
361
|
-
width: 6px; height: 6px;
|
|
362
|
-
border-radius: 50%;
|
|
363
|
-
background: #00d68f;
|
|
364
|
-
display: inline-block;
|
|
365
|
-
}
|
|
366
|
-
.runtime-bar .dot.idle { background: #8899b4; }
|
|
367
|
-
.runtime-bar span { white-space: nowrap; }
|
|
368
|
-
.streaming-cursor::after {
|
|
369
|
-
content: "▊";
|
|
370
|
-
animation: blink 0.8s step-end infinite;
|
|
371
|
-
color: #1a1a2e;
|
|
372
|
-
margin-left: 2px;
|
|
373
|
-
}
|
|
374
|
-
@keyframes blink {
|
|
375
|
-
50% { opacity: 0; }
|
|
376
|
-
}
|
|
377
|
-
@media (max-width: 768px) {
|
|
378
|
-
.sidebar { display: none; }
|
|
379
|
-
.messages { padding: 16px; }
|
|
380
|
-
.input-area { padding: 12px 16px; }
|
|
381
|
-
}
|
|
382
|
-
</style>
|
|
383
11
|
</head>
|
|
384
12
|
<body>
|
|
385
13
|
<div class="layout">
|
|
386
14
|
<div class="sidebar">
|
|
387
15
|
<div class="brand">
|
|
388
|
-
|
|
389
|
-
<div>
|
|
390
|
-
<h1>llm.rb</h1>
|
|
391
|
-
<span>Agents</span>
|
|
392
|
-
</div>
|
|
16
|
+
<div class="product-sub">AGENTS</div>
|
|
393
17
|
</div>
|
|
394
|
-
<%= link_to
|
|
395
|
-
class: "new-agent" %>
|
|
18
|
+
<%= link_to agents_path, method: :post,
|
|
19
|
+
class: "new-agent" do %>
|
|
20
|
+
<span class="plus">+</span>
|
|
21
|
+
<span>New Agent</span>
|
|
22
|
+
<% end %>
|
|
396
23
|
<nav>
|
|
397
24
|
<% if @agents %>
|
|
398
25
|
<% @agents.each do |agent| %>
|
|
@@ -401,6 +28,10 @@
|
|
|
401
28
|
<% end %>
|
|
402
29
|
<% end %>
|
|
403
30
|
</nav>
|
|
31
|
+
<a href="https://github.com/llmrb/llm.rb#readme"
|
|
32
|
+
class="powered-by" target="_blank" rel="noopener">
|
|
33
|
+
Powered by llm.rb
|
|
34
|
+
</a>
|
|
404
35
|
</div>
|
|
405
36
|
<div class="main">
|
|
406
37
|
<%= yield %>
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
<div class="input-area">
|
|
7
7
|
<%= form_tag agents_path, method: :post, id: "new-agent-form" do %>
|
|
8
|
+
<div class="prompt">$</div>
|
|
8
9
|
<%= text_field_tag :prompt, nil,
|
|
9
|
-
placeholder: "
|
|
10
|
+
placeholder: "type your message...",
|
|
10
11
|
autofocus: true,
|
|
11
12
|
autocomplete: "off" %>
|
|
12
13
|
<%= submit_tag "Start Chat", data: { disable_with: "..." } %>
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
<div class="input-area">
|
|
15
15
|
<%= form_tag ask_agent_path(@agent), method: :post, id: "ask-form", data: { turbo: false } do %>
|
|
16
|
+
<div class="prompt">$</div>
|
|
16
17
|
<%= text_field_tag :prompt, nil,
|
|
17
|
-
placeholder: "
|
|
18
|
+
placeholder: "type your message...",
|
|
18
19
|
autofocus: true,
|
|
19
20
|
required: true,
|
|
20
21
|
autocomplete: "off" %>
|
|
@@ -25,6 +26,6 @@
|
|
|
25
26
|
<div class="runtime-bar">
|
|
26
27
|
<span class="dot idle"></span>
|
|
27
28
|
<span>llm.rb</span>
|
|
28
|
-
<span><%= @agent.usage.total_tokens || 0 %> tokens</span>
|
|
29
|
-
<span><%= @messages.size %> messages</span>
|
|
29
|
+
<span id="token-count"><%= @agent.usage.total_tokens || 0 %> tokens</span>
|
|
30
|
+
<span id="message-count"><%= @messages.size %> messages</span>
|
|
30
31
|
</div>
|
data/lib/rails_llm/stream.rb
CHANGED
|
@@ -55,9 +55,11 @@ module RailsLLM
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
##
|
|
58
|
+
# @param [Integer, nil] total_tokens
|
|
59
|
+
# @param [Integer, nil] message_count
|
|
58
60
|
# @return [void]
|
|
59
|
-
def finish
|
|
60
|
-
write(type: "done")
|
|
61
|
+
def finish(total_tokens: nil, message_count: nil)
|
|
62
|
+
write(type: "done", total_tokens:, message_count:)
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
private
|
data/lib/rails_llm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- '0x1eef'
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- README.md
|
|
130
130
|
- app/assets/images/llm.png
|
|
131
131
|
- app/assets/javascripts/rails_llm/application.js
|
|
132
|
+
- app/assets/stylesheets/rails_llm/application.css
|
|
132
133
|
- app/controllers/rails_llm/agents_controller.rb
|
|
133
134
|
- app/views/layouts/rails_llm/application.html.erb
|
|
134
135
|
- app/views/rails_llm/agents/_message.html.erb
|