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
53 changes: 48 additions & 5 deletions src/ui/panels/violation/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,20 @@ export default (detectionType: CliScanType) => `
const resetAiElements = () => {
if (isAiEnabled) {
showElement('ai-remediation-btn');
resetButton('ai-remediation-btn');
} else {
hideElement('ai-remediation-btn');
}

hideElement('ai-apply-fix-btn');
resetButton('ai-apply-fix-btn');

hideElement('ai-remediation');
ge('ai-remediation-text').innerText = 'None';
ge('ai-remediation-diff').innerText = '';
}

const renderAiRemediation = (remediation, unifyDiff, isFixAvailable) => {
isFixAvailable = false; // disable fix for now; not ready for production

hideElement('ai-remediation-btn');
ge('ai-remediation-text').innerHTML = remediation;
showElement('ai-remediation');
Expand Down Expand Up @@ -113,10 +114,52 @@ export default (detectionType: CliScanType) => `
showElement('ai-remediation-diff');
};

const registerAiButtonCallbacks = () => {
ge('ai-remediation-btn').onclick = () => {
vscode.postMessage({ command: 'getAiRemediation', uniqueDetectionId });
const resetButton = (buttonClassName) => {
const button = ge(buttonClassName);
if (button && button._originalText !== undefined) {
button.disabled = button._originalDisabled;
button.innerText = button._originalText;
}
};

const registerButton = (buttonClassName, commandId, inProgressText, additionalData = {}) => {
const button = ge(buttonClassName);
if (!button) return;

// Store the original state in private fields for later restoration
button._originalText = button.innerText;
button._originalDisabled = button.disabled;

button.onclick = () => {
button.disabled = true;
if (inProgressText !== undefined) {
button.innerText = inProgressText;
}

const messageData = { command: commandId, ...additionalData };
if (uniqueDetectionId) {
messageData.uniqueDetectionId = uniqueDetectionId;
}

vscode.postMessage(messageData);
};

const messageHandler = event => {
if (event.data.command === commandId && event.data.finished !== undefined) {
button.disabled = button._originalDisabled;
button.innerText = button._originalText;
}
};
window.addEventListener('message', messageHandler);

button._cleanupHandler = () => {
window.removeEventListener('message', messageHandler);
};
};

const registerAiButtonCallbacks = () => {
registerButton('ai-remediation-btn', 'getAiRemediation', 'Generating...');
registerButton('ai-apply-fix-btn', 'applyAiSuggestedFix', 'Applying...');
}
</script>
${detectionType === CliScanType.Sca ? scaRenderer : ''}
Expand Down
88 changes: 68 additions & 20 deletions src/ui/panels/violation/violation-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,39 +135,85 @@ const _extractAiRemediationParts = (remediation: string) => {
return undefined;
};

const _sendCommandCompletionMessage = async (panel: vscode.WebviewPanel, command: string, success: boolean) => {
const logger = container.resolve<ILoggerService>(LoggerServiceSymbol);
const sendRes = await panel.webview.postMessage({
command: command,
finished: success,
});
if (!sendRes) {
logger.error(`Failed to send command completion message for ${command}`);
}
};

const _getAiRemediationHandler = async (panel: vscode.WebviewPanel, uniqueDetectionId: string) => {
const scanResultsService = container.resolve<IScanResultsService>(ScanResultsServiceSymbol);
const detection = scanResultsService.getDetectionById(uniqueDetectionId);
if (!detection) {
await _sendCommandCompletionMessage(panel, 'getAiRemediation', false);
return;
}

const cycodeService = container.resolve<ICycodeService>(CycodeServiceSymbol);
const logger = container.resolve<ILoggerService>(LoggerServiceSymbol);
const aiRemediation = await cycodeService.getAiRemediation(detection.id);
if (!aiRemediation) {
logger.error('Failed to get AI remediation');
return;
}

let remediationMarkdown = aiRemediation.remediation;
let unifyDiff = undefined;
try {
const aiRemediation = await cycodeService.getAiRemediation(detection.id);
if (!aiRemediation) {
logger.error('Failed to get AI remediation');
await _sendCommandCompletionMessage(panel, 'getAiRemediation', false);
return;
}

let remediationMarkdown = aiRemediation.remediation;
let unifyDiff = undefined;

const remediationParts = _extractAiRemediationParts(aiRemediation.remediation);
if (remediationParts) {
remediationMarkdown = remediationParts.remediationMarkdown;
unifyDiff = remediationParts.unifyDiff;
}

const sendRes = await panel.webview.postMessage({
command: 'getAiRemediation',
finished: true,
aiRemediation: {
remediation: getMarkdownForRender(remediationMarkdown),
unifyDiff: unifyDiff,
isFixAvailable: aiRemediation.isFixAvailable,
},
});

const remediationParts = _extractAiRemediationParts(aiRemediation.remediation);
if (remediationParts) {
remediationMarkdown = remediationParts.remediationMarkdown;
unifyDiff = remediationParts.unifyDiff;
if (!sendRes) {
logger.error('Failed to send AI Remediation to render on the violation card');
}
} catch (error) {
logger.error(`Error in AI remediation handler: ${error}`);
await _sendCommandCompletionMessage(panel, 'getAiRemediation', false);
}
};

const sendRes = await panel.webview.postMessage({
aiRemediation: {
remediation: getMarkdownForRender(remediationMarkdown),
unifyDiff: unifyDiff,
isFixAvailable: aiRemediation.isFixAvailable,
},
});
if (!sendRes) {
logger.error('Failed to send AI Remediation to render on the violation card');
const _applyAiSuggestedFixHandler = async (panel: vscode.WebviewPanel, uniqueDetectionId: string) => {
const logger = container.resolve<ILoggerService>(LoggerServiceSymbol);

try {
/*
* TODO: Implement actual AI fix application when CLI command is ready
* For now, simulate the operation with a delay
*/
logger.debug(`[AI FIX] Start applying AI suggested fix for ${uniqueDetectionId}`);

// Simulate processing time
await new Promise((resolve) => setTimeout(resolve, 2000));

// For now, just log that the fix would be applied
logger.debug(`[AI FIX] Finish applying AI suggested fix for ${uniqueDetectionId}`);

// Send the success completion message
await _sendCommandCompletionMessage(panel, 'applyAiSuggestedFix', true);
} catch (error) {
logger.error(`Error in AI fix handler: ${error}`);
await _sendCommandCompletionMessage(panel, 'applyAiSuggestedFix', false);
}
};

Expand All @@ -179,6 +225,8 @@ const _getOnDidReceiveMessage = (panel: vscode.WebviewPanel, onLoadResolve: (val

if (message.command == 'getAiRemediation') {
await _getAiRemediationHandler(panel, message.uniqueDetectionId);
} else if (message.command == 'applyAiSuggestedFix') {
await _applyAiSuggestedFixHandler(panel, message.uniqueDetectionId);
} else if (message.command == 'ready') {
_readyCommandHandler(onLoadResolve);
} else if (message.command.startsWith('ignore')) {
Expand Down