@artooi/ag-ui-web-component 0.34.0 → 0.35.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +501 -1
  2. package/README.md +232 -13
  3. package/dist/ag-ui-web-component.bundle.js +614 -96
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +76 -3
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +37 -2
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/dom/animations.d.ts +14 -0
  10. package/dist/dom/animations.d.ts.map +1 -1
  11. package/dist/dom/highlight_overlay.d.ts +47 -0
  12. package/dist/dom/highlight_overlay.d.ts.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1886 -320
  16. package/dist/index.js.map +4 -4
  17. package/dist/tools/chat_surface_tools.d.ts +96 -0
  18. package/dist/tools/chat_surface_tools.d.ts.map +1 -0
  19. package/dist/tools/page_action_tools.d.ts +2 -0
  20. package/dist/tools/page_action_tools.d.ts.map +1 -1
  21. package/dist/ui/clamp_launcher.d.ts +10 -5
  22. package/dist/ui/clamp_launcher.d.ts.map +1 -1
  23. package/dist/ui/clamp_panel.d.ts +2 -2
  24. package/dist/ui/clamp_panel.d.ts.map +1 -1
  25. package/dist/ui/launcher_drag.d.ts +2 -2
  26. package/dist/ui/launcher_drag.d.ts.map +1 -1
  27. package/dist/ui/launcher_placement.d.ts +14 -1
  28. package/dist/ui/launcher_placement.d.ts.map +1 -1
  29. package/dist/ui/panel_drag.d.ts.map +1 -1
  30. package/dist/ui/place_widget.d.ts +9 -1
  31. package/dist/ui/place_widget.d.ts.map +1 -1
  32. package/dist/ui/run_notice.d.ts +14 -3
  33. package/dist/ui/run_notice.d.ts.map +1 -1
  34. package/dist/ui/styles.d.ts +1 -1
  35. package/dist/ui/styles.d.ts.map +1 -1
  36. package/dist/ui/thread_drawer.d.ts +21 -0
  37. package/dist/ui/thread_drawer.d.ts.map +1 -1
  38. package/dist/ui/ui_strings.d.ts +16 -0
  39. package/dist/ui/ui_strings.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/constants.ts +82 -3
  42. package/src/core/ag_ui_chat.ts +965 -55
  43. package/src/dom/animations.ts +30 -0
  44. package/src/dom/highlight_overlay.ts +256 -0
  45. package/src/index.ts +12 -0
  46. package/src/tools/chat_surface_tools.ts +207 -0
  47. package/src/tools/page_action_tools.ts +2 -0
  48. package/src/ui/clamp_launcher.ts +25 -7
  49. package/src/ui/clamp_panel.ts +10 -4
  50. package/src/ui/launcher_drag.ts +11 -2
  51. package/src/ui/launcher_placement.ts +34 -8
  52. package/src/ui/panel_drag.ts +4 -0
  53. package/src/ui/place_widget.ts +11 -3
  54. package/src/ui/run_notice.ts +32 -3
  55. package/src/ui/styles.ts +563 -45
  56. package/src/ui/thread_drawer.ts +138 -8
  57. package/src/ui/ui_strings.ts +24 -0
  58. package/src/version.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { EDGE_MARGIN } from "../constants.js";
1
+ import { SCREEN_EDGE_MARGIN } from "../constants.js";
2
2
  import { clampPanel } from "./clamp_panel.js";
3
3
  import { placeWidget } from "./place_widget.js";
4
4
 
@@ -10,6 +10,20 @@ export interface LauncherBox {
10
10
  readonly height: number;
11
11
  }
12
12
 
13
+ /**
14
+ * The part of the screen a widget may rest in: a width and height, and the
15
+ * corner they start from.
16
+ *
17
+ * The origin is not always zero. A host can reserve the edges its own chrome
18
+ * occupies, and a panel clamped against a viewport that starts at the top-left
19
+ * of the screen will happily park itself underneath a sticky header -- where it
20
+ * cannot be reached, and where collapsing it only hides it further.
21
+ */
22
+ export interface ViewportBox extends Extent {
23
+ readonly left: number;
24
+ readonly top: number;
25
+ }
26
+
13
27
  /** A width/height pair, in CSS pixels. */
14
28
  export interface Extent {
15
29
  readonly width: number;
@@ -71,15 +85,24 @@ export interface LauncherPlacement {
71
85
  export function launcherPlacement(
72
86
  launcher: LauncherBox,
73
87
  panel: Extent,
74
- viewport: Extent,
75
- margin: number = EDGE_MARGIN,
88
+ viewport: ViewportBox,
89
+ screen: Extent,
90
+ margin: number = SCREEN_EDGE_MARGIN,
76
91
  ): LauncherPlacement {
77
92
  // Room for a panel pinned to each side of the launcher. A tie goes to the
78
93
  // first branch, so the result is deterministic for a centred launcher.
79
- const roomRunningRight = viewport.width - launcher.left;
80
- const roomRunningLeft = launcher.left + launcher.width;
81
- const roomRunningDown = viewport.height - launcher.top;
82
- const roomRunningUp = launcher.top + launcher.height;
94
+ //
95
+ // Measured against the usable box's own edges, not against its width and
96
+ // height as if it started at the origin. The launcher's coordinates are the
97
+ // screen's, while the extents have already had the host's reserved edges
98
+ // taken out of them, so mixing the two understates the room on one side and
99
+ // overstates it on the other -- by the same reserved inset, in opposite
100
+ // directions, which is what makes the comparison flip rather than merely
101
+ // drift. The clamp below reads `viewport.left`/`top`; this had to as well.
102
+ const roomRunningRight = viewport.left + viewport.width - launcher.left;
103
+ const roomRunningLeft = launcher.left + launcher.width - viewport.left;
104
+ const roomRunningDown = viewport.top + viewport.height - launcher.top;
105
+ const roomRunningUp = launcher.top + launcher.height - viewport.top;
83
106
  const corner: ExpandCorner = {
84
107
  x: roomRunningRight >= roomRunningLeft ? "left" : "right",
85
108
  y: roomRunningDown >= roomRunningUp ? "top" : "bottom",
@@ -103,5 +126,8 @@ export function launcherPlacement(
103
126
  margin,
104
127
  );
105
128
 
106
- return { corner, ...placeWidget(host, launcher, corner, viewport) };
129
+ // The usable box decides where things may rest; the screen is what the
130
+ // resulting insets are measured from. They are the same only when the host
131
+ // has reserved nothing.
132
+ return { corner, ...placeWidget(host, launcher, corner, screen) };
107
133
  }
@@ -99,6 +99,7 @@ export function enablePanelDrag(handle: HTMLElement, options: PanelDragOptions):
99
99
  const onUp = (up: PointerEvent): void => {
100
100
  window.removeEventListener("pointermove", onMove);
101
101
  window.removeEventListener("pointerup", onUp);
102
+ window.removeEventListener("pointercancel", onUp);
102
103
  if (!dragging) {
103
104
  return;
104
105
  }
@@ -113,6 +114,9 @@ export function enablePanelDrag(handle: HTMLElement, options: PanelDragOptions):
113
114
  // and would otherwise strand the panel mid-move with no pointerup.
114
115
  window.addEventListener("pointermove", onMove);
115
116
  window.addEventListener("pointerup", onUp);
117
+ // Routine on touch rather than exceptional: the browser takes the pointer
118
+ // back for a scroll or a system gesture and never sends pointerup.
119
+ window.addEventListener("pointercancel", onUp);
116
120
  });
117
121
  }
118
122
 
@@ -20,18 +20,26 @@ export interface WidgetInsets {
20
20
  * moves nothing, and the launcher may sit outside its own host box. Nothing
21
21
  * clips it there, and that is what lets a launcher be flush to a screen corner
22
22
  * while the panel it opens keeps its margin.
23
+ *
24
+ * `screen` is the **whole** viewport, not the part a host has left free. These
25
+ * are CSS `inset` values on a fixed element, and the browser measures those
26
+ * from the real edges -- so a `bottom` expressed against a box inset from the
27
+ * top comes out short by exactly that inset. It only shows when the corner
28
+ * flips mid-drag, because that is when the same point stops being expressed
29
+ * from `top` and starts being expressed from `bottom`: the widget then leaps by
30
+ * the reserved edge, which is a jump the gesture cannot explain.
23
31
  */
24
32
  export function placeWidget(
25
33
  host: PanelRect,
26
34
  launcher: LauncherBox,
27
35
  corner: ExpandCorner,
28
- viewport: Extent,
36
+ screen: Extent,
29
37
  ): WidgetInsets {
30
38
  return {
31
39
  hostInset: inset({
32
40
  top: corner.y === "top" ? host.top : null,
33
- right: corner.x === "right" ? viewport.width - host.right : null,
34
- bottom: corner.y === "bottom" ? viewport.height - host.bottom : null,
41
+ right: corner.x === "right" ? screen.width - host.right : null,
42
+ bottom: corner.y === "bottom" ? screen.height - host.bottom : null,
35
43
  left: corner.x === "left" ? host.left : null,
36
44
  }),
37
45
  launcherInset: inset({
@@ -3,10 +3,23 @@
3
3
  * skill loaded), rendered inline between turns.
4
4
  *
5
5
  * Distinct from a tool card, which reports work the agent asked for and
6
- * settles, and from an error, which is a failure. A notice never settles, takes
7
- * no action, and carries no controls.
6
+ * settles, and from an error, which is a failure. A notice never settles and
7
+ * takes no action of its own.
8
+ *
9
+ * It may carry exactly one control, and only ever an undo. That is a narrower
10
+ * rule than "no controls", which is what this said until the agent could move
11
+ * the panel it speaks from: something that rearranges the user's window without
12
+ * being asked has to be both visible and reversible, and a notice is already
13
+ * the surface that says what the run did. Anything the user has to *decide* is
14
+ * a confirmation card instead -- the difference is that this reports something
15
+ * already done.
8
16
  */
9
- export function renderRunNotice(icon: string, text: string, kind: string): HTMLDivElement {
17
+ export function renderRunNotice(
18
+ icon: string,
19
+ text: string,
20
+ kind: string,
21
+ undo?: { readonly label: string; readonly onActivate: () => void },
22
+ ): HTMLDivElement {
10
23
  const notice = document.createElement("div");
11
24
  notice.className = `run-notice run-notice--${kind}`;
12
25
  notice.setAttribute("part", `run-notice run-notice-${kind}`);
@@ -28,5 +41,21 @@ export function renderRunNotice(icon: string, text: string, kind: string): HTMLD
28
41
  label.textContent = text;
29
42
 
30
43
  notice.append(glyph, label);
44
+
45
+ if (undo !== undefined) {
46
+ const button = document.createElement("button");
47
+ button.type = "button";
48
+ button.className = "run-notice-undo";
49
+ button.setAttribute("part", "run-notice-undo");
50
+ button.textContent = undo.label;
51
+ button.addEventListener("click", () => {
52
+ // One use. The state it restores is the state as it was when the notice
53
+ // was written, so offering it twice would put back something that has
54
+ // since moved again.
55
+ button.disabled = true;
56
+ undo.onActivate();
57
+ });
58
+ notice.append(button);
59
+ }
31
60
  return notice;
32
61
  }