@aganzefelicite/responsekit-react 0.1.0 → 0.2.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/README.md +28 -10
- package/dist/index.cjs +255 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +250 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css +97 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -15,21 +15,16 @@ components — safely, and without ever crashing on unknown or invalid data.
|
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
|
-
Not published to npm yet — install from a local build of this repo (`@responsekit/schema`
|
|
19
|
-
is bundled in, so it's a single tarball):
|
|
20
|
-
|
|
21
18
|
```bash
|
|
22
|
-
|
|
23
|
-
pnpm pack:local
|
|
24
|
-
# in your app
|
|
25
|
-
pnpm add /ABS/PATH/responsekit-react-0.0.0.tgz react react-dom
|
|
19
|
+
pnpm add @aganzefelicite/responsekit-react react react-dom
|
|
26
20
|
```
|
|
27
21
|
|
|
28
22
|
```ts
|
|
29
23
|
import "@aganzefelicite/responsekit-react/styles.css"; // optional default styling
|
|
30
24
|
```
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
The response-contract package (types + zod validators + SSE reducer) is bundled in and
|
|
27
|
+
re-exported, so there's nothing else to install. `react`/`react-dom` are peer deps (>=18).
|
|
33
28
|
|
|
34
29
|
## Quick start
|
|
35
30
|
|
|
@@ -52,8 +47,9 @@ backend). The message endpoint is POST + `text/event-stream`; the SDK drives
|
|
|
52
47
|
|
|
53
48
|
## API
|
|
54
49
|
|
|
55
|
-
**Components** — `AiRenderer`, `AiStream`
|
|
50
|
+
**Components** — `AiRenderer`, `AiStream`, `AiWidget` (draggable/resizable pop-up)
|
|
56
51
|
**Hooks** — `useAiStream`, `useChat`
|
|
52
|
+
**Actions** — `withDefaultActions`, `runDefaultAction`, `performDownload`, `performOpenLink`, `triggerDownload`
|
|
57
53
|
**Registry** — `registerBlock`, `unregisterBlock`, `resolveBlockComponent`
|
|
58
54
|
**Default blocks** — `TextBlock`, `KpiBlock`, `ChartBlock`, `TableBlock`, `InsightBlock`,
|
|
59
55
|
`RecommendationBlock`, `FilterBlock`, `FallbackBlock`, `defaultBlockComponents`
|
|
@@ -77,10 +73,32 @@ registerBlock({ type: "MAP", component: RwandaMap });
|
|
|
77
73
|
Resolution order per block type: `components` prop → globally registered → built-in default
|
|
78
74
|
→ graceful fallback.
|
|
79
75
|
|
|
76
|
+
## Floating widget
|
|
77
|
+
|
|
78
|
+
Wrap any content in a draggable, resizable pop-up with a launcher button:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { AiWidget } from "@aganzefelicite/responsekit-react";
|
|
82
|
+
|
|
83
|
+
<AiWidget title="Assistant" defaultOpen>
|
|
84
|
+
{/* useChat + AiRenderer, a single AiRenderer, or your own chat UI */}
|
|
85
|
+
</AiWidget>;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Drag by the header, resize from the bottom-right corner, toggle via the launcher (or
|
|
89
|
+
control `open`/`onOpenChange`). Chrome only — it never touches your data flow.
|
|
90
|
+
|
|
80
91
|
## Actions
|
|
81
92
|
|
|
82
93
|
Blocks emit intent through one `onAction(action)` callback (`DOWNLOAD`, `OPEN_LINK`,
|
|
83
|
-
`REFRESH`, `CUSTOM`, or host-defined). The SDK never runs business logic
|
|
94
|
+
`REFRESH`, `CUSTOM`, or host-defined). The SDK never runs business logic — but for the two
|
|
95
|
+
actions with an obvious browser behavior it ships a default. Wrap your handler with
|
|
96
|
+
`withDefaultActions` and CSV/JSON/Blob downloads and link-opens work out of the box:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { withDefaultActions } from "@aganzefelicite/responsekit-react";
|
|
100
|
+
<AiRenderer response={r} onAction={withDefaultActions((a) => track(a))} />;
|
|
101
|
+
```
|
|
84
102
|
|
|
85
103
|
## License
|
|
86
104
|
|
package/dist/index.cjs
CHANGED
|
@@ -4981,9 +4981,259 @@ function useChat(options) {
|
|
|
4981
4981
|
react.useEffect(() => () => abortRef.current?.abort(), []);
|
|
4982
4982
|
return { conversationId, turns, isStreaming, error, send, stop, reset, newConversation };
|
|
4983
4983
|
}
|
|
4984
|
+
var MARGIN = 24;
|
|
4985
|
+
var clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
|
|
4986
|
+
var viewport = () => ({
|
|
4987
|
+
w: typeof window === "undefined" ? 1024 : window.innerWidth,
|
|
4988
|
+
h: typeof window === "undefined" ? 768 : window.innerHeight
|
|
4989
|
+
});
|
|
4990
|
+
function AiWidget({
|
|
4991
|
+
children,
|
|
4992
|
+
title = "Assistant",
|
|
4993
|
+
headerActions,
|
|
4994
|
+
defaultOpen = false,
|
|
4995
|
+
open: openProp,
|
|
4996
|
+
onOpenChange,
|
|
4997
|
+
initialPosition,
|
|
4998
|
+
initialSize = { width: 384, height: 560 },
|
|
4999
|
+
minWidth = 300,
|
|
5000
|
+
minHeight = 360,
|
|
5001
|
+
showLauncher = true,
|
|
5002
|
+
launcher,
|
|
5003
|
+
className
|
|
5004
|
+
}) {
|
|
5005
|
+
const isControlled = openProp !== void 0;
|
|
5006
|
+
const [openState, setOpenState] = react.useState(defaultOpen);
|
|
5007
|
+
const open = isControlled ? openProp : openState;
|
|
5008
|
+
const setOpen = react.useCallback(
|
|
5009
|
+
(next) => {
|
|
5010
|
+
if (!isControlled) setOpenState(next);
|
|
5011
|
+
onOpenChange?.(next);
|
|
5012
|
+
},
|
|
5013
|
+
[isControlled, onOpenChange]
|
|
5014
|
+
);
|
|
5015
|
+
const [size, setSize] = react.useState(initialSize);
|
|
5016
|
+
const [pos, setPos] = react.useState(initialPosition ?? null);
|
|
5017
|
+
react.useEffect(() => {
|
|
5018
|
+
if (pos) return;
|
|
5019
|
+
const { w, h } = viewport();
|
|
5020
|
+
setPos({
|
|
5021
|
+
x: Math.max(MARGIN, w - size.width - MARGIN),
|
|
5022
|
+
y: Math.max(MARGIN, h - size.height - MARGIN)
|
|
5023
|
+
});
|
|
5024
|
+
}, [pos]);
|
|
5025
|
+
react.useEffect(() => {
|
|
5026
|
+
if (typeof window === "undefined") return;
|
|
5027
|
+
const onResize = () => {
|
|
5028
|
+
setPos((p) => {
|
|
5029
|
+
if (!p) return p;
|
|
5030
|
+
const { w, h } = viewport();
|
|
5031
|
+
return {
|
|
5032
|
+
x: clamp(p.x, 0, Math.max(0, w - size.width)),
|
|
5033
|
+
y: clamp(p.y, 0, Math.max(0, h - size.height))
|
|
5034
|
+
};
|
|
5035
|
+
});
|
|
5036
|
+
};
|
|
5037
|
+
window.addEventListener("resize", onResize);
|
|
5038
|
+
return () => window.removeEventListener("resize", onResize);
|
|
5039
|
+
}, [size.width, size.height]);
|
|
5040
|
+
const dragRef = react.useRef(null);
|
|
5041
|
+
const onHeaderPointerDown = (e) => {
|
|
5042
|
+
if (e.button !== 0 || !pos) return;
|
|
5043
|
+
if (e.target.closest("button, a, input, select")) return;
|
|
5044
|
+
dragRef.current = { px: e.clientX, py: e.clientY, ox: pos.x, oy: pos.y };
|
|
5045
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
5046
|
+
};
|
|
5047
|
+
const onHeaderPointerMove = (e) => {
|
|
5048
|
+
const d = dragRef.current;
|
|
5049
|
+
if (!d) return;
|
|
5050
|
+
const { w, h } = viewport();
|
|
5051
|
+
setPos({
|
|
5052
|
+
x: clamp(d.ox + (e.clientX - d.px), 0, Math.max(0, w - size.width)),
|
|
5053
|
+
y: clamp(d.oy + (e.clientY - d.py), 0, Math.max(0, h - size.height))
|
|
5054
|
+
});
|
|
5055
|
+
};
|
|
5056
|
+
const endHeaderPointer = (e) => {
|
|
5057
|
+
dragRef.current = null;
|
|
5058
|
+
if (e.currentTarget.hasPointerCapture?.(e.pointerId)) {
|
|
5059
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
5060
|
+
}
|
|
5061
|
+
};
|
|
5062
|
+
const resizeRef = react.useRef(null);
|
|
5063
|
+
const onResizePointerDown = (e) => {
|
|
5064
|
+
if (e.button !== 0) return;
|
|
5065
|
+
e.stopPropagation();
|
|
5066
|
+
resizeRef.current = { px: e.clientX, py: e.clientY, ow: size.width, oh: size.height };
|
|
5067
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
5068
|
+
};
|
|
5069
|
+
const onResizePointerMove = (e) => {
|
|
5070
|
+
const r = resizeRef.current;
|
|
5071
|
+
if (!r || !pos) return;
|
|
5072
|
+
const { w, h } = viewport();
|
|
5073
|
+
setSize({
|
|
5074
|
+
width: clamp(r.ow + (e.clientX - r.px), minWidth, w - pos.x - MARGIN / 2),
|
|
5075
|
+
height: clamp(r.oh + (e.clientY - r.py), minHeight, h - pos.y - MARGIN / 2)
|
|
5076
|
+
});
|
|
5077
|
+
};
|
|
5078
|
+
const endResizePointer = (e) => {
|
|
5079
|
+
resizeRef.current = null;
|
|
5080
|
+
if (e.currentTarget.hasPointerCapture?.(e.pointerId)) {
|
|
5081
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
5082
|
+
}
|
|
5083
|
+
};
|
|
5084
|
+
if (!open) {
|
|
5085
|
+
if (!showLauncher) return null;
|
|
5086
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5087
|
+
"button",
|
|
5088
|
+
{
|
|
5089
|
+
type: "button",
|
|
5090
|
+
className: "rk-widget-launcher",
|
|
5091
|
+
onClick: () => setOpen(true),
|
|
5092
|
+
"aria-label": `Open ${title}`,
|
|
5093
|
+
children: launcher ?? /* @__PURE__ */ jsxRuntime.jsx(LauncherIcon, {})
|
|
5094
|
+
}
|
|
5095
|
+
);
|
|
5096
|
+
}
|
|
5097
|
+
const panelStyle = pos ? {
|
|
5098
|
+
left: pos.x,
|
|
5099
|
+
top: pos.y,
|
|
5100
|
+
width: size.width,
|
|
5101
|
+
height: size.height
|
|
5102
|
+
} : { visibility: "hidden" };
|
|
5103
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5104
|
+
"div",
|
|
5105
|
+
{
|
|
5106
|
+
className: `rk-widget${className ? ` ${className}` : ""}`,
|
|
5107
|
+
style: panelStyle,
|
|
5108
|
+
role: "dialog",
|
|
5109
|
+
"aria-label": title,
|
|
5110
|
+
children: [
|
|
5111
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5112
|
+
"div",
|
|
5113
|
+
{
|
|
5114
|
+
className: "rk-widget-header",
|
|
5115
|
+
onPointerDown: onHeaderPointerDown,
|
|
5116
|
+
onPointerMove: onHeaderPointerMove,
|
|
5117
|
+
onPointerUp: endHeaderPointer,
|
|
5118
|
+
onPointerCancel: endHeaderPointer,
|
|
5119
|
+
children: [
|
|
5120
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "rk-widget-title", children: title }),
|
|
5121
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rk-widget-header-actions", children: [
|
|
5122
|
+
headerActions,
|
|
5123
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5124
|
+
"button",
|
|
5125
|
+
{
|
|
5126
|
+
type: "button",
|
|
5127
|
+
className: "rk-widget-close",
|
|
5128
|
+
onClick: () => setOpen(false),
|
|
5129
|
+
"aria-label": `Close ${title}`,
|
|
5130
|
+
children: "\xD7"
|
|
5131
|
+
}
|
|
5132
|
+
)
|
|
5133
|
+
] })
|
|
5134
|
+
]
|
|
5135
|
+
}
|
|
5136
|
+
),
|
|
5137
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rk-widget-body", children }),
|
|
5138
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5139
|
+
"div",
|
|
5140
|
+
{
|
|
5141
|
+
className: "rk-widget-resize",
|
|
5142
|
+
onPointerDown: onResizePointerDown,
|
|
5143
|
+
onPointerMove: onResizePointerMove,
|
|
5144
|
+
onPointerUp: endResizePointer,
|
|
5145
|
+
onPointerCancel: endResizePointer,
|
|
5146
|
+
role: "separator",
|
|
5147
|
+
"aria-label": "Resize",
|
|
5148
|
+
"aria-orientation": "horizontal"
|
|
5149
|
+
}
|
|
5150
|
+
)
|
|
5151
|
+
]
|
|
5152
|
+
}
|
|
5153
|
+
);
|
|
5154
|
+
}
|
|
5155
|
+
function LauncherIcon() {
|
|
5156
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5157
|
+
"path",
|
|
5158
|
+
{
|
|
5159
|
+
d: "M21 11.5a8.38 8.38 0 0 1-8.5 8.5 9.3 9.3 0 0 1-4-.9L3 21l1.9-5.5a8.38 8.38 0 0 1-.9-4A8.5 8.5 0 0 1 12.5 3 8.38 8.38 0 0 1 21 11.5Z",
|
|
5160
|
+
stroke: "currentColor",
|
|
5161
|
+
strokeWidth: "1.8",
|
|
5162
|
+
strokeLinecap: "round",
|
|
5163
|
+
strokeLinejoin: "round"
|
|
5164
|
+
}
|
|
5165
|
+
) });
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
// src/download.ts
|
|
5169
|
+
var MIME = {
|
|
5170
|
+
CSV: "text/csv",
|
|
5171
|
+
JSON: "application/json",
|
|
5172
|
+
TXT: "text/plain"
|
|
5173
|
+
};
|
|
5174
|
+
function inBrowser() {
|
|
5175
|
+
return typeof document !== "undefined" && typeof URL !== "undefined";
|
|
5176
|
+
}
|
|
5177
|
+
function triggerDownload(blob, filename) {
|
|
5178
|
+
if (!inBrowser()) return;
|
|
5179
|
+
const url = URL.createObjectURL(blob);
|
|
5180
|
+
const a = document.createElement("a");
|
|
5181
|
+
a.href = url;
|
|
5182
|
+
a.download = filename;
|
|
5183
|
+
a.rel = "noopener";
|
|
5184
|
+
document.body.appendChild(a);
|
|
5185
|
+
a.click();
|
|
5186
|
+
a.remove();
|
|
5187
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
5188
|
+
}
|
|
5189
|
+
function performDownload(action) {
|
|
5190
|
+
if (!inBrowser()) return false;
|
|
5191
|
+
const { data, format, filename } = action;
|
|
5192
|
+
if (data === null || data === void 0) return false;
|
|
5193
|
+
const fmt = typeof format === "string" ? format.toUpperCase() : void 0;
|
|
5194
|
+
let blob;
|
|
5195
|
+
let name = filename;
|
|
5196
|
+
if (data instanceof Blob) {
|
|
5197
|
+
blob = data;
|
|
5198
|
+
name ?? (name = "download");
|
|
5199
|
+
} else if (typeof data === "string") {
|
|
5200
|
+
const mime = fmt && MIME[fmt] || "text/plain";
|
|
5201
|
+
blob = new Blob([data], { type: `${mime};charset=utf-8` });
|
|
5202
|
+
name ?? (name = `download.${(fmt ?? "TXT").toLowerCase()}`);
|
|
5203
|
+
} else {
|
|
5204
|
+
blob = new Blob([JSON.stringify(data, null, 2)], {
|
|
5205
|
+
type: "application/json;charset=utf-8"
|
|
5206
|
+
});
|
|
5207
|
+
name ?? (name = "download.json");
|
|
5208
|
+
}
|
|
5209
|
+
triggerDownload(blob, name);
|
|
5210
|
+
return true;
|
|
5211
|
+
}
|
|
5212
|
+
function performOpenLink(action) {
|
|
5213
|
+
if (typeof window === "undefined" || !action.href) return false;
|
|
5214
|
+
window.open(action.href, action.target ?? "_blank", "noopener,noreferrer");
|
|
5215
|
+
return true;
|
|
5216
|
+
}
|
|
5217
|
+
function runDefaultAction(action) {
|
|
5218
|
+
switch (action.type) {
|
|
5219
|
+
case "DOWNLOAD":
|
|
5220
|
+
return performDownload(action);
|
|
5221
|
+
case "OPEN_LINK":
|
|
5222
|
+
return performOpenLink(action);
|
|
5223
|
+
default:
|
|
5224
|
+
return false;
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
function withDefaultActions(onAction) {
|
|
5228
|
+
return (action) => {
|
|
5229
|
+
runDefaultAction(action);
|
|
5230
|
+
onAction?.(action);
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
4984
5233
|
|
|
4985
5234
|
exports.AiRenderer = AiRenderer;
|
|
4986
5235
|
exports.AiStream = AiStream;
|
|
5236
|
+
exports.AiWidget = AiWidget;
|
|
4987
5237
|
exports.ChartBlock = ChartBlock;
|
|
4988
5238
|
exports.ErrorBoundary = ErrorBoundary;
|
|
4989
5239
|
exports.ErrorView = ErrorView;
|
|
@@ -5024,6 +5274,8 @@ exports.noopAction = noopAction;
|
|
|
5024
5274
|
exports.parseAiResponse = parseAiResponse;
|
|
5025
5275
|
exports.parseBlock = parseBlock;
|
|
5026
5276
|
exports.parseSseEvent = parseSseEvent;
|
|
5277
|
+
exports.performDownload = performDownload;
|
|
5278
|
+
exports.performOpenLink = performOpenLink;
|
|
5027
5279
|
exports.progressEventSchema = progressEventSchema;
|
|
5028
5280
|
exports.progressPhaseSchema = progressPhaseSchema;
|
|
5029
5281
|
exports.recommendationBlockSchema = recommendationBlockSchema;
|
|
@@ -5036,6 +5288,7 @@ exports.responseEndSchema = responseEndSchema;
|
|
|
5036
5288
|
exports.responseMetadataSchema = responseMetadataSchema;
|
|
5037
5289
|
exports.responseStartSchema = responseStartSchema;
|
|
5038
5290
|
exports.responseTypeSchema = responseTypeSchema;
|
|
5291
|
+
exports.runDefaultAction = runDefaultAction;
|
|
5039
5292
|
exports.safeParseAiResponse = safeParseAiResponse;
|
|
5040
5293
|
exports.severitySchema = severitySchema;
|
|
5041
5294
|
exports.streamMessage = streamMessage;
|
|
@@ -5046,8 +5299,10 @@ exports.textBlockSchema = textBlockSchema;
|
|
|
5046
5299
|
exports.textFormatSchema = textFormatSchema;
|
|
5047
5300
|
exports.textResponseSchema = textResponseSchema;
|
|
5048
5301
|
exports.trendSchema = trendSchema;
|
|
5302
|
+
exports.triggerDownload = triggerDownload;
|
|
5049
5303
|
exports.unregisterBlock = unregisterBlock;
|
|
5050
5304
|
exports.useAiStream = useAiStream;
|
|
5051
5305
|
exports.useChat = useChat;
|
|
5306
|
+
exports.withDefaultActions = withDefaultActions;
|
|
5052
5307
|
//# sourceMappingURL=index.cjs.map
|
|
5053
5308
|
//# sourceMappingURL=index.cjs.map
|