Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions packages/convai-widget-core/src/contexts/conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ function useConversationSetup() {
sendUserActivity: () => {
conversationRef.current?.sendUserActivity();
},
sendContextualUpdate: (text: string) => {
conversationRef.current?.sendContextualUpdate(text)
},
addModeToggleEntry: (mode: ConversationMode) => {
// Only add entry if conversation is active
if (!conversationRef.current?.isOpen()) return;
Expand Down
7 changes: 7 additions & 0 deletions packages/convai-widget-core/src/contexts/widget-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,13 @@ export function useSyntaxTheme() {
});
}

export function useAllowEvents() {
const allowEvents = useAttribute("allow-events");
return useComputed(() => {
return parseBoolAttribute(allowEvents.value) ?? false
})
}

async function fetchConfig(
agentId: string,
serverUrl: string,
Expand Down
10 changes: 10 additions & 0 deletions packages/convai-widget-core/src/index.dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function Playground() {
const [showAgentStatus, setShowAgentStatus] = useState(false);
const [dynamicVariablesStr, setDynamicVariablesStr] = useState("");
const [expanded, setExpanded] = useState(false);
const [allowEvents, setAllowEvents] = useState(false);
const [overrideFirstMessage, setOverrideFirstMessage] = useState(false);
const [firstMessage, setFirstMessage] = useState(
"Hi, how can I help you today?"
Expand Down Expand Up @@ -121,6 +122,14 @@ function Playground() {
/>{" "}
Dismissible
</label>
<label className="flex items-center gap-1">
<input
type="checkbox"
checked={allowEvents}
onChange={e => setAllowEvents(e.currentTarget.checked)}
/>{" "}
Allow events
</label>
<label className="flex items-center gap-1">
<input
type="checkbox"
Expand Down Expand Up @@ -206,6 +215,7 @@ function Playground() {
override-first-message={
overrideFirstMessage ? firstMessage : undefined
}
allow-events={JSON.stringify(allowEvents)}
/>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/convai-widget-core/src/markdown.dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ function MockConversationProvider({
sendFeedback: () => {},
sendUserMessage: () => {},
sendUserActivity: () => {},
sendContextualUpdate: () => {},
addModeToggleEntry: () => {},
}),
[mockTranscript]
Expand Down
1 change: 1 addition & 0 deletions packages/convai-widget-core/src/types/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const CustomAttributeList = [
"markdown-link-allowed-hosts",
"markdown-link-include-www",
"markdown-link-allow-http",
"allow-events",
"show-agent-status",
] as const;

Expand Down
73 changes: 73 additions & 0 deletions packages/convai-widget-core/src/widget/EventBridge.tsx
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we want to duplicate the pattern from elevenlabs-agent:expand tbh

The reason it attaches a handler to both the document and the widget, and marks it via _convaiEventHandled, is because whoever implemented it initially listened for the event only on the document. Users then started complaining that this breaks when there are multiple widgets on the page. So I added a proper listener on the shadow host, but kept the document listeners and used a custom _convaiEventHandled instead of stopPropagation() for backwards compatibility

Here, since we are starting clean, I'd suggest we only listen on the host

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { memo } from "preact/compat";
import { useSignalEffect } from "@preact/signals";
import { useShadowHost } from "../contexts/shadow-host";
import { useConversation } from "../contexts/conversation";
import { useAllowEvents } from "../contexts/widget-config";

const eventUserActivity = "elevenlabs-agent:user-activity";
const eventUserMessage = "elevenlabs-agent:user-message";
const eventContextualUpdate = "elevenlabs-agent:contextual-update";

export const EventBridge = memo(function EventBridge({ children }) {
const { sendContextualUpdate, sendUserActivity, sendUserMessage } = useConversation();
const shadowHost = useShadowHost();
const allowEvents = useAllowEvents();

useSignalEffect(() => {
function handleUserActivity(event: Event): void {
if (!isCustomEvent(event) || event.detail._convaiEventHandled) {
return;
}

event.detail._convaiEventHandled = true;
sendUserActivity();
}

function handleUserMessage(event: Event): void {
if (!isCustomEventWithMessage(event) || event.detail._convaiEventHandled) {
return;
}

event.detail._convaiEventHandled = true;
sendUserMessage(event.detail.message);
}

function handleContextualUpdate(event: Event): void {
if (!isCustomEventWithMessage(event) || event.detail._convaiEventHandled) {
return;
}

event.detail._convaiEventHandled = true;
sendContextualUpdate(event.detail.message);
}

const host = shadowHost.value;
if (allowEvents.value) {
document.addEventListener(eventUserActivity, handleUserActivity);
host?.addEventListener(eventUserActivity, handleUserActivity);
document.addEventListener(eventUserMessage, handleUserMessage);
host?.addEventListener(eventUserMessage, handleUserMessage);
document.addEventListener(eventContextualUpdate, handleContextualUpdate);
host?.addEventListener(eventContextualUpdate, handleContextualUpdate);
}

return () => {
document.removeEventListener(eventUserActivity, handleUserActivity);
host?.removeEventListener(eventUserActivity, handleUserActivity);
document.removeEventListener(eventUserMessage, handleUserMessage);
host?.removeEventListener(eventUserMessage, handleUserMessage);
document.removeEventListener(eventContextualUpdate, handleContextualUpdate);
host?.removeEventListener(eventContextualUpdate, handleContextualUpdate);
};
});

return children;
});

function isCustomEvent(event: Event): event is CustomEvent {
return "detail" in event && !!event.detail;
}

function isCustomEventWithMessage(event: Event): event is CustomEvent<{ _convaiEventHandled?: boolean, message: string }> {
return isCustomEvent(event) && typeof event.detail.message === "string" && !!event.detail.message;
}
5 changes: 4 additions & 1 deletion packages/convai-widget-core/src/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FeedbackProvider } from "../contexts/feedback";
import { ShadowHostProvider } from "../contexts/shadow-host";
import { WidgetSizeProvider } from "../contexts/widget-size";
import { ConversationModeProvider } from "../contexts/conversation-mode";
import { EventBridge } from "./EventBridge";

export function ConvAIWidget(attributes: CustomAttributes) {
return (
Expand All @@ -35,7 +36,9 @@ export function ConvAIWidget(attributes: CustomAttributes) {
<SheetContentProvider>
<FeedbackProvider>
<Style />
<Wrapper />
<EventBridge>
<Wrapper />
</EventBridge>
</FeedbackProvider>
</SheetContentProvider>
</AvatarConfigProvider>
Expand Down