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
← All changes | src/Agent/components/ChatInput.jsx +67 -37 3.1.33.2.1 View file →
@@ -1,9 +1,10 @@
1 -import { ChatTools } from '@agent/components/ChatTools';
1 +import { useCanvasAssist } from '@agent/components/Canvas';
2 +import { PageDocument } from '@agent/components/PageDocument';
2 3 import { cancelRequest } from '@agent/icons';
3 4 import { useChatStore } from '@agent/state/chat';
4 -import { useGlobalStore } from '@agent/state/global';
5 5 import { useWorkflowStore } from '@agent/state/workflows';
6 +import { askAiTarget } from '@quick-edit/lib/hover-bar';
6 7 import { useQuickEditStore } from '@quick-edit/state/store';
7 8 import {
8 9 useCallback,
9 10 useEffect,
@@ -14,8 +15,24 @@
14 15 import { __ } from '@wordpress/i18n';
15 16 import { arrowUp, Icon } from '@wordpress/icons';
16 17 import classNames from 'classnames';
17 18
19 +const placeholderFor = ({ disabled, editing, block }) => {
20 + // `disabled` also covers being out of credits, so it outranks the rest.
21 + if (disabled) return __('Ask anything', 'extendify-local');
22 + if (editing) {
23 + // translators: Shown in the AI chat box while a Quick Edit editor is open on a block.
24 + return __('Finish editing first', 'extendify-local');
25 + }
26 + if (block) {
27 + return __(
28 + 'What do you want to change in the selected content?',
29 + 'extendify-local',
30 + );
31 + }
32 + return __('Ask anything', 'extendify-local');
33 +};
34 +
18 35 export const ChatInput = ({ disabled, handleSubmit }) => {
19 36 const textareaRef = useRef(null);
20 37 const [input, setInput] = useState('');
21 38 const [history, setHistory] = useState([]);
@@ -20,21 +37,26 @@
20 37 const [input, setInput] = useState('');
21 38 const [history, setHistory] = useState([]);
22 39 const dirtyRef = useRef(false);
23 40 const [historyIndex, setHistoryIndex] = useState(null);
24 - const { getWorkflowsByFeature } = useWorkflowStore();
41 + const { workflow } = useWorkflowStore();
42 + const canvasAssist = useCanvasAssist();
25 43 const block = useQuickEditStore((s) => s.agentBlock);
26 - const { isMobile } = useGlobalStore();
27 - const domTool =
28 - getWorkflowsByFeature({ requires: ['block'] })?.length > 0 && !isMobile;
44 + // Quick Edit's modals mount off `selected` too, so this covers them.
45 + const editing = useQuickEditStore((s) => Boolean(s.selected));
29 46 const INPUT_LIMIT = 1500;
30 47 const inputTrimmed = input.trim();
31 48 const overLimit = inputTrimmed.length > INPUT_LIMIT;
49 + // The workflow stays set with a canvas open, so this would show cancel.
50 + const busy = disabled || (Boolean(workflow?.id) && !canvasAssist);
51 + const inputDisabled = disabled || editing;
32 52
33 53 // resize the height of the textarea based on the content
34 54 const adjustHeight = useCallback(() => {
35 55 if (!textareaRef.current) return;
36 56 textareaRef.current.style.height = 'auto';
57 + // while empty, scrollHeight measures the wrapped placeholder and overshoots
58 + if (!textareaRef.current.value) return;
37 59 const chat =
38 60 textareaRef.current.closest('#extendify-agent-chat').offsetHeight * 0.55;
39 61 const h = Math.min(chat, textareaRef.current.scrollHeight);
40 62 textareaRef.current.style.height = `${block && h < 60 ? 60 : h}px`;
@@ -131,8 +153,15 @@
131 153 },
132 154 [history, historyIndex, submitForm, overLimit],
133 155 );
134 156
157 + // Typing beside a pinned bar is a request about that block.
158 + const stagePinnedSelection = useCallback(() => {
159 + const { committedSelection, agentBlock } = useQuickEditStore.getState();
160 + if (!committedSelection?.el || agentBlock) return;
161 + askAiTarget(committedSelection.el);
162 + }, []);
163 +
135 164 const handleCancel = useCallback((e) => {
136 165 e.stopPropagation();
137 166 window.dispatchEvent(new CustomEvent('extendify-agent:cancel-workflow'));
138 167 }, []);
@@ -144,28 +173,26 @@
144 173 onClick={() => textareaRef.current?.focus()}
145 174 className={classNames(
146 175 'relative flex w-full flex-col rounded-sm border border-gray-300 focus-within:outline-design-main focus:rounded-sm focus:border-design-main focus:ring-design-main',
147 176 {
148 - 'bg-gray-300': disabled,
149 - 'bg-gray-50': !disabled,
177 + 'bg-gray-300': inputDisabled,
178 + 'bg-gray-50': !inputDisabled,
150 179 },
151 180 )}
152 181 >
182 + {block ? (
183 + <div className="px-2 pt-2">
184 + <PageDocument busy={busy} />
185 + </div>
186 + ) : null}
153 187 <textarea
154 188 ref={textareaRef}
155 189 id="extendify-agent-chat-textarea"
156 - disabled={disabled}
190 + disabled={inputDisabled}
157 191 className={classNames(
158 192 'flex max-h-[calc(75dvh)] min-h-16 w-full resize-none overflow-y-auto bg-transparent px-2 pb-4 pt-2.5 text-base placeholder:text-gray-700 focus:shadow-none focus:outline-hidden disabled:opacity-50 md:text-sm border-none text-gray-900',
159 193 )}
160 - placeholder={
161 - block
162 - ? __(
163 - 'What do you want to change in the selected content?',
164 - 'extendify-local',
165 - )
166 - : __('Ask anything', 'extendify-local')
167 - }
194 + placeholder={placeholderFor({ disabled, editing, block })}
168 195 rows="1"
169 196 // biome-ignore lint: Allow autofocus here
170 197 autoFocus
171 198 value={input}
@@ -174,12 +201,12 @@
174 201 setHistoryIndex(null);
175 202 adjustHeight();
176 203 }}
177 204 onKeyDown={handleKeyDown}
205 + onFocus={stagePinnedSelection}
178 206 />
179 207 <div className="flex justify-between gap-4 px-2 pb-2">
180 - {domTool ? <ChatTools disabled={disabled} /> : null}
181 - <div className="ms-auto flex items-center gap-2">
208 + <div className="ms-auto flex items-center gap-1">
182 209 <span
183 210 className={classNames(
184 211 'text-xs font-medium',
185 212 overLimit ? 'text-red-600' : 'invisible',
@@ -188,11 +215,12 @@
188 215 >
189 216 {overLimit && __('Message too long', 'extendify-local')}
190 217 </span>
191 218 <SubmitButton
192 - disabled={disabled}
219 + disabled={inputDisabled}
193 220 noInput={input.trim().length === 0}
194 221 overLimit={overLimit}
222 + showCancel={busy}
195 223 handleCancel={handleCancel}
196 224 />
197 225 </div>
198 226 </div>
@@ -199,28 +227,30 @@
199 227 </form>
200 228 );
201 229 };
202 230
203 -const SubmitButton = ({ disabled, noInput, overLimit, handleCancel }) => {
204 - if (disabled) {
205 - return (
206 - <button
207 - type="button"
208 - onClick={handleCancel}
209 - className="inline-flex h-fit items-center justify-center gap-2 whitespace-nowrap rounded-full border-0 bg-design-main p-1 text-sm font-medium text-white transition-colors focus-visible:ring-design-main disabled:opacity-20"
210 - >
211 - <Icon fill="currentColor" icon={cancelRequest} size={18} />
212 - <span className="sr-only">{__('Cancel', 'extendify-local')}</span>
213 - </button>
214 - );
215 - }
216 - return (
231 +const SubmitButton = ({
232 + disabled,
233 + noInput,
234 + overLimit,
235 + showCancel,
236 + handleCancel,
237 +}) =>
238 + showCancel ? (
217 239 <button
240 + type="button"
241 + onClick={handleCancel}
242 + className="inline-flex h-7 w-7 items-center justify-center rounded-full border-0 bg-design-main p-0 text-white transition-colors focus-visible:ring-design-main disabled:opacity-20"
243 + >
244 + <Icon fill="currentColor" icon={cancelRequest} size={14} />
245 + <span className="sr-only">{__('Cancel', 'extendify-local')}</span>
246 + </button>
247 + ) : (
248 + <button
218 249 type="submit"
219 - className="inline-flex h-fit items-center justify-center gap-2 whitespace-nowrap rounded-full border-0 bg-design-main p-0.5 text-sm font-medium text-white transition-colors focus-visible:ring-design-main disabled:opacity-20"
250 + className="inline-flex h-7 w-7 items-center justify-center rounded-full border-0 bg-design-main p-0 text-white transition-colors focus-visible:ring-design-main disabled:opacity-20"
220 251 disabled={disabled || noInput || overLimit}
221 252 >
222 - <Icon fill="currentColor" icon={arrowUp} size={24} />
253 + <Icon fill="currentColor" icon={arrowUp} size={20} />
223 254 <span className="sr-only">{__('Send message', 'extendify-local')}</span>
224 255 </button>
225 256 );
226 -};