Skip to content

Commit

Permalink
Merge pull request #135 from nlxai/new-features-peter
Browse files Browse the repository at this point in the history
New features (Peter)
  • Loading branch information
peterszerzo committed Jul 23, 2024
2 parents 343bfdc + 34fc51b commit 5e1f09b
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 76 deletions.
69 changes: 28 additions & 41 deletions packages/journey-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,10 @@ export const run = async (props: RunProps): Promise<RunOutput> => {

await waitUntilDomContentLoaded();

const triggeredSteps = getTriggeredSteps(props.config.conversationId);
let triggeredSteps = getTriggeredSteps(props.config.conversationId);

// TODO: type this more accurately
let uiElement: any;

/**
* Digression detection
Expand All @@ -379,10 +382,6 @@ export const run = async (props: RunProps): Promise<RunOutput> => {
},
);

if (isDigressionDetectable && !urlConditions.some(matchesUrlCondition)) {
props.onDigression?.(client);
}

const sendStep = (stepId: string, once: boolean): void => {
if (
triggeredSteps.some((triggeredStep) => triggeredStep.stepId === stepId)
Expand All @@ -391,7 +390,13 @@ export const run = async (props: RunProps): Promise<RunOutput> => {
return;
}
} else {
triggeredSteps.push({ stepId, url: window.location.toString() });
triggeredSteps = [
...triggeredSteps,
{ stepId, url: window.location.toString() },
];
if (uiElement != null) {
uiElement.triggeredSteps = triggeredSteps;
}
saveTriggeredSteps(props.config.conversationId, triggeredSteps);
}
props.onStep?.(stepId);
Expand Down Expand Up @@ -524,9 +529,6 @@ export const run = async (props: RunProps): Promise<RunOutput> => {
* UI management
*/

// TODO: type this more accurately
let uiElement: any;

const setHighlights = debounce((): void => {
const highlightElements = findActiveTriggers("click").flatMap(
(activeTrigger) => activeTrigger.elements,
Expand All @@ -543,41 +545,10 @@ export const run = async (props: RunProps): Promise<RunOutput> => {
uiElement.style.zIndex = 1000;
uiElement.config = props.ui;
uiElement.client = client;
const handleAction = (ev: any): void => {
const action = ev.detail?.action;
if (action == null) {
return;
}
if (action === "escalate" && props.ui?.onEscalation != null) {
props.ui.onEscalation({ sendStep: client.sendStep });
return;
}
if (action === "end" && props.ui?.onEnd != null) {
props.ui.onEnd({ sendStep: client.sendStep });
return;
}
if (action === "previous") {
if (props.ui?.onPreviousStep != null) {
props.ui.onPreviousStep({
sendStep: client.sendStep,
triggeredSteps,
});
} else {
const lastTriggeredStep = triggeredSteps[triggeredSteps.length - 1];
if (lastTriggeredStep != null) {
sendStep(lastTriggeredStep.stepId, false);
// Redirect to previous page if the last triggered step occurred on it
if (lastTriggeredStep.url !== window.location.toString()) {
window.location.href = lastTriggeredStep.url;
}
}
}
}
};
uiElement.triggeredSteps = triggeredSteps;
if (props.ui.highlights ?? false) {
setHighlights();
}
uiElement.addEventListener("action", handleAction);
document.body.appendChild(uiElement);
teardownUiElement = () => {
document.body.removeChild(uiElement);
Expand Down Expand Up @@ -621,7 +592,23 @@ export const run = async (props: RunProps): Promise<RunOutput> => {
* Change detection
*/

let digressionCallbackCalled = false;

const documentObserver = new MutationObserver((mutations) => {
if (isDigressionDetectable) {
const userHasDigressed = !urlConditions.some(matchesUrlCondition);
if (userHasDigressed) {
// Avoid calling the digression callback multiple times after a digression has been detected
if (!digressionCallbackCalled) {
props.onDigression?.(client);
}
digressionCallbackCalled = true;
uiElement.digression = true;
} else {
digressionCallbackCalled = false;
uiElement.digression = false;
}
}
// If any of the added nodes are inside matches on appear events, trigger those events
const targets = withElementsSync(appearSteps);
mutations.forEach((mutation) => {
Expand Down
141 changes: 108 additions & 33 deletions packages/journey-manager/src/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ export interface Theme {
fontFamily: string;
}

interface HandlerArg {
sendStep: Client["sendStep"];
triggeredSteps: Array<{ stepId: string; url: string }>;
}

/**
* Button configuration
*/
export interface ButtonConfig {
/**
* Button label
*/
label: string;
/**
* Button confirmation: if present, the button click handler only triggers after the confirmation button is hit
*/
confirmation?: string;
/**
* Icon URL
*/
iconUrl?: string;
/**
* Click handler
*/
onClick: (config: HandlerArg) => void;
}

/**
* Deep partial variant of the UI theme, input by the library user
*/
Expand Down Expand Up @@ -103,22 +130,15 @@ export interface UiConfig {
/**
* On previous step
*/
onPreviousStep?: (config: {
sendStep: Client["sendStep"];
triggeredSteps: Array<{ stepId: string; url: string }>;
}) => void;
onPreviousStep?: (config: HandlerArg) => void;
/**
* Previous step button label
*/
previousStepButtonLabel?: string;
/**
* Custom buttons
*/
customButtons?: Array<{
label: string;
iconUrl?: string;
onClick: () => void;
}>;
buttons?: ButtonConfig[];
/**
* If this is set, the journey manager will show a call-to-action tooltip to invite the user to interact with the overlay pin.
* it will be shown only if the user never interacts with the overlay pin, after `tooltipShowAfterMs` milliseconds.
Expand Down Expand Up @@ -563,8 +583,6 @@ const Highlight: FunctionComponent<{ element: HTMLElement }> = ({
);
};

type Action = "end" | "escalate" | "previous";

type ControlCenterStatus =
| null
| "pending-escalation"
Expand All @@ -574,10 +592,11 @@ type ControlCenterStatus =

const ControlCenter: FunctionComponent<{
config: UiConfig;
client: Client;
triggeredSteps: TriggeredStep[];
digression: boolean;
highlightElements: HTMLElement[];
onAction: (action: Action) => void;
}> = ({ config, highlightElements, digression, onAction }) => {
}> = ({ config, client, triggeredSteps, highlightElements, digression }) => {
const [hasBeenOpened, setHasBeenOpened] = useState<boolean>(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [status, setStatus] = useState<ControlCenterStatus>(null);
Expand Down Expand Up @@ -622,6 +641,27 @@ const ControlCenter: FunctionComponent<{
return null;
}, [status]);

const onPreviousStep = config.onPreviousStep
? () => {
config.onPreviousStep?.({
sendStep: client.sendStep,
triggeredSteps,
});
}
: () => {
const lastTriggeredStep = triggeredSteps[triggeredSteps.length - 1];
if (lastTriggeredStep != null) {
client.sendStep(lastTriggeredStep.stepId).catch((err) => {
// eslint-disable-next-line no-console
console.warn(err);
});
// Redirect to previous page if the last triggered step occurred on it
if (lastTriggeredStep.url !== window.location.toString()) {
window.location.href = lastTriggeredStep.url;
}
}
};

return (
<>
<style>{styles(mergeWithDefault(config.theme))}</style>
Expand Down Expand Up @@ -673,19 +713,19 @@ const ControlCenter: FunctionComponent<{
<div className="drawer-buttons">
<button
onClick={() => {
onAction("previous");
onPreviousStep();
setIsOpen(false);
}}
>
<ArrowBackIcon />
<span>Previous Screen</span>
{config.previousStepButtonLabel ?? "Previous Screen"}
</button>

{config.onEscalation != null ? (
<button
disabled={status === "pending-escalation"}
onClick={() => {
onAction("escalate");
config.onEscalation?.({ sendStep: client.sendStep });
setStatus("pending-escalation");
setTimeout(() => {
setStatus("success-escalation");
Expand All @@ -696,15 +736,15 @@ const ControlCenter: FunctionComponent<{
}}
>
<SupportAgentIcon />
Escalate to Agent
{config.escalationButtonLabel ?? "Escalate to Agent"}
</button>
) : null}

{config.onEnd != null ? (
<button
disabled={status === "pending-end"}
onClick={() => {
onAction("end");
config.onEnd?.({ sendStep: client.sendStep });
setStatus("pending-end");
setTimeout(() => {
setStatus("success-end");
Expand All @@ -715,9 +755,22 @@ const ControlCenter: FunctionComponent<{
}}
>
<CallEndIcon />
End Call
{config.endButtonLabel ?? "End Call"}
</button>
) : null}
{(config.buttons ?? []).map((buttonConfig, buttonIndex) => (
<button
key={buttonIndex}
onClick={() => {
buttonConfig.onClick({
sendStep: client.sendStep,
triggeredSteps,
});
}}
>
{buttonConfig.label}
</button>
))}
</div>
)}
<div className="drawer-footer">
Expand All @@ -741,21 +794,30 @@ const ControlCenter: FunctionComponent<{
);
};

interface TriggeredStep {
stepId: string;
url: string;
}

/**
* @hidden @internal
*/
export class JourneyManagerElement extends HTMLElement {
_shadowRoot: ShadowRoot | null = null;
_client: Client | null = null;
_triggeredSteps: TriggeredStep[] | null = null;
_config: UiConfig | null = null;
_digression?: boolean;
_digression: boolean = false;
_highlightElements: HTMLElement[] = [];

/**
* Set digression attribute
*/
set digression(value: boolean) {
this._digression = value;
this.render();
if (this._digression !== value) {
this._digression = value;
this.render();
}
}

/**
Expand All @@ -766,6 +828,22 @@ export class JourneyManagerElement extends HTMLElement {
this.render();
}

/**
* Set SDK client
*/
set client(value: Client) {
this._client = value;
this.render();
}

/**
* Set triggered steps
*/
set triggeredSteps(value: TriggeredStep[]) {
this._triggeredSteps = value;
this.render();
}

/**
* Set UI configuration
*/
Expand All @@ -779,23 +857,20 @@ export class JourneyManagerElement extends HTMLElement {
*/
render(): void {
this._shadowRoot = this._shadowRoot ?? this.attachShadow({ mode: "open" });
if (this._config == null) {
if (
this._config == null ||
this._client == null ||
this._triggeredSteps == null
) {
return;
}
render(
<ControlCenter
config={this._config}
digression={this._digression ?? false}
digression={this._digression}
client={this._client}
triggeredSteps={this._triggeredSteps}
highlightElements={this._highlightElements}
onAction={(action) => {
this.dispatchEvent(
new CustomEvent("action", {
detail: {
action,
},
}),
);
}}
/>,
this._shadowRoot,
);
Expand Down
Loading

0 comments on commit 5e1f09b

Please sign in to comment.