User:JSherman (WMF)/revertrisk.js

// /* * Add a portlet link on diff pages that: *  - fetches the language agnostic revert risk score *  - displays it in a Codex dialog * See: https://meta.wikimedia.org/wiki/Machine_learning_models/Proposed/Language-agnostic_revert_risk * @author: JSherman (WMF) */ ( function {	// Define the Vue component in a variable because this is a single file	const rootComponent = {		name: 'DiffRevertRisk',		// Wrap the Vue template in a js template string because this is a		// JavaScript (.js) file, not a Vue Single File Component (.vue)		// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals		template: `						 Revert probability 				 Revision 				 Revert risk technical info 									Language-agnostic revert risk model card									Language-agnostic revert score object   `,		// Run in Vue 3 mode // See: https://www.mediawiki.org/wiki/Vue.js/Vue_3_migration compatConfig: { MODE: 3 },		compilerOptions: { whitespace: 'condense' },		props: { revId: { type: Number, required: true }, lang: { type: String, required: true } },		data: function { return { open: false, data: null }; },		methods: { // Fetch the language-agnostic revert risk for this revision fetchData: function { fetch( 'https://api.wikimedia.org/service/lw/inference/v1/models/revertrisk-language-agnostic:predict', {					method: 'POST',					// eslint-disable-next-line camelcase					body: JSON.stringify( { rev_id: this.revId, lang: this.lang } )				} ) .then( function ( response ) {						return response.json;					} ) .then( function ( inferenceData ) {						try {							// Get score if it exists							const score = ( ( ( ( inferenceData || {} ).output || {} ).probabilities || {} ).true );							// If there is a score, display it to 2 decimal points							// Otherwise display the 'detail' or 'error' response element							this.data = score ? score.toFixed( 2 ) : ( ( ( inferenceData || {} ).detail || inferenceData || {} ).error );						} catch ( error ) {							this.data = error;						}					}.bind( this ) ); }		},		mounted: function { // Get the toolbox const toolboxId = 'p-tb', toolbox = document.getElementById( toolboxId ); if ( !toolbox ) { return; }			// Create the portlet link const portletLink = mw.util.addPortletLink(				toolboxId,				'#',				'Get revert risk',				'user-jsherman-wmf-revertrisk-fetch'			); // Open the dialog and fetch the data (if necessary) on click portletLink.addEventListener( 'click', function ( e ) {				// Prevent scrolling that would normally occur with '#' anchor				e.preventDefault;				this.open = true;				// return early if we already have data for this revision				if ( this.data ) {					return;				}				this.fetchData;			}.bind( this ) ); },		setup: function { // A hacky way to use codex icons in user scripts is to just copy the svg for the icon // See: https://doc.wikimedia.org/codex/latest/icons/all-icons.html const cdxIconInfo = ' </g> '; return { cdxIconInfo }; }	};	// The list of supported projects as of 2023-10-18 function validLang( lang ) { if ( !lang ) { return false; }		return [ 'aa', 'ab', 'ace', 'ady', 'af', 'ak', 'als', 'alt', 'am', 'ami', 'an', 'ang', 'anp', 'ar', 'arc', 'ary', 'arz', 'as', 'ast', 'atj', 'av', 'avk', 'awa', 'ay', 'az', 'azb', 'ba', 'ban', 'bar', 'bat-smg', 'bcl', 'be', 'be-tarask', 'be-x-old', 'bg', 'bh', 'bi', 'bjn', 'blk', 'bm', 'bn', 'bo', 'bpy', 'br', 'bs', 'bug', 'bxr', 'ca', 'cbk-zam', 'cdo', 'ce', 'ceb', 'ch', 'cho', 'chr', 'chy', 'ckb', 'co', 'cr', 'crh', 'cs', 'csb', 'cu', 'cv', 'cy', 'da', 'dag', 'de', 'din', 'diq', 'dsb', 'dty', 'dv', 'dz', 'ee', 'el', 'eml', 'en', 'eo', 'es', 'et', 'eu', 'ext', 'fa', 'fat', 'ff', 'fi', 'fiu-vro', 'fj', 'fo', 'fr', 'frp', 'frr', 'fur', 'fy', 'ga', 'gag', 'gan', 'gcr', 'gd', 'gl', 'glk', 'gn', 'gom', 'gor', 'got', 'gpe', 'gsw', 'gu', 'guc', 'gur', 'guw', 'gv', 'ha', 'hak', 'haw', 'he', 'hi', 'hif', 'ho', 'hr', 'hsb', 'ht', 'hu', 'hy', 'hyw', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'ilo', 'inh', 'io', 'is', 'it', 'iu', 'ja', 'jam', 'jbo', 'jv', 'ka', 'kaa', 'kab', 'kbd', 'kbp', 'kcg', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'koi', 'kr', 'krc', 'ks', 'ksh', 'ku', 'kv', 'kw', 'ky', 'la', 'lad', 'lb', 'lbe', 'lez', 'lfn', 'lg', 'li', 'lij', 'lld', 'lmo', 'ln', 'lo', 'lrc', 'lt', 'ltg', 'lv', 'lzh', 'mad', 'mai', 'map-bms', 'mdf', 'mg', 'mh', 'mhr', 'mi', 'min', 'mk', 'ml', 'mn', 'mni', 'mnw', 'mr', 'mrj', 'ms', 'mt', 'mus', 'mwl', 'my', 'myv', 'mzn', 'na', 'nah', 'nan', 'nap', 'nds', 'nds-nl', 'ne', 'new', 'ng', 'nia', 'nl', 'nn', 'no', 'nostalgia', 'nov', 'nqo', 'nrm', 'nso', 'nv', 'ny', 'oc', 'olo', 'om', 'or', 'os', 'pa', 'pag', 'pam', 'pap', 'pcd', 'pcm', 'pdc', 'pfl', 'pi', 'pih', 'pl', 'pms', 'pnb', 'pnt', 'ps', 'pt', 'pwn', 'qu', 'rm', 'rmy', 'rn', 'ro', 'roa-rup', 'roa-tara', 'ru', 'rue', 'rup', 'rw', 'sa', 'sah', 'sat', 'sc', 'scn', 'sco', 'sd', 'se', 'sg', 'sgs', 'sh', 'shi', 'shn', 'si', 'simple', 'sk', 'skr', 'sl', 'sm', 'smn', 'sn', 'so', 'sq', 'sr', 'srn', 'ss', 'st', 'stq', 'su', 'sv', 'sw', 'szl', 'szy', 'ta', 'tay', 'tcy', 'te', 'test', 'test2', 'tet', 'tg', 'th', 'ti', 'tk', 'tl', 'tly', 'tn', 'to', 'tpi', 'tr', 'trv', 'ts', 'tt', 'tum', 'tw', 'ty', 'tyv', 'udm', 'ug', 'uk', 'ur', 'uz', 've', 'vec', 'vep', 'vi', 'vls', 'vo', 'vro', 'wa', 'war', 'wo', 'wuu', 'xal', 'xh', 'xmf', 'yi', 'yo', 'yue', 'za', 'zea', 'zh', 'zh-classical', 'zh-min-nan', 'zh-yue', 'zu' ].indexOf( lang ) !== -1; }	function init { // Get the diff revision const revId = parseInt( mw.util.getParamValue( 'oldid' ) ); if ( !revId || Number.isNaN( revId ) ) { return; }		// Get the page content const content = document.getElementById( 'mw-content-text' ); if ( !content ) { return; }		// Get the wiki servername language code const lang = mw.config.get( 'wgServerName' ).split( '.' )[ 0 ]; if ( !validLang( lang ) ) { return; }		// Insert a container in which we'll mount the app const container = document.createElement( 'div' ); container.setAttribute( 'id', '#user-jsherman-wmf-revertrisk-dialog' ); content.insertBefore( container, content.firstChild ); mw.loader.using(			[				'vue',				'@wikimedia/codex'			],			// Register codex components and mount App			function {				const { CdxButton, CdxDialog, CdxIcon } = mw.loader.require( '@wikimedia/codex' );				const { createMwApp } = mw.loader.require( 'vue' );				createMwApp( rootComponent, { revId, lang } )					.component( 'CdxButton', CdxButton )					.component( 'CdxDialog', CdxDialog )					.component( 'CdxIcon', CdxIcon )					.mount( container );			}		); }	mw.hook( 'wikipage.content' ).add( init ); } ); //