@plasius/chatbot 1.0.0 → 1.0.1
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 +19 -1
- package/dist/chatbot.d.ts +8 -5
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +142 -162
- package/dist/client.d.ts +41 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +168 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/styles/chatbot.module.css +94 -61
- package/dist-cjs/chatbot.d.ts +8 -5
- package/dist-cjs/chatbot.d.ts.map +1 -1
- package/dist-cjs/chatbot.js +141 -161
- package/dist-cjs/client.d.ts +41 -0
- package/dist-cjs/client.d.ts.map +1 -0
- package/dist-cjs/client.js +174 -0
- package/dist-cjs/index.d.ts +1 -0
- package/dist-cjs/index.d.ts.map +1 -1
- package/dist-cjs/index.js +15 -0
- package/dist-cjs/styles/chatbot.module.css +94 -61
- package/docs/adrs/index.md +4 -0
- package/package.json +1 -3
- package/src/chatbot.tsx +219 -255
- package/src/client.ts +254 -0
- package/src/index.ts +1 -0
- package/src/styles/chatbot.module.css +94 -61
|
@@ -1,106 +1,139 @@
|
|
|
1
|
-
/* src/components/Chatbot.css */
|
|
2
1
|
.chatbotcontainer {
|
|
3
2
|
display: flex;
|
|
4
3
|
flex-direction: column;
|
|
5
|
-
height: 100vh;
|
|
6
4
|
width: 100%;
|
|
7
|
-
max-width:
|
|
5
|
+
max-width: 720px;
|
|
8
6
|
margin: 0 auto;
|
|
9
|
-
border: 1px solid #
|
|
10
|
-
border-radius:
|
|
11
|
-
background: #
|
|
7
|
+
border: 1px solid #d1d7e0;
|
|
8
|
+
border-radius: 12px;
|
|
9
|
+
background: #f7f9fc;
|
|
10
|
+
min-height: 70vh;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.header {
|
|
15
|
+
display: flex;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
align-items: center;
|
|
18
|
+
padding: 12px 16px;
|
|
19
|
+
border-bottom: 1px solid #d1d7e0;
|
|
20
|
+
background: #eef3fb;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.title {
|
|
24
|
+
font-size: 16px;
|
|
25
|
+
font-weight: 700;
|
|
26
|
+
color: #1f2a3d;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.usage {
|
|
30
|
+
font-size: 13px;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
color: #4c5970;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.notice {
|
|
36
|
+
padding: 10px 16px;
|
|
37
|
+
font-size: 13px;
|
|
38
|
+
border-bottom: 1px solid #d1d7e0;
|
|
39
|
+
background: #fff8db;
|
|
40
|
+
color: #6f5200;
|
|
12
41
|
}
|
|
13
42
|
|
|
14
43
|
.messagesbox {
|
|
15
44
|
flex: 1;
|
|
16
|
-
padding:
|
|
45
|
+
padding: 12px;
|
|
17
46
|
overflow-y: auto;
|
|
18
|
-
background: #
|
|
47
|
+
background: #ffffff;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: 8px;
|
|
19
51
|
}
|
|
20
52
|
|
|
21
53
|
.message {
|
|
22
54
|
display: flex;
|
|
23
|
-
margin-bottom: 10px;
|
|
24
55
|
}
|
|
25
56
|
|
|
26
|
-
.
|
|
27
|
-
|
|
57
|
+
.user {
|
|
58
|
+
margin-left: auto;
|
|
28
59
|
}
|
|
29
60
|
|
|
30
|
-
.
|
|
31
|
-
|
|
61
|
+
.assistant,
|
|
62
|
+
.system {
|
|
63
|
+
margin-right: auto;
|
|
32
64
|
}
|
|
33
65
|
|
|
34
66
|
.bubble {
|
|
35
|
-
max-width:
|
|
36
|
-
padding: 10px
|
|
37
|
-
border-radius:
|
|
67
|
+
max-width: 85%;
|
|
68
|
+
padding: 10px 12px;
|
|
69
|
+
border-radius: 12px;
|
|
70
|
+
line-height: 1.4;
|
|
71
|
+
word-break: break-word;
|
|
72
|
+
white-space: pre-wrap;
|
|
38
73
|
font-size: 14px;
|
|
39
|
-
line-height: 1.5;
|
|
40
|
-
position: relative;
|
|
41
|
-
word-wrap: break-word;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.message.user .bubble {
|
|
45
|
-
background: #007bff;
|
|
46
|
-
color: #fff;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.message.system .bubble {
|
|
50
|
-
background: #e9ecef;
|
|
51
|
-
color: #333;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.bubble::after {
|
|
55
|
-
content: '';
|
|
56
|
-
position: absolute;
|
|
57
|
-
width: 0;
|
|
58
|
-
height: 0;
|
|
59
|
-
border-style: solid;
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
border-width: 10px 0 10px 10px;
|
|
66
|
-
border-color: transparent transparent transparent #007bff;
|
|
67
|
-
transform: translateY(-50%);
|
|
76
|
+
.user .bubble {
|
|
77
|
+
background: #2463eb;
|
|
78
|
+
color: #ffffff;
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
border-color: transparent #e9ecef transparent transparent;
|
|
75
|
-
transform: translateY(-50%);
|
|
81
|
+
.assistant .bubble,
|
|
82
|
+
.system .bubble {
|
|
83
|
+
background: #edf2fa;
|
|
84
|
+
color: #182230;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
.inputbox {
|
|
79
88
|
display: flex;
|
|
80
89
|
align-items: center;
|
|
90
|
+
gap: 8px;
|
|
81
91
|
padding: 10px;
|
|
82
|
-
border-top: 1px solid #
|
|
83
|
-
background: #
|
|
92
|
+
border-top: 1px solid #d1d7e0;
|
|
93
|
+
background: #f7f9fc;
|
|
94
|
+
position: relative;
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
.inputbox input {
|
|
87
98
|
flex: 1;
|
|
88
|
-
|
|
89
|
-
border:
|
|
90
|
-
|
|
99
|
+
border: 1px solid #c3cedf;
|
|
100
|
+
border-radius: 999px;
|
|
101
|
+
padding: 10px 14px;
|
|
91
102
|
font-size: 14px;
|
|
92
103
|
}
|
|
93
104
|
|
|
94
|
-
.
|
|
95
|
-
|
|
105
|
+
.inputbox input:disabled {
|
|
106
|
+
cursor: not-allowed;
|
|
107
|
+
opacity: 0.75;
|
|
108
|
+
background: #f1f4f9;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.iconButton {
|
|
112
|
+
width: 36px;
|
|
113
|
+
height: 36px;
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
border: 1px solid #c3cedf;
|
|
118
|
+
border-radius: 999px;
|
|
119
|
+
background: #ffffff;
|
|
96
120
|
cursor: pointer;
|
|
97
|
-
color: whitesmoke;
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
.
|
|
101
|
-
|
|
123
|
+
.iconButton:disabled {
|
|
124
|
+
cursor: not-allowed;
|
|
125
|
+
opacity: 0.6;
|
|
102
126
|
}
|
|
103
127
|
|
|
128
|
+
.emojiicon,
|
|
104
129
|
.sendicon {
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
color: #2f4f95;
|
|
131
|
+
font-size: 16px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.emojiPicker {
|
|
135
|
+
position: absolute;
|
|
136
|
+
right: 44px;
|
|
137
|
+
bottom: 52px;
|
|
138
|
+
z-index: 20;
|
|
139
|
+
}
|
package/dist-cjs/chatbot.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { type ChatMessage, type ChatbotClientOptions, type ChatbotUsage } from "./client.js";
|
|
3
|
+
export interface ChatBotProps extends ChatbotClientOptions {
|
|
4
|
+
initialMessages?: ChatMessage[];
|
|
5
|
+
systemPrompt?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
onUsageChange?: (usage: ChatbotUsage) => void;
|
|
9
|
+
onAuthRequired?: () => void;
|
|
6
10
|
}
|
|
7
11
|
export default function ChatBot(props: React.PropsWithChildren<ChatBotProps>): React.ReactElement;
|
|
8
|
-
export {};
|
|
9
12
|
//# sourceMappingURL=chatbot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatbot.d.ts","sourceRoot":"","sources":["../src/chatbot.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"chatbot.d.ts","sourceRoot":"","sources":["../src/chatbot.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoE,MAAM,OAAO,CAAC;AAIzF,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAUrB,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAoBD,MAAM,CAAC,OAAO,UAAU,OAAO,CAC7B,KAAK,EAAE,KAAK,CAAC,iBAAiB,CAAC,YAAY,CAAC,GAC3C,KAAK,CAAC,YAAY,CA2NpB"}
|
package/dist-cjs/chatbot.js
CHANGED
|
@@ -39,181 +39,161 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.default = ChatBot;
|
|
40
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
41
41
|
const react_1 = require("react");
|
|
42
|
+
const fa_1 = require("react-icons/fa");
|
|
43
|
+
const chatbot_module_css_1 = __importDefault(require("./styles/chatbot.module.css"));
|
|
44
|
+
const client_js_1 = require("./client.js");
|
|
42
45
|
const EmojiPicker = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require("emoji-picker-react/dist/emoji-picker-react.esm.js"))).then((module) => ({
|
|
43
|
-
default: module.EmojiPicker,
|
|
46
|
+
default: module.EmojiPicker,
|
|
44
47
|
})));
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
+
const DEFAULT_TITLE = "Plasius Chatbot";
|
|
49
|
+
const DEFAULT_PLACEHOLDER = "Ask Plasius something...";
|
|
50
|
+
const DEFAULT_SYSTEM_PROMPT = "You are the Plasius assistant. Keep responses concise, practical, and factual.";
|
|
51
|
+
function statusMessage(state, usage) {
|
|
52
|
+
if (state === "loading")
|
|
53
|
+
return "Checking access...";
|
|
54
|
+
if (state === "signed_out")
|
|
55
|
+
return "Sign in to use chatbot.";
|
|
56
|
+
if (state === "limit_reached") {
|
|
57
|
+
if (usage) {
|
|
58
|
+
return `Demo limit reached (${usage.used}/${usage.limit} messages).`;
|
|
59
|
+
}
|
|
60
|
+
return "Demo limit reached.";
|
|
61
|
+
}
|
|
62
|
+
if (state === "error")
|
|
63
|
+
return "Chatbot is currently unavailable.";
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
48
66
|
function ChatBot(props) {
|
|
49
|
-
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
67
|
+
const [messages, setMessages] = (0, react_1.useState)(props.initialMessages ?? []);
|
|
50
68
|
const [input, setInput] = (0, react_1.useState)("");
|
|
51
69
|
const [showEmojiPicker, setShowEmojiPicker] = (0, react_1.useState)(false);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const [isSending, setIsSending] = (0, react_1.useState)(false);
|
|
71
|
+
const [state, setState] = (0, react_1.useState)("loading");
|
|
72
|
+
const [usage, setUsage] = (0, react_1.useState)(null);
|
|
73
|
+
const [errorMessage, setErrorMessage] = (0, react_1.useState)(null);
|
|
74
|
+
const clientOptions = (0, react_1.useMemo)(() => ({
|
|
75
|
+
endpoint: props.endpoint,
|
|
76
|
+
credentials: props.credentials,
|
|
77
|
+
headers: props.headers,
|
|
78
|
+
fetchFn: props.fetchFn,
|
|
79
|
+
csrfCookieName: props.csrfCookieName,
|
|
80
|
+
csrfHeaderName: props.csrfHeaderName,
|
|
81
|
+
bootstrapCsrf: props.bootstrapCsrf,
|
|
82
|
+
}), [
|
|
83
|
+
props.endpoint,
|
|
84
|
+
props.credentials,
|
|
85
|
+
props.headers,
|
|
86
|
+
props.fetchFn,
|
|
87
|
+
props.csrfCookieName,
|
|
88
|
+
props.csrfHeaderName,
|
|
89
|
+
props.bootstrapCsrf,
|
|
90
|
+
]);
|
|
91
|
+
const applyUsage = (0, react_1.useCallback)((nextUsage) => {
|
|
92
|
+
setUsage(nextUsage);
|
|
93
|
+
props.onUsageChange?.(nextUsage);
|
|
94
|
+
setState(nextUsage.exhausted ? "limit_reached" : "ready");
|
|
95
|
+
}, [props.onUsageChange]);
|
|
76
96
|
(0, react_1.useEffect)(() => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
using your knowledge of gameplay mechanics and world building you are going to help assign objects to the map.
|
|
82
|
-
|
|
83
|
-
You can find the list of objects from the following url: ${objects}
|
|
84
|
-
You can find the list of decorations from the following url: ${decorations}
|
|
85
|
-
You can find the list of surfaces from the following url: ${surfaces}
|
|
86
|
-
|
|
87
|
-
Each location is a hexagon with a radius of 10 meters and 10m tall (allowing for locations to be on top of each other!), and a q and r coordinate system.
|
|
88
|
-
The q coordinate is the horizontal axis, and the r coordinate is the vertical axis. The center of the hexagon is at (0, 0),
|
|
89
|
-
and the corners are at (5, 8.66), (10, 0), (5, -8.66), (-5, -8.66), (-10, 0), and (-5, 8.66).
|
|
90
|
-
Adjacent hexagons are at (q + 1, r), (q - 1, r), (q, r + 1), (q, r - 1), (q + 1, r - 1), and (q - 1, r + 1) and you should try and
|
|
91
|
-
coordinate over the hexagons to make sure the objects are placed in a way that makes sense.
|
|
92
|
-
|
|
93
|
-
Try and align surfaces, decorations, and objects to a 1m size hexagon when placing items so they align to each other in the world,
|
|
94
|
-
but avoid overlapping the objects with each other, unless they are meant to overlap (like a chair under a table, or a tree in a bush).
|
|
95
|
-
Surfaces should not overlap with each other, and should be placed in a way that makes sense for the location,
|
|
96
|
-
such as a road should be continuous and have purpose, to or from somewhere,
|
|
97
|
-
use the locations map to identify good roads, forests, mountains, lakes and oceans locations.
|
|
98
|
-
|
|
99
|
-
for each prompt the user gives you, will relate to a specific location in the game world, you should take in the location,
|
|
100
|
-
some basic information about the users expectations for the location and return a json object with the following fields:
|
|
101
|
-
{
|
|
102
|
-
"location": {
|
|
103
|
-
"r": "number", // 10m hexagon radius
|
|
104
|
-
"q": "number", // 10m hexagon radius
|
|
105
|
-
"elevation": "number",
|
|
106
|
-
"name": "string",
|
|
107
|
-
"description": "string",
|
|
108
|
-
"type": "string",
|
|
109
|
-
|
|
110
|
-
"surfaces": [{
|
|
111
|
-
"location": {
|
|
112
|
-
"q": "number", // 1m hexagon radius
|
|
113
|
-
"r": "number", // 1m hexagon radius
|
|
114
|
-
"elevation": "number"
|
|
115
|
-
},
|
|
116
|
-
"name": "string",
|
|
117
|
-
"type": "string",
|
|
118
|
-
"description": "string",
|
|
119
|
-
"url": "string",
|
|
120
|
-
"image": "string",
|
|
121
|
-
"rotation": "number",
|
|
122
|
-
"color": "string"
|
|
123
|
-
}],
|
|
124
|
-
"decorations": [
|
|
125
|
-
{
|
|
126
|
-
"name": "string",
|
|
127
|
-
"type": "string",
|
|
128
|
-
"description": "string",
|
|
129
|
-
"url": "string",
|
|
130
|
-
"image": "string",
|
|
131
|
-
"rotation": "number",
|
|
132
|
-
"scale": "number",
|
|
133
|
-
"color": "string",
|
|
134
|
-
"location": {
|
|
135
|
-
"x": "number",
|
|
136
|
-
"y": "number",
|
|
137
|
-
"z": "number"
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
],
|
|
141
|
-
"objects": [
|
|
142
|
-
{
|
|
143
|
-
"name": "string",
|
|
144
|
-
"type": "string",
|
|
145
|
-
"description": "string",
|
|
146
|
-
"url": "string",
|
|
147
|
-
"image": "string",
|
|
148
|
-
"rotation": "number",
|
|
149
|
-
"scale": "number",
|
|
150
|
-
"color": "string",
|
|
151
|
-
"location": {
|
|
152
|
-
"x": "number",
|
|
153
|
-
"y": "number",
|
|
154
|
-
"z": "number"
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
You can find the list of populated locations from the following url: ${locations} for reference and to allow you to be more creative in your assignments.
|
|
162
|
-
If your current location is in the list, then take the current objects and decorations into account when placing the new objects, and remove or replace the old ones.`,
|
|
163
|
-
},
|
|
164
|
-
], (arg) => {
|
|
165
|
-
setMessages((prev) => [...prev, arg]);
|
|
166
|
-
});
|
|
167
|
-
}, []);
|
|
168
|
-
const handleSend = async () => {
|
|
169
|
-
if (input.trim()) {
|
|
170
|
-
setMessages((prev) => [...prev, { content: input, role: "user" }]);
|
|
171
|
-
setInput("");
|
|
172
|
-
setShowEmojiPicker(false);
|
|
97
|
+
let active = true;
|
|
98
|
+
const loadUsage = async () => {
|
|
99
|
+
setState("loading");
|
|
100
|
+
setErrorMessage(null);
|
|
173
101
|
try {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
value.choices.forEach((choice) => {
|
|
179
|
-
setMessages((prev) => [
|
|
180
|
-
...prev,
|
|
181
|
-
{ content: choice.message.content ?? "", role: "system" },
|
|
182
|
-
]);
|
|
183
|
-
});
|
|
102
|
+
const result = await (0, client_js_1.getChatbotUsage)(clientOptions);
|
|
103
|
+
if (!active)
|
|
104
|
+
return;
|
|
105
|
+
applyUsage(result.usage);
|
|
184
106
|
}
|
|
185
|
-
catch (
|
|
186
|
-
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (!active)
|
|
109
|
+
return;
|
|
110
|
+
if (error instanceof client_js_1.ChatbotClientError && error.status === 401) {
|
|
111
|
+
setState("signed_out");
|
|
112
|
+
setErrorMessage("Sign in to start chatting.");
|
|
113
|
+
props.onAuthRequired?.();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
setState("error");
|
|
117
|
+
setErrorMessage(error instanceof Error ? error.message : "Failed to load chatbot.");
|
|
187
118
|
}
|
|
188
|
-
}
|
|
189
|
-
|
|
119
|
+
};
|
|
120
|
+
void loadUsage();
|
|
121
|
+
return () => {
|
|
122
|
+
active = false;
|
|
123
|
+
};
|
|
124
|
+
}, [applyUsage, clientOptions, props.onAuthRequired]);
|
|
125
|
+
const sendDisabled = isSending ||
|
|
126
|
+
state === "loading" ||
|
|
127
|
+
state === "signed_out" ||
|
|
128
|
+
state === "limit_reached" ||
|
|
129
|
+
!input.trim();
|
|
190
130
|
const handleEmojiClick = (emojiData) => {
|
|
191
131
|
setInput((prev) => prev + (emojiData.emoji ?? ""));
|
|
192
132
|
};
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
133
|
+
const handleSend = (0, react_1.useCallback)(async () => {
|
|
134
|
+
const message = input.trim();
|
|
135
|
+
if (!message || sendDisabled)
|
|
136
|
+
return;
|
|
137
|
+
const userMessage = { role: "user", content: message };
|
|
138
|
+
const nextHistory = [...messages, userMessage].slice(-20);
|
|
139
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
140
|
+
setInput("");
|
|
141
|
+
setShowEmojiPicker(false);
|
|
142
|
+
setIsSending(true);
|
|
143
|
+
setErrorMessage(null);
|
|
144
|
+
try {
|
|
145
|
+
const response = await (0, client_js_1.sendChatbotMessage)({
|
|
146
|
+
message,
|
|
147
|
+
history: nextHistory,
|
|
148
|
+
systemPrompt: props.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,
|
|
149
|
+
}, clientOptions);
|
|
150
|
+
setMessages((prev) => [
|
|
151
|
+
...prev,
|
|
152
|
+
{ role: "assistant", content: response.reply },
|
|
153
|
+
]);
|
|
154
|
+
applyUsage(response.usage);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error instanceof client_js_1.ChatbotClientError) {
|
|
158
|
+
if (error.status === 401) {
|
|
159
|
+
setState("signed_out");
|
|
160
|
+
setErrorMessage("You must be signed in to use chatbot.");
|
|
161
|
+
props.onAuthRequired?.();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (error.status === 429) {
|
|
165
|
+
if (error.usage) {
|
|
166
|
+
applyUsage(error.usage);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
setState("limit_reached");
|
|
170
|
+
}
|
|
171
|
+
setErrorMessage("You reached the 10 message demo limit.");
|
|
172
|
+
return;
|
|
206
173
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
174
|
+
setState("error");
|
|
175
|
+
setErrorMessage(error.message);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
setState("error");
|
|
179
|
+
setErrorMessage(error instanceof Error ? error.message : "Message failed.");
|
|
210
180
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
181
|
+
finally {
|
|
182
|
+
setIsSending(false);
|
|
183
|
+
}
|
|
184
|
+
}, [
|
|
185
|
+
applyUsage,
|
|
186
|
+
clientOptions,
|
|
187
|
+
input,
|
|
188
|
+
messages,
|
|
189
|
+
props.onAuthRequired,
|
|
190
|
+
props.systemPrompt,
|
|
191
|
+
sendDisabled,
|
|
192
|
+
]);
|
|
193
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: chatbot_module_css_1.default.chatbotcontainer, children: [(0, jsx_runtime_1.jsxs)("div", { className: chatbot_module_css_1.default.header, children: [(0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.title, children: props.title ?? DEFAULT_TITLE }), (0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.usage, children: usage ? `${usage.used}/${usage.limit} used` : "No usage data" })] }), (state !== "ready" || errorMessage) && ((0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.notice, children: errorMessage ?? statusMessage(state, usage) })), (0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.messagesbox, children: messages.map((msg, index) => ((0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.message, children: (0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default[msg.role], children: (0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.bubble, children: msg.content }) }) }, `${msg.role}-${index}`))) }), (0, jsx_runtime_1.jsxs)("div", { className: chatbot_module_css_1.default.inputbox, children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: input, disabled: sendDisabled, onChange: (event) => setInput(event.target.value), onKeyUp: async (event) => {
|
|
194
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
215
195
|
await handleSend();
|
|
216
|
-
|
|
196
|
+
event.stopPropagation();
|
|
217
197
|
}
|
|
218
|
-
}, placeholder:
|
|
198
|
+
}, placeholder: props.placeholder ?? DEFAULT_PLACEHOLDER }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: chatbot_module_css_1.default.iconButton, onClick: () => setShowEmojiPicker((current) => !current), disabled: state === "signed_out" || state === "limit_reached", "aria-label": "Open emoji picker", children: (0, jsx_runtime_1.jsx)(fa_1.FaSmile, { className: chatbot_module_css_1.default.emojiicon }) }), showEmojiPicker && ((0, jsx_runtime_1.jsx)("div", { className: chatbot_module_css_1.default.emojiPicker, children: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: (0, jsx_runtime_1.jsx)("div", { children: "Loading emoji picker..." }), children: (0, jsx_runtime_1.jsx)(EmojiPicker, { onEmojiClick: handleEmojiClick }) }) })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: chatbot_module_css_1.default.iconButton, onClick: () => void handleSend(), disabled: sendDisabled, "aria-label": "Send message", children: (0, jsx_runtime_1.jsx)(fa_1.FaPaperPlane, { className: chatbot_module_css_1.default.sendicon }) })] })] }));
|
|
219
199
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type ChatRole = "system" | "user" | "assistant";
|
|
2
|
+
export interface ChatMessage {
|
|
3
|
+
role: ChatRole;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ChatbotUsage {
|
|
7
|
+
limit: number;
|
|
8
|
+
used: number;
|
|
9
|
+
remaining: number;
|
|
10
|
+
exhausted: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ChatbotReply {
|
|
13
|
+
reply: string;
|
|
14
|
+
model: string;
|
|
15
|
+
usage: ChatbotUsage;
|
|
16
|
+
}
|
|
17
|
+
export interface ChatbotUsageResponse {
|
|
18
|
+
usage: ChatbotUsage;
|
|
19
|
+
}
|
|
20
|
+
export interface ChatbotClientOptions {
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
credentials?: RequestCredentials;
|
|
23
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
24
|
+
fetchFn?: typeof fetch;
|
|
25
|
+
csrfCookieName?: string;
|
|
26
|
+
csrfHeaderName?: string;
|
|
27
|
+
bootstrapCsrf?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare class ChatbotClientError extends Error {
|
|
30
|
+
status: number;
|
|
31
|
+
code?: string;
|
|
32
|
+
usage?: ChatbotUsage;
|
|
33
|
+
constructor(status: number, message: string, code?: string, usage?: ChatbotUsage);
|
|
34
|
+
}
|
|
35
|
+
export declare function getChatbotUsage(options?: ChatbotClientOptions): Promise<ChatbotUsageResponse>;
|
|
36
|
+
export declare function sendChatbotMessage(payload: {
|
|
37
|
+
message: string;
|
|
38
|
+
history?: ChatMessage[];
|
|
39
|
+
systemPrompt?: string;
|
|
40
|
+
}, options?: ChatbotClientOptions): Promise<ChatbotReply>;
|
|
41
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAQD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,YAAY,CAAC;gBAET,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,YAAY;CAOjF;AAiHD,wBAAsB,eAAe,CACnC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,oBAAoB,CAAC,CA4B/B;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE;IACP,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,EACD,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,YAAY,CAAC,CAiDvB"}
|