Julien Simon commited on
Commit
77de2c1
Β·
1 Parent(s): d674bd0

Add Verdicts tab with Linus/Donald/Bjarne opinions

Browse files
Files changed (5) hide show
  1. app.js +276 -0
  2. index.html +120 -0
  3. public/app.js +2 -1
  4. public/index.html +1 -0
  5. server.js +10 -1
app.js ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* --- DOM refs --- */
2
+ const form = document.getElementById("review-form");
3
+ const urlInput = document.getElementById("url-input");
4
+ const submitBtn = document.getElementById("submit-btn");
5
+ const inputError = document.getElementById("input-error");
6
+ const metaEl = document.getElementById("meta");
7
+ const metaRepo = document.getElementById("meta-repo");
8
+ const metaPath = document.getElementById("meta-path");
9
+ const metaBranch = document.getElementById("meta-branch");
10
+ const tabsEl = document.getElementById("tabs");
11
+ const tabContent = document.getElementById("tab-content");
12
+ const streamError = document.getElementById("stream-error");
13
+ const tabButtons = document.querySelectorAll(".tab");
14
+
15
+ const GITHUB_BLOB_RE = /^https?:\/\/github\.com\/[^/]+\/[^/]+\/blob\/[^/]+\/.+$/;
16
+
17
+ /* --- Section parsing --- */
18
+
19
+ const SECTION_KEYS = ["summary", "quality", "performance", "security", "suggestions", "verdicts"];
20
+
21
+ // Maps heading text (lowercased) to our section key
22
+ const HEADING_MAP = {
23
+ summary: "summary",
24
+ "code quality": "quality",
25
+ performance: "performance",
26
+ security: "security",
27
+ suggestions: "suggestions",
28
+ verdicts: "verdicts",
29
+ };
30
+
31
+ function parseSections(markdown) {
32
+ const sections = {};
33
+ let currentKey = null;
34
+
35
+ for (const line of markdown.split("\n")) {
36
+ const m = line.match(/^## (.+)$/);
37
+ if (m) {
38
+ const title = m[1].trim().toLowerCase();
39
+ const matched = Object.entries(HEADING_MAP).find(([kw]) => title.includes(kw));
40
+ if (matched) {
41
+ currentKey = matched[1];
42
+ sections[currentKey] = "";
43
+ continue;
44
+ }
45
+ }
46
+ if (currentKey) {
47
+ sections[currentKey] = (sections[currentKey] || "") + line + "\n";
48
+ }
49
+ }
50
+ return sections;
51
+ }
52
+
53
+ // Detect which section the model is currently writing to (last matched heading)
54
+ function detectCurrentSection(markdown) {
55
+ let last = null;
56
+ for (const line of markdown.split("\n")) {
57
+ const m = line.match(/^## (.+)$/);
58
+ if (m) {
59
+ const title = m[1].trim().toLowerCase();
60
+ const matched = Object.entries(HEADING_MAP).find(([kw]) => title.includes(kw));
61
+ if (matched) last = matched[1];
62
+ }
63
+ }
64
+ return last;
65
+ }
66
+
67
+ /* --- Diff-aware marked renderer --- */
68
+
69
+ function escapeHtml(str) {
70
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
71
+ }
72
+
73
+ function isDiffContent(text) {
74
+ const lines = text.split("\n");
75
+ let diffLines = 0;
76
+ for (const l of lines) {
77
+ if (l.startsWith("+") || l.startsWith("-") || l.startsWith("@@")) diffLines++;
78
+ }
79
+ return diffLines >= 2;
80
+ }
81
+
82
+ function renderDiffBlock(text) {
83
+ const lines = text.split("\n").map((line) => {
84
+ const escaped = escapeHtml(line);
85
+ if (line.startsWith("+++") || line.startsWith("---")) {
86
+ return `<span class="diff-line diff-info">${escaped}</span>`;
87
+ }
88
+ if (line.startsWith("+")) {
89
+ return `<span class="diff-line diff-add">${escaped}</span>`;
90
+ }
91
+ if (line.startsWith("-")) {
92
+ return `<span class="diff-line diff-del">${escaped}</span>`;
93
+ }
94
+ if (line.startsWith("@@")) {
95
+ return `<span class="diff-line diff-info">${escaped}</span>`;
96
+ }
97
+ return `<span class="diff-line diff-neutral">${escaped}</span>`;
98
+ });
99
+ return `<pre class="diff-block"><code>${lines.join("")}</code></pre>`;
100
+ }
101
+
102
+ const renderer = {
103
+ code({ text, lang, language }) {
104
+ const codeLang = (lang || language || "").toLowerCase().trim();
105
+ // Render as diff if tagged as diff OR if content looks like a diff
106
+ if (codeLang === "diff" || (!codeLang && isDiffContent(text))) {
107
+ return renderDiffBlock(text);
108
+ }
109
+ const langClass = codeLang ? ` class="language-${escapeHtml(codeLang)}"` : "";
110
+ return `<pre><code${langClass}>${escapeHtml(text)}</code></pre>`;
111
+ },
112
+ };
113
+
114
+ marked.use({ renderer });
115
+
116
+ function renderMarkdown(md) {
117
+ return DOMPurify.sanitize(marked.parse(md));
118
+ }
119
+
120
+ /* --- Tab management --- */
121
+
122
+ let activeTab = "summary";
123
+ let manualSwitch = false;
124
+ let currentSections = {};
125
+
126
+ tabButtons.forEach((btn) => {
127
+ btn.addEventListener("click", () => {
128
+ manualSwitch = true;
129
+ setActiveTab(btn.dataset.section);
130
+ renderActiveTab();
131
+ });
132
+ });
133
+
134
+ function setActiveTab(section) {
135
+ activeTab = section;
136
+ tabButtons.forEach((btn) => {
137
+ btn.classList.toggle("active", btn.dataset.section === section);
138
+ });
139
+ }
140
+
141
+ function renderActiveTab() {
142
+ const md = currentSections[activeTab] || "";
143
+ if (md.trim()) {
144
+ tabContent.innerHTML = renderMarkdown(md);
145
+ tabContent.classList.remove("tab-content-empty");
146
+ } else {
147
+ tabContent.textContent = "Waiting for content\u2026";
148
+ tabContent.classList.add("tab-content-empty");
149
+ }
150
+ }
151
+
152
+ function updateTabs(sections, currentStreamSection) {
153
+ currentSections = sections;
154
+
155
+ // Mark tabs that have content + which one is streaming
156
+ tabButtons.forEach((btn) => {
157
+ const key = btn.dataset.section;
158
+ btn.classList.toggle("has-content", !!sections[key]?.trim());
159
+ btn.classList.toggle("streaming", key === currentStreamSection);
160
+ });
161
+
162
+ // Auto-switch to the section currently being written (unless user clicked a tab)
163
+ if (!manualSwitch && currentStreamSection) {
164
+ setActiveTab(currentStreamSection);
165
+ }
166
+
167
+ renderActiveTab();
168
+ }
169
+
170
+ /* --- Review flow --- */
171
+
172
+ let currentSource = null;
173
+
174
+ form.addEventListener("submit", (e) => {
175
+ e.preventDefault();
176
+ startReview();
177
+ });
178
+
179
+ /* --- Sample buttons --- */
180
+
181
+ document.querySelectorAll(".sample-btn").forEach((btn) => {
182
+ btn.addEventListener("click", () => {
183
+ urlInput.value = btn.dataset.url;
184
+ startReview();
185
+ });
186
+ });
187
+
188
+ function startReview() {
189
+ const url = urlInput.value.trim();
190
+
191
+ // Reset
192
+ inputError.hidden = true;
193
+ streamError.hidden = true;
194
+ metaEl.hidden = true;
195
+ tabsEl.hidden = true;
196
+ tabContent.textContent = "";
197
+ manualSwitch = false;
198
+ setActiveTab("summary");
199
+ tabButtons.forEach((btn) => {
200
+ btn.classList.remove("has-content", "streaming");
201
+ });
202
+
203
+ if (!url) { showInputError("Please enter a GitHub file URL."); return; }
204
+ if (!GITHUB_BLOB_RE.test(url)) {
205
+ showInputError("URL must be a GitHub blob URL (e.g. https://github.com/owner/repo/blob/main/file.js).");
206
+ return;
207
+ }
208
+
209
+ if (currentSource) { currentSource.close(); currentSource = null; }
210
+ setLoading(true);
211
+
212
+ let markdown = "";
213
+ const source = new EventSource(`/api/review?url=${encodeURIComponent(url)}`);
214
+ currentSource = source;
215
+
216
+ source.addEventListener("meta", (e) => {
217
+ const data = JSON.parse(e.data);
218
+ metaRepo.textContent = `${data.owner}/${data.repo}`;
219
+ metaPath.textContent = data.path;
220
+ metaBranch.textContent = data.branch;
221
+ metaEl.hidden = false;
222
+ tabsEl.hidden = false;
223
+ tabContent.classList.add("is-streaming");
224
+ });
225
+
226
+ source.addEventListener("content", (e) => {
227
+ const data = JSON.parse(e.data);
228
+ markdown += data.text;
229
+ const sections = parseSections(markdown);
230
+ const current = detectCurrentSection(markdown);
231
+ updateTabs(sections, current);
232
+ });
233
+
234
+ source.addEventListener("done", () => {
235
+ cleanup();
236
+ // Final render with no streaming section
237
+ const sections = parseSections(markdown);
238
+ updateTabs(sections, null);
239
+ });
240
+
241
+ source.addEventListener("error", (e) => {
242
+ if (e.data) {
243
+ const data = JSON.parse(e.data);
244
+ showStreamError(data.message);
245
+ } else {
246
+ showStreamError("Connection lost. Please try again.");
247
+ }
248
+ cleanup();
249
+ });
250
+
251
+ function cleanup() {
252
+ source.close();
253
+ currentSource = null;
254
+ setLoading(false);
255
+ tabContent.classList.remove("is-streaming");
256
+ tabButtons.forEach((btn) => btn.classList.remove("streaming"));
257
+ }
258
+ }
259
+
260
+ /* --- Helpers --- */
261
+
262
+ function setLoading(on) {
263
+ submitBtn.disabled = on;
264
+ submitBtn.textContent = on ? "Reviewing\u2026" : "Review Code";
265
+ document.body.classList.toggle("loading", on);
266
+ }
267
+
268
+ function showInputError(msg) {
269
+ inputError.textContent = msg;
270
+ inputError.hidden = false;
271
+ }
272
+
273
+ function showStreamError(msg) {
274
+ streamError.textContent = msg;
275
+ streamError.hidden = false;
276
+ }
index.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Arcee AI Trinity Large Code Review</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <header class="app-header">
12
+ <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/6435718aaaef013d1aec3b8b/GZPnGkfMn8Ino6JbkL4fJ.png" alt="Arcee AI" class="logo">
13
+ <div>
14
+ <h1>Arcee AI Trinity Large Code Review</h1>
15
+ <p class="subtitle">Paste a public GitHub file URL for an AI-powered code review.</p>
16
+ </div>
17
+ </header>
18
+ <nav class="resources">
19
+ <a href="https://www.arcee.ai/blog/trinity-large" target="_blank" rel="noopener">Trinity Large Preview blog post</a>
20
+ <a href="https://huggingface.co/arcee-ai/Trinity-Large-Preview" target="_blank" rel="noopener">Trinity Large Preview on Hugging Face</a>
21
+ <a href="https://openrouter.ai/arcee-ai/trinity-large-preview:free" target="_blank" rel="noopener">Trinity Large Preview on OpenRouter</a>
22
+ </nav>
23
+
24
+ <form id="review-form">
25
+ <div class="input-row">
26
+ <input
27
+ type="url"
28
+ id="url-input"
29
+ placeholder="https://github.com/owner/repo/blob/main/path/to/file.js"
30
+ required
31
+ >
32
+ <button type="submit" id="submit-btn">Review Code</button>
33
+ </div>
34
+ <p id="input-error" class="error" hidden></p>
35
+ </form>
36
+
37
+ <section id="samples">
38
+ <p class="samples-label">Try a sample:</p>
39
+ <div class="samples-grid">
40
+ <button class="sample-btn" data-url="https://github.com/torvalds/linux/blob/master/lib/rbtree.c">
41
+ <span class="sample-lang">C</span>
42
+ <span class="sample-name">Linux Kernel β€” rbtree.c</span>
43
+ </button>
44
+ <button class="sample-btn" data-url="https://github.com/redis/redis/blob/unstable/src/dict.c">
45
+ <span class="sample-lang">C</span>
46
+ <span class="sample-name">Redis β€” dict.c</span>
47
+ </button>
48
+ <button class="sample-btn" data-url="https://github.com/kubernetes/kubernetes/blob/master/pkg/scheduler/schedule_one.go">
49
+ <span class="sample-lang">Go</span>
50
+ <span class="sample-name">Kubernetes β€” schedule_one.go</span>
51
+ </button>
52
+ <button class="sample-btn" data-url="https://github.com/golang/go/blob/master/src/runtime/malloc.go">
53
+ <span class="sample-lang">Go</span>
54
+ <span class="sample-name">Go runtime β€” malloc.go</span>
55
+ </button>
56
+ <button class="sample-btn" data-url="https://github.com/pytorch/pytorch/blob/main/torch/autograd/function.py">
57
+ <span class="sample-lang">Python</span>
58
+ <span class="sample-name">PyTorch β€” autograd/function.py</span>
59
+ </button>
60
+ <button class="sample-btn" data-url="https://github.com/django/django/blob/main/django/db/models/expressions.py">
61
+ <span class="sample-lang">Python</span>
62
+ <span class="sample-name">Django β€” expressions.py</span>
63
+ </button>
64
+ <button class="sample-btn" data-url="https://github.com/nodejs/node/blob/main/lib/internal/streams/readable.js">
65
+ <span class="sample-lang">JavaScript</span>
66
+ <span class="sample-name">Node.js β€” streams/readable.js</span>
67
+ </button>
68
+ <button class="sample-btn" data-url="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiber.js">
69
+ <span class="sample-lang">JavaScript</span>
70
+ <span class="sample-name">React β€” ReactFiber.js</span>
71
+ </button>
72
+ <button class="sample-btn" data-url="https://github.com/microsoft/TypeScript/blob/main/src/compiler/visitorPublic.ts">
73
+ <span class="sample-lang">TypeScript</span>
74
+ <span class="sample-name">TypeScript β€” visitorPublic.ts</span>
75
+ </button>
76
+ <button class="sample-btn" data-url="https://github.com/rails/rails/blob/main/activerecord/lib/active_record/relation/query_methods.rb">
77
+ <span class="sample-lang">Ruby</span>
78
+ <span class="sample-name">Rails β€” query_methods.rb</span>
79
+ </button>
80
+ <button class="sample-btn" data-url="https://github.com/ggerganov/llama.cpp/blob/master/src/llama.cpp">
81
+ <span class="sample-lang">C++</span>
82
+ <span class="sample-name">llama.cpp β€” llama.cpp</span>
83
+ </button>
84
+ <button class="sample-btn" data-url="https://github.com/ARM-software/optimized-routines/blob/master/string/aarch64/memcpy.S">
85
+ <span class="sample-lang">ARM64 ASM</span>
86
+ <span class="sample-name">ARM β€” memcpy.S</span>
87
+ </button>
88
+ <button class="sample-btn" data-url="https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S">
89
+ <span class="sample-lang">x86-64 ASM</span>
90
+ <span class="sample-name">glibc β€” memmove-vec.S</span>
91
+ </button>
92
+ </div>
93
+ </section>
94
+
95
+ <div id="meta" hidden>
96
+ <span id="meta-repo"></span>
97
+ <span id="meta-path"></span>
98
+ <span id="meta-branch"></span>
99
+ </div>
100
+
101
+ <div id="tabs" hidden>
102
+ <nav class="tab-bar">
103
+ <button class="tab active" data-section="summary">Summary</button>
104
+ <button class="tab" data-section="quality">Code Quality</button>
105
+ <button class="tab" data-section="performance">Performance</button>
106
+ <button class="tab" data-section="security">Security</button>
107
+ <button class="tab" data-section="suggestions">Suggestions</button>
108
+ <button class="tab" data-section="verdicts">Verdicts</button>
109
+ </nav>
110
+ <div id="tab-content" class="tab-content"></div>
111
+ </div>
112
+
113
+ <div id="stream-error" class="error" hidden></div>
114
+ </main>
115
+
116
+ <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
117
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
118
+ <script src="app.js"></script>
119
+ </body>
120
+ </html>
public/app.js CHANGED
@@ -16,7 +16,7 @@ const GITHUB_BLOB_RE = /^https?:\/\/github\.com\/[^/]+\/[^/]+\/blob\/[^/]+\/.+$/
16
 
17
  /* --- Section parsing --- */
18
 
19
- const SECTION_KEYS = ["summary", "quality", "performance", "security", "suggestions"];
20
 
21
  // Maps heading text (lowercased) to our section key
22
  const HEADING_MAP = {
@@ -25,6 +25,7 @@ const HEADING_MAP = {
25
  performance: "performance",
26
  security: "security",
27
  suggestions: "suggestions",
 
28
  };
29
 
30
  function parseSections(markdown) {
 
16
 
17
  /* --- Section parsing --- */
18
 
19
+ const SECTION_KEYS = ["summary", "quality", "performance", "security", "suggestions", "verdicts"];
20
 
21
  // Maps heading text (lowercased) to our section key
22
  const HEADING_MAP = {
 
25
  performance: "performance",
26
  security: "security",
27
  suggestions: "suggestions",
28
+ verdicts: "verdicts",
29
  };
30
 
31
  function parseSections(markdown) {
public/index.html CHANGED
@@ -105,6 +105,7 @@
105
  <button class="tab" data-section="performance">Performance</button>
106
  <button class="tab" data-section="security">Security</button>
107
  <button class="tab" data-section="suggestions">Suggestions</button>
 
108
  </nav>
109
  <div id="tab-content" class="tab-content"></div>
110
  </div>
 
105
  <button class="tab" data-section="performance">Performance</button>
106
  <button class="tab" data-section="security">Security</button>
107
  <button class="tab" data-section="suggestions">Suggestions</button>
108
+ <button class="tab" data-section="verdicts">Verdicts</button>
109
  </nav>
110
  <div id="tab-content" class="tab-content"></div>
111
  </div>
server.js CHANGED
@@ -96,8 +96,17 @@ Numbered list of concrete improvements, ranked ruthlessly by impact. No hand-wav
96
 
97
  (continue numbering)
98
 
 
 
 
 
 
 
 
 
 
99
  Rules:
100
- - You MUST use exactly these five ## headings and no others.
101
  - ALWAYS reference line numbers from the provided source.
102
  - EVERY ISSUE MUST HAVE A BEFORE/AFTER DIFF. No exceptions. No issue without a \`\`\`diff block. No diff without both \`-\` (before) and \`+\` (after) lines. If you cannot show a concrete fix, do not mention the issue.
103
  - DIFF FORMAT: Lines with \`-\` are BEFORE (old code). Lines with \`+\` are AFTER (new code). Lines with neither are context. Show what you're removing AND what you're replacing it with. Example:
 
96
 
97
  (continue numbering)
98
 
99
+ ## Verdicts
100
+ After completing ALL the above sections, provide these three expert opinions. Each verdict should be 2-3 sentences, written in character:
101
+
102
+ **What Linus would say:** [Brutally honest LKML style. If the code is garbage, say it's garbage. No corporate-speak, no hand-holding. Call out stupidity directly.]
103
+
104
+ **What Donald would say:** [Precise, algorithmic, almost mathematical. Focus on correctness, invariants, whether the code could be formally verified. Reference complexity if relevant.]
105
+
106
+ **What Bjarne would say:** [Principled design perspective. Focus on abstraction quality, type safety, resource management (RAII), zero-overhead principle. Is the design sound?]
107
+
108
  Rules:
109
+ - You MUST use exactly these six ## headings in this order: Summary, Code Quality, Performance, Security, Suggestions, Verdicts.
110
  - ALWAYS reference line numbers from the provided source.
111
  - EVERY ISSUE MUST HAVE A BEFORE/AFTER DIFF. No exceptions. No issue without a \`\`\`diff block. No diff without both \`-\` (before) and \`+\` (after) lines. If you cannot show a concrete fix, do not mention the issue.
112
  - DIFF FORMAT: Lines with \`-\` are BEFORE (old code). Lines with \`+\` are AFTER (new code). Lines with neither are context. Show what you're removing AND what you're replacing it with. Example: