|
|
|
|
@ -0,0 +1,330 @@
|
|
|
|
|
(() => {
|
|
|
|
|
const API_ENDPOINT = 'http://localhost:4001/api/code/evaluate';
|
|
|
|
|
const qualityTextMap = {
|
|
|
|
|
excellent: '优秀',
|
|
|
|
|
good: '良好',
|
|
|
|
|
average: '一般',
|
|
|
|
|
poor: '需改进',
|
|
|
|
|
};
|
|
|
|
|
const qualityStyleMap = {
|
|
|
|
|
excellent: { background: 'rgba(0, 255, 0, 0.2)', border: '1px solid rgba(0, 255, 0, 0.3)', color: '#66ff99' },
|
|
|
|
|
good: { background: 'rgba(0, 212, 255, 0.2)', border: '1px solid rgba(0, 212, 255, 0.3)', color: '#00d4ff' },
|
|
|
|
|
average: { background: 'rgba(255, 165, 0, 0.2)', border: '1px solid rgba(255, 165, 0, 0.3)', color: '#ffcc66' },
|
|
|
|
|
poor: { background: 'rgba(255, 0, 0, 0.2)', border: '1px solid rgba(255, 0, 0, 0.3)', color: '#ff6b6b' },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const dashboardCtor = globalThis && globalThis.OpenRankDashboard ? globalThis.OpenRankDashboard : undefined;
|
|
|
|
|
if (!dashboardCtor) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { document } = globalThis;
|
|
|
|
|
|
|
|
|
|
const escapeHtml = (value) => {
|
|
|
|
|
if (typeof value !== 'string') return '';
|
|
|
|
|
return value
|
|
|
|
|
.replaceAll('&', '&')
|
|
|
|
|
.replaceAll('<', '<')
|
|
|
|
|
.replaceAll('>', '>')
|
|
|
|
|
.replaceAll('"', '"')
|
|
|
|
|
.replaceAll("'", ''');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getLanguageLabel = (language) => {
|
|
|
|
|
const key = (language || '').toLowerCase();
|
|
|
|
|
if (key === 'c') return 'C';
|
|
|
|
|
if (key === 'cpp' || key === 'c++' || key === 'cxx') return 'C++';
|
|
|
|
|
return 'Python';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getToolLabel = (tool) => {
|
|
|
|
|
const key = (tool || '').toLowerCase();
|
|
|
|
|
if (key === 'cppcheck') return 'Cppcheck';
|
|
|
|
|
if (key === 'pylint') return 'Pylint';
|
|
|
|
|
return tool || '未知工具';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const applyQualityStyles = (element, level) => {
|
|
|
|
|
if (!element) return;
|
|
|
|
|
const styles = qualityStyleMap[level] || qualityStyleMap.average;
|
|
|
|
|
element.style.background = styles.background;
|
|
|
|
|
element.style.border = styles.border;
|
|
|
|
|
element.style.color = styles.color;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const normalizeLineEndings = (text) => {
|
|
|
|
|
if (typeof text !== 'string') return '';
|
|
|
|
|
return text.replaceAll('\r\n', '\n');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const renderScoreQuality = (scoreElement, qualityElement, score, qualityKey, qualityText) => {
|
|
|
|
|
if (scoreElement) scoreElement.textContent = score.toFixed(1);
|
|
|
|
|
if (!qualityElement) return;
|
|
|
|
|
qualityElement.textContent = qualityText;
|
|
|
|
|
qualityElement.className = `score-quality ${qualityKey}`;
|
|
|
|
|
applyQualityStyles(qualityElement, qualityKey);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const buildIssuesMarkup = (issues) => {
|
|
|
|
|
return issues.map((issue) => {
|
|
|
|
|
let label = '信息';
|
|
|
|
|
if (issue.type === 'error') label = '错误';
|
|
|
|
|
else if (issue.type === 'warning') label = '警告';
|
|
|
|
|
const ruleTag = issue.rule ? `<span style="font-size:0.75rem;opacity:0.65;">${escapeHtml(issue.rule)}</span>` : '';
|
|
|
|
|
return `
|
|
|
|
|
<div class="issue-item">
|
|
|
|
|
<div class="issue-header">
|
|
|
|
|
<span class="issue-type ${escapeHtml(issue.type || 'info')}">${label}</span>
|
|
|
|
|
<span class="issue-line">第 ${(issue.line || 0)} 行</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="issue-message">${escapeHtml(issue.message || '检测到潜在问题')}</div>
|
|
|
|
|
${ruleTag ? `<div>${ruleTag}</div>` : ''}
|
|
|
|
|
</div>`;
|
|
|
|
|
}).join('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const renderSummary = (summaryElement, summaryInfo) => {
|
|
|
|
|
if (!summaryElement) return;
|
|
|
|
|
const {
|
|
|
|
|
score,
|
|
|
|
|
qualityText,
|
|
|
|
|
languageLabel,
|
|
|
|
|
toolLabel,
|
|
|
|
|
runtimeLabel,
|
|
|
|
|
errors,
|
|
|
|
|
warnings,
|
|
|
|
|
infos,
|
|
|
|
|
lineCount,
|
|
|
|
|
} = summaryInfo;
|
|
|
|
|
|
|
|
|
|
summaryElement.innerHTML = `
|
|
|
|
|
<ul>
|
|
|
|
|
<li><i class="fas fa-check-circle"></i> 代码评分: ${score.toFixed(1)}/10 (${qualityText})</li>
|
|
|
|
|
<li><i class="fas fa-microchip"></i> 分析语言: ${languageLabel}</li>
|
|
|
|
|
<li><i class="fas fa-robot"></i> 分析工具: ${toolLabel}</li>
|
|
|
|
|
<li><i class="fas fa-tachometer-alt"></i> 检测耗时: ${runtimeLabel}</li>
|
|
|
|
|
<li><i class="fas fa-exclamation-circle"></i> 错误: ${errors} 项</li>
|
|
|
|
|
<li><i class="fas fa-exclamation-triangle"></i> 警告: ${warnings} 项</li>
|
|
|
|
|
<li><i class="fas fa-info-circle"></i> 建议: ${infos} 项</li>
|
|
|
|
|
<li><i class="fas fa-code"></i> 代码行数: ${lineCount}</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p style="margin-top: 1rem;">
|
|
|
|
|
${score >= 7 ? '代码整体质量良好,继续保持!' : '建议根据提示逐项优化代码质量。'}
|
|
|
|
|
</p>`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const exampleByLanguage = {
|
|
|
|
|
c: `#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
int sum(const int *values, int length) {
|
|
|
|
|
if (values == NULL || length <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
int total = 0;
|
|
|
|
|
for (int i = 0; i < length; ++i) {
|
|
|
|
|
total += values[i];
|
|
|
|
|
}
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(void) {
|
|
|
|
|
int data[] = {1, 2, 3, 4, 5};
|
|
|
|
|
printf("total = %d\n", sum(data, 5));
|
|
|
|
|
return 0;
|
|
|
|
|
}`,
|
|
|
|
|
cpp: `#include <iostream>
|
|
|
|
|
#include <numeric>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
double average(const std::vector<int>& scores) {
|
|
|
|
|
if (scores.empty()) {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
int total = std::accumulate(scores.begin(), scores.end(), 0);
|
|
|
|
|
return static_cast<double>(total) / scores.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
std::vector<int> scores{85, 90, 78, 92, 88};
|
|
|
|
|
std::cout << "Average score: " << average(scores) << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}`,
|
|
|
|
|
python: `def calculate_average(numbers):
|
|
|
|
|
"""计算数字列表的平均值"""
|
|
|
|
|
if not numbers:
|
|
|
|
|
return 0
|
|
|
|
|
total = sum(numbers)
|
|
|
|
|
return total / len(numbers)
|
|
|
|
|
|
|
|
|
|
def get_user_name():
|
|
|
|
|
name = input("请输入您的名字: ")
|
|
|
|
|
print(f"你好, {name}!")
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
user = get_user_name()
|
|
|
|
|
scores = [85, 90, 78, 92, 88]
|
|
|
|
|
avg = calculate_average(scores)
|
|
|
|
|
print(f"平均分数是: {avg}")`,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getExampleCode = (language) => {
|
|
|
|
|
const normalized = (language || '').toLowerCase();
|
|
|
|
|
if (normalized === 'c') return exampleByLanguage.c;
|
|
|
|
|
if (normalized === 'cpp' || normalized === 'c++' || normalized === 'cxx') return exampleByLanguage.cpp;
|
|
|
|
|
return exampleByLanguage.python;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const syncExampleIfUsingTemplate = (language, force = false) => {
|
|
|
|
|
const codeInput = document.getElementById('python-code-input');
|
|
|
|
|
if (!codeInput) return;
|
|
|
|
|
const nextExample = getExampleCode(language);
|
|
|
|
|
const isTemplate = codeInput.dataset.isTemplate === '1';
|
|
|
|
|
const currentValue = normalizeLineEndings(codeInput.value);
|
|
|
|
|
const sampleContent = normalizeLineEndings(codeInput.dataset.sampleContent || '');
|
|
|
|
|
const hasUserContent = currentValue.length > 0 && (!isTemplate && currentValue !== sampleContent);
|
|
|
|
|
if (!force && hasUserContent) return;
|
|
|
|
|
codeInput.value = nextExample;
|
|
|
|
|
codeInput.dataset.sampleLanguage = language;
|
|
|
|
|
codeInput.dataset.sampleContent = normalizeLineEndings(nextExample);
|
|
|
|
|
codeInput.dataset.isTemplate = '1';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const originalSetup = dashboardCtor.prototype.setupCodeReviewPage;
|
|
|
|
|
|
|
|
|
|
dashboardCtor.prototype.setupCodeReviewPage = function codeReviewSetupOverride() {
|
|
|
|
|
if (typeof originalSetup === 'function') {
|
|
|
|
|
originalSetup.call(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const languageSelect = document.getElementById('code-language-select');
|
|
|
|
|
const codeInput = document.getElementById('python-code-input');
|
|
|
|
|
codeInput?.addEventListener('input', () => {
|
|
|
|
|
codeInput.dataset.isTemplate = '0';
|
|
|
|
|
});
|
|
|
|
|
languageSelect?.addEventListener('change', () => {
|
|
|
|
|
const nextLanguage = languageSelect.value;
|
|
|
|
|
syncExampleIfUsingTemplate(nextLanguage);
|
|
|
|
|
this.resetCodeReviewResults();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 初次挂载时,如果文本框为空则填充默认示例(Python)
|
|
|
|
|
syncExampleIfUsingTemplate(languageSelect?.value || 'python');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dashboardCtor.prototype.loadExampleCode = function loadExampleCodeOverride() {
|
|
|
|
|
const language = document.getElementById('code-language-select')?.value || 'python';
|
|
|
|
|
syncExampleIfUsingTemplate(language, true);
|
|
|
|
|
this.resetCodeReviewResults();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dashboardCtor.prototype.resetCodeReviewResults = function resetCodeReviewResultsOverride() {
|
|
|
|
|
const scoreElement = document.getElementById('code-score');
|
|
|
|
|
const qualityElement = document.getElementById('score-quality');
|
|
|
|
|
const issuesCount = document.getElementById('issues-count');
|
|
|
|
|
const issuesList = document.getElementById('issues-list');
|
|
|
|
|
const summary = document.getElementById('lint-summary');
|
|
|
|
|
|
|
|
|
|
if (scoreElement) scoreElement.textContent = '0';
|
|
|
|
|
if (qualityElement) {
|
|
|
|
|
qualityElement.textContent = '待评分';
|
|
|
|
|
qualityElement.className = 'score-quality';
|
|
|
|
|
qualityElement.style.background = '';
|
|
|
|
|
qualityElement.style.border = '';
|
|
|
|
|
qualityElement.style.color = '';
|
|
|
|
|
}
|
|
|
|
|
if (issuesCount) issuesCount.textContent = '0';
|
|
|
|
|
if (issuesList) issuesList.innerHTML = '<p class="empty-message">请运行测评查看代码中的问题</p>';
|
|
|
|
|
if (summary) summary.innerHTML = '<p class="empty-message">请运行测评查看代码质量总结</p>';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dashboardCtor.prototype.runCodeLinting = function runCodeLintingOverride() {
|
|
|
|
|
const codeInput = document.getElementById('python-code-input');
|
|
|
|
|
const languageSelect = document.getElementById('code-language-select');
|
|
|
|
|
const language = languageSelect?.value || 'python';
|
|
|
|
|
if (!codeInput || !codeInput.value.trim()) {
|
|
|
|
|
this.showNotification('请先输入代码再运行测评', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.resetCodeReviewResults();
|
|
|
|
|
this.submitCodeForAnalysis(language, codeInput.value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dashboardCtor.prototype.submitCodeForAnalysis = async function submitCodeForAnalysis(language, code) {
|
|
|
|
|
const runButton = document.getElementById('run-lint-btn');
|
|
|
|
|
const originalLabel = runButton ? runButton.innerHTML : '';
|
|
|
|
|
if (runButton) {
|
|
|
|
|
runButton.disabled = true;
|
|
|
|
|
runButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 分析中...';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
this.showNotification('正在运行代码测评...', 'info');
|
|
|
|
|
const response = await fetch(API_ENDPOINT, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({ language, code })
|
|
|
|
|
});
|
|
|
|
|
const payload = await response.json();
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const message = payload?.message || '代码测评失败';
|
|
|
|
|
throw new Error(message);
|
|
|
|
|
}
|
|
|
|
|
this.displayLintResults(payload);
|
|
|
|
|
this.showNotification('代码测评完成', 'success');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : '未知错误';
|
|
|
|
|
this.showNotification(`代码测评失败: ${message}`, 'negative');
|
|
|
|
|
} finally {
|
|
|
|
|
if (runButton) {
|
|
|
|
|
runButton.disabled = false;
|
|
|
|
|
runButton.innerHTML = originalLabel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dashboardCtor.prototype.displayLintResults = function displayLintResultsOverride(result) {
|
|
|
|
|
const score = typeof result?.score === 'number' ? result.score : 0;
|
|
|
|
|
const qualityKey = result?.quality || 'average';
|
|
|
|
|
const qualityText = qualityTextMap[qualityKey] || qualityTextMap.average;
|
|
|
|
|
const issues = Array.isArray(result?.issues) ? [...result.issues] : [];
|
|
|
|
|
const totals = result?.totals || { errors: 0, warnings: 0, infos: 0 };
|
|
|
|
|
const scoreElement = document.getElementById('code-score');
|
|
|
|
|
const qualityElement = document.getElementById('score-quality');
|
|
|
|
|
const issuesCount = document.getElementById('issues-count');
|
|
|
|
|
const issuesList = document.getElementById('issues-list');
|
|
|
|
|
const summary = document.getElementById('lint-summary');
|
|
|
|
|
const lineCount = document.getElementById('python-code-input')?.value.split('\n').length || 0;
|
|
|
|
|
|
|
|
|
|
renderScoreQuality(scoreElement, qualityElement, score, qualityKey, qualityText);
|
|
|
|
|
|
|
|
|
|
if (issuesCount) issuesCount.textContent = issues.length.toString();
|
|
|
|
|
if (issuesList) {
|
|
|
|
|
if (issues.length === 0) {
|
|
|
|
|
issuesList.innerHTML = '<p class="empty-message">代码质量良好,未发现问题</p>';
|
|
|
|
|
} else {
|
|
|
|
|
issues.sort((a, b) => (a.line || 0) - (b.line || 0));
|
|
|
|
|
issuesList.innerHTML = buildIssuesMarkup(issues);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const languageLabel = getLanguageLabel(result?.language);
|
|
|
|
|
const toolLabel = getToolLabel(result?.tool);
|
|
|
|
|
const runtimeLabel = typeof result?.runtimeMs === 'number' ? `${Math.max(1, Math.round(result.runtimeMs))} ms` : '未知';
|
|
|
|
|
const errors = typeof totals.errors === 'number' ? totals.errors : issues.filter((item) => item.type === 'error').length;
|
|
|
|
|
const warnings = typeof totals.warnings === 'number' ? totals.warnings : issues.filter((item) => item.type === 'warning').length;
|
|
|
|
|
const infos = typeof totals.infos === 'number' ? totals.infos : issues.filter((item) => item.type === 'info').length;
|
|
|
|
|
|
|
|
|
|
renderSummary(summary, {
|
|
|
|
|
score,
|
|
|
|
|
qualityText,
|
|
|
|
|
languageLabel,
|
|
|
|
|
toolLabel,
|
|
|
|
|
runtimeLabel,
|
|
|
|
|
errors,
|
|
|
|
|
warnings,
|
|
|
|
|
infos,
|
|
|
|
|
lineCount,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
})();
|