Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions assets/css/plugin-check-admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,58 @@
}
}

/* AI Analysis Styles */
.plugin-check__ai-analysis {
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 8px;
padding: 6px 10px;
border-radius: 3px;
font-size: 0.9em;
line-height: 1.4;
}

.plugin-check__ai-analysis--false-positive {
background-color: #fff3cd;
border-left: 3px solid #ffc107;
color: #856404;
}

.plugin-check__ai-analysis--valid {
background-color: #d1ecf1;
border-left: 3px solid #17a2b8;
color: #0c5460;
}

.plugin-check__ai-analysis .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
}

.plugin-check__ai-reasoning {
display: block;
margin-top: 5px;
padding: 8px;
background-color: #f8f9fa;
border-left: 3px solid #6c757d;
font-size: 0.9em;
font-style: normal;
color: #495057;
line-height: 1.5;
}

.plugin-check__ai-recommendation {
display: block;
margin-top: 5px;
padding: 8px;
background-color: #e7f3ff;
border-left: 3px solid #0066cc;
font-size: 0.9em;
color: #004085;
line-height: 1.5;
}
.plugin-check__options {
display: flex;
}
Expand All @@ -70,6 +122,10 @@
margin-left: 40px;
}

.plugin-check__options #plugin-check__ai-container {
margin-left: 40px;
}

/* JSON output formatting */
#plugin-check-namer-raw {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
Expand Down
194 changes: 166 additions & 28 deletions assets/js/plugin-check-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
const includeExperimental = document.getElementById(
'plugin-check__include-experimental'
);
const useAi = document.getElementById( 'plugin-check__use-ai' );

// Handle disabling the Check it button when a plugin is not selected.
function canRunChecks() {
Expand Down Expand Up @@ -87,6 +88,12 @@
for ( let i = 0; i < typesList.length; i++ ) {
typesList[ i ].disabled = true;
}
if ( useAi ) {
useAi.disabled = true;
}
if ( includeExperimental ) {
includeExperimental.disabled = true;
}

getChecksToRun()
.then( setUpEnvironment )
Expand Down Expand Up @@ -133,6 +140,12 @@
for ( let i = 0; i < typesList.length; i++ ) {
typesList[ i ].disabled = false;
}
if ( useAi ) {
useAi.disabled = false;
}
if ( includeExperimental ) {
includeExperimental.disabled = false;
}
}

function createEmptyAggregatedResults() {
Expand Down Expand Up @@ -400,6 +413,10 @@
'include-experimental',
includeExperimental && includeExperimental.checked ? 1 : 0
);
pluginCheckData.append(

Check failure on line 416 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎↹↹↹'use-ai',⏎↹↹↹useAi·&&·useAi.checked·?·1·:·0⏎↹↹` with `·'use-ai',·useAi·&&·useAi.checked·?·1·:·0·`
'use-ai',
useAi && useAi.checked ? 1 : 0
);

for ( let i = 0; i < data.checks.length; i++ ) {
pluginCheckData.append( 'checks[]', data.checks[ i ] );
Expand Down Expand Up @@ -472,6 +489,10 @@
'include-experimental',
includeExperimental && includeExperimental.checked ? 1 : 0
);
pluginCheckData.append(

Check failure on line 492 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎↹↹↹'use-ai',⏎↹↹↹useAi·&&·useAi.checked·?·1·:·0⏎↹↹` with `·'use-ai',·useAi·&&·useAi.checked·?·1·:·0·`
'use-ai',
useAi && useAi.checked ? 1 : 0
);

for ( let i = 0; i < categoriesList.length; i++ ) {
if ( categoriesList[ i ].checked ) {
Expand Down Expand Up @@ -515,6 +536,7 @@
*/
async function runChecks( data ) {
let isSuccessMessage = true;
let aiStats = null;
for ( let i = 0; i < data.checks.length; i++ ) {
try {
const results = await runCheck( data.plugin, data.checks[ i ] );
Expand All @@ -528,12 +550,27 @@
}
mergeAggregatedResults( results );
renderResults( results );

// Collect AI stats from the last check.
if ( results.ai_stats ) {
// Merge stats if multiple checks.
if ( ! aiStats ) {
aiStats = {
tokens_spent: 0,
false_positives: 0,
issues_analyzed: 0,
};
}
aiStats.tokens_spent += results.ai_stats.tokens_spent || 0;
aiStats.false_positives += results.ai_stats.false_positives || 0;

Check failure on line 565 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `·` with `⏎↹↹↹↹↹↹`
aiStats.issues_analyzed += results.ai_stats.issues_analyzed || 0;

Check failure on line 566 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `·` with `⏎↹↹↹↹↹↹`
}
} catch {
// Ignore for now.
}
}

renderResultsMessage( isSuccessMessage );
renderResultsMessage( isSuccessMessage, aiStats );
}

/**
Expand All @@ -542,13 +579,24 @@
* @since 1.0.0
*
* @param {boolean} isSuccessMessage Whether the message is a success message.
* @param {Object} aiStats AI statistics.
*/
function renderResultsMessage( isSuccessMessage ) {
function renderResultsMessage( isSuccessMessage, aiStats ) {
const messageType = isSuccessMessage ? 'success' : 'error';
const messageText = isSuccessMessage
let messageText = isSuccessMessage
? pluginCheck.successMessage
: pluginCheck.errorMessage;

// Add AI statistics to the message if available.
if ( aiStats && aiStats.false_positives > 0 ) {
let aiInfo = ' AI detected ' + aiStats.false_positives + ' ';
aiInfo += ( 1 === aiStats.false_positives ) ? 'false positive' : 'false positives';

Check failure on line 593 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `·(·1·===·aiStats.false_positives·)·?·'false·positive'·` with `⏎↹↹↹↹1·===·aiStats.false_positives⏎↹↹↹↹↹?·'false·positive'⏎↹↹↹↹↹`
if ( aiStats.tokens_spent > 0 ) {
aiInfo += ' (Tokens spent: ' + aiStats.tokens_spent.toLocaleString() + ')';

Check failure on line 595 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `·'·(Tokens·spent:·'·+·aiStats.tokens_spent.toLocaleString()·+·` with `⏎↹↹↹↹↹'·(Tokens·spent:·'·+⏎↹↹↹↹↹aiStats.tokens_spent.toLocaleString()·+⏎↹↹↹↹↹`
}
messageText += '.' + aiInfo;
}

resultsContainer.innerHTML =
renderTemplate( 'plugin-check-results-complete', {
type: messageType,
Expand Down Expand Up @@ -578,6 +626,10 @@
'include-experimental',
includeExperimental && includeExperimental.checked ? 1 : 0
);
pluginCheckData.append(

Check failure on line 629 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎↹↹↹'use-ai',⏎↹↹↹useAi·&&·useAi.checked·?·1·:·0⏎↹↹` with `·'use-ai',·useAi·&&·useAi.checked·?·1·:·0·`
'use-ai',
useAi && useAi.checked ? 1 : 0
);

for ( let i = 0; i < typesList.length; i++ ) {
if ( typesList[ i ].checked ) {
Expand All @@ -600,6 +652,14 @@
throw new Error( 'Response contains no data' );
}

// Debug: Log AI data if present.
if ( responseData.data.ai_analysis ) {
console.log( 'AI Analysis received:', responseData.data.ai_analysis );

Check failure on line 657 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `·'AI·Analysis·received:',·responseData.data.ai_analysis·` with `⏎↹↹↹↹↹↹'AI·Analysis·received:',⏎↹↹↹↹↹↹responseData.data.ai_analysis⏎↹↹↹↹↹`
}
if ( responseData.data.ai_stats ) {
console.log( 'AI Stats received:', responseData.data.ai_stats );

Check failure on line 660 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Replace `·'AI·Stats·received:',·responseData.data.ai_stats·` with `⏎↹↹↹↹↹↹'AI·Stats·received:',⏎↹↹↹↹↹↹responseData.data.ai_stats⏎↹↹↹↹↹`
}

return responseData.data;
} );
}
Expand Down Expand Up @@ -638,20 +698,25 @@
* @param {Object} results The results object.
*/
function renderResults( results ) {
const { errors, warnings } = results;
const { errors, warnings, ai_analysis } = results || {};

Check failure on line 701 in assets/js/plugin-check-admin.js

View workflow job for this annotation

GitHub Actions / Lint

Identifier 'ai_analysis' is not in camel case

// Debug: Log AI analysis data if available.
if ( ai_analysis && typeof ai_analysis === 'object' && Object.keys( ai_analysis ).length > 0 ) {
console.log( 'AI Analysis data in renderResults:', ai_analysis );
}
// Render errors and warnings for files.
for ( const file in errors ) {
if ( warnings[ file ] ) {
renderFileResults( file, errors[ file ], warnings[ file ] );
renderFileResults( file, errors[ file ], warnings[ file ], ai_analysis );
delete warnings[ file ];
} else {
renderFileResults( file, errors[ file ], [] );
renderFileResults( file, errors[ file ], [], ai_analysis );
}
}

// Render remaining files with only warnings.
for ( const file in warnings ) {
renderFileResults( file, [], warnings[ file ] );
renderFileResults( file, [], warnings[ file ], ai_analysis );
}
}

Expand All @@ -660,11 +725,12 @@
*
* @since 1.0.0
*
* @param {string} file The file name for the results.
* @param {Object} errors The file errors.
* @param {Object} warnings The file warnings.
* @param {string} file The file name for the results.
* @param {Object} errors The file errors.
* @param {Object} warnings The file warnings.
* @param {Object} ai_analysis AI analysis results.
*/
function renderFileResults( file, errors, warnings ) {
function renderFileResults( file, errors, warnings, ai_analysis ) {
const index =
Date.now().toString( 36 ) +
Math.random().toString( 36 ).substr( 2 );
Expand All @@ -683,8 +749,8 @@
);

// Render results to the table.
renderResultRows( 'ERROR', errors, resultsTable, hasLinks );
renderResultRows( 'WARNING', warnings, resultsTable, hasLinks );
renderResultRows( 'ERROR', errors, resultsTable, hasLinks, ai_analysis, file );
renderResultRows( 'WARNING', warnings, resultsTable, hasLinks, ai_analysis, file );
}

/**
Expand Down Expand Up @@ -713,12 +779,14 @@
*
* @since 1.0.0
*
* @param {string} type The result type. Either ERROR or WARNING.
* @param {Object} results The results object.
* @param {Object} table The HTML table to append a result row to.
* @param {boolean} hasLinks Whether any result has links.
* @param {string} type The result type. Either ERROR or WARNING.
* @param {Object} results The results object.
* @param {Object} table The HTML table to append a result row to.
* @param {boolean} hasLinks Whether any result has links.
* @param {Object} ai_analysis AI analysis results.
* @param {string} file The file path.
*/
function renderResultRows( type, results, table, hasLinks ) {
function renderResultRows( type, results, table, hasLinks, ai_analysis, file ) {
// Loop over each result by the line, column and messages.
for ( const line in results ) {
for ( const column in results[ line ] ) {
Expand All @@ -728,24 +796,94 @@
const code = results[ line ][ column ][ i ].code;
const link = results[ line ][ column ][ i ].link;

// Find AI analysis for this issue.
let aiData = null;
if ( ai_analysis && typeof ai_analysis === 'object' ) {
// Try to find by file, line, column, and code match.
// ai_analysis is an object where keys are MD5 hashes and values are analysis data.
const analysisEntries = Object.values( ai_analysis );
aiData = analysisEntries.find( function( analysis ) {
if ( ! analysis || typeof analysis !== 'object' ) {
return false;
}
// Normalize values for comparison.
const analysisFile = String( analysis.file || '' );
const currentFile = String( file || '' );
const analysisLine = parseInt( analysis.line, 10 );
const currentLine = parseInt( line, 10 );
const analysisColumn = parseInt( analysis.column, 10 );
const currentColumn = parseInt( column, 10 );
const analysisCode = String( analysis.code || '' );
const currentCode = String( code || '' );

const fileMatch = analysisFile === currentFile;
const lineMatch = analysisLine === currentLine;
const columnMatch = analysisColumn === currentColumn;
const codeMatch = analysisCode === currentCode;

if ( fileMatch && lineMatch && columnMatch && codeMatch ) {
console.log( 'AI match found:', {
file: currentFile,
line: currentLine,
column: currentColumn,
code: currentCode,
analysis: analysis,
} );
return true;
}

return false;
} ) || null;
}

const rowData = {
line,
column,
type,
message,
docs,
code,
link,
hasLinks,
};

// Add AI analysis data if available.
if ( aiData ) {
rowData.ai_analysis = aiData;
}

table.innerHTML += renderTemplate(
'plugin-check-results-row',
{
line,
column,
type,
message,
docs,
code,
link,
hasLinks,
}
rowData
);
}
}
}
}

/**
* Generates a unique key for an issue.
*
* @since 1.8.0
*
* @param {string} file File path.
* @param {number} line Line number.
* @param {number} column Column number.
* @param {string} code Issue code.
* @return {string} Unique key.
*/
function getIssueKey( file, line, column, code ) {
const str = file + ':' + line + ':' + column + ':' + code;
// Simple MD5-like hash (using built-in hash if available, otherwise a simple hash).
let hash = 0;
for ( let i = 0; i < str.length; i++ ) {
const char = str.charCodeAt( i );
hash = ( hash << 5 ) - hash + char;
hash = hash & hash; // Convert to 32bit integer.
}
return hash.toString( 36 );
}

/**
* Renders the template with data.
*
Expand Down
Loading
Loading