PluginProbe
Extendify / trunk
Extendify vtrunk
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.2 → trunk 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 -};