envoy_ai 0.0.1 → 0.0.3
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 +181 -2
- data/app/assets/stylesheets/envoy/chat.css +236 -0
- data/app/assets/stylesheets/envoy/console.css +50 -176
- data/app/controllers/envoy/conversations_controller.rb +1 -4
- data/app/controllers/envoy/panels_controller.rb +30 -0
- data/app/javascript/envoy/controllers/autoscroll_controller.js +26 -32
- data/app/jobs/envoy/run_job.rb +25 -4
- data/app/models/envoy/conversation.rb +33 -6
- data/app/views/envoy/conversations/_composer_input.html.erb +1 -1
- data/app/views/envoy/conversations/index.html.erb +0 -2
- data/app/views/envoy/conversations/new.html.erb +1 -7
- data/app/views/envoy/conversations/show.html.erb +1 -17
- data/app/views/envoy/messages/_error.html.erb +6 -0
- data/app/views/envoy/messages/_streaming.html.erb +1 -1
- data/app/views/envoy/messages/create.turbo_stream.erb +5 -5
- data/app/views/envoy/panels/_panel.html.erb +3 -0
- data/app/views/envoy/panels/_transcript.html.erb +21 -0
- data/config/routes.rb +1 -1
- data/db/migrate/20260716000001_drop_envoy_system_prompts.rb +12 -0
- data/db/migrate/20260716000002_add_surface_to_envoy_conversations.rb +15 -0
- data/lib/envoy/engine.rb +6 -0
- data/lib/envoy/errors.rb +4 -0
- data/lib/envoy/guard.rb +13 -1
- data/lib/envoy/library.rb +82 -0
- data/lib/envoy/llm.rb +1 -1
- data/lib/envoy/page_surface.rb +34 -0
- data/lib/envoy/prompt.rb +55 -0
- data/lib/envoy/runner.rb +17 -3
- data/lib/envoy/surface.rb +90 -0
- data/lib/envoy/tool_definition.rb +12 -2
- data/lib/envoy/version.rb +1 -1
- data/lib/envoy.rb +59 -0
- metadata +12 -12
- data/app/controllers/envoy/system_prompts_controller.rb +0 -52
- data/app/models/envoy/system_prompt.rb +0 -24
- data/app/models/envoy/system_prompt_version.rb +0 -14
- data/app/views/envoy/system_prompts/_form.html.erb +0 -14
- data/app/views/envoy/system_prompts/edit.html.erb +0 -2
- data/app/views/envoy/system_prompts/index.html.erb +0 -14
- data/app/views/envoy/system_prompts/new.html.erb +0 -2
- data/app/views/envoy/system_prompts/show.html.erb +0 -15
- data/db/migrate/20260711000001_create_envoy_system_prompts.rb +0 -10
- data/db/migrate/20260711000002_create_envoy_system_prompt_versions.rb +0 -13
- data/db/migrate/20260711000003_add_system_prompt_version_to_envoy_conversations.rb +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91cc30ece48d5717f72bedaa2a6bb2ca693e237411c89fcf394988a15d5f34ce
|
|
4
|
+
data.tar.gz: a581c3c845910a89433ca60189ea62657b66dac927f66ca829394978cd7074c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc2294c5a6538b7daca827b967f69c87701ba103eb73cfe03910075efac5938f96a2fe4d593e77176ad6694258603199f7b4862feed2cca4b71d239927baeebd
|
|
7
|
+
data.tar.gz: f01ee674e09999df11d161ccc7c7f9cfea299e923cb656bfc06e69633bb6c958f78e2eb00454d2f984ef172f6e49f64408a2a07bb63848bf01ba474d256d40ae
|
data/README.md
CHANGED
|
@@ -135,14 +135,19 @@ result) — a user can only see and post to their own conversations.
|
|
|
135
135
|
## 5. Migrations
|
|
136
136
|
|
|
137
137
|
Envoy ships its own migrations (`envoy_conversations`, `envoy_messages`,
|
|
138
|
-
`envoy_tool_calls`, `
|
|
139
|
-
|
|
138
|
+
`envoy_tool_calls`, plus `surface_key`/`context_key`/`prompt_key` columns on
|
|
139
|
+
`envoy_conversations` for surface-bound and DSL-prompt conversations):
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
142
|
bin/rails envoy:install:migrations
|
|
143
143
|
bin/rails db:migrate
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
If you installed Envoy before this version, `envoy:install:migrations` also
|
|
147
|
+
copies over a migration that drops `envoy_system_prompts` and
|
|
148
|
+
`envoy_system_prompt_versions` — see the Unreleased section of the CHANGELOG
|
|
149
|
+
before running `db:migrate` on an app with real rows in those tables.
|
|
150
|
+
|
|
146
151
|
## 6. Assets
|
|
147
152
|
|
|
148
153
|
The engine ships two Stimulus controllers under its own `app/javascript`
|
|
@@ -341,6 +346,180 @@ bundle exec bin/test test/envoy/guard_test.rb
|
|
|
341
346
|
bundle exec bin/test test/envoy/guard_test.rb:12
|
|
342
347
|
```
|
|
343
348
|
|
|
349
|
+
## 12. Embedding a chat
|
|
350
|
+
|
|
351
|
+
The console (section 6 onward) is a full page. Most hosts instead want a small
|
|
352
|
+
chat panel bolted onto an existing page — "ask about this record" next to a
|
|
353
|
+
show view, say — without adopting the console's chrome or its mounted URL.
|
|
354
|
+
That's what a **surface** is for: it binds a page to a toolset, a model and a
|
|
355
|
+
prompt, and a mounted `POST` endpoint turns that binding into a Turbo Frame
|
|
356
|
+
you can drop anywhere.
|
|
357
|
+
|
|
358
|
+
### Define a surface
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
# config/initializers/envoy.rb, alongside the toolset force-load block
|
|
362
|
+
Envoy.define_surface :today do
|
|
363
|
+
toolset :assistant # a registered Envoy.define_toolset key
|
|
364
|
+
model "claude-sonnet-4-5" # optional; falls back to config.default_model
|
|
365
|
+
system_prompt :today_page # a Prompt key (see below), or a literal String
|
|
366
|
+
read_only true # optional; hides `access :write` tools entirely
|
|
367
|
+
|
|
368
|
+
# Runs in the job for every turn — see the callout below.
|
|
369
|
+
context do |actor:, key:|
|
|
370
|
+
date = key.delete_prefix("day:")
|
|
371
|
+
"The user is looking at the training day for #{date}."
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`toolset` is required; `Envoy.validate!` (see below) fails fast if it names an
|
|
377
|
+
unregistered toolset or an unregistered `system_prompt` key. `model`,
|
|
378
|
+
`read_only` and `system_prompt` are all optional.
|
|
379
|
+
|
|
380
|
+
### Define prompts and libraries
|
|
381
|
+
|
|
382
|
+
`Envoy.define_prompt` registers reusable system-prompt text, composable with
|
|
383
|
+
`use` (like a toolset) so shared framing lives in one place:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
Envoy.define_prompt :safety_framing do
|
|
387
|
+
body "Never invent data; say when you don't know."
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
Envoy.define_prompt :today_page do
|
|
391
|
+
use :safety_framing
|
|
392
|
+
body { File.read(Rails.root.join("app/envoy/prompts/today_page.md")) }
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
A block body stays lazy, so editing the file on disk is picked up on the next
|
|
397
|
+
`to_prepare` reload rather than requiring a boot.
|
|
398
|
+
|
|
399
|
+
`Envoy.define_library` is for reference documents the model should fetch on
|
|
400
|
+
demand rather than have preloaded into every turn's instructions — it
|
|
401
|
+
compiles to an ordinary toolset (so it composes via `use` on a real
|
|
402
|
+
toolset), exposing one `read_<key>` tool with a closed `enum` of document
|
|
403
|
+
keys:
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
Envoy.define_library :principles do
|
|
407
|
+
directory Rails.root.join("docs/principles") # README.md -> description, *.md -> documents
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
Envoy.define_toolset :assistant do
|
|
411
|
+
description "General assistant tools."
|
|
412
|
+
use :principles
|
|
413
|
+
end
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Wire a page to it
|
|
417
|
+
|
|
418
|
+
Any host controller can call `envoy_surface` — it's mixed into
|
|
419
|
+
`ActionController::Base` by the engine, no include needed:
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
class TodayController < ApplicationController
|
|
423
|
+
envoy_surface :today, context_key: -> { "day:#{@date.to_fs(:iso8601)}" }
|
|
424
|
+
end
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`context_key` is instance_exec'd in the controller, so instance variables the
|
|
428
|
+
action set (`@date` here) are in scope. The helper method `envoy_page_surface`
|
|
429
|
+
(available in views) returns `{ surface:, context_key: }`, or `nil` if the
|
|
430
|
+
controller never declared a surface — pages opt in, they don't get a bubble by
|
|
431
|
+
default.
|
|
432
|
+
|
|
433
|
+
> **A `context` block runs in the job.** There is no request, no `params` and
|
|
434
|
+
> no session — only `actor` and `key`. Everything it needs must be derivable
|
|
435
|
+
> from the `context_key`. It is also the authorization boundary: `surface` and
|
|
436
|
+
> `context_key` both arrive from the browser, so raise `Envoy::Forbidden` if
|
|
437
|
+
> this actor may not see this subject.
|
|
438
|
+
|
|
439
|
+
### Render the panel
|
|
440
|
+
|
|
441
|
+
`POST envoy.panel_path` (mounted by `resource :panel, only: :create`) finds
|
|
442
|
+
or creates the conversation for `(actor, surface, context_key)` and renders it
|
|
443
|
+
inside a `turbo_frame_tag "envoy_panel"`. Point a `button_to` at it with a
|
|
444
|
+
matching frame already on the page — same "create-or-resume" semantics as
|
|
445
|
+
opening a tab twice, so re-clicking never duplicates the conversation:
|
|
446
|
+
|
|
447
|
+
```erb
|
|
448
|
+
<% if (surface = envoy_page_surface) %>
|
|
449
|
+
<%= turbo_frame_tag "envoy_panel" %>
|
|
450
|
+
<%= button_to "Ask about this page", envoy.panel_path,
|
|
451
|
+
params: { surface: surface[:surface], context_key: surface[:context_key] },
|
|
452
|
+
data: { turbo_frame: "envoy_panel" } %>
|
|
453
|
+
<% end %>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
An unregistered `surface` param renders `404`; the endpoint is otherwise
|
|
457
|
+
subject to the same `authenticate`/`actor_resolver` config as every other
|
|
458
|
+
Envoy route, so a signed-out visitor never reaches it.
|
|
459
|
+
|
|
460
|
+
### Style it
|
|
461
|
+
|
|
462
|
+
The panel (and the console) render the same
|
|
463
|
+
`app/views/envoy/panels/_transcript.html.erb` partial, styled entirely by
|
|
464
|
+
`app/assets/stylesheets/envoy/chat.css` — no body rules, no fixed widths, the
|
|
465
|
+
host owns the chrome around it. `console.css` pulls it in automatically
|
|
466
|
+
(`@import url("chat.css")`, so loading the console stylesheet already gives
|
|
467
|
+
you these rules too), or import it directly from the host's own stylesheet if
|
|
468
|
+
you don't want the console's chrome at all:
|
|
469
|
+
|
|
470
|
+
```css
|
|
471
|
+
@import url("envoy/chat.css");
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Use the `url(...)` form, not a bare string — Propshaft only fingerprints
|
|
475
|
+
`url(...)` references in CSS, not `@import` strings, and its asset server
|
|
476
|
+
404s on an undigested path.
|
|
477
|
+
|
|
478
|
+
Every color it uses is a CSS custom property, and every declaration falls
|
|
479
|
+
back to its own original console value when the var is unset, so it renders
|
|
480
|
+
reasonably with zero host configuration and themes cleanly by setting only
|
|
481
|
+
the vars below:
|
|
482
|
+
|
|
483
|
+
| Var | Affects | Fallback (console light mode) |
|
|
484
|
+
|---|---|---|
|
|
485
|
+
| `--envoy-surface` | message bubble / input background | `#ffffff` |
|
|
486
|
+
| `--envoy-surface-alt` | assistant bubble background *and* tool-call panel background | `#eff6ff` / `#f9fafb` (each rule keeps its own original shade until you set this) |
|
|
487
|
+
| `--envoy-ink` | body text | `#111827` |
|
|
488
|
+
| `--envoy-subtle` | role labels, hints, placeholder text | `#6b7280` |
|
|
489
|
+
| `--envoy-line` | borders | `#e5e7eb` |
|
|
490
|
+
| `--envoy-accent` | primary button background | `#111827` |
|
|
491
|
+
| `--envoy-accent-ink` | primary button text | `#ffffff` |
|
|
492
|
+
| `--envoy-warn` | the failed-turn error bubble | `#b91c1c` |
|
|
493
|
+
| `--envoy-radius` | corner rounding (message/tool/input/button each keep their own default until set) | `0.5rem` / `0.375rem` |
|
|
494
|
+
|
|
495
|
+
Setting `--envoy-surface-alt` or `--envoy-radius` themes every rule that uses
|
|
496
|
+
it identically; the console itself never sets them, so it keeps its original,
|
|
497
|
+
slightly more varied look.
|
|
498
|
+
|
|
499
|
+
### Assets and boot-time validation
|
|
500
|
+
|
|
501
|
+
Pin the same two Stimulus controllers as the console (section 6) — the
|
|
502
|
+
composer for Enter-to-submit, the autoscroll controller (now scrolls its own
|
|
503
|
+
`.envoy-chat__log` container rather than the window, so it sticks the panel
|
|
504
|
+
without hijacking the host page's scroll position):
|
|
505
|
+
|
|
506
|
+
```ruby
|
|
507
|
+
# config/importmap.rb
|
|
508
|
+
pin "envoy/controllers/composer_controller", to: "envoy/controllers/composer_controller.js"
|
|
509
|
+
pin "envoy/controllers/autoscroll_controller", to: "envoy/controllers/autoscroll_controller.js"
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Finally, call `Envoy.validate!` at the end of the `to_prepare` block that
|
|
513
|
+
force-loads your definitions, so a typo'd toolset or prompt key fails on boot
|
|
514
|
+
— not silently, inside a job, where the user just watches "working…" forever:
|
|
515
|
+
|
|
516
|
+
```ruby
|
|
517
|
+
Rails.application.config.to_prepare do
|
|
518
|
+
Dir[Rails.root.join("app/envoy/**/*.rb")].each { |f| require_dependency f }
|
|
519
|
+
Envoy.validate!
|
|
520
|
+
end
|
|
521
|
+
```
|
|
522
|
+
|
|
344
523
|
## The debug console
|
|
345
524
|
|
|
346
525
|
Envoy ships a minimal chat UI mounted at whatever path the host chooses
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/* Embeddable chat rules. No body, no page-level layout, no fixed widths —
|
|
2
|
+
the host owns the chrome. Every colour is a var with a fallback, so the
|
|
3
|
+
stylesheet stands alone in the console and themes cleanly in a host.
|
|
4
|
+
|
|
5
|
+
Public vars (all optional — every rule falls back to the console's own
|
|
6
|
+
light-mode palette when unset): --envoy-surface, --envoy-surface-alt,
|
|
7
|
+
--envoy-ink, --envoy-subtle, --envoy-line, --envoy-accent,
|
|
8
|
+
--envoy-accent-ink, --envoy-warn, --envoy-radius. */
|
|
9
|
+
|
|
10
|
+
/* Fills a flex-column container (an embedded, fixed-height panel) so the log
|
|
11
|
+
below can scroll internally. Inert when the parent isn't a flex container —
|
|
12
|
+
e.g. the full-page console, which keeps its natural height and page-scrolls —
|
|
13
|
+
because flex item properties only apply inside a flex parent. The embedding
|
|
14
|
+
host is responsible for giving this element a bounded height (a flex column
|
|
15
|
+
with a definite height); the transcript then fills it and scrolls its log. */
|
|
16
|
+
.envoy-chat {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
min-height: 0;
|
|
20
|
+
flex: 1 1 auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.envoy-chat__log {
|
|
24
|
+
flex: 1 1 auto;
|
|
25
|
+
min-height: 0;
|
|
26
|
+
overflow-y: auto;
|
|
27
|
+
overscroll-behavior: contain;
|
|
28
|
+
scrollbar-width: thin;
|
|
29
|
+
scrollbar-color: var(--envoy-subtle, #6b7280) transparent;
|
|
30
|
+
}
|
|
31
|
+
.envoy-chat__log::-webkit-scrollbar { width: 8px; }
|
|
32
|
+
.envoy-chat__log::-webkit-scrollbar-track { background: transparent; }
|
|
33
|
+
.envoy-chat__log::-webkit-scrollbar-thumb {
|
|
34
|
+
background: var(--envoy-subtle, #6b7280);
|
|
35
|
+
border-radius: 9999px;
|
|
36
|
+
}
|
|
37
|
+
.envoy-chat__log::-webkit-scrollbar-thumb:hover {
|
|
38
|
+
background: var(--envoy-ink, #111827);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Separate the transcript from the composer: in the flex column the log sits
|
|
42
|
+
flush against the input form, so the last message and the textarea run
|
|
43
|
+
together without a divider. */
|
|
44
|
+
.envoy-chat > form {
|
|
45
|
+
margin-top: 0.75rem;
|
|
46
|
+
padding-top: 0.75rem;
|
|
47
|
+
border-top: 1px solid var(--envoy-line, #e5e7eb);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ---------------------------------------------------------------------- */
|
|
51
|
+
/* Messages */
|
|
52
|
+
/* ---------------------------------------------------------------------- */
|
|
53
|
+
|
|
54
|
+
.envoy-msg {
|
|
55
|
+
border: 1px solid var(--envoy-line, #e5e7eb);
|
|
56
|
+
border-radius: var(--envoy-radius, 0.5rem);
|
|
57
|
+
padding: 1rem;
|
|
58
|
+
background: var(--envoy-surface, #ffffff);
|
|
59
|
+
}
|
|
60
|
+
.envoy-msg--assistant { background: var(--envoy-surface-alt, #eff6ff); }
|
|
61
|
+
|
|
62
|
+
.envoy-msg__role {
|
|
63
|
+
font-size: 0.75rem;
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
color: var(--envoy-subtle, #6b7280);
|
|
66
|
+
margin-bottom: 0.25rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Subtle "working…" shimmer while a reply is being generated. */
|
|
70
|
+
.envoy-working { animation: envoy-working 1.4s ease-in-out infinite; }
|
|
71
|
+
@keyframes envoy-working {
|
|
72
|
+
0%, 100% { opacity: 0.4; }
|
|
73
|
+
50% { opacity: 0.95; }
|
|
74
|
+
}
|
|
75
|
+
@media (prefers-reduced-motion: reduce) {
|
|
76
|
+
.envoy-working { animation: none; opacity: 0.7; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ---------------------------------------------------------------------- */
|
|
80
|
+
/* Rendered markdown body */
|
|
81
|
+
/* ---------------------------------------------------------------------- */
|
|
82
|
+
|
|
83
|
+
.envoy-md { font-size: 0.9rem; line-height: 1.6; }
|
|
84
|
+
.envoy-md > * + * { margin-top: 0.7rem; }
|
|
85
|
+
.envoy-md h1 { font-size: 1.3em; font-weight: 700; }
|
|
86
|
+
.envoy-md h2 { font-size: 1.15em; font-weight: 700; }
|
|
87
|
+
.envoy-md h3, .envoy-md h4 { font-size: 1em; font-weight: 700; }
|
|
88
|
+
.envoy-md ul { padding-left: 1.25rem; list-style: disc; }
|
|
89
|
+
.envoy-md ol { padding-left: 1.25rem; list-style: decimal; }
|
|
90
|
+
.envoy-md li + li { margin-top: 0.2rem; }
|
|
91
|
+
.envoy-md a { text-decoration: underline; }
|
|
92
|
+
.envoy-md blockquote { border-left: 3px solid rgba(128, 128, 128, 0.4); padding-left: 0.75rem; }
|
|
93
|
+
.envoy-md hr { border: 0; border-top: 1px solid rgba(128, 128, 128, 0.3); }
|
|
94
|
+
/* Fenced code (comrak carries its own dark background + inline styles). */
|
|
95
|
+
.envoy-md pre { padding: 0.75rem 1rem; border-radius: 0.5rem; }
|
|
96
|
+
/* Inline code gets a subtle theme-agnostic chip. */
|
|
97
|
+
.envoy-md :not(pre) > code {
|
|
98
|
+
font-size: 0.9em;
|
|
99
|
+
overflow-wrap: anywhere;
|
|
100
|
+
background: rgba(128, 128, 128, 0.15);
|
|
101
|
+
padding: 0.1em 0.3em;
|
|
102
|
+
border-radius: 0.25rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Code blocks scroll within the message instead of spilling past it, with a
|
|
106
|
+
thin, theme-agnostic overlay scrollbar (mid-gray at 50% reads on both the
|
|
107
|
+
light tool-call panels and the dark highlighted blocks). Scoped to the
|
|
108
|
+
transcript log (.envoy-chat__log, see panels/_transcript.html.erb) rather
|
|
109
|
+
than an id, since message-list ids are conversation-scoped
|
|
110
|
+
(dom_id(conversation, :messages)) and no element has a literal
|
|
111
|
+
#envoy_messages id. */
|
|
112
|
+
.envoy-chat__log pre {
|
|
113
|
+
overflow-x: auto;
|
|
114
|
+
max-width: 100%;
|
|
115
|
+
scrollbar-width: thin;
|
|
116
|
+
scrollbar-color: rgba(128, 128, 128, 0.5) transparent;
|
|
117
|
+
}
|
|
118
|
+
.envoy-chat__log pre::-webkit-scrollbar { height: 8px; }
|
|
119
|
+
.envoy-chat__log pre::-webkit-scrollbar-track { background: transparent; }
|
|
120
|
+
.envoy-chat__log pre::-webkit-scrollbar-thumb {
|
|
121
|
+
background: rgba(128, 128, 128, 0.5);
|
|
122
|
+
border-radius: 9999px;
|
|
123
|
+
}
|
|
124
|
+
.envoy-chat__log pre::-webkit-scrollbar-thumb:hover { background: rgba(128, 128, 128, 0.8); }
|
|
125
|
+
|
|
126
|
+
/* ---------------------------------------------------------------------- */
|
|
127
|
+
/* Tool calls */
|
|
128
|
+
/* ---------------------------------------------------------------------- */
|
|
129
|
+
|
|
130
|
+
.envoy-tool {
|
|
131
|
+
margin-top: 0.5rem;
|
|
132
|
+
font-size: 0.875rem;
|
|
133
|
+
border: 1px solid var(--envoy-line, #e5e7eb);
|
|
134
|
+
border-radius: var(--envoy-radius, 0.375rem);
|
|
135
|
+
padding: 0.5rem;
|
|
136
|
+
background: var(--envoy-surface-alt, #f9fafb);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.envoy-tool__summary {
|
|
140
|
+
cursor: pointer;
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 0.5rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.envoy-tool pre {
|
|
147
|
+
font-size: 0.75rem;
|
|
148
|
+
margin-top: 0.5rem;
|
|
149
|
+
overflow-x: auto;
|
|
150
|
+
max-width: 100%;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.envoy-mono {
|
|
154
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Tool-call outcome badge (rendered by Envoy::Badges.outcome). Colors match
|
|
158
|
+
the original Tailwind red-100/amber-100/green-100 pairs; intentionally
|
|
159
|
+
scheme-independent (not vars), as the original badge had no dark variant. */
|
|
160
|
+
.envoy-badge {
|
|
161
|
+
display: inline-block;
|
|
162
|
+
font-size: 0.75rem;
|
|
163
|
+
padding: 0.125rem 0.5rem;
|
|
164
|
+
border-radius: var(--envoy-radius, 0.375rem);
|
|
165
|
+
font-weight: 500;
|
|
166
|
+
}
|
|
167
|
+
.envoy-badge--ok { background: #dcfce7; color: #166534; }
|
|
168
|
+
.envoy-badge--failed { background: #fef3c7; color: #92400e; }
|
|
169
|
+
.envoy-badge--denied { background: #fee2e2; color: #991b1b; }
|
|
170
|
+
|
|
171
|
+
/* ---------------------------------------------------------------------- */
|
|
172
|
+
/* Composer */
|
|
173
|
+
/* ---------------------------------------------------------------------- */
|
|
174
|
+
|
|
175
|
+
.envoy-composer {
|
|
176
|
+
display: flex;
|
|
177
|
+
gap: 0.5rem;
|
|
178
|
+
align-items: flex-end;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.envoy-input {
|
|
182
|
+
flex: 1;
|
|
183
|
+
border: 1px solid var(--envoy-line, #e5e7eb);
|
|
184
|
+
border-radius: var(--envoy-radius, 0.375rem);
|
|
185
|
+
padding: 0.5rem;
|
|
186
|
+
background: var(--envoy-surface, #ffffff);
|
|
187
|
+
color: var(--envoy-ink, #111827);
|
|
188
|
+
}
|
|
189
|
+
.envoy-input::placeholder { color: var(--envoy-subtle, #6b7280); opacity: 1; }
|
|
190
|
+
|
|
191
|
+
.envoy-hint {
|
|
192
|
+
font-size: 0.75rem;
|
|
193
|
+
color: var(--envoy-subtle, #6b7280);
|
|
194
|
+
margin-top: 0.25rem;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* ---------------------------------------------------------------------- */
|
|
198
|
+
/* Buttons */
|
|
199
|
+
/* ---------------------------------------------------------------------- */
|
|
200
|
+
|
|
201
|
+
.envoy-btn {
|
|
202
|
+
padding: 0.375rem 0.75rem;
|
|
203
|
+
border-radius: var(--envoy-radius, 0.375rem);
|
|
204
|
+
background: var(--envoy-accent, #111827);
|
|
205
|
+
color: var(--envoy-accent-ink, #ffffff);
|
|
206
|
+
border: 0;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
text-decoration: none;
|
|
209
|
+
display: inline-block;
|
|
210
|
+
flex-shrink: 0;
|
|
211
|
+
font: inherit;
|
|
212
|
+
}
|
|
213
|
+
.envoy-btn--secondary {
|
|
214
|
+
background: transparent;
|
|
215
|
+
color: inherit;
|
|
216
|
+
border: 1px solid var(--envoy-line, #e5e7eb);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* ---------------------------------------------------------------------- */
|
|
220
|
+
/* Layout / state helpers */
|
|
221
|
+
/* ---------------------------------------------------------------------- */
|
|
222
|
+
|
|
223
|
+
.envoy-stack > * + * { margin-top: 1rem; }
|
|
224
|
+
.envoy-stack--sm > * + * { margin-top: 0.75rem; }
|
|
225
|
+
|
|
226
|
+
.envoy-muted {
|
|
227
|
+
color: var(--envoy-subtle, #6b7280);
|
|
228
|
+
font-size: 0.875rem;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* A .envoy-msg modifier (see app/views/envoy/messages/_error.html.erb): turns
|
|
232
|
+
the assistant bubble's border and text red when a turn failed. */
|
|
233
|
+
.envoy-error {
|
|
234
|
+
border-color: var(--envoy-warn, #b91c1c);
|
|
235
|
+
color: var(--envoy-warn, #b91c1c);
|
|
236
|
+
}
|