Skip to content

Commit 0b54eab

Browse files
committed
tweaks for the menu
1 parent a798df1 commit 0b54eab

11 files changed

Lines changed: 257 additions & 7 deletions

File tree

reviewbot/static/admin.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8">
55
<title>Serge · admin</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
78
<link rel="stylesheet" href="/static/styles.css">
89
</head>
910
<body>
@@ -14,6 +15,7 @@
1415
</div>
1516
<div class="right">
1617
<a href="/" class="secondary nav-link">New review</a>
18+
<a href="/admin" class="secondary nav-link" aria-current="page">Admin</a>
1719
<a href="/journal" class="secondary nav-link">Journal</a>
1820
<form method="post" action="/auth/logout" style="margin:0">
1921
<button type="submit" class="secondary">Sign out</button>
@@ -37,8 +39,9 @@ <h2>Provider configs</h2>
3739
<table class="jobs-list" id="configs-table">
3840
<thead>
3941
<tr>
40-
<th>Provider</th>
4142
<th>Repo</th>
43+
<th>Provider</th>
44+
<th>Base URL</th>
4245
<th>Default model</th>
4346
<th>Users / orgs</th>
4447
<th>Key</th>

reviewbot/static/admin.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,18 @@
110110
for (const cfg of configs) {
111111
const tr = document.createElement("tr");
112112

113+
const tdRepo = document.createElement("td");
114+
tdRepo.textContent = cfg.repo_pattern;
115+
tr.appendChild(tdRepo);
116+
113117
const tdProvider = document.createElement("td");
114118
tdProvider.textContent = cfg.provider;
115119
tr.appendChild(tdProvider);
116120

117-
const tdRepo = document.createElement("td");
118-
tdRepo.textContent = cfg.repo_pattern;
119-
tr.appendChild(tdRepo);
121+
const tdBase = document.createElement("td");
122+
tdBase.textContent = cfg.api_base || "—";
123+
tdBase.title = cfg.api_base || "";
124+
tr.appendChild(tdBase);
120125

121126
const tdModel = document.createElement("td");
122127
tdModel.textContent = cfg.default_model || "—";

reviewbot/static/favicon.svg

Lines changed: 3 additions & 0 deletions
Loading

reviewbot/static/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8">
55
<title>Serge · new review</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
78
<link rel="stylesheet" href="/static/styles.css">
89
</head>
910
<body>
@@ -13,6 +14,7 @@
1314
<!-- POWERED_BY -->
1415
</div>
1516
<div class="right">
17+
<a href="/" class="secondary nav-link" aria-current="page">New review</a>
1618
<a href="/admin" class="secondary nav-link">Admin</a>
1719
<a href="/journal" class="secondary nav-link">Journal</a>
1820
<form method="post" action="/auth/logout" style="margin:0">
@@ -53,6 +55,7 @@ <h2>Ask Serge to review a PR</h2>
5355
<label for="llm-base-url">Base URL</label>
5456
<input type="text" id="llm-base-url" name="llm_base_url" placeholder="https://example.com/v1">
5557
</div>
58+
<div id="provider-hint" class="hint" style="margin-top:10px;"></div>
5659
<div class="presets">
5760
<span class="hint">Presets:</span>
5861
<button type="button" class="secondary preset" data-preset="first-pass">First-pass</button>

reviewbot/static/index.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
const form = document.getElementById("submit-form");
33
const btn = document.getElementById("submit-btn");
44
const banner = document.getElementById("error-banner");
5+
const prEl = document.getElementById("pr");
56
const commentEl = document.getElementById("comment");
67
const providerEl = document.getElementById("llm-provider");
78
const modelEl = document.getElementById("llm-model");
89
const baseUrlEl = document.getElementById("llm-base-url");
910
const customBaseRow = document.getElementById("custom-base-row");
11+
const providerHint = document.getElementById("provider-hint");
1012
const jobsSection = document.getElementById("jobs-section");
1113
const jobsTbody = document.getElementById("jobs-tbody");
1214
const jobsCount = document.getElementById("jobs-count");
@@ -210,6 +212,78 @@
210212
}
211213
}
212214

215+
// Mirrors the server-side _parse_pr_ref shapes: full GitHub URL,
216+
// "owner/repo#NN", or "owner/repo/pull/NN". Returns null when the
217+
// string isn't yet a parseable PR reference (e.g. half-typed).
218+
function parseOwnerRepo(raw) {
219+
const s = (raw || "").trim();
220+
if (!s) return null;
221+
const name = "[A-Za-z0-9._-]+";
222+
let m;
223+
m = s.match(new RegExp(`github\\.com/(${name})/(${name})/(?:pull|pulls)/\\d+`));
224+
if (m) return { owner: m[1], repo: m[2] };
225+
m = s.match(new RegExp(`^(${name})/(${name})#\\d+`));
226+
if (m) return { owner: m[1], repo: m[2] };
227+
m = s.match(new RegExp(`^(${name})/(${name})/pull/\\d+`));
228+
if (m) return { owner: m[1], repo: m[2] };
229+
return null;
230+
}
231+
232+
let lookupTimer = 0;
233+
let lastLookupKey = "";
234+
235+
function scheduleProviderLookup() {
236+
clearTimeout(lookupTimer);
237+
lookupTimer = setTimeout(runProviderLookup, 250);
238+
}
239+
240+
async function runProviderLookup() {
241+
const parsed = parseOwnerRepo(prEl.value);
242+
if (!parsed) {
243+
providerHint.textContent = "";
244+
lastLookupKey = "";
245+
return;
246+
}
247+
const key = `${parsed.owner}/${parsed.repo}`.toLowerCase();
248+
if (key === lastLookupKey) return;
249+
lastLookupKey = key;
250+
try {
251+
const qs = new URLSearchParams({ owner: parsed.owner, repo: parsed.repo });
252+
const r = await fetch(`/reviews/lookup-provider?${qs}`);
253+
if (!r.ok) {
254+
providerHint.textContent = "";
255+
return;
256+
}
257+
const data = await r.json();
258+
if (!data.match) {
259+
providerHint.textContent = `No provider config matches ${parsed.owner}/${parsed.repo}. Add one at /admin or your submission will be refused.`;
260+
return;
261+
}
262+
applyMatchedConfig(data.match, parsed);
263+
} catch {
264+
// Non-fatal — the user can still submit; the server will reject
265+
// with the same message if no config matches.
266+
providerHint.textContent = "";
267+
}
268+
}
269+
270+
function applyMatchedConfig(match, parsed) {
271+
providerEl.value = match.provider;
272+
if (match.default_model) {
273+
modelEl.value = match.default_model;
274+
}
275+
if (match.provider === "custom" && match.api_base) {
276+
baseUrlEl.value = match.api_base;
277+
}
278+
updateProviderFields();
279+
const modelPart = match.default_model ? ` · model ${match.default_model}` : "";
280+
const scope =
281+
match.repo_pattern && match.repo_pattern !== `${parsed.owner}/${parsed.repo}`
282+
? ` (via ${match.repo_pattern})`
283+
: "";
284+
providerHint.textContent = `Using ${match.provider}${modelPart}${scope}.`;
285+
}
286+
213287
form.addEventListener("submit", async (e) => {
214288
e.preventDefault();
215289
btn.disabled = true;
@@ -254,8 +328,18 @@
254328
modelEl.addEventListener("change", saveLlmPrefs);
255329
baseUrlEl.addEventListener("change", saveLlmPrefs);
256330

331+
// Auto-fill provider/model from the matching DB config whenever the
332+
// PR field changes. Debounced so we don't hit the endpoint on every
333+
// keystroke, and short-circuited by lastLookupKey when the
334+
// owner/repo hasn't actually changed.
335+
prEl.addEventListener("input", scheduleProviderLookup);
336+
prEl.addEventListener("change", runProviderLookup);
337+
257338
loadLlmOptions();
258339
loadJobs();
340+
// Catch the case where the input already has a value at load time
341+
// (e.g. browser autofill or a prefilled paste).
342+
runProviderLookup();
259343
// Soft-refresh every 5s so running reviews tick over to "done" /
260344
// "published" without the user having to reload.
261345
setInterval(loadJobs, 5000);

reviewbot/static/journal.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8">
55
<title>Serge · journal</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
78
<link rel="stylesheet" href="/static/styles.css">
89
</head>
910
<body>
@@ -14,6 +15,8 @@
1415
</div>
1516
<div class="right">
1617
<a href="/" class="secondary nav-link">New review</a>
18+
<a href="/admin" class="secondary nav-link">Admin</a>
19+
<a href="/journal" class="secondary nav-link" aria-current="page">Journal</a>
1720
<form method="post" action="/auth/logout" style="margin:0">
1821
<button type="submit" class="secondary">Sign out</button>
1922
</form>

reviewbot/static/login.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8">
55
<title>Serge · sign in</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
78
<link rel="stylesheet" href="/static/styles.css">
89
</head>
910
<body>

reviewbot/static/review.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8">
55
<title>Serge · live review</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
78
<link rel="stylesheet" href="/static/styles.css">
89
</head>
910
<body>
@@ -16,8 +17,12 @@
1617
<span id="target"></span>
1718
<span class="status-badge" id="status">running</span>
1819
<button class="secondary" id="rerun-btn" title="Re-run with the same PR + comment">Review again</button>
20+
<a href="/" class="secondary nav-link">New review</a>
21+
<a href="/admin" class="secondary nav-link">Admin</a>
1922
<a href="/journal" class="secondary nav-link">Journal</a>
20-
<a href="/"><button class="secondary">New review</button></a>
23+
<form method="post" action="/auth/logout" style="margin:0">
24+
<button type="submit" class="secondary">Sign out</button>
25+
</form>
2126
</div>
2227
</header>
2328
<main>

reviewbot/static/styles.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ a.nav-link {
147147
line-height: 1;
148148
}
149149
a.nav-link:hover { opacity: 0.85; }
150+
a.nav-link[aria-current="page"] {
151+
background: var(--bg);
152+
color: var(--muted);
153+
pointer-events: none;
154+
}
150155

151156
.actions { display: flex; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
152157

reviewbot/store.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,43 @@ def get_provider_config(self, config_id: str) -> Optional[dict[str, Any]]:
402402
return None
403403
return _decode_provider_config(row)
404404

405+
def allowed_orgs_for_repo(self, owner: str, repo: str) -> list[str]:
406+
"""Union of ``allowed_orgs`` across every provider_config whose
407+
``repo_pattern`` could match ``owner/repo`` (exact or wildcard).
408+
Used at request time to decide which orgs are worth probing via
409+
the GitHub App when the user's session has no cached org list —
410+
e.g. SAML-protected memberships that don't appear in
411+
``/user/orgs``."""
412+
exact = f"{owner}/{repo}".lower()
413+
wildcard = f"{owner}/*".lower()
414+
with self._lock:
415+
rows = self._conn.execute(
416+
"""
417+
SELECT allowed_orgs FROM provider_configs
418+
WHERE LOWER(repo_pattern) IN (?, ?)
419+
""",
420+
(exact, wildcard),
421+
).fetchall()
422+
seen: set[str] = set()
423+
result: list[str] = []
424+
for row in rows:
425+
raw = row["allowed_orgs"] or "[]"
426+
try:
427+
items = json.loads(raw)
428+
except json.JSONDecodeError:
429+
continue
430+
if not isinstance(items, list):
431+
continue
432+
for item in items:
433+
if not isinstance(item, str):
434+
continue
435+
lc = item.lower()
436+
if lc in seen:
437+
continue
438+
seen.add(lc)
439+
result.append(lc)
440+
return result
441+
405442
def find_provider_config(
406443
self,
407444
*,

0 commit comments

Comments
 (0)