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

feat: 🎸 optimize crisp human assistance #178

Merged
merged 1 commit into from
Oct 13, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const updateCrispConfig = async (
res: NextApiResponse
) => {
const data = req.body as z.infer<typeof schema>;

// const websites = await getConnectedWebsites();

let metadata = {} as any;
Expand Down
231 changes: 152 additions & 79 deletions apps/dashboard/pages/api/integrations/crisp/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ CrispClient.authenticateTier(
// Set current RTM mode to Web Hooks
CrispClient.setRtmMode(Crisp.RTM_MODES.WebHooks);

enum AIStatus {
enabled = 'enabled',
disabled = 'disabled',
}

enum Action {
enable_ai = 'enable_ai',
request_human = 'request_human',
mark_as_resolved = 'mark_as_resolved',
}

type ConversationMetadata = {
aiStatus?: AIStatus;
choice?: Action;
aiDisabledDate?: Date;
};

type HookEventType =
| 'message:send'
| 'message:received'
Expand Down Expand Up @@ -79,7 +96,7 @@ type HookBodyMessageUpdated = HookBodyBase & {
explain: string;
value?: string;
choices?: {
value: 'resolved' | 'request_human' | 'enable_ai';
value: Action;
icon: string;
label: string;
selected: boolean;
Expand Down Expand Up @@ -241,13 +258,13 @@ const handleQuery = async (
text: finalAnser,
choices: [
{
value: 'resolved',
value: Action.mark_as_resolved,
icon: '✅',
label: 'Mark as resolved',
selected: false,
},
{
value: 'request_human',
value: Action.request_human,
icon: '💬',
label: 'Request a human operator',
selected: false,
Expand Down Expand Up @@ -294,6 +311,7 @@ export const hook = async (req: AppNextApiRequest, res: NextApiResponse) => {
// }

if (req.headers['x-delivery-attempt-count'] !== '1') {
console.log('x-delivery-attempt-count abort');
return "Not the first attempt, don't handle.";
}

Expand All @@ -302,27 +320,37 @@ export const hook = async (req: AppNextApiRequest, res: NextApiResponse) => {
body.website_id,
body.data.session_id
)
)?.data;

const newChoice = body?.data?.content?.choices?.find(
(one: any) => one.selected
);
)?.data as ConversationMetadata;

if (
metadata?.choice === 'request_human' &&
newChoice?.value !== 'enable_ai'
) {
return 'User has requested a human operator, do not handle.';
}
// const newChoice = body?.data?.content?.choices?.find(
// (one: any) => one.selected
// );

switch (body.event) {
case 'message:send':
if (
body.data.origin === 'chat' &&
body.data.from === 'user' &&
body.data.type === 'text' &&
metadata?.choice !== 'request_human'
body.data.type === 'text'
) {
if (metadata?.aiStatus === AIStatus.disabled) {
const oneHourAgo = new Date().getTime() - 60 * 60 * 1000;

if (new Date(metadata?.aiDisabledDate!).getTime() < oneHourAgo) {
await CrispClient.website.updateConversationMetas(
body.website_id,
body.data.session_id,
{
data: {
aiStatus: AIStatus.enabled,
} as ConversationMetadata,
}
);
} else {
return 'Converstaion disabled dot not proceed';
}
}

CrispClient.website.composeMessageInConversation(
body.website_id,
body.data.session_id,
Expand All @@ -348,15 +376,16 @@ export const hook = async (req: AppNextApiRequest, res: NextApiResponse) => {
if (
body.data.from === 'operator' &&
body.data.type === 'text' &&
metadata?.choice !== 'request_human'
metadata?.aiStatus === AIStatus.enabled
) {
await CrispClient.website.updateConversationMetas(
body.website_id,
body.data.session_id,
{
data: {
choice: 'request_human',
},
aiStatus: AIStatus.disabled,
aiDisabledDate: new Date(),
} as ConversationMetadata,
}
);
}
Expand All @@ -368,78 +397,122 @@ export const hook = async (req: AppNextApiRequest, res: NextApiResponse) => {
const selected = choices?.find((one) => one.selected);

switch (selected?.value) {
case 'request_human':
await CrispClient.website.updateConversationMetas(
body.website_id,
body.data.session_id,
{
data: {
choice: 'request_human',
},
}
);

// const data =
// await CrispClient.website.listLastActiveWebsiteOperators(
// body.website_id
// );

// await CrispClient.website.sendMessageInConversation(
// body.website_id,
// body.data.session_id,
// {
// type: 'text',
// from: 'operator',
// origin: 'chat',

// content: 'An operator will get back to you shortly.',
// user: {
// type: 'participant',
// // nickname: agent?.name || 'Chaindesk',
// avatar: 'https://chaindesk.ai/app-rounded-bg-white.png',
// },
// // mentions: [data?.[0]?.user_id],
// }
// );

await CrispClient.website.sendMessageInConversation(
body.website_id,
body.data.session_id,
{
type: 'picker',
from: 'operator',
origin: 'chat',

content: {
id: 'chaindesk-enable',
text: 'An operator will get back to you shortly.',
choices: [
{
value: 'enable_ai',
icon: '▶️',
label: 'Re-enable AI',
selected: false,
},
],
},
}
);

case Action.request_human:
const availibility =
await CrispClient.website.getWebsiteAvailabilityStatus(
body.data.website_id
);
const status = availibility?.status;

if (status === 'online') {
// Get last active operator
const active_operators: {
user_id: string;
avatar: string | null;
timestamp: number;
}[] = await CrispClient.website.listLastActiveWebsiteOperators(
body.data.website_id
);

// const highly_active_operator = active_operators.filter(
// (op) =>
// op.timestamp ==
// Math.min(...active_operators.map((o) => o.timestamp))
// )[0];

await CrispClient.website.updateConversationMetas(
body.website_id,
body.data.session_id,
{
data: {
aiStatus: AIStatus.disabled,
aiDisabledDate: new Date(),
} as ConversationMetadata,
}
);

await CrispClient.website.sendMessageInConversation(
body.website_id,
body.data.session_id,
{
type: 'picker',
from: 'operator',
origin: 'chat',
content: {
id: 'chaindesk-enable',
text: 'An operator will get back to you shortly.',
choices: [
{
value: Action.enable_ai,
icon: '▶️',
label: 'Re-enable AI',
selected: false,
},
],
},
// mentions: [highly_active_operator.user_id],
mentions: active_operators.map((each) => each.user_id),
user: {
type: 'website',
nickname: 'chaindesk',
},
}
);
} else {
// website offline
await CrispClient.website.updateConversationMetas(
body.website_id,
body.data.session_id,
{
data: {
aiStatus: AIStatus.disabled,
} as ConversationMetadata,
}
);

await CrispClient.website.sendMessageInConversation(
body.website_id,
body.data.session_id,
{
type: 'picker',
from: 'operator',
origin: 'chat',

content: {
id: 'chaindesk-answer',
text: 'Unfortunately, no operators are available at the moment.',
choices: [
{
value: Action.enable_ai,
icon: '▶️',
label: 'Re-enable AI',
selected: false,
},
],
},
// user: {
// type: 'participant',
// nickname: agent?.name || 'Chaindesk',
// avatar: agent.iconUrl || 'https://chaindesk.ai/app-rounded-bg-white.png',
// },
}
);
}
break;
case 'resolved':
case Action.mark_as_resolved:
await CrispClient.website.changeConversationState(
body.website_id,
body.data.session_id,
'resolved'
);
break;
case 'enable_ai':
case Action.enable_ai:
await CrispClient.website.updateConversationMetas(
body.website_id,
body.data.session_id,
{
data: {
choice: 'enable_ai',
aiStatus: AIStatus.enabled,
},
}
);
Expand Down