@artooi/ag-ui-web-component 0.11.0 → 0.12.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 +35 -1
- package/README.md +55 -0
- package/dist/ag-ui-web-component.bundle.js +130 -54
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +50 -0
- package/dist/core/run_index.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +351 -27
- package/dist/index.js.map +3 -3
- package/dist/ui/checkpoint_menu.d.ts +32 -0
- package/dist/ui/checkpoint_menu.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/ui_strings.d.ts +10 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +91 -1
- package/src/core/run_index.ts +91 -0
- package/src/index.ts +2 -0
- package/src/ui/checkpoint_menu.ts +153 -0
- package/src/ui/styles.ts +76 -0
- package/src/ui/ui_strings.ts +15 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { RunRow } from "../core/run_index.js";
|
|
2
|
+
import { relativeTime } from "./relative_time.js";
|
|
3
|
+
import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
|
|
4
|
+
|
|
5
|
+
/** How the host continues a picked run. */
|
|
6
|
+
export type CheckpointVerb = "resume" | "fork";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The checkpoint panel: continuable runs, each offering **resume** or **fork**.
|
|
10
|
+
*
|
|
11
|
+
* A separate surface from the thread drawer on purpose — they are different
|
|
12
|
+
* axes. A thread is a conversation you switch *to*; a checkpoint is a run you
|
|
13
|
+
* continue *from*, and one thread can hold many. Folding them into one list
|
|
14
|
+
* would make "resume" look like "open", which it isn't: resuming starts a new
|
|
15
|
+
* run seeded from a snapshot.
|
|
16
|
+
*
|
|
17
|
+
* Only rows the server marked `continuable` are worth offering, so the host
|
|
18
|
+
* feeds those; a run with no snapshot would resume from nothing. Pure DOM in
|
|
19
|
+
* the spirit of {@link SkillsMenu} — the host appends {@link element}, toggles
|
|
20
|
+
* it, feeds rows via {@link setRuns}, and acts on {@link onPick}.
|
|
21
|
+
*/
|
|
22
|
+
export class CheckpointMenu {
|
|
23
|
+
/** The panel root. Append to the chat shell; hidden until opened. */
|
|
24
|
+
readonly element: HTMLDivElement;
|
|
25
|
+
|
|
26
|
+
readonly #onPick: (runId: string, verb: CheckpointVerb) => void;
|
|
27
|
+
readonly #list: HTMLDivElement;
|
|
28
|
+
readonly #heading: HTMLSpanElement;
|
|
29
|
+
#strings: UiStrings;
|
|
30
|
+
#runs: readonly RunRow[] = [];
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
onPick: (runId: string, verb: CheckpointVerb) => void,
|
|
34
|
+
strings: UiStrings = DEFAULT_UI_STRINGS,
|
|
35
|
+
) {
|
|
36
|
+
this.#onPick = onPick;
|
|
37
|
+
this.#strings = strings;
|
|
38
|
+
|
|
39
|
+
this.element = document.createElement("div");
|
|
40
|
+
this.element.className = "checkpoints";
|
|
41
|
+
this.element.setAttribute("part", "checkpoints");
|
|
42
|
+
this.element.setAttribute("role", "dialog");
|
|
43
|
+
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
44
|
+
this.element.hidden = true;
|
|
45
|
+
|
|
46
|
+
const header = document.createElement("div");
|
|
47
|
+
header.className = "checkpoints-header";
|
|
48
|
+
header.setAttribute("part", "checkpoints-header");
|
|
49
|
+
this.#heading = document.createElement("span");
|
|
50
|
+
this.#heading.className = "checkpoints-title";
|
|
51
|
+
this.#heading.textContent = strings.checkpoints;
|
|
52
|
+
header.append(this.#heading);
|
|
53
|
+
|
|
54
|
+
this.#list = document.createElement("div");
|
|
55
|
+
this.#list.className = "checkpoints-list";
|
|
56
|
+
this.#list.setAttribute("part", "checkpoints-list");
|
|
57
|
+
|
|
58
|
+
this.element.append(header, this.#list);
|
|
59
|
+
this.element.addEventListener("keydown", (event) => {
|
|
60
|
+
if (event.key === "Escape") {
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
this.close();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Replace the rows. The host passes only `continuable` runs. */
|
|
68
|
+
setRuns(runs: readonly RunRow[]): void {
|
|
69
|
+
this.#runs = runs;
|
|
70
|
+
this.#render();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Re-localize a panel built before the host's strings resolved. */
|
|
74
|
+
setStrings(strings: UiStrings): void {
|
|
75
|
+
this.#strings = strings;
|
|
76
|
+
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
77
|
+
this.#heading.textContent = strings.checkpoints;
|
|
78
|
+
this.#render();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
open(): void {
|
|
82
|
+
this.element.hidden = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
close(): void {
|
|
86
|
+
this.element.hidden = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get open_(): boolean {
|
|
90
|
+
return !this.element.hidden;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#render(): void {
|
|
94
|
+
this.#list.replaceChildren();
|
|
95
|
+
if (this.#runs.length === 0) {
|
|
96
|
+
const empty = document.createElement("div");
|
|
97
|
+
empty.className = "checkpoints-empty";
|
|
98
|
+
empty.setAttribute("part", "checkpoints-empty");
|
|
99
|
+
empty.textContent = this.#strings.noCheckpoints;
|
|
100
|
+
this.#list.append(empty);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const run of this.#runs) {
|
|
104
|
+
this.#list.append(this.#row(run));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#row(run: RunRow): HTMLDivElement {
|
|
109
|
+
const row = document.createElement("div");
|
|
110
|
+
row.className = "checkpoint-row";
|
|
111
|
+
row.setAttribute("part", "checkpoint-row");
|
|
112
|
+
|
|
113
|
+
const label = document.createElement("span");
|
|
114
|
+
label.className = "checkpoint-label";
|
|
115
|
+
// A run id is opaque to a person, so the time is the identifying detail;
|
|
116
|
+
// the id rides `title` for anyone who needs to correlate with server logs.
|
|
117
|
+
label.textContent =
|
|
118
|
+
run.started_at === null
|
|
119
|
+
? run.run_id
|
|
120
|
+
: relativeTime(Date.parse(run.started_at), Date.now(), this.#strings);
|
|
121
|
+
label.title = run.run_id;
|
|
122
|
+
row.append(label);
|
|
123
|
+
|
|
124
|
+
if (run.parent_run_id !== null) {
|
|
125
|
+
// Lineage, so a branch doesn't read as a duplicate of its parent.
|
|
126
|
+
const branch = document.createElement("span");
|
|
127
|
+
branch.className = "checkpoint-branch";
|
|
128
|
+
branch.setAttribute("part", "checkpoint-branch");
|
|
129
|
+
branch.textContent = this.#strings.forkedRun;
|
|
130
|
+
branch.title = run.parent_run_id;
|
|
131
|
+
row.append(branch);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
row.append(
|
|
135
|
+
this.#action(run.run_id, "resume", this.#strings.resumeRun),
|
|
136
|
+
this.#action(run.run_id, "fork", this.#strings.forkRun),
|
|
137
|
+
);
|
|
138
|
+
return row;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#action(runId: string, verb: CheckpointVerb, label: string): HTMLButtonElement {
|
|
142
|
+
const button = document.createElement("button");
|
|
143
|
+
button.type = "button";
|
|
144
|
+
button.className = `checkpoint-action checkpoint-${verb}`;
|
|
145
|
+
button.setAttribute("part", `checkpoint-action checkpoint-${verb}`);
|
|
146
|
+
button.textContent = label;
|
|
147
|
+
button.addEventListener("click", () => {
|
|
148
|
+
this.close();
|
|
149
|
+
this.#onPick(runId, verb);
|
|
150
|
+
});
|
|
151
|
+
return button;
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/ui/styles.ts
CHANGED
|
@@ -1229,6 +1229,82 @@ export const STYLES = `
|
|
|
1229
1229
|
display: none;
|
|
1230
1230
|
}
|
|
1231
1231
|
|
|
1232
|
+
.checkpoints {
|
|
1233
|
+
position: absolute;
|
|
1234
|
+
inset-block-start: 3rem;
|
|
1235
|
+
inset-inline: 0.75rem;
|
|
1236
|
+
z-index: 6;
|
|
1237
|
+
display: flex;
|
|
1238
|
+
flex-direction: column;
|
|
1239
|
+
gap: 0.25rem;
|
|
1240
|
+
padding: 0.5rem;
|
|
1241
|
+
border: 1px solid var(--agui-border, #d7d7dc);
|
|
1242
|
+
border-radius: 0.5rem;
|
|
1243
|
+
background: var(--agui-surface, #fff);
|
|
1244
|
+
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
1245
|
+
max-height: 60%;
|
|
1246
|
+
overflow-y: auto;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.checkpoints[hidden] {
|
|
1250
|
+
display: none;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.checkpoints-title {
|
|
1254
|
+
font-size: 0.75rem;
|
|
1255
|
+
font-weight: 600;
|
|
1256
|
+
opacity: 0.7;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.checkpoints-empty {
|
|
1260
|
+
padding: 0.5rem 0.25rem;
|
|
1261
|
+
font-size: 0.8125rem;
|
|
1262
|
+
opacity: 0.7;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.checkpoint-row {
|
|
1266
|
+
display: flex;
|
|
1267
|
+
align-items: center;
|
|
1268
|
+
gap: 0.5rem;
|
|
1269
|
+
padding: 0.25rem;
|
|
1270
|
+
border-radius: 0.375rem;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
.checkpoint-row:hover {
|
|
1274
|
+
background: var(--agui-hover, #f3f3f5);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.checkpoint-label {
|
|
1278
|
+
flex: 1;
|
|
1279
|
+
font-size: 0.8125rem;
|
|
1280
|
+
white-space: nowrap;
|
|
1281
|
+
overflow: hidden;
|
|
1282
|
+
text-overflow: ellipsis;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.checkpoint-branch {
|
|
1286
|
+
font-size: 0.6875rem;
|
|
1287
|
+
padding: 0 0.375rem;
|
|
1288
|
+
border-radius: 999px;
|
|
1289
|
+
background: var(--agui-hover, #f3f3f5);
|
|
1290
|
+
opacity: 0.8;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.checkpoint-action {
|
|
1294
|
+
font: inherit;
|
|
1295
|
+
font-size: 0.75rem;
|
|
1296
|
+
cursor: pointer;
|
|
1297
|
+
padding: 0.125rem 0.5rem;
|
|
1298
|
+
border: 1px solid var(--agui-border, #d7d7dc);
|
|
1299
|
+
border-radius: 0.375rem;
|
|
1300
|
+
background: transparent;
|
|
1301
|
+
color: inherit;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.checkpoint-action:hover {
|
|
1305
|
+
background: var(--agui-hover, #f3f3f5);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1232
1308
|
.drawer-backdrop {
|
|
1233
1309
|
position: absolute;
|
|
1234
1310
|
inset: 0;
|
package/src/ui/ui_strings.ts
CHANGED
|
@@ -157,6 +157,16 @@ export interface UiStrings {
|
|
|
157
157
|
minutesAgo: string;
|
|
158
158
|
/** Hours ago. Token: `{n}`. */
|
|
159
159
|
hoursAgo: string;
|
|
160
|
+
/** Title of the checkpoint panel. */
|
|
161
|
+
checkpoints: string;
|
|
162
|
+
/** Empty state when no run can be continued. */
|
|
163
|
+
noCheckpoints: string;
|
|
164
|
+
/** Action: continue a run from its last checkpoint. */
|
|
165
|
+
resumeRun: string;
|
|
166
|
+
/** Action: branch a run without touching the original. */
|
|
167
|
+
forkRun: string;
|
|
168
|
+
/** Badge on a run that branched from another. */
|
|
169
|
+
forkedRun: string;
|
|
160
170
|
/** Days ago. Token: `{n}`. */
|
|
161
171
|
daysAgo: string;
|
|
162
172
|
/** Weeks ago. Token: `{n}`. */
|
|
@@ -171,6 +181,11 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
|
|
|
171
181
|
collapse: "Collapse",
|
|
172
182
|
expand: "Expand",
|
|
173
183
|
toggleTheme: "Toggle theme",
|
|
184
|
+
checkpoints: "Continue a run",
|
|
185
|
+
noCheckpoints: "Nothing to continue yet.",
|
|
186
|
+
resumeRun: "Resume",
|
|
187
|
+
forkRun: "Fork",
|
|
188
|
+
forkedRun: "branched",
|
|
174
189
|
|
|
175
190
|
conversation: "Conversation",
|
|
176
191
|
thinking: "Assistant is thinking…",
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.12.0";
|