sgiant-ai-widget 1.0.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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/ai-invalidation.d.ts +29 -0
  4. package/dist/ai-invalidation.d.ts.map +1 -0
  5. package/dist/ai-invalidation.js +121 -0
  6. package/dist/ai-invalidation.js.map +1 -0
  7. package/dist/contrast.d.ts +87 -0
  8. package/dist/contrast.d.ts.map +1 -0
  9. package/dist/contrast.js +151 -0
  10. package/dist/contrast.js.map +1 -0
  11. package/dist/directive.d.ts +37 -0
  12. package/dist/directive.d.ts.map +1 -0
  13. package/dist/directive.js +87 -0
  14. package/dist/directive.js.map +1 -0
  15. package/dist/dom.d.ts +83 -0
  16. package/dist/dom.d.ts.map +1 -0
  17. package/dist/dom.js +146 -0
  18. package/dist/dom.js.map +1 -0
  19. package/dist/host-actions.d.ts +192 -0
  20. package/dist/host-actions.d.ts.map +1 -0
  21. package/dist/host-actions.js +443 -0
  22. package/dist/host-actions.js.map +1 -0
  23. package/dist/icons.d.ts +21 -0
  24. package/dist/icons.d.ts.map +1 -0
  25. package/dist/icons.js +84 -0
  26. package/dist/icons.js.map +1 -0
  27. package/dist/index.d.ts +600 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +4833 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/labels.d.ts +187 -0
  32. package/dist/labels.d.ts.map +1 -0
  33. package/dist/labels.js +232 -0
  34. package/dist/labels.js.map +1 -0
  35. package/dist/limits.d.ts +43 -0
  36. package/dist/limits.d.ts.map +1 -0
  37. package/dist/limits.js +48 -0
  38. package/dist/limits.js.map +1 -0
  39. package/dist/markdown.d.ts +23 -0
  40. package/dist/markdown.d.ts.map +1 -0
  41. package/dist/markdown.js +0 -0
  42. package/dist/markdown.js.map +1 -0
  43. package/dist/message-chrome.d.ts +45 -0
  44. package/dist/message-chrome.d.ts.map +1 -0
  45. package/dist/message-chrome.js +101 -0
  46. package/dist/message-chrome.js.map +1 -0
  47. package/dist/pane-follow.d.ts +67 -0
  48. package/dist/pane-follow.d.ts.map +1 -0
  49. package/dist/pane-follow.js +61 -0
  50. package/dist/pane-follow.js.map +1 -0
  51. package/dist/prefix.d.ts +9 -0
  52. package/dist/prefix.d.ts.map +1 -0
  53. package/dist/prefix.js +9 -0
  54. package/dist/prefix.js.map +1 -0
  55. package/dist/proposal-summary.d.ts +19 -0
  56. package/dist/proposal-summary.d.ts.map +1 -0
  57. package/dist/proposal-summary.js +73 -0
  58. package/dist/proposal-summary.js.map +1 -0
  59. package/dist/replay.d.ts +125 -0
  60. package/dist/replay.d.ts.map +1 -0
  61. package/dist/replay.js +172 -0
  62. package/dist/replay.js.map +1 -0
  63. package/dist/safe-url.d.ts +47 -0
  64. package/dist/safe-url.d.ts.map +1 -0
  65. package/dist/safe-url.js +80 -0
  66. package/dist/safe-url.js.map +1 -0
  67. package/dist/sgiant-ai-widget.global.js +778 -0
  68. package/dist/sgiant-ai-widget.global.js.map +7 -0
  69. package/dist/specs.d.ts +173 -0
  70. package/dist/specs.d.ts.map +1 -0
  71. package/dist/specs.js +231 -0
  72. package/dist/specs.js.map +1 -0
  73. package/dist/storage.d.ts +77 -0
  74. package/dist/storage.d.ts.map +1 -0
  75. package/dist/storage.js +124 -0
  76. package/dist/storage.js.map +1 -0
  77. package/dist/styles.d.ts +29 -0
  78. package/dist/styles.d.ts.map +1 -0
  79. package/dist/styles.js +876 -0
  80. package/dist/styles.js.map +1 -0
  81. package/dist/ui-control.d.ts +8 -0
  82. package/dist/ui-control.d.ts.map +1 -0
  83. package/dist/ui-control.js +8 -0
  84. package/dist/ui-control.js.map +1 -0
  85. package/dist/ui-render.d.ts +75 -0
  86. package/dist/ui-render.d.ts.map +1 -0
  87. package/dist/ui-render.js +617 -0
  88. package/dist/ui-render.js.map +1 -0
  89. package/dist/ui-spec.d.ts +143 -0
  90. package/dist/ui-spec.d.ts.map +1 -0
  91. package/dist/ui-spec.js +288 -0
  92. package/dist/ui-spec.js.map +1 -0
  93. package/package.json +61 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The component vocabulary the assistant composes chat UI from — `[[ui:{…}]]`.
3
+ *
4
+ * WHY THIS EXISTS. Every rich thing the chat could draw used to need frontend
5
+ * code: a card kind here, a host renderer there, a release before the model
6
+ * could show it. So the chat could only ever show what the UI had been built to
7
+ * show, and each new scenario (a storyboard, a shortlist, a room-type picker)
8
+ * meant the same work again. This is the substrate that ends that: a small set
9
+ * of primitives the widget knows how to draw, composed BY THE MODEL per
10
+ * situation. A new scenario becomes a spec, not a release.
11
+ *
12
+ * It is deliberately a VOCABULARY and not a language. The model cannot send
13
+ * markup or code — it names primitives (a tile, a status, a button) and the
14
+ * widget draws them in its own styles. That keeps every card looking like the
15
+ * product rather than like whatever the model imagined this time, and it keeps
16
+ * model output out of the DOM.
17
+ *
18
+ * Everything here is PURE so it can be tested without a browser: the widget is
19
+ * imperative DOM with no test harness, and the rules below (what is dropped,
20
+ * what is capped, what a missing field means) are exactly the part worth
21
+ * pinning down.
22
+ */
23
+ /**
24
+ * How `items` are laid out. Unknown values fall back to a strip.
25
+ *
26
+ * "carousel" shows ONE item at a time with paging, which is the right shape for
27
+ * a set the client is judging rather than scanning: a reel's scenes want the
28
+ * space to be looked at properly, and a strip of six thumbnails invites
29
+ * approving what was never really seen. The other layouts remain right for
30
+ * picking from many (grid) or reading down a short list (list).
31
+ */
32
+ export type UiLayout = "strip" | "grid" | "list" | "carousel";
33
+ /**
34
+ * The one action name the WIDGET answers itself: send `data.text` (falling back
35
+ * to the button's label) back into the conversation as if the user had typed
36
+ * it. Everything else goes to the host by name.
37
+ *
38
+ * This is what makes the vocabulary self-sufficient. Without it, every button a
39
+ * composed card wants — "Redo scene 2", "Make it warmer", "Use the sunset one"
40
+ * — is a capability the HOST has to implement and name in advance, which is the
41
+ * per-scenario frontend work this whole design exists to remove. With it, the
42
+ * assistant's own tools are the implementation: the button says the thing, the
43
+ * assistant does it, and a card can offer an action nobody wrote code for.
44
+ *
45
+ * Precedent, not novelty: `[[chips:…]]` already answers by sending the chosen
46
+ * text as an ordinary message, which keeps the transcript in order and honest —
47
+ * the click is visible in the conversation as the sentence it stood for.
48
+ */
49
+ export declare const UI_SAY_ACTION = "say";
50
+ /**
51
+ * A button on a card or tile. Dispatched through the host's existing
52
+ * `onWidgetAction(name, data)` — the same path `[[action:…]]` already uses, so
53
+ * a UI card can only ever do what the host already permits by name — except for
54
+ * `say` (see UI_SAY_ACTION), which the widget answers itself.
55
+ */
56
+ export interface UiAction {
57
+ name: string;
58
+ label?: string;
59
+ /** When set, the user must approve before the host is called. */
60
+ confirm?: string;
61
+ data?: Record<string, string>;
62
+ variant?: "primary" | "default" | "danger";
63
+ }
64
+ /** One thing in the card: a picture/clip with a caption, a state and buttons. */
65
+ export interface UiItem {
66
+ /** Caller's own id for this item, echoed back in an action's `data.itemId`
67
+ * so a handler knows WHICH tile was acted on without the model having to
68
+ * repeat it in every button. */
69
+ id?: string;
70
+ title?: string;
71
+ caption?: string;
72
+ /** Library media to show. An ID, never a URL — see `sanitizeMediaId`. */
73
+ mediaId?: string;
74
+ /** Still to show for a video tile before it plays. */
75
+ posterMediaId?: string;
76
+ /** How to draw `mediaId`. Inferred by the widget when omitted — but a signed
77
+ * URL carries no extension, so AUDIO in particular must be declared or it
78
+ * is drawn as a broken picture. */
79
+ media?: "image" | "video" | "audio";
80
+ /** Drawn as a badge; a known one is also coloured. */
81
+ status?: UiStatus;
82
+ /**
83
+ * A running job this tile is the view of — the card stops being a snapshot.
84
+ *
85
+ * Without it, a storyboard card drawn while six clips render is stale the
86
+ * moment it paints: the client scrolls back an hour later to a rail of
87
+ * "rendering" badges over empty boxes, with the finished clips sitting in the
88
+ * library. The tile subscribes to the job's existing poll and fills in its
89
+ * own media and status when the render lands.
90
+ *
91
+ * The GENERIC job id (what `/accounts/:id/jobs/:jobId` answers), not a
92
+ * media_job detail id — the wrong one 404s, and a 404 makes the widget
93
+ * conclude the job is gone.
94
+ */
95
+ jobId?: string;
96
+ actions?: UiAction[];
97
+ }
98
+ export interface UiSpec {
99
+ title?: string;
100
+ /** A sentence above the items — what the card is for. */
101
+ caption?: string;
102
+ layout: UiLayout;
103
+ /** Shape of every tile's media box. */
104
+ aspect: UiAspect;
105
+ items: UiItem[];
106
+ /** Card-level buttons, under the items. */
107
+ actions: UiAction[];
108
+ }
109
+ /**
110
+ * The shape tiles are drawn in — named format keywords ("square" 1:1,
111
+ * "portrait" 4:5, "story" 9:16, "landscape" 4:3, "wide" 16:9) rather than
112
+ * raw ratios, so the model describes a card in the same vocabulary the
113
+ * social platforms use.
114
+ *
115
+ * Card-level rather than per-tile: the items of one card share a format, and
116
+ * a strip whose tiles were each a different shape would read as a mistake. It
117
+ * matters because portrait media previewed in square tiles shows the client a
118
+ * crop of what they are looking at, and they approve it anyway.
119
+ */
120
+ export type UiAspect = "square" | "portrait" | "story" | "landscape" | "wide";
121
+ /**
122
+ * States a tile can be in. Kept OPEN — an unrecognised status is still shown,
123
+ * just without semantic colour, because the alternative is that the model
124
+ * cannot describe a state until the frontend ships a name for it, which is the
125
+ * exact coupling this whole file exists to remove.
126
+ */
127
+ export type UiStatus = string;
128
+ /** The four the widget colours. Everything else is drawn neutral. */
129
+ export declare const KNOWN_UI_STATUSES: readonly ["pending", "running", "ready", "failed"];
130
+ /**
131
+ * Turn whatever the model emitted into a spec the widget can draw, or null if
132
+ * there is nothing worth drawing.
133
+ *
134
+ * Null rather than an empty card on purpose: the caller leaves the reply's text
135
+ * alone, so a malformed directive costs the user nothing. A card that renders
136
+ * as an empty box would instead look like the assistant tried to show something
137
+ * and the product lost it.
138
+ */
139
+ export declare function normalizeUiSpec(input: unknown): UiSpec | null;
140
+ /** Every media id a spec will need resolved, in one list — so the host makes
141
+ * ONE batched round-trip instead of a request per tile. */
142
+ export declare function uiSpecMediaIds(spec: UiSpec): string[];
143
+ //# sourceMappingURL=ui-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-spec.d.ts","sourceRoot":"","sources":["../src/ui-spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAE9D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,QAAQ,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC5C;AAED,iFAAiF;AACjF,MAAM,WAAW,MAAM;IACrB;;qCAEiC;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;wCAEoC;IACpC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,sDAAsD;IACtD,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2CAA2C;IAC3C,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AAU9E;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,oDAKpB,CAAC;AAoLX;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAiC7D;AAED;4DAC4D;AAC5D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOrD"}
@@ -0,0 +1,288 @@
1
+ /**
2
+ * The component vocabulary the assistant composes chat UI from — `[[ui:{…}]]`.
3
+ *
4
+ * WHY THIS EXISTS. Every rich thing the chat could draw used to need frontend
5
+ * code: a card kind here, a host renderer there, a release before the model
6
+ * could show it. So the chat could only ever show what the UI had been built to
7
+ * show, and each new scenario (a storyboard, a shortlist, a room-type picker)
8
+ * meant the same work again. This is the substrate that ends that: a small set
9
+ * of primitives the widget knows how to draw, composed BY THE MODEL per
10
+ * situation. A new scenario becomes a spec, not a release.
11
+ *
12
+ * It is deliberately a VOCABULARY and not a language. The model cannot send
13
+ * markup or code — it names primitives (a tile, a status, a button) and the
14
+ * widget draws them in its own styles. That keeps every card looking like the
15
+ * product rather than like whatever the model imagined this time, and it keeps
16
+ * model output out of the DOM.
17
+ *
18
+ * Everything here is PURE so it can be tested without a browser: the widget is
19
+ * imperative DOM with no test harness, and the rules below (what is dropped,
20
+ * what is capped, what a missing field means) are exactly the part worth
21
+ * pinning down.
22
+ */
23
+ /**
24
+ * The one action name the WIDGET answers itself: send `data.text` (falling back
25
+ * to the button's label) back into the conversation as if the user had typed
26
+ * it. Everything else goes to the host by name.
27
+ *
28
+ * This is what makes the vocabulary self-sufficient. Without it, every button a
29
+ * composed card wants — "Redo scene 2", "Make it warmer", "Use the sunset one"
30
+ * — is a capability the HOST has to implement and name in advance, which is the
31
+ * per-scenario frontend work this whole design exists to remove. With it, the
32
+ * assistant's own tools are the implementation: the button says the thing, the
33
+ * assistant does it, and a card can offer an action nobody wrote code for.
34
+ *
35
+ * Precedent, not novelty: `[[chips:…]]` already answers by sending the chosen
36
+ * text as an ordinary message, which keeps the transcript in order and honest —
37
+ * the click is visible in the conversation as the sentence it stood for.
38
+ */
39
+ export const UI_SAY_ACTION = "say";
40
+ const ASPECTS = [
41
+ "square",
42
+ "portrait",
43
+ "story",
44
+ "landscape",
45
+ "wide",
46
+ ];
47
+ /** The four the widget colours. Everything else is drawn neutral. */
48
+ export const KNOWN_UI_STATUSES = [
49
+ "pending",
50
+ "running",
51
+ "ready",
52
+ "failed",
53
+ ];
54
+ /** Words the model reaches for that mean one of the four. Being forgiving here
55
+ * is cheaper than a badge that silently loses its colour because the model
56
+ * wrote "rendering" instead of "running". */
57
+ const STATUS_SYNONYMS = {
58
+ queued: "pending",
59
+ waiting: "pending",
60
+ rendering: "running",
61
+ generating: "running",
62
+ processing: "running",
63
+ in_progress: "running",
64
+ done: "ready",
65
+ complete: "ready",
66
+ completed: "ready",
67
+ ok: "ready",
68
+ success: "ready",
69
+ error: "failed",
70
+ cancelled: "failed",
71
+ canceled: "failed",
72
+ };
73
+ /**
74
+ * Caps. A spec is model output, so it is not a promise of reasonable size: an
75
+ * 800-item strip would lock the DOM, and a 4000-character caption would push
76
+ * the composer off screen. Generous enough that no honest card hits them.
77
+ */
78
+ const MAX_ITEMS = 24;
79
+ const MAX_ACTIONS = 4;
80
+ const MAX_TEXT = 240;
81
+ const MAX_DATA_KEYS = 12;
82
+ const clamp = (v, max = MAX_TEXT) => {
83
+ if (typeof v !== "string")
84
+ return undefined;
85
+ const s = v.trim();
86
+ if (!s)
87
+ return undefined;
88
+ return s.length > max ? `${s.slice(0, max - 1)}…` : s;
89
+ };
90
+ const isRecord = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
91
+ /**
92
+ * A media id, or nothing.
93
+ *
94
+ * The model may name library media only BY ID — never by URL. Model output is
95
+ * untrusted (it routinely carries text scraped from third-party pages), and a
96
+ * URL it chose would be fetched by the client's browser the moment the card
97
+ * painted: a tracking pixel, or a request to an attacker's host carrying the
98
+ * user's IP, drawn inside the product's own chat. An id can only ever be
99
+ * resolved by the host against media the ACCOUNT owns, so the worst case is a
100
+ * tile that resolves to nothing.
101
+ *
102
+ * Shape-checked rather than trusted: ids are opaque to the widget, so anything
103
+ * that isn't a plain id-looking token is refused.
104
+ */
105
+ function sanitizeMediaId(v) {
106
+ if (typeof v !== "string")
107
+ return undefined;
108
+ const s = v.trim();
109
+ if (!s || s.length > 64)
110
+ return undefined;
111
+ return /^[A-Za-z0-9_-]+$/.test(s) ? s : undefined;
112
+ }
113
+ /**
114
+ * Canonicalize a status we recognise; otherwise hand back the model's own
115
+ * wording untouched.
116
+ *
117
+ * The match is done on a squashed key so "In Progress" and "in-progress" both
118
+ * land on `running`, but an unrecognised status is DISPLAYED, and displaying
119
+ * the squashed key would put `awaiting_client_sign_off` on a badge in front of
120
+ * a client. The open half of this vocabulary is only useful if the words the
121
+ * model chose survive it.
122
+ */
123
+ function normalizeStatus(v) {
124
+ const s = clamp(v, 32);
125
+ if (!s)
126
+ return undefined;
127
+ const key = s.toLowerCase().replace(/[\s-]+/g, "_");
128
+ if (STATUS_SYNONYMS[key])
129
+ return STATUS_SYNONYMS[key];
130
+ return KNOWN_UI_STATUSES.includes(key) ? key : s;
131
+ }
132
+ /** Action `data` is passed to the host verbatim, so it is flattened to plain
133
+ * strings — a nested object would reach a handler expecting a value and fail
134
+ * somewhere far from here. */
135
+ function normalizeData(v) {
136
+ if (!isRecord(v))
137
+ return undefined;
138
+ const out = {};
139
+ for (const [k, raw] of Object.entries(v)) {
140
+ if (Object.keys(out).length >= MAX_DATA_KEYS)
141
+ break;
142
+ if (typeof raw === "string" ||
143
+ typeof raw === "number" ||
144
+ typeof raw === "boolean")
145
+ out[k] = String(raw);
146
+ }
147
+ return Object.keys(out).length ? out : undefined;
148
+ }
149
+ /**
150
+ * One button, or null.
151
+ *
152
+ * An action with no `name` is DROPPED rather than drawn inert: a button that
153
+ * dispatches nothing is indistinguishable from one that works, so the user
154
+ * clicks it, nothing happens, and they have no way to tell whether the product
155
+ * is broken or their request was refused.
156
+ */
157
+ function normalizeAction(v) {
158
+ if (!isRecord(v))
159
+ return null;
160
+ const name = clamp(v.name, 64);
161
+ if (!name)
162
+ return null;
163
+ const label = clamp(v.label, 48);
164
+ const confirm = clamp(v.confirm);
165
+ const data = normalizeData(v.data);
166
+ const variant = v.variant === "primary" || v.variant === "danger" ? v.variant : undefined;
167
+ // A `say` button with nothing to say is inert for the same reason a nameless
168
+ // one is: it looks exactly like a button that works.
169
+ if (name === UI_SAY_ACTION && !data?.text && !label)
170
+ return null;
171
+ return {
172
+ name,
173
+ ...(label ? { label } : {}),
174
+ ...(confirm ? { confirm } : {}),
175
+ ...(data ? { data } : {}),
176
+ ...(variant ? { variant } : {}),
177
+ };
178
+ }
179
+ function normalizeActions(v) {
180
+ if (!Array.isArray(v))
181
+ return [];
182
+ const out = [];
183
+ for (const raw of v) {
184
+ if (out.length >= MAX_ACTIONS)
185
+ break;
186
+ const a = normalizeAction(raw);
187
+ if (a)
188
+ out.push(a);
189
+ }
190
+ return out;
191
+ }
192
+ /**
193
+ * One tile, or null when there is nothing to show.
194
+ *
195
+ * "Nothing to show" is deliberate: an item with no media, no text and no state
196
+ * would paint as an empty box, which reads as a broken card rather than as an
197
+ * item the model chose not to describe.
198
+ */
199
+ function normalizeItem(v) {
200
+ if (!isRecord(v))
201
+ return null;
202
+ const id = clamp(v.id, 64);
203
+ const title = clamp(v.title, 80);
204
+ const caption = clamp(v.caption);
205
+ const mediaId = sanitizeMediaId(v.mediaId);
206
+ const posterMediaId = sanitizeMediaId(v.posterMediaId);
207
+ const status = normalizeStatus(v.status);
208
+ const media = v.media === "video" || v.media === "image" || v.media === "audio"
209
+ ? v.media
210
+ : undefined;
211
+ // Same opaque-token shape as a media id, and checked for the same reason: it
212
+ // is put straight into a URL the widget then polls.
213
+ const jobId = sanitizeMediaId(v.jobId);
214
+ const actions = normalizeActions(v.actions);
215
+ // A tile that is NOTHING but a pending render is the whole point of `jobId` —
216
+ // "scene 3 is being made, watch this space" — so it must survive the
217
+ // has-anything-to-show check that would otherwise drop it.
218
+ if (!mediaId && !title && !caption && !status && !jobId && !actions.length)
219
+ return null;
220
+ return {
221
+ ...(id ? { id } : {}),
222
+ ...(title ? { title } : {}),
223
+ ...(caption ? { caption } : {}),
224
+ ...(mediaId ? { mediaId } : {}),
225
+ ...(posterMediaId ? { posterMediaId } : {}),
226
+ ...(media ? { media } : {}),
227
+ ...(status ? { status } : {}),
228
+ ...(jobId ? { jobId } : {}),
229
+ ...(actions.length ? { actions } : {}),
230
+ };
231
+ }
232
+ /**
233
+ * Turn whatever the model emitted into a spec the widget can draw, or null if
234
+ * there is nothing worth drawing.
235
+ *
236
+ * Null rather than an empty card on purpose: the caller leaves the reply's text
237
+ * alone, so a malformed directive costs the user nothing. A card that renders
238
+ * as an empty box would instead look like the assistant tried to show something
239
+ * and the product lost it.
240
+ */
241
+ export function normalizeUiSpec(input) {
242
+ if (!isRecord(input))
243
+ return null;
244
+ const items = [];
245
+ if (Array.isArray(input.items)) {
246
+ for (const raw of input.items) {
247
+ if (items.length >= MAX_ITEMS)
248
+ break;
249
+ const it = normalizeItem(raw);
250
+ if (it)
251
+ items.push(it);
252
+ }
253
+ }
254
+ const actions = normalizeActions(input.actions);
255
+ const title = clamp(input.title, 80);
256
+ const caption = clamp(input.caption);
257
+ if (!items.length && !actions.length)
258
+ return null;
259
+ const layout = input.layout === "grid" ||
260
+ input.layout === "list" ||
261
+ input.layout === "carousel"
262
+ ? input.layout
263
+ : "strip";
264
+ const aspect = (typeof input.aspect === "string" && ASPECTS.includes(input.aspect)
265
+ ? input.aspect
266
+ : "square");
267
+ return {
268
+ ...(title ? { title } : {}),
269
+ ...(caption ? { caption } : {}),
270
+ layout,
271
+ aspect,
272
+ items,
273
+ actions,
274
+ };
275
+ }
276
+ /** Every media id a spec will need resolved, in one list — so the host makes
277
+ * ONE batched round-trip instead of a request per tile. */
278
+ export function uiSpecMediaIds(spec) {
279
+ const ids = new Set();
280
+ for (const it of spec.items) {
281
+ if (it.mediaId)
282
+ ids.add(it.mediaId);
283
+ if (it.posterMediaId)
284
+ ids.add(it.posterMediaId);
285
+ }
286
+ return [...ids];
287
+ }
288
+ //# sourceMappingURL=ui-spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-spec.js","sourceRoot":"","sources":["../src/ui-spec.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "sgiant-ai-widget",
3
+ "version": "1.0.0",
4
+ "description": "Framework-agnostic embeddable AI chat widget. One call mounts a streaming chat bubble into any page — no React, no framework, themeable.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "ai",
8
+ "chat",
9
+ "widget",
10
+ "chatbot",
11
+ "streaming",
12
+ "embeddable",
13
+ "vanilla",
14
+ "no-framework",
15
+ "llm",
16
+ "assistant"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/vedatugur/sgiant-ai-widget.git"
21
+ },
22
+ "homepage": "https://vedatugur.github.io/sgiant-ai-widget/",
23
+ "bugs": {
24
+ "url": "https://github.com/vedatugur/sgiant-ai-widget/issues"
25
+ },
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./global": "./dist/sgiant-ai-widget.global.js"
36
+ },
37
+ "unpkg": "./dist/sgiant-ai-widget.global.js",
38
+ "jsdelivr": "./dist/sgiant-ai-widget.global.js",
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsc -b && node scripts/fix-esm-extensions.mjs && npm run build:global",
44
+ "typecheck": "tsc --noEmit",
45
+ "clean": "rm -rf dist *.tsbuildinfo",
46
+ "build:global": "node scripts/build-global.mjs",
47
+ "audit": "./scripts/audit-tarball.sh",
48
+ "prepublishOnly": "npm run build && npm run audit"
49
+ },
50
+ "dependencies": {
51
+ "sgiant-ai-agent-bridge": "1.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "typescript": "^5.7.2",
55
+ "esbuild": "^0.24.0"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "registry": "https://registry.npmjs.org/"
60
+ }
61
+ }