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

Chat SDK #372

Closed
wants to merge 5 commits into from
Closed
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
51 changes: 51 additions & 0 deletions copilot-widget/lib/chat-sdk/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AxiosInstance } from "axios";
import { Massenger } from ".";
import { HistoryMessage, SessionIdType, getInstance } from "./data";
import { radnom } from "./rand";
import get from 'lodash.get';

type ChatOptionsType = {
copilotToken: string;
baseUrl: string;
inject?: {
user?: Record<string, any>;
headers?: Record<string, any>;
}
}

export class Chat {
private sessionId?: SessionIdType;
history: HistoryMessage[] = [];
options: ChatOptionsType;
messenger: Massenger = new Massenger(this);
axiosInstance: AxiosInstance | null = null;

constructor(options: ChatOptionsType) {
this.options = options;
this.initAxios();
}

initAxios() {
this.axiosInstance = getInstance({
baseUrl: get(this.options, "baseUrl"),
copilotToken: get(this.options, "copilotToken"),
sessionId: this.getSessionId,
});
}

get getSessionId(): SessionIdType {
if (!this.sessionId) {
this.setSessionId = this.generateSessionId();
}
return this.sessionId!;
}

set setSessionId(sessionId: SessionIdType) {
this.sessionId = sessionId;
}

generateSessionId(): SessionIdType {
return (radnom() + ":" + this.options.copilotToken) as SessionIdType;
}

}
60 changes: 60 additions & 0 deletions copilot-widget/lib/chat-sdk/data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios, { AxiosInstance } from "axios";

export type SessionIdType = `${string}:${string}`;
const SESSION_ID_HEADER = "X-Session-Id";
const COPILOT_TOKEN_HEADER = "X-Bot-Token";

type InstanceOptions = {
copilotToken: string;
sessionId: SessionIdType;
baseUrl: string;
}

// only one instance per copilotToken
let instances = new Map<string, AxiosInstance>();

export function getInstance(
{
copilotToken,
sessionId,
baseUrl,
}: InstanceOptions,
) {
// make sure we only have one instance
if (!instances.has(copilotToken)) {
const instance = axios.create({
baseURL: baseUrl,
headers: {
[SESSION_ID_HEADER]: sessionId,
[COPILOT_TOKEN_HEADER]: copilotToken,
},
});
instances.set(copilotToken, instance);
}
return instances.get(copilotToken)!;
}

export function getInitialData(instance: AxiosInstance) {
return instance.get<InitialDataType>("/chat/init");
}

export type HistoryMessage = {
chatbot_id: string;
created_at: string;
from_user: boolean;
id: number;
message: string;
session_id: string;
updated_at: string;
};

export interface InitialDataType {
bot_name: string;
logo: string;
history: HistoryMessage[];
sound_effects: {
submit: string;
response: string;
};
inital_questions: string[];
}
1 change: 1 addition & 0 deletions copilot-widget/lib/chat-sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Chat } from './chat';
96 changes: 96 additions & 0 deletions copilot-widget/lib/chat-sdk/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import get from "lodash.get";
import { radnom } from "./rand";

type Actor = "user" | "bot";
type IdType = string | number;
type TimeStampType = number | Date;

type UserMessageOptions = {
timestamp?: TimeStampType;
id?: IdType;
from?: Actor;
content: string;
}

export type UserMessagePayload = {
message: string;
id?: IdType;
timestamp?: TimeStampType;
}
export class UserMessage {
id: IdType;
timestamp?: TimeStampType;
from: Actor = "user";
content: string;

constructor(
content: string,
options?: UserMessageOptions
) {
this.content = content;
this.id = get(options, "id", radnom());
this.timestamp = get(options, "timestamp", Date.now());
}

public get payload(): {
content: string;
id?: IdType;
timestamp?: TimeStampType;
} {
return {
content: this.content,
id: this.id,
timestamp: this.timestamp,
}
}
}

type CopilotMessageTypes = "text";

class CopilotResponse {
id: IdType;
timestamp: TimeStampType;
from: Actor = "bot";
type: CopilotMessageTypes;
private userMessageId: IdType | null = null;

constructor({
type,
id,
timestamp = Date.now(),
}: { type: CopilotMessageTypes, id: IdType, timestamp?: TimeStampType }) {
this.type = type;
this.id = id;
this.timestamp = timestamp;

}
public set setUserMessageId(id: IdType) {
this.userMessageId = id;
}
public get getUserMessageId() {
return this.userMessageId;
}
}
export class CopilotTextResponse extends CopilotResponse {
response: string;

constructor(response: string, id?: IdType) {
super({ type: "text", id: id || radnom() });
this.response = response;
}

public get payload(): {
response: string;
id?: IdType;
timestamp?: TimeStampType;
} {
return {
response: this.response,
id: this.id,
timestamp: this.timestamp,
}
}
}


export type MessageType = CopilotTextResponse | UserMessage;
67 changes: 67 additions & 0 deletions copilot-widget/lib/chat-sdk/messenger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Chat } from "./chat";
import { CopilotTextResponse, MessageType, UserMessage } from "./messages";

export class Massenger {
// responsible for sending,storing messages
private messages: Set<MessageType> = new Set();

chat: Chat;

constructor(chat: Chat) {
this.chat = chat;
}

private get axios() {
return this.chat.axiosInstance
}

private set addMessage(message: MessageType) {
this.messages.add(message)
}

public get getMessages() {
return this.messages;
}

public clearMessages() {
this.messages.clear();
}

public queryMessage(id: string | number) {
// query message by id
return [...this.messages].find((message) => message.id === id);
}

async handleMessage(message: UserMessage) {
if (!this.axios) {
throw new Error("axios is not initialized");
}
// Currently we only support text messages
const { data, status } = await this.axios.post<{
type: "text",
response: {
text: string | null
}
}>("/chat/send", message.payload);

if (status !== 200) {
throw new Error("Failed to send message");
}

if (data.type === 'text') {
const copilotResp = new CopilotTextResponse(data.response.text || 'null')
copilotResp.setUserMessageId = message.id;
this.addMessage = copilotResp;
}
}

async sendMessage(message: string) {
const userMessage = new UserMessage(message);
this.addMessage = userMessage;
await this.handleMessage(userMessage);
}
toArray() {
return [...this.messages]
}

}
3 changes: 3 additions & 0 deletions copilot-widget/lib/chat-sdk/rand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function radnom() {
return Math.random().toString(36).substring(2, 15)
}
2 changes: 2 additions & 0 deletions copilot-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-tooltip": "^1.0.6",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash.get": "^4.4.9",
"@types/node": "^20.4.7",
"@types/react": "^18.x",
"@types/react-dom": "^18.x",
Expand All @@ -33,6 +34,7 @@
"eslint": "^8.38.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"lodash.get": "^4.4.2",
"postcss": "^8.4.31",
"postcss-prefix-selector": "^1.16.0",
"prettier": "^2.8.8",
Expand Down
20 changes: 20 additions & 0 deletions copilot-widget/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading