@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChatbotClientError = void 0;
|
|
4
|
+
exports.getChatbotUsage = getChatbotUsage;
|
|
5
|
+
exports.sendChatbotMessage = sendChatbotMessage;
|
|
6
|
+
class ChatbotClientError extends Error {
|
|
7
|
+
constructor(status, message, code, usage) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ChatbotClientError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.usage = usage;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.ChatbotClientError = ChatbotClientError;
|
|
16
|
+
const DEFAULT_ENDPOINT = "/ai/chatbot";
|
|
17
|
+
const DEFAULT_CSRF_COOKIE_NAME = "csrf-token";
|
|
18
|
+
const DEFAULT_CSRF_HEADER_NAME = "x-csrf-token";
|
|
19
|
+
function resolveFetch(fetchFn) {
|
|
20
|
+
const resolved = fetchFn ?? (typeof fetch !== "undefined" ? fetch : undefined);
|
|
21
|
+
if (!resolved) {
|
|
22
|
+
throw new Error("No fetch implementation is available.");
|
|
23
|
+
}
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
async function resolveHeaders(headers) {
|
|
27
|
+
if (!headers)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (typeof headers === "function") {
|
|
30
|
+
return await headers();
|
|
31
|
+
}
|
|
32
|
+
return headers;
|
|
33
|
+
}
|
|
34
|
+
function readCookie(name) {
|
|
35
|
+
if (typeof document === "undefined" || typeof document.cookie !== "string") {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const entry = document.cookie
|
|
39
|
+
.split(";")
|
|
40
|
+
.map((part) => part.trim())
|
|
41
|
+
.find((value) => value.startsWith(`${name}=`));
|
|
42
|
+
if (!entry)
|
|
43
|
+
return undefined;
|
|
44
|
+
const [, rawValue = ""] = entry.split("=");
|
|
45
|
+
try {
|
|
46
|
+
return decodeURIComponent(rawValue);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return rawValue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function normalizeUsage(value) {
|
|
53
|
+
if (!value || typeof value !== "object")
|
|
54
|
+
return undefined;
|
|
55
|
+
const usage = value;
|
|
56
|
+
if (typeof usage.limit !== "number" ||
|
|
57
|
+
typeof usage.used !== "number" ||
|
|
58
|
+
typeof usage.remaining !== "number" ||
|
|
59
|
+
typeof usage.exhausted !== "boolean") {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
limit: usage.limit,
|
|
64
|
+
used: usage.used,
|
|
65
|
+
remaining: usage.remaining,
|
|
66
|
+
exhausted: usage.exhausted,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function parseBody(response) {
|
|
70
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
71
|
+
if (contentType.includes("application/json")) {
|
|
72
|
+
return await response.json();
|
|
73
|
+
}
|
|
74
|
+
const text = await response.text();
|
|
75
|
+
if (!text)
|
|
76
|
+
return undefined;
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(text);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function normalizeError(status, body) {
|
|
85
|
+
const payload = body && typeof body === "object" ? body : undefined;
|
|
86
|
+
const fallbackMessage = status === 401
|
|
87
|
+
? "Sign in to use chatbot."
|
|
88
|
+
: status === 429
|
|
89
|
+
? "Chatbot usage limit reached."
|
|
90
|
+
: "Chatbot request failed.";
|
|
91
|
+
const message = payload?.message ?? fallbackMessage;
|
|
92
|
+
return new ChatbotClientError(status, message, payload?.error, payload?.usage);
|
|
93
|
+
}
|
|
94
|
+
async function ensureCsrfToken(fetcher, endpoint, options, baseHeaders) {
|
|
95
|
+
const cookieName = options.csrfCookieName ?? DEFAULT_CSRF_COOKIE_NAME;
|
|
96
|
+
const existing = readCookie(cookieName);
|
|
97
|
+
if (existing || options.bootstrapCsrf === false) {
|
|
98
|
+
return existing;
|
|
99
|
+
}
|
|
100
|
+
await fetcher(endpoint, {
|
|
101
|
+
method: "GET",
|
|
102
|
+
credentials: options.credentials ?? "include",
|
|
103
|
+
headers: baseHeaders,
|
|
104
|
+
});
|
|
105
|
+
return readCookie(cookieName);
|
|
106
|
+
}
|
|
107
|
+
async function getChatbotUsage(options = {}) {
|
|
108
|
+
const fetcher = resolveFetch(options.fetchFn);
|
|
109
|
+
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
110
|
+
const customHeaders = await resolveHeaders(options.headers);
|
|
111
|
+
const response = await fetcher(endpoint, {
|
|
112
|
+
method: "GET",
|
|
113
|
+
credentials: options.credentials ?? "include",
|
|
114
|
+
headers: {
|
|
115
|
+
Accept: "application/json",
|
|
116
|
+
...(customHeaders ?? {}),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const body = await parseBody(response);
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
throw normalizeError(response.status, body);
|
|
122
|
+
}
|
|
123
|
+
if (!body || typeof body !== "object") {
|
|
124
|
+
throw new Error("Invalid chatbot usage response.");
|
|
125
|
+
}
|
|
126
|
+
const usage = normalizeUsage(body.usage);
|
|
127
|
+
if (!usage) {
|
|
128
|
+
throw new Error("Invalid chatbot usage response.");
|
|
129
|
+
}
|
|
130
|
+
return { usage };
|
|
131
|
+
}
|
|
132
|
+
async function sendChatbotMessage(payload, options = {}) {
|
|
133
|
+
const fetcher = resolveFetch(options.fetchFn);
|
|
134
|
+
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
135
|
+
const customHeaders = await resolveHeaders(options.headers);
|
|
136
|
+
const baseHeaders = {
|
|
137
|
+
Accept: "application/json",
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
...(customHeaders ?? {}),
|
|
140
|
+
};
|
|
141
|
+
const csrfToken = await ensureCsrfToken(fetcher, endpoint, options, baseHeaders);
|
|
142
|
+
const csrfHeader = options.csrfHeaderName ?? DEFAULT_CSRF_HEADER_NAME;
|
|
143
|
+
const requestHeaders = csrfToken
|
|
144
|
+
? {
|
|
145
|
+
...baseHeaders,
|
|
146
|
+
[csrfHeader]: csrfToken,
|
|
147
|
+
}
|
|
148
|
+
: baseHeaders;
|
|
149
|
+
const response = await fetcher(endpoint, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
credentials: options.credentials ?? "include",
|
|
152
|
+
headers: requestHeaders,
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
message: payload.message,
|
|
155
|
+
history: payload.history ?? [],
|
|
156
|
+
systemPrompt: payload.systemPrompt,
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
const body = await parseBody(response);
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw normalizeError(response.status, body);
|
|
162
|
+
}
|
|
163
|
+
if (!body || typeof body !== "object") {
|
|
164
|
+
throw new Error("Invalid chatbot response.");
|
|
165
|
+
}
|
|
166
|
+
const content = body;
|
|
167
|
+
const reply = content.reply;
|
|
168
|
+
const model = content.model;
|
|
169
|
+
const usage = normalizeUsage(content.usage);
|
|
170
|
+
if (typeof reply !== "string" || typeof model !== "string" || !usage) {
|
|
171
|
+
throw new Error("Invalid chatbot response.");
|
|
172
|
+
}
|
|
173
|
+
return { reply, model, usage };
|
|
174
|
+
}
|
package/dist-cjs/index.d.ts
CHANGED
package/dist-cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,cAAc,aAAa,CAAC"}
|
package/dist-cjs/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
18
|
};
|
|
@@ -6,3 +20,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
20
|
exports.ChatBot = void 0;
|
|
7
21
|
var chatbot_js_1 = require("./chatbot.js");
|
|
8
22
|
Object.defineProperty(exports, "ChatBot", { enumerable: true, get: function () { return __importDefault(chatbot_js_1).default; } });
|
|
23
|
+
__exportStar(require("./client.js"), exports);
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/chatbot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "OpenAI Chatbot",
|
|
5
5
|
"main": "./dist-cjs/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"react-icons": "^5.5.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"openai": "^5.19.1",
|
|
42
41
|
"react": "^19.1.0"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
@@ -52,7 +51,6 @@
|
|
|
52
51
|
"depcheck": "^1.4.7",
|
|
53
52
|
"eslint": "^9.33.0",
|
|
54
53
|
"npm-run-all": "^4.1.5",
|
|
55
|
-
"openai": "^5.19.1",
|
|
56
54
|
"react": "19.1.0",
|
|
57
55
|
"react-dom": "19.1.0",
|
|
58
56
|
"tsx": "^4.20.3",
|