code-gate 0.1.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +222 -114
- package/README_ZH.md +224 -0
- package/bin/code-gate.js +12 -19
- package/dist/__tests__/config.test.js +1 -1
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/render.test.js +24 -7
- package/dist/__tests__/render.test.js.map +1 -1
- package/dist/cli/commands/hook.d.ts +1 -0
- package/dist/cli/commands/hook.js +164 -0
- package/dist/cli/commands/hook.js.map +1 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.js +157 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +1 -0
- package/dist/cli/commands/setup.js +19 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/hook.js +7 -0
- package/dist/commands/hook.js.map +1 -1
- package/dist/commands/init.js +20 -20
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +33 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +77 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +55 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.d.ts +111 -21
- package/dist/config.js +83 -21
- package/dist/config.js.map +1 -1
- package/dist/core/git.d.ts +7 -0
- package/dist/core/git.js +67 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/review-flow.js +105 -59
- package/dist/core/review-flow.js.map +1 -1
- package/dist/core/review.d.ts +6 -0
- package/dist/core/review.js +167 -0
- package/dist/core/review.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/base.d.ts +14 -0
- package/dist/llm/base.js +10 -0
- package/dist/llm/base.js.map +1 -0
- package/dist/llm/deepseek.js +3 -3
- package/dist/llm/deepseek.js.map +1 -1
- package/dist/llm/index.d.ts +4 -0
- package/dist/llm/index.js +41 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/ollama.js +2 -2
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/providers/aliyun.d.ts +4 -0
- package/dist/llm/providers/aliyun.js +25 -0
- package/dist/llm/providers/aliyun.js.map +1 -0
- package/dist/llm/providers/anthropic.d.ts +4 -0
- package/dist/llm/providers/anthropic.js +52 -0
- package/dist/llm/providers/anthropic.js.map +1 -0
- package/dist/llm/providers/azure.d.ts +4 -0
- package/dist/llm/providers/azure.js +38 -0
- package/dist/llm/providers/azure.js.map +1 -0
- package/dist/llm/providers/cohere.d.ts +4 -0
- package/dist/llm/providers/cohere.js +39 -0
- package/dist/llm/providers/cohere.js.map +1 -0
- package/dist/llm/providers/deepseek.d.ts +4 -0
- package/dist/llm/providers/deepseek.js +25 -0
- package/dist/llm/providers/deepseek.js.map +1 -0
- package/dist/llm/providers/gemini.d.ts +4 -0
- package/dist/llm/providers/gemini.js +40 -0
- package/dist/llm/providers/gemini.js.map +1 -0
- package/dist/llm/providers/mistral.d.ts +4 -0
- package/dist/llm/providers/mistral.js +42 -0
- package/dist/llm/providers/mistral.js.map +1 -0
- package/dist/llm/providers/ollama.d.ts +5 -0
- package/dist/llm/providers/ollama.js +67 -0
- package/dist/llm/providers/ollama.js.map +1 -0
- package/dist/llm/providers/openai.d.ts +4 -0
- package/dist/llm/providers/openai.js +27 -0
- package/dist/llm/providers/openai.js.map +1 -0
- package/dist/llm/providers/volcengine.d.ts +4 -0
- package/dist/llm/providers/volcengine.js +29 -0
- package/dist/llm/providers/volcengine.js.map +1 -0
- package/dist/llm/providers/zhipu.d.ts +4 -0
- package/dist/llm/providers/zhipu.js +25 -0
- package/dist/llm/providers/zhipu.js.map +1 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/de.js +33 -0
- package/dist/locales/de.js.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.js +33 -0
- package/dist/locales/en.js.map +1 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/fr.js +33 -0
- package/dist/locales/fr.js.map +1 -0
- package/dist/locales/index.d.ts +3 -0
- package/dist/locales/index.js +41 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/locales/ja.d.ts +2 -0
- package/dist/locales/ja.js +33 -0
- package/dist/locales/ja.js.map +1 -0
- package/dist/locales/ko.d.ts +2 -0
- package/dist/locales/ko.js +33 -0
- package/dist/locales/ko.js.map +1 -0
- package/dist/locales/types.d.ts +32 -0
- package/dist/locales/types.js +2 -0
- package/dist/locales/types.js.map +1 -0
- package/dist/locales/zh-CN.d.ts +2 -0
- package/dist/locales/zh-CN.js +33 -0
- package/dist/locales/zh-CN.js.map +1 -0
- package/dist/locales/zh-TW.d.ts +2 -0
- package/dist/locales/zh-TW.js +33 -0
- package/dist/locales/zh-TW.js.map +1 -0
- package/dist/ui/render/assets.d.ts +8 -0
- package/dist/ui/render/assets.js +89 -0
- package/dist/ui/render/assets.js.map +1 -0
- package/dist/ui/render/html.d.ts +35 -0
- package/dist/ui/render/html.js +295 -0
- package/dist/ui/render/html.js.map +1 -0
- package/dist/ui/render.d.ts +11 -0
- package/dist/ui/render.js +198 -13
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/server.d.ts +4 -2
- package/dist/ui/server.js +30 -8
- package/dist/ui/server.js.map +1 -1
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +10 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +27 -8
- package/scripts/hook.sh +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/ui/render/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE9C,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAA;QAC7E,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAA;QAEzE,0DAA0D;QAC1D,sFAAsF;QACtF,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAChD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,CAAA;QAE3E,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAA;QACrE,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAA;QAE1E,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QACpD,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QAC1D,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAEhD,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QAClD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;QAEpD,OAAO;YACL,GAAG,EAAE,GAAG,MAAM,KAAK,OAAO,EAAE;YAC5B,EAAE,EAAE,GAAG,QAAQ,KAAK,WAAW,KAAK,KAAK,EAAE;SAC5C,CAAA;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAA;QACzC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAA;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,4l4NAA4l4N,CAAA;AAEvn4N,MAAM,CAAC,MAAM,UAAU,GAAG,quBAAquB,CAAA;AAE/vB,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;CAU5B,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CzB,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare function renderReviewItem(item: {
|
|
2
|
+
file: string;
|
|
3
|
+
review: string;
|
|
4
|
+
diff: string;
|
|
5
|
+
}): {
|
|
6
|
+
file: string;
|
|
7
|
+
reviewHtml: string;
|
|
8
|
+
diffHtml: any;
|
|
9
|
+
};
|
|
10
|
+
export declare function renderHTMLTabs(files: Array<{
|
|
11
|
+
file: string;
|
|
12
|
+
review: string;
|
|
13
|
+
diff: string;
|
|
14
|
+
}>, meta?: {
|
|
15
|
+
aiInvoked?: boolean;
|
|
16
|
+
aiSucceeded?: boolean;
|
|
17
|
+
provider?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
datetime?: string;
|
|
21
|
+
subtitle?: string;
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function renderHTMLLive(id: string, meta?: {
|
|
24
|
+
aiInvoked?: boolean;
|
|
25
|
+
aiSucceeded?: boolean;
|
|
26
|
+
provider?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
status?: string;
|
|
29
|
+
datetime?: string;
|
|
30
|
+
subtitle?: string;
|
|
31
|
+
}, initial?: Array<{
|
|
32
|
+
file: string;
|
|
33
|
+
review: string;
|
|
34
|
+
diff: string;
|
|
35
|
+
}>): string;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
import { JSDOM } from 'jsdom';
|
|
3
|
+
import createDOMPurify from 'dompurify';
|
|
4
|
+
import hljs from 'highlight.js';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { getAssets, CUSTOM_CSS, CLIENT_SCRIPT, LOGO_BASE64, GITHUB_SVG } from './assets.js';
|
|
7
|
+
import { t } from '../../locales/index.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
// Use the bundle directly to avoid issues with package.json "main" resolution in some environments
|
|
10
|
+
const { parse, html: d2hHtml } = require('diff2html/bundles/js/diff2html.min.js');
|
|
11
|
+
// Setup JSDOM for DOMPurify
|
|
12
|
+
const window = new JSDOM('').window;
|
|
13
|
+
const DOMPurify = createDOMPurify(window);
|
|
14
|
+
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
|
|
15
|
+
if (node.tagName === 'SPAN' && node.className && node.className.startsWith('hljs-')) {
|
|
16
|
+
// allow
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
// Setup Marked
|
|
20
|
+
marked.setOptions({
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
highlight: function (code, lang) {
|
|
23
|
+
if (lang === 'xml' || !lang) {
|
|
24
|
+
if (code.includes('import ') ||
|
|
25
|
+
code.includes('export ') ||
|
|
26
|
+
code.includes('const ') ||
|
|
27
|
+
code.includes('interface ') ||
|
|
28
|
+
(code.includes('<') && code.includes('/>'))) {
|
|
29
|
+
lang = 'tsx';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
33
|
+
try {
|
|
34
|
+
return hljs.highlight(code, { language: language }).value;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return code;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
function escapeHtml(s) {
|
|
42
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
43
|
+
}
|
|
44
|
+
export function renderReviewItem(item) {
|
|
45
|
+
const json = parse(item.diff, { inputFormat: 'diff' });
|
|
46
|
+
const diffHtml = d2hHtml(json, { showFiles: false, matching: 'lines' });
|
|
47
|
+
const reviewHtml = item.review
|
|
48
|
+
? DOMPurify.sanitize(marked.parse(item.review), { ADD_TAGS: ['span'], ADD_ATTR: ['class'] })
|
|
49
|
+
: `<div class="review-body empty">${t('ui.emptyReview')}</div>`;
|
|
50
|
+
return {
|
|
51
|
+
file: item.file,
|
|
52
|
+
reviewHtml,
|
|
53
|
+
diffHtml
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function getBaseTemplate(title, body, extraHead = '', extraScript = '') {
|
|
57
|
+
const { css } = getAssets();
|
|
58
|
+
return `<!doctype html>
|
|
59
|
+
<html>
|
|
60
|
+
<head>
|
|
61
|
+
<meta charset="utf-8">
|
|
62
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
63
|
+
<title>${title}</title>
|
|
64
|
+
<style>
|
|
65
|
+
${css}
|
|
66
|
+
${CUSTOM_CSS}
|
|
67
|
+
</style>
|
|
68
|
+
${extraHead}
|
|
69
|
+
</head>
|
|
70
|
+
<body>
|
|
71
|
+
<div class="container">
|
|
72
|
+
${body}
|
|
73
|
+
</div>
|
|
74
|
+
<script>
|
|
75
|
+
${CLIENT_SCRIPT}
|
|
76
|
+
${extraScript}
|
|
77
|
+
</script>
|
|
78
|
+
</body>
|
|
79
|
+
</html>`;
|
|
80
|
+
}
|
|
81
|
+
export function renderHTMLTabs(files, meta) {
|
|
82
|
+
const renderedFiles = files.map(renderReviewItem);
|
|
83
|
+
const tabs = renderedFiles
|
|
84
|
+
.map((f, i) => `<button type="button" class="tab" data-idx="${i}" title="${escapeHtml(f.file)}">${escapeHtml(f.file)}</button>`)
|
|
85
|
+
.join('');
|
|
86
|
+
const panes = renderedFiles
|
|
87
|
+
.map((f, i) => `
|
|
88
|
+
<div class="pane" data-idx="${i}">
|
|
89
|
+
<div class="split">
|
|
90
|
+
<div class="panel">
|
|
91
|
+
<div class="panel-title">${t('ui.panelAI')}</div>
|
|
92
|
+
<div class="review-body">${f.reviewHtml}</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="panel">
|
|
95
|
+
<div class="panel-title">${t('ui.panelDiff')}</div>
|
|
96
|
+
<div class="diff-body">${f.diffHtml}</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>`)
|
|
100
|
+
.join('');
|
|
101
|
+
let statusBadge = '';
|
|
102
|
+
if (!meta?.aiInvoked) {
|
|
103
|
+
statusBadge = `<span class="badge red">${t('ui.statusPending')}</span>`;
|
|
104
|
+
}
|
|
105
|
+
else if (meta?.aiInvoked && !meta?.aiSucceeded) {
|
|
106
|
+
statusBadge = `<span class="badge red">${t('ui.statusFailed')}</span>`;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// 静态页面生成时,肯定是“审核完毕”
|
|
110
|
+
statusBadge = `<span class="badge green">${t('ui.statusDone')}</span>`;
|
|
111
|
+
}
|
|
112
|
+
const badge = meta
|
|
113
|
+
? `<div class="meta">
|
|
114
|
+
${statusBadge}
|
|
115
|
+
${meta.provider ? `<span class="badge">Provider: ${escapeHtml(meta.provider)}</span>` : ''}
|
|
116
|
+
${meta.model ? `<span class="badge">Model: ${escapeHtml(meta.model)}</span>` : ''}
|
|
117
|
+
${meta.status ? `<div class="status">${escapeHtml(meta.status)}</div>` : ''}
|
|
118
|
+
</div>`
|
|
119
|
+
: '';
|
|
120
|
+
const header = `<div class="header-row">
|
|
121
|
+
<img src="${LOGO_BASE64}" class="logo" alt="Code Gate Logo" />
|
|
122
|
+
<h1>${t('ui.title')}</h1>
|
|
123
|
+
${meta?.subtitle ? `<div class="subtitle">${escapeHtml(meta.subtitle)}</div>` : ''}
|
|
124
|
+
</div>
|
|
125
|
+
<div class="top-right-area">
|
|
126
|
+
<a href="https://github.com/Gil2015/code-gate" target="_blank" class="github-link" aria-label="GitHub Repo">
|
|
127
|
+
${GITHUB_SVG}
|
|
128
|
+
</a>
|
|
129
|
+
${meta?.datetime ? `<div class="timestamp">${escapeHtml(meta.datetime)}</div>` : ''}
|
|
130
|
+
</div>
|
|
131
|
+
`;
|
|
132
|
+
const script = `
|
|
133
|
+
const tabs = document.querySelectorAll('.tab');
|
|
134
|
+
const panes = document.querySelectorAll('.pane');
|
|
135
|
+
function activate(i) {
|
|
136
|
+
tabs.forEach(t => t.classList.toggle('active', t.dataset.idx == i));
|
|
137
|
+
panes.forEach(p => p.classList.toggle('active', p.dataset.idx == i));
|
|
138
|
+
}
|
|
139
|
+
tabs.forEach(t => t.addEventListener('click', () => activate(t.dataset.idx)));
|
|
140
|
+
if(tabs.length > 0) activate(0);
|
|
141
|
+
`;
|
|
142
|
+
return getBaseTemplate(t('ui.title'), `${header}${badge}<div class="tabs">${tabs}</div><div class="panes">${panes}</div>`, '', script);
|
|
143
|
+
}
|
|
144
|
+
export function renderHTMLLive(id, meta, initial) {
|
|
145
|
+
// Logic for live template... similar structure but uses polling script
|
|
146
|
+
// Since we want to be robust, let's keep using CDN for the live page dependencies if we want, OR use the same bundled assets.
|
|
147
|
+
// The original live template used CDN for marked/diff2html etc because it rendered client side.
|
|
148
|
+
// But wait, our `renderReviewItem` logic is server side (in node).
|
|
149
|
+
// The live page receives JSON updates and renders them.
|
|
150
|
+
// If we want to keep the live page rendering client-side, we need to inject the libraries or bundle them.
|
|
151
|
+
// The previous implementation used CDNs for the live page. Let's stick to that for the live page to avoid complex bundling of JS libraries.
|
|
152
|
+
const initialJson = JSON.stringify(initial || []);
|
|
153
|
+
const initialB64 = Buffer.from(initialJson, 'utf8').toString('base64');
|
|
154
|
+
let statusBadge = '';
|
|
155
|
+
// 实时页面初始状态:
|
|
156
|
+
// 1. 如果 aiInvoked=true 但 aiSucceeded=false,说明可能一开始就挂了,显示红色失败
|
|
157
|
+
// 2. 否则,默认为正在审核(蓝色),因为 live 页面就是在审核过程中打开的
|
|
158
|
+
// 注意:我们不再完全依赖 meta.aiInvoked 的 false 状态来显示“未参与”,因为在 live 模式下,
|
|
159
|
+
// 初始调用时 aiInvoked 可能还没被设置为 true(取决于调用时机),但只要进了 live 页面,就是为了看 AI 结果。
|
|
160
|
+
if (meta?.aiInvoked && meta?.status && meta.status.includes('失败')) {
|
|
161
|
+
statusBadge = `<span class="badge red">${t('ui.statusFailed')}</span>`;
|
|
162
|
+
}
|
|
163
|
+
else if (meta?.aiInvoked === false && meta?.status && meta.status.includes('失败')) {
|
|
164
|
+
statusBadge = `<span class="badge red">${t('ui.statusPending')}</span>`;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
statusBadge = `<span class="badge blue" id="ai-status">${t('ui.statusProcessing')}</span>`;
|
|
168
|
+
}
|
|
169
|
+
const badge = meta
|
|
170
|
+
? `<div class="meta">
|
|
171
|
+
${statusBadge}
|
|
172
|
+
${meta.provider ? `<span class="badge">Provider: ${escapeHtml(meta.provider)}</span>` : ''}
|
|
173
|
+
${meta.model ? `<span class="badge">Model: ${escapeHtml(meta.model)}</span>` : ''}
|
|
174
|
+
${meta.status ? `<div class="status">${escapeHtml(meta.status)}</div>` : ''}
|
|
175
|
+
</div>`
|
|
176
|
+
: '';
|
|
177
|
+
const header = `<div class="header-row">
|
|
178
|
+
<img src="${LOGO_BASE64}" class="logo" alt="Code Gate Logo" />
|
|
179
|
+
<h1>${t('ui.title')}</h1>
|
|
180
|
+
${meta?.subtitle ? `<div class="subtitle">${escapeHtml(meta.subtitle)}</div>` : ''}
|
|
181
|
+
</div>
|
|
182
|
+
<div class="top-right-area">
|
|
183
|
+
<a href="https://github.com/Gil2015/code-gate" target="_blank" class="github-link" aria-label="GitHub Repo">
|
|
184
|
+
${GITHUB_SVG}
|
|
185
|
+
</a>
|
|
186
|
+
${meta?.datetime ? `<div class="timestamp">${escapeHtml(meta.datetime)}</div>` : ''}
|
|
187
|
+
</div>
|
|
188
|
+
`;
|
|
189
|
+
const head = `
|
|
190
|
+
<script>
|
|
191
|
+
${getAssets().js}
|
|
192
|
+
</script>
|
|
193
|
+
`;
|
|
194
|
+
const script = `
|
|
195
|
+
const I18N = {
|
|
196
|
+
statusDone: "${t('ui.statusDone')}",
|
|
197
|
+
emptyReview: "${t('ui.emptyReview')}",
|
|
198
|
+
panelAI: "${t('ui.panelAI')}",
|
|
199
|
+
panelDiff: "${t('ui.panelDiff')}"
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const route = '/review/${id}/status';
|
|
203
|
+
function _decodeB64Json(b){ try { return JSON.parse(decodeURIComponent(escape(atob(b)))); } catch(e){ return []; } }
|
|
204
|
+
const initial = _decodeB64Json('${initialB64}');
|
|
205
|
+
const tabsContainer = document.querySelector('.tabs');
|
|
206
|
+
const panesContainer = document.querySelector('.panes');
|
|
207
|
+
const known = new Set();
|
|
208
|
+
|
|
209
|
+
function activate(i){
|
|
210
|
+
[...tabsContainer.children].forEach((t,idx)=>t.classList.toggle('active',idx===i));
|
|
211
|
+
[...panesContainer.children].forEach((p,idx)=>p.classList.toggle('active',idx===i));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function addItem(item){
|
|
215
|
+
if(known.has(item.file)) return;
|
|
216
|
+
known.add(item.file);
|
|
217
|
+
const i = known.size - 1;
|
|
218
|
+
|
|
219
|
+
const t = document.createElement('button');
|
|
220
|
+
t.type = 'button';
|
|
221
|
+
t.className = 'tab';
|
|
222
|
+
t.title = item.file;
|
|
223
|
+
t.textContent = item.file;
|
|
224
|
+
t.addEventListener('click', () => activate(i));
|
|
225
|
+
tabsContainer.appendChild(t);
|
|
226
|
+
|
|
227
|
+
const p = document.createElement('div');
|
|
228
|
+
p.className = 'pane';
|
|
229
|
+
|
|
230
|
+
const reviewHtml = item.review ? DOMPurify.sanitize(marked.parse(item.review)) : '<div class="review-body empty">' + I18N.emptyReview + '</div>';
|
|
231
|
+
const parsed = window.Diff2Html.parse(item.diff, {inputFormat:'diff'});
|
|
232
|
+
const diffHtml = window.Diff2Html.html(parsed, { showFiles:false, matching:'lines' });
|
|
233
|
+
|
|
234
|
+
p.innerHTML = \`
|
|
235
|
+
<div class="split">
|
|
236
|
+
<div class="panel">
|
|
237
|
+
<div class="panel-title">\${I18N.panelAI}</div>
|
|
238
|
+
<div class="review-body">\${reviewHtml}</div>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="panel">
|
|
241
|
+
<div class="panel-title">\${I18N.panelDiff}</div>
|
|
242
|
+
<div class="diff-body">\${diffHtml}</div>
|
|
243
|
+
</div>
|
|
244
|
+
</div>\`;
|
|
245
|
+
panesContainer.appendChild(p);
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const el = p.querySelector('.review-body');
|
|
249
|
+
const blocks = el ? el.querySelectorAll('pre code') : [];
|
|
250
|
+
blocks.forEach((code) => {
|
|
251
|
+
if (code.classList.contains('language-tsx')) {
|
|
252
|
+
code.classList.remove('language-tsx');
|
|
253
|
+
code.classList.add('language-typescript');
|
|
254
|
+
}
|
|
255
|
+
hljs.highlightElement(code);
|
|
256
|
+
});
|
|
257
|
+
} catch(e){}
|
|
258
|
+
|
|
259
|
+
if(i === 0) activate(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function poll(){
|
|
263
|
+
try{
|
|
264
|
+
const res = await fetch(route);
|
|
265
|
+
if(!res.ok) {
|
|
266
|
+
const n = document.getElementById('ai-status');
|
|
267
|
+
if(n && known.size > 0) {
|
|
268
|
+
n.textContent = I18N.statusDone;
|
|
269
|
+
n.className = 'badge green';
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const data = await res.json();
|
|
274
|
+
const list = data && Array.isArray(data.files) ? data.files : [];
|
|
275
|
+
list.forEach(it => { if(it && it.done) addItem(it); });
|
|
276
|
+
if(data.done) {
|
|
277
|
+
const n = document.getElementById('ai-status');
|
|
278
|
+
if(n) {
|
|
279
|
+
n.textContent = I18N.statusDone;
|
|
280
|
+
n.className = 'badge green';
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
setTimeout(poll, 2000);
|
|
284
|
+
}
|
|
285
|
+
} catch(e) {
|
|
286
|
+
setTimeout(poll, 5000);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try { initial.forEach(addItem); } catch(e){}
|
|
291
|
+
poll();
|
|
292
|
+
`;
|
|
293
|
+
return getBaseTemplate(t('ui.title'), `${header}${badge}<div class="tabs"></div><div class="panes"></div>`, head, script);
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/ui/render/html.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/render.d.ts
CHANGED
|
@@ -16,3 +16,14 @@ export declare function renderHTMLTabs(files: Array<{
|
|
|
16
16
|
model?: string;
|
|
17
17
|
status?: string;
|
|
18
18
|
}): string;
|
|
19
|
+
export declare function renderHTMLLive(id: string, meta?: {
|
|
20
|
+
aiInvoked?: boolean;
|
|
21
|
+
aiSucceeded?: boolean;
|
|
22
|
+
provider?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
status?: string;
|
|
25
|
+
}, initial?: Array<{
|
|
26
|
+
file: string;
|
|
27
|
+
review: string;
|
|
28
|
+
diff: string;
|
|
29
|
+
}>): string;
|
package/dist/ui/render.js
CHANGED
|
@@ -18,6 +18,7 @@ export function renderHTML(diff, review, meta) {
|
|
|
18
18
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
19
19
|
<title>code-gate review</title>
|
|
20
20
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
|
21
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
21
22
|
<style>
|
|
22
23
|
body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
|
|
23
24
|
.container{max-width:1200px;margin:24px auto;padding:0 16px}
|
|
@@ -25,6 +26,7 @@ body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetic
|
|
|
25
26
|
.meta{display:flex;gap:8px;align-items:center;margin-bottom:12px}
|
|
26
27
|
.badge{display:inline-block;background:#eaeef2;border:1px solid #d0d7de;border-radius:999px;padding:4px 10px;font-size:12px;color:#24292f}
|
|
27
28
|
.status{background:#fff8c5;border:1px solid #d0d7de;border-radius:6px;padding:8px 12px;font-size:12px;color:#4b4b00}
|
|
29
|
+
.hljs{background:#f6f8fa;border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:12px;padding:12px}
|
|
28
30
|
</style>
|
|
29
31
|
</head>
|
|
30
32
|
<body>
|
|
@@ -34,6 +36,18 @@ ${badge}
|
|
|
34
36
|
${reviewHtml}
|
|
35
37
|
${diffHtml}
|
|
36
38
|
</div>
|
|
39
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
40
|
+
<script>
|
|
41
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
42
|
+
document.querySelectorAll('pre code').forEach((el) => {
|
|
43
|
+
if (el.classList.contains('language-tsx')) {
|
|
44
|
+
el.classList.remove('language-tsx');
|
|
45
|
+
el.classList.add('language-typescript');
|
|
46
|
+
}
|
|
47
|
+
hljs.highlightElement(el);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
37
51
|
</body>
|
|
38
52
|
</html>`;
|
|
39
53
|
return page;
|
|
@@ -42,6 +56,12 @@ function escapeHtml(s) {
|
|
|
42
56
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
43
57
|
}
|
|
44
58
|
export function renderHTMLTabs(files, meta) {
|
|
59
|
+
const reviewsArr = files.map((f) => (f.review || '')
|
|
60
|
+
.replace(/<\/script/gi, '<\\/script')
|
|
61
|
+
.replace(/<\/style/gi, '<\\/style')
|
|
62
|
+
.replace(/<\/textarea/gi, '<\\/textarea'));
|
|
63
|
+
const reviewsJson = JSON.stringify(reviewsArr);
|
|
64
|
+
const reviewsB64 = Buffer.from(reviewsJson, 'utf8').toString('base64');
|
|
45
65
|
const tabs = files
|
|
46
66
|
.map((f, i) => `<button type="button" class="tab" data-idx="${i}" title="${escapeHtml(f.file)}">${escapeHtml(f.file)}</button>`)
|
|
47
67
|
.join('');
|
|
@@ -79,28 +99,43 @@ export function renderHTMLTabs(files, meta) {
|
|
|
79
99
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
80
100
|
<title>code-gate review</title>
|
|
81
101
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
|
102
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
82
103
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
83
104
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
|
|
105
|
+
<script src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js"></script>
|
|
106
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
84
107
|
<style>
|
|
85
108
|
*{box-sizing:border-box}
|
|
86
109
|
html,body{height:100%;}
|
|
87
110
|
body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;overflow:hidden}
|
|
88
|
-
.container{width:100%;max-width:100%;margin:0 auto;padding:16px}
|
|
89
|
-
.tabs{display:flex;gap:8px;overflow-x:auto;
|
|
90
|
-
.tab{flex:0 0 auto;background
|
|
91
|
-
.tab
|
|
92
|
-
.
|
|
111
|
+
.container{width:100%;max-width:100%;margin:0 auto;padding:16px;height:100%;display:flex;flex-direction:column}
|
|
112
|
+
.tabs{display:flex;gap:8px;overflow-x:auto;border-bottom:1px solid #d0d7de;flex:0 0 auto;scrollbar-width:thin}
|
|
113
|
+
.tab{flex:0 0 auto;background:none;border:none;border-bottom:3px solid transparent;padding:8px 16px;font-size:14px;color:#57606a;cursor:pointer;transition:color 0.2s;white-space:nowrap}
|
|
114
|
+
.tab:hover{color:#24292f;border-bottom-color:#d0d7de}
|
|
115
|
+
.tab.active{color:#24292f;border-bottom-color:#0969da}
|
|
116
|
+
.panes{flex:1;overflow:hidden;position:relative}
|
|
117
|
+
.pane{display:none;height:100%;padding-top:12px}
|
|
93
118
|
.pane.active{display:block}
|
|
94
|
-
.split{display:flex;gap:12px;align-items:stretch}
|
|
95
|
-
.panel{border:1px solid #d0d7de;border-radius:6px;background:#fff;display:flex;flex-direction:column;
|
|
96
|
-
.panel-title{font-weight:600;padding:8px 12px;border-bottom:1px solid #d0d7de;background:#f6f8fa}
|
|
97
|
-
.review-body{padding:
|
|
119
|
+
.split{display:flex;gap:12px;align-items:stretch;height:100%}
|
|
120
|
+
.panel{border:1px solid #d0d7de;border-radius:6px;background:#fff;display:flex;flex-direction:column;flex:1 1 50%;min-width:0;max-width:50%;height:100%}
|
|
121
|
+
.panel-title{font-weight:600;padding:8px 12px;border-bottom:1px solid #d0d7de;background:#f6f8fa;flex:0 0 auto}
|
|
122
|
+
.review-body{padding:16px;overflow:auto;flex:1;font-size:14px;line-height:1.5;color:#24292f}
|
|
123
|
+
.review-body h1,.review-body h2,.review-body h3{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}
|
|
124
|
+
.review-body h1{font-size:2em;border-bottom:1px solid #d0d7de;padding-bottom:.3em}
|
|
125
|
+
.review-body h2{font-size:1.5em;border-bottom:1px solid #d0d7de;padding-bottom:.3em}
|
|
126
|
+
.review-body h3{font-size:1.25em}
|
|
127
|
+
.review-body p{margin-top:0;margin-bottom:16px}
|
|
128
|
+
.review-body blockquote{margin:0 0 16px;padding:0 1em;color:#57606a;border-left:.25em solid #d0d7de}
|
|
129
|
+
.review-body ul,.review-body ol{margin-top:0;margin-bottom:16px;padding-left:2em}
|
|
130
|
+
.review-body a{color:#0969da;text-decoration:none}
|
|
131
|
+
.review-body a:hover{text-decoration:underline}
|
|
98
132
|
.review-body.empty{color:#57606a}
|
|
99
|
-
.diff-body{padding:12px;overflow:auto}
|
|
133
|
+
.diff-body{padding:12px;overflow:auto;flex:1;display:flex;flex-direction:column}
|
|
100
134
|
.diff-body .d2h-code-side-linenumber,.diff-body .d2h-code-linenumber{position:static!important}
|
|
101
135
|
.meta{display:flex;gap:8px;align-items:center;margin-bottom:12px}
|
|
102
136
|
.badge{display:inline-block;background:#eaeef2;border:1px solid #d0d7de;border-radius:999px;padding:4px 10px;font-size:12px;color:#24292f}
|
|
103
137
|
.status{background:#fff8c5;border:1px solid #d0d7de;border-radius:6px;padding:8px 12px;font-size:12px;color:#4b4b00}
|
|
138
|
+
.hljs{background:#f6f8fa;border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:12px;padding:12px}
|
|
104
139
|
</style>
|
|
105
140
|
</head>
|
|
106
141
|
<body>
|
|
@@ -111,6 +146,8 @@ ${badge}
|
|
|
111
146
|
${panes}
|
|
112
147
|
</div>
|
|
113
148
|
<script>
|
|
149
|
+
function _decodeB64Json(b){ try { return JSON.parse(decodeURIComponent(escape(atob(b)))); } catch(e){ return []; } }
|
|
150
|
+
const _reviews_b64 = ${JSON.stringify(reviewsB64)};
|
|
114
151
|
const tabs=[...document.querySelectorAll('.tab')];
|
|
115
152
|
const panes=[...document.querySelectorAll('.pane')];
|
|
116
153
|
function activate(i){
|
|
@@ -119,14 +156,24 @@ function activate(i){
|
|
|
119
156
|
}
|
|
120
157
|
tabs.forEach(t=>t.addEventListener('click',()=>activate(t.dataset.idx)));
|
|
121
158
|
activate(0);
|
|
122
|
-
// Render markdown reviews
|
|
123
|
-
try{
|
|
124
|
-
const reviews
|
|
159
|
+
// Render markdown reviews
|
|
160
|
+
try{
|
|
161
|
+
const reviews=_decodeB64Json(_reviews_b64);
|
|
125
162
|
reviews.forEach((md,i)=>{
|
|
126
163
|
const el=document.querySelector(\`.pane[data-idx="\${i}"] .review-body\`);
|
|
127
164
|
if(el && md){
|
|
128
165
|
const html=DOMPurify.sanitize(marked.parse(md));
|
|
129
166
|
el.innerHTML=html;
|
|
167
|
+
try{
|
|
168
|
+
const blocks=el.querySelectorAll('pre code');
|
|
169
|
+
blocks.forEach((code)=>{
|
|
170
|
+
if (code.classList.contains('language-tsx')) {
|
|
171
|
+
code.classList.remove('language-tsx');
|
|
172
|
+
code.classList.add('language-typescript');
|
|
173
|
+
}
|
|
174
|
+
hljs.highlightElement(code);
|
|
175
|
+
});
|
|
176
|
+
}catch(e){}
|
|
130
177
|
}
|
|
131
178
|
})
|
|
132
179
|
}catch(e){}
|
|
@@ -135,4 +182,142 @@ try{
|
|
|
135
182
|
</html>`;
|
|
136
183
|
return page;
|
|
137
184
|
}
|
|
185
|
+
export function renderHTMLLive(id, meta, initial) {
|
|
186
|
+
const initialJsonLive = JSON.stringify(initial || []);
|
|
187
|
+
const initialB64 = Buffer.from(initialJsonLive, 'utf8').toString('base64');
|
|
188
|
+
const badge = meta
|
|
189
|
+
? `<div class="meta">
|
|
190
|
+
<span class="badge">AI: ${meta.aiInvoked ? (meta.aiSucceeded ? '参与' : '尝试失败') : '未参与'}</span>
|
|
191
|
+
${meta.provider ? `<span class="badge">Provider: ${escapeHtml(meta.provider)}</span>` : ''}
|
|
192
|
+
${meta.model ? `<span class="badge">Model: ${escapeHtml(meta.model)}</span>` : ''}
|
|
193
|
+
${meta.status ? `<div class="status">${escapeHtml(meta.status)}</div>` : ''}
|
|
194
|
+
</div>`
|
|
195
|
+
: '';
|
|
196
|
+
const page = `<!doctype html>
|
|
197
|
+
<html>
|
|
198
|
+
<head>
|
|
199
|
+
<meta charset="utf-8">
|
|
200
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
201
|
+
<title>code-gate review</title>
|
|
202
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
|
203
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
204
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
205
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
|
|
206
|
+
<script src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js"></script>
|
|
207
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
208
|
+
<style>
|
|
209
|
+
*{box-sizing:border-box}
|
|
210
|
+
html,body{height:100%;}
|
|
211
|
+
body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;overflow:hidden}
|
|
212
|
+
.container{width:100%;max-width:100%;margin:0 auto;padding:16px;height:100%;display:flex;flex-direction:column}
|
|
213
|
+
.tabs{display:flex;gap:8px;overflow-x:auto;border-bottom:1px solid #d0d7de;flex:0 0 auto;scrollbar-width:thin}
|
|
214
|
+
.tab{flex:0 0 auto;background:none;border:none;border-bottom:3px solid transparent;padding:8px 16px;font-size:14px;color:#57606a;cursor:pointer;transition:color 0.2s;white-space:nowrap}
|
|
215
|
+
.tab:hover{color:#24292f;border-bottom-color:#d0d7de}
|
|
216
|
+
.tab.active{color:#24292f;border-bottom-color:#0969da}
|
|
217
|
+
.panes{flex:1;overflow:hidden;position:relative}
|
|
218
|
+
.pane{display:none;height:100%;padding-top:12px}
|
|
219
|
+
.pane.active{display:block}
|
|
220
|
+
.split{display:flex;gap:12px;align-items:stretch;height:100%}
|
|
221
|
+
.panel{border:1px solid #d0d7de;border-radius:6px;background:#fff;display:flex;flex-direction:column;flex:1 1 50%;min-width:0;max-width:50%;height:100%}
|
|
222
|
+
.panel-title{font-weight:600;padding:8px 12px;border-bottom:1px solid #d0d7de;background:#f6f8fa;flex:0 0 auto}
|
|
223
|
+
.review-body{padding:16px;overflow:auto;flex:1;font-size:14px;line-height:1.5;color:#24292f}
|
|
224
|
+
.review-body h1,.review-body h2,.review-body h3{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}
|
|
225
|
+
.review-body h1{font-size:2em;border-bottom:1px solid #d0d7de;padding-bottom:.3em}
|
|
226
|
+
.review-body h2{font-size:1.5em;border-bottom:1px solid #d0d7de;padding-bottom:.3em}
|
|
227
|
+
.review-body h3{font-size:1.25em}
|
|
228
|
+
.review-body p{margin-top:0;margin-bottom:16px}
|
|
229
|
+
.review-body blockquote{margin:0 0 16px;padding:0 1em;color:#57606a;border-left:.25em solid #d0d7de}
|
|
230
|
+
.review-body ul,.review-body ol{margin-top:0;margin-bottom:16px;padding-left:2em}
|
|
231
|
+
.review-body a{color:#0969da;text-decoration:none}
|
|
232
|
+
.review-body a:hover{text-decoration:underline}
|
|
233
|
+
.review-body.empty{color:#57606a}
|
|
234
|
+
.diff-body{padding:12px;overflow:auto;flex:1;display:flex;flex-direction:column}
|
|
235
|
+
.diff-body .d2h-code-side-linenumber,.diff-body .d2h-code-linenumber{position:static!important}
|
|
236
|
+
.meta{display:flex;gap:8px;align-items:center;margin-bottom:12px}
|
|
237
|
+
.badge{display:inline-block;background:#eaeef2;border:1px solid #d0d7de;border-radius:999px;padding:4px 10px;font-size:12px;color:#24292f}
|
|
238
|
+
.status{background:#fff8c5;border:1px solid #d0d7de;border-radius:6px;padding:8px 12px;font-size:12px;color:#4b4b00}
|
|
239
|
+
.notice{margin:8px 0;color:#57606a}
|
|
240
|
+
.hljs{background:#f6f8fa;border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:12px;padding:12px}
|
|
241
|
+
</style>
|
|
242
|
+
</head>
|
|
243
|
+
<body>
|
|
244
|
+
<div class="container">
|
|
245
|
+
<h1>Code Review <span class="status-text" style="font-size:14px;color:#57606a;font-weight:normal;">后台继续审查中…</span></h1>
|
|
246
|
+
${badge}
|
|
247
|
+
<div class="tabs"></div>
|
|
248
|
+
<div class="panes"></div>
|
|
249
|
+
</div>
|
|
250
|
+
<script>
|
|
251
|
+
const route = ${JSON.stringify('/review/' + id + '/status')};
|
|
252
|
+
function _decodeB64Json(b){ try { return JSON.parse(decodeURIComponent(escape(atob(b)))); } catch(e){ return []; } }
|
|
253
|
+
const initial=_decodeB64Json(${JSON.stringify(initialB64)});
|
|
254
|
+
const tabs=document.querySelector('.tabs');
|
|
255
|
+
const panes=document.querySelector('.panes');
|
|
256
|
+
const known=new Set();
|
|
257
|
+
function activate(i){
|
|
258
|
+
[...tabs.children].forEach((t,idx)=>t.classList.toggle('active',idx===i));
|
|
259
|
+
[...panes.children].forEach((p,idx)=>p.classList.toggle('active',idx===i));
|
|
260
|
+
}
|
|
261
|
+
function addItem(item){
|
|
262
|
+
if(known.has(item.file))return;
|
|
263
|
+
known.add(item.file);
|
|
264
|
+
const i=[...known].length-1;
|
|
265
|
+
const t=document.createElement('button');
|
|
266
|
+
t.type='button';
|
|
267
|
+
t.className='tab';
|
|
268
|
+
t.title=item.file;
|
|
269
|
+
t.textContent=item.file;
|
|
270
|
+
t.addEventListener('click',()=>activate(i));
|
|
271
|
+
tabs.appendChild(t);
|
|
272
|
+
const p=document.createElement('div');
|
|
273
|
+
p.className='pane';
|
|
274
|
+
const reviewHtml=item.review?DOMPurify.sanitize(marked.parse(item.review)):'<div class=\"review-body empty\">暂无审查内容</div>';
|
|
275
|
+
const parsed=window.Diff2Html.parse(item.diff,{inputFormat:'diff'});
|
|
276
|
+
const diffHtml=window.Diff2Html.html(parsed,{ showFiles:false, matching:'lines' });
|
|
277
|
+
p.innerHTML='<div class=\"split\"><div class=\"panel panel-left\"><div class=\"panel-title\">AI Review</div><div class=\"review-body\">'+reviewHtml+'</div></div><div class=\"panel panel-right\"><div class=\"panel-title\">Diff</div><div class=\"diff-body\">'+diffHtml+'</div></div></div>';
|
|
278
|
+
panes.appendChild(p);
|
|
279
|
+
try{
|
|
280
|
+
const el=p.querySelector('.review-body');
|
|
281
|
+
const blocks=el?el.querySelectorAll('pre code'):[];
|
|
282
|
+
blocks.forEach((code)=>{
|
|
283
|
+
if (code.classList.contains('language-tsx')) {
|
|
284
|
+
code.classList.remove('language-tsx');
|
|
285
|
+
code.classList.add('language-typescript');
|
|
286
|
+
}
|
|
287
|
+
hljs.highlightElement(code);
|
|
288
|
+
});
|
|
289
|
+
}catch(e){}
|
|
290
|
+
if(i===0)activate(0);
|
|
291
|
+
try{
|
|
292
|
+
const aiBadge=document.querySelector('.meta .badge');
|
|
293
|
+
if(aiBadge && aiBadge.textContent && aiBadge.textContent.indexOf('AI:')===0){
|
|
294
|
+
aiBadge.textContent='AI: 参与';
|
|
295
|
+
}
|
|
296
|
+
}catch(e){}
|
|
297
|
+
}
|
|
298
|
+
async function poll(){
|
|
299
|
+
try{
|
|
300
|
+
const res=await fetch(route);
|
|
301
|
+
if(!res.ok) {
|
|
302
|
+
const n=document.querySelector('.status-text');
|
|
303
|
+
if(n && known.size>0) n.textContent='全部完成';
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const data=await res.json();
|
|
307
|
+
const list=data && Array.isArray(data.files)?data.files:[];
|
|
308
|
+
list.forEach(it=>{ if(it && it.done) addItem(it); });
|
|
309
|
+
if(data.done) {
|
|
310
|
+
const n=document.querySelector('.status-text');
|
|
311
|
+
if(n) n.textContent='全部完成';
|
|
312
|
+
}
|
|
313
|
+
}catch(e){}
|
|
314
|
+
}
|
|
315
|
+
setInterval(poll,5000);
|
|
316
|
+
try{ initial.forEach(addItem); }catch(e){}
|
|
317
|
+
poll();
|
|
318
|
+
</script>
|
|
319
|
+
</body>
|
|
320
|
+
</html>`;
|
|
321
|
+
return page;
|
|
322
|
+
}
|
|
138
323
|
//# sourceMappingURL=render.js.map
|