> */ function aisubu_get_providers() { $providers = array( 'chatgpt' => array( 'label' => __( 'ChatGPT', 'ai-summarise-button' ), 'base' => 'https://chatgpt.com/?hints=search&q=', 'query' => true, ), 'perplexity' => array( 'label' => __( 'Perplexity', 'ai-summarise-button' ), 'base' => 'https://www.perplexity.ai/?q=', 'query' => true, ), 'copilot' => array( 'label' => __( 'Copilot', 'ai-summarise-button' ), 'base' => 'https://copilot.microsoft.com/?q=', 'query' => true, ), 'claude' => array( 'label' => __( 'Claude', 'ai-summarise-button' ), 'base' => 'https://claude.ai/new', 'query' => false, ), 'gemini' => array( 'label' => __( 'Gemini', 'ai-summarise-button' ), 'base' => 'https://gemini.google.com/app', 'query' => false, ), ); /** * Filters the available AI tools. * * @param array $providers Slug => array( label, base, query ). */ return apply_filters( 'aisubu_providers', $providers ); } /** * Destination URL for a provider. * * @param string $provider Provider slug. * @param string $q Prompt. * @return string */ function aisubu_provider_url( $provider, $q ) { $providers = aisubu_get_providers(); $definition = isset( $providers[ $provider ] ) ? $providers[ $provider ] : $providers['chatgpt']; $url = $definition['query'] ? $definition['base'] . rawurlencode( $q ) : $definition['base']; /** * Filters the destination URL for a provider. * * @param string $url Destination URL. * @param string $provider Provider slug. * @param string $q Prompt. */ return apply_filters( 'aisubu_provider_url', $url, $provider, $q ); } /** * Builds the prompt sent to the AI tool. * * @param string $title Page title. * @param string $url Page URL. * @param array $attrs Prompt parts: qPrefix, noun, qSuffix. * @return string */ function aisubu_build_q( $title, $url, $attrs ) { $prefix = isset( $attrs['qPrefix'] ) ? $attrs['qPrefix'] : 'Read and summarise this'; $noun = isset( $attrs['noun'] ) ? $attrs['noun'] : 'page'; $suffix = isset( $attrs['qSuffix'] ) ? $attrs['qSuffix'] : 'so I can ask questions about it.'; $title = trim( wp_strip_all_tags( (string) $title ) ); $q = sprintf( '%s "%s" %s from %s %s', $prefix, $title, $noun, $url, $suffix ); /** * Filters the prompt. * * @param string $q Prompt. * @param string $title Page title. * @param string $url Page URL. */ return apply_filters( 'aisubu_prompt', $q, $title, $url ); } /** * Default block attributes. * * @return array */ function aisubu_get_defaults() { return array( 'mode' => 'basic', 'buttonText' => 'Summarise with AI', 'qPrefix' => 'Read and summarise this', 'qSuffix' => 'so I can ask questions about it.', 'noun' => 'page', 'provider' => 'chatgpt', 'bgColor' => '', 'textColor' => '', 'borderColor' => '', 'borderRadius' => 8, 'paddingX' => 10, 'paddingY' => 6, 'isFullWidth' => false, 'iconPosition' => 'left', 'justify' => 'left', 'logoSize' => 15, 'maxWidth' => 200, 'maxHeight' => 32, 'preset' => 'chatgpt', 'openInNewTab' => true, 'relNoFollow' => true, 'align' => '', ); } /** * Colour presets. * * @return array> */ function aisubu_get_presets() { return array( 'chatgpt' => array( 'bg' => '#10A37F', 'fg' => '#ffffff', 'bd' => '#0D8667', ), 'outline' => array( 'bg' => '#ffffff', 'fg' => '#111827', 'bd' => '#D1D5DB', ), 'dark' => array( 'bg' => '#111827', 'fg' => '#ffffff', 'bd' => '#1F2937', ), 'blue' => array( 'bg' => '#2563EB', 'fg' => '#ffffff', 'bd' => '#1E40AF', ), ); } /** * Accepts a hex, rgb() or rgba() colour; anything else falls back. * * @param string $colour Candidate colour. * @param string $fallback Value returned when the candidate is not valid. * @return string */ function aisubu_safe_colour( $colour, $fallback = '' ) { $colour = trim( (string) $colour ); if ( '' === $colour ) { return $fallback; } $hex = sanitize_hex_color( $colour ); if ( $hex ) { return $hex; } if ( preg_match( '/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*(0|1|0?\.\d+)\s*)?\)$/i', $colour ) ) { return $colour; } return $fallback; } /** * The inline sparkle logo. Uses currentColor so it follows the button text. * * @return string */ function aisubu_get_logo_svg() { return ''; } /** * Registers shared assets. Runs before block registration. * * @return void */ function aisubu_register_assets() { wp_register_style( 'aisubu-frontend', AISUBU_URL . 'inc/frontend.css', array(), AISUBU_VER ); wp_register_script( 'aisubu-frontend', AISUBU_URL . 'inc/frontend.js', array(), AISUBU_VER, array( 'strategy' => 'defer', 'in_footer' => true, ) ); wp_set_script_translations( 'aisubu-frontend', 'ai-summarise-button' ); $providers = array(); foreach ( aisubu_get_providers() as $slug => $definition ) { $providers[] = array( 'value' => $slug, 'label' => $definition['label'], 'base' => $definition['base'], 'query' => (bool) $definition['query'], ); } wp_localize_script( 'aisubu-frontend', 'aisubuData', array( 'providers' => $providers, /** * Filters which tools get the page text copied to the clipboard. * * @param array $slugs Provider slugs. */ 'copyFor' => apply_filters( 'aisubu_copy_page_for', array( 'perplexity', 'claude', 'gemini' ) ), 'i18n' => array( 'title' => __( 'Choose AI to summarise with', 'ai-summarise-button' ), 'hint' => __( 'For Perplexity, Claude and Gemini we’ll copy the page text for quick paste.', 'ai-summarise-button' ), 'cancel' => __( 'Cancel', 'ai-summarise-button' ), 'continue' => __( 'Continue', 'ai-summarise-button' ), 'copyFail' => __( 'Couldn’t copy; please paste manually.', 'ai-summarise-button' ), ), ) ); wp_register_script( 'aisubu-block', AISUBU_URL . 'block.js', array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-i18n' ), AISUBU_VER, true ); wp_set_script_translations( 'aisubu-block', 'ai-summarise-button' ); } add_action( 'init', 'aisubu_register_assets', 5 ); /** * Registers the block from block.json. * * @return void */ function aisubu_register_block() { register_block_type( AISUBU_PATH . 'block.json', array( 'render_callback' => 'aisubu_render_button' ) ); } add_action( 'init', 'aisubu_register_block' ); /** * Loads front-end styles in the editor too, so the preview matches. * * @return void */ function aisubu_editor_assets() { wp_enqueue_style( 'aisubu-frontend' ); } add_action( 'enqueue_block_editor_assets', 'aisubu_editor_assets' ); /** * Renders the button. Shared by the block and the shortcode. * * @param array $attributes Block attributes. * @return string */ function aisubu_render_button( $attributes ) { $a = wp_parse_args( is_array( $attributes ) ? $attributes : array(), aisubu_get_defaults() ); wp_enqueue_style( 'aisubu-frontend' ); $title = function_exists( 'wp_get_document_title' ) ? wp_get_document_title() : get_bloginfo( 'name' ); $url = home_url( '/' ); if ( is_singular() ) { $maybe_title = get_the_title(); if ( $maybe_title ) { $title = $maybe_title; } $maybe_url = get_permalink(); if ( $maybe_url ) { $url = $maybe_url; } } $q = aisubu_build_q( $title, $url, array( 'qPrefix' => sanitize_text_field( (string) $a['qPrefix'] ), 'qSuffix' => sanitize_text_field( (string) $a['qSuffix'] ), 'noun' => sanitize_text_field( (string) $a['noun'] ), ) ); // Colours: an explicit colour wins, otherwise the preset, otherwise ChatGPT green. $presets = aisubu_get_presets(); $preset = isset( $presets[ $a['preset'] ] ) ? $presets[ $a['preset'] ] : $presets['chatgpt']; $bg = aisubu_safe_colour( $a['bgColor'] ); $fg = aisubu_safe_colour( $a['textColor'] ); $bd = aisubu_safe_colour( $a['borderColor'] ); if ( 'custom' !== $a['preset'] && ! $bg && ! $fg && ! $bd ) { $bg = $preset['bg']; $fg = $preset['fg']; $bd = $preset['bd']; } $bg = $bg ? $bg : '#10A37F'; $fg = $fg ? $fg : '#ffffff'; $bd = $bd ? $bd : 'rgba(0,0,0,.08)'; $radius = max( 0, min( 60, absint( $a['borderRadius'] ) ) ); $padding_x = max( 0, min( 60, absint( $a['paddingX'] ) ) ); $padding_y = max( 0, min( 60, absint( $a['paddingY'] ) ) ); $max_width = max( 60, min( 1200, absint( $a['maxWidth'] ) ) ); $max_height = max( 20, min( 120, absint( $a['maxHeight'] ) ) ); $logo_size = max( 8, min( 64, absint( $a['logoSize'] ) ) ); $mode = in_array( $a['mode'], array( 'basic', 'advanced' ), true ) ? $a['mode'] : 'basic'; $justify = in_array( $a['justify'], array( 'left', 'center', 'right' ), true ) ? $a['justify'] : 'left'; $icon_pos = 'right' === $a['iconPosition'] ? 'right' : 'left'; $provider = array_key_exists( $a['provider'], aisubu_get_providers() ) ? $a['provider'] : 'chatgpt'; $rel = $a['relNoFollow'] ? 'noopener nofollow' : 'noopener'; $target = $a['openInNewTab'] ? '_blank' : '_self'; $justify_content = 'flex-start'; $text_align = 'left'; $auto_margin = ''; if ( 'center' === $justify ) { $justify_content = 'center'; $text_align = 'center'; $auto_margin = 'margin-left:auto;margin-right:auto;'; } elseif ( 'right' === $justify ) { $justify_content = 'flex-end'; $text_align = 'right'; $auto_margin = 'margin-left:auto;'; } $wrap_style = 'width:100%;display:flex;align-items:center;gap:.5rem;margin:.5rem 0;justify-content:' . $justify_content . ';text-align:' . $text_align . ';'; $btn_style = $auto_margin . 'display:inline-flex;align-items:center;gap:0;' . 'border:1px solid ' . $bd . ';' . 'border-radius:' . $radius . 'px;' . 'background:' . $bg . ';color:' . $fg . ';' . 'text-decoration:none;line-height:1;' . 'max-width:' . $max_width . 'px;height:' . $max_height . 'px;' . 'padding:' . $padding_y . 'px ' . $padding_x . 'px;' . 'white-space:nowrap;overflow:hidden;'; if ( $a['isFullWidth'] ) { $btn_style .= 'width:100%;justify-content:center;'; } $icon_html = sprintf( '%3$s', 'right' === $icon_pos ? ' right' : '', $logo_size, aisubu_get_logo_svg() ); $label_html = sprintf( '%s', esc_html( sanitize_text_field( (string) $a['buttonText'] ) ) ); $inner = 'right' === $icon_pos ? $label_html . $icon_html : $icon_html . $label_html; if ( 'basic' === $mode ) { $button = sprintf( '%5$s', esc_attr( $btn_style ), esc_attr( $target ), esc_attr( $rel ), esc_url( aisubu_provider_url( 'chatgpt', $q ) ), $inner ); } else { $button = sprintf( '%6$s', esc_attr( $q ), esc_attr( $provider ), esc_attr( $btn_style ), esc_attr( $target ), esc_attr( $rel ), $inner ); wp_enqueue_script( 'aisubu-frontend' ); } return sprintf( '
%2$s
', esc_attr( $wrap_style ), $button ); } /** * Shortcode handler: [aisubu_summarise_button]. * * @param array|string $atts Shortcode attributes. * @return string */ function aisubu_shortcode( $atts ) { $atts = shortcode_atts( array( 'mode' => 'basic', 'text' => 'Summarise with AI', 'preset' => 'chatgpt', 'align' => 'left', 'width' => '200', 'height' => '32', 'logosize' => '15', 'qprefix' => 'Read and summarise this', 'qsuffix' => 'so I can ask questions about it.', 'noun' => 'page', 'provider' => 'chatgpt', 'newtab' => '1', 'nofollow' => '1', 'bgcolor' => '', 'textcolor' => '', 'bordercolor' => '', ), $atts, 'aisubu_summarise_button' ); return aisubu_render_button( array( 'mode' => $atts['mode'], 'buttonText' => $atts['text'], 'preset' => $atts['preset'], 'justify' => $atts['align'], 'maxWidth' => (int) $atts['width'], 'maxHeight' => (int) $atts['height'], 'logoSize' => (int) $atts['logosize'], 'qPrefix' => $atts['qprefix'], 'qSuffix' => $atts['qsuffix'], 'noun' => $atts['noun'], 'provider' => $atts['provider'], 'openInNewTab' => ( '1' === (string) $atts['newtab'] ), 'relNoFollow' => ( '1' === (string) $atts['nofollow'] ), 'bgColor' => $atts['bgcolor'], 'textColor' => $atts['textcolor'], 'borderColor' => $atts['bordercolor'], ) ); } add_shortcode( 'aisubu_summarise_button', 'aisubu_shortcode' );