Changeset 3437036
- Timestamp:
- 01/11/2026 11:55:52 AM (3 months ago)
- Location:
- talkgenai/trunk
- Files:
-
- 7 edited
-
admin/js/admin.js (modified) (2 diffs)
-
admin/js/article-job-integration.js (modified) (5 diffs)
-
includes/class-talkgenai-admin.php (modified) (3 diffs)
-
includes/class-talkgenai-api.php (modified) (2 diffs)
-
includes/talkgenai-functions.php (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
-
talkgenai.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
talkgenai/trunk/admin/js/admin.js
r3423014 r3437036 3770 3770 const $resultArea = $('#article-result-area'); 3771 3771 const $content = $('#article-content'); 3772 const appUrl = ($('#app_url').length ? ($('#app_url').val() || '') : ''); 3772 3773 3773 3774 // Show loading state with progress indicator … … 3801 3802 app_id: appId, 3802 3803 length: length, 3803 instructions: instructions 3804 instructions: instructions, 3805 app_url: appUrl 3804 3806 }, 3805 3807 success: function(response) { -
talkgenai/trunk/admin/js/article-job-integration.js
r3406312 r3437036 114 114 let appDescription = ''; 115 115 let appSpec = null; // Declare outside try block for later use 116 let appHtml = ''; // Optional - used for SoftwareApplication schema generation (premium) 116 117 117 118 try { … … 134 135 appTitle = (meta && (meta.title || meta.name || meta.app_title)) || ''; 135 136 appDescription = (meta && (meta.description || meta.app_description || meta.summary)) || ''; 137 appHtml = (data && data.html_content) ? data.html_content : ''; 136 138 137 139 // If description still missing, try extracting a short summary from html_content … … 199 201 const articleLength = $('#article_length').val() || 'medium'; 200 202 const additionalInstructions = $('#article_instructions').val() || ''; 203 const appPageUrl = $('#app_url').val() || ''; // Get app URL for schema generation (premium) 201 204 202 205 // Disable generate button … … 209 212 article_length: articleLength, 210 213 additional_instructions: additionalInstructions, 211 app_spec: appSpec // Include full app specification for AI customization 214 app_spec: appSpec, // Include full app specification for AI customization 215 app_url: appPageUrl, // Include app URL for schema generation (premium feature) 216 app_html: appHtml // Include app HTML so Python can generate SoftwareApplication schema 212 217 }; 213 218 try { console.log('TalkGenAI_ArticleJob: createJob payload', inputData); } catch (e) {} … … 245 250 246 251 // Success message 247 this.showNotification('Article generated successfully!', 'success'); 252 // Check if schemas were included (Premium feature for paid users) 253 const hasSchemas = result.html && result.html.includes('application/ld+json'); 254 if (hasSchemas) { 255 this.showNotification('✅ Article generated with SEO schemas! (Premium feature)', 'success'); 256 } else { 257 this.showNotification('Article generated successfully!', 'success'); 258 } 248 259 }, 249 260 -
talkgenai/trunk/includes/class-talkgenai-admin.php
r3413653 r3437036 1564 1564 </td> 1565 1565 </tr> 1566 1567 <!-- Schema Generation - Premium Feature --> 1568 <tr id="app-url-row"> 1569 <th scope="row"> 1570 <label for="app_url"> 1571 <?php esc_html_e('App Page URL', 'talkgenai'); ?> 1572 <span class="talkgenai-premium-badge" style="background: #ff6b00; color: white; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: bold; margin-left: 8px;">PREMIUM</span> 1573 </label> 1574 </th> 1575 <td> 1576 <input type="url" id="app_url" name="app_url" class="regular-text" placeholder="https://yourdomain.com/my-app-page/" /> 1577 <p class="description"> 1578 <?php esc_html_e('Optional - Premium users only: Enter the URL where this app will be published to automatically generate Google schemas (SoftwareApplication + FAQ) for better SEO.', 'talkgenai'); ?> 1579 </p> 1580 </td> 1581 </tr> 1566 1582 </table> 1567 1583 … … 3740 3756 $app_description = $app['description'] ?? 'A useful web application'; 3741 3757 $app_spec = $app['json_spec'] ?? null; 3758 $app_url = isset($_POST['app_url']) ? esc_url_raw(wp_unslash($_POST['app_url'])) : ''; 3759 $internal_link_candidates = function_exists('talkgenai_get_internal_link_candidates') 3760 ? talkgenai_get_internal_link_candidates($app_title, $app_description, 60) 3761 : array(); 3742 3762 3743 3763 // Parse JSON spec if it's a string … … 3753 3773 $app_spec, 3754 3774 $length, 3755 $instructions 3775 $instructions, 3776 $app_url, 3777 $internal_link_candidates 3756 3778 ); 3757 3779 -
talkgenai/trunk/includes/class-talkgenai-api.php
r3410898 r3437036 245 245 * Generate article for an app via server API 246 246 */ 247 public function generate_article($app_id, $app_title, $app_description, $app_spec = null, $article_length = 'medium', $additional_instructions = '' ) {247 public function generate_article($app_id, $app_title, $app_description, $app_spec = null, $article_length = 'medium', $additional_instructions = '', $app_url = '', $internal_link_candidates = array()) { 248 248 $config = $this->get_server_config(); 249 249 if (is_wp_error($config)) { … … 264 264 'ai_provider' => 'anthropic' // Default AI provider for article generation 265 265 ); 266 267 // Optional: app URL (used for schema generation / internal links context) 268 if (!empty($app_url)) { 269 $request_data['app_url'] = esc_url_raw($app_url); 270 } 271 272 // Optional: internal link candidates (WordPress posts/pages) 273 if (is_array($internal_link_candidates) && !empty($internal_link_candidates)) { 274 // Best-effort sanitize 275 $sanitized_candidates = array(); 276 foreach ($internal_link_candidates as $c) { 277 if (!is_array($c)) { continue; } 278 $u = isset($c['url']) ? esc_url_raw($c['url']) : ''; 279 if (empty($u)) { continue; } 280 $sanitized_candidates[] = array( 281 'title' => isset($c['title']) ? sanitize_text_field($c['title']) : '', 282 'url' => $u, 283 'type' => isset($c['type']) ? sanitize_text_field($c['type']) : '', 284 'excerpt' => isset($c['excerpt']) ? sanitize_textarea_field($c['excerpt']) : '' 285 ); 286 } 287 if (!empty($sanitized_candidates)) { 288 $request_data['internal_link_candidates'] = $sanitized_candidates; 289 } 290 } 266 291 267 292 if ($app_spec) { -
talkgenai/trunk/includes/talkgenai-functions.php
r3401250 r3437036 255 255 256 256 /** 257 * Build internal link candidates (WordPress posts/pages only) for AI enrichment. 258 * 259 * Returns an array of items: 260 * - title: string 261 * - url: string 262 * - type: 'post'|'page' 263 * - excerpt: string 264 */ 265 function talkgenai_get_internal_link_candidates($app_title, $app_description = '', $limit = 60) { 266 $limit = intval($limit); 267 if ($limit <= 0) { 268 $limit = 60; 269 } 270 if ($limit > 80) { 271 $limit = 80; // safety cap 272 } 273 274 $title = is_string($app_title) ? trim(wp_strip_all_tags($app_title)) : ''; 275 $desc = is_string($app_description) ? trim(wp_strip_all_tags($app_description)) : ''; 276 if ($title === '' && $desc === '') { 277 return array(); 278 } 279 280 // Simple keyword extraction from title/description (best-effort). 281 $text = strtolower($title . ' ' . $desc); 282 $words = preg_split('/[^a-z0-9]+/i', $text); 283 $stop = array('the','and','for','with','from','that','this','your','you','are','was','were','will','have','has','can','app','web','site','website','page','pages','post','posts','tool','tools','use','using','how','what','why','when','where','about','into','over','under','more','most','less','than','then','them','they','their','ours','ourselves','it','its','a','an','to','of','in','on','at','by','as','is','be','or'); 284 $freq = array(); 285 foreach ($words as $w) { 286 $w = trim($w); 287 if ($w === '' || strlen($w) < 4) { continue; } 288 if (in_array($w, $stop, true)) { continue; } 289 $freq[$w] = isset($freq[$w]) ? $freq[$w] + 1 : 1; 290 } 291 arsort($freq); 292 $top = array_slice(array_keys($freq), 0, 8); 293 $keyword_query = implode(' ', $top); 294 295 $search_queries = array(); 296 if ($title !== '') { $search_queries[] = $title; } 297 if ($keyword_query !== '' && $keyword_query !== $title) { $search_queries[] = $keyword_query; } 298 299 $seen = array(); 300 $out = array(); 301 302 foreach ($search_queries as $q) { 303 if (count($out) >= $limit) { break; } 304 $q = trim($q); 305 if ($q === '') { continue; } 306 307 $wpq = new WP_Query(array( 308 's' => $q, 309 'post_type' => array('post', 'page'), 310 'post_status' => 'publish', 311 'posts_per_page' => min(40, $limit), 312 'fields' => 'ids', 313 'no_found_rows' => true, 314 'ignore_sticky_posts' => true, 315 )); 316 317 if (empty($wpq->posts) || !is_array($wpq->posts)) { 318 continue; 319 } 320 321 foreach ($wpq->posts as $post_id) { 322 if (count($out) >= $limit) { break; } 323 $post_id = intval($post_id); 324 if ($post_id <= 0) { continue; } 325 if (isset($seen[$post_id])) { continue; } 326 $seen[$post_id] = true; 327 328 $post = get_post($post_id); 329 if (!$post) { continue; } 330 if ($post->post_status !== 'publish') { continue; } 331 332 $permalink = get_permalink($post_id); 333 if (!$permalink) { continue; } 334 335 $ptype = get_post_type($post_id); 336 $excerpt = ''; 337 if (!empty($post->post_excerpt)) { 338 $excerpt = $post->post_excerpt; 339 } else { 340 $excerpt = $post->post_content; 341 } 342 $excerpt = wp_trim_words(wp_strip_all_tags($excerpt), 24, '…'); 343 344 $out[] = array( 345 'title' => get_the_title($post_id), 346 'url' => $permalink, 347 'type' => $ptype === 'page' ? 'page' : 'post', 348 'excerpt' => $excerpt, 349 ); 350 } 351 } 352 353 // Fallback: if we found too few via search, expand candidate pool with recent posts/pages. 354 // This helps the LLM find 3–6 relevant internal links even when WP search is sparse. 355 $min_pool = 30; 356 if (count($out) < $min_pool && count($out) < $limit) { 357 $remaining_needed = $min_pool - count($out); 358 $fetch = min($limit - count($out), max(0, $remaining_needed)); 359 if ($fetch > 0) { 360 $wpq_recent = new WP_Query(array( 361 'post_type' => array('post', 'page'), 362 'post_status' => 'publish', 363 'posts_per_page' => min(80, $fetch), 364 'fields' => 'ids', 365 'no_found_rows' => true, 366 'ignore_sticky_posts' => true, 367 'orderby' => 'date', 368 'order' => 'DESC', 369 )); 370 371 if (!empty($wpq_recent->posts) && is_array($wpq_recent->posts)) { 372 foreach ($wpq_recent->posts as $post_id) { 373 if (count($out) >= $limit) { break; } 374 $post_id = intval($post_id); 375 if ($post_id <= 0) { continue; } 376 if (isset($seen[$post_id])) { continue; } 377 $seen[$post_id] = true; 378 379 $post = get_post($post_id); 380 if (!$post) { continue; } 381 if ($post->post_status !== 'publish') { continue; } 382 383 $permalink = get_permalink($post_id); 384 if (!$permalink) { continue; } 385 386 $ptype = get_post_type($post_id); 387 $excerpt = ''; 388 if (!empty($post->post_excerpt)) { 389 $excerpt = $post->post_excerpt; 390 } else { 391 $excerpt = $post->post_content; 392 } 393 $excerpt = wp_trim_words(wp_strip_all_tags($excerpt), 24, '…'); 394 395 $out[] = array( 396 'title' => get_the_title($post_id), 397 'url' => $permalink, 398 'type' => $ptype === 'page' ? 'page' : 'post', 399 'excerpt' => $excerpt, 400 ); 401 } 402 } 403 } 404 } 405 406 return $out; 407 } 408 409 /** 257 410 * Get plugin settings with defaults 258 411 */ -
talkgenai/trunk/readme.txt
r3423023 r3437036 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 2.4. 17 Stable tag: 2.4.2 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 181 181 182 182 == Changelog == 183 184 = 2.4.2 - 2026-01-11 = 185 * ✨ **Article Generation (Premium)**: Improved article generation context by sending the app URL and a curated pool of internal post/page link candidates to the TalkGenAI service (enables smarter internal/external linking + schema on supported servers) 186 * 🧹 **Performance**: Removed exclusionary `post__not_in` usage from internal link candidate fallback query (dedupe is handled in PHP) 183 187 184 188 = 2.4.1 - 2025-12-18 = -
talkgenai/trunk/talkgenai.php
r3423023 r3437036 4 4 * Plugin URI: https://app.talkgen.ai 5 5 * Description: Generate complete web applications using AI. Connect to TalkGenAI server for intelligent app generation with WordPress integration. 6 * Version: 2.4. 16 * Version: 2.4.2 7 7 * Author: TalkGenAI Team 8 8 * License: GPLv2 or later … … 56 56 57 57 // Define plugin constants 58 define('TALKGENAI_VERSION', '2.4. 1');58 define('TALKGENAI_VERSION', '2.4.2'); 59 59 define('TALKGENAI_PLUGIN_URL', plugin_dir_url(__FILE__)); 60 60 define('TALKGENAI_PLUGIN_PATH', plugin_dir_path(__FILE__)); … … 844 844 // Article integration JavaScript - ENABLED (async article generation) 845 845 $article_integration_js = TALKGENAI_PLUGIN_PATH . 'admin/js/article-job-integration.js'; 846 $article_integration_version = file_exists($article_integration_js) ? filemtime($article_integration_js) : TALKGENAI_VERSION; 846 // Strong cache busting (admin pages are sensitive to stale JS during development) 847 $article_integration_version = '2.3.SCHEMA_' . time(); 847 848 wp_enqueue_script( 848 849 'talkgenai-article-job', … … 1457 1458 $input_data = is_array($input_data_raw) ? $this->sanitize_input_data_array($input_data_raw) : array(); 1458 1459 } 1460 1461 // Add internal link candidates for article jobs (posts/pages only), unless already provided 1462 if ($job_type === 'article') { 1463 $has_candidates = isset($input_data['internal_link_candidates']) && is_array($input_data['internal_link_candidates']) && !empty($input_data['internal_link_candidates']); 1464 if (!$has_candidates && function_exists('talkgenai_get_internal_link_candidates')) { 1465 $t = isset($input_data['app_title']) ? $input_data['app_title'] : (isset($input_data['title']) ? $input_data['title'] : ''); 1466 $d = isset($input_data['app_description']) ? $input_data['app_description'] : (isset($input_data['description']) ? $input_data['description'] : ''); 1467 $cands = talkgenai_get_internal_link_candidates($t, $d, 60); 1468 if (is_array($cands) && !empty($cands)) { 1469 $input_data['internal_link_candidates'] = $cands; 1470 } 1471 } 1472 } 1459 1473 1460 1474 // Debug logging removed for WordPress.org compliance (no unsanitized array data in logs)
Note: See TracChangeset
for help on using the changeset viewer.