Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce more chat customization options #671

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 26 additions & 19 deletions copilot-widget/README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,57 @@
# Copilot widget
# OpenCopilot widget

This is the widget for your copilot, it's what your users will interact with.
This is the widget for OpenCopilot: it's what your users will interact with.

It's a simple react application built to be used in any webpage as a widget, to download the latest build of the widget, go to the actions tab and download the latest build artifact.
It's a simple React application that can be used in any webpage as a widget. To download the latest build of the widget, go to the actions tab in GitHub and download the latest build artifact.

## How to install

1. download the latest build artifact from the actions tab.
1. Download the latest build artifact from the actions tab.

2. extract the zip file.
2. Extract the zip file.

3. copy the `assets/*.js` file to your project.
3. Copy the `assets/*.js` file to your project.

4. reference the js file in your html file as follows:
4. Reference the js file in your HTML file as follows:

```html
<script src="[some_js_file].js"></script>
```

5. init the widget.
5. Initialize the widget.

```html
<script>
// you should run it after window loads
window.onload = () => {
initAiCoPilot({
initialMessage: "Hi Sir", // initial message obiviously.
token: "not_super_secret_token", // your copilot token.
rootId: "copilot-widget", //@optional: the root element id in which the widget will mount on
triggerSelector: "#triggerSelector", // the selector of the element that will trigger the widget on click.
apiUrl: "https://cloud.openchat.so/api", // the url of the copilot api.
initialMessage: "Hey, how can I help you today?", // initial bot message
token: "not_super_secret_token", // your OpenCopilot token
rootId: "copilot-widget", // @optional: the root element id in which the widget will mount on
triggerSelector: "#triggerSelector", // the selector of the element that will trigger the widget on click
apiUrl: "https://cloud.openchat.so/api", // the url of the OpenCopilot api.
headers: {
// headers that you want to send with every message request.
Authorization: "Bearer your_auth_token_goes_here",
},
user: { name: "Some User" }, // @optional: user object
}, // @optional: headers that you want to send with every message request
bot: {
name: "AI Assistant",
avatarUrl: "https://example.com/company-logo.png"
}, // @optional: bot object – this info will be used in the chat
user: {
name: "John Doe",
avatarUrl: "https://example.com/user-avatar.png"
}, // @optional: user object – this info will be used in the chat
containerProps: {}, // @optional: `HTMLProps`
warnBeforeClose: true, // @optional: Set to false if you don't want to warn the user before closing the chat
});
};
</script>
```

### How to use
## How to use

1. click on the trigger element to open the widget.
1. Click on the trigger element to open the widget.

2. type your message and press enter to send it.
2. Type your message and press enter to send it.

<img width="394" alt="OpenCopilot widget" src="https://github.com/openchatai/OpenCopilot/assets/32633162/77b30faa-c59e-4a3a-821a-d14a61a49a65">
96 changes: 38 additions & 58 deletions copilot-widget/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,6 @@
href="https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
</head>

<body>
<main>
<h2>
This is an Example on how the widget is fluid and can be placed
anywhere;
</h2>
</main>
<style>
/* reset */
* {
Expand All @@ -34,9 +25,7 @@ <h2>
background-color: #f5f5f5;
font-family: "Noto Sans Arabic", sans-serif;
}
</style>

<style>
#opencopilot-root {
margin-top: 50px;
background-color: red;
Expand All @@ -49,7 +38,7 @@ <h2>
}

#opencopilot-root::after {
content: "Cotainer";
content: "Container";
position: absolute;
top: 0;
translate: -50% -50%;
Expand All @@ -60,60 +49,59 @@ <h2>
color: black;
}
</style>
</head>

<body>
<main>
<h2>
Playground: here you can see how the widget behaves in different
contexts
</h2>
</main>

<script type="module" src="/src/main.tsx"></script>
<div class="some-class">some text here, this is a sample text</div>
<style>
.some-class {
background-color: #fff;
padding: 5px;
border-radius: 5px;
color: black;
position: fixed;
bottom: 10px;
right: 20px;
}
</style>
<div id="opencopilot-root"></div>

<script>
const token = "NtITS4Z07ZrdctTN";
const apiUrl = "http://localhost:8888/backend";
const socketUrl = "http://localhost:8888";
</script>
<script>
const sharedConfig = {
initialMessage: "Welcome back! How can I help you today?", // optional
token: token, // required
apiUrl: apiUrl, // required
socketUrl: socketUrl, // required
defaultOpen: true, // optional
language: "nl", // optional
warnBeforeClose: true,
headers: {
// optional
// Authorization: "Bearer BQAlIe479yo16aXf",
},
bot: {
name: "AI Bot",
},
user: {
// optional
name: "John Doe",
avatarUrl: "https://i.pravatar.cc/150?u=fake@pravatar.com"
},
};

// this one loads in the center of the screen
document.addEventListener("DOMContentLoaded", () => {
initAiCoPilot({
initialMessage: "Hi Sir", // optional
token: token, // required
...sharedConfig,
triggerSelector: "#triggerSelector", // optional
rootId: "opencopilot-root", // optional otherwise it will create a div with id opencopilot-root
apiUrl: apiUrl, // required
socketUrl: socketUrl, // required
defaultOpen: true, // optional
language: "ar", // optional
containerProps: {}, // optional
headers: {
// optional
// Authorization: "Bearer BQAlIe479yo16aXf",
},
user: {
// optional
name: "John Doe",
},
});
});
</script>

<script>
// this one is rendered on the bottom right corner
// this one is rendered in the bottom right corner
document.addEventListener("DOMContentLoaded", () => {
initAiCoPilot({
initialMessage: "Hi Sir", // optional
token: token, // required
// triggerSelector: "#triggerSelector", // optional
...sharedConfig,
rootId: "opencopilot-root-2", // optional otherwise it will create a div with id opencopilot-root
apiUrl: apiUrl, // required
socketUrl: socketUrl,
defaultOpen: true, // optional
containerProps: {
// optional
style: {
Expand All @@ -126,14 +114,6 @@ <h2>
overflow: "hidden",
},
}, // optional
headers: {
// optional
Authorization: "Bearer BQAlIe479yo16aXf",
},
user: {
// optional
name: "John Doe",
},
});
});
</script>
Expand Down
22 changes: 12 additions & 10 deletions copilot-widget/lib/CopilotWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { useEffect, useRef } from "react";
import { useWidgetState } from "./contexts/WidgetState";
import cn from "./utils/cn";
import ChatScreen from "./screens/ChatScreen";
import { IS_SERVER } from "./utils/is_server";
import { isServer } from "./utils/isServer.ts";
import { MessageCircle } from "lucide-react";

function useTrigger(selector?: string, toggle?: () => void) {
const trigger = useRef<HTMLElement | null>(
!selector ? null : IS_SERVER ? null : document.querySelector(selector)
!selector ? null : isServer ? null : document.querySelector(selector)
).current;

useEffect(() => {
if (!selector) {
return;
}
if (trigger && !IS_SERVER) {

if (trigger && !isServer) {
trigger.addEventListener("click", () => toggle?.());
return () => trigger.removeEventListener("click", () => toggle?.());
} else {
Expand All @@ -25,8 +26,8 @@ function useTrigger(selector?: string, toggle?: () => void) {
}, [selector, toggle, trigger]);
}

const TRIGGER_BOTTOM = "20px";
const TRIGGER_RIGHT = "20px";
const OFFSET_BOTTOM = "20px";
const OFFSET_RIGHT = "20px";

export function CopilotWidget({
triggerSelector,
Expand All @@ -38,6 +39,7 @@ export function CopilotWidget({
const [open, toggle] = useWidgetState();
useTrigger(triggerSelector, toggle);
const SHOULD_RENDER_IN_THE_RIGHT_CORNER = !triggerSelector && __isEmbedded;

return (
<>
<div
Expand All @@ -52,13 +54,13 @@ export function CopilotWidget({
)}
style={{
right: SHOULD_RENDER_IN_THE_RIGHT_CORNER
? `calc(${TRIGGER_RIGHT})`
? `calc(${OFFSET_RIGHT})`
: undefined,
bottom: SHOULD_RENDER_IN_THE_RIGHT_CORNER
? `calc(${TRIGGER_BOTTOM} + 60px)`
? `calc(${OFFSET_BOTTOM} + 60px)`
: undefined,
height: SHOULD_RENDER_IN_THE_RIGHT_CORNER
? `calc(95% - ${TRIGGER_BOTTOM} - 100px)`
? `calc(95% - ${OFFSET_BOTTOM} - 100px)`
: "100%",
}}
>
Expand All @@ -69,8 +71,8 @@ export function CopilotWidget({
<div
className="fixed z-50 pointer-events-auto transition-all ease-in-out duration-300"
style={{
bottom: TRIGGER_BOTTOM,
right: TRIGGER_RIGHT,
bottom: OFFSET_BOTTOM,
right: OFFSET_RIGHT,
}}
>
<button
Expand Down
3 changes: 3 additions & 0 deletions copilot-widget/lib/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const cssColors = {
"--opencopilot-primary-clr": "hsl(200 18% 46%)",
"--opencopilot-accent-clr": "hsl(300, 7%, 97%)",
};

type RootProps = {
children: React.ReactNode;
options: ConfigDataContextType;
Expand All @@ -22,8 +23,10 @@ type RootProps = {
HTMLDivElement
>;
};

function Root({ children, options, containerProps }: RootProps) {
const { style, ...containerProp } = containerProps || {};

return (
<root.div
{...containerProp}
Expand Down
Empty file.
Loading
Loading