loopengine 0.1.63 → 0.1.64
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.
- package/README.md +20 -11
- package/dist/web/agents-config-page.js +228 -294
- package/dist/web/agents-list-page.js +80 -99
- package/dist/web/console-shell.d.ts +2 -0
- package/dist/web/console-shell.js +156 -0
- package/dist/web/dev-ui-styles.js +146 -40
- package/dist/web/env-admin.js +4 -6
- package/dist/web/global-config-page.js +26 -80
- package/dist/web/playground.js +161 -105
- package/package.json +1 -1
|
@@ -20,67 +20,19 @@
|
|
|
20
20
|
// hazard (template-literal escapes run twice: once when this constant
|
|
21
21
|
// is built, once when a browser parses the served <script> as JS).
|
|
22
22
|
import { devUiCss } from './dev-ui-styles.js';
|
|
23
|
+
import { consoleShellScript, consoleSidebarHtml } from './console-shell.js';
|
|
23
24
|
export const globalConfigPageHtml = `<!doctype html>
|
|
24
25
|
<html lang="en">
|
|
25
26
|
<head>
|
|
26
27
|
<meta charset="utf-8">
|
|
27
28
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28
|
-
<title>LoopEngine
|
|
29
|
+
<title>LoopEngine Settings</title>
|
|
29
30
|
<style>${devUiCss}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
height: 100vh;
|
|
34
|
-
}
|
|
35
|
-
.layout { flex: 1; display: flex; min-height: 0; }
|
|
36
|
-
nav.sidebar {
|
|
37
|
-
width: 180px;
|
|
38
|
-
flex-shrink: 0;
|
|
39
|
-
border-right: 1px solid light-dark(#ddd, #333);
|
|
40
|
-
padding: 6px;
|
|
41
|
-
}
|
|
42
|
-
nav.sidebar button, nav.sidebar a {
|
|
43
|
-
display: block;
|
|
44
|
-
width: 100%;
|
|
45
|
-
text-align: left;
|
|
46
|
-
background: none;
|
|
47
|
-
border: none;
|
|
48
|
-
border-radius: 6px;
|
|
49
|
-
padding: 8px 10px;
|
|
50
|
-
font-size: 13px;
|
|
51
|
-
cursor: pointer;
|
|
52
|
-
text-decoration: none;
|
|
53
|
-
color: inherit;
|
|
54
|
-
box-sizing: border-box;
|
|
55
|
-
}
|
|
56
|
-
nav.sidebar button:hover, nav.sidebar a:hover { background: light-dark(#eee, #26262b); }
|
|
57
|
-
nav.sidebar button.active { background: light-dark(#dbeafe, #1e3a5f); font-weight: 600; }
|
|
58
|
-
main {
|
|
59
|
-
flex: 1;
|
|
60
|
-
overflow-y: auto;
|
|
61
|
-
padding: 20px 28px;
|
|
62
|
-
}
|
|
63
|
-
h2 { font-size: 20px; margin: 0 0 4px; font-family: ui-monospace, monospace; }
|
|
64
|
-
section { margin-bottom: 22px; }
|
|
65
|
-
section h3 {
|
|
66
|
-
font-size: 12px;
|
|
67
|
-
text-transform: uppercase;
|
|
68
|
-
letter-spacing: 0.04em;
|
|
69
|
-
color: light-dark(#666, #999);
|
|
70
|
-
margin: 0 0 8px;
|
|
71
|
-
border-bottom: 1px solid light-dark(#eee, #2a2a2e);
|
|
72
|
-
padding-bottom: 4px;
|
|
73
|
-
}
|
|
74
|
-
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
75
|
-
th, td {
|
|
76
|
-
text-align: left;
|
|
77
|
-
padding: 6px 8px;
|
|
78
|
-
border-bottom: 1px solid light-dark(#eee, #2a2a2e);
|
|
79
|
-
vertical-align: top;
|
|
80
|
-
}
|
|
81
|
-
th { color: light-dark(#666, #999); font-weight: 600; }
|
|
31
|
+
section { margin-bottom: 28px; }
|
|
32
|
+
section h3 { font-size: 15px; font-weight: 600; margin: 0 0 10px; }
|
|
33
|
+
section > table { border: 1px solid var(--line); border-radius: var(--radius); border-collapse: separate; border-spacing: 0; overflow: hidden; }
|
|
82
34
|
.source {
|
|
83
|
-
border: 1px solid
|
|
35
|
+
border: 1px solid var(--line);
|
|
84
36
|
border-radius: 8px;
|
|
85
37
|
padding: 12px 14px;
|
|
86
38
|
margin-bottom: 10px;
|
|
@@ -88,10 +40,10 @@ export const globalConfigPageHtml = `<!doctype html>
|
|
|
88
40
|
.source.unsupported { opacity: 0.6; }
|
|
89
41
|
.source-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
90
42
|
.source-head h4 { margin: 0; font-size: 14px; font-family: ui-monospace, monospace; }
|
|
91
|
-
.hint { font-size: 11px; color:
|
|
43
|
+
.hint { font-size: 11px; color: var(--ink-muted); }
|
|
92
44
|
pre {
|
|
93
|
-
background:
|
|
94
|
-
border: 1px solid
|
|
45
|
+
background: var(--bg);
|
|
46
|
+
border: 1px solid var(--line);
|
|
95
47
|
border-radius: 6px;
|
|
96
48
|
padding: 8px 10px;
|
|
97
49
|
font-size: 12px;
|
|
@@ -105,36 +57,30 @@ export const globalConfigPageHtml = `<!doctype html>
|
|
|
105
57
|
this pink-for-destructive convention is meant to drift between them. */
|
|
106
58
|
.delete-btn {
|
|
107
59
|
font-size: 12px;
|
|
108
|
-
color:
|
|
109
|
-
background:
|
|
110
|
-
border-color:
|
|
60
|
+
color: var(--bad);
|
|
61
|
+
background: var(--bad-soft);
|
|
62
|
+
border-color: var(--bad);
|
|
111
63
|
}
|
|
112
|
-
.delete-btn:hover { background:
|
|
64
|
+
.delete-btn:hover { background: var(--bad-soft); }
|
|
113
65
|
</style>
|
|
114
66
|
</head>
|
|
115
|
-
<body>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
<div
|
|
122
|
-
<
|
|
123
|
-
<a href="/agents/config">Agents</a>
|
|
124
|
-
<button class="section-btn active" data-section="models">Models</button>
|
|
125
|
-
<button class="section-btn" data-section="gateways">Gateways</button>
|
|
126
|
-
</nav>
|
|
127
|
-
<main>
|
|
128
|
-
<div class="section-panel" data-section-panel="models">
|
|
129
|
-
<h2>Models</h2>
|
|
130
|
-
<div id="modelsContent"><p class="hint">Loading…</p></div>
|
|
67
|
+
<body data-le-page="config">
|
|
68
|
+
<div class="le-app">
|
|
69
|
+
${consoleSidebarHtml}
|
|
70
|
+
<main class="le-main">
|
|
71
|
+
<div class="section-panel" data-section-panel="models" style="display:contents">
|
|
72
|
+
<div class="le-head plain"><div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
73
|
+
<div><h1>Models</h1><p>Which model providers have an API key set, and which agents use each one.</p></div></div></div>
|
|
74
|
+
<div class="le-scroll"><div class="le-page" style="display:block"><div id="modelsContent"><p class="hint">Loading…</p></div></div></div>
|
|
131
75
|
</div>
|
|
132
76
|
<div class="section-panel" data-section-panel="gateways" style="display:none">
|
|
133
|
-
<
|
|
134
|
-
|
|
77
|
+
<div class="le-head plain"><div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>
|
|
78
|
+
<div><h1>Integrations</h1><p>Outside tool sources (such as Composio) that agents can add tools from.</p></div></div></div>
|
|
79
|
+
<div class="le-scroll"><div class="le-page" style="display:block"><div id="gatewaysContent"><p class="hint">Loading…</p></div></div></div>
|
|
135
80
|
</div>
|
|
136
81
|
</main>
|
|
137
82
|
</div>
|
|
83
|
+
${consoleShellScript}
|
|
138
84
|
<script>
|
|
139
85
|
(function () {
|
|
140
86
|
var main = document.querySelector('main');
|
|
@@ -279,7 +225,7 @@ export const globalConfigPageHtml = `<!doctype html>
|
|
|
279
225
|
}
|
|
280
226
|
var panels = main.querySelectorAll('.section-panel');
|
|
281
227
|
for (var j = 0; j < panels.length; j++) {
|
|
282
|
-
panels[j].style.display = panels[j].dataset.sectionPanel === section ? '
|
|
228
|
+
panels[j].style.display = panels[j].dataset.sectionPanel === section ? 'contents' : 'none';
|
|
283
229
|
}
|
|
284
230
|
if (section === 'models' && !loadedSections.models) loadModels();
|
|
285
231
|
if (section === 'gateways' && !loadedSections.gateways) loadGateways();
|
package/dist/web/playground.js
CHANGED
|
@@ -16,57 +16,55 @@
|
|
|
16
16
|
// outer one. Plain string concatenation sidesteps that class of bug
|
|
17
17
|
// entirely rather than relying on getting every escape right.
|
|
18
18
|
import { devUiCss } from './dev-ui-styles.js';
|
|
19
|
+
import { consoleShellScript, consoleSidebarHtml } from './console-shell.js';
|
|
19
20
|
export const playgroundHtml = `<!doctype html>
|
|
20
21
|
<html lang="en">
|
|
21
22
|
<head>
|
|
22
23
|
<meta charset="utf-8">
|
|
23
24
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
24
|
-
<title>LoopEngine
|
|
25
|
+
<title>LoopEngine Chat</title>
|
|
25
26
|
<style>${devUiCss}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
header {
|
|
32
|
-
padding: 10px 16px;
|
|
33
|
-
border-bottom: 1px solid light-dark(#ddd, #333);
|
|
27
|
+
/* The Chat tab of the agent console (see web/console-shell.ts): a
|
|
28
|
+
toolbar, then History | conversation | Run trace. History and Run
|
|
29
|
+
trace can each be hidden (.no-history / .no-trace on .chat-body). */
|
|
30
|
+
.chat-toolbar {
|
|
34
31
|
display: flex;
|
|
35
32
|
align-items: center;
|
|
36
|
-
gap:
|
|
33
|
+
gap: 8px;
|
|
37
34
|
flex-wrap: wrap;
|
|
35
|
+
padding: 8px 24px;
|
|
36
|
+
border-bottom: 1px solid var(--line);
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
font-size: 12px;
|
|
42
|
-
color: light-dark(#666, #999);
|
|
43
|
-
max-width: 360px;
|
|
44
|
-
overflow: hidden;
|
|
45
|
-
text-overflow: ellipsis;
|
|
46
|
-
white-space: nowrap;
|
|
47
|
-
}
|
|
48
|
-
#configLink { font-size: 12px; white-space: nowrap; text-decoration: none; }
|
|
38
|
+
.chat-toolbar button { padding: 4px 10px; font-size: 12.5px; }
|
|
39
|
+
.chat-toolbar button[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-soft); }
|
|
49
40
|
#sessionLabel {
|
|
50
41
|
margin-left: auto;
|
|
51
42
|
font-size: 12px;
|
|
52
|
-
font-family:
|
|
53
|
-
color:
|
|
43
|
+
font-family: var(--font-mono);
|
|
44
|
+
color: var(--ink-faint);
|
|
54
45
|
}
|
|
55
46
|
#sessionLabel.copyable { cursor: pointer; }
|
|
56
47
|
#sessionLabel.copyable:hover { text-decoration: underline; }
|
|
57
|
-
|
|
48
|
+
.chat-body {
|
|
58
49
|
flex: 1;
|
|
59
|
-
display:
|
|
50
|
+
display: grid;
|
|
51
|
+
grid-template-columns: 220px minmax(0, 1fr) 340px;
|
|
60
52
|
min-height: 0;
|
|
61
53
|
}
|
|
54
|
+
.chat-body.no-history { grid-template-columns: minmax(0, 1fr) 340px; }
|
|
55
|
+
.chat-body.no-trace { grid-template-columns: 220px minmax(0, 1fr); }
|
|
56
|
+
.chat-body.no-history.no-trace { grid-template-columns: minmax(0, 1fr); }
|
|
57
|
+
.chat-body.no-history .sessions-pane, .chat-body.no-trace .trace-pane { display: none; }
|
|
62
58
|
.pane {
|
|
63
|
-
flex: 1;
|
|
64
59
|
display: flex;
|
|
65
60
|
flex-direction: column;
|
|
66
61
|
min-width: 0;
|
|
67
|
-
|
|
62
|
+
min-height: 0;
|
|
63
|
+
border-right: 1px solid var(--line);
|
|
68
64
|
}
|
|
69
65
|
.pane:last-child { border-right: none; }
|
|
66
|
+
.sessions-pane, .trace-pane { background: var(--surface); }
|
|
67
|
+
.sessions-pane .pane-body { padding: 6px; gap: 2px; }
|
|
70
68
|
.sessions-pane { flex: 0 0 200px; }
|
|
71
69
|
.sessions-pane .pane-body { padding: 6px; gap: 2px; }
|
|
72
70
|
.session-item {
|
|
@@ -77,8 +75,8 @@ export const playgroundHtml = `<!doctype html>
|
|
|
77
75
|
padding: 6px 8px;
|
|
78
76
|
cursor: pointer;
|
|
79
77
|
}
|
|
80
|
-
.session-item:hover { background:
|
|
81
|
-
.session-item.active { background:
|
|
78
|
+
.session-item:hover { background: var(--surface); }
|
|
79
|
+
.session-item.active { background: var(--accent-soft); }
|
|
82
80
|
.session-item .preview {
|
|
83
81
|
flex: 1;
|
|
84
82
|
min-width: 0;
|
|
@@ -87,7 +85,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
87
85
|
text-overflow: ellipsis;
|
|
88
86
|
white-space: nowrap;
|
|
89
87
|
}
|
|
90
|
-
.session-item .session-time { font-size: 10px; color:
|
|
88
|
+
.session-item .session-time { font-size: 10px; color: var(--ink-faint); flex-shrink: 0; }
|
|
91
89
|
.session-item .session-remove {
|
|
92
90
|
flex-shrink: 0;
|
|
93
91
|
border: none;
|
|
@@ -95,61 +93,60 @@ export const playgroundHtml = `<!doctype html>
|
|
|
95
93
|
padding: 0 2px;
|
|
96
94
|
font-size: 13px;
|
|
97
95
|
line-height: 1;
|
|
98
|
-
color:
|
|
96
|
+
color: var(--ink-faint);
|
|
99
97
|
display: none;
|
|
100
98
|
}
|
|
101
99
|
.session-item:hover .session-remove { display: block; }
|
|
102
|
-
.session-item .session-remove:hover { color:
|
|
100
|
+
.session-item .session-remove:hover { color: var(--bad); }
|
|
103
101
|
.pane h2 {
|
|
104
|
-
font-size:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
color: light-dark(#666, #999);
|
|
102
|
+
font-size: 13px;
|
|
103
|
+
font-weight: 500;
|
|
104
|
+
color: var(--ink);
|
|
108
105
|
margin: 0;
|
|
109
|
-
padding:
|
|
110
|
-
border-bottom: 1px solid
|
|
106
|
+
padding: 10px 14px;
|
|
107
|
+
border-bottom: 1px solid var(--line);
|
|
111
108
|
}
|
|
112
109
|
.pane-body {
|
|
113
110
|
flex: 1;
|
|
114
111
|
overflow-y: auto;
|
|
115
|
-
padding:
|
|
112
|
+
padding: 12px 14px;
|
|
116
113
|
display: flex;
|
|
117
114
|
flex-direction: column;
|
|
118
|
-
gap:
|
|
115
|
+
gap: 10px;
|
|
119
116
|
}
|
|
120
|
-
.
|
|
117
|
+
.chat-pane .pane-body { padding: 20px 24px; gap: 14px; }
|
|
118
|
+
.msg { max-width: min(72ch, 92%); }
|
|
121
119
|
.msg-label {
|
|
122
|
-
font-size:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
margin-bottom: 2px;
|
|
120
|
+
font-size: 12px;
|
|
121
|
+
color: var(--ink-faint);
|
|
122
|
+
margin-bottom: 3px;
|
|
126
123
|
}
|
|
127
124
|
.msg-body {
|
|
128
|
-
padding:
|
|
129
|
-
border-radius:
|
|
125
|
+
padding: 10px 14px;
|
|
126
|
+
border-radius: 10px;
|
|
130
127
|
white-space: pre-wrap;
|
|
131
128
|
word-break: break-word;
|
|
132
129
|
}
|
|
133
130
|
.msg-user { align-self: flex-end; }
|
|
134
|
-
.msg-user .msg-body { background:
|
|
135
|
-
.msg-assistant .msg-body { background:
|
|
136
|
-
.msg-error .msg-body { background:
|
|
137
|
-
.msg-stopped .msg-body { font-style: italic; color:
|
|
131
|
+
.msg-user .msg-body { background: var(--accent-soft); }
|
|
132
|
+
.msg-assistant .msg-body { background: var(--surface); }
|
|
133
|
+
.msg-error .msg-body { background: var(--bad-soft); color: var(--bad); }
|
|
134
|
+
.msg-stopped .msg-body { font-style: italic; color: var(--ink-muted); background: var(--surface); }
|
|
138
135
|
.msg-thinking .msg-body { display: inline-flex; gap: 4px; padding: 11px 10px; }
|
|
139
136
|
.msg-approval .msg-body {
|
|
140
|
-
background:
|
|
141
|
-
border: 1px solid
|
|
137
|
+
background: var(--bg);
|
|
138
|
+
border: 1px solid var(--warn);
|
|
142
139
|
display: flex;
|
|
143
140
|
flex-direction: column;
|
|
144
141
|
gap: 6px;
|
|
145
142
|
}
|
|
146
143
|
.approval-tool { font-family: ui-monospace, monospace; font-weight: 600; }
|
|
147
|
-
.approval-scope { font-size: 11px; color:
|
|
144
|
+
.approval-scope { font-size: 11px; color: var(--ink-muted); }
|
|
148
145
|
.approval-reason { font-size: 12px; }
|
|
149
146
|
.approval-args {
|
|
150
147
|
font-size: 11px;
|
|
151
148
|
margin: 0;
|
|
152
|
-
background:
|
|
149
|
+
background: var(--bg);
|
|
153
150
|
border-radius: 6px;
|
|
154
151
|
padding: 6px 8px;
|
|
155
152
|
overflow-x: auto;
|
|
@@ -158,12 +155,12 @@ export const playgroundHtml = `<!doctype html>
|
|
|
158
155
|
}
|
|
159
156
|
.approval-actions { display: flex; gap: 8px; }
|
|
160
157
|
.approval-actions button { font-size: 12px; padding: 5px 12px; }
|
|
161
|
-
.approval-actions .approve { background:
|
|
162
|
-
.approval-actions .deny {
|
|
163
|
-
.approval-status { font-size: 12px; font-style: italic; color:
|
|
158
|
+
.approval-actions .approve { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
|
159
|
+
.approval-actions .deny { color: var(--bad); }
|
|
160
|
+
.approval-status { font-size: 12px; font-style: italic; color: var(--ink-muted); }
|
|
164
161
|
.msg-question .msg-body {
|
|
165
|
-
background:
|
|
166
|
-
border: 1px solid
|
|
162
|
+
background: var(--accent-soft);
|
|
163
|
+
border: 1px solid var(--accent);
|
|
167
164
|
display: flex;
|
|
168
165
|
flex-direction: column;
|
|
169
166
|
gap: 6px;
|
|
@@ -174,7 +171,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
174
171
|
.question-answer-row { display: flex; gap: 6px; }
|
|
175
172
|
.question-answer-row input { flex: 1; font-size: 12px; }
|
|
176
173
|
.question-answer-row button { font-size: 12px; padding: 5px 12px; }
|
|
177
|
-
.question-status { font-size: 12px; font-style: italic; color:
|
|
174
|
+
.question-status { font-size: 12px; font-style: italic; color: var(--ink-muted); }
|
|
178
175
|
/* An assistant message's rendered markdown (renderMessageMarkdown) —
|
|
179
176
|
tight margins since .msg-body already pads the bubble; a nested
|
|
180
177
|
paragraph/heading with normal margins would double up on that. */
|
|
@@ -182,10 +179,10 @@ export const playgroundHtml = `<!doctype html>
|
|
|
182
179
|
.msg-body p:first-child { margin-top: 0; }
|
|
183
180
|
.msg-body p:last-child { margin-bottom: 0; }
|
|
184
181
|
.msg-body h1, .msg-body h2, .msg-body h3, .msg-body h4 { margin: 10px 0 4px; font-size: 14px; }
|
|
185
|
-
.msg-body code { font-family: ui-monospace, monospace; font-size: 12px; background:
|
|
186
|
-
.msg-body pre { background:
|
|
182
|
+
.msg-body code { font-family: ui-monospace, monospace; font-size: 12px; background: var(--line); padding: 1px 4px; border-radius: 4px; }
|
|
183
|
+
.msg-body pre { background: var(--surface); border-radius: 6px; padding: 8px; overflow-x: auto; }
|
|
187
184
|
.msg-body pre code { background: none; padding: 0; }
|
|
188
|
-
.msg-body a { color:
|
|
185
|
+
.msg-body a { color: var(--accent); }
|
|
189
186
|
/* An image immediately followed by a download link (see
|
|
190
187
|
renderMessageMarkdown's own doc comment) merges into this — full
|
|
191
188
|
bubble width, a corner download icon that's a plain <a href> (a real
|
|
@@ -269,7 +266,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
269
266
|
width: 6px;
|
|
270
267
|
height: 6px;
|
|
271
268
|
border-radius: 50%;
|
|
272
|
-
background:
|
|
269
|
+
background: var(--ink-faint);
|
|
273
270
|
animation: dot-blink 1.4s infinite both;
|
|
274
271
|
}
|
|
275
272
|
.dot:nth-child(2) { animation-delay: 0.2s; }
|
|
@@ -277,7 +274,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
277
274
|
@keyframes dot-blink { 0%, 80%, 100% { opacity: 0.2; } 40% { opacity: 1; } }
|
|
278
275
|
.empty-hint { padding: 10px 2px; }
|
|
279
276
|
.event {
|
|
280
|
-
border-left: 3px solid
|
|
277
|
+
border-left: 3px solid var(--line);
|
|
281
278
|
padding: 4px 8px;
|
|
282
279
|
font-size: 12px;
|
|
283
280
|
}
|
|
@@ -288,7 +285,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
288
285
|
font-size: 11px;
|
|
289
286
|
white-space: pre-wrap;
|
|
290
287
|
word-break: break-word;
|
|
291
|
-
color:
|
|
288
|
+
color: var(--ink-muted);
|
|
292
289
|
}
|
|
293
290
|
.event-actauth { border-left-color: #f59e0b; }
|
|
294
291
|
.event-toollane { border-left-color: #3b82f6; }
|
|
@@ -299,54 +296,60 @@ export const playgroundHtml = `<!doctype html>
|
|
|
299
296
|
.event-approval { border-left-color: #eab308; }
|
|
300
297
|
.event-question { border-left-color: #3b82f6; }
|
|
301
298
|
.composer {
|
|
302
|
-
border-top: 1px solid
|
|
303
|
-
padding:
|
|
299
|
+
border-top: 1px solid var(--line);
|
|
300
|
+
padding: 12px 24px 16px;
|
|
304
301
|
display: flex;
|
|
305
302
|
flex-direction: column;
|
|
306
303
|
gap: 8px;
|
|
307
304
|
}
|
|
308
|
-
.send-row { display: flex; gap: 8px; }
|
|
309
|
-
#messageInput { flex: 1; resize: vertical; height:
|
|
305
|
+
.send-row { display: flex; gap: 8px; align-items: flex-end; }
|
|
306
|
+
#messageInput { flex: 1; min-width: 0; resize: vertical; height: 64px; border-radius: var(--radius); padding: 10px 12px; }
|
|
307
|
+
#sendButton { background: var(--accent); border-color: var(--accent); color: var(--on-accent); padding: 8px 16px; }
|
|
310
308
|
#messageInput:disabled, #sendButton:disabled { cursor: not-allowed; }
|
|
311
|
-
details summary { cursor: pointer; font-size: 12px; color:
|
|
309
|
+
details summary { cursor: pointer; font-size: 12px; color: var(--ink-muted); }
|
|
312
310
|
.advanced-hint { margin: 6px 0 0; font-size: 11px; }
|
|
313
311
|
.advanced-fields { display: flex; gap: 8px; margin-top: 6px; }
|
|
314
312
|
.advanced-fields > div { flex: 1; display: flex; flex-direction: column; gap: 4px; }
|
|
315
|
-
.advanced-fields label { font-size: 11px; color:
|
|
313
|
+
.advanced-fields label { font-size: 11px; color: var(--ink-muted); }
|
|
316
314
|
.advanced-fields textarea { height: 50px; font-family: ui-monospace, monospace; font-size: 11px; resize: vertical; }
|
|
317
|
-
@media (max-width:
|
|
318
|
-
|
|
319
|
-
.
|
|
320
|
-
.
|
|
321
|
-
.
|
|
315
|
+
@media (max-width: 860px) {
|
|
316
|
+
.chat-toolbar { padding: 8px 16px; }
|
|
317
|
+
.chat-body, .chat-body.no-history, .chat-body.no-trace { grid-template-columns: minmax(0, 1fr); grid-auto-rows: minmax(0, auto); overflow-y: auto; }
|
|
318
|
+
.chat-body .pane { border-right: none; border-bottom: 1px solid var(--line); }
|
|
319
|
+
.chat-pane { min-height: 60vh; }
|
|
320
|
+
.sessions-pane, .trace-pane { max-height: 40vh; }
|
|
321
|
+
.chat-pane .pane-body, .composer { padding-left: 16px; padding-right: 16px; }
|
|
322
322
|
}
|
|
323
323
|
</style>
|
|
324
324
|
</head>
|
|
325
|
-
<body>
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
</
|
|
339
|
-
<
|
|
325
|
+
<body data-le-page="chat">
|
|
326
|
+
<div class="le-app">
|
|
327
|
+
${consoleSidebarHtml}
|
|
328
|
+
<main class="le-main">
|
|
329
|
+
<div id="leHead"></div>
|
|
330
|
+
<div class="chat-toolbar">
|
|
331
|
+
<select id="agentSelect" hidden aria-label="Agent"></select>
|
|
332
|
+
<span id="agentCaption" hidden></span>
|
|
333
|
+
<a id="configLink" href="/agents/config" hidden>Agent config</a>
|
|
334
|
+
<button id="newConversationButton" type="button">New conversation</button>
|
|
335
|
+
<button id="historyToggle" type="button" aria-pressed="true">History</button>
|
|
336
|
+
<button id="traceToggle" type="button" aria-pressed="true">Run trace</button>
|
|
337
|
+
<span id="sessionLabel">session: (new)</span>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="chat-body" id="chatBody">
|
|
340
340
|
<section class="pane sessions-pane">
|
|
341
|
-
<h2>
|
|
341
|
+
<h2>History</h2>
|
|
342
342
|
<div class="pane-body" id="sessionsPane"></div>
|
|
343
343
|
</section>
|
|
344
|
-
<section class="pane">
|
|
345
|
-
<h2>Chat</h2>
|
|
344
|
+
<section class="pane chat-pane" aria-label="Conversation">
|
|
346
345
|
<div class="pane-body" id="chatPane"></div>
|
|
347
346
|
<div class="composer">
|
|
347
|
+
<div class="send-row">
|
|
348
|
+
<textarea id="messageInput" placeholder="Message… (Enter to send, Shift+Enter for a new line)"></textarea>
|
|
349
|
+
<button id="sendButton" type="button">Send</button>
|
|
350
|
+
</div>
|
|
348
351
|
<details>
|
|
349
|
-
<summary>
|
|
352
|
+
<summary>Request options</summary>
|
|
350
353
|
<p class="advanced-hint muted">For agents whose sessionIdFor/tenantFor need something beyond a plain sessionId — e.g. customer-service reads customerEmail from the body.</p>
|
|
351
354
|
<div class="advanced-fields">
|
|
352
355
|
<div>
|
|
@@ -359,17 +362,16 @@ export const playgroundHtml = `<!doctype html>
|
|
|
359
362
|
</div>
|
|
360
363
|
</div>
|
|
361
364
|
</details>
|
|
362
|
-
<div class="send-row">
|
|
363
|
-
<textarea id="messageInput" placeholder="Message… (Enter to send, Shift+Enter for a new line)"></textarea>
|
|
364
|
-
<button id="sendButton" type="button">Send</button>
|
|
365
|
-
</div>
|
|
366
365
|
</div>
|
|
367
366
|
</section>
|
|
368
|
-
<section class="pane">
|
|
369
|
-
<h2>
|
|
367
|
+
<section class="pane trace-pane">
|
|
368
|
+
<h2>Run trace</h2>
|
|
370
369
|
<div class="pane-body" id="timelinePane"></div>
|
|
371
370
|
</section>
|
|
371
|
+
</div>
|
|
372
372
|
</main>
|
|
373
|
+
</div>
|
|
374
|
+
${consoleShellScript}
|
|
373
375
|
<div class="lightbox-overlay" id="lightboxOverlay">
|
|
374
376
|
<div class="lightbox-inner">
|
|
375
377
|
<button type="button" class="lightbox-close" id="lightboxClose" aria-label="Close">×</button>
|
|
@@ -1133,7 +1135,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
1133
1135
|
var list = loadSavedSessions().filter(function (s) { return s.agent === agent; });
|
|
1134
1136
|
sessionsPane.textContent = '';
|
|
1135
1137
|
if (!list.length) {
|
|
1136
|
-
setEmptyHint(sessionsPane, '
|
|
1138
|
+
setEmptyHint(sessionsPane, 'Past conversations with this agent appear here.');
|
|
1137
1139
|
return;
|
|
1138
1140
|
}
|
|
1139
1141
|
for (var i = 0; i < list.length; i++) {
|
|
@@ -1243,7 +1245,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
1243
1245
|
chatPane.textContent = '';
|
|
1244
1246
|
toolCallCardsById = {};
|
|
1245
1247
|
knownUrlsByBase = {};
|
|
1246
|
-
setEmptyHint(timelinePane, '
|
|
1248
|
+
setEmptyHint(timelinePane, 'Each step of a turn (model calls, tool calls, permission checks) appears here as the agent runs.');
|
|
1247
1249
|
renderSessionsPane();
|
|
1248
1250
|
|
|
1249
1251
|
fetch('/agents/' + encodeURIComponent(item.agent) + '/sessions/' + encodeURIComponent(item.sessionId))
|
|
@@ -1400,10 +1402,61 @@ export const playgroundHtml = `<!doctype html>
|
|
|
1400
1402
|
sessionLabel.title = '';
|
|
1401
1403
|
var name = agentSelect.value;
|
|
1402
1404
|
setEmptyHint(chatPane, name ? 'No messages yet \\u2014 say hi to ' + name + '.' : 'No messages yet \\u2014 pick an agent above to get started.');
|
|
1403
|
-
setEmptyHint(timelinePane, '
|
|
1405
|
+
setEmptyHint(timelinePane, 'Each step of a turn (model calls, tool calls, permission checks) appears here as the agent runs.');
|
|
1404
1406
|
renderSessionsPane();
|
|
1405
1407
|
}
|
|
1406
1408
|
|
|
1409
|
+
// The console's agent header + tabs (web/console-shell.ts), with this
|
|
1410
|
+
// page as the Chat tab. Rendered from the /agents list right away, then
|
|
1411
|
+
// again once this agent's config arrives with its model and counts.
|
|
1412
|
+
function renderAgentHead() {
|
|
1413
|
+
var head = document.getElementById('leHead');
|
|
1414
|
+
var name = agentSelect.value;
|
|
1415
|
+
if (!name) {
|
|
1416
|
+
head.innerHTML = '<div class="le-head plain"><div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">☰</button>' +
|
|
1417
|
+
'<div><h1>Chat</h1><p>No agents yet. <a href="/agents?new=1">Create one</a> or add a folder under agents/.</p></div></div></div>';
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
leShell.setActive(name);
|
|
1421
|
+
var agent = currentAgents.filter(function (a) { return a.name === name; })[0] || {};
|
|
1422
|
+
function draw(cfg) {
|
|
1423
|
+
var chips = [];
|
|
1424
|
+
var counts = {};
|
|
1425
|
+
if (cfg) {
|
|
1426
|
+
chips.push({ text: typeof cfg.model === 'string' ? cfg.model : cfg.model.provider + ' \u00b7 ' + (cfg.model.model || 'default model'), mono: true });
|
|
1427
|
+
counts = { tools: cfg.tools.length, skills: cfg.skills.length, actauth: cfg.permissions.rules.length };
|
|
1428
|
+
}
|
|
1429
|
+
head.innerHTML = leShell.renderHead({ name: name, prompt: agent.systemPrompt, chips: chips, tab: 'chat', counts: counts });
|
|
1430
|
+
}
|
|
1431
|
+
draw(null);
|
|
1432
|
+
fetch('/agents/' + encodeURIComponent(name) + '/config')
|
|
1433
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
1434
|
+
.then(function (cfg) { if (cfg && agentSelect.value === name) draw(cfg); })
|
|
1435
|
+
.catch(function () {});
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// History and Run trace can each be hidden; remembered per browser.
|
|
1439
|
+
// Both start hidden on a narrow screen, where they'd stack above and
|
|
1440
|
+
// below the conversation.
|
|
1441
|
+
var chatBody = document.getElementById('chatBody');
|
|
1442
|
+
var historyToggle = document.getElementById('historyToggle');
|
|
1443
|
+
var traceToggle = document.getElementById('traceToggle');
|
|
1444
|
+
var panes = { history: window.innerWidth > 860, trace: window.innerWidth > 860 };
|
|
1445
|
+
try {
|
|
1446
|
+
var savedPanes = JSON.parse(localStorage.getItem('loopengine.chatPanes') || 'null');
|
|
1447
|
+
if (savedPanes && window.innerWidth > 860) panes = savedPanes;
|
|
1448
|
+
} catch (e) {}
|
|
1449
|
+
function applyPanes() {
|
|
1450
|
+
chatBody.classList.toggle('no-history', !panes.history);
|
|
1451
|
+
chatBody.classList.toggle('no-trace', !panes.trace);
|
|
1452
|
+
historyToggle.setAttribute('aria-pressed', String(panes.history));
|
|
1453
|
+
traceToggle.setAttribute('aria-pressed', String(panes.trace));
|
|
1454
|
+
try { localStorage.setItem('loopengine.chatPanes', JSON.stringify(panes)); } catch (e) {}
|
|
1455
|
+
}
|
|
1456
|
+
historyToggle.addEventListener('click', function () { panes.history = !panes.history; applyPanes(); });
|
|
1457
|
+
traceToggle.addEventListener('click', function () { panes.trace = !panes.trace; applyPanes(); });
|
|
1458
|
+
applyPanes();
|
|
1459
|
+
|
|
1407
1460
|
function loadAgents() {
|
|
1408
1461
|
fetch('/agents')
|
|
1409
1462
|
.then(function (r) { return r.json(); })
|
|
@@ -1429,12 +1482,14 @@ export const playgroundHtml = `<!doctype html>
|
|
|
1429
1482
|
? 'Message\\u2026 (Enter to send, Shift+Enter for a new line)'
|
|
1430
1483
|
: 'No agents registered';
|
|
1431
1484
|
updateCaption();
|
|
1485
|
+
renderAgentHead();
|
|
1432
1486
|
resetConversation();
|
|
1433
1487
|
updateSendButtonState();
|
|
1434
1488
|
if (hasAgents) messageInput.focus();
|
|
1435
1489
|
})
|
|
1436
1490
|
.catch(function (err) {
|
|
1437
1491
|
agentCaption.textContent = 'Could not load agents: ' + err.message;
|
|
1492
|
+
renderAgentHead();
|
|
1438
1493
|
resetConversation();
|
|
1439
1494
|
});
|
|
1440
1495
|
}
|
|
@@ -1622,6 +1677,7 @@ export const playgroundHtml = `<!doctype html>
|
|
|
1622
1677
|
|
|
1623
1678
|
agentSelect.addEventListener('change', function () {
|
|
1624
1679
|
updateCaption();
|
|
1680
|
+
renderAgentHead();
|
|
1625
1681
|
resetConversation();
|
|
1626
1682
|
updateSendButtonState();
|
|
1627
1683
|
});
|