PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
2.7.0 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.3 2.3.2 2.3.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 All 129 releases
wdesignkit / includes / abilities / templates / wdesignkit-ai-import-template.php

wdesignkit-ai-import-template.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.7.0, at includes/abilities/templates/wdesignkit-ai-import-template.php

267 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: Generate template content via the WDesignKit AI template-import endpoint.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/ai-import-template', [
13 'label' => __('AI Import WDesignKit Template', 'wdesignkit'),
14 'description' => __(
15 'Calls the WDesignKit "ai/template_import" endpoint to generate AI-tailored template content for a given site profile. Returns the AI-rewritten copy that the caller can then feed into a builder. Maps to the "Import Template — AI Import" UI flow.',
16 'wdesignkit',
17 ),
18 'category' => 'wdesignkit',
19 'input_schema' => [
20 'type' => 'object',
21 'properties' => [
22 'template_id' => [
23 'type' => 'string',
24 'description' => 'Cloud template ID to AI-rewrite. Required. The ability fetches the template content and extracts text automatically — you do not need to supply text_array manually.',
25 ],
26 'text_array' => [
27 'type' => 'array',
28 'description' => 'Optional override: array of text snippets to rewrite. When omitted the ability auto-extracts text from template_id.',
29 'items' => ['type' => ['string', 'object']],
30 ],
31 'site_type' => [
32 'type' => 'string',
33 'description' => 'Industry / vertical of the destination site (e.g. "saas", "restaurant").',
34 ],
35 'site_title' => [
36 'type' => 'string',
37 'description' => 'Destination site title.',
38 ],
39 'site_language' => [
40 'type' => 'string',
41 'description' => 'Output language. Defaults to "english".',
42 ],
43 'site_agency' => [
44 'type' => 'string',
45 'description' => 'Agency / brand name to mention in the AI rewrite.',
46 ],
47 'site_description' => [
48 'type' => 'string',
49 'description' => 'Free-form description of the destination site for the AI prompt. STRONGLY RECOMMENDED — the server requires a non-empty description to run the AI rewrite. When omitted a fallback is auto-generated from site_title and site_type, but providing a real description produces better AI output.',
50 ],
51 'builder' => [
52 'type' => 'string',
53 'description' => 'Target builder.',
54 'enum' => ['elementor', 'gutenberg'],
55 ],
56 'global_colors' => [
57 'type' => 'string',
58 'description' => 'JSON-encoded global colour palette to apply after AI rewrite.',
59 ],
60 'global_typography' => [
61 'type' => 'string',
62 'description' => 'JSON-encoded global typography settings to apply after AI rewrite.',
63 ],
64 'images' => [
65 'type' => 'array',
66 'description' => 'Optional array of image URLs to substitute into template image slots. Use wdesignkit/browse-template-images to source these.',
67 'items' => ['type' => 'string'],
68 ],
69 'wireframe' => [
70 'type' => 'boolean',
71 'description' => 'When true, imports the template as a wireframe (placeholder layout without AI-rewritten content).',
72 ],
73 'import_ai_blog_posts' => [
74 'type' => 'boolean',
75 'description' => 'When true, the AI endpoint also generates blog post content for any blog/post listing sections in the template.',
76 ],
77 ],
78 'required' => ['template_id'],
79 'additionalProperties' => false,
80 ],
81 'output_schema' => [
82 'type' => 'object',
83 'properties' => [
84 'success' => ['type' => 'boolean'],
85 'message' => ['type' => 'string'],
86 'response' => ['type' => ['object', 'array']],
87 ],
88 ],
89 'execute_callback' => 'wdesignkit_mcp_ai_import_template',
90 'permission_callback' => 'wdesignkit_mcp_permission_callback',
91 'meta' => [
92 'public' => true,
93 'show_in_rest' => true,
94 'mcp' => ['public' => true],
95 'annotations' => [
96 'instructions' => implode("\n", [
97 'AI-rewrites a template\'s copy for a given site profile.',
98 'template_id is required — the ability fetches the template and auto-extracts text_array from it.',
99 'IMPORTANT: site_description is effectively required by the server — the AI endpoint rejects requests where description is empty. Always ask the user for a site description before calling this ability. A fallback is auto-generated when omitted, but it produces poor AI output.',
100 'site_type is also required by the server (maps to "type"). Pass a value like "saas", "restaurant", "agency", "portfolio", etc.',
101 'Requires WDesignKit cloud login. Consumes AI credits — surface the credit cost to the user before bulk runs.',
102 'For a plain (non-AI) import use wdesignkit/import-template instead.',
103 'Use wdesignkit/browse-template-images to source image URLs for the images[] field.',
104 ]),
105 'readonly' => false,
106 'destructive' => false,
107 'idempotent' => false,
108 ],
109 ],
110 ]);
111
112 function wdesignkit_mcp_ai_import_template(array $input): array {
113 set_time_limit(150);
114
115 $auth = wdesignkit_mcp_template_get_auth();
116 if (empty($auth['logged_in'])) {
117 return ['success' => false, 'message' => $auth['message'] ?? 'Not logged in.'];
118 }
119
120 $template_id = sanitize_text_field((string) ($input['template_id'] ?? ''));
121 if ($template_id === '') {
122 return ['success' => false, 'message' => 'template_id is required.'];
123 }
124
125 // Resolve text_array: use caller-supplied value or auto-extract from the template content.
126 $text_array = $input['text_array'] ?? null;
127
128 if (!is_array($text_array) || empty($text_array)) {
129 // Fetch the template and extract all non-empty string values as the text_array.
130 $tpl = wdesignkit_mcp_template_cloud_call('import_template', [
131 'token' => $auth['token'],
132 'template_id' => $template_id,
133 'unique_id' => get_option('wdkit_unique_id', ''),
134 ], 'json');
135
136 $content_raw = $tpl['content'] ?? '';
137 $content_arr = is_string($content_raw) ? json_decode($content_raw, true) : $content_raw;
138
139 $text_array = [];
140 if (is_array($content_arr)) {
141 wdesignkit_mcp_ai_extract_strings($content_arr, $text_array);
142 }
143
144 if (empty($text_array)) {
145 return [
146 'success' => false,
147 'message' => 'Could not extract text content from template. Provide text_array manually or verify template_id.',
148 'response' => $tpl,
149 ];
150 }
151 }
152
153 $payload = [
154 'text_array' => $text_array,
155 'template_id' => $template_id,
156 'type' => sanitize_text_field((string) ($input['site_type'] ?? '')),
157 'title' => sanitize_text_field((string) ($input['site_title'] ?? '')),
158 'language' => sanitize_text_field((string) ($input['site_language'] ?? 'english')),
159 'agency' => sanitize_text_field((string) ($input['site_agency'] ?? '')),
160 'description' => sanitize_textarea_field((string) ($input['site_description'] ?? '')),
161 'builder' => sanitize_text_field((string) ($input['builder'] ?? '')),
162 'token' => $auth['token'],
163 ];
164
165 // Server's next_check_ai_request() (AIController.php line 87) requires description AND type to be non-empty.
166 // Auto-generate a contextual fallback when site_description was not provided so the request doesn't fail.
167 if (empty($payload['description'])) {
168 $desc_parts = array_filter([
169 $payload['title'] !== '' ? $payload['title'] : '',
170 $payload['type'] !== '' ? 'a ' . $payload['type'] . ' website' : '',
171 ]);
172 $payload['description'] = !empty($desc_parts) ? implode(' — ', $desc_parts) : 'A website';
173 }
174
175 // type is also required by the server; default to 'business' when not supplied.
176 if (empty($payload['type'])) {
177 $payload['type'] = 'business';
178 }
179
180 // Optional extra params
181 if (!empty($input['global_colors'])) {
182 $payload['global_colors'] = (string) $input['global_colors'];
183 }
184 if (!empty($input['global_typography'])) {
185 $payload['global_typography'] = (string) $input['global_typography'];
186 }
187 if (!empty($input['images']) && is_array($input['images'])) {
188 $payload['images'] = array_values(array_map('esc_url_raw', $input['images']));
189 }
190 if (isset($input['wireframe'])) {
191 $payload['wireframe'] = (bool) $input['wireframe'];
192 }
193 if (isset($input['import_ai_blog_posts'])) {
194 $payload['import_ai_blog_posts'] = (bool) $input['import_ai_blog_posts'];
195 }
196
197 // ai/template_import is posted to the WDesignKit Next.js frontend API
198 // (WDKIT_SERVER_SITE_URL . 'next/api/v2/'), not the Laravel backend — mirrors
199 // class-wdkit-import-temp-ajax.php::wkit_generate_ai_content() which uses 'frontside' mode.
200 if (!defined('WDKIT_SERVER_SITE_URL')) {
201 return ['success' => false, 'message' => 'WDesignKit plugin core not loaded.'];
202 }
203
204 $response = wp_remote_post(
205 WDKIT_SERVER_SITE_URL . 'next/api/v2/ai/template_import',
206 [
207 'method' => 'POST',
208 'body' => wp_json_encode($payload),
209 'headers' => ['Content-Type' => 'application/json'],
210 'timeout' => 120,
211 ]
212 );
213
214 if (is_wp_error($response)) {
215 return ['success' => false, 'message' => $response->get_error_message()];
216 }
217
218 $code = wp_remote_retrieve_response_code($response);
219 $body = wp_remote_retrieve_body($response);
220 $data = json_decode($body, true);
221
222 if (200 !== (int) $code) {
223 return [
224 'success' => false,
225 'message' => 'WDesignKit AI endpoint returned status ' . $code,
226 'response' => wdesignkit_mcp_ensure_object($data, $body),
227 ];
228 }
229
230 return [
231 'success' => (bool) ($data['success'] ?? !empty($data)),
232 'message' => $data['message'] ?? $data['massage'] ?? '',
233 'response' => wdesignkit_mcp_ensure_object($data ?: null, $body),
234 ];
235 }
236
237 /**
238 * Recursively walk a decoded template JSON array and collect non-empty string leaves.
239 * These become the text_array for the AI rewrite endpoint.
240 *
241 * @param mixed $node Current node (array or scalar).
242 * @param string[] $out Accumulator for string values.
243 * @param int $depth Guard against pathologically deep trees.
244 */
245 function wdesignkit_mcp_ai_extract_strings($node, array &$out, int $depth = 0): void {
246 if ($depth > 20) {
247 return;
248 }
249 if (is_string($node)) {
250 $trimmed = trim(wp_strip_all_tags($node));
251 if ($trimmed !== '' && strlen($trimmed) >= 2) {
252 $out[] = $trimmed;
253 }
254 return;
255 }
256 if (is_array($node)) {
257 // Skip known non-text keys that hold code / IDs / URLs.
258 static $skip_keys = ['id', 'elType', 'widgetType', 'version', '__globals__', 'url', 'src', 'href'];
259 foreach ($node as $key => $val) {
260 if (in_array($key, $skip_keys, true)) {
261 continue;
262 }
263 wdesignkit_mcp_ai_extract_strings($val, $out, $depth + 1);
264 }
265 }
266 }
267