tinywebui 0.1.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.
- package/LICENSE +661 -0
- package/README.md +586 -0
- package/bin/tinywebui.js +105 -0
- package/package.json +49 -0
- package/public/app.js +81 -0
- package/public/chat/attachments.js +304 -0
- package/public/chat/chat.js +282 -0
- package/public/chat/composer.js +289 -0
- package/public/chat/library.js +76 -0
- package/public/chat/outline.js +39 -0
- package/public/chat/queue.js +124 -0
- package/public/chat/sidebar.js +485 -0
- package/public/chat/stream.js +162 -0
- package/public/chat/tasks.js +36 -0
- package/public/chat/transcript.js +691 -0
- package/public/core/access.js +42 -0
- package/public/core/api.js +27 -0
- package/public/core/dom.js +13 -0
- package/public/core/id.js +7 -0
- package/public/core/md.js +131 -0
- package/public/core/state.js +31 -0
- package/public/favicon.svg +9 -0
- package/public/index.html +261 -0
- package/public/panels/admin.js +199 -0
- package/public/panels/automations.js +464 -0
- package/public/panels/panels.js +52 -0
- package/public/panels/settings.js +365 -0
- package/public/panels/usage.js +474 -0
- package/public/shared/attachment_note.js +23 -0
- package/public/shared/events.js +27 -0
- package/public/shared/features.js +21 -0
- package/public/shared/origins.js +8 -0
- package/public/shared/usage.js +59 -0
- package/public/styles.css +1455 -0
- package/public/themes/README.md +178 -0
- package/public/themes/cyber-grid/cyber-grid.css +59 -0
- package/public/themes/drivers/scene-kit.js +422 -0
- package/public/themes/fall-fairy/fall-fairy.css +221 -0
- package/public/themes/fall-fairy/fall-fairy.js +859 -0
- package/public/themes/manifest.json +6 -0
- package/public/themes/theme-loader.js +123 -0
- package/src/access/auth.js +228 -0
- package/src/access/auth_gate.js +67 -0
- package/src/access/policy.js +180 -0
- package/src/audit.js +7 -0
- package/src/automations/automation.js +136 -0
- package/src/automations/scheduler.js +147 -0
- package/src/chat/compact.js +242 -0
- package/src/chat/llm.js +448 -0
- package/src/chat/runs.js +180 -0
- package/src/chat/tool_executor.js +239 -0
- package/src/cli.js +478 -0
- package/src/config/approval.js +54 -0
- package/src/config/config.js +479 -0
- package/src/config/models.js +112 -0
- package/src/files/documents.js +91 -0
- package/src/files/images.js +62 -0
- package/src/harness/agent.js +115 -0
- package/src/harness/attribution.js +75 -0
- package/src/harness/checkpoint.js +96 -0
- package/src/harness/history.js +40 -0
- package/src/harness/prompt.js +93 -0
- package/src/harness/provider.js +430 -0
- package/src/http.js +95 -0
- package/src/log.js +24 -0
- package/src/mcp.js +405 -0
- package/src/retrieval/embedding.js +298 -0
- package/src/retrieval/retrieval.js +250 -0
- package/src/routes/admin.js +112 -0
- package/src/routes/auth.js +97 -0
- package/src/routes/automations.js +92 -0
- package/src/routes/chats.js +232 -0
- package/src/routes/documents.js +133 -0
- package/src/routes/library.js +51 -0
- package/src/routes/mcp.js +59 -0
- package/src/routes/settings.js +101 -0
- package/src/server.js +397 -0
- package/src/set-password.js +32 -0
- package/src/store/automations.js +119 -0
- package/src/store/chats.js +254 -0
- package/src/store/documents.js +256 -0
- package/src/store/embeddings.js +67 -0
- package/src/store/index.js +90 -0
- package/src/store/messages.js +93 -0
- package/src/store/schema.js +480 -0
- package/src/store/scope.js +43 -0
- package/src/store/search.js +56 -0
- package/src/store/turns.js +78 -0
- package/src/store/usage.js +327 -0
- package/src/store/users.js +102 -0
- package/src/store/wire.js +55 -0
- package/src/tools/ask_tool.js +93 -0
- package/src/tools/chat_search_tool.js +63 -0
- package/src/tools/context_tool.js +183 -0
- package/src/tools/document_tool.js +102 -0
- package/src/tools/task_tool.js +63 -0
|
@@ -0,0 +1,1455 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #f8f9fb; --panel: #eef1f5; --line: #d9dee6; --fg: #1c2230;
|
|
3
|
+
--muted: #555e72; --faint: #6b7385; --accent: #2f55cc; --tool: #0f6e66; --err: #b3261e;
|
|
4
|
+
--raise: #ffffff; /* the one surface that sits above the page: the composer, dialogs */
|
|
5
|
+
--accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
6
|
+
--bg-soft: var(--panel);
|
|
7
|
+
--mono: ui-monospace, "Cascadia Code", SFMono-Regular, Menlo, monospace;
|
|
8
|
+
--sans: "Segoe UI Variable Text", "Segoe UI", -apple-system, system-ui, sans-serif;
|
|
9
|
+
|
|
10
|
+
/* Radius is a small, deliberate scale. Components use these semantic
|
|
11
|
+
tokens instead of accumulating one-off values, so nested surfaces keep
|
|
12
|
+
their hierarchy at every viewport. */
|
|
13
|
+
--r-xs: 4px;
|
|
14
|
+
--r-sm: 8px;
|
|
15
|
+
--r-md: 12px;
|
|
16
|
+
--r-lg: 20px;
|
|
17
|
+
--r-bubble: 18px;
|
|
18
|
+
--r-composer: 24px;
|
|
19
|
+
--r-pill: 999px;
|
|
20
|
+
--fs-small: .875rem;
|
|
21
|
+
|
|
22
|
+
/* One type scale, in rem, so the browser's own font-size setting still
|
|
23
|
+
applies. Nothing below 0.8125rem/13px: that was the floor the old 10-11px
|
|
24
|
+
metadata kept falling through. */
|
|
25
|
+
--fs-meta: .8125rem; /* timestamps, counts, badges */
|
|
26
|
+
--fs-ui: .9375rem; /* controls, labels, list rows */
|
|
27
|
+
--fs-body: 1.0625rem; /* the transcript itself */
|
|
28
|
+
|
|
29
|
+
/* Minimum hit target. Every interactive element is at least this tall, which
|
|
30
|
+
is what drives the bar height and the row padding rather than the reverse. */
|
|
31
|
+
--tap: 2.25rem;
|
|
32
|
+
--bar: 3rem;
|
|
33
|
+
--measure: 46rem;
|
|
34
|
+
}
|
|
35
|
+
@media (prefers-color-scheme: dark) {
|
|
36
|
+
:root:not([data-theme="light"]) {
|
|
37
|
+
--bg: #14171d; --panel: #1b1f27; --line: #2f3542; --fg: #e6e9ef;
|
|
38
|
+
--muted: #a3abbb; --faint: #8b93a3; --accent: #8ea6ff; --tool: #5fd4c4; --err: #f4a19a;
|
|
39
|
+
--raise: #20252f;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
* { box-sizing: border-box; }
|
|
43
|
+
body {
|
|
44
|
+
margin: 0; background: var(--bg); color: var(--fg);
|
|
45
|
+
font: var(--fs-body)/1.6 var(--sans);
|
|
46
|
+
-webkit-font-smoothing: antialiased;
|
|
47
|
+
display: flex; flex-direction: column; height: 100dvh; overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
/* The two panes below the header, previously body's own direct children and
|
|
50
|
+
row-flexed themselves. Split out so header can run the full page width as
|
|
51
|
+
one top bar instead of being confined to the main column beside it. */
|
|
52
|
+
#shell { display: flex; flex: 1; min-height: 0; overflow: hidden; }
|
|
53
|
+
|
|
54
|
+
/* The one top bar, full page width, above both panes. Centered on the row's
|
|
55
|
+
own cross axis -- the same rule at every width, which is what keeps menu,
|
|
56
|
+
title, status and settings sitting on one clean line whether the header is
|
|
57
|
+
512px or 1514px wide. This only reads as clean because #status below is
|
|
58
|
+
pinned to a single line: two mismatched buttons and two lines of status
|
|
59
|
+
text fighting over "centered" is what baseline alignment used to paper
|
|
60
|
+
over, one font's metrics at a time, instead of removing the actual cause. */
|
|
61
|
+
header {
|
|
62
|
+
height: var(--bar); flex-shrink: 0; padding: 0 .875rem;
|
|
63
|
+
display: flex; align-items: center; gap: .5rem;
|
|
64
|
+
border-bottom: 1px solid var(--line);
|
|
65
|
+
}
|
|
66
|
+
header h1 {
|
|
67
|
+
font-size: 1rem; margin: 0 .25rem 0 0; font-weight: 650; flex-shrink: 0;
|
|
68
|
+
letter-spacing: -.01em;
|
|
69
|
+
}
|
|
70
|
+
header button { border-color: transparent; background: none; color: var(--muted); }
|
|
71
|
+
header button:hover { background: var(--panel); border-color: transparent; color: var(--fg); }
|
|
72
|
+
header button.active { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
|
|
73
|
+
header button.active:hover { background: var(--accent-soft); }
|
|
74
|
+
header .grow { flex: 1; }
|
|
75
|
+
/* #status is a long, dynamic string -- model id plus a tool count -- that
|
|
76
|
+
fits fine on a wide header and does not on a narrow one. Flex items don't
|
|
77
|
+
shrink past their own content width by default (min-width:auto), so
|
|
78
|
+
without help here it wraps to a second line instead: the header grows
|
|
79
|
+
past its own fixed height, and every other child's baseline alignment goes
|
|
80
|
+
with it -- which is what made the whole bar look wrong, not just this one
|
|
81
|
+
span. One line, truncated, is what a status string is for anyway; the
|
|
82
|
+
model and tool count are both one click away in settings if cut off. */
|
|
83
|
+
#status {
|
|
84
|
+
font-size: var(--fs-meta); color: var(--muted); font-family: var(--mono);
|
|
85
|
+
min-width: 3rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
|
86
|
+
}
|
|
87
|
+
#menu { display: none; }
|
|
88
|
+
|
|
89
|
+
/* One focus ring for everything, drawn outside the element so a dense row does
|
|
90
|
+
not clip it. Keyboard only -- the mouse never sees it. */
|
|
91
|
+
:focus-visible {
|
|
92
|
+
outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-xs);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
button {
|
|
96
|
+
font: inherit; font-size: var(--fs-ui); line-height: 1.2;
|
|
97
|
+
min-height: var(--tap); padding: .375rem .75rem; border-radius: var(--r-sm);
|
|
98
|
+
border: 1px solid var(--line); background: var(--raise); color: var(--fg); cursor: pointer;
|
|
99
|
+
transition: background .12s, border-color .12s, color .12s;
|
|
100
|
+
}
|
|
101
|
+
button:hover { border-color: var(--faint); }
|
|
102
|
+
button.primary { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 550; }
|
|
103
|
+
button.primary:hover { background: color-mix(in srgb, var(--accent) 88%, #000); border-color: transparent; }
|
|
104
|
+
button:disabled { opacity: .55; cursor: default; }
|
|
105
|
+
input, textarea, select {
|
|
106
|
+
font: inherit; font-size: var(--fs-ui); width: 100%; min-height: var(--tap);
|
|
107
|
+
padding: .375rem .625rem; color: var(--fg);
|
|
108
|
+
background: var(--raise); border: 1px solid var(--line); border-radius: var(--r-sm); resize: vertical;
|
|
109
|
+
transition: border-color .12s, box-shadow .12s;
|
|
110
|
+
}
|
|
111
|
+
input:focus, textarea:focus, select:focus {
|
|
112
|
+
outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
|
|
113
|
+
}
|
|
114
|
+
textarea#systemPrompt, textarea#mcpText { font-family: var(--mono); font-size: var(--fs-ui); }
|
|
115
|
+
label { font-size: var(--fs-ui); color: var(--muted); display: block; margin-bottom: .25rem; }
|
|
116
|
+
|
|
117
|
+
/* history sidebar -- scoped to #side, not the bare tag: the right rail and
|
|
118
|
+
the outline minimap are <aside> too, and don't want the sidebar's chrome. */
|
|
119
|
+
#side {
|
|
120
|
+
/* Driven by the drag handle below; the fallback is the width it always had. */
|
|
121
|
+
width: var(--side-w, 15rem);
|
|
122
|
+
position: relative;
|
|
123
|
+
flex-shrink: 0; border-right: 1px solid var(--line);
|
|
124
|
+
display: flex; flex-direction: column; background: var(--panel);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* The drag handle straddles the sidebar border: a 1px border is not a target,
|
|
128
|
+
so the grab area is widened without moving anything it sits against. */
|
|
129
|
+
#side-resize {
|
|
130
|
+
position: absolute; top: 0; bottom: 0; right: -3px; width: 7px;
|
|
131
|
+
cursor: col-resize; z-index: 2; touch-action: none;
|
|
132
|
+
}
|
|
133
|
+
#side-resize::after {
|
|
134
|
+
content: ""; position: absolute; inset: 0 3px; background: var(--accent);
|
|
135
|
+
opacity: 0; transition: opacity .12s;
|
|
136
|
+
}
|
|
137
|
+
#side-resize:hover::after,
|
|
138
|
+
#side-resize:focus-visible::after,
|
|
139
|
+
body.resizing #side-resize::after { opacity: 1; }
|
|
140
|
+
#side-resize:focus-visible { outline: none; }
|
|
141
|
+
/* While dragging, nothing else should be selecting text or fighting the cursor. */
|
|
142
|
+
body.resizing { cursor: col-resize; user-select: none; }
|
|
143
|
+
/* Same fixed height as the header, so the two top bars line up across the
|
|
144
|
+
sidebar border instead of each sizing to its own contents. */
|
|
145
|
+
#side .head {
|
|
146
|
+
height: var(--bar); flex-shrink: 0; padding: 0 .5rem; gap: .5rem;
|
|
147
|
+
display: flex; align-items: center; border-bottom: 1px solid var(--line);
|
|
148
|
+
}
|
|
149
|
+
#side .head button { flex: 1; min-width: 0; }
|
|
150
|
+
|
|
151
|
+
.search-row {
|
|
152
|
+
flex-shrink: 0; position: relative; padding: .5rem; border-bottom: 1px solid var(--line);
|
|
153
|
+
}
|
|
154
|
+
.search-row input { padding-right: 2rem; }
|
|
155
|
+
.search-row input::-webkit-search-cancel-button { display: none; }
|
|
156
|
+
#chatSearchClear {
|
|
157
|
+
position: absolute; top: 50%; right: .75rem; transform: translateY(-50%);
|
|
158
|
+
width: 1.5rem; height: 1.5rem; min-height: 0; padding: 0; border: 0; background: none;
|
|
159
|
+
display: flex; align-items: center; justify-content: center;
|
|
160
|
+
color: var(--faint); font-size: var(--fs-body); line-height: 1;
|
|
161
|
+
}
|
|
162
|
+
#chatSearchClear:hover { color: var(--err); }
|
|
163
|
+
#chatSearchClear[hidden] { display: none; }
|
|
164
|
+
|
|
165
|
+
#chats {
|
|
166
|
+
flex: 1; overflow-y: auto;
|
|
167
|
+
scrollbar-width: thin; scrollbar-color: var(--line) transparent;
|
|
168
|
+
}
|
|
169
|
+
#chats::-webkit-scrollbar { width: .5rem; }
|
|
170
|
+
#chats::-webkit-scrollbar-track { background: transparent; }
|
|
171
|
+
#chats::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-pill); }
|
|
172
|
+
#chats::-webkit-scrollbar-thumb:hover { background: var(--muted); }
|
|
173
|
+
#chats { padding: .25rem .375rem .5rem; }
|
|
174
|
+
.chat-item {
|
|
175
|
+
display: flex; align-items: center; gap: .25rem; padding: .3125rem .375rem .3125rem .625rem;
|
|
176
|
+
margin: 1px 0; border-radius: var(--r-sm);
|
|
177
|
+
min-height: var(--tap); cursor: pointer; font-size: var(--fs-ui);
|
|
178
|
+
color: var(--muted);
|
|
179
|
+
}
|
|
180
|
+
.chat-item:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); color: var(--fg); }
|
|
181
|
+
.chat-item.active { background: var(--raise); color: var(--fg); box-shadow: 0 0 0 1px var(--line); font-weight: 500; }
|
|
182
|
+
.chat-item .t { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
183
|
+
/* The delete control used to appear on hover alone, which put it out of reach
|
|
184
|
+
of touch and of the keyboard. It is always present now, just quiet. */
|
|
185
|
+
.chat-item .x {
|
|
186
|
+
flex-shrink: 0; width: 1.75rem; height: 1.75rem; min-height: 0; padding: 0;
|
|
187
|
+
display: flex; align-items: center; justify-content: center;
|
|
188
|
+
border: 0; background: none; color: var(--faint); font-size: var(--fs-body);
|
|
189
|
+
}
|
|
190
|
+
.chat-item .x:hover, .chat-item .x:focus-visible { color: var(--err); }
|
|
191
|
+
.chat-item .organize-chat {
|
|
192
|
+
flex: 0 0 auto; min-width: 1.75rem; min-height: 1.75rem; padding: 0;
|
|
193
|
+
border: 0; background: transparent; color: var(--faint); font-size: 1.1rem;
|
|
194
|
+
}
|
|
195
|
+
.chat-item .organize-chat:hover, .chat-item .organize-chat:focus-visible { color: var(--accent); }
|
|
196
|
+
dialog#organizeDialog {
|
|
197
|
+
width: min(24rem, calc(100vw - 2rem)); padding: 1.25rem;
|
|
198
|
+
border: 1px solid var(--line); border-radius: var(--r-lg);
|
|
199
|
+
background: var(--raise); color: var(--fg);
|
|
200
|
+
}
|
|
201
|
+
dialog#organizeDialog::backdrop { background: rgba(0, 0, 0, .62); }
|
|
202
|
+
#organizeForm { display: flex; flex-direction: column; gap: .5rem; }
|
|
203
|
+
#organizeForm h2 { margin: 0 0 .5rem; font-size: var(--fs-body); }
|
|
204
|
+
#organizeForm label { color: var(--muted); font-size: var(--fs-meta); }
|
|
205
|
+
dialog#libraryDialog {
|
|
206
|
+
width: min(34rem, calc(100vw - 2rem)); padding: 1.25rem;
|
|
207
|
+
border: 1px solid var(--line); border-radius: var(--r-lg);
|
|
208
|
+
background: var(--raise); color: var(--fg);
|
|
209
|
+
}
|
|
210
|
+
dialog#libraryDialog::backdrop { background: rgba(0, 0, 0, .62); }
|
|
211
|
+
#libraryForm { display: flex; flex-direction: column; gap: .5rem; }
|
|
212
|
+
#libraryForm h2 { margin: 0 0 .5rem; font-size: var(--fs-body); }
|
|
213
|
+
.library-list { max-height: min(22rem, 50vh); overflow-y: auto; display: flex; flex-direction: column; }
|
|
214
|
+
.library-row {
|
|
215
|
+
display: grid; grid-template-columns: auto minmax(0, 1fr) 3.5rem 6.5rem; column-gap: .625rem; align-items: center;
|
|
216
|
+
padding: .4rem .5rem; border-radius: var(--r-sm); cursor: pointer;
|
|
217
|
+
}
|
|
218
|
+
.library-row:hover { background: var(--accent-soft); }
|
|
219
|
+
.library-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
220
|
+
.library-meta { color: var(--muted); font-size: var(--fs-meta); white-space: nowrap; }
|
|
221
|
+
.library-meta:last-child { text-align: right; }
|
|
222
|
+
.dialog-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: .75rem; }
|
|
223
|
+
.dialog-actions .danger { margin-right: auto; color: var(--err); border-color: var(--err); }
|
|
224
|
+
.organize-error { color: var(--err); font-size: var(--fs-meta); }
|
|
225
|
+
.empty { padding: .625rem .5rem; font-size: var(--fs-ui); color: var(--muted); }
|
|
226
|
+
|
|
227
|
+
.chat-folder { margin-bottom: .25rem; }
|
|
228
|
+
.chat-folder-heading { border-radius: var(--r-sm); }
|
|
229
|
+
.chat-folder-heading {
|
|
230
|
+
position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: .5rem;
|
|
231
|
+
padding: .625rem .625rem; cursor: pointer; list-style: none;
|
|
232
|
+
background: var(--panel, var(--bg)); color: var(--fg); font-size: var(--fs-ui); font-weight: 600;
|
|
233
|
+
}
|
|
234
|
+
.chat-folder-heading::-webkit-details-marker { display: none; }
|
|
235
|
+
.chat-folder-heading::before { content: '▸'; color: var(--accent); transition: transform .12s ease; }
|
|
236
|
+
.chat-folder[open] > .chat-folder-heading::before { transform: rotate(90deg); }
|
|
237
|
+
.chat-folder-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
238
|
+
.chat-folder-count {
|
|
239
|
+
margin-left: auto; padding: .05rem .4rem; border: 1px solid var(--line); border-radius: var(--r-pill);
|
|
240
|
+
color: var(--faint); font-size: var(--fs-meta); font-weight: 400;
|
|
241
|
+
}
|
|
242
|
+
.chat-folder-content { padding: 0 0 .25rem .4rem; }
|
|
243
|
+
.chat-date-heading {
|
|
244
|
+
padding: .75rem .625rem .25rem; color: var(--faint); font-size: var(--fs-meta);
|
|
245
|
+
font-weight: 600;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* A search result carries a title and a snippet, so it grows past the plain
|
|
249
|
+
list's single line rather than truncating either one away. */
|
|
250
|
+
.chat-item.search-hit { flex-direction: column; align-items: stretch; gap: .1875rem; padding: .5rem; }
|
|
251
|
+
.chat-item.search-hit .t { flex: initial; white-space: normal; overflow: visible; }
|
|
252
|
+
.hit-title {
|
|
253
|
+
display: flex; align-items: baseline; gap: .375rem;
|
|
254
|
+
color: var(--fg); font-size: var(--fs-ui);
|
|
255
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
256
|
+
}
|
|
257
|
+
.hit-role {
|
|
258
|
+
flex-shrink: 0; margin-left: auto; color: var(--faint); font-size: var(--fs-meta);
|
|
259
|
+
font-family: var(--mono); text-transform: uppercase; letter-spacing: .04em;
|
|
260
|
+
}
|
|
261
|
+
.hit-snippet {
|
|
262
|
+
color: var(--muted); font-size: var(--fs-meta); line-height: 1.4;
|
|
263
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
264
|
+
}
|
|
265
|
+
.hit-snippet mark {
|
|
266
|
+
background: none; color: var(--fg); font-weight: 600;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* main column */
|
|
270
|
+
main { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; position: relative; }
|
|
271
|
+
|
|
272
|
+
/* Settings takes the whole main column rather than squeezing the transcript. */
|
|
273
|
+
#settings {
|
|
274
|
+
display: none; position: absolute; inset: 0; z-index: 10; padding: 0;
|
|
275
|
+
background: var(--bg); flex-direction: column; overflow: hidden;
|
|
276
|
+
}
|
|
277
|
+
#settings.open { display: flex; }
|
|
278
|
+
.settings-content { overflow-y: auto; }
|
|
279
|
+
#automations {
|
|
280
|
+
display: none; position: absolute; inset: 0; z-index: 9; background: var(--bg);
|
|
281
|
+
flex-direction: column; overflow: hidden;
|
|
282
|
+
}
|
|
283
|
+
#automations.open { display: flex; }
|
|
284
|
+
#statistics {
|
|
285
|
+
display: none; position: absolute; inset: 0; z-index: 9; background: var(--bg);
|
|
286
|
+
flex-direction: column; overflow: hidden;
|
|
287
|
+
}
|
|
288
|
+
#statistics.open { display: flex; }
|
|
289
|
+
#admin {
|
|
290
|
+
display: none; position: absolute; inset: 0; z-index: 9; background: var(--bg);
|
|
291
|
+
flex-direction: column; overflow: hidden;
|
|
292
|
+
}
|
|
293
|
+
#admin.open { display: flex; }
|
|
294
|
+
#login { position:fixed; inset:0; z-index:50; display:flex; align-items:center; justify-content:center; padding:16px; background:var(--bg); }
|
|
295
|
+
#login[hidden] { display:none; }
|
|
296
|
+
.login-card { width:min(22rem,100%); display:flex; flex-direction:column; gap:.6rem; padding:1.5rem; border:1px solid var(--line); border-radius:var(--r-md); background:var(--raise); }
|
|
297
|
+
.login-card h2 { margin:0 0 .5rem; }
|
|
298
|
+
#loginMsg:empty { display:none; }
|
|
299
|
+
.admin-users { display:flex; flex-direction:column; gap:.4rem; }
|
|
300
|
+
.admin-row { display:flex; flex-wrap:wrap; gap:.5rem .75rem; align-items:center; padding:.5rem 0; border-top:1px solid var(--line); font-size:var(--fs-meta); }
|
|
301
|
+
.admin-row:first-child { border-top:0; }
|
|
302
|
+
.admin-who { flex:1 1 14rem; min-width:0; display:flex; flex-direction:column; }
|
|
303
|
+
.admin-who strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
304
|
+
.admin-who .usage { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
305
|
+
.admin-row select { flex:0 0 auto; }
|
|
306
|
+
.admin-row .admin-err { flex-basis:100%; color:var(--err); }
|
|
307
|
+
.admin-row .admin-err:empty { display:none; }
|
|
308
|
+
#adminMsg:empty { display:none; }
|
|
309
|
+
.admin-note { margin-bottom:.75rem; }
|
|
310
|
+
.admin-chat { display:flex; gap:.75rem; align-items:center; width:100%; text-align:left; padding:.45rem .5rem; border:0; border-top:1px solid var(--line); background:none; color:inherit; font:inherit; cursor:pointer; }
|
|
311
|
+
.admin-chat:hover { background:var(--bg); }
|
|
312
|
+
.admin-chat .usage { margin-left:auto; white-space:nowrap; }
|
|
313
|
+
.admin-live { color:var(--accent, currentColor); font-weight:600; }
|
|
314
|
+
.admin-msg { padding:.6rem 0; border-top:1px solid var(--line); }
|
|
315
|
+
.admin-msg-role { font-family:var(--mono); font-size:var(--fs-meta); color:var(--muted); margin-bottom:.25rem; }
|
|
316
|
+
.admin-msg-body { overflow-wrap:anywhere; }
|
|
317
|
+
.admin-msg-body pre { white-space:pre-wrap; }
|
|
318
|
+
.admin-msg details { font-size:var(--fs-meta); }
|
|
319
|
+
.admin-msg details pre { max-height:20rem; overflow:auto; white-space:pre-wrap; }
|
|
320
|
+
.statistics-content { overflow:auto; padding:1rem; }
|
|
321
|
+
.statistics-block { width:min(52rem,100%); margin:0 auto 1.5rem; padding:1rem; border:1px solid var(--line); border-radius:var(--r-md); background:var(--raise); }
|
|
322
|
+
.statistics-block .sec-h { margin-bottom:.75rem; }
|
|
323
|
+
.statistics-block .sec-h .usage { margin-left:auto; text-transform:none; letter-spacing:normal; font-family:inherit; }
|
|
324
|
+
#statisticsCoverage { margin-top:.75rem; line-height:1.5; }
|
|
325
|
+
.statistics-costs { display:flex; flex-direction:column; gap:.4rem; margin-top:.75rem; padding-top:.75rem; border-top:1px solid var(--line); }
|
|
326
|
+
.statistics-cost-row { display:flex; gap:.75rem; align-items:center; font-family:var(--mono); font-size:var(--fs-meta); color:var(--muted); }
|
|
327
|
+
.statistics-cost-row .usage-model-name { flex:1 1 auto; }
|
|
328
|
+
.statistics-tool-row { display:flex; gap:.75rem; align-items:center; font-family:var(--mono); font-size:var(--fs-meta); color:var(--muted); }
|
|
329
|
+
.statistics-tool-name { flex:0 0 auto; min-width:11rem; max-width:16rem; white-space:nowrap; color:var(--fg); }
|
|
330
|
+
.statistics-tool-track { flex:1 1 auto; height:.5rem; border-radius:var(--r-xs); background:var(--faint); overflow:hidden; }
|
|
331
|
+
.statistics-tool-bar { display:block; height:100%; background:var(--accent); border-radius:var(--r-xs); }
|
|
332
|
+
.statistics-miss-hint { font-size:var(--fs-meta); color:var(--muted); margin:-.15rem 0 .35rem; }
|
|
333
|
+
.statistics-tool-count { flex:0 0 auto; width:4.5rem; text-align:right; font-variant-numeric:tabular-nums; }
|
|
334
|
+
.automation-head { height: var(--bar); flex: 0 0 auto; padding: 0 1rem; display:flex; align-items:center; gap:.75rem; border-bottom:1px solid var(--line); }
|
|
335
|
+
.automation-head h2 { margin:0; font-size:var(--fs-ui); }
|
|
336
|
+
.automation-head button { margin-left:auto; }
|
|
337
|
+
.automation-head button + button { margin-left:0; }
|
|
338
|
+
.automation-head button[hidden] + button { margin-left:auto; }
|
|
339
|
+
.automation-content { overflow:auto; padding:1rem; }
|
|
340
|
+
.automation-content { display:flex; flex-direction:column; gap:.75rem; }
|
|
341
|
+
.automation-list { display:flex; flex-direction:column; gap:.75rem; }
|
|
342
|
+
.automation-card {
|
|
343
|
+
width:min(48rem,100%); margin:0 auto; padding:1rem 1.125rem; border:1px solid var(--line);
|
|
344
|
+
border-radius:var(--r-md); background:var(--raise); display:flex; flex-direction:column; gap:.625rem;
|
|
345
|
+
}
|
|
346
|
+
.automation-card.is-paused .automation-title h3, .automation-card.is-paused .automation-preview { opacity:.6; }
|
|
347
|
+
.automation-card h3 { margin:0; font-size:var(--fs-ui); font-weight:600; line-height:1.35; }
|
|
348
|
+
.automation-card-head { display:flex; align-items:flex-start; gap:.75rem; }
|
|
349
|
+
.automation-title { flex:1; min-width:0; }
|
|
350
|
+
.automation-schedule-line { margin:.125rem 0 0; color:var(--muted); font-size:var(--fs-meta); }
|
|
351
|
+
.automation-facts {
|
|
352
|
+
display:flex; flex-wrap:wrap; align-items:center; gap:.375rem 1rem;
|
|
353
|
+
color:var(--muted); font-size:var(--fs-meta);
|
|
354
|
+
}
|
|
355
|
+
.automation-last { display:inline-flex; align-items:center; gap:.375rem; }
|
|
356
|
+
.automation-facts button.link {
|
|
357
|
+
min-height:0; padding:0; border:0; background:none; color:var(--accent); font-size:var(--fs-meta);
|
|
358
|
+
max-width:20rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
359
|
+
}
|
|
360
|
+
.automation-facts button.link:hover { text-decoration:underline; }
|
|
361
|
+
.automation-preview {
|
|
362
|
+
margin:0; padding:.5rem .75rem; border-radius:var(--r-sm); background:var(--panel);
|
|
363
|
+
color:var(--muted); font-size:var(--fs-meta); line-height:1.5; white-space:pre-line;
|
|
364
|
+
display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
|
|
365
|
+
}
|
|
366
|
+
.automation-actions { display:flex; align-items:center; flex-wrap:wrap; gap:.5rem; }
|
|
367
|
+
.automation-actions .grow { flex:1; }
|
|
368
|
+
.automation-actions .danger { color:var(--err); }
|
|
369
|
+
.automation-actions .danger:hover { border-color:var(--err); }
|
|
370
|
+
|
|
371
|
+
.automation-pill {
|
|
372
|
+
display:inline-block; padding:.0625rem .5rem; border-radius:var(--r-pill); font-size:var(--fs-meta);
|
|
373
|
+
line-height:1.5; border:1px solid var(--line); color:var(--muted); background:var(--panel);
|
|
374
|
+
}
|
|
375
|
+
.automation-pill.is-completed { color:var(--tool); border-color:color-mix(in srgb, var(--tool) 40%, transparent); background:color-mix(in srgb, var(--tool) 10%, transparent); }
|
|
376
|
+
.automation-pill.is-failed { color:var(--err); border-color:color-mix(in srgb, var(--err) 40%, transparent); background:color-mix(in srgb, var(--err) 10%, transparent); }
|
|
377
|
+
.automation-pill.is-running, .automation-pill.is-queued { color:var(--accent); border-color:color-mix(in srgb, var(--accent) 40%, transparent); background:var(--accent-soft); }
|
|
378
|
+
|
|
379
|
+
.automation-switch { position:relative; display:inline-flex; flex:0 0 auto; margin:.125rem 0 0; cursor:pointer; }
|
|
380
|
+
.automation-switch input { position:absolute; opacity:0; width:100%; height:100%; margin:0; cursor:pointer; }
|
|
381
|
+
.automation-switch .switch-track {
|
|
382
|
+
width:2.25rem; height:1.25rem; border-radius:var(--r-pill); background:var(--line); position:relative; transition:background var(--t-fast, .14s);
|
|
383
|
+
}
|
|
384
|
+
.automation-switch .switch-track::after {
|
|
385
|
+
content:''; position:absolute; top:.1875rem; left:.1875rem; width:.875rem; height:.875rem;
|
|
386
|
+
border-radius:50%; background:#fff; transition:transform var(--t-fast, .14s);
|
|
387
|
+
}
|
|
388
|
+
.automation-switch input:checked + .switch-track { background:var(--accent); }
|
|
389
|
+
.automation-switch input:checked + .switch-track::after { transform:translateX(1rem); }
|
|
390
|
+
.automation-switch input:focus-visible + .switch-track { box-shadow:0 0 0 3px var(--accent-soft); }
|
|
391
|
+
|
|
392
|
+
/* Inline create/edit form: one column, instructions full width. */
|
|
393
|
+
.automation-editor { display:flex; flex-direction:column; align-items:stretch; gap:.75rem; margin:0; max-width:none; }
|
|
394
|
+
.automation-card .automation-editor { border-top:1px solid var(--line); padding-top:.75rem; }
|
|
395
|
+
.automation-card > h3 + .automation-editor { border-top:0; padding-top:0; }
|
|
396
|
+
.automation-row { display:flex; flex-wrap:wrap; gap:.75rem; }
|
|
397
|
+
.automation-row > .automation-field { flex:1 1 10rem; }
|
|
398
|
+
.automation-field { display:flex; flex-direction:column; gap:.25rem; margin:0; min-width:0; }
|
|
399
|
+
.automation-field[hidden] { display:none; }
|
|
400
|
+
.automation-label { font-size:var(--fs-meta); color:var(--muted); }
|
|
401
|
+
.automation-editor input.mono { font-family:var(--mono); }
|
|
402
|
+
.automation-editor textarea {
|
|
403
|
+
min-height:20rem; resize:vertical; font-family:var(--mono); font-size:var(--fs-meta); line-height:1.6;
|
|
404
|
+
}
|
|
405
|
+
.automation-enabled-row { display:flex; align-items:center; gap:.375rem; margin:0; color:var(--fg); font-size:var(--fs-meta); cursor:pointer; }
|
|
406
|
+
.automation-enabled-row input { width:auto; min-height:0; }
|
|
407
|
+
.automation-error { color:var(--err); font-size:var(--fs-meta); }
|
|
408
|
+
.automation-error:empty { display:none; }
|
|
409
|
+
.automation-preview { cursor:pointer; }
|
|
410
|
+
.automation-preview.expanded { -webkit-line-clamp:unset; display:block; }
|
|
411
|
+
|
|
412
|
+
.automation-history { font-size:var(--fs-meta); color:var(--muted); }
|
|
413
|
+
.automation-history > summary { cursor:pointer; padding:.375rem .25rem; }
|
|
414
|
+
.automation-history[open] { flex-basis:100%; order:10; }
|
|
415
|
+
.automation-runs { display:flex; flex-direction:column; margin-top:.25rem; border:1px solid var(--line); border-radius:var(--r-sm); overflow:hidden; }
|
|
416
|
+
.automation-run + .automation-run { border-top:1px solid var(--line); }
|
|
417
|
+
.automation-run > summary {
|
|
418
|
+
display:flex; flex-wrap:wrap; align-items:center; gap:.375rem .75rem; padding:.375rem .625rem;
|
|
419
|
+
cursor:pointer; list-style:none;
|
|
420
|
+
}
|
|
421
|
+
.automation-run > summary::-webkit-details-marker { display:none; }
|
|
422
|
+
.automation-run > summary::after { content:'›'; margin-left:auto; color:var(--faint); transition:transform var(--t-fast, .14s); }
|
|
423
|
+
.automation-run[open] > summary::after { transform:rotate(90deg); }
|
|
424
|
+
.automation-run > summary:hover { background:var(--panel); }
|
|
425
|
+
.automation-run .muted { color:var(--faint); }
|
|
426
|
+
.automation-run-body {
|
|
427
|
+
padding:.75rem 1rem 1rem; border-top:1px dashed var(--line); background:var(--bg);
|
|
428
|
+
display:flex; flex-direction:column; gap:.625rem; align-items:flex-start;
|
|
429
|
+
}
|
|
430
|
+
.automation-run-output {
|
|
431
|
+
width:100%; max-height:32rem; overflow:auto; color:var(--fg); font-size:var(--fs-ui); line-height:1.55;
|
|
432
|
+
}
|
|
433
|
+
.automation-run-output table { font-size:var(--fs-meta); }
|
|
434
|
+
.automation-run-error { margin:0; color:var(--err); }
|
|
435
|
+
.automation-run-body button.link {
|
|
436
|
+
min-height:0; padding:0; border:0; background:none; color:var(--accent); font-size:var(--fs-meta);
|
|
437
|
+
}
|
|
438
|
+
.automation-run-body button.link:hover { text-decoration:underline; }
|
|
439
|
+
.automation-history > p { margin:.35rem .25rem; }
|
|
440
|
+
|
|
441
|
+
.automation-empty { width:min(48rem,100%); margin:2rem auto; text-align:center; }
|
|
442
|
+
.automation-empty p { margin:.25rem 0; }
|
|
443
|
+
.automation-empty .muted { color:var(--muted); font-size:var(--fs-meta); }
|
|
444
|
+
.sec {
|
|
445
|
+
display: flex; flex-direction: column; gap: .75rem;
|
|
446
|
+
padding: 1.125rem; max-width: 48rem; width: 100%; margin: 0 auto;
|
|
447
|
+
}
|
|
448
|
+
.sec + .sec { border-top: 1px solid var(--line); }
|
|
449
|
+
.sec:last-child { padding-bottom: 1.5rem; }
|
|
450
|
+
|
|
451
|
+
.sec-h {
|
|
452
|
+
font-size: var(--fs-ui); font-family: var(--mono); text-transform: uppercase;
|
|
453
|
+
letter-spacing: .08em; color: var(--fg); display: flex; gap: .5rem; align-items: baseline;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/* tool inspector */
|
|
457
|
+
.tool-list { display: flex; flex-direction: column; gap: .625rem; }
|
|
458
|
+
|
|
459
|
+
/* One group per source: the built-ins, and each MCP server. */
|
|
460
|
+
.tool-group {
|
|
461
|
+
border: 1px solid var(--line); border-radius: var(--r-md); background: var(--raise);
|
|
462
|
+
overflow: hidden;
|
|
463
|
+
}
|
|
464
|
+
.tool-group-h {
|
|
465
|
+
display: flex; align-items: center; gap: .5rem; padding: .4375rem .625rem;
|
|
466
|
+
min-height: var(--tap); background: var(--panel); border-bottom: 1px solid var(--line);
|
|
467
|
+
font-size: var(--fs-ui);
|
|
468
|
+
}
|
|
469
|
+
/* A group with nothing under it (built-ins with no doc, an empty server) has
|
|
470
|
+
no border to close, so the header's own bottom rule stands in for it. */
|
|
471
|
+
.tool-group:not(:has(details, .empty)) .tool-group-h { border-bottom: 0; }
|
|
472
|
+
.tool-group-h .name { font-family: var(--mono); color: var(--fg); font-weight: 500; }
|
|
473
|
+
.tool-group-h .meta { color: var(--muted); font-size: var(--fs-meta); margin-left: auto; }
|
|
474
|
+
.tool-group-err {
|
|
475
|
+
padding: .375rem .625rem .5rem 2rem; color: var(--err); font-size: var(--fs-meta);
|
|
476
|
+
font-family: var(--mono); white-space: pre-wrap; word-break: break-word;
|
|
477
|
+
}
|
|
478
|
+
/* The fix for a missing launcher, set apart from the error it follows. */
|
|
479
|
+
.install-link { color: var(--accent); font-family: var(--sans); font-weight: 550; white-space: nowrap; }
|
|
480
|
+
.install-link:hover { text-decoration: underline; }
|
|
481
|
+
/* The server's own top-level guidance from MCP initialize -- prose, not a tool
|
|
482
|
+
doc, so it reads like a note rather than another row in the list. Often
|
|
483
|
+
long, so it starts collapsed, same as an individual tool's doc. */
|
|
484
|
+
.tool-group-instructions { border-bottom: 1px solid var(--line); }
|
|
485
|
+
.tool-group-instructions > summary {
|
|
486
|
+
cursor: pointer; list-style: none; padding: .375rem .625rem .375rem 2rem;
|
|
487
|
+
color: var(--muted); font-size: var(--fs-meta); font-style: italic;
|
|
488
|
+
}
|
|
489
|
+
.tool-group-instructions > .body {
|
|
490
|
+
padding: 0 .625rem .5rem 2rem; color: var(--muted); font-size: var(--fs-meta);
|
|
491
|
+
white-space: pre-wrap; word-break: break-word;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Connection health, read at a glance before the label is even parsed. */
|
|
495
|
+
.tool-group .dot { width: .5rem; height: .5rem; border-radius: 50%; flex-shrink: 0; background: var(--faint); }
|
|
496
|
+
.tool-group.server.ok .dot { background: var(--tool); }
|
|
497
|
+
.tool-group.server.error .dot { background: var(--err); }
|
|
498
|
+
.tool-group.server.disabled .dot,
|
|
499
|
+
.tool-group.server.disabled .name { color: var(--faint); opacity: .7; }
|
|
500
|
+
.tool-group.server.disabled .dot { background: var(--faint); }
|
|
501
|
+
|
|
502
|
+
/* The switch that disables a tool or a whole server. Plain, like every other
|
|
503
|
+
control here -- this is not the app's one place for a styled toggle. */
|
|
504
|
+
label.tgl { display: inline-flex; align-items: center; cursor: pointer; }
|
|
505
|
+
label.tgl input { margin: 0; cursor: pointer; }
|
|
506
|
+
|
|
507
|
+
details.tool + details.tool { border-top: 1px solid var(--line); }
|
|
508
|
+
details.tool > summary {
|
|
509
|
+
cursor: pointer; list-style: none; padding: .4375rem .625rem; display: flex;
|
|
510
|
+
align-items: baseline; gap: .5rem; font-size: var(--fs-ui); min-height: var(--tap);
|
|
511
|
+
}
|
|
512
|
+
details.tool > summary::-webkit-details-marker { display: none; }
|
|
513
|
+
details.tool > summary:hover { background: var(--panel); }
|
|
514
|
+
/* A disabled tool stays fully legible -- dimming it away would make the one
|
|
515
|
+
state you are here to check on the hardest one to read. */
|
|
516
|
+
details.tool.off { background: var(--panel); }
|
|
517
|
+
details.tool.off .name, details.tool.off .desc { opacity: .6; }
|
|
518
|
+
.tool .name { font-family: var(--mono); color: var(--tool); }
|
|
519
|
+
.tool .desc {
|
|
520
|
+
color: var(--muted); font-size: var(--fs-meta); flex: 1; min-width: 0;
|
|
521
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
522
|
+
}
|
|
523
|
+
.tool .doc {
|
|
524
|
+
padding: .625rem .75rem; border-top: 1px solid var(--line); background: var(--panel);
|
|
525
|
+
font-size: var(--fs-ui); color: var(--muted); white-space: pre-wrap;
|
|
526
|
+
}
|
|
527
|
+
.tool .doc pre {
|
|
528
|
+
margin: .5rem 0 0; font: var(--fs-meta)/1.55 var(--mono);
|
|
529
|
+
white-space: pre-wrap; word-break: break-word; max-height: 15rem; overflow: auto;
|
|
530
|
+
}
|
|
531
|
+
.tool .params { margin-top: .5rem; }
|
|
532
|
+
.tool .param {
|
|
533
|
+
display: flex; gap: .5rem; font-family: var(--mono);
|
|
534
|
+
font-size: var(--fs-meta); padding: .125rem 0;
|
|
535
|
+
}
|
|
536
|
+
.tool .param b { color: var(--fg); font-weight: 500; }
|
|
537
|
+
.tool .param .ty { color: var(--faint); }
|
|
538
|
+
.tool .param .req { color: var(--err); }
|
|
539
|
+
.tool .param .d { color: var(--muted); flex: 1; min-width: 0; }
|
|
540
|
+
.row { display: flex; gap: .75rem; flex-wrap: wrap; align-items: flex-end; }
|
|
541
|
+
.row > * { flex: 1 1 11rem; }
|
|
542
|
+
|
|
543
|
+
.usage-stat-value {
|
|
544
|
+
font-family: var(--mono); font-size: var(--fs-ui); color: var(--fg);
|
|
545
|
+
font-variant-numeric: tabular-nums;
|
|
546
|
+
}
|
|
547
|
+
.usage-stat-sub { font-size: var(--fs-small, .8em); color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
548
|
+
#usageRange a { color: var(--muted); text-decoration: none; }
|
|
549
|
+
#usageRange a:hover, #usageRange a:focus-visible { color: var(--accent); text-decoration: underline; }
|
|
550
|
+
.usage-models {
|
|
551
|
+
display: flex; flex-direction: column; gap: .375rem; margin-top: .75rem;
|
|
552
|
+
padding-top: .75rem; border-top: 1px solid var(--line);
|
|
553
|
+
}
|
|
554
|
+
.usage-model-row {
|
|
555
|
+
display: flex; align-items: center; gap: .625rem;
|
|
556
|
+
font-family: var(--mono); font-size: var(--fs-meta); color: var(--muted);
|
|
557
|
+
}
|
|
558
|
+
.usage-model-name {
|
|
559
|
+
flex: 0 0 11rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
560
|
+
color: var(--fg);
|
|
561
|
+
}
|
|
562
|
+
.usage-model-track {
|
|
563
|
+
flex: 1 1 auto; height: .5rem; background: var(--panel);
|
|
564
|
+
border: 1px solid var(--line); border-radius: var(--r-xs); overflow: hidden;
|
|
565
|
+
}
|
|
566
|
+
.usage-model-bar { display: flex; height: 100%; width: 100%; }
|
|
567
|
+
.usage-model-seg { display: block; height: 100%; }
|
|
568
|
+
.usage-model-seg.fresh { background: var(--accent); }
|
|
569
|
+
.usage-model-seg.cached { background: var(--tool); }
|
|
570
|
+
.usage-model-seg.out { background: var(--faint); }
|
|
571
|
+
.usage-model-counts { flex: 0 0 auto; text-align: right; font-variant-numeric: tabular-nums; }
|
|
572
|
+
.usage-model-legend {
|
|
573
|
+
display: flex; gap: .875rem; margin-top: .125rem;
|
|
574
|
+
font-family: var(--mono); font-size: var(--fs-meta); color: var(--muted);
|
|
575
|
+
}
|
|
576
|
+
.usage-model-legend-item { display: flex; align-items: center; gap: .3125rem; }
|
|
577
|
+
.usage-model-swatch { width: .625rem; height: .625rem; border-radius: var(--r-xs); }
|
|
578
|
+
.usage-model-swatch.fresh { background: var(--accent); }
|
|
579
|
+
.usage-model-swatch.cached { background: var(--tool); }
|
|
580
|
+
.usage-model-swatch.out { background: var(--faint); }
|
|
581
|
+
.usage-bars { display: flex; flex-direction: column; gap: .25rem; margin-top: .625rem; }
|
|
582
|
+
.usage-bar-row {
|
|
583
|
+
display: flex; align-items: center; gap: .625rem;
|
|
584
|
+
font-family: var(--mono); font-size: var(--fs-meta); color: var(--muted);
|
|
585
|
+
}
|
|
586
|
+
button.usage-bar-row {
|
|
587
|
+
width: 100%; min-height: 0; padding: 0; border: 0; background: none; text-align: inherit;
|
|
588
|
+
}
|
|
589
|
+
.usage-bar-row.clickable { cursor: pointer; border-radius: var(--r-xs); }
|
|
590
|
+
.usage-bar-row.clickable:hover { background: var(--panel); }
|
|
591
|
+
.usage-bar-label { flex: 0 0 4rem; text-align: right; }
|
|
592
|
+
.usage-bar-track {
|
|
593
|
+
flex: 1 1 auto; height: .5rem; background: var(--panel);
|
|
594
|
+
border: 1px solid var(--line); border-radius: var(--r-xs); overflow: hidden;
|
|
595
|
+
}
|
|
596
|
+
.usage-bar-fill { display: flex; height: 100%; background: var(--accent); }
|
|
597
|
+
.usage-bar-seg { display: block; height: 100%; }
|
|
598
|
+
.usage-bar-count {
|
|
599
|
+
flex: 0 0 auto; min-width: 3.5rem; text-align: right;
|
|
600
|
+
font-variant-numeric: tabular-nums; color: var(--fg);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
#log { flex: 1; min-height: 0; overflow-y: auto; padding: 1.25rem .875rem .5rem; }
|
|
604
|
+
.wrap {
|
|
605
|
+
max-width: var(--measure); margin: 0 auto;
|
|
606
|
+
display: flex; flex-direction: column; gap: 1rem;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.who {
|
|
610
|
+
font-size: var(--fs-meta); text-transform: uppercase; letter-spacing: .08em;
|
|
611
|
+
color: var(--muted); margin-bottom: .25rem;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* Three layers, visually distinct:
|
|
615
|
+
1. the question -- solid accent bar, full contrast
|
|
616
|
+
2. the work -- indented rail, dimmed, mono
|
|
617
|
+
3. the final answer -- no rail, widest measure, normal prose */
|
|
618
|
+
|
|
619
|
+
.msg.user { padding-top: .25rem; }
|
|
620
|
+
.msg.user + * { margin-top: .125rem; }
|
|
621
|
+
/* The question is the one tinted block in the transcript, so a long chat
|
|
622
|
+
scans as question / answer / question without reading a word. */
|
|
623
|
+
.msg.user .body {
|
|
624
|
+
white-space: pre-wrap; background: var(--accent-soft);
|
|
625
|
+
border-left: 3px solid var(--accent); border-radius: var(--r-md);
|
|
626
|
+
padding: .625rem .875rem; font-weight: 500;
|
|
627
|
+
}
|
|
628
|
+
.msg.user .who { display: none; }
|
|
629
|
+
/* A hairline above every question but the first, so turns are countable. */
|
|
630
|
+
.msg.user:not(:first-child)::before {
|
|
631
|
+
content: ""; display: block; border-top: 1px solid var(--line);
|
|
632
|
+
margin: .5rem 0 1.125rem;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* A chat working in the background. The dot is the only thing that says a turn
|
|
636
|
+
outlived the tab that started it, so it stays visible rather than subtle. */
|
|
637
|
+
.chat-item .dot {
|
|
638
|
+
width: .4375rem; height: .4375rem; flex: none; border-radius: 50%;
|
|
639
|
+
background: var(--tool); margin-left: .375rem;
|
|
640
|
+
animation: pulse 1.6s ease-in-out infinite;
|
|
641
|
+
}
|
|
642
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
|
|
643
|
+
@media (prefers-reduced-motion: reduce) { .chat-item .dot { animation: none; } }
|
|
644
|
+
|
|
645
|
+
/* Send doubles as stop while a turn runs. */
|
|
646
|
+
#send.stop { background: none; color: var(--err); border-color: var(--err); }
|
|
647
|
+
|
|
648
|
+
/* Controls on a question. Kept out of the way until the question is hovered
|
|
649
|
+
or focused, so the transcript still reads as a transcript. */
|
|
650
|
+
.msg-tools {
|
|
651
|
+
display: flex; gap: .75rem; margin-top: .375rem; padding-left: .75rem;
|
|
652
|
+
opacity: 0; transition: opacity .12s;
|
|
653
|
+
}
|
|
654
|
+
.msg.user:hover .msg-tools,
|
|
655
|
+
.msg.user:focus-within .msg-tools { opacity: 1; }
|
|
656
|
+
@media (hover: none) { .msg-tools { opacity: 1; } }
|
|
657
|
+
|
|
658
|
+
button.linkish {
|
|
659
|
+
border: 0; background: none; padding: 0; min-height: 0;
|
|
660
|
+
color: var(--muted); font: var(--fs-meta) var(--mono);
|
|
661
|
+
text-transform: uppercase; letter-spacing: .06em; cursor: pointer;
|
|
662
|
+
}
|
|
663
|
+
button.linkish:hover { color: var(--fg); border-color: transparent; }
|
|
664
|
+
|
|
665
|
+
.edit-box { margin-top: .5rem; padding-left: .75rem; }
|
|
666
|
+
.edit-box textarea { width: 100%; font-size: var(--fs-body); }
|
|
667
|
+
.edit-actions { display: flex; gap: .5rem; align-items: center; margin-top: .5rem; }
|
|
668
|
+
/* The original text is hidden, not removed: cancel has to put it back. */
|
|
669
|
+
.msg.user.editing .body { display: none; }
|
|
670
|
+
|
|
671
|
+
.turn { display: flex; flex-direction: column; gap: 1rem; }
|
|
672
|
+
|
|
673
|
+
/* The work is a disclosure: open and naming its current step while the turn
|
|
674
|
+
runs, collapsed to a recap once there is an answer to read instead. */
|
|
675
|
+
details.work > summary {
|
|
676
|
+
cursor: pointer; list-style: none;
|
|
677
|
+
display: flex; align-items: center; gap: .5rem;
|
|
678
|
+
min-height: 1.9rem;
|
|
679
|
+
color: var(--faint); font: var(--fs-meta)/1 var(--mono);
|
|
680
|
+
text-transform: uppercase; letter-spacing: .06em;
|
|
681
|
+
}
|
|
682
|
+
details.work > summary::-webkit-details-marker { display: none; }
|
|
683
|
+
details.work > summary::before { content: "+ "; white-space: pre; }
|
|
684
|
+
details.work[open] > summary::before { content: "- "; white-space: pre; }
|
|
685
|
+
details.work > summary:hover { color: var(--fg); }
|
|
686
|
+
.work .act { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
687
|
+
.work .tally { margin-left: auto; color: var(--faint); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
|
|
688
|
+
/* In progress: the step name is the live status line, so it carries the accent
|
|
689
|
+
and the ellipsis that say this is still moving. */
|
|
690
|
+
.work.live .act { color: var(--tool); text-transform: none; letter-spacing: 0; }
|
|
691
|
+
.work.live .act::after { content: "\2026"; }
|
|
692
|
+
|
|
693
|
+
.work-body {
|
|
694
|
+
display: flex; flex-direction: column; gap: .375rem;
|
|
695
|
+
margin-left: 1px; padding: .2rem 0 .2rem .75rem;
|
|
696
|
+
border-left: 1px dashed var(--line);
|
|
697
|
+
}
|
|
698
|
+
.work-body .steps { background: none; }
|
|
699
|
+
|
|
700
|
+
/* The answer is the only block with no left rule -- that absence is what marks
|
|
701
|
+
it as the result rather than another step. */
|
|
702
|
+
.msg.assistant .md { padding-left: 0; }
|
|
703
|
+
/* Narration that turned out not to be the answer: same words, quieter, and
|
|
704
|
+
filed under the work where it belongs. */
|
|
705
|
+
.msg.assistant.note .md { color: var(--faint); font-size: var(--fs-meta); }
|
|
706
|
+
.msg.assistant.final .who { color: var(--accent); }
|
|
707
|
+
|
|
708
|
+
/* rendered markdown */
|
|
709
|
+
.md > *:first-child { margin-top: 0; }
|
|
710
|
+
.md > *:last-child { margin-bottom: 0; }
|
|
711
|
+
.md p { margin: 0 0 .7em; }
|
|
712
|
+
.md h1, .md h2, .md h3, .md h4 { margin: 1.1em 0 .4em; line-height: 1.3; font-weight: 600; font-size: 1em; }
|
|
713
|
+
.md h1 { font-size: 1.15em; } .md h2 { font-size: 1.08em; }
|
|
714
|
+
.md ul, .md ol { margin: 0 0 .7em; padding-left: 1.3em; }
|
|
715
|
+
.md li { margin: .15em 0; }
|
|
716
|
+
.md li > ul, .md li > ol { margin-bottom: 0; }
|
|
717
|
+
.md a { color: var(--accent); }
|
|
718
|
+
.md code { background: var(--panel); border-radius: var(--r-xs); padding: .1em .3em; font: .9em var(--mono); }
|
|
719
|
+
.md pre {
|
|
720
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-sm);
|
|
721
|
+
padding: .625rem .75rem; overflow-x: auto; margin: 0 0 .7em;
|
|
722
|
+
}
|
|
723
|
+
.md pre code { background: none; padding: 0; font-size: .9em; line-height: 1.55; }
|
|
724
|
+
.md blockquote { margin: 0 0 .7em; padding-left: .8em; border-left: 2px solid var(--line); color: var(--muted); }
|
|
725
|
+
.md table { border-collapse: collapse; margin: 0 0 .7em; font-size: .95em; display: block; overflow-x: auto; }
|
|
726
|
+
.md th, .md td { border: 1px solid var(--line); padding: .25rem .5rem; text-align: left; }
|
|
727
|
+
.md th { background: var(--panel); font-weight: 600; }
|
|
728
|
+
.md hr { border: 0; border-top: 1px solid var(--line); margin: 1em 0; }
|
|
729
|
+
|
|
730
|
+
/* thinking */
|
|
731
|
+
details.think > summary,
|
|
732
|
+
details.step > summary { cursor: pointer; list-style: none; }
|
|
733
|
+
details.think > summary::-webkit-details-marker,
|
|
734
|
+
details.step > summary::-webkit-details-marker { display: none; }
|
|
735
|
+
details.think > summary {
|
|
736
|
+
color: var(--faint); font-size: var(--fs-meta); font-family: var(--mono);
|
|
737
|
+
text-transform: uppercase; letter-spacing: .06em;
|
|
738
|
+
min-height: 1.9rem; display: flex; align-items: center;
|
|
739
|
+
}
|
|
740
|
+
details.think > summary:hover { color: var(--muted); }
|
|
741
|
+
details.think > summary::before { content: "+ "; white-space: pre; }
|
|
742
|
+
details.think[open] > summary::before { content: "- "; white-space: pre; }
|
|
743
|
+
.think-body {
|
|
744
|
+
margin-top: .2rem; padding-left: .75rem; border-left: 1px solid var(--line);
|
|
745
|
+
color: var(--faint); white-space: pre-wrap; font-size: var(--fs-meta); line-height: 1.5;
|
|
746
|
+
max-height: 14rem; overflow-y: auto;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/* tool steps -- deliberately quieter and denser than the answer: this is the
|
|
750
|
+
model's scratch work, not the result, and shouldn't compete with it. */
|
|
751
|
+
.steps { border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
|
|
752
|
+
details.step + details.step { border-top: 1px solid var(--line); }
|
|
753
|
+
details.step > summary {
|
|
754
|
+
padding: .3rem .625rem; display: flex; align-items: baseline; gap: .5rem;
|
|
755
|
+
font-size: var(--fs-meta); font-family: var(--mono); min-height: 1.9rem;
|
|
756
|
+
}
|
|
757
|
+
details.step > summary:hover { background: var(--panel); }
|
|
758
|
+
.step .name { color: var(--tool); }
|
|
759
|
+
.step.bad .name { color: var(--err); }
|
|
760
|
+
.step .arg { color: var(--faint); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
761
|
+
.step .ms { color: var(--faint); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
|
|
762
|
+
.step pre {
|
|
763
|
+
margin: 0; padding: .45rem .625rem; background: var(--panel); border-top: 1px solid var(--line);
|
|
764
|
+
font: var(--fs-meta)/1.45 var(--mono); color: var(--faint);
|
|
765
|
+
white-space: pre-wrap; word-break: break-word; max-height: 14rem; overflow: auto;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.usage { font-size: var(--fs-meta); color: var(--muted); font-family: var(--mono); }
|
|
769
|
+
/* The whole turn's cost, under the answer rather than inside the collapsed
|
|
770
|
+
work: a rule above it separates it from the answer it is reporting on. */
|
|
771
|
+
.usage-total {
|
|
772
|
+
margin-top: .5rem;
|
|
773
|
+
padding-top: .375rem;
|
|
774
|
+
border-top: 1px solid var(--line);
|
|
775
|
+
}
|
|
776
|
+
/* A compacted tool result: the model was sent a short stub, but the transcript
|
|
777
|
+
still shows the full output, so the badge is the only visible difference. */
|
|
778
|
+
.step.compacted .name::after {
|
|
779
|
+
content: 'compacted';
|
|
780
|
+
margin-left: .375rem;
|
|
781
|
+
padding: 0 .25rem;
|
|
782
|
+
border: 1px solid var(--line);
|
|
783
|
+
border-radius: var(--r-xs);
|
|
784
|
+
color: var(--muted);
|
|
785
|
+
font-size: var(--fs-meta);
|
|
786
|
+
}
|
|
787
|
+
.work-body > .usage { margin: .125rem 0 .375rem; }
|
|
788
|
+
|
|
789
|
+
/* An automation run opens its turn like a question does, but nobody asked
|
|
790
|
+
it: a quiet ruled label instead of the tinted user block. */
|
|
791
|
+
.msg.automation-run {
|
|
792
|
+
font-size: var(--fs-ui); color: var(--muted);
|
|
793
|
+
border-left: 3px solid var(--tool); padding: .25rem 0 .25rem .875rem;
|
|
794
|
+
}
|
|
795
|
+
.msg.automation-run:not(:first-child) { margin-top: 1.125rem; }
|
|
796
|
+
.automation-run-head { display: flex; flex-wrap: wrap; gap: .25rem .75rem; align-items: baseline; }
|
|
797
|
+
.automation-run-name { color: var(--fg); font-weight: 600; }
|
|
798
|
+
.automation-run-name::before { content: "⏱ "; }
|
|
799
|
+
.automation-run-meta { font-family: var(--mono); font-size: var(--fs-meta); }
|
|
800
|
+
.automation-run-prompt summary { cursor: pointer; margin-top: .25rem; font-size: var(--fs-meta); }
|
|
801
|
+
.automation-run-prompt .body { white-space: pre-wrap; margin-top: .375rem; color: var(--fg); }
|
|
802
|
+
|
|
803
|
+
.err { color: var(--err); font-size: var(--fs-ui); font-family: var(--mono); }
|
|
804
|
+
.notice {
|
|
805
|
+
color: var(--muted); font-size: var(--fs-ui); font-family: var(--mono);
|
|
806
|
+
border-left: 2px solid var(--err); padding-left: .75rem;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/* The composer: one raised card holding attach, input and send, rather than
|
|
810
|
+
three loose controls on a ruled strip. The page's one shadow lives here. */
|
|
811
|
+
footer { padding: .5rem .875rem 1rem; flex-shrink: 0; }
|
|
812
|
+
form {
|
|
813
|
+
max-width: var(--measure); margin: 0 auto;
|
|
814
|
+
display: flex; gap: .5rem; align-items: flex-end;
|
|
815
|
+
}
|
|
816
|
+
#form {
|
|
817
|
+
background: var(--raise); border: 1px solid var(--line); border-radius: var(--r-lg);
|
|
818
|
+
padding: .4375rem; box-shadow: 0 6px 24px -12px color-mix(in srgb, var(--fg) 35%, transparent);
|
|
819
|
+
transition: border-color .12s, box-shadow .12s;
|
|
820
|
+
}
|
|
821
|
+
#form:focus-within { border-color: var(--accent); }
|
|
822
|
+
#input {
|
|
823
|
+
max-height: 13rem; font-size: var(--fs-body); resize: none;
|
|
824
|
+
border: 0; background: none; box-shadow: none; padding: .4375rem .25rem;
|
|
825
|
+
}
|
|
826
|
+
#input:focus { box-shadow: none; }
|
|
827
|
+
#send { min-width: 4.5rem; border-radius: var(--r-md); }
|
|
828
|
+
#attach {
|
|
829
|
+
flex: 0 0 auto; min-width: var(--tap); padding: 0; border-radius: var(--r-md);
|
|
830
|
+
background: none; border: 0; color: var(--muted);
|
|
831
|
+
}
|
|
832
|
+
#attach:hover { background: var(--panel); color: var(--fg); }
|
|
833
|
+
.attachments {
|
|
834
|
+
max-width: var(--measure); margin: 0 auto .5rem; display: flex; flex-wrap: wrap; gap: .4rem;
|
|
835
|
+
}
|
|
836
|
+
.attachments:empty, .attachments[hidden] { display: none; }
|
|
837
|
+
.attachment-chip {
|
|
838
|
+
display: inline-flex; align-items: center; gap: .35rem;
|
|
839
|
+
border: 1px solid var(--line); border-radius: var(--r-pill); padding: .2rem .65rem;
|
|
840
|
+
font-size: var(--fs-small); background: var(--bg-soft, transparent);
|
|
841
|
+
}
|
|
842
|
+
.attachment-chip .remove { cursor: pointer; opacity: .6; }
|
|
843
|
+
.attachment-chip .remove:hover { opacity: 1; }
|
|
844
|
+
.attachment-chip-image { padding: .2rem; border-radius: var(--r-sm); }
|
|
845
|
+
/* Input queued on a running turn, listed above the text box until delivered. */
|
|
846
|
+
.queue { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .4rem; }
|
|
847
|
+
.queue[hidden] { display: none; }
|
|
848
|
+
.queued {
|
|
849
|
+
display: flex; align-items: center; gap: .5rem; min-width: 0;
|
|
850
|
+
border: 1px dashed var(--line); border-radius: var(--r-md); padding: .25rem .25rem .25rem .6rem;
|
|
851
|
+
font-size: var(--fs-small);
|
|
852
|
+
}
|
|
853
|
+
.queued-kind { flex: 0 0 auto; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; font-size: .7rem; }
|
|
854
|
+
.queued-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
855
|
+
.queued-drop {
|
|
856
|
+
flex: 0 0 auto; min-width: var(--tap); min-height: 0; padding: 0 .4rem;
|
|
857
|
+
background: none; border: 0; color: var(--muted); font-size: 1rem; cursor: pointer;
|
|
858
|
+
}
|
|
859
|
+
.queued-drop:hover { color: var(--fg); }
|
|
860
|
+
.queued.held { border-style: solid; }
|
|
861
|
+
.queued-send {
|
|
862
|
+
flex: 0 0 auto; min-height: 0; padding: .15rem .6rem; border-radius: var(--r-md);
|
|
863
|
+
background: var(--fg); color: var(--bg); border: 0; font-size: var(--fs-small); cursor: pointer;
|
|
864
|
+
}
|
|
865
|
+
.queued-send:hover { filter: brightness(1.08); }
|
|
866
|
+
.attachment-thumb {
|
|
867
|
+
width: 2.5rem; height: 2.5rem; border-radius: var(--r-sm); object-fit: cover;
|
|
868
|
+
background: var(--bg-soft, transparent);
|
|
869
|
+
}
|
|
870
|
+
.attachment-thumb.pending {
|
|
871
|
+
animation: attachment-pulse 1.1s ease-in-out infinite;
|
|
872
|
+
}
|
|
873
|
+
.attachment-thumb.broken {
|
|
874
|
+
opacity: .5; outline: 1px solid var(--danger, #c0392b);
|
|
875
|
+
}
|
|
876
|
+
@keyframes attachment-pulse {
|
|
877
|
+
0%, 100% { opacity: .35; } 50% { opacity: .7; }
|
|
878
|
+
}
|
|
879
|
+
.edit-artifacts { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .4rem; }
|
|
880
|
+
.attachment-chip.removable { cursor: pointer; user-select: none; }
|
|
881
|
+
.attachment-chip.removable::before { content: '✓'; opacity: .5; font-size: var(--fs-meta); }
|
|
882
|
+
.attachment-chip.removable.removed {
|
|
883
|
+
opacity: .45; text-decoration: line-through; border-style: dashed;
|
|
884
|
+
}
|
|
885
|
+
.attachment-chip.removable.removed::before { content: '✕'; }
|
|
886
|
+
.attachment-chip.removable.removed .attachment-thumb { filter: grayscale(1); }
|
|
887
|
+
.msg-attachments { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .4rem; }
|
|
888
|
+
.msg-images { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .4rem; }
|
|
889
|
+
.msg-image-thumb {
|
|
890
|
+
max-width: 12rem; max-height: 12rem; border-radius: var(--r-sm);
|
|
891
|
+
border: 1px solid var(--line); object-fit: contain;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/* Pinned to `main`, which never scrolls -- only #log's content does -- so this
|
|
895
|
+
stays in view the whole time the user is reading back through a chat,
|
|
896
|
+
instead of riding away with the transcript the way it used to. */
|
|
897
|
+
.right-rail {
|
|
898
|
+
position: absolute; top: 1.25rem; right: .875rem; width: 12rem;
|
|
899
|
+
display: flex; flex-direction: column; gap: .6rem; pointer-events: none;
|
|
900
|
+
}
|
|
901
|
+
.right-rail > * { pointer-events: auto; }
|
|
902
|
+
|
|
903
|
+
.rail-card {
|
|
904
|
+
border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel);
|
|
905
|
+
padding: .4rem;
|
|
906
|
+
display: flex; flex-direction: column; gap: .05rem;
|
|
907
|
+
}
|
|
908
|
+
.rail-card[hidden] { display: none; }
|
|
909
|
+
.rail-card .label {
|
|
910
|
+
font-size: var(--fs-meta); text-transform: uppercase; letter-spacing: .04em;
|
|
911
|
+
color: var(--faint); margin: 0 0 .2rem .35rem;
|
|
912
|
+
}
|
|
913
|
+
.rail-card .rail-item {
|
|
914
|
+
display: flex; align-items: center; gap: .4rem; width: 100%; text-align: left;
|
|
915
|
+
background: none; border: none; border-radius: var(--r-sm); padding: .3rem .35rem; margin: 0;
|
|
916
|
+
min-width: 0; min-height: 0; color: var(--muted); font-size: var(--fs-small);
|
|
917
|
+
font-family: inherit; line-height: 1.5; cursor: pointer; transition: background .1s, color .1s;
|
|
918
|
+
}
|
|
919
|
+
.rail-card .rail-item:hover { background: var(--bg); color: var(--fg); }
|
|
920
|
+
.rail-card .rail-item .icon {
|
|
921
|
+
flex-shrink: 0; width: 1em; height: 1em; display: flex; align-items: center;
|
|
922
|
+
justify-content: center; opacity: .6;
|
|
923
|
+
}
|
|
924
|
+
.rail-card .rail-item .icon svg { width: 100%; height: 100%; }
|
|
925
|
+
.rail-card .rail-item .text {
|
|
926
|
+
min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
|
927
|
+
}
|
|
928
|
+
.rail-card .rail-more {
|
|
929
|
+
align-self: stretch; text-align: left; background: none; border: none; border-radius: var(--r-sm);
|
|
930
|
+
padding: .3rem .35rem; margin: 0; min-height: 0; color: var(--faint); font-size: var(--fs-meta);
|
|
931
|
+
font-family: inherit; line-height: 1.5; cursor: pointer; transition: background .1s, color .1s;
|
|
932
|
+
}
|
|
933
|
+
.rail-card .rail-more:hover { background: var(--bg); color: var(--muted); }
|
|
934
|
+
|
|
935
|
+
@media (max-width: 1040px) {
|
|
936
|
+
/* No room beside the measure column at this width -- would either overlap
|
|
937
|
+
the transcript or force it to shrink, so it steps aside instead. */
|
|
938
|
+
.right-rail, .outline-rail { display: none; }
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/* The conversation minimap: one flat tick per question, vertically centered
|
|
942
|
+
like a scrollbar of turns rather than a list -- the text only exists on
|
|
943
|
+
hover, so at rest it reads as a mark, not a panel. */
|
|
944
|
+
.outline-rail {
|
|
945
|
+
position: absolute; top: 50%; right: .875rem; transform: translateY(-50%);
|
|
946
|
+
display: flex; flex-direction: column; align-items: flex-end; gap: .3rem;
|
|
947
|
+
}
|
|
948
|
+
.outline-rail[hidden] { display: none; }
|
|
949
|
+
.outline-tick {
|
|
950
|
+
position: relative; background: none; border: none; padding: .2rem 0; margin: 0;
|
|
951
|
+
cursor: pointer; display: flex; align-items: center; justify-content: flex-end;
|
|
952
|
+
}
|
|
953
|
+
.outline-tick .bar {
|
|
954
|
+
width: 1.125rem; height: 2px; background: var(--line); border-radius: var(--r-xs);
|
|
955
|
+
transition: background .1s, width .1s;
|
|
956
|
+
}
|
|
957
|
+
.outline-tick:hover .bar, .outline-tick:focus-visible .bar {
|
|
958
|
+
background: var(--accent); width: 1.5rem;
|
|
959
|
+
}
|
|
960
|
+
.outline-tick .tip {
|
|
961
|
+
position: absolute; right: 100%; top: 50%; transform: translateY(-50%);
|
|
962
|
+
margin-right: .5rem; border: 1px solid var(--line); border-radius: var(--r-sm);
|
|
963
|
+
background: var(--panel); color: var(--fg); font-size: var(--fs-small);
|
|
964
|
+
padding: .3rem .55rem; white-space: nowrap; max-width: 16rem;
|
|
965
|
+
overflow: hidden; text-overflow: ellipsis;
|
|
966
|
+
opacity: 0; pointer-events: none; transition: opacity .1s;
|
|
967
|
+
}
|
|
968
|
+
.outline-tick:hover .tip, .outline-tick:focus-visible .tip { opacity: 1; }
|
|
969
|
+
|
|
970
|
+
#side-backdrop { display: none; }
|
|
971
|
+
|
|
972
|
+
@media (max-width: 720px) {
|
|
973
|
+
/* The drawer slides out under the header rather than over it, so the menu
|
|
974
|
+
button that opened it -- and the settings button beside it -- stay in
|
|
975
|
+
place and tappable the whole time it is open, instead of vanishing
|
|
976
|
+
behind an overlay the moment it appears. */
|
|
977
|
+
#side {
|
|
978
|
+
position: fixed; top: var(--bar); bottom: 0; left: 0; width: min(85vw, 22rem);
|
|
979
|
+
z-index: 20; transform: translateX(-100%); transition: transform .18s ease;
|
|
980
|
+
}
|
|
981
|
+
#side.open { transform: none; }
|
|
982
|
+
/* A fixed width here rather than var(--side-w): the drag handle that sets
|
|
983
|
+
that variable is mouse-only and hidden on this path, and an overlay
|
|
984
|
+
drawer has no edge to drag against the content anyway. */
|
|
985
|
+
#side-resize { display: none; }
|
|
986
|
+
#menu { display: inline-flex; align-items: center; justify-content: center; }
|
|
987
|
+
|
|
988
|
+
/* Closing the drawer needs more ways in than hunting for the menu button
|
|
989
|
+
again: tapping the dimmed content behind it, or Escape, both close it
|
|
990
|
+
(wired in app.js). */
|
|
991
|
+
#side-backdrop {
|
|
992
|
+
display: block; position: fixed; top: var(--bar); right: 0; bottom: 0; left: 0;
|
|
993
|
+
z-index: 19; background: rgb(0 0 0 / 40%);
|
|
994
|
+
opacity: 0; pointer-events: none; transition: opacity .18s ease;
|
|
995
|
+
}
|
|
996
|
+
#side-backdrop.open { opacity: 1; pointer-events: auto; }
|
|
997
|
+
|
|
998
|
+
/* iOS zooms the whole page in on focusing any input under 16px -- these are
|
|
999
|
+
all a notch smaller than that on the desktop type scale. */
|
|
1000
|
+
input, textarea, select { font-size: 16px; }
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/* A tool call held for approval: the row stays open with its arguments and
|
|
1004
|
+
the three answers under them until the server settles it. */
|
|
1005
|
+
details.step.waiting > summary .ms { color: var(--tool); }
|
|
1006
|
+
.step .approve {
|
|
1007
|
+
display: flex; flex-wrap: wrap; gap: .4rem;
|
|
1008
|
+
padding: .5rem .6rem; border-top: 1px solid var(--line);
|
|
1009
|
+
}
|
|
1010
|
+
.step .approve button { font-size: var(--fs-meta); }
|
|
1011
|
+
details.tool > summary select.approval {
|
|
1012
|
+
margin-left: auto; flex-shrink: 0;
|
|
1013
|
+
font-size: var(--fs-meta); max-width: 11rem;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/* ---- motion and softness ----
|
|
1017
|
+
Motion only answers something the user did: a panel opening, a disclosure
|
|
1018
|
+
expanding, a press. Nothing moves on its own, and all of it is switched off
|
|
1019
|
+
under reduced motion at the bottom of this block. */
|
|
1020
|
+
:root { --ease: cubic-bezier(.2, .7, .2, 1); --t-fast: .14s; --t-med: .22s; }
|
|
1021
|
+
|
|
1022
|
+
/* Full-column panels slide up and fade in instead of popping. They stay in
|
|
1023
|
+
layout (display:flex) and are hidden with visibility, which is what lets
|
|
1024
|
+
the transition run in both directions. */
|
|
1025
|
+
#settings, #automations, #statistics, #admin {
|
|
1026
|
+
display: flex; visibility: hidden; opacity: 0; transform: translateY(8px);
|
|
1027
|
+
pointer-events: none;
|
|
1028
|
+
transition: opacity var(--t-med) var(--ease), transform var(--t-med) var(--ease),
|
|
1029
|
+
visibility 0s linear var(--t-med);
|
|
1030
|
+
}
|
|
1031
|
+
#settings.open, #automations.open, #statistics.open, #admin.open {
|
|
1032
|
+
visibility: visible; opacity: 1; transform: none; pointer-events: auto;
|
|
1033
|
+
transition: opacity var(--t-med) var(--ease), transform var(--t-med) var(--ease), visibility 0s;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/* Disclosures (work, thinking, tool steps, folders) ease their content in. */
|
|
1037
|
+
details[open] > :not(summary) { animation: reveal var(--t-med) var(--ease); }
|
|
1038
|
+
@keyframes reveal { from { opacity: 0; transform: translateY(-3px); } }
|
|
1039
|
+
details.work > summary::before, details.think > summary::before { transition: opacity var(--t-fast); }
|
|
1040
|
+
|
|
1041
|
+
/* Dialogs rise in over a fading backdrop. */
|
|
1042
|
+
dialog[open] { animation: dialog-in var(--t-med) var(--ease); }
|
|
1043
|
+
dialog[open]::backdrop { animation: fade var(--t-med) ease; }
|
|
1044
|
+
@keyframes dialog-in { from { opacity: 0; transform: translateY(6px) scale(.985); } }
|
|
1045
|
+
@keyframes fade { from { opacity: 0; } }
|
|
1046
|
+
dialog { border-radius: var(--r-lg); box-shadow: 0 24px 60px -20px rgb(0 0 0 / 45%); }
|
|
1047
|
+
|
|
1048
|
+
/* A press is felt, not just seen. */
|
|
1049
|
+
button:active:not(:disabled) { transform: scale(.97); }
|
|
1050
|
+
button { transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast), transform .08s; }
|
|
1051
|
+
.chat-item, details.step > summary, details.tool > summary, .rail-item {
|
|
1052
|
+
transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
|
|
1053
|
+
}
|
|
1054
|
+
#side { transition: transform var(--t-med) var(--ease); }
|
|
1055
|
+
|
|
1056
|
+
/* Softer structure: dividers recede to half strength so the content, not the
|
|
1057
|
+
grid, carries the page. */
|
|
1058
|
+
header, #side .head, .search-row, .automation-head, .sec + .sec {
|
|
1059
|
+
border-color: color-mix(in srgb, var(--line) 55%, transparent);
|
|
1060
|
+
}
|
|
1061
|
+
header { background: color-mix(in srgb, var(--bg) 85%, transparent); backdrop-filter: blur(8px); }
|
|
1062
|
+
|
|
1063
|
+
/* Thin, quiet scrollbars everywhere, not only in the chat list. */
|
|
1064
|
+
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--faint) 45%, transparent) transparent; }
|
|
1065
|
+
::-webkit-scrollbar { width: .5rem; height: .5rem; }
|
|
1066
|
+
::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--faint) 45%, transparent); border-radius: var(--r-pill); }
|
|
1067
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
1068
|
+
|
|
1069
|
+
/* The answer area fades out under the composer instead of being cut off. */
|
|
1070
|
+
footer { position: relative; }
|
|
1071
|
+
footer::before {
|
|
1072
|
+
content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 1.5rem;
|
|
1073
|
+
background: linear-gradient(to bottom, transparent, var(--bg)); pointer-events: none;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
.md pre, .md code { border-radius: var(--r-sm); }
|
|
1077
|
+
.md pre { border-radius: var(--r-md); }
|
|
1078
|
+
|
|
1079
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1080
|
+
*, *::before, *::after, ::backdrop {
|
|
1081
|
+
animation-duration: .01ms !important; transition-duration: .01ms !important;
|
|
1082
|
+
}
|
|
1083
|
+
button:active:not(:disabled) { transform: none; }
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/* Sidebar actions: "New chat" is the main action and takes the room; the
|
|
1087
|
+
folder button sizes to its label. Neither may wrap. */
|
|
1088
|
+
#side .head button { white-space: nowrap; }
|
|
1089
|
+
#side .head #newFolder { flex: 0 0 auto; }
|
|
1090
|
+
|
|
1091
|
+
/* Composer row: attach, input and send share one height so a single-line
|
|
1092
|
+
message sits on one centre line; a growing message keeps them at the bottom. */
|
|
1093
|
+
#form { --row: 2.5rem; }
|
|
1094
|
+
#form > button { height: var(--row); min-height: var(--row); display: inline-flex; align-items: center; justify-content: center; }
|
|
1095
|
+
#input { min-height: var(--row); line-height: 1.5; padding: calc((var(--row) - 1.5em) / 2) .25rem; }
|
|
1096
|
+
#attach svg { display: block; }
|
|
1097
|
+
|
|
1098
|
+
/* ==== Layout v2: floating sidebar, no top bar, centred first message ====
|
|
1099
|
+
Neutral greys; the send button's ink is the only strong mark on the page. */
|
|
1100
|
+
:root {
|
|
1101
|
+
--bg: #ffffff; --panel: #f7f7f8; --line: #e7e7ea; --fg: #18181b;
|
|
1102
|
+
--muted: #5f5f68; --faint: #75757e; --accent: #18181b; --tool: #0f6e66; --err: #b3261e;
|
|
1103
|
+
--raise: #ffffff; --accent-soft: color-mix(in srgb, var(--fg) 6%, transparent);
|
|
1104
|
+
--focus: #3b6fe0;
|
|
1105
|
+
}
|
|
1106
|
+
@media (prefers-color-scheme: dark) {
|
|
1107
|
+
:root:not([data-theme="light"]) {
|
|
1108
|
+
--bg: #161618; --panel: #1e1e21; --line: #2e2e33; --fg: #ececef;
|
|
1109
|
+
--muted: #a6a6ae; --faint: #8c8c95; --accent: #ececef; --tool: #5fd4c4; --err: #f4a19a;
|
|
1110
|
+
--raise: #232327; --focus: #86a8ff;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
:focus-visible { outline-color: var(--focus); }
|
|
1114
|
+
input:focus, textarea:focus, select:focus { border-color: var(--focus); box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 18%, transparent); }
|
|
1115
|
+
button.primary { color: var(--bg); }
|
|
1116
|
+
|
|
1117
|
+
.ic { width: 1.125rem; height: 1.125rem; flex-shrink: 0; display: block; }
|
|
1118
|
+
.icon-btn {
|
|
1119
|
+
min-height: 0; width: 2rem; height: 2rem; padding: 0; border: 0; background: none;
|
|
1120
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1121
|
+
color: var(--muted); border-radius: var(--r-sm);
|
|
1122
|
+
}
|
|
1123
|
+
.icon-btn:hover { background: var(--accent-soft); color: var(--fg); }
|
|
1124
|
+
|
|
1125
|
+
/* Sidebar: a floating card inset from the window edge. */
|
|
1126
|
+
#shell { padding: .5rem; gap: .5rem; }
|
|
1127
|
+
#side {
|
|
1128
|
+
border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--panel);
|
|
1129
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 4%), 0 8px 24px -16px rgb(0 0 0 / 18%);
|
|
1130
|
+
padding: .5rem; overflow: hidden;
|
|
1131
|
+
}
|
|
1132
|
+
.brand { display: flex; align-items: center; gap: .5rem; padding: .25rem .25rem .5rem; }
|
|
1133
|
+
.logo { width: 2rem; height: 2rem; display: block; flex: none; }
|
|
1134
|
+
.brand-name { flex: 1; font-weight: 650; font-size: var(--fs-ui); letter-spacing: -.01em; }
|
|
1135
|
+
|
|
1136
|
+
.nav { display: flex; flex-direction: column; gap: 1px; }
|
|
1137
|
+
.nav-item {
|
|
1138
|
+
display: flex; align-items: center; gap: .625rem; width: 100%;
|
|
1139
|
+
min-height: 2.25rem; padding: .375rem .5rem; border: 0; border-radius: var(--r-sm);
|
|
1140
|
+
background: none; color: var(--fg); font-size: var(--fs-ui); font-weight: 500; text-align: left;
|
|
1141
|
+
}
|
|
1142
|
+
.nav-item:hover { background: var(--accent-soft); }
|
|
1143
|
+
.nav-item.active { background: var(--accent-soft); color: var(--fg); }
|
|
1144
|
+
.nav-item .ic { color: var(--muted); stroke-width: 1.7; transition: color var(--t-fast); }
|
|
1145
|
+
.nav-item:hover .ic, .nav-item.active .ic, .search-row:focus-within .ic { color: var(--fg); }
|
|
1146
|
+
button.nav-item:active:not(:disabled) { transform: none; }
|
|
1147
|
+
|
|
1148
|
+
/* Search looks like a nav row until it is focused. */
|
|
1149
|
+
.search-row.nav-item { position: relative; padding: 0 .5rem; border: 0; cursor: text; }
|
|
1150
|
+
.search-row.nav-item:focus-within { background: var(--raise); box-shadow: 0 0 0 1px var(--line); }
|
|
1151
|
+
.search-row input {
|
|
1152
|
+
border: 0; background: none; padding: .375rem 1.75rem .375rem 0; min-height: 2.25rem;
|
|
1153
|
+
font-weight: 500;
|
|
1154
|
+
}
|
|
1155
|
+
.search-row input:focus { box-shadow: none; }
|
|
1156
|
+
.search-row input::placeholder { color: var(--fg); opacity: 1; }
|
|
1157
|
+
.search-row:focus-within input::placeholder { color: var(--faint); }
|
|
1158
|
+
#chatSearchClear { right: .375rem; }
|
|
1159
|
+
|
|
1160
|
+
.side-label {
|
|
1161
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1162
|
+
padding: 1rem .5rem .25rem; color: var(--faint); font-size: var(--fs-meta); font-weight: 500;
|
|
1163
|
+
}
|
|
1164
|
+
.side-label .icon-btn { width: 1.75rem; height: 1.75rem; }
|
|
1165
|
+
.side-label .ic { width: 1rem; height: 1rem; }
|
|
1166
|
+
#chats { padding: 0; margin: 0 -.125rem; }
|
|
1167
|
+
|
|
1168
|
+
/* Nav, label and chat list scroll together, as one column under the pinned
|
|
1169
|
+
brand row: scrolling down moves the nav away and brings the chats up. */
|
|
1170
|
+
.side-scroll {
|
|
1171
|
+
flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
|
|
1172
|
+
margin: 0 -.5rem; padding: 0 .5rem;
|
|
1173
|
+
scrollbar-width: thin; scrollbar-color: var(--line) transparent;
|
|
1174
|
+
}
|
|
1175
|
+
.side-scroll::-webkit-scrollbar { width: .5rem; }
|
|
1176
|
+
.side-scroll::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-pill); }
|
|
1177
|
+
.side-scroll #chats { flex: none; overflow: visible; }
|
|
1178
|
+
.chat-item { padding-left: .5rem; }
|
|
1179
|
+
.chat-item.active { background: var(--accent-soft); box-shadow: none; }
|
|
1180
|
+
.chat-date-heading { padding: .625rem .5rem .25rem; font-weight: 500; }
|
|
1181
|
+
.chat-folder-heading { background: var(--panel); padding: .5rem; }
|
|
1182
|
+
#side-resize { right: -.3125rem; }
|
|
1183
|
+
|
|
1184
|
+
/* Collapsed: the sidebar leaves, and the same button that opens the mobile
|
|
1185
|
+
drawer brings it back. */
|
|
1186
|
+
#menu { position: absolute; top: .25rem; left: .25rem; z-index: 8; display: none; }
|
|
1187
|
+
body.side-collapsed #side { display: none; }
|
|
1188
|
+
body.side-collapsed #menu { display: inline-flex; }
|
|
1189
|
+
|
|
1190
|
+
/* Full-column panels clip themselves to this frame. Keeping main overflow
|
|
1191
|
+
visible lets focus rings and composer popovers extend where they need to. */
|
|
1192
|
+
main { border-radius: var(--r-lg); }
|
|
1193
|
+
|
|
1194
|
+
/* Transcript: questions are soft grey bubbles on the right. */
|
|
1195
|
+
.msg.user { align-self: flex-end; max-width: 85%; }
|
|
1196
|
+
.msg.user:not(:first-child)::before { display: none; }
|
|
1197
|
+
.msg.user .body {
|
|
1198
|
+
background: var(--panel); border: 0; border-radius: var(--r-bubble);
|
|
1199
|
+
padding: .625rem 1rem; font-weight: 400;
|
|
1200
|
+
}
|
|
1201
|
+
.msg-tools { justify-content: flex-end; padding-left: 0; }
|
|
1202
|
+
.msg.user .edit-box { padding-left: 0; }
|
|
1203
|
+
/* The question shrinks to its bubble; with the bubble hidden for editing it
|
|
1204
|
+
would shrink to a bare textarea, so an edit takes the bubble's full width. */
|
|
1205
|
+
.msg.user.editing { width: 85%; }
|
|
1206
|
+
|
|
1207
|
+
/* Composer: text on top, a tool bar under it. */
|
|
1208
|
+
footer { padding: .25rem 1rem 1rem; }
|
|
1209
|
+
#form {
|
|
1210
|
+
flex-direction: column; align-items: stretch; gap: .25rem;
|
|
1211
|
+
border-radius: var(--r-composer); padding: .625rem .625rem .5rem;
|
|
1212
|
+
background: var(--panel); border-color: var(--line);
|
|
1213
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 4%), 0 10px 30px -18px rgb(0 0 0 / 25%);
|
|
1214
|
+
}
|
|
1215
|
+
#form:focus-within { border-color: color-mix(in srgb, var(--fg) 22%, transparent); }
|
|
1216
|
+
#input { min-height: 2.75rem; padding: .375rem .5rem; line-height: 1.5; }
|
|
1217
|
+
.composer-bar { display: flex; align-items: center; gap: .5rem; }
|
|
1218
|
+
.composer-bar .grow { flex: 1; }
|
|
1219
|
+
#form .composer-bar > button { height: 2.125rem; min-height: 0; }
|
|
1220
|
+
#attach {
|
|
1221
|
+
width: 2.125rem; min-width: 0; border-radius: var(--r-pill);
|
|
1222
|
+
background: var(--raise); border: 1px solid var(--line); color: var(--fg);
|
|
1223
|
+
}
|
|
1224
|
+
#attach:hover { background: var(--accent-soft); }
|
|
1225
|
+
#modelBtn.model-chip {
|
|
1226
|
+
max-width: 22rem; padding: .25rem .625rem; border: 1px solid var(--line); border-radius: var(--r-pill);
|
|
1227
|
+
background: var(--raise); color: var(--fg); font: 500 var(--fs-meta)/1.4 var(--sans);
|
|
1228
|
+
overflow: hidden; white-space: nowrap; text-overflow: ellipsis; min-width: 0;
|
|
1229
|
+
}
|
|
1230
|
+
.model-chip:empty { display: none; }
|
|
1231
|
+
#send { width: 2.125rem; min-width: 0; padding: 0; border-radius: var(--r-pill); }
|
|
1232
|
+
#send.stop { background: var(--fg); color: var(--bg); border-color: var(--fg); }
|
|
1233
|
+
footer::before { display: none; }
|
|
1234
|
+
|
|
1235
|
+
/* Empty chat: greeting and composer sit together in the middle of the page. */
|
|
1236
|
+
#hello { display: none; text-align: center; padding: 0 1rem 1.5rem; }
|
|
1237
|
+
#hello h2 { margin: 0 0 .375rem; font-size: 1.875rem; font-weight: 650; letter-spacing: -.02em; line-height: 1.2; }
|
|
1238
|
+
#hello p { margin: 0; color: var(--muted); }
|
|
1239
|
+
main:has(#wrap:empty) { justify-content: center; padding-bottom: 8vh; }
|
|
1240
|
+
main:has(#wrap:empty) #log,
|
|
1241
|
+
main:has(#wrap:empty) .right-rail,
|
|
1242
|
+
main:has(#wrap:empty) .outline-rail { display: none; }
|
|
1243
|
+
main:has(#wrap:empty) #hello { display: block; animation: fade var(--t-med) ease; }
|
|
1244
|
+
main:has(#wrap:empty) footer { padding-top: 0; }
|
|
1245
|
+
main:has(#wrap:empty) #form { max-width: 48rem; }
|
|
1246
|
+
|
|
1247
|
+
/* Panels open inside main, so they take its rounded corners too. */
|
|
1248
|
+
#settings, #automations, #statistics, #admin, #mcp { border-radius: inherit; }
|
|
1249
|
+
|
|
1250
|
+
@media (max-width: 720px) {
|
|
1251
|
+
#shell { padding: 0; gap: 0; }
|
|
1252
|
+
#side { top: 0; border-radius: 0 var(--r-lg) var(--r-lg) 0; box-shadow: 0 0 40px rgb(0 0 0 / 25%); }
|
|
1253
|
+
#side-backdrop { top: 0; }
|
|
1254
|
+
#menu { display: inline-flex; top: .5rem; left: .5rem; }
|
|
1255
|
+
body.side-collapsed #side { display: flex; }
|
|
1256
|
+
#log { padding-top: 3rem; }
|
|
1257
|
+
footer { padding: .25rem .5rem .5rem; }
|
|
1258
|
+
.model-chip { max-width: 55vw; }
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/* Icon-only buttons centre their glyph on both axes; a block SVG in a plain
|
|
1262
|
+
button otherwise sits at the left edge. */
|
|
1263
|
+
#send, #attach, .icon-btn {
|
|
1264
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1265
|
+
padding: 0; line-height: 0; flex-shrink: 0;
|
|
1266
|
+
}
|
|
1267
|
+
#send .ic { width: 1.125rem; height: 1.125rem; }
|
|
1268
|
+
.composer-bar { min-height: 2.125rem; }
|
|
1269
|
+
.nav-item > span { line-height: 1.2; }
|
|
1270
|
+
|
|
1271
|
+
/* ---- composer menus ---- */
|
|
1272
|
+
#form { position: relative; }
|
|
1273
|
+
#modelBtn.model-chip {
|
|
1274
|
+
display: inline-flex; align-items: center; gap: .25rem; height: 2rem; min-height: 0;
|
|
1275
|
+
padding: 0 .5rem 0 .75rem; cursor: pointer; line-height: 1.2;
|
|
1276
|
+
}
|
|
1277
|
+
#modelBtn:hover, #modelBtn[aria-expanded="true"] { background: var(--accent-soft); border-color: var(--line); }
|
|
1278
|
+
#modelBtn #status { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
1279
|
+
#modelBtn .chev { width: .875rem; height: .875rem; color: var(--muted); transition: transform var(--t-fast); }
|
|
1280
|
+
#modelBtn[aria-expanded="true"] .chev { transform: rotate(180deg); }
|
|
1281
|
+
#attach[aria-expanded="true"] { background: var(--accent-soft); }
|
|
1282
|
+
#attach .ic { transition: transform var(--t-fast); }
|
|
1283
|
+
#attach[aria-expanded="true"] .ic { transform: rotate(45deg); }
|
|
1284
|
+
|
|
1285
|
+
.popover {
|
|
1286
|
+
position: absolute; bottom: calc(100% + .5rem); z-index: 30;
|
|
1287
|
+
width: min(22rem, calc(100vw - 2rem)); max-height: min(28rem, 60vh);
|
|
1288
|
+
display: flex; flex-direction: column; padding: .375rem;
|
|
1289
|
+
background: var(--raise); border: 1px solid var(--line); border-radius: var(--r-md);
|
|
1290
|
+
box-shadow: 0 16px 40px -12px rgb(0 0 0 / 28%), 0 2px 6px rgb(0 0 0 / 6%);
|
|
1291
|
+
animation: pop-in var(--t-fast) var(--ease); transform-origin: bottom left;
|
|
1292
|
+
font-size: var(--fs-ui);
|
|
1293
|
+
}
|
|
1294
|
+
.popover-left { left: 0; }
|
|
1295
|
+
.popover-right { right: 0; transform-origin: bottom right; }
|
|
1296
|
+
@keyframes pop-in { from { opacity: 0; transform: translateY(4px) scale(.98); } }
|
|
1297
|
+
/* An empty chat has room below the centred composer, not above it. */
|
|
1298
|
+
main:has(#wrap:empty) .popover { bottom: auto; top: calc(100% + .5rem); transform-origin: top left; }
|
|
1299
|
+
|
|
1300
|
+
.pop-scroll { overflow-y: auto; min-height: 0; flex: 1; }
|
|
1301
|
+
.pop-item {
|
|
1302
|
+
display: flex; align-items: center; gap: .5rem; width: 100%; min-height: 2.25rem;
|
|
1303
|
+
padding: .375rem .5rem; border: 0; border-radius: var(--r-sm); background: none;
|
|
1304
|
+
color: var(--fg); text-align: left; cursor: pointer; font-size: var(--fs-ui);
|
|
1305
|
+
}
|
|
1306
|
+
.pop-item:hover { background: var(--accent-soft); }
|
|
1307
|
+
button.pop-item:active:not(:disabled) { transform: none; }
|
|
1308
|
+
.pop-item .ic { color: var(--muted); }
|
|
1309
|
+
.pop-sep { border: 0; border-top: 1px solid var(--line); margin: .25rem -.375rem; }
|
|
1310
|
+
.pop-label { padding: .5rem .5rem .25rem; color: var(--faint); font-size: var(--fs-meta); font-weight: 500; }
|
|
1311
|
+
.pop-note { padding: .5rem; color: var(--muted); font-size: var(--fs-meta); }
|
|
1312
|
+
.pop-foot { color: var(--muted); font-size: var(--fs-meta); margin-top: .25rem; border-top: 1px solid var(--line); border-radius: 0 0 var(--r-sm) var(--r-sm); }
|
|
1313
|
+
.pop-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1314
|
+
.pop-meta { color: var(--faint); font-size: var(--fs-meta); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
|
1315
|
+
|
|
1316
|
+
.pop-group > summary { list-style: none; }
|
|
1317
|
+
.pop-group > summary::-webkit-details-marker { display: none; }
|
|
1318
|
+
.pop-group > summary::before {
|
|
1319
|
+
content: ""; width: .375rem; height: .375rem; flex-shrink: 0; margin: 0 .125rem;
|
|
1320
|
+
border-right: 1.5px solid var(--faint); border-bottom: 1.5px solid var(--faint);
|
|
1321
|
+
transform: rotate(-45deg); transition: transform var(--t-fast);
|
|
1322
|
+
}
|
|
1323
|
+
.pop-group[open] > summary::before { transform: rotate(45deg); }
|
|
1324
|
+
.pop-dot { width: .5rem; height: .5rem; border-radius: 50%; flex-shrink: 0; background: var(--tool); }
|
|
1325
|
+
.pop-dot.error { background: var(--err); }
|
|
1326
|
+
.pop-dot.disabled { background: var(--faint); opacity: .6; }
|
|
1327
|
+
.pop-tools { padding: 0 0 .25rem 1.5rem; }
|
|
1328
|
+
.pop-tool { display: flex; align-items: center; gap: .5rem; min-height: 2rem; padding: 0 .5rem; border-radius: var(--r-sm); }
|
|
1329
|
+
.pop-tool:hover { background: var(--accent-soft); }
|
|
1330
|
+
.pop-tool-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--fs-meta) var(--mono); color: var(--muted); }
|
|
1331
|
+
|
|
1332
|
+
.switch {
|
|
1333
|
+
position: relative; flex-shrink: 0; width: 2rem; height: 1.125rem; min-height: 0; padding: 0;
|
|
1334
|
+
border: 0; border-radius: var(--r-pill); background: var(--line); cursor: pointer;
|
|
1335
|
+
transition: background var(--t-fast);
|
|
1336
|
+
}
|
|
1337
|
+
.switch::after {
|
|
1338
|
+
content: ""; position: absolute; top: .125rem; left: .125rem; width: .875rem; height: .875rem;
|
|
1339
|
+
border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / 25%);
|
|
1340
|
+
transition: transform var(--t-fast) var(--ease);
|
|
1341
|
+
}
|
|
1342
|
+
.switch[aria-checked="true"] { background: var(--fg); }
|
|
1343
|
+
.switch[aria-checked="true"]::after { transform: translateX(.875rem); }
|
|
1344
|
+
.switch[aria-checked="true"]::after { background: var(--bg); }
|
|
1345
|
+
.switch:disabled { opacity: .5; }
|
|
1346
|
+
.switch:active:not(:disabled) { transform: none; }
|
|
1347
|
+
|
|
1348
|
+
.pop-search { margin-bottom: .375rem; min-height: 2.25rem; }
|
|
1349
|
+
/* Model rows stack the description under the name, so a long description can
|
|
1350
|
+
never squeeze the name down to an ellipsis on a narrow screen. */
|
|
1351
|
+
.pop-model { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: .5rem; row-gap: .0625rem; }
|
|
1352
|
+
.pop-model .pop-name { grid-column: 1; }
|
|
1353
|
+
.pop-model .pop-meta {
|
|
1354
|
+
grid-column: 1; flex-shrink: 1; min-width: 0; font-weight: 400;
|
|
1355
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1356
|
+
}
|
|
1357
|
+
.pop-model::after { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
|
|
1358
|
+
.pop-model[aria-checked="true"] { background: var(--accent-soft); }
|
|
1359
|
+
.pop-model[aria-checked="true"] .pop-name { font-weight: 600; }
|
|
1360
|
+
.pop-model[aria-checked="true"]::after {
|
|
1361
|
+
content: ""; width: .5rem; height: .875rem; margin-right: .25rem; flex-shrink: 0;
|
|
1362
|
+
border-right: 2px solid var(--fg); border-bottom: 2px solid var(--fg); transform: rotate(45deg) translate(-2px, -2px);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/* MCP servers: its own full-column panel, same behaviour as the others. */
|
|
1366
|
+
#mcp {
|
|
1367
|
+
position: absolute; inset: 0; z-index: 9; background: var(--bg); border-radius: inherit;
|
|
1368
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
1369
|
+
visibility: hidden; opacity: 0; transform: translateY(8px); pointer-events: none;
|
|
1370
|
+
transition: opacity var(--t-med) var(--ease), transform var(--t-med) var(--ease), visibility 0s linear var(--t-med);
|
|
1371
|
+
}
|
|
1372
|
+
#mcp.open {
|
|
1373
|
+
visibility: visible; opacity: 1; transform: none; pointer-events: auto;
|
|
1374
|
+
transition: opacity var(--t-med) var(--ease), transform var(--t-med) var(--ease), visibility 0s;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/* Settings: one card per concern, a title and one line saying what it is
|
|
1378
|
+
for, fields laid out two to a row with their help text under them. */
|
|
1379
|
+
#settings .settings-content { padding: 1.25rem 1rem 2rem; }
|
|
1380
|
+
.set-group {
|
|
1381
|
+
width: min(46rem, 100%); margin: 0 auto 1rem; padding: 1.125rem 1.25rem 1.25rem;
|
|
1382
|
+
background: var(--raise); border: 1px solid var(--line); border-radius: var(--r-md);
|
|
1383
|
+
}
|
|
1384
|
+
.set-group > header { margin-bottom: .875rem; }
|
|
1385
|
+
.set-group h3 { margin: 0; font-size: 1rem; font-weight: 600; letter-spacing: -.005em; }
|
|
1386
|
+
.set-group > header p { margin: .125rem 0 0; color: var(--muted); font-size: var(--fs-small); }
|
|
1387
|
+
.set-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 1rem; }
|
|
1388
|
+
.set-grid .span-2 { grid-column: 1 / -1; }
|
|
1389
|
+
.set-group label { color: var(--fg); font-weight: 500; font-size: var(--fs-small); margin-bottom: .3125rem; }
|
|
1390
|
+
.set-group small { display: block; margin-top: .3125rem; color: var(--faint); font-size: var(--fs-meta); line-height: 1.4; }
|
|
1391
|
+
.set-group textarea#systemPrompt { font-family: var(--sans); font-size: var(--fs-ui); line-height: 1.55; }
|
|
1392
|
+
@media (max-width: 720px) { .set-grid { grid-template-columns: 1fr; } }
|
|
1393
|
+
|
|
1394
|
+
.brand-home {
|
|
1395
|
+
flex: 1; min-width: 0; display: flex; align-items: center; gap: .5rem;
|
|
1396
|
+
min-height: 0; padding: .125rem .375rem .125rem .125rem; margin: -.125rem 0 -.125rem -.125rem;
|
|
1397
|
+
border: 0; border-radius: var(--r-pill); background: none; color: var(--fg); text-align: left;
|
|
1398
|
+
}
|
|
1399
|
+
.brand-home:hover { background: var(--accent-soft); }
|
|
1400
|
+
.brand-home:active:not(:disabled) { transform: none; }
|
|
1401
|
+
.attachment-chip-error {
|
|
1402
|
+
border-color: var(--danger, #c0392b); color: var(--danger, #c0392b);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
#modelBtn.static { cursor: default; }
|
|
1406
|
+
#modelBtn.static .chev { display: none; }
|
|
1407
|
+
[hidden] { display: none !important; }
|
|
1408
|
+
|
|
1409
|
+
/* An ask_user question: the run is paused on it until it is answered,
|
|
1410
|
+
skipped, times out or is stopped. */
|
|
1411
|
+
.step .ask {
|
|
1412
|
+
display: flex; flex-direction: column; gap: .5rem;
|
|
1413
|
+
padding: .6rem; border-top: 1px solid var(--line);
|
|
1414
|
+
}
|
|
1415
|
+
.step .ask-q { margin: 0; white-space: pre-wrap; color: var(--fg); }
|
|
1416
|
+
.step .ask-choices { display: flex; flex-wrap: wrap; gap: .4rem; }
|
|
1417
|
+
.step .ask-free { display: flex; gap: .4rem; }
|
|
1418
|
+
.step .ask-free input { flex: 1; min-width: 0; }
|
|
1419
|
+
.step .ask-foot { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
|
|
1420
|
+
.step .ask button { font-size: var(--fs-meta); }
|
|
1421
|
+
.step .ask-clock { color: var(--muted); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
|
|
1422
|
+
|
|
1423
|
+
/* Saved request attribution */
|
|
1424
|
+
#usageDistribution { margin: 1rem 0; }
|
|
1425
|
+
.usage-composition { margin: 1rem 0; }
|
|
1426
|
+
.usage-composition h3 { font-size: 1rem; margin: .5rem 0; }
|
|
1427
|
+
.usage-attribution-note { color: var(--muted); font-size: .8rem; line-height: 1.5; }
|
|
1428
|
+
.usage-composition-table { width: 100%; font-size: .8rem; border-collapse: collapse; }
|
|
1429
|
+
.usage-composition-table td { padding: .35rem .25rem; overflow-wrap: anywhere; }
|
|
1430
|
+
.usage-composition-table td:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }
|
|
1431
|
+
.usage-composition-track { display: flex; height: .7rem; overflow: hidden; border-radius: var(--r-xs); margin: .5rem 0; }
|
|
1432
|
+
#statisticsFilter { margin-left: auto; min-width: 0; }
|
|
1433
|
+
#statisticsFilter select { max-width: 100%; }
|
|
1434
|
+
#statisticsFilter + button { margin-left: 0; }
|
|
1435
|
+
.task-card {
|
|
1436
|
+
max-width: var(--measure); margin: 0 auto .5rem; padding: .5rem .75rem;
|
|
1437
|
+
border: 1px solid var(--line); border-radius: var(--r-md); background: var(--panel);
|
|
1438
|
+
}
|
|
1439
|
+
.task-card[hidden] { display: none; }
|
|
1440
|
+
.task-heading {
|
|
1441
|
+
display: flex; justify-content: space-between; gap: 8px; font-size: 13px;
|
|
1442
|
+
cursor: pointer; list-style: none;
|
|
1443
|
+
}
|
|
1444
|
+
.task-heading::-webkit-details-marker { display: none; }
|
|
1445
|
+
.task-heading span { opacity: .65; }
|
|
1446
|
+
/* Capped so a long list scrolls instead of pushing the composer off screen. */
|
|
1447
|
+
.task-items {
|
|
1448
|
+
list-style: none; margin: .5rem 0 0; padding: 0; display: grid; gap: 6px;
|
|
1449
|
+
max-height: 9rem; overflow-y: auto;
|
|
1450
|
+
}
|
|
1451
|
+
.task-item { display: flex; align-items: baseline; gap: 8px; font-size: 13px; line-height: 1.4; overflow-wrap: anywhere; }
|
|
1452
|
+
.task-mark { flex: none; width: 1em; text-align: center; font-weight: 700; }
|
|
1453
|
+
.task-completed { opacity: .6; }
|
|
1454
|
+
.task-completed span:last-child { text-decoration: line-through; }
|
|
1455
|
+
.no-shrink { flex: 0 0 auto; }
|