@artooi/ag-ui-web-component 0.4.0 → 0.6.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/CHANGELOG.md +50 -1
- package/README.md +69 -2
- package/dist/ag-ui-web-component.bundle.js +355 -59
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +17 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +7 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/attachment.d.ts +35 -0
- package/dist/core/attachment.d.ts.map +1 -0
- package/dist/core/conversation_store.d.ts +37 -8
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +35 -0
- package/dist/core/remote_conversation_store.d.ts.map +1 -0
- package/dist/core/upload_attachment.d.ts +32 -0
- package/dist/core/upload_attachment.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1198 -24
- package/dist/index.js.map +4 -4
- package/dist/ui/attachment_chips.d.ts +13 -0
- package/dist/ui/attachment_chips.d.ts.map +1 -0
- package/dist/ui/attachment_tray.d.ts +42 -0
- package/dist/ui/attachment_tray.d.ts.map +1 -0
- package/dist/ui/relative_time.d.ts +11 -0
- package/dist/ui/relative_time.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +33 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/constants.ts +18 -0
- package/src/core/ag_ui_chat.ts +267 -14
- package/src/core/agui_client.ts +15 -2
- package/src/core/attachment.ts +39 -0
- package/src/core/conversation_store.ts +148 -9
- package/src/core/remote_conversation_store.ts +147 -0
- package/src/core/upload_attachment.ts +113 -0
- package/src/index.ts +8 -0
- package/src/ui/attachment_chips.ts +68 -0
- package/src/ui/attachment_tray.ts +237 -0
- package/src/ui/relative_time.ts +28 -0
- package/src/ui/styles.ts +294 -0
- package/src/ui/thread_drawer.ts +200 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { randomUUID } from "@ag-ui/client";
|
|
2
|
+
import { ATTACHMENT_STATUS } from "../constants.js";
|
|
3
|
+
import type { AttachmentRef } from "../core/attachment.js";
|
|
4
|
+
import type { UploadHandler } from "../core/upload_attachment.js";
|
|
5
|
+
import { formatBytes, iconFor } from "./attachment_chips.js";
|
|
6
|
+
|
|
7
|
+
/** Status of a pending tray chip. */
|
|
8
|
+
type AttachmentStatus = (typeof ATTACHMENT_STATUS)[keyof typeof ATTACHMENT_STATUS];
|
|
9
|
+
|
|
10
|
+
/** Config the host ({@link AgUiChat}) hands the tray. */
|
|
11
|
+
export interface AttachmentTrayConfig {
|
|
12
|
+
/** Upload one file, reporting `0..1` progress; resolves to a durable ref. */
|
|
13
|
+
readonly upload: UploadHandler;
|
|
14
|
+
/** Client-side size cap in bytes (`0` = no cap). The server stays authoritative. */
|
|
15
|
+
readonly maxBytes: number;
|
|
16
|
+
/** Client-side accept list (`<input accept>` syntax; `""` = any). */
|
|
17
|
+
readonly accept: string;
|
|
18
|
+
/** Fired when the set of attachments changes (add / settle / remove). */
|
|
19
|
+
readonly onChange?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One pending file in the tray, from pick to ready/error. */
|
|
23
|
+
interface TrayItem {
|
|
24
|
+
readonly localId: string;
|
|
25
|
+
readonly file: File;
|
|
26
|
+
status: AttachmentStatus;
|
|
27
|
+
progress: number;
|
|
28
|
+
ref: AttachmentRef | null;
|
|
29
|
+
error: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The composer's pending-attachments tray: a chip per picked file with a
|
|
34
|
+
* progress bar while it uploads, settling to a ready chip (holding the durable
|
|
35
|
+
* ref) or an error chip (with retry). A *stateful view* in the spirit of
|
|
36
|
+
* {@link ThreadDrawer} — the host appends {@link element}, calls {@link add} on
|
|
37
|
+
* pick/drop, reads {@link readyRefs} when the user sends, and clears it.
|
|
38
|
+
*
|
|
39
|
+
* Client-side size/type guards reject a bad file into an error chip without
|
|
40
|
+
* uploading — instant feedback, but the server is the authority.
|
|
41
|
+
*/
|
|
42
|
+
export class AttachmentTray {
|
|
43
|
+
/** The tray root; append above the input row. Hidden while empty. */
|
|
44
|
+
readonly element: HTMLDivElement;
|
|
45
|
+
|
|
46
|
+
readonly #config: AttachmentTrayConfig;
|
|
47
|
+
#items: TrayItem[] = [];
|
|
48
|
+
|
|
49
|
+
constructor(config: AttachmentTrayConfig) {
|
|
50
|
+
this.#config = config;
|
|
51
|
+
this.element = document.createElement("div");
|
|
52
|
+
this.element.className = "attachment-tray";
|
|
53
|
+
this.element.hidden = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Queue a file: reject oversize/disallowed into an error chip, else upload. */
|
|
57
|
+
add(file: File): void {
|
|
58
|
+
const item: TrayItem = {
|
|
59
|
+
localId: randomUUID(),
|
|
60
|
+
file,
|
|
61
|
+
status: ATTACHMENT_STATUS.UPLOADING,
|
|
62
|
+
progress: 0,
|
|
63
|
+
ref: null,
|
|
64
|
+
error: "",
|
|
65
|
+
};
|
|
66
|
+
this.#items.push(item);
|
|
67
|
+
const rejection = this.#reject(file);
|
|
68
|
+
if (rejection !== null) {
|
|
69
|
+
item.status = ATTACHMENT_STATUS.ERROR;
|
|
70
|
+
item.error = rejection;
|
|
71
|
+
this.#render();
|
|
72
|
+
this.#config.onChange?.();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.#render();
|
|
76
|
+
this.#config.onChange?.();
|
|
77
|
+
this.#upload(item);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The durable refs of every chip that finished uploading. */
|
|
81
|
+
readyRefs(): readonly AttachmentRef[] {
|
|
82
|
+
const refs: AttachmentRef[] = [];
|
|
83
|
+
for (const item of this.#items) {
|
|
84
|
+
if (item.ref !== null) {
|
|
85
|
+
refs.push(item.ref);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return refs;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Whether any chip is still uploading (a send would drop nothing if false). */
|
|
92
|
+
hasPending(): boolean {
|
|
93
|
+
return this.#items.some((item) => item.status === ATTACHMENT_STATUS.UPLOADING);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Whether the tray holds no chips. */
|
|
97
|
+
isEmpty(): boolean {
|
|
98
|
+
return this.#items.length === 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Drop the settled (ready / error) chips, leaving any still uploading. */
|
|
102
|
+
clearReady(): void {
|
|
103
|
+
this.#items = this.#items.filter((item) => item.status === ATTACHMENT_STATUS.UPLOADING);
|
|
104
|
+
this.#render();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Drop every chip (a reset / new-chat). */
|
|
108
|
+
clear(): void {
|
|
109
|
+
this.#items = [];
|
|
110
|
+
this.#render();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The size/type rejection reason for a file, or `null` when accepted. */
|
|
114
|
+
#reject(file: File): string | null {
|
|
115
|
+
if (this.#config.maxBytes > 0 && file.size > this.#config.maxBytes) {
|
|
116
|
+
return `Too large (max ${formatBytes(this.#config.maxBytes)})`;
|
|
117
|
+
}
|
|
118
|
+
if (!accepts(this.#config.accept, file)) {
|
|
119
|
+
return "File type not allowed";
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#upload(item: TrayItem): void {
|
|
125
|
+
item.status = ATTACHMENT_STATUS.UPLOADING;
|
|
126
|
+
item.progress = 0;
|
|
127
|
+
item.error = "";
|
|
128
|
+
this.#render();
|
|
129
|
+
this.#config
|
|
130
|
+
.upload(item.file, (fraction) => {
|
|
131
|
+
item.progress = fraction;
|
|
132
|
+
this.#render();
|
|
133
|
+
})
|
|
134
|
+
.then((ref) => {
|
|
135
|
+
item.status = ATTACHMENT_STATUS.READY;
|
|
136
|
+
item.ref = ref;
|
|
137
|
+
})
|
|
138
|
+
.catch((error: unknown) => {
|
|
139
|
+
item.status = ATTACHMENT_STATUS.ERROR;
|
|
140
|
+
item.error = error instanceof Error ? error.message : "upload failed";
|
|
141
|
+
})
|
|
142
|
+
.finally(() => {
|
|
143
|
+
this.#render();
|
|
144
|
+
this.#config.onChange?.();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#remove(item: TrayItem): void {
|
|
149
|
+
this.#items = this.#items.filter((other) => other !== item);
|
|
150
|
+
this.#render();
|
|
151
|
+
this.#config.onChange?.();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#render(): void {
|
|
155
|
+
this.element.replaceChildren();
|
|
156
|
+
this.element.hidden = this.#items.length === 0;
|
|
157
|
+
for (const item of this.#items) {
|
|
158
|
+
this.element.appendChild(this.#renderChip(item));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#renderChip(item: TrayItem): HTMLDivElement {
|
|
163
|
+
const chip = document.createElement("div");
|
|
164
|
+
chip.className = `attachment-chip attachment-chip--${item.status}`;
|
|
165
|
+
|
|
166
|
+
const icon = document.createElement("span");
|
|
167
|
+
icon.className = "attachment-chip-icon";
|
|
168
|
+
icon.textContent = iconFor(item.file.type);
|
|
169
|
+
icon.setAttribute("aria-hidden", "true");
|
|
170
|
+
|
|
171
|
+
const name = document.createElement("span");
|
|
172
|
+
name.className = "attachment-chip-name";
|
|
173
|
+
name.textContent = item.file.name;
|
|
174
|
+
name.title = item.file.name;
|
|
175
|
+
|
|
176
|
+
const meta = document.createElement("span");
|
|
177
|
+
meta.className = "attachment-chip-size";
|
|
178
|
+
meta.textContent =
|
|
179
|
+
item.status === ATTACHMENT_STATUS.ERROR ? item.error : formatBytes(item.file.size);
|
|
180
|
+
|
|
181
|
+
chip.append(icon, name, meta);
|
|
182
|
+
|
|
183
|
+
if (item.status === ATTACHMENT_STATUS.UPLOADING) {
|
|
184
|
+
const bar = document.createElement("div");
|
|
185
|
+
bar.className = "attachment-chip-bar";
|
|
186
|
+
const fill = document.createElement("div");
|
|
187
|
+
fill.className = "attachment-chip-bar-fill";
|
|
188
|
+
fill.style.width = `${Math.round(item.progress * 100)}%`;
|
|
189
|
+
bar.appendChild(fill);
|
|
190
|
+
chip.appendChild(bar);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (item.status === ATTACHMENT_STATUS.ERROR) {
|
|
194
|
+
const retry = document.createElement("button");
|
|
195
|
+
retry.type = "button";
|
|
196
|
+
retry.className = "attachment-chip-retry";
|
|
197
|
+
retry.title = "Retry";
|
|
198
|
+
retry.setAttribute("aria-label", "Retry upload");
|
|
199
|
+
retry.textContent = "↻";
|
|
200
|
+
retry.addEventListener("click", () => this.#upload(item));
|
|
201
|
+
chip.appendChild(retry);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const remove = document.createElement("button");
|
|
205
|
+
remove.type = "button";
|
|
206
|
+
remove.className = "attachment-chip-remove";
|
|
207
|
+
remove.title = "Remove";
|
|
208
|
+
remove.setAttribute("aria-label", "Remove attachment");
|
|
209
|
+
remove.textContent = "✕";
|
|
210
|
+
remove.addEventListener("click", () => this.#remove(item));
|
|
211
|
+
chip.appendChild(remove);
|
|
212
|
+
|
|
213
|
+
return chip;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Whether `file` matches an `<input accept>` list (`""` accepts anything). */
|
|
218
|
+
function accepts(accept: string, file: File): boolean {
|
|
219
|
+
const tokens = accept
|
|
220
|
+
.split(",")
|
|
221
|
+
.map((token) => token.trim().toLowerCase())
|
|
222
|
+
.filter((token) => token !== "");
|
|
223
|
+
if (tokens.length === 0) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
const mime = file.type.toLowerCase();
|
|
227
|
+
const name = file.name.toLowerCase();
|
|
228
|
+
return tokens.some((token) => {
|
|
229
|
+
if (token.startsWith(".")) {
|
|
230
|
+
return name.endsWith(token);
|
|
231
|
+
}
|
|
232
|
+
if (token.endsWith("/*")) {
|
|
233
|
+
return mime.startsWith(token.slice(0, -1));
|
|
234
|
+
}
|
|
235
|
+
return mime === token;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A compact relative timestamp for a thread row — e.g. `"just now"`, `"5m ago"`,
|
|
3
|
+
* `"3h ago"`, `"2d ago"`, `"4w ago"`.
|
|
4
|
+
*
|
|
5
|
+
* `now` is injectable so callers (and tests) can pin the reference point; it
|
|
6
|
+
* defaults to the current time. A timestamp in the future (clock skew) reads as
|
|
7
|
+
* `"just now"`. Kept locale-independent on purpose so the rendered label is
|
|
8
|
+
* stable across environments.
|
|
9
|
+
*/
|
|
10
|
+
export function relativeTime(timestamp: number, now: number = Date.now()): string {
|
|
11
|
+
const seconds = Math.round((now - timestamp) / 1000);
|
|
12
|
+
if (seconds < 60) {
|
|
13
|
+
return "just now";
|
|
14
|
+
}
|
|
15
|
+
const minutes = Math.round(seconds / 60);
|
|
16
|
+
if (minutes < 60) {
|
|
17
|
+
return `${minutes}m ago`;
|
|
18
|
+
}
|
|
19
|
+
const hours = Math.round(minutes / 60);
|
|
20
|
+
if (hours < 24) {
|
|
21
|
+
return `${hours}h ago`;
|
|
22
|
+
}
|
|
23
|
+
const days = Math.round(hours / 24);
|
|
24
|
+
if (days < 7) {
|
|
25
|
+
return `${days}d ago`;
|
|
26
|
+
}
|
|
27
|
+
return `${Math.round(days / 7)}w ago`;
|
|
28
|
+
}
|
package/src/ui/styles.ts
CHANGED
|
@@ -506,6 +506,116 @@ export const STYLES = `
|
|
|
506
506
|
background: var(--ag-ui-muted);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
/* ── File attachments ───────────────────────────────────────────────────── */
|
|
510
|
+
/* The 📎 picker button sits left of the input; hidden until data-attachments-url. */
|
|
511
|
+
.attach-btn {
|
|
512
|
+
border: 1px solid var(--ag-ui-border);
|
|
513
|
+
border-radius: 8px;
|
|
514
|
+
padding: 0 10px;
|
|
515
|
+
background: var(--ag-ui-input-bg);
|
|
516
|
+
color: inherit;
|
|
517
|
+
font: inherit;
|
|
518
|
+
cursor: pointer;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.attach-btn:hover {
|
|
522
|
+
border-color: var(--ag-ui-accent);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.attach-input {
|
|
526
|
+
display: none;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */
|
|
530
|
+
.attachment-slot {
|
|
531
|
+
display: contents;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.attachment-tray {
|
|
535
|
+
display: flex;
|
|
536
|
+
flex-wrap: wrap;
|
|
537
|
+
gap: 6px;
|
|
538
|
+
padding: 8px 12px 0;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.attachment-chips {
|
|
542
|
+
display: flex;
|
|
543
|
+
flex-wrap: wrap;
|
|
544
|
+
gap: 6px;
|
|
545
|
+
margin-top: 6px;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.attachment-chip {
|
|
549
|
+
display: inline-flex;
|
|
550
|
+
align-items: center;
|
|
551
|
+
gap: 6px;
|
|
552
|
+
max-width: 100%;
|
|
553
|
+
padding: 4px 8px;
|
|
554
|
+
border: 1px solid var(--ag-ui-border);
|
|
555
|
+
border-radius: 999px;
|
|
556
|
+
background: var(--ag-ui-assistant-bg);
|
|
557
|
+
font-size: 0.85em;
|
|
558
|
+
position: relative;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.attachment-chip--error {
|
|
562
|
+
border-color: var(--ag-ui-danger);
|
|
563
|
+
color: var(--ag-ui-danger);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.attachment-chip-name {
|
|
567
|
+
overflow: hidden;
|
|
568
|
+
text-overflow: ellipsis;
|
|
569
|
+
white-space: nowrap;
|
|
570
|
+
max-width: 14ch;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.attachment-chip-size {
|
|
574
|
+
color: var(--ag-ui-muted);
|
|
575
|
+
white-space: nowrap;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.attachment-chip--error .attachment-chip-size {
|
|
579
|
+
color: var(--ag-ui-danger);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/* The progress bar fills as the file uploads. */
|
|
583
|
+
.attachment-chip-bar {
|
|
584
|
+
flex-basis: 100%;
|
|
585
|
+
height: 3px;
|
|
586
|
+
border-radius: 2px;
|
|
587
|
+
background: var(--ag-ui-border);
|
|
588
|
+
overflow: hidden;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.attachment-chip-bar-fill {
|
|
592
|
+
height: 100%;
|
|
593
|
+
background: var(--ag-ui-accent);
|
|
594
|
+
transition: width 0.15s ease;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.attachment-chip-remove,
|
|
598
|
+
.attachment-chip-retry {
|
|
599
|
+
border: none;
|
|
600
|
+
background: none;
|
|
601
|
+
color: inherit;
|
|
602
|
+
cursor: pointer;
|
|
603
|
+
padding: 0;
|
|
604
|
+
line-height: 1;
|
|
605
|
+
opacity: 0.7;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.attachment-chip-remove:hover,
|
|
609
|
+
.attachment-chip-retry:hover {
|
|
610
|
+
opacity: 1;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/* A subtle outline while a file is dragged over the shell. */
|
|
614
|
+
.chat--dragover {
|
|
615
|
+
outline: 2px dashed var(--ag-ui-accent);
|
|
616
|
+
outline-offset: -4px;
|
|
617
|
+
}
|
|
618
|
+
|
|
509
619
|
/* Muted "⏹ Stopped" line after a cancelled run — a note, not an error bubble. */
|
|
510
620
|
.stopped-note {
|
|
511
621
|
align-self: flex-start;
|
|
@@ -644,4 +754,188 @@ export const STYLES = `
|
|
|
644
754
|
font-size: 0.85em;
|
|
645
755
|
color: var(--ag-ui-danger);
|
|
646
756
|
}
|
|
757
|
+
|
|
758
|
+
/* Chat-history drawer — a slide-over within the chat panel. */
|
|
759
|
+
.drawer {
|
|
760
|
+
position: absolute;
|
|
761
|
+
inset: 0;
|
|
762
|
+
z-index: 5;
|
|
763
|
+
display: flex;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.drawer[hidden] {
|
|
767
|
+
display: none;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.drawer-backdrop {
|
|
771
|
+
position: absolute;
|
|
772
|
+
inset: 0;
|
|
773
|
+
background: rgba(20, 20, 50, 0.32);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.drawer-panel {
|
|
777
|
+
position: relative;
|
|
778
|
+
display: flex;
|
|
779
|
+
flex-direction: column;
|
|
780
|
+
width: min(300px, 85%);
|
|
781
|
+
height: 100%;
|
|
782
|
+
background: var(--ag-ui-bg);
|
|
783
|
+
border-right: 1px solid var(--ag-ui-border);
|
|
784
|
+
box-shadow: var(--ag-ui-shadow);
|
|
785
|
+
overflow: hidden;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.drawer-header {
|
|
789
|
+
display: flex;
|
|
790
|
+
align-items: center;
|
|
791
|
+
justify-content: space-between;
|
|
792
|
+
gap: var(--ag-ui-space);
|
|
793
|
+
padding: var(--ag-ui-pad);
|
|
794
|
+
border-bottom: 1px solid var(--ag-ui-border);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.drawer-title {
|
|
798
|
+
font-weight: 600;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.drawer-new {
|
|
802
|
+
border: 1px solid var(--ag-ui-border);
|
|
803
|
+
border-radius: var(--ag-ui-radius);
|
|
804
|
+
background: var(--ag-ui-bg);
|
|
805
|
+
color: var(--ag-ui-accent);
|
|
806
|
+
padding: 4px 10px;
|
|
807
|
+
font: inherit;
|
|
808
|
+
font-size: 0.85em;
|
|
809
|
+
cursor: pointer;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.drawer-list {
|
|
813
|
+
flex: 1;
|
|
814
|
+
min-height: 0;
|
|
815
|
+
overflow-y: auto;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.drawer-empty {
|
|
819
|
+
padding: var(--ag-ui-pad);
|
|
820
|
+
font-size: 0.9em;
|
|
821
|
+
color: var(--ag-ui-muted);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.drawer-row {
|
|
825
|
+
display: flex;
|
|
826
|
+
align-items: stretch;
|
|
827
|
+
border-bottom: 1px solid var(--ag-ui-border);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.drawer-row--active {
|
|
831
|
+
background: var(--ag-ui-assistant-bg);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.drawer-row-select {
|
|
835
|
+
flex: 1;
|
|
836
|
+
min-width: 0;
|
|
837
|
+
display: flex;
|
|
838
|
+
flex-direction: column;
|
|
839
|
+
gap: 2px;
|
|
840
|
+
padding: 8px 12px;
|
|
841
|
+
border: none;
|
|
842
|
+
background: none;
|
|
843
|
+
color: inherit;
|
|
844
|
+
font: inherit;
|
|
845
|
+
text-align: left;
|
|
846
|
+
cursor: pointer;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.drawer-row-title {
|
|
850
|
+
font-weight: 600;
|
|
851
|
+
overflow: hidden;
|
|
852
|
+
white-space: nowrap;
|
|
853
|
+
text-overflow: ellipsis;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.drawer-row-time {
|
|
857
|
+
font-size: 0.72em;
|
|
858
|
+
color: var(--ag-ui-muted);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
.drawer-row-preview {
|
|
862
|
+
font-size: 0.8em;
|
|
863
|
+
color: var(--ag-ui-muted);
|
|
864
|
+
overflow: hidden;
|
|
865
|
+
white-space: nowrap;
|
|
866
|
+
text-overflow: ellipsis;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.drawer-row-actions {
|
|
870
|
+
display: flex;
|
|
871
|
+
align-items: center;
|
|
872
|
+
gap: 2px;
|
|
873
|
+
padding: 0 6px;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.drawer-row-rename,
|
|
877
|
+
.drawer-row-delete {
|
|
878
|
+
border: none;
|
|
879
|
+
background: none;
|
|
880
|
+
color: var(--ag-ui-muted);
|
|
881
|
+
font-size: 0.9em;
|
|
882
|
+
padding: 4px;
|
|
883
|
+
cursor: pointer;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.drawer-rename-input {
|
|
887
|
+
flex: 1;
|
|
888
|
+
min-width: 0;
|
|
889
|
+
margin: 6px 10px;
|
|
890
|
+
padding: 4px 8px;
|
|
891
|
+
border: 1px solid var(--ag-ui-accent);
|
|
892
|
+
border-radius: 6px;
|
|
893
|
+
background: var(--ag-ui-input-bg);
|
|
894
|
+
color: var(--ag-ui-fg);
|
|
895
|
+
font: inherit;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.drawer-confirm {
|
|
899
|
+
display: flex;
|
|
900
|
+
align-items: center;
|
|
901
|
+
gap: 8px;
|
|
902
|
+
padding: 8px 12px;
|
|
903
|
+
font-size: 0.85em;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.drawer-confirm-label {
|
|
907
|
+
color: var(--ag-ui-danger);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.drawer-confirm-yes {
|
|
911
|
+
border: none;
|
|
912
|
+
border-radius: 6px;
|
|
913
|
+
background: var(--ag-ui-danger);
|
|
914
|
+
color: #ffffff;
|
|
915
|
+
padding: 3px 10px;
|
|
916
|
+
font: inherit;
|
|
917
|
+
cursor: pointer;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
.drawer-confirm-no {
|
|
921
|
+
border: 1px solid var(--ag-ui-border);
|
|
922
|
+
border-radius: 6px;
|
|
923
|
+
background: none;
|
|
924
|
+
color: inherit;
|
|
925
|
+
padding: 3px 10px;
|
|
926
|
+
font: inherit;
|
|
927
|
+
cursor: pointer;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/* Embedded placement: an inline, flush side panel rather than a dimmed,
|
|
931
|
+
floating slide-over. */
|
|
932
|
+
:host([placement="embedded"]) .drawer-backdrop {
|
|
933
|
+
background: none;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
:host([placement="embedded"]) .drawer-panel {
|
|
937
|
+
width: 100%;
|
|
938
|
+
border-right: none;
|
|
939
|
+
box-shadow: none;
|
|
940
|
+
}
|
|
647
941
|
`;
|