diff --git a/README.md b/README.md
index d90e406..c8631f6 100644
--- a/README.md
+++ b/README.md
@@ -62,17 +62,23 @@ make dev
```
**NOTE:** `make dev` Requires Node.js environment installed, or at least have `node_modules` specified in `package.json` installed on your server. Please see [Local Machine](#local-machine) section.
-### Setup in your server
-1. Download this repo into your host machine.
-2. Open [generate_production_env.html](./generate_production_env.html) in your browser and
- * Set fields you want to make change according to instructions
- * Click `Generate Env File` button on the bottom
- * Rename the downloaded file to `.env.production` and copy-paste it into root folder of this project
-3. Make sure you installed `docker` and `make` in your host machine.
-4. Run command `make up` to build & run, find this app on your host machine's port `8000`.
-
-
-**Hint**: Check [docker-compose.yaml](./docker-compose.yaml), [Makefile](./Makefile) and [plugin.js](./tools/plugin.js) to make it fits your own preference.
+## Embed your chatbot into website
+To embed a chatbot to your website, simply add
+```html
+
+```
+into the bottom of your html body element. So easy!
+Please change the `http://localhost:8000` and the base_url in request query to your real host to make sure it works.
+
+If you want to hide the real link, in your javascript code you can do
+```js
+const chatbox_script = await (await fetch("http://localhost:8000/chatbox?base_url=http%3A%2F%2Flocalhost%3A8000")).blob();
+const chatbox_url = URL.createObjectURL(chatbox_script);
+const script_elem = document.createElement('script');
+script_elem.src = chatbox_url;
+document.body.append(script_elem);
+```
+And remember to use `URL.revokeObjectURL(chatbox_url)` if you don't need it anymore.
## Lint
To start lint your code, simply run
diff --git a/generate_production_env.html b/generate_production_env.html
deleted file mode 100644
index 1abd35b..0000000
--- a/generate_production_env.html
+++ /dev/null
@@ -1,376 +0,0 @@
-
-
-
-
-
-
- Generate Voyager Production Env
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/routes/embedding.js b/routes/embedding.js
index 87229db..95da940 100644
--- a/routes/embedding.js
+++ b/routes/embedding.js
@@ -20,7 +20,7 @@ import { isRouteEnabled } from "../tools/enabledApiDecoder.js";
export default function embeddingRoute() {
const router = Router();
- isRouteEnabled("embedding", "index") && router.post("/", embeddings);
+ isRouteEnabled("embedding", "calculate") && router.post("/", embeddings);
isRouteEnabled("embedding", "dataset") && router.post("/dataset", uploadDataset);
return router;
diff --git a/routes/index.js b/routes/index.js
index 20e5863..04e64e5 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -23,6 +23,7 @@ import embeddingRoute from "./embedding.js";
// import decoderRoute from "./decoder.js";
import versionRoute from "./version.js";
import { isRouteEnabled } from "../tools/enabledApiDecoder.js";
+import { generateScript } from "../tools/web_embed.js";
function indexRoute() {
const router = Router();
@@ -33,6 +34,13 @@ function indexRoute() {
})
}
+ if(isRouteEnabled("index", "chatbox")) {
+ router.get('/chatbox', (req, res)=>{
+ res.setHeader("Content-Type", "application/json; charset=utf-8")
+ res.send(generateScript(req.query.base_url));
+ })
+ }
+
return router;
}
diff --git a/setup/config.h b/setup/config.h
index b5b3bed..6a48c69 100644
--- a/setup/config.h
+++ b/setup/config.h
@@ -34,6 +34,7 @@
#define API_INDEX_DOC_ENABLED 1
#define API_INDEX_STATS_ENABLED 1
#define API_INDEX_HEALTHY_ENABLED 1
+#define API_INDEX_CHATBOX_ENABLED 1
#define API_INFERENCE_COMP_ENABLED 1
#define API_INFERENCE_RAG_ENABLED 1
#define API_TOKEN_ENABLED 1
diff --git a/setup/default_config.h b/setup/default_config.h
index 1d01dd8..e9fc66b 100644
--- a/setup/default_config.h
+++ b/setup/default_config.h
@@ -34,6 +34,7 @@
#define DEFAULT_API_INDEX_DOC_ENABLED 1
#define DEFAULT_API_INDEX_STATS_ENABLED 1
#define DEFAULT_API_INDEX_HEALTHY_ENABLED 1
+#define DEFAULT_API_INDEX_CHATBOX_ENABLED 1
#define DEFAULT_API_INFERENCE_COMP_ENABLED 1
#define DEFAULT_API_INFERENCE_RAG_ENABLED 1
#define DEFAULT_API_TOKEN_ENABLED 1
diff --git a/setup/setup.c b/setup/setup.c
index 6fbf4fb..7c8c540 100644
--- a/setup/setup.c
+++ b/setup/setup.c
@@ -31,6 +31,7 @@ char* default_dataset_name = DEFAULT_DATASET_NAME;
int api_index_doc_enabled = API_INDEX_DOC_ENABLED;
int api_index_stats_enabled = API_INDEX_STATS_ENABLED;
int api_index_healthy_enabled = API_INDEX_HEALTHY_ENABLED;
+int api_index_chatbox_enabled = API_INDEX_CHATBOX_ENABLED;
int api_inference_comp_enabled = API_INFERENCE_COMP_ENABLED;
int api_inference_rag_enabled = API_INFERENCE_RAG_ENABLED;
int api_token_enabled = API_TOKEN_ENABLED;
@@ -465,6 +466,7 @@ void logAPIMenu() {
printf(API_INDEX_DOC, api_index_doc_enabled?ENABLED:DISABLED);
printf(API_INDEX_STATS, api_index_stats_enabled?ENABLED:DISABLED);
printf(API_INDEX_HEALTHY, api_index_healthy_enabled?ENABLED:DISABLED);
+ printf(API_INDEX_CHATBOX, api_index_chatbox_enabled?ENABLED:DISABLED);
printf(API_INFERENCE_COMP, api_inference_comp_enabled?ENABLED:DISABLED);
printf(API_INFERENCE_RAG, api_inference_rag_enabled?ENABLED:DISABLED);
printf(API_TOKEN, api_token_enabled?ENABLED:DISABLED);
@@ -484,16 +486,18 @@ void apiSettings() {
case 'c': case 'C':
api_index_healthy_enabled = !api_index_healthy_enabled; break;
case 'd': case 'D':
- api_inference_comp_enabled = !api_inference_comp_enabled; break;
+ api_index_chatbox_enabled = !api_index_chatbox_enabled; break;
case 'e': case 'E':
- api_inference_rag_enabled = !api_inference_rag_enabled; break;
+ api_inference_comp_enabled = !api_inference_comp_enabled; break;
case 'f': case 'F':
- api_token_enabled = !api_token_enabled; break;
+ api_inference_rag_enabled = !api_inference_rag_enabled; break;
case 'g': case 'G':
- api_embedding_calc_enabled = !api_embedding_calc_enabled; break;
+ api_token_enabled = !api_token_enabled; break;
case 'h': case 'H':
- api_embedding_ds_enabled = !api_embedding_ds_enabled; break;
+ api_embedding_calc_enabled = !api_embedding_calc_enabled; break;
case 'i': case 'I':
+ api_embedding_ds_enabled = !api_embedding_ds_enabled; break;
+ case 'j': case 'J':
api_version_enabled = !api_version_enabled; break;
case 'q': case 'Q': case ESC:
return;
@@ -501,6 +505,7 @@ void apiSettings() {
api_index_doc_enabled = 1;
api_index_stats_enabled = 1;
api_index_healthy_enabled = 1;
+ api_index_chatbox_enabled = 1;
api_inference_comp_enabled = 1;
api_inference_rag_enabled = 1;
api_token_enabled = 1;
@@ -512,6 +517,7 @@ void apiSettings() {
api_index_doc_enabled = 0;
api_index_stats_enabled = 0;
api_index_healthy_enabled = 0;
+ api_index_chatbox_enabled = 0;
api_inference_comp_enabled = 0;
api_inference_rag_enabled = 0;
api_token_enabled = 0;
@@ -578,6 +584,7 @@ void saveSettings(int show_message) {
api_index_doc_enabled,
api_index_stats_enabled,
api_index_healthy_enabled,
+ api_index_chatbox_enabled,
api_inference_comp_enabled,
api_inference_rag_enabled,
api_token_enabled,
@@ -605,6 +612,7 @@ void saveSettings(int show_message) {
api_index_doc_enabled &&
api_index_stats_enabled &&
api_index_healthy_enabled &&
+ api_index_chatbox_enabled &&
api_inference_comp_enabled &&
api_inference_rag_enabled &&
api_token_enabled &&
@@ -619,6 +627,7 @@ void saveSettings(int show_message) {
api_index_doc_enabled,
api_index_stats_enabled,
api_index_healthy_enabled,
+ api_index_chatbox_enabled,
api_inference_comp_enabled,
api_inference_rag_enabled,
api_token_enabled,
@@ -736,6 +745,7 @@ void saveDefaultSettings() {
api_index_doc_enabled = DEFAULT_API_INDEX_DOC_ENABLED;
api_index_stats_enabled = DEFAULT_API_INDEX_STATS_ENABLED;
api_index_healthy_enabled = DEFAULT_API_INDEX_HEALTHY_ENABLED;
+ api_index_chatbox_enabled = DEFAULT_API_INDEX_CHATBOX_ENABLED;
api_inference_comp_enabled = DEFAULT_API_INFERENCE_COMP_ENABLED;
api_inference_rag_enabled = DEFAULT_API_INFERENCE_RAG_ENABLED;
api_token_enabled = DEFAULT_API_TOKEN_ENABLED;
diff --git a/setup/setup_types.h b/setup/setup_types.h
index 95682fa..41c6951 100644
--- a/setup/setup_types.h
+++ b/setup/setup_types.h
@@ -392,23 +392,26 @@
#define API_INDEX_HEALTHY \
"c. %s Healthy..............(/healthy)\n"
+#define API_INDEX_CHATBOX \
+ "d. %s Chatbox Embed........(/chatbox)\n"
+
#define API_INFERENCE_COMP \
- "d. %s Inference............(/v1/chat/completions)\n"
+ "e. %s Inference............(/v1/chat/completions)\n"
#define API_INFERENCE_RAG \
- "e. %s RAG Inference........(/v1/chat/rag-completions)\n"
+ "f. %s RAG Inference........(/v1/chat/rag-completions)\n"
#define API_TOKEN \
- "f. %s API Key..............(/v1/token/api-key)\n"
+ "g. %s API Key..............(/v1/token/api-key)\n"
#define API_EMBEDDING_CALC \
- "g. %s Calcualte Embedding..(/v1/embeddings)\n"
+ "h. %s Calcualte Embedding..(/v1/embeddings)\n"
#define API_EMBEDDING_DS \
- "h. %s Upload Dataset.......(/v1/embeddings/dataset)\n"
+ "i. %s Upload Dataset.......(/v1/embeddings/dataset)\n"
#define API_VERSION \
- "i. %s Version..............(/v1/version)\n"
+ "j. %s Version..............(/v1/version)\n"
#define API_MENU_END \
"y. Enable All\n"\
@@ -488,6 +491,7 @@
"#define API_INDEX_DOC_ENABLED %d\n"\
"#define API_INDEX_STATS_ENABLED %d\n"\
"#define API_INDEX_HEALTHY_ENABLED %d\n"\
+"#define API_INDEX_CHATBOX_ENABLED %d\n"\
"#define API_INFERENCE_COMP_ENABLED %d\n"\
"#define API_INFERENCE_RAG_ENABLED %d\n"\
"#define API_TOKEN_ENABLED %d\n"\
@@ -588,7 +592,7 @@
"SYSTEM_INSTRUCTION=\"%s\"\n"\
"AVAILABLE_APIS=\"%s\""
-#define AVAILABLE_APIS_FORMAT "%d%d%d.%d%d.%d.%d%d.%d"
+#define AVAILABLE_APIS_FORMAT "%d%d%d%d.%d%d.%d.%d%d.%d"
#define DOCKERFILE \
"FROM node:20.15.1-slim\n"\
diff --git a/swagger.json b/swagger.json
index 38c0838..9a18c22 100644
--- a/swagger.json
+++ b/swagger.json
@@ -87,6 +87,29 @@
}
}
},
+ "/chatbox": {
+ "get": {
+ "tags": ["Index"],
+ "summary" : "Route to embed chatbot",
+ "description": "Please see this [README](https://github.com/SkywardAI/voyager?tab=readme-ov-file#embed-your-chatbot-into-website).",
+ "parameters": [
+ {
+ "in": "query",
+ "name": "base_url",
+ "type": "string",
+ "description": "The base url of request, usually the url of this page you are looking at. Default http://localhost:8000."
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Returns the load script, usually load with script tag in HTML",
+ "content":{
+ "application/json; charset=utf-8": {}
+ }
+ }
+ }
+ }
+ },
"/v1/chat/completions": {
"post": {
"tags": [
diff --git a/tools/enabledApiDecoder.js b/tools/enabledApiDecoder.js
index 28f35a0..2341ec3 100644
--- a/tools/enabledApiDecoder.js
+++ b/tools/enabledApiDecoder.js
@@ -17,7 +17,8 @@ const allow_paths = {
index: {
docs: false,
stats: false,
- healthy: false
+ healthy: false,
+ chatbox: false
},
inference: {
completions: false,
@@ -71,7 +72,7 @@ export function decodeEnabledAPIs() {
/**
* check if provided route enabled
* @param {"index"|"inference"|"embedding"|"version"|"token"} index_path_name name of route index
- * @param {"docs"|"stats"|"healthy"|"completions"|"rag"} api_name name of specific api
+ * @param {"docs"|"stats"|"healthy"|"chatbox"|"completions"|"rag"|"calculate"|"dataset"} api_name name of specific api
* @returns {Boolean}
*/
export function isRouteEnabled(index_path_name, api_name = null) {
diff --git a/tools/web_embed.js b/tools/web_embed.js
new file mode 100644
index 0000000..c84cc6d
--- /dev/null
+++ b/tools/web_embed.js
@@ -0,0 +1,358 @@
+/**
+ * Generates the script with base_url, to simply embed a chatbot in web page
+ * @param {String} base_url The url to send request, default `http://localhost:8000`
+ */
+export function generateScript(base_url = 'http://localhost:8000') {
+ return `
+(function() {
+ 'use strict';
+
+ const BASE_URL="${base_url}";
+
+const styles = \`
+
+\`
+
+ // styles
+ document.head.insertAdjacentHTML("beforeend", styles);
+
+ const chat_icon = \`
+
+
+
+ \`
+
+ const cancel_icon = \`
+
+
+ \`
+
+ const send_icon = \`
+
+
+ \`
+
+ const circle_icon = \`
+
+
+ \`
+
+ document.body.insertAdjacentHTML("beforeend",
+ \`\`)
+
+ function submitMessage(evt) {
+ evt.preventDefault();
+ const message = evt.target.message.value;
+ if(message) inference(message);
+ evt.target.message.value = '';
+ }
+
+ function createElement(tagName, className, textContent) {
+ const elem = document.createElement(tagName);
+ elem.className = className;
+ elem.textContent = textContent;
+ return elem;
+ }
+
+ async function inference(message) {
+ pending_conversation.before(createElement('div', 'bubble user', message));
+ conversation_main.scrollTo({behavior: "smooth", top: conversation_main.scrollHeight})
+
+ pending_conversation.innerHTML = \`\${circle_icon}\${circle_icon}\${circle_icon}\`
+ pending_conversation.classList.remove('empty')
+
+ const resp = await fetch(\`\${BASE_URL}/v1/chat/completions\`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ authorization: "Bearer example-chatbot"
+ },
+ body: JSON.stringify({
+ messages: [
+ {role: 'system', content: "You are a helpful assistant solves problem"},
+ {role: 'user', content: message}
+ ],
+ stream: true
+ })
+ });
+ if(resp.ok) {
+ const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader();
+ let response = ''
+ while(true) {
+ const { value, done } = await reader.read();
+ if(done) break;
+ try {
+ value.split("\\n\\n").forEach(json_str => {
+ if(json_str) {
+ const { choices } = JSON.parse(json_str);
+ const content = choices[0].delta.content
+ response += content;
+ pending_conversation.textContent = response;
+ conversation_main.scrollTo({
+ behavior: "smooth",
+ top: conversation_main.scrollHeight
+ })
+ }
+ })
+ } catch(error) {
+ console.error(error, value)
+ }
+ }
+ pending_conversation.textContent = '';
+ pending_conversation.classList.add('empty');
+ pending_conversation.before(createElement('div', 'bubble assistant', response));
+ }
+ }
+
+ const chatbox = document.getElementById('voyager-chatbox');
+ const pending_conversation = document.getElementById('pending-conversation');
+ const conversation_main = document.getElementById('conversation-main');
+
+ document.querySelector('#voyager-chatbox > .expand-btn').onclick =
+ ()=>chatbox.classList.replace('folded', 'expanded');
+ document.querySelector('#voyager-chatbox .icon.cancel').onclick =
+ ()=>chatbox.classList.replace('expanded', 'folded');
+ document.querySelector('#voyager-chatbox form.input-message-form').onsubmit = submitMessage;
+})();`
+}
\ No newline at end of file