Plugin Directory

Changeset 3437036


Ignore:
Timestamp:
01/11/2026 11:55:52 AM (3 months ago)
Author:
talkgenai
Message:

Initial release v2.4.2

Location:
talkgenai/trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • talkgenai/trunk/admin/js/admin.js

    r3423014 r3437036  
    37703770        const $resultArea = $('#article-result-area');
    37713771        const $content = $('#article-content');
     3772        const appUrl = ($('#app_url').length ? ($('#app_url').val() || '') : '');
    37723773       
    37733774        // Show loading state with progress indicator
     
    38013802                app_id: appId,
    38023803                length: length,
    3803                 instructions: instructions
     3804                instructions: instructions,
     3805                app_url: appUrl
    38043806            },
    38053807            success: function(response) {
  • talkgenai/trunk/admin/js/article-job-integration.js

    r3406312 r3437036  
    114114                let appDescription = '';
    115115                let appSpec = null;  // Declare outside try block for later use
     116                let appHtml = '';    // Optional - used for SoftwareApplication schema generation (premium)
    116117               
    117118                try {
     
    134135                    appTitle = (meta && (meta.title || meta.name || meta.app_title)) || '';
    135136                    appDescription = (meta && (meta.description || meta.app_description || meta.summary)) || '';
     137                    appHtml = (data && data.html_content) ? data.html_content : '';
    136138
    137139                    // If description still missing, try extracting a short summary from html_content
     
    199201                const articleLength = $('#article_length').val() || 'medium';
    200202                const additionalInstructions = $('#article_instructions').val() || '';
     203                const appPageUrl = $('#app_url').val() || '';  // Get app URL for schema generation (premium)
    201204               
    202205                // Disable generate button
     
    209212                    article_length: articleLength,
    210213                    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
    212217                };
    213218                try { console.log('TalkGenAI_ArticleJob: createJob payload', inputData); } catch (e) {}
     
    245250                       
    246251                        // 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                        }
    248259                    },
    249260                   
  • talkgenai/trunk/includes/class-talkgenai-admin.php

    r3413653 r3437036  
    15641564                                    </td>
    15651565                                </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>
    15661582                            </table>
    15671583                           
     
    37403756            $app_description = $app['description'] ?? 'A useful web application';
    37413757            $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();
    37423762           
    37433763            // Parse JSON spec if it's a string
     
    37533773                $app_spec,
    37543774                $length,
    3755                 $instructions
     3775                $instructions,
     3776                $app_url,
     3777                $internal_link_candidates
    37563778            );
    37573779           
  • talkgenai/trunk/includes/class-talkgenai-api.php

    r3410898 r3437036  
    245245     * Generate article for an app via server API
    246246     */
    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()) {
    248248        $config = $this->get_server_config();
    249249        if (is_wp_error($config)) {
     
    264264            'ai_provider' => 'anthropic'  // Default AI provider for article generation
    265265        );
     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        }
    266291       
    267292        if ($app_spec) {
  • talkgenai/trunk/includes/talkgenai-functions.php

    r3401250 r3437036  
    255255
    256256/**
     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 */
     265function 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/**
    257410 * Get plugin settings with defaults
    258411 */
  • talkgenai/trunk/readme.txt

    r3423023 r3437036  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 2.4.1
     7Stable tag: 2.4.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    181181
    182182== 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)
    183187
    184188= 2.4.1 - 2025-12-18 =
  • talkgenai/trunk/talkgenai.php

    r3423023 r3437036  
    44 * Plugin URI: https://app.talkgen.ai
    55 * Description: Generate complete web applications using AI. Connect to TalkGenAI server for intelligent app generation with WordPress integration.
    6  * Version: 2.4.1
     6 * Version: 2.4.2
    77 * Author: TalkGenAI Team
    88 * License: GPLv2 or later
     
    5656
    5757// Define plugin constants
    58 define('TALKGENAI_VERSION', '2.4.1');
     58define('TALKGENAI_VERSION', '2.4.2');
    5959define('TALKGENAI_PLUGIN_URL', plugin_dir_url(__FILE__));
    6060define('TALKGENAI_PLUGIN_PATH', plugin_dir_path(__FILE__));
     
    844844        // Article integration JavaScript - ENABLED (async article generation)
    845845        $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();
    847848        wp_enqueue_script(
    848849            'talkgenai-article-job',
     
    14571458            $input_data = is_array($input_data_raw) ? $this->sanitize_input_data_array($input_data_raw) : array();
    14581459        }
     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        }
    14591473       
    14601474        // Debug logging removed for WordPress.org compliance (no unsanitized array data in logs)
Note: See TracChangeset for help on using the changeset viewer.