PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.6.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.6.1
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 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / REST / AIFaq.php

AIFaq.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.6.1, at includes/REST/AIFaq.php

144 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\REST;
4
5 use WP_REST_Request;
6 use WPDeveloper\BetterDocs\Core\BaseAPI;
7 use WPDeveloper\BetterDocs\Utils\AIUsage;
8
9 /**
10 * Server-side proxy for the "Write FAQ with BetterDocs AI" feature.
11 *
12 * The FAQ builder used to call OpenAI directly from the browser with the secret API key —
13 * this endpoint moves that call server-side so the key never leaves the site. Uses the
14 * same dynamic model/token settings as Write with AI (write_with_ai_model).
15 */
16 class AIFaq extends BaseAPI {
17
18 const MAX_QUESTION_LENGTH = 500;
19 const MAX_KEYWORDS_LENGTH = 300;
20
21 public function register() {
22 $this->post(
23 '/ai-faq',
24 array( $this, 'generate' ),
25 array(
26 'question' => array(
27 'type' => 'string',
28 'required' => true
29 ),
30 'keywords' => array(
31 'type' => 'string',
32 'required' => false,
33 'default' => ''
34 )
35 )
36 );
37 }
38
39 public function permission_check() {
40 return current_user_can( 'edit_others_posts' );
41 }
42
43 public function generate( WP_REST_Request $request ) {
44 $settings = betterdocs()->settings;
45
46 if ( ! $settings->get( 'enable_faq_write_with_ai', true ) ) {
47 return $this->error(
48 'ai_disabled',
49 __( 'Write FAQ with AI is disabled. Enable it from BetterDocs settings.', 'betterdocs' ),
50 400
51 );
52 }
53
54 $write_ai = betterdocs()->ai_autowrtie;
55 if ( empty( $write_ai ) || empty( $write_ai->get_api_key() ) ) {
56 return $this->error(
57 'ai_no_key',
58 __( 'OpenAI API key is missing. Add one in BetterDocs settings.', 'betterdocs' ),
59 400
60 );
61 }
62
63 $question = trim( wp_strip_all_tags( (string) $request->get_param( 'question' ) ) );
64 $keywords = trim( wp_strip_all_tags( (string) $request->get_param( 'keywords' ) ) );
65
66 if ( $question === '' ) {
67 return $this->error(
68 'ai_empty_question',
69 __( 'Please provide a question for the AI.', 'betterdocs' ),
70 400
71 );
72 }
73
74 if ( strlen( $question ) > self::MAX_QUESTION_LENGTH ) {
75 $question = substr( $question, 0, self::MAX_QUESTION_LENGTH );
76 }
77 if ( strlen( $keywords ) > self::MAX_KEYWORDS_LENGTH ) {
78 $keywords = substr( $keywords, 0, self::MAX_KEYWORDS_LENGTH );
79 }
80
81 $prompt = sprintf(
82 'Write a clear, concise answer in plain text for the following FAQ question related to %s. The question: %s',
83 $keywords,
84 $question
85 );
86 $system = __( 'You are a helpful assistant that writes clear, concise FAQ answers in plain text. Do not use any Markdown or special formatting — no asterisks, bold, italics, headings, bullet points, backticks or code fences. Reply with a direct answer written in plain sentences.', 'betterdocs' );
87
88 $result = $write_ai->generate_text( $prompt, $system );
89
90 if ( empty( $result['success'] ) ) {
91 $message = isset( $result['error'] ) ? (string) $result['error'] : __( 'Unknown AI error.', 'betterdocs' );
92 return $this->error( 'ai_upstream', $message, 502 );
93 }
94
95 $answer = $this->clean_response( (string) $result['content'] );
96
97 if ( $answer === '' ) {
98 return $this->error(
99 'ai_empty_response',
100 __( 'The AI returned no answer. Try again or rephrase your question.', 'betterdocs' ),
101 502
102 );
103 }
104
105 // FAQ answers are generated before the FAQ post exists — no reliable post id.
106 AIUsage::record( 'faq_write_with_ai' );
107
108 return $this->success(
109 array(
110 'answer' => $answer,
111 'model' => isset( $result['model'] ) ? $result['model'] : null
112 )
113 );
114 }
115
116 /**
117 * Strip the leading "Answer:" label / stray leading "?" the browser-side code removed.
118 */
119 protected function clean_response( $text ) {
120 $text = trim( $text );
121 $text = preg_replace( '/^\s*answer\s*:\s*/i', '', $text );
122 $text = preg_replace( '/^\s*\?/', '', $text );
123 $text = $this->strip_markdown( $text );
124
125 return trim( $text );
126 }
127
128 /**
129 * Safety net: strip the common Markdown the model may still emit despite the
130 * plain-text instruction, so FAQ answers never render literal asterisks.
131 */
132 protected function strip_markdown( $text ) {
133 $text = preg_replace( '/```.*?```/s', '', $text ); // fenced code blocks
134 $text = preg_replace( '/\*\*(.+?)\*\*/s', '$1', $text ); // **bold**
135 $text = preg_replace( '/__(.+?)__/s', '$1', $text ); // __bold__
136 $text = preg_replace( '/`([^`]*)`/', '$1', $text ); // `inline code`
137 $text = preg_replace( '/^\s{0,3}#{1,6}\s+/m', '', $text ); // # headings
138 $text = preg_replace( '/^\s{0,3}>\s?/m', '', $text ); // > blockquotes
139 $text = preg_replace( '/^\s{0,3}[-*+]\s+/m', '', $text ); // -, *, + bullet markers
140
141 return trim( $text );
142 }
143 }
144