PluginProbe
Extendify / 2.2.0
Extendify v2.2.0
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 0.7.0 All 126 releases
extendify / src / Agent / components / ChatInput.jsx

ChatInput.jsx in Extendify 2.2.0, at src/Agent/components/ChatInput.jsx

194 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 useState,
3 useRef,
4 useLayoutEffect,
5 useEffect,
6 useCallback,
7 } from '@wordpress/element';
8 import { __ } from '@wordpress/i18n';
9 import { Icon, arrowUp } from '@wordpress/icons';
10 import classNames from 'classnames';
11 import { ChatTools } from '@agent/components/ChatTools';
12 import { useGlobalStore } from '@agent/state/global';
13 import { useWorkflowStore } from '@agent/state/workflows';
14
15 export const ChatInput = ({ disabled, handleSubmit }) => {
16 const textareaRef = useRef(null);
17 const [input, setInput] = useState('');
18 const [history, setHistory] = useState([]);
19 const dirtyRef = useRef(false);
20 const [historyIndex, setHistoryIndex] = useState(null);
21 const { getWorkflowsByFeature, block } = useWorkflowStore();
22 const { isMobile } = useGlobalStore();
23 const domTool =
24 getWorkflowsByFeature({ requires: ['block'] })?.length > 0 && !isMobile;
25
26 // resize the height of the textarea based on the content
27 const adjustHeight = useCallback(() => {
28 if (!textareaRef.current) return;
29 textareaRef.current.style.height = 'auto';
30 const chat =
31 textareaRef.current.closest('#extendify-agent-chat').offsetHeight * 0.55;
32 const h = Math.min(chat, textareaRef.current.scrollHeight);
33 textareaRef.current.style.height = `${block && h < 60 ? 60 : h}px`;
34 }, [block]);
35
36 useLayoutEffect(() => {
37 window.addEventListener('extendify-agent:resize-end', adjustHeight);
38 adjustHeight();
39 return () =>
40 window.removeEventListener('extendify-agent:resize-end', adjustHeight);
41 }, [adjustHeight]);
42
43 useEffect(() => {
44 const watchForSubmit = ({ detail }) => {
45 setHistory((prev) => {
46 // avoid duplicates
47 if (prev?.at(-1) === detail.message) return prev;
48 return [...prev, detail.message];
49 });
50 setHistoryIndex(null);
51 };
52 window.addEventListener('extendify-agent:chat-submit', watchForSubmit);
53 return () =>
54 window.removeEventListener('extendify-agent:chat-submit', watchForSubmit);
55 }, []);
56
57 useEffect(() => {
58 adjustHeight();
59 }, [input, adjustHeight]);
60
61 useEffect(() => {
62 const userMessages = Array.from(
63 document.querySelectorAll(
64 '#extendify-agent-chat-scroll-area > [data-agent-message-role="user"]',
65 ),
66 )?.map((el) => el.textContent || '');
67 const deduped = userMessages.filter(
68 (msg, i, arr) => i === 0 || msg !== arr[i - 1],
69 );
70 setHistory(deduped);
71 }, []);
72
73 const submitForm = useCallback(
74 (e) => {
75 e?.preventDefault();
76 if (!input.trim()) return;
77 handleSubmit(input);
78 setHistory((prev) => {
79 // avoid duplicates
80 if (prev?.at(-1) === input) return prev;
81 return [...prev, input];
82 });
83 setHistoryIndex(null);
84 setInput('');
85 requestAnimationFrame(() => {
86 dirtyRef.current = false;
87 adjustHeight();
88 textareaRef.current?.focus();
89 });
90 },
91 [input, handleSubmit, adjustHeight],
92 );
93
94 const handleKeyDown = useCallback(
95 (event) => {
96 if (
97 event.key === 'Enter' &&
98 !event.shiftKey &&
99 !event.nativeEvent.isComposing
100 ) {
101 event.preventDefault();
102 submitForm();
103 return;
104 }
105 if (dirtyRef.current) return;
106 if (event.key === 'ArrowUp') {
107 if (!history.length) return;
108 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
109 return;
110 setHistoryIndex((prev) => {
111 const next =
112 prev === null ? history.length - 1 : Math.max(prev - 1, 0);
113 setInput(history[next]);
114 return next;
115 });
116 event.preventDefault();
117 return;
118 }
119 if (event.key === 'ArrowDown') {
120 if (historyIndex === null) return;
121 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
122 return;
123 setHistoryIndex((prev) => {
124 if (prev === null) return null;
125 const next = prev + 1;
126 if (next >= history.length) {
127 setInput('');
128 return null;
129 }
130 setInput(history[next]);
131 return next;
132 });
133 event.preventDefault();
134 return;
135 }
136 dirtyRef.current = true;
137 },
138 [history, historyIndex, submitForm],
139 );
140
141 return (
142 <form
143 onSubmit={submitForm}
144 onClick={() => textareaRef.current?.focus()}
145 className={classNames(
146 'relative flex w-full flex-col rounded border border-gray-300 focus-within:outline-design-main focus:rounded focus:border-design-main focus:ring-design-main',
147 {
148 'bg-gray-300': disabled,
149 'bg-gray-50': !disabled,
150 },
151 )}>
152 <textarea
153 ref={textareaRef}
154 id="extendify-agent-chat-textarea"
155 disabled={disabled}
156 className={classNames(
157 'flex max-h-[calc(75dvh)] min-h-10 w-full resize-none overflow-hidden bg-transparent px-2 pb-4 pt-2.5 text-base placeholder:text-gray-700 focus:shadow-none focus:outline-none disabled:opacity-50 md:text-sm',
158 )}
159 placeholder={
160 block
161 ? __(
162 'What do you want to change in the selected content?',
163 'extendify-local',
164 )
165 : __('Ask anything', 'extendify-local')
166 }
167 rows="1"
168 autoFocus
169 value={input}
170 onChange={(e) => {
171 setInput(e.target.value);
172 setHistoryIndex(null);
173 adjustHeight();
174 }}
175 onKeyDown={handleKeyDown}
176 />
177 <div className="flex justify-between gap-4 px-2 pb-2">
178 {domTool ? <ChatTools disabled={disabled} /> : null}
179 <div className="ms-auto flex items-center">
180 <button
181 type="submit"
182 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"
183 disabled={disabled || input.trim().length === 0}>
184 <Icon fill="currentColor" icon={arrowUp} size={24} />
185 <span className="sr-only">
186 {__('Send message', 'extendify-local')}
187 </span>
188 </button>
189 </div>
190 </div>
191 </form>
192 );
193 };
194