PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / faq-builder / components / FaqAIModalForm.js

FaqAIModalForm.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/faq-builder/components/FaqAIModalForm.js

307 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState, useEffect } from "react";
2 import { __ } from "@wordpress/i18n";
3 import { normalizeAIError } from "../../../utils/aiErrorMessage";
4 import Modal from "react-modal";
5
6 import { getSettingsData, generateFaqAnswer } from "../utils/function";
7
8 // Custom modal styles
9 const customStyles = {
10 overlay: {
11 position: "fixed",
12 top: 0,
13 left: 0,
14 right: 0,
15 bottom: 0,
16 backgroundColor: "rgba(35, 40, 45, 0.5)",
17 zIndex: 9990,
18 padding: 50,
19 paddingTop: 70,
20 display: "flex",
21 flexDirection: "column",
22 overflow: "auto",
23 },
24 content: {
25 width: "40rem",
26 maxWidth: "90%",
27 position: "static",
28 height: "auto",
29 overflow: "initial",
30 margin: "auto",
31 },
32 };
33 const FaqAIModalForm = ({
34 open,
35 setShowFaqAIModalForm,
36 // reFetchCategories,
37 // setShowCategoryModal,
38 // updateCategory,
39 // setUpdateCategory,
40 setFormFields,
41 formFields,
42 // handleAnswerChange
43 }) => {
44 const [generatedResponse, setGeneratedResponse] = useState(formFields);
45 const [generatedAnswer, setGeneratedAnswer] = useState(null);
46 const [error, setError] = useState(null);
47 const [isLoading, setIsLoading] = useState(false);
48 const [contentGenerated, setContentGenerated] = useState(false);
49
50 const [settings, setSettings] = useState(null);
51
52 useEffect(() => {
53 getSettingsData().then((getSettings) => {
54 setSettings(getSettings);
55 }).catch(function (error) {
56 console.log(error);
57 });
58 }, []);
59
60 const resetErrors = () => {
61 setError(null);
62 };
63
64 const handleCancel = () => {
65 resetErrors();
66 // setShowCategoryModal(false);
67 setShowFaqAIModalForm(false);
68 // setUpdateCategory(null);
69 };
70
71
72 const highlightKeywords = (inputString, keywords) => {
73 let modifiedString = inputString.replace(/<(h\d)(.*?)>(.*?)<\/\1>/g, '<$1$2><span className="highlight">$3</span></$1>');
74
75 const keywordArray = keywords.split(',').map(keyword => keyword.trim());
76
77 keywordArray.forEach(keyword => {
78 modifiedString = modifiedString.replace(new RegExp(`\\b(${keyword})\\b`, 'gi'), '<span className="highlight">$1</span>');
79 });
80
81 return modifiedString;
82 }
83
84 const getAPIKey = (settings) => {
85 if (!settings) {
86 return '';
87 }
88
89 // Mirror the server-side ProviderFactory::key_field_for(): the key lives in
90 // the field for the *selected* AI platform, not always OpenAI. OpenAI keeps
91 // the legacy `ai_autowrite_api_key`; every other platform (gemini, claude,
92 // deepseek, …) uses `ai_api_key_<platform>`. Checking only the OpenAI field
93 // blocked "Write with AI" whenever another provider was configured.
94 const platform = settings.ai_platform || 'openai';
95 const field = platform === 'openai' ? 'ai_autowrite_api_key' : `ai_api_key_${platform}`;
96
97 return settings[field] || '';
98 }
99
100 const handleSubmit = async (event) => {
101 event.preventDefault();
102
103 const question = document.getElementById('betterdocs-ai-title').value;
104 const keywords = document.getElementById('betterdocs-ai-keyword').value;
105
106 try {
107 setIsLoading(true);
108 setError(null);
109
110 // The prompt build, model selection and "Answer:" cleanup now happen
111 // server-side; the OpenAI API key never leaves the server.
112 const res = await generateFaqAnswer({ question, keywords });
113 const answer = res?.data?.answer || "";
114
115 setIsLoading(false);
116
117 if (!answer) {
118 setContentGenerated(false);
119 setError(__("The AI returned no answer. Try again or rephrase your question.", "betterdocs"));
120 return;
121 }
122
123 setContentGenerated(true);
124 setGeneratedResponse(state => ({
125 ...state,
126 question: question,
127 aiAnswer: answer,
128 }));
129 setGeneratedAnswer(answer);
130 } catch (error) {
131 setIsLoading(false);
132 setContentGenerated(false);
133 setError(normalizeAIError(error));
134 }
135 };
136
137
138 const handleKeep = () => {
139 // Set `answer` (the field actually saved as post_content) directly to the AI
140 // answer — do NOT rely on the editor's async aiValue effect to copy it across.
141 // If that effect races the editor's init or the answer is unchanged, `answer`
142 // stayed empty and the FAQ saved blank ("content not being saved"). aiAnswer is
143 // kept so the editor still renders the content visually.
144 setFormFields({
145 ...generatedResponse,
146 answer: generatedResponse?.aiAnswer || generatedResponse?.answer || "",
147 });
148 handleCancel();
149 };
150
151 return (
152 <Modal
153 isOpen={open}
154 className="bd-faq-ctg-modal"
155 onRequestClose={() => handleCancel()}
156 style={customStyles}
157 >
158 <form
159 className={settings && !getAPIKey(settings) ? "disabled-input-field" : ""}
160 onSubmit={handleSubmit}
161 >
162 <div className="bd-faq-ctg-header">
163 <span
164 className="bd-faq-stat-card__icon"
165 style={{ "--bd-icon-tint": "#7f56d9", "--bd-icon-tint-rgb": "127, 86, 217" }}
166 >
167 <svg
168 xmlns="http://www.w3.org/2000/svg"
169 width={17}
170 height={17}
171 viewBox="0 0 32 32"
172 fill="none"
173 >
174 <path
175 d="M15.8322 30.1765L8.97508 30.7708L9.56936 23.9136L23.8322 9.74219C24.0451 9.52448 24.2993 9.3515 24.58 9.23341C24.8606 9.11531 25.162 9.05447 25.4665 9.05447C25.771 9.05447 26.0724 9.11531 26.3531 9.23341C26.6337 9.3515 26.8879 9.52448 27.1008 9.74219L30.0037 12.6679C30.2176 12.8805 30.3874 13.1334 30.5033 13.4119C30.6192 13.6904 30.6788 13.9891 30.6788 14.2908C30.6788 14.5924 30.6192 14.8911 30.5033 15.1696C30.3874 15.4481 30.2176 15.701 30.0037 15.9136L15.8322 30.1765ZM1.92593 9.07933C1.12365 8.9399 1.12365 7.7879 1.92593 7.64848C3.34647 7.40124 4.6612 6.73658 5.70251 5.73923C6.74382 4.74188 7.46455 3.45703 7.77279 2.04848L7.82079 1.82676C7.99451 1.03591 9.12365 1.02905 9.30651 1.82219L9.36365 2.07819C9.68319 3.48048 10.4099 4.75709 11.4526 5.74772C12.4953 6.73834 13.8074 7.39881 15.2242 7.64619C16.0311 7.78333 16.0311 8.94219 15.2242 9.0839C13.8073 9.33071 12.4949 9.99066 11.4518 10.9809C10.4087 11.9711 9.68146 13.2474 9.36136 14.6496L9.30193 14.9079C9.12136 15.6988 7.99222 15.6942 7.81851 14.901L7.77279 14.6793C7.46424 13.2702 6.74284 11.9849 5.70064 10.9874C4.65845 9.99003 3.34273 9.32574 1.92136 9.07933H1.92593Z"
176 stroke="currentColor"
177 strokeWidth="2.28571"
178 strokeLinecap="round"
179 strokeLinejoin="round"
180 />
181 </svg>
182 </span>
183 <div className="bd-faq-ctg-heading">
184 <h3 className="bd-faq-ctg-title">
185 {__("Write FAQ with BetterDocs AI", "betterdocs")}
186 </h3>
187 <p className="bd-faq-ctg-subtitle">
188 {__("Generate FAQs effortlessly with BetterDocs AI. Input your question and relevant keywords and let the system generate FAQs tailored to your needs.", "betterdocs")}
189 </p>
190 </div>
191 </div>
192
193 <div className="bd-faq-ctg-body">
194 {settings && !getAPIKey(settings) && (
195 <div id="betterdocs-ai-message">
196 <div className="warning-message">
197 <span className="dashicons dashicons-warning"></span>
198 <div>
199 {__("Please insert your", "betterdocs")}{" "}
200 <a
201 target="_blank"
202 href="/wp-admin/admin.php?page=betterdocs-settings#betterdocs-ai"
203 >
204 {__("AI API Key", "betterdocs")}
205 </a>{" "}
206 {__('to use this "Write with AI" feature.', "betterdocs")}
207 </div>
208 </div>
209 </div>
210 )}
211
212 {error && (
213 <div id="betterdocs-ai-error-message">
214 <div className="warning-message">
215 <span className="dashicons dashicons-warning"></span>
216 <div className="footer-message">{error}</div>
217 </div>
218 </div>
219 )}
220
221 <div className="bd-faq-cgt-field">
222 <label className="bd-faq-label" htmlFor="betterdocs-ai-title">
223 {__("Question", "betterdocs")}
224 </label>
225 <input
226 type="text"
227 className="bd-faq-input"
228 placeholder={__("Insert your question to generate a relevant response.", "betterdocs")}
229 id="betterdocs-ai-title"
230 name="betterdocs-ai-title"
231 required
232 value={formFields?.question}
233 onChange={(e) =>
234 setFormFields((state) => ({
235 ...state,
236 question: e.target.value.replace(/^\s+/, ""),
237 }))
238 }
239 />
240 </div>
241
242 <div className="bd-faq-cgt-field">
243 <label className="bd-faq-label" htmlFor="betterdocs-ai-keyword">
244 {__("Keywords", "betterdocs")}
245 </label>
246 <input
247 type="text"
248 className="bd-faq-input"
249 placeholder={__("Add keywords to generate precise and relevant FAQs (comma-separated).", "betterdocs")}
250 id="betterdocs-ai-keyword"
251 name="betterdocs-ai-keyword"
252 required
253 />
254 </div>
255
256 {generatedAnswer && (
257 <div className="bd-faq-cgt-field generated-answer">
258 <label className="bd-faq-label">
259 {__("Generated FAQ", "betterdocs")}
260 </label>
261 <div className="betterdocs-ai-content-wrapper">
262 <p className="betterdocs-ai-content" dangerouslySetInnerHTML={{ __html: generatedAnswer }} />
263 </div>
264 </div>
265 )}
266 </div>
267
268 <div className="bd-faq-ctg-footer">
269 <button
270 type="submit"
271 className={`bd-faq-button ${contentGenerated ? "button-ghost" : "button-success"}`}
272 disabled={isLoading}
273 >
274 <span className="bd-faq-text">
275 {isLoading ? __("Generating…", "betterdocs") : contentGenerated ? __("Regenerate FAQ", "betterdocs") : __("Generate FAQ", "betterdocs")}
276 </span>
277 </button>
278
279 {contentGenerated && (
280 <button
281 type="button"
282 className="bd-faq-button button-success"
283 onClick={() => handleKeep(generatedResponse)}
284 >
285 <span className="bd-faq-text">{__("Keep", "betterdocs")}</span>
286 </button>
287 )}
288 </div>
289 </form>
290
291 <button
292 type="button"
293 className="bd-faq-modal-close-button"
294 onClick={() => handleCancel()}
295 >
296 <svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
297 <path d="M1.69687 14L0.296875 12.6L5.89687 7L0.296875 1.4L1.69687 0L7.29688 5.6L12.8969 0L14.2969 1.4L8.69688 7L14.2969 12.6L12.8969 14L7.29688 8.4L1.69687 14Z" fill="currentColor" />
298 </svg>
299 </button>
300 </Modal>
301 );
302 };
303
304 //Modal.setAppElement("#betterdocsFaqBuilder");
305
306 export default FaqAIModalForm;
307