@artooi/ag-ui-web-component 0.3.1 → 0.5.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 +59 -1
- package/README.md +25 -1
- package/dist/ag-ui-web-component.bundle.js +251 -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/agui_client.d.ts +18 -2
- package/dist/core/agui_client.d.ts.map +1 -1
- 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/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +740 -13
- package/dist/index.js.map +4 -4
- package/dist/ui/confirmation_card.d.ts +10 -1
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- 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/core/ag_ui_chat.ts +163 -8
- package/src/core/agui_client.ts +63 -3
- package/src/core/conversation_store.ts +148 -9
- package/src/core/remote_conversation_store.ts +147 -0
- package/src/index.ts +7 -1
- package/src/ui/confirmation_card.ts +22 -0
- package/src/ui/relative_time.ts +28 -0
- package/src/ui/styles.ts +197 -0
- package/src/ui/thread_drawer.ts +200 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { ThreadMeta } from "../core/conversation_store.js";
|
|
2
|
+
import { relativeTime } from "./relative_time.js";
|
|
3
|
+
|
|
4
|
+
/** Actions the host ({@link AgUiChat}) wires to the drawer's rows. */
|
|
5
|
+
export interface ThreadDrawerCallbacks {
|
|
6
|
+
/** A row was picked — load that thread and make it active. */
|
|
7
|
+
readonly onSelect: (threadId: string) => void;
|
|
8
|
+
/** The "New chat" action — start a fresh thread. */
|
|
9
|
+
readonly onNew: () => void;
|
|
10
|
+
/** A row was renamed to `title`. */
|
|
11
|
+
readonly onRename: (threadId: string, title: string) => void;
|
|
12
|
+
/** A row was deleted (after the inline confirm). */
|
|
13
|
+
readonly onDelete: (threadId: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The chat-history drawer: a slide-over listing the user's threads (title,
|
|
18
|
+
* relative time, preview), with select / new / rename / delete actions and an
|
|
19
|
+
* empty state. Pure DOM in the spirit of {@link SkillsMenu} — the host appends
|
|
20
|
+
* {@link element}, toggles it, feeds rows via {@link setThreads}, and acts on
|
|
21
|
+
* the callbacks. The drawer is a *view*: it does not mutate the store; after a
|
|
22
|
+
* callback the host updates the store and calls {@link setThreads} to refresh.
|
|
23
|
+
*/
|
|
24
|
+
export class ThreadDrawer {
|
|
25
|
+
/** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
|
|
26
|
+
readonly element: HTMLDivElement;
|
|
27
|
+
|
|
28
|
+
readonly #callbacks: ThreadDrawerCallbacks;
|
|
29
|
+
readonly #list: HTMLDivElement;
|
|
30
|
+
#threads: readonly ThreadMeta[] = [];
|
|
31
|
+
#activeId = "";
|
|
32
|
+
|
|
33
|
+
constructor(callbacks: ThreadDrawerCallbacks) {
|
|
34
|
+
this.#callbacks = callbacks;
|
|
35
|
+
|
|
36
|
+
this.element = document.createElement("div");
|
|
37
|
+
this.element.className = "drawer";
|
|
38
|
+
this.element.hidden = true;
|
|
39
|
+
|
|
40
|
+
const backdrop = document.createElement("div");
|
|
41
|
+
backdrop.className = "drawer-backdrop";
|
|
42
|
+
backdrop.addEventListener("click", () => this.close());
|
|
43
|
+
|
|
44
|
+
const panel = document.createElement("div");
|
|
45
|
+
panel.className = "drawer-panel";
|
|
46
|
+
panel.setAttribute("role", "dialog");
|
|
47
|
+
panel.setAttribute("aria-label", "Chat history");
|
|
48
|
+
|
|
49
|
+
const header = document.createElement("div");
|
|
50
|
+
header.className = "drawer-header";
|
|
51
|
+
const heading = document.createElement("span");
|
|
52
|
+
heading.className = "drawer-title";
|
|
53
|
+
heading.textContent = "Chats";
|
|
54
|
+
const newButton = document.createElement("button");
|
|
55
|
+
newButton.type = "button";
|
|
56
|
+
newButton.className = "drawer-new";
|
|
57
|
+
newButton.textContent = "New chat";
|
|
58
|
+
newButton.addEventListener("click", () => {
|
|
59
|
+
this.close();
|
|
60
|
+
this.#callbacks.onNew();
|
|
61
|
+
});
|
|
62
|
+
header.append(heading, newButton);
|
|
63
|
+
|
|
64
|
+
this.#list = document.createElement("div");
|
|
65
|
+
this.#list.className = "drawer-list";
|
|
66
|
+
|
|
67
|
+
panel.append(header, this.#list);
|
|
68
|
+
this.element.append(backdrop, panel);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isOpen(): boolean {
|
|
72
|
+
return !this.element.hidden;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
open(): void {
|
|
76
|
+
this.element.hidden = false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
close(): void {
|
|
80
|
+
this.element.hidden = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toggle(): void {
|
|
84
|
+
this.element.hidden = !this.element.hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Render the rows (or the empty state), highlighting the active thread. */
|
|
88
|
+
setThreads(threads: readonly ThreadMeta[], activeId: string): void {
|
|
89
|
+
this.#threads = threads;
|
|
90
|
+
this.#activeId = activeId;
|
|
91
|
+
this.#renderList();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#renderList(): void {
|
|
95
|
+
this.#list.replaceChildren();
|
|
96
|
+
if (this.#threads.length === 0) {
|
|
97
|
+
const empty = document.createElement("div");
|
|
98
|
+
empty.className = "drawer-empty";
|
|
99
|
+
empty.textContent = "No conversations yet.";
|
|
100
|
+
this.#list.appendChild(empty);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const meta of this.#threads) {
|
|
104
|
+
this.#list.appendChild(this.#renderRow(meta));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#renderRow(meta: ThreadMeta): HTMLDivElement {
|
|
109
|
+
const row = document.createElement("div");
|
|
110
|
+
row.className = "drawer-row";
|
|
111
|
+
if (meta.threadId === this.#activeId) {
|
|
112
|
+
row.classList.add("drawer-row--active");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const select = document.createElement("button");
|
|
116
|
+
select.type = "button";
|
|
117
|
+
select.className = "drawer-row-select";
|
|
118
|
+
const title = document.createElement("span");
|
|
119
|
+
title.className = "drawer-row-title";
|
|
120
|
+
title.textContent = meta.title;
|
|
121
|
+
const time = document.createElement("span");
|
|
122
|
+
time.className = "drawer-row-time";
|
|
123
|
+
time.textContent = relativeTime(meta.updatedAt);
|
|
124
|
+
const preview = document.createElement("span");
|
|
125
|
+
preview.className = "drawer-row-preview";
|
|
126
|
+
preview.textContent = meta.preview;
|
|
127
|
+
select.append(title, time, preview);
|
|
128
|
+
select.addEventListener("click", () => {
|
|
129
|
+
this.close();
|
|
130
|
+
this.#callbacks.onSelect(meta.threadId);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const rename = document.createElement("button");
|
|
134
|
+
rename.type = "button";
|
|
135
|
+
rename.className = "drawer-row-rename";
|
|
136
|
+
rename.title = "Rename";
|
|
137
|
+
rename.setAttribute("aria-label", "Rename conversation");
|
|
138
|
+
rename.textContent = "✎";
|
|
139
|
+
rename.addEventListener("click", () => this.#startRename(row, meta));
|
|
140
|
+
|
|
141
|
+
const remove = document.createElement("button");
|
|
142
|
+
remove.type = "button";
|
|
143
|
+
remove.className = "drawer-row-delete";
|
|
144
|
+
remove.title = "Delete";
|
|
145
|
+
remove.setAttribute("aria-label", "Delete conversation");
|
|
146
|
+
remove.textContent = "🗑";
|
|
147
|
+
remove.addEventListener("click", () => this.#confirmDelete(row, meta));
|
|
148
|
+
|
|
149
|
+
const actions = document.createElement("div");
|
|
150
|
+
actions.className = "drawer-row-actions";
|
|
151
|
+
actions.append(rename, remove);
|
|
152
|
+
|
|
153
|
+
row.append(select, actions);
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Swap a row for an inline rename input; Enter commits, Escape cancels. */
|
|
158
|
+
#startRename(row: HTMLDivElement, meta: ThreadMeta): void {
|
|
159
|
+
const input = document.createElement("input");
|
|
160
|
+
input.type = "text";
|
|
161
|
+
input.className = "drawer-rename-input";
|
|
162
|
+
input.value = meta.title;
|
|
163
|
+
input.addEventListener("keydown", (event) => {
|
|
164
|
+
if (event.key === "Enter") {
|
|
165
|
+
const value = input.value.trim();
|
|
166
|
+
if (value === "") {
|
|
167
|
+
this.#renderList();
|
|
168
|
+
} else {
|
|
169
|
+
this.#callbacks.onRename(meta.threadId, value);
|
|
170
|
+
}
|
|
171
|
+
} else if (event.key === "Escape") {
|
|
172
|
+
this.#renderList();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
row.replaceChildren(input);
|
|
176
|
+
input.focus();
|
|
177
|
+
input.select();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Swap a row for an inline "Delete? [Delete] [Cancel]" confirm. */
|
|
181
|
+
#confirmDelete(row: HTMLDivElement, meta: ThreadMeta): void {
|
|
182
|
+
const confirm = document.createElement("div");
|
|
183
|
+
confirm.className = "drawer-confirm";
|
|
184
|
+
const label = document.createElement("span");
|
|
185
|
+
label.className = "drawer-confirm-label";
|
|
186
|
+
label.textContent = "Delete?";
|
|
187
|
+
const yes = document.createElement("button");
|
|
188
|
+
yes.type = "button";
|
|
189
|
+
yes.className = "drawer-confirm-yes";
|
|
190
|
+
yes.textContent = "Delete";
|
|
191
|
+
yes.addEventListener("click", () => this.#callbacks.onDelete(meta.threadId));
|
|
192
|
+
const no = document.createElement("button");
|
|
193
|
+
no.type = "button";
|
|
194
|
+
no.className = "drawer-confirm-no";
|
|
195
|
+
no.textContent = "Cancel";
|
|
196
|
+
no.addEventListener("click", () => this.#renderList());
|
|
197
|
+
confirm.append(label, yes, no);
|
|
198
|
+
row.replaceChildren(confirm);
|
|
199
|
+
}
|
|
200
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.5.0";
|