From 16b9f38e013d375ade41a4ab8f366bc5a3eb2cd6 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 26 Aug 2024 16:29:26 -0600 Subject: [PATCH 1/2] Use on-device AI to predict links users are most likely to visit for prerendering --- plugins/speculation-rules/hooks.php | 18 ++++++++++ plugins/speculation-rules/predict.js | 54 ++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 plugins/speculation-rules/predict.js diff --git a/plugins/speculation-rules/hooks.php b/plugins/speculation-rules/hooks.php index fa478b9843..0fbac8cc0f 100644 --- a/plugins/speculation-rules/hooks.php +++ b/plugins/speculation-rules/hooks.php @@ -38,3 +38,21 @@ function plsr_render_generator_meta_tag(): void { echo '' . "\n"; } add_action( 'wp_head', 'plsr_render_generator_meta_tag' ); + +/** + * Load the predict.js script which will uses on-device AI to predict links users are most likely to visit for prerendering. + * + * @since n.e.x.t + */ +function plsr_load_predict_script(): void { + wp_enqueue_script( + 'plsr-predict', + plugin_dir_url( __FILE__ ) . 'predict.js', // @todo switch to build version. + array( 'genai_bundle' ), + SPECULATION_RULES_VERSION, + array( + 'strategy' => 'defer', + ) + ); +} +add_action( 'wp_enqueue_scripts', 'plsr_load_predict_script' ); diff --git a/plugins/speculation-rules/predict.js b/plugins/speculation-rules/predict.js new file mode 100644 index 0000000000..14c738bb73 --- /dev/null +++ b/plugins/speculation-rules/predict.js @@ -0,0 +1,54 @@ +( async () => { + // Start the AI engine. + // eslint-disable-next-line no-console -- For testing only. + console.log( 'Starting...' ); + + async function predictLinks( { close } ) { + close(); + + const session = await window.ai.assistant.create(); + + const stream = session.promptStreaming( + `You will predict the links on a web page that visitors are most likely to visit. We will pre-render those links to make them load instantly for users. Please provide the five links users are most likely to visit as a JSON object. Here is the HTML: ${ document.documentElement.outerHTML }` + ); + + let result = ''; + + for await ( const value of stream ) { + result = value; + } + + // eslint-disable-next-line no-console -- For testing only. + console.log( result ); + + // Add a prerender link for each URL using the Speculation Rules API + const links = JSON.parse( result ); + + for ( const link of links ) { + /** + * Add speculation rules API prerendering. + * + * It will be something like this: + * + * + */ + const prerenderLink = document.createElement( 'link' ); + prerenderLink.type = 'speculationrules'; + const rules = + '{ "prerender": [ { "where": { "href_matches": "URL" }, "eagerness": "moderate" }] }'; + prerenderLink.textContent = rules.replace( 'URL', link ); + document.head.appendChild( prerenderLink ); + } + + close(); + } + await predictLinks(); +} )(); From 5bbb6e5914adc3a052f4de355d2f6af70212d6ff Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 29 Aug 2024 13:04:20 -0600 Subject: [PATCH 2/2] continue work on link preload prediction --- plugins/speculation-rules/hooks.php | 2 +- plugins/speculation-rules/predict.js | 71 ++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/plugins/speculation-rules/hooks.php b/plugins/speculation-rules/hooks.php index 0fbac8cc0f..43bf5f1b9b 100644 --- a/plugins/speculation-rules/hooks.php +++ b/plugins/speculation-rules/hooks.php @@ -48,7 +48,7 @@ function plsr_load_predict_script(): void { wp_enqueue_script( 'plsr-predict', plugin_dir_url( __FILE__ ) . 'predict.js', // @todo switch to build version. - array( 'genai_bundle' ), + array(), SPECULATION_RULES_VERSION, array( 'strategy' => 'defer', diff --git a/plugins/speculation-rules/predict.js b/plugins/speculation-rules/predict.js index 14c738bb73..3e1306032a 100644 --- a/plugins/speculation-rules/predict.js +++ b/plugins/speculation-rules/predict.js @@ -3,28 +3,70 @@ // eslint-disable-next-line no-console -- For testing only. console.log( 'Starting...' ); - async function predictLinks( { close } ) { - close(); - + async function predictLinks() { const session = await window.ai.assistant.create(); - const stream = session.promptStreaming( - `You will predict the links on a web page that visitors are most likely to visit. We will pre-render those links to make them load instantly for users. Please provide the five links users are most likely to visit as a JSON object. Here is the HTML: ${ document.documentElement.outerHTML }` - ); + const body = document.getElementsByTagName( 'body' )[ 0 ].innerHTML; + + //const prompt = `Predict the five links users are most likely to visit on this page, returning ONLY the 5 full urls as a JSON object: ${ body }`; + // const prompt = `Given a web page's HTML, find the links and predict which is most likely to be clicked by a user. Return the top 5 links as a JSON object. Only return the JSON object as your complete answer. Here is the HTML: ${ body }`; + const prompt = `In order to prerender links so users get instant navigations, can you predict the three links users are most likely to visit on this page, returning ONLY the 3 full urls as a JSON object in the format ['url','url2','url3']. Here is the HTML: ${ body }`; + + // eslint-disable-next-line no-console -- For testing only. + console.log( `The size of the prompt is ${ prompt.length }.` ); + + let result = false; + try { + result = await session.prompt( prompt ); + } catch ( error ) { + // eslint-disable-next-line no-console -- For testing only. + console.error( error ); + return; + } + + if ( ! result ) { + // eslint-disable-next-line no-console -- For testing only. + console.log( 'No result.' ); + return; + } + + // Log result so far + // eslint-disable-next-line no-console -- For testing only. + console.log( result ); - let result = ''; + // Grab everything after "Output:" or "```json" strings etc... + const output = + result.split( 'Output:' )[ 1 ] || + result.split( '```json' )[ 1 ] || + result.split( 'Answer:' )[ 1 ] || + result.split( 'Solution:' )[ 1 ]; - for await ( const value of stream ) { - result = value; + // If there is no output, return. + if ( ! output ) { + // eslint-disable-next-line no-console -- For testing only. + console.log( 'No output.' ); + return; } + // Remove the two "```" formatting strings. + result = output.replace( /```JSON/g, '' ); + result = output.replace( /```/g, '' ); + + // Remove any newlines. + result = result.replace( /\n/g, '' ); + + // Remove any whitespace. + result = result.replace( / /g, '' ); + // eslint-disable-next-line no-console -- For testing only. console.log( result ); // Add a prerender link for each URL using the Speculation Rules API - const links = JSON.parse( result ); + const resultData = JSON.parse( result ); - for ( const link of links ) { + for ( const link of resultData.links ) { + // eslint-disable-next-line no-console -- For testing only. + console.log( `Link: ${ link }` ); /** * Add speculation rules API prerendering. * @@ -44,11 +86,12 @@ prerenderLink.type = 'speculationrules'; const rules = '{ "prerender": [ { "where": { "href_matches": "URL" }, "eagerness": "moderate" }] }'; - prerenderLink.textContent = rules.replace( 'URL', link ); + prerenderLink.textContent = rules.replace( 'URL', link.href ); document.head.appendChild( prerenderLink ); - } - close(); + // eslint-disable-next-line no-console -- For testing only. + console.log( `Prerendering link: ${ link }` ); + } } await predictLinks(); } )();