PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
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 0.6.0 All 127 releases
extendify / src / Agent / components / ChatSuggestions.jsx

ChatSuggestions.jsx in Extendify 3.2.1, at src/Agent/components/ChatSuggestions.jsx

158 lines 4.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { sparkle } from '@agent/icons';
2 import { useDomainActivities } from '@agent/state/domain-activities';
3 import { useSuggestionsStore } from '@agent/state/suggestions';
4 import { track } from '@shared/lib/track';
5 import { useEffect, useState } from '@wordpress/element';
6 import { __ } from '@wordpress/i18n';
7 import {
8 chevronRight,
9 drafts,
10 help,
11 pencil,
12 published,
13 siteLogo,
14 styles,
15 swatch,
16 typography,
17 video,
18 } from '@wordpress/icons';
19
20 const icons = {
21 styles,
22 edit: pencil,
23 help,
24 video,
25 sparkle,
26 drafts,
27 published,
28 typography,
29 pencil,
30 siteLogo,
31 swatch,
32 };
33
34 export const ChatSuggestions = ({ suggestions }) => {
35 const { markAsClicked, isAvailable, getSuggestions } = useSuggestionsStore();
36 const { setDomainActivity } = useDomainActivities();
37 const [allSuggestions, setAllSuggestions] = useState(suggestions);
38
39 const handleSelect = (suggestion) => {
40 markAsClicked(suggestion);
41 if (suggestion.telemetry) {
42 track(suggestion.telemetry.key, suggestion.telemetry.payload);
43 } else if (suggestion.tracking) {
44 setDomainActivity({ ...suggestion.tracking, action: 'clicked' });
45 }
46 // External-link suggestions are plain links: the anchor opens the new tab.
47 // They don't trigger a workflow or post a message in the chat.
48 if (suggestion.type === 'external-link') return;
49 window.dispatchEvent(
50 new CustomEvent('extendify-agent:chat-submit', {
51 detail: { message: suggestion.message },
52 }),
53 );
54 };
55
56 const handleShowMore = () => {
57 const next = getSuggestions({ slice: 6, exclude: allSuggestions });
58 setAllSuggestions((prev) => [...prev, ...next]);
59 };
60
61 if (!suggestions) return null;
62
63 return (
64 <>
65 {allSuggestions.map((suggestion) => {
66 // Hide them if it's not available to the user
67 if (!isAvailable(suggestion)) return null;
68 return (
69 <SuggestionButton
70 key={suggestion.message}
71 suggestion={suggestion}
72 onSelect={handleSelect}
73 />
74 );
75 })}
76 <ShowMoreButton handleShowMore={handleShowMore} />
77 </>
78 );
79 };
80
81 const SuggestionButton = ({ suggestion, onSelect }) => {
82 const { setDomainActivity } = useDomainActivities();
83 const icon = icons[suggestion?.icon] ?? icons.sparkle;
84
85 useEffect(() => {
86 if (suggestion.viewTelemetry) {
87 track(suggestion.viewTelemetry.key, suggestion.viewTelemetry.payload);
88 }
89 if (suggestion.tracking) {
90 setDomainActivity({ ...suggestion.tracking, action: 'viewed' });
91 }
92 }, [suggestion, setDomainActivity]);
93
94 const className =
95 'group flex items-center justify-between rounded-sm bg-transparent px-1 py-1 text-left text-sm not-italic text-gray-900 transition-colors duration-100 hover:bg-gray-100 focus:outline-hidden focus:ring-2 focus:ring-design-main';
96 const content = (
97 <>
98 <div className="flex items-center gap-1.5 leading-none">
99 <span className="h-5 w-5 shrink-0 self-start fill-gray-700">
100 {icon}
101 </span>
102 <span className="leading-5">{suggestion.message}</span>
103 </div>
104 <span className="inline-block h-5 w-5 fill-gray-700 leading-none opacity-0 transition-opacity duration-100 group-hover:opacity-100 rtl:scale-x-[-1]">
105 {chevronRight}
106 </span>
107 </>
108 );
109
110 // External-link suggestions render as a real anchor that opens in a new tab.
111 if (suggestion.type === 'external-link') {
112 return (
113 <a
114 href={suggestion.url}
115 target="_blank"
116 rel="noopener noreferrer"
117 className={`${className} no-underline`}
118 onClick={() => onSelect(suggestion)}
119 >
120 {content}
121 </a>
122 );
123 }
124
125 return (
126 <button
127 type="button"
128 className={className}
129 onClick={() => onSelect(suggestion)}
130 >
131 {content}
132 </button>
133 );
134 };
135
136 const ShowMoreButton = ({ handleShowMore }) => {
137 const [hide, setHide] = useState(false);
138 const handleClick = () => {
139 handleShowMore();
140 setHide(true);
141 };
142
143 if (hide) return null;
144 return (
145 <button
146 type="button"
147 className="group flex w-full items-center gap-3 rounded-sm bg-transparent px-1 py-1 text-xs not-italic text-gray-800 transition-colors duration-100 hover:text-gray-900 focus:outline-hidden group"
148 onClick={handleClick}
149 >
150 <span className="h-px flex-1 bg-gray-200" />
151 <span className="group-hover:underline group-focus:ring-2 group-focus:ring-design-main">
152 {__('Show more', 'extendify-local')}
153 </span>
154 <span className="h-px flex-1 bg-gray-200" />
155 </button>
156 );
157 };
158