chatbotlite 0.2.0 → 0.3.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/README.md +93 -53
- package/dist/client/index.cjs +27 -91
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +14 -9
- package/dist/client/index.d.ts +14 -9
- package/dist/client/index.js +28 -91
- package/dist/client/index.js.map +1 -1
- package/dist/core/index.cjs +15 -50
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -7
- package/dist/core/index.d.ts +9 -7
- package/dist/core/index.js +15 -50
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +27 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -91
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +41 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +37 -0
- package/dist/node/index.d.ts +37 -0
- package/dist/node/index.js +38 -0
- package/dist/node/index.js.map +1 -0
- package/dist/react/index.cjs +32 -95
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +7 -7
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +32 -95
- package/dist/react/index.js.map +1 -1
- package/dist/types-4alyzg8O.d.cts +16 -0
- package/dist/types-4alyzg8O.d.ts +16 -0
- package/dist/types-J7BXpiRU.d.cts +63 -0
- package/dist/types-J7BXpiRU.d.ts +63 -0
- package/package.json +12 -3
- package/dist/types-DS7rM6Vl.d.cts +0 -82
- package/dist/types-DS7rM6Vl.d.ts +0 -82
- package/dist/types-DYNx47by.d.cts +0 -39
- package/dist/types-DYNx47by.d.ts +0 -39
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
|
|
6
|
+
// src/node/index.ts
|
|
7
|
+
function knowledgeFromDir(dir, opts = {}) {
|
|
8
|
+
const exts = (opts.exts ?? [".md", ".markdown", ".txt"]).map((e) => e.toLowerCase());
|
|
9
|
+
const useHeaders = opts.headers ?? true;
|
|
10
|
+
const abs = path.resolve(dir);
|
|
11
|
+
const stat = fs.statSync(abs);
|
|
12
|
+
if (!stat.isDirectory()) {
|
|
13
|
+
throw new Error(`chatbotlite: ${abs} is not a directory.`);
|
|
14
|
+
}
|
|
15
|
+
const files = fs.readdirSync(abs).filter((f) => exts.includes(path.extname(f).toLowerCase())).sort();
|
|
16
|
+
if (files.length === 0) {
|
|
17
|
+
throw new Error(`chatbotlite: no ${exts.join("/")} files found in ${abs}.`);
|
|
18
|
+
}
|
|
19
|
+
const parts = [];
|
|
20
|
+
for (const f of files) {
|
|
21
|
+
const content = fs.readFileSync(path.join(abs, f), "utf8").trim();
|
|
22
|
+
if (useHeaders) {
|
|
23
|
+
const heading = path.basename(f, path.extname(f));
|
|
24
|
+
parts.push(`# ${heading}
|
|
25
|
+
|
|
26
|
+
${content}`);
|
|
27
|
+
} else {
|
|
28
|
+
parts.push(content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return parts.join("\n\n");
|
|
32
|
+
}
|
|
33
|
+
function knowledgeFromFile(path$1) {
|
|
34
|
+
const abs = path.resolve(path$1);
|
|
35
|
+
return fs.readFileSync(abs, "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.knowledgeFromDir = knowledgeFromDir;
|
|
39
|
+
exports.knowledgeFromFile = knowledgeFromFile;
|
|
40
|
+
//# sourceMappingURL=index.cjs.map
|
|
41
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/node/index.ts"],"names":["resolve","statSync","readdirSync","extname","readFileSync","join","basename","path"],"mappings":";;;;;;AA2BO,SAAS,gBAAA,CACd,GAAA,EACA,IAAA,GAA+C,EAAC,EACxC;AACR,EAAA,MAAM,IAAA,GAAA,CAAQ,IAAA,CAAK,IAAA,IAAQ,CAAC,KAAA,EAAO,WAAA,EAAa,MAAM,CAAA,EAAG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AACnF,EAAA,MAAM,UAAA,GAAa,KAAK,OAAA,IAAW,IAAA;AACnC,EAAA,MAAM,GAAA,GAAMA,aAAQ,GAAG,CAAA;AACvB,EAAA,MAAM,IAAA,GAAOC,YAAS,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,IAAA,CAAK,WAAA,EAAY,EAAG;AACvB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,aAAA,EAAgB,GAAG,CAAA,oBAAA,CAAsB,CAAA;AAAA,EAC3D;AACA,EAAA,MAAM,QAAQC,cAAA,CAAY,GAAG,CAAA,CAC1B,MAAA,CAAO,CAAC,CAAA,KAAM,IAAA,CAAK,QAAA,CAASC,YAAA,CAAQ,CAAC,CAAA,CAAE,WAAA,EAAa,CAAC,EACrD,IAAA,EAAK;AACR,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,IAAA,MAAM,IAAI,MAAM,CAAA,gBAAA,EAAmB,IAAA,CAAK,KAAK,GAAG,CAAC,CAAA,gBAAA,EAAmB,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,EAC5E;AACA,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,MAAM,OAAA,GAAUC,gBAAaC,SAAA,CAAK,GAAA,EAAK,CAAC,CAAA,EAAG,MAAM,EAAE,IAAA,EAAK;AACxD,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,MAAM,OAAA,GAAUC,aAAA,CAAS,CAAA,EAAGH,YAAA,CAAQ,CAAC,CAAC,CAAA;AACtC,MAAA,KAAA,CAAM,IAAA,CAAK,KAAK,OAAO;;AAAA,EAAO,OAAO,CAAA,CAAE,CAAA;AAAA,IACzC,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,IACpB;AAAA,EACF;AACA,EAAA,OAAO,KAAA,CAAM,KAAK,MAAM,CAAA;AAC1B;AAWO,SAAS,kBAAkBI,MAAA,EAAsB;AACtD,EAAA,MAAM,GAAA,GAAMP,aAAQO,MAAI,CAAA;AACxB,EAAA,OAAOH,eAAA,CAAa,KAAK,MAAM,CAAA;AACjC","file":"index.cjs","sourcesContent":["// Node-only helpers — use these in your server code.\n// Browser code should pass knowledge as a string directly.\n\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join, basename, extname, resolve } from \"node:path\";\n\n/**\n * Load a folder of markdown / text files as a single concatenated knowledge string.\n *\n * Each file becomes a top-level section in the output, headed by its filename\n * (without extension). Files are concatenated in alphabetical order.\n *\n * @example\n * ```ts\n * import { ChatBot } from \"chatbotlite\";\n * import { knowledgeFromDir } from \"chatbotlite/node\";\n *\n * const bot = new ChatBot({\n * knowledge: knowledgeFromDir(\"./kb\"),\n * providers: { keys: { openai: process.env.OPENAI_API_KEY! } }\n * });\n * ```\n *\n * @param dir Path to the folder (absolute or relative to cwd).\n * @param opts.exts File extensions to include (default `[\".md\", \".markdown\", \".txt\"]`).\n * @param opts.headers If true (default), wrap each file's content in a `# filename` heading.\n */\nexport function knowledgeFromDir(\n dir: string,\n opts: { exts?: string[]; headers?: boolean } = {}\n): string {\n const exts = (opts.exts ?? [\".md\", \".markdown\", \".txt\"]).map((e) => e.toLowerCase());\n const useHeaders = opts.headers ?? true;\n const abs = resolve(dir);\n const stat = statSync(abs);\n if (!stat.isDirectory()) {\n throw new Error(`chatbotlite: ${abs} is not a directory.`);\n }\n const files = readdirSync(abs)\n .filter((f) => exts.includes(extname(f).toLowerCase()))\n .sort();\n if (files.length === 0) {\n throw new Error(`chatbotlite: no ${exts.join(\"/\")} files found in ${abs}.`);\n }\n const parts: string[] = [];\n for (const f of files) {\n const content = readFileSync(join(abs, f), \"utf8\").trim();\n if (useHeaders) {\n const heading = basename(f, extname(f));\n parts.push(`# ${heading}\\n\\n${content}`);\n } else {\n parts.push(content);\n }\n }\n return parts.join(\"\\n\\n\");\n}\n\n/**\n * Load a single markdown/text file as a knowledge string.\n *\n * @example\n * ```ts\n * import { knowledgeFromFile } from \"chatbotlite/node\";\n * const knowledge = knowledgeFromFile(\"./business.md\");\n * ```\n */\nexport function knowledgeFromFile(path: string): string {\n const abs = resolve(path);\n return readFileSync(abs, \"utf8\");\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load a folder of markdown / text files as a single concatenated knowledge string.
|
|
3
|
+
*
|
|
4
|
+
* Each file becomes a top-level section in the output, headed by its filename
|
|
5
|
+
* (without extension). Files are concatenated in alphabetical order.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { ChatBot } from "chatbotlite";
|
|
10
|
+
* import { knowledgeFromDir } from "chatbotlite/node";
|
|
11
|
+
*
|
|
12
|
+
* const bot = new ChatBot({
|
|
13
|
+
* knowledge: knowledgeFromDir("./kb"),
|
|
14
|
+
* providers: { keys: { openai: process.env.OPENAI_API_KEY! } }
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @param dir Path to the folder (absolute or relative to cwd).
|
|
19
|
+
* @param opts.exts File extensions to include (default `[".md", ".markdown", ".txt"]`).
|
|
20
|
+
* @param opts.headers If true (default), wrap each file's content in a `# filename` heading.
|
|
21
|
+
*/
|
|
22
|
+
declare function knowledgeFromDir(dir: string, opts?: {
|
|
23
|
+
exts?: string[];
|
|
24
|
+
headers?: boolean;
|
|
25
|
+
}): string;
|
|
26
|
+
/**
|
|
27
|
+
* Load a single markdown/text file as a knowledge string.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { knowledgeFromFile } from "chatbotlite/node";
|
|
32
|
+
* const knowledge = knowledgeFromFile("./business.md");
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function knowledgeFromFile(path: string): string;
|
|
36
|
+
|
|
37
|
+
export { knowledgeFromDir, knowledgeFromFile };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load a folder of markdown / text files as a single concatenated knowledge string.
|
|
3
|
+
*
|
|
4
|
+
* Each file becomes a top-level section in the output, headed by its filename
|
|
5
|
+
* (without extension). Files are concatenated in alphabetical order.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { ChatBot } from "chatbotlite";
|
|
10
|
+
* import { knowledgeFromDir } from "chatbotlite/node";
|
|
11
|
+
*
|
|
12
|
+
* const bot = new ChatBot({
|
|
13
|
+
* knowledge: knowledgeFromDir("./kb"),
|
|
14
|
+
* providers: { keys: { openai: process.env.OPENAI_API_KEY! } }
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @param dir Path to the folder (absolute or relative to cwd).
|
|
19
|
+
* @param opts.exts File extensions to include (default `[".md", ".markdown", ".txt"]`).
|
|
20
|
+
* @param opts.headers If true (default), wrap each file's content in a `# filename` heading.
|
|
21
|
+
*/
|
|
22
|
+
declare function knowledgeFromDir(dir: string, opts?: {
|
|
23
|
+
exts?: string[];
|
|
24
|
+
headers?: boolean;
|
|
25
|
+
}): string;
|
|
26
|
+
/**
|
|
27
|
+
* Load a single markdown/text file as a knowledge string.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { knowledgeFromFile } from "chatbotlite/node";
|
|
32
|
+
* const knowledge = knowledgeFromFile("./business.md");
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function knowledgeFromFile(path: string): string;
|
|
36
|
+
|
|
37
|
+
export { knowledgeFromDir, knowledgeFromFile };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { statSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { resolve, extname, join, basename } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/node/index.ts
|
|
5
|
+
function knowledgeFromDir(dir, opts = {}) {
|
|
6
|
+
const exts = (opts.exts ?? [".md", ".markdown", ".txt"]).map((e) => e.toLowerCase());
|
|
7
|
+
const useHeaders = opts.headers ?? true;
|
|
8
|
+
const abs = resolve(dir);
|
|
9
|
+
const stat = statSync(abs);
|
|
10
|
+
if (!stat.isDirectory()) {
|
|
11
|
+
throw new Error(`chatbotlite: ${abs} is not a directory.`);
|
|
12
|
+
}
|
|
13
|
+
const files = readdirSync(abs).filter((f) => exts.includes(extname(f).toLowerCase())).sort();
|
|
14
|
+
if (files.length === 0) {
|
|
15
|
+
throw new Error(`chatbotlite: no ${exts.join("/")} files found in ${abs}.`);
|
|
16
|
+
}
|
|
17
|
+
const parts = [];
|
|
18
|
+
for (const f of files) {
|
|
19
|
+
const content = readFileSync(join(abs, f), "utf8").trim();
|
|
20
|
+
if (useHeaders) {
|
|
21
|
+
const heading = basename(f, extname(f));
|
|
22
|
+
parts.push(`# ${heading}
|
|
23
|
+
|
|
24
|
+
${content}`);
|
|
25
|
+
} else {
|
|
26
|
+
parts.push(content);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return parts.join("\n\n");
|
|
30
|
+
}
|
|
31
|
+
function knowledgeFromFile(path) {
|
|
32
|
+
const abs = resolve(path);
|
|
33
|
+
return readFileSync(abs, "utf8");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { knowledgeFromDir, knowledgeFromFile };
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/node/index.ts"],"names":[],"mappings":";;;;AA2BO,SAAS,gBAAA,CACd,GAAA,EACA,IAAA,GAA+C,EAAC,EACxC;AACR,EAAA,MAAM,IAAA,GAAA,CAAQ,IAAA,CAAK,IAAA,IAAQ,CAAC,KAAA,EAAO,WAAA,EAAa,MAAM,CAAA,EAAG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AACnF,EAAA,MAAM,UAAA,GAAa,KAAK,OAAA,IAAW,IAAA;AACnC,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAG,CAAA;AACvB,EAAA,MAAM,IAAA,GAAO,SAAS,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,IAAA,CAAK,WAAA,EAAY,EAAG;AACvB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,aAAA,EAAgB,GAAG,CAAA,oBAAA,CAAsB,CAAA;AAAA,EAC3D;AACA,EAAA,MAAM,QAAQ,WAAA,CAAY,GAAG,CAAA,CAC1B,MAAA,CAAO,CAAC,CAAA,KAAM,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA,CAAE,WAAA,EAAa,CAAC,EACrD,IAAA,EAAK;AACR,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,IAAA,MAAM,IAAI,MAAM,CAAA,gBAAA,EAAmB,IAAA,CAAK,KAAK,GAAG,CAAC,CAAA,gBAAA,EAAmB,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,EAC5E;AACA,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,MAAM,OAAA,GAAU,aAAa,IAAA,CAAK,GAAA,EAAK,CAAC,CAAA,EAAG,MAAM,EAAE,IAAA,EAAK;AACxD,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAC,CAAA;AACtC,MAAA,KAAA,CAAM,IAAA,CAAK,KAAK,OAAO;;AAAA,EAAO,OAAO,CAAA,CAAE,CAAA;AAAA,IACzC,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,IACpB;AAAA,EACF;AACA,EAAA,OAAO,KAAA,CAAM,KAAK,MAAM,CAAA;AAC1B;AAWO,SAAS,kBAAkB,IAAA,EAAsB;AACtD,EAAA,MAAM,GAAA,GAAM,QAAQ,IAAI,CAAA;AACxB,EAAA,OAAO,YAAA,CAAa,KAAK,MAAM,CAAA;AACjC","file":"index.js","sourcesContent":["// Node-only helpers — use these in your server code.\n// Browser code should pass knowledge as a string directly.\n\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join, basename, extname, resolve } from \"node:path\";\n\n/**\n * Load a folder of markdown / text files as a single concatenated knowledge string.\n *\n * Each file becomes a top-level section in the output, headed by its filename\n * (without extension). Files are concatenated in alphabetical order.\n *\n * @example\n * ```ts\n * import { ChatBot } from \"chatbotlite\";\n * import { knowledgeFromDir } from \"chatbotlite/node\";\n *\n * const bot = new ChatBot({\n * knowledge: knowledgeFromDir(\"./kb\"),\n * providers: { keys: { openai: process.env.OPENAI_API_KEY! } }\n * });\n * ```\n *\n * @param dir Path to the folder (absolute or relative to cwd).\n * @param opts.exts File extensions to include (default `[\".md\", \".markdown\", \".txt\"]`).\n * @param opts.headers If true (default), wrap each file's content in a `# filename` heading.\n */\nexport function knowledgeFromDir(\n dir: string,\n opts: { exts?: string[]; headers?: boolean } = {}\n): string {\n const exts = (opts.exts ?? [\".md\", \".markdown\", \".txt\"]).map((e) => e.toLowerCase());\n const useHeaders = opts.headers ?? true;\n const abs = resolve(dir);\n const stat = statSync(abs);\n if (!stat.isDirectory()) {\n throw new Error(`chatbotlite: ${abs} is not a directory.`);\n }\n const files = readdirSync(abs)\n .filter((f) => exts.includes(extname(f).toLowerCase()))\n .sort();\n if (files.length === 0) {\n throw new Error(`chatbotlite: no ${exts.join(\"/\")} files found in ${abs}.`);\n }\n const parts: string[] = [];\n for (const f of files) {\n const content = readFileSync(join(abs, f), \"utf8\").trim();\n if (useHeaders) {\n const heading = basename(f, extname(f));\n parts.push(`# ${heading}\\n\\n${content}`);\n } else {\n parts.push(content);\n }\n }\n return parts.join(\"\\n\\n\");\n}\n\n/**\n * Load a single markdown/text file as a knowledge string.\n *\n * @example\n * ```ts\n * import { knowledgeFromFile } from \"chatbotlite/node\";\n * const knowledge = knowledgeFromFile(\"./business.md\");\n * ```\n */\nexport function knowledgeFromFile(path: string): string {\n const abs = resolve(path);\n return readFileSync(abs, \"utf8\");\n}\n"]}
|
package/dist/react/index.cjs
CHANGED
|
@@ -6,56 +6,21 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
6
6
|
// src/react/ChatWidget.tsx
|
|
7
7
|
|
|
8
8
|
// src/core/prompts.ts
|
|
9
|
-
function buildSystemPrompt(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
parts.push(`Hours: ${business.hours}`);
|
|
25
|
-
}
|
|
26
|
-
if (business.serviceArea && business.serviceArea.length > 0) {
|
|
27
|
-
parts.push(`Service area: ${business.serviceArea.join(", ")}.`);
|
|
28
|
-
parts.push("If the caller is outside this area, say so and recommend owner review.");
|
|
29
|
-
}
|
|
30
|
-
if (business.policies && business.policies.length > 0) {
|
|
31
|
-
parts.push("");
|
|
32
|
-
parts.push("Known policies (use these exact answers when asked):");
|
|
33
|
-
for (const p of business.policies) {
|
|
34
|
-
parts.push(`- ${p.topic}: ${p.answer}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
parts.push("");
|
|
38
|
-
parts.push("Rules:");
|
|
39
|
-
parts.push("- Reply in 1-2 short sentences, conversational tone.");
|
|
40
|
-
parts.push("- NEVER invent prices, availability, dispatch times, or appointment confirmations.");
|
|
41
|
-
parts.push("- For anything not covered in the setup above, say it needs owner review.");
|
|
42
|
-
parts.push('- If the caller is clearly a vendor/sales pitch, say: "This does not look like a customer service request, so we will not continue this thread."');
|
|
43
|
-
parts.push(`- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`);
|
|
44
|
-
if (business.doNotPromise && business.doNotPromise.length > 0) {
|
|
45
|
-
parts.push("");
|
|
46
|
-
parts.push("Never promise:");
|
|
47
|
-
for (const p of business.doNotPromise) parts.push(`- ${p}`);
|
|
48
|
-
}
|
|
49
|
-
if (business.customInstructions) {
|
|
50
|
-
parts.push("");
|
|
51
|
-
parts.push("Additional instructions:");
|
|
52
|
-
parts.push(business.customInstructions);
|
|
53
|
-
}
|
|
54
|
-
if (business.language && business.language !== "en") {
|
|
55
|
-
parts.push("");
|
|
56
|
-
parts.push(`Reply in ${business.language}.`);
|
|
57
|
-
}
|
|
58
|
-
return parts.join("\n");
|
|
9
|
+
function buildSystemPrompt(knowledge) {
|
|
10
|
+
return [
|
|
11
|
+
"You are an AI assistant on a business website. Use ONLY the knowledge below to answer.",
|
|
12
|
+
"",
|
|
13
|
+
"## Business knowledge",
|
|
14
|
+
knowledge.trim(),
|
|
15
|
+
"",
|
|
16
|
+
"## Reply rules",
|
|
17
|
+
"- Reply in 1-2 short sentences, conversational tone.",
|
|
18
|
+
"- NEVER invent prices, availability, dispatch times, appointment confirmations, or facts not present in the business knowledge above.",
|
|
19
|
+
"- For anything not covered in the knowledge above, say the owner will follow up \u2014 do NOT guess.",
|
|
20
|
+
'- If the caller is clearly a vendor/sales pitch, say: "This does not look like a customer service request, so we will not continue this thread."',
|
|
21
|
+
`- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`,
|
|
22
|
+
"- Match the caller's language automatically."
|
|
23
|
+
].join("\n");
|
|
59
24
|
}
|
|
60
25
|
|
|
61
26
|
// src/core/guards.ts
|
|
@@ -119,24 +84,6 @@ var PROVIDER_NAMES = /* @__PURE__ */ new Set([
|
|
|
119
84
|
"openrouter",
|
|
120
85
|
"moonshot"
|
|
121
86
|
]);
|
|
122
|
-
function parseChainSpec(spec) {
|
|
123
|
-
const slash = spec.indexOf("/");
|
|
124
|
-
if (slash === -1) {
|
|
125
|
-
if (!PROVIDER_NAMES.has(spec)) {
|
|
126
|
-
throw new Error(`chatbotlite: unknown provider "${spec}". Use "provider/model" or a known provider name.`);
|
|
127
|
-
}
|
|
128
|
-
return { provider: spec, model: null };
|
|
129
|
-
}
|
|
130
|
-
const provider = spec.slice(0, slash);
|
|
131
|
-
const model = spec.slice(slash + 1);
|
|
132
|
-
if (!PROVIDER_NAMES.has(provider)) {
|
|
133
|
-
throw new Error(`chatbotlite: unknown provider "${provider}" in chain spec "${spec}".`);
|
|
134
|
-
}
|
|
135
|
-
if (!model) {
|
|
136
|
-
throw new Error(`chatbotlite: empty model name in chain spec "${spec}".`);
|
|
137
|
-
}
|
|
138
|
-
return { provider, model };
|
|
139
|
-
}
|
|
140
87
|
function isKnownProvider(name) {
|
|
141
88
|
return PROVIDER_NAMES.has(name);
|
|
142
89
|
}
|
|
@@ -162,19 +109,20 @@ function isRetryableError(err) {
|
|
|
162
109
|
|
|
163
110
|
// src/client/chatbot.ts
|
|
164
111
|
var ChatBot = class {
|
|
165
|
-
business;
|
|
166
112
|
steps;
|
|
167
113
|
keys;
|
|
168
114
|
fetcher;
|
|
169
115
|
timeoutMs;
|
|
170
116
|
cachedSystemPrompt;
|
|
171
117
|
constructor(init) {
|
|
172
|
-
|
|
118
|
+
if (!init.knowledge || typeof init.knowledge !== "string" || init.knowledge.trim().length === 0) {
|
|
119
|
+
throw new Error("chatbotlite: knowledge is required (a non-empty markdown string).");
|
|
120
|
+
}
|
|
173
121
|
this.keys = init.providers.keys ?? {};
|
|
174
122
|
this.steps = resolveChain(init.providers);
|
|
175
123
|
this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
176
124
|
this.timeoutMs = init.options?.timeoutMs ?? 3e4;
|
|
177
|
-
this.cachedSystemPrompt = buildSystemPrompt(
|
|
125
|
+
this.cachedSystemPrompt = buildSystemPrompt(init.knowledge);
|
|
178
126
|
}
|
|
179
127
|
async reply(message, opts = {}) {
|
|
180
128
|
const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;
|
|
@@ -211,7 +159,7 @@ var ChatBot = class {
|
|
|
211
159
|
latencyMs: Date.now() - t0
|
|
212
160
|
});
|
|
213
161
|
if (!isRetryableError(err)) {
|
|
214
|
-
throw new Error(`chatbotlite: ${step.
|
|
162
|
+
throw new Error(`chatbotlite: ${step.label} failed (non-retryable). ${errMsg}`);
|
|
215
163
|
}
|
|
216
164
|
}
|
|
217
165
|
}
|
|
@@ -268,30 +216,19 @@ function resolveChain(providers) {
|
|
|
268
216
|
return orderedProviders.map((provider) => ({
|
|
269
217
|
provider,
|
|
270
218
|
model: PROVIDER_ENDPOINTS[provider].defaultModel,
|
|
271
|
-
|
|
219
|
+
label: `${provider}/${PROVIDER_ENDPOINTS[provider].defaultModel}`
|
|
272
220
|
}));
|
|
273
221
|
}
|
|
274
222
|
function normalizeChainEntry(entry, keys) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
let spec;
|
|
278
|
-
if (typeof entry === "string") {
|
|
279
|
-
const parsed = parseChainSpec(entry);
|
|
280
|
-
provider = parsed.provider;
|
|
281
|
-
model = parsed.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
|
|
282
|
-
spec = entry;
|
|
283
|
-
} else {
|
|
284
|
-
if (!isKnownProvider(entry.provider)) {
|
|
285
|
-
throw new Error(`chatbotlite: unknown provider "${entry.provider}" in chain entry.`);
|
|
286
|
-
}
|
|
287
|
-
provider = entry.provider;
|
|
288
|
-
model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
|
|
289
|
-
spec = `${provider}/${model}`;
|
|
223
|
+
if (!isKnownProvider(entry.provider)) {
|
|
224
|
+
throw new Error(`chatbotlite: unknown provider "${entry.provider}" in chain entry.`);
|
|
290
225
|
}
|
|
226
|
+
const provider = entry.provider;
|
|
227
|
+
const model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
|
|
291
228
|
if (!keys[provider]) {
|
|
292
|
-
throw new Error(`chatbotlite: chain
|
|
229
|
+
throw new Error(`chatbotlite: chain entry for "${provider}" needs a matching key in providers.keys.`);
|
|
293
230
|
}
|
|
294
|
-
return { provider, model,
|
|
231
|
+
return { provider, model, label: `${provider}/${model}` };
|
|
295
232
|
}
|
|
296
233
|
var BOLT = "\u26A1";
|
|
297
234
|
var DEFAULT_PRIMARY = "#0f172a";
|
|
@@ -342,8 +279,8 @@ function ChatWidget(props) {
|
|
|
342
279
|
position = "bottom-right"
|
|
343
280
|
} = props;
|
|
344
281
|
const isEndpointMode = "endpoint" in props && typeof props.endpoint === "string";
|
|
345
|
-
const resolvedTitle = title ??
|
|
346
|
-
const resolvedGreeting = greeting ??
|
|
282
|
+
const resolvedTitle = title ?? "Chat";
|
|
283
|
+
const resolvedGreeting = greeting ?? "Hi! How can we help?";
|
|
347
284
|
const primary = themeOverrides?.primary ?? DEFAULT_PRIMARY;
|
|
348
285
|
const onPrimary = themeOverrides?.onPrimary ?? DEFAULT_ON_PRIMARY;
|
|
349
286
|
const [open, setOpen] = react.useState(false);
|
|
@@ -359,9 +296,9 @@ function ChatWidget(props) {
|
|
|
359
296
|
}, []);
|
|
360
297
|
const bot = react.useMemo(() => {
|
|
361
298
|
if (isEndpointMode) return null;
|
|
362
|
-
if (!props.
|
|
363
|
-
return new ChatBot({
|
|
364
|
-
}, [isEndpointMode, props.
|
|
299
|
+
if (!props.knowledge || !props.providers) return null;
|
|
300
|
+
return new ChatBot({ knowledge: props.knowledge, providers: props.providers });
|
|
301
|
+
}, [isEndpointMode, props.knowledge, props.providers]);
|
|
365
302
|
react.useEffect(() => {
|
|
366
303
|
if (scrollRef.current) {
|
|
367
304
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|