@citewidget/react 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +92 -2
- package/dist/CiteWidget.d.ts +8 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +73 -0
- package/dist/registry.d.ts +10 -0
- package/dist/useCiteWidget.d.ts +13 -0
- package/package.json +40 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CiteWidget
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @citewidget/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The CiteWidget chat for React and Next.js: visitors ask questions and get answers from your
|
|
4
|
+
documents, with links to the pages they came from. React 18 or 19.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install @citewidget/react
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
This package is ES modules only (no CommonJS build): use `import`, not `require()`. Every current bundler and Node 22+ handle that; with an older CommonJS-only setup, load the script tag version instead.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { CiteWidget } from "@citewidget/react";
|
|
16
|
+
|
|
17
|
+
export function App() {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{/* your app */}
|
|
21
|
+
<CiteWidget workspaceKey="pk_..." primaryColor="#0F766E" />
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`<CiteWidget>` renders nothing where you place it: after mounting it adds the chat as a fixed
|
|
28
|
+
overlay at the end of `<body>`. Put it once, high in the tree (a layout), so it stays mounted
|
|
29
|
+
across pages.
|
|
30
|
+
|
|
31
|
+
### Next.js (App Router)
|
|
32
|
+
|
|
33
|
+
The package is marked `"use client"`, so a server component such as `app/layout.tsx` can render
|
|
34
|
+
it directly. Nothing touches `window` or `document` during server rendering.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// app/layout.tsx
|
|
38
|
+
import { CiteWidget } from "@citewidget/react";
|
|
39
|
+
|
|
40
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
41
|
+
return (
|
|
42
|
+
<html lang="en">
|
|
43
|
+
<body>
|
|
44
|
+
{children}
|
|
45
|
+
<CiteWidget workspaceKey={process.env.NEXT_PUBLIC_CITEWIDGET_KEY!} />
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The workspace key is public (it starts with `pk_`), so a `NEXT_PUBLIC_` variable is fine.
|
|
53
|
+
|
|
54
|
+
## Your own button
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
"use client";
|
|
58
|
+
import { useCiteWidget } from "@citewidget/react";
|
|
59
|
+
|
|
60
|
+
export function AskButton() {
|
|
61
|
+
const widget = useCiteWidget();
|
|
62
|
+
return <button onClick={widget.open}>Ask about this product</button>;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`useCiteWidget()` returns stable `open`, `close`, `toggle` and `newChat` functions for the
|
|
67
|
+
mounted `<CiteWidget>` (the newest one if there are several). Combine it with
|
|
68
|
+
`showLauncher={false}` to hide the round button. Calling them while no widget is mounted logs a
|
|
69
|
+
warning and does nothing.
|
|
70
|
+
|
|
71
|
+
## Props
|
|
72
|
+
|
|
73
|
+
Every option of `createCiteWidget` from
|
|
74
|
+
[`@citewidget/widget`](https://www.npmjs.com/package/@citewidget/widget) is a prop:
|
|
75
|
+
`workspaceKey` (required), `apiUrl`, `serverUrl`, `loadDashboardSettings`, `title`, `greeting`,
|
|
76
|
+
`botName`, `primaryColor`, `surfaceColor`, `colorScheme`, `position`, `showLauncher`,
|
|
77
|
+
`launcherLabel`, `launcherIcon`, `launcherIconUrl`, `softRefusal`, `goodbye`, `suggestions`,
|
|
78
|
+
`handoff`, `handoffReplyTime`, `leadCapture`, `chatDurationMinutes`, `nonce`, and the callbacks
|
|
79
|
+
`onOpen`, `onClose`, `onHandoff` (called with no arguments). See that package for types, limits
|
|
80
|
+
and defaults.
|
|
81
|
+
|
|
82
|
+
- Settings combine as: built-in defaults < your CiteWidget dashboard < props. Removing a prop
|
|
83
|
+
hands that setting back to the dashboard.
|
|
84
|
+
- Changing props updates the widget; it is rebuilt only when something visible changed (a new
|
|
85
|
+
array with the same questions, or a new callback function, changes nothing). Callbacks always
|
|
86
|
+
use your latest props.
|
|
87
|
+
- Under React Strict Mode's double mount you still get exactly one widget.
|
|
88
|
+
- A missing or invalid `workspaceKey` is logged with `console.error` and nothing is shown; other
|
|
89
|
+
invalid values are ignored with a console warning.
|
|
90
|
+
|
|
91
|
+
## Licence
|
|
92
|
+
|
|
93
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CiteWidgetOptions } from "@citewidget/widget";
|
|
2
|
+
/** Every createCiteWidget() option as a prop; workspaceKey is required. */
|
|
3
|
+
export type CiteWidgetProps = CiteWidgetOptions;
|
|
4
|
+
/**
|
|
5
|
+
* The CiteWidget chat. Renders nothing in place: the widget is a fixed overlay appended to
|
|
6
|
+
* <body> after mount, so it is safe in server rendering and under Strict Mode.
|
|
7
|
+
*/
|
|
8
|
+
export declare function CiteWidget(props: CiteWidgetProps): null;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @citewidget/react: the CiteWidget chat for React and Next.js (App Router: the build keeps
|
|
3
|
+
* "use client"; nothing touches window or document during server rendering).
|
|
4
|
+
*/
|
|
5
|
+
export { CiteWidget, type CiteWidgetProps } from "./CiteWidget";
|
|
6
|
+
export { useCiteWidget, type CiteWidgetControls } from "./useCiteWidget";
|
|
7
|
+
export type { CiteWidgetColorScheme, CiteWidgetLauncherIcon, CiteWidgetOptions, CiteWidgetPosition, } from "@citewidget/widget";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/CiteWidget.ts
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { createCiteWidget } from "@citewidget/widget";
|
|
6
|
+
|
|
7
|
+
// src/registry.ts
|
|
8
|
+
var mounted = [];
|
|
9
|
+
function registerWidget(widget) {
|
|
10
|
+
return mounted = [...mounted, widget], () => {
|
|
11
|
+
mounted = mounted.filter((entry) => entry !== widget);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function currentWidget() {
|
|
15
|
+
return mounted[mounted.length - 1] ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/CiteWidget.ts
|
|
19
|
+
var CALLBACKS = /* @__PURE__ */ new Set(["onOpen", "onClose", "onHandoff"]);
|
|
20
|
+
function dataOptions(props) {
|
|
21
|
+
return Object.fromEntries(Object.entries(props).filter(([key]) => !CALLBACKS.has(key)));
|
|
22
|
+
}
|
|
23
|
+
function patchFrom(next, previous) {
|
|
24
|
+
return { ...Object.fromEntries(Object.keys(previous).map((key) => [key, void 0])), ...next };
|
|
25
|
+
}
|
|
26
|
+
function CiteWidget(props) {
|
|
27
|
+
let latest = useRef(props), widget = useRef(null), unregister = useRef(() => {
|
|
28
|
+
}), sent = useRef({ workspaceKey: props.workspaceKey }), sentKey = useRef(""), optionsKey = JSON.stringify(dataOptions(props));
|
|
29
|
+
return useEffect(() => {
|
|
30
|
+
latest.current = props;
|
|
31
|
+
}), useEffect(
|
|
32
|
+
() => () => {
|
|
33
|
+
unregister.current(), widget.current?.destroy(), widget.current = null, sentKey.current = "";
|
|
34
|
+
},
|
|
35
|
+
[]
|
|
36
|
+
), useEffect(() => {
|
|
37
|
+
if (optionsKey === sentKey.current) return;
|
|
38
|
+
let next = dataOptions(latest.current);
|
|
39
|
+
try {
|
|
40
|
+
widget.current ? widget.current.update(patchFrom(next, sent.current)) : (widget.current = createCiteWidget({
|
|
41
|
+
...next,
|
|
42
|
+
onOpen: () => latest.current.onOpen?.(),
|
|
43
|
+
onClose: () => latest.current.onClose?.(),
|
|
44
|
+
onHandoff: () => latest.current.onHandoff?.()
|
|
45
|
+
}), unregister.current = registerWidget(widget.current)), sent.current = next, sentKey.current = optionsKey;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(error instanceof Error ? error.message : error);
|
|
48
|
+
}
|
|
49
|
+
}, [optionsKey]), null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/useCiteWidget.ts
|
|
53
|
+
function call(method) {
|
|
54
|
+
let widget = currentWidget();
|
|
55
|
+
if (widget) {
|
|
56
|
+
widget[method]();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.warn(`[CiteWidget] ${method}() was called, but no <CiteWidget> is mounted.`);
|
|
60
|
+
}
|
|
61
|
+
var controls = Object.freeze({
|
|
62
|
+
open: () => call("open"),
|
|
63
|
+
close: () => call("close"),
|
|
64
|
+
toggle: () => call("toggle"),
|
|
65
|
+
newChat: () => call("newChat")
|
|
66
|
+
});
|
|
67
|
+
function useCiteWidget() {
|
|
68
|
+
return controls;
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
CiteWidget,
|
|
72
|
+
useCiteWidget
|
|
73
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widgets <CiteWidget> has mounted, newest last, so useCiteWidget() can reach the current one
|
|
3
|
+
* from anywhere in the tree without a provider. Written only from effects (browser), so server
|
|
4
|
+
* rendering never sees or shares it.
|
|
5
|
+
*/
|
|
6
|
+
import type { CiteWidgetController } from "@citewidget/widget";
|
|
7
|
+
/** Adds a widget; the returned function removes it again. */
|
|
8
|
+
export declare function registerWidget(widget: CiteWidgetController): () => void;
|
|
9
|
+
/** The most recently mounted widget, or null. */
|
|
10
|
+
export declare function currentWidget(): CiteWidgetController | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Controls for the mounted <CiteWidget>; stable across renders, safe to pass to onClick. */
|
|
2
|
+
export interface CiteWidgetControls {
|
|
3
|
+
open(): void;
|
|
4
|
+
close(): void;
|
|
5
|
+
toggle(): void;
|
|
6
|
+
/** Clears the conversation. */
|
|
7
|
+
newChat(): void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Opens, closes or resets the mounted <CiteWidget> from your own UI (for example a "Ask about
|
|
11
|
+
* this product" button with showLauncher={false}). With several mounted, the newest one wins.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useCiteWidget(): CiteWidgetControls;
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citewidget/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CiteWidget chat for React and Next.js: <CiteWidget workspaceKey=\"pk_...\" /> and useCiteWidget().",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"citewidget",
|
|
21
|
+
"react",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"chat-widget"
|
|
24
|
+
],
|
|
25
|
+
"homepage": "https://citewidget.com",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@citewidget/widget": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "node build.mjs",
|
|
38
|
+
"prepack": "node build.mjs",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
}
|
|
42
|
+
}
|