PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
extendify / src / Agent / components / FollowUpCard.jsx

FollowUpCard.jsx in Extendify 3.2.2, at src/Agent/components/FollowUpCard.jsx

107 lines 3.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useFollowUpHistory } from '@agent/follow-ups/history';
2 import { cards } from '@agent/follow-ups/registry';
3 import { useChatStore } from '@agent/state/chat';
4 import { workflows } from '@agent/workflows/workflows';
5 import { track } from '@shared/lib/track';
6 import { useEffect } from '@wordpress/element';
7 import { __ } from '@wordpress/i18n';
8 import { close as closeIcon, Icon } from '@wordpress/icons';
9
10 const submitFor = ({ message, workflowId }) => {
11 if (message) return { message };
12 const target = workflows.find(({ id }) => id === workflowId);
13 return { message: target?.example?.text };
14 };
15
16 export const FollowUpCard = ({ message }) => {
17 const updateMessage = useChatStore((state) => state.updateMessage);
18 const {
19 cards: history,
20 markShown,
21 markClicked,
22 markDismissed,
23 } = useFollowUpHistory();
24 const { workflowId, followUp, followUpData: data } = message.details;
25 const card = cards.find(({ id }) => id === followUp);
26 const shownFor = history[card?.id]?.shownFor;
27
28 // Without shownFor, every reload with the card open counts another view.
29 useEffect(() => {
30 if (!card || shownFor === message.id) return;
31 markShown(card.id, message.id);
32 track('agent_follow_up_view', { id: card.id, workflowId });
33 }, [card, shownFor, message.id, markShown, workflowId]);
34
35 // Partner offers count an impression per showing, not per message.
36 useEffect(() => {
37 card?.onView?.(data);
38 }, [card, data]);
39
40 if (!card) return null;
41
42 const { title, body, action } = card.content?.(data) ?? card;
43 const close = () => updateMessage(message.id, { followUpClosed: true });
44 const handleClick = () => {
45 markClicked(card.id);
46 track('agent_follow_up_click', { id: card.id, workflowId });
47 card.onClick?.(data);
48 close();
49 };
50 const handleDismiss = () => {
51 markDismissed(card.id);
52 track('agent_follow_up_dismiss', { id: card.id, workflowId });
53 close();
54 };
55 const handleAction = () => {
56 handleClick();
57 window.dispatchEvent(
58 new CustomEvent('extendify-agent:chat-submit', {
59 detail: submitFor(action),
60 }),
61 );
62 };
63 const primary =
64 'rounded-sm border border-design-main bg-transparent px-3 py-1.5 text-sm text-design-main hover:opacity-80';
65
66 return (
67 <div className="relative mt-2 max-w-[90%] rounded-xl border border-gray-200 bg-gray-50 p-3 text-sm text-gray-900">
68 <button
69 type="button"
70 className="absolute end-2 top-2 flex items-center rounded-none border-0 bg-transparent p-0 text-gray-700 outline-hidden ring-design-main hover:text-gray-900 focus:shadow-none focus:outline-hidden focus-visible:outline-design-main"
71 onClick={handleDismiss}
72 >
73 <Icon
74 className="pointer-events-none fill-current leading-none"
75 icon={closeIcon}
76 size={16}
77 />
78 <span className="sr-only">
79 {
80 // translators: Screen-reader label for the X button that hides a suggested next step card in the AI Agent chat.
81 __('Dismiss', 'extendify-local')
82 }
83 </span>
84 </button>
85 <p className="m-0 pe-6 font-semibold">{title}</p>
86 {body && <p className="m-0 mt-1">{body}</p>}
87 <div className="mt-3 flex flex-wrap gap-2">
88 {action.url ? (
89 <a
90 href={action.url}
91 target="_blank"
92 rel="noopener noreferrer"
93 className={`${primary} no-underline`}
94 onClick={handleClick}
95 >
96 {action.label}
97 </a>
98 ) : (
99 <button type="button" className={primary} onClick={handleAction}>
100 {action.label}
101 </button>
102 )}
103 </div>
104 </div>
105 );
106 };
107