PluginProbe
Extendify / 3.2.3
Extendify v3.2.3
3.2.3 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 All 129 releases
extendify / src / Agent / components / FollowUpCard.jsx

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

134 lines 4.2 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 { doReload } from '@agent/lib/reload';
4 import { useChatStore } from '@agent/state/chat';
5 import { workflows } from '@agent/workflows/workflows';
6 import { track } from '@shared/lib/track';
7 import { useEffect } from '@wordpress/element';
8 import { __ } from '@wordpress/i18n';
9 import { close as closeIcon, Icon } from '@wordpress/icons';
10
11 const submitFor = ({ message, workflowId }) => {
12 if (message) return { message };
13 const target = workflows.find(({ id }) => id === workflowId);
14 return { message: target?.example?.text };
15 };
16
17 const newTab = { target: '_blank', rel: 'noopener noreferrer' };
18
19 const toolContext = (card, previous) => {
20 try {
21 return card.toolContext?.(previous);
22 } catch (error) {
23 // A throw here would swallow the message the user clicked to send.
24 window.extSharedData?.devbuild && console.error(error);
25 }
26 };
27
28 export const FollowUpCard = ({ message }) => {
29 const updateMessage = useChatStore((state) => state.updateMessage);
30 const {
31 cards: history,
32 markShown,
33 markClicked,
34 markDismissed,
35 } = useFollowUpHistory();
36 const { workflowId, label, followUp, followUpData: data } = message.details;
37 const card = cards.find(({ id }) => id === followUp);
38 const shownFor = history[card?.id]?.shownFor;
39
40 // Without shownFor, every reload with the card open counts another view.
41 useEffect(() => {
42 if (!card || shownFor === message.id) return;
43 markShown(card.id, message.id);
44 track('agent_follow_up_view', { id: card.id, workflowId });
45 }, [card, shownFor, message.id, markShown, workflowId]);
46
47 // Partner offers count an impression per showing, not per message.
48 useEffect(() => {
49 card?.onView?.(data);
50 }, [card, data]);
51
52 if (!card) return null;
53
54 const { title, body, action } = card.content?.(data) ?? card;
55 const close = () => updateMessage(message.id, { followUpClosed: true });
56 const handleClick = () => {
57 markClicked(card.id);
58 track('agent_follow_up_click', { id: card.id, workflowId });
59 card.onClick?.(data);
60 close();
61 };
62 const handleLink = (event) => {
63 handleClick();
64 if (!action.sameTab) return;
65 event.preventDefault();
66 doReload(action.url);
67 };
68 const handleDismiss = () => {
69 markDismissed(card.id);
70 track('agent_follow_up_dismiss', { id: card.id, workflowId });
71 close();
72 };
73 const handleAction = () => {
74 handleClick();
75 const detail = submitFor(action);
76 const step = {
77 id: 'get-clicked-suggestion-context',
78 // Without suggestionSent and a card nextStep the fill asks what "this" means.
79 result: {
80 suggestionSent: detail.message,
81 shownAfter: label ?? workflowId,
82 ...toolContext(card, { workflowId, data }),
83 },
84 };
85 window.dispatchEvent(
86 new CustomEvent('extendify-agent:chat-submit', {
87 detail: action.workflowId ? detail : { ...detail, step },
88 }),
89 );
90 };
91 const primary =
92 'rounded-sm border border-design-main bg-transparent px-3 py-1.5 text-sm text-design-main hover:opacity-80';
93
94 return (
95 <div className="relative mt-2 max-w-[90%] rounded-xl border border-gray-200 bg-gray-50 p-3 text-sm text-gray-900">
96 <button
97 type="button"
98 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"
99 onClick={handleDismiss}
100 >
101 <Icon
102 className="pointer-events-none fill-current leading-none"
103 icon={closeIcon}
104 size={16}
105 />
106 <span className="sr-only">
107 {
108 // translators: Screen-reader label for the X button that hides a suggested next step card in the AI Agent chat.
109 __('Dismiss', 'extendify-local')
110 }
111 </span>
112 </button>
113 <p className="m-0 pe-6 font-semibold">{title}</p>
114 {body && <p className="m-0 mt-1">{body}</p>}
115 <div className="mt-3 flex flex-wrap gap-2">
116 {action.url ? (
117 <a
118 href={action.url}
119 {...(!action.sameTab && newTab)}
120 className={`${primary} no-underline`}
121 onClick={handleLink}
122 >
123 {action.label}
124 </a>
125 ) : (
126 <button type="button" className={primary} onClick={handleAction}>
127 {action.label}
128 </button>
129 )}
130 </div>
131 </div>
132 );
133 };
134