@wendellresearch-ai/benchwarmers 0.5.3 → 0.5.4
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/VERSION +1 -1
- package/package.json +1 -1
- package/scripts/test_serve_edit.py +9 -0
- package/serve.py +14 -4
- package/ui/bench.css +18 -8
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.5.
|
|
1
|
+
0.5.4
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wendellresearch-ai/benchwarmers",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Self-hosted panel gateway for LLMs: ask a collection of models at once, keep every answer side by side, rotate explorer models in, and let your verdicts pick the next panel.",
|
|
5
5
|
"mcpName": "io.github.WendellResearch-AI/benchwarmers",
|
|
6
6
|
"license": "MIT",
|
|
@@ -461,6 +461,15 @@ def main() -> int:
|
|
|
461
461
|
print(("ok " if ok else "FAIL ") + "action needed drops alerts on retired models")
|
|
462
462
|
failures += 0 if ok else 1
|
|
463
463
|
|
|
464
|
+
# A provider's words end where its link begins, and are not cut short here:
|
|
465
|
+
# the page cuts them to a line, and a phone, with no hover, shows them whole.
|
|
466
|
+
text, url = serve.said("Error: Error code: 403 - {'error': {'message': 'm needs your consent. Give it once at"
|
|
467
|
+
" https://p.example/consents?model=m; it covers every model.'}}")
|
|
468
|
+
long, _ = serve.said("Error: Error code: 429 - {'error': {'message': 'Too many requests. " + "w " * 80 + "end'}}")
|
|
469
|
+
ok = text.endswith("Give it once at") and url == "https://p.example/consents?model=m" and long.endswith("end")
|
|
470
|
+
print(("ok " if ok else "FAIL ") + "action needed shows a provider's words whole, its link after them")
|
|
471
|
+
failures += 0 if ok else 1
|
|
472
|
+
|
|
464
473
|
# Dismiss: one picker key or many in one write, a change-log line each;
|
|
465
474
|
# undo brings back only the ones named; nothing named is refused.
|
|
466
475
|
serve.DISMISSED_PATH = cfg / "dismissed.json"
|
package/serve.py
CHANGED
|
@@ -2007,7 +2007,9 @@ def said(detail: str) -> tuple[str, str]:
|
|
|
2007
2007
|
"""An error as one short line, and the link the provider put in it: the
|
|
2008
2008
|
status code and the provider's own words, without the JSON around them or
|
|
2009
2009
|
a lead-in such as "Too many requests." that says less than the sentence
|
|
2010
|
-
after it
|
|
2010
|
+
after it, ending where the link it gives begins (the link follows it on
|
|
2011
|
+
the page). Cut to a line by the page, not here: a phone shows it whole.
|
|
2012
|
+
The whole detail stays in the row's title."""
|
|
2011
2013
|
code = re.search(r"Error code: (\w+)", detail)
|
|
2012
2014
|
quoted = re.search(r"""['"](?:message|detail)['"]: ['"](.+?)(?:['"]\s*[,}]|$)""", detail)
|
|
2013
2015
|
text = (quoted.group(1) if quoted else re.sub(r"^.*?Error: ", "", detail) if "Error: " in detail
|
|
@@ -2017,8 +2019,16 @@ def said(detail: str) -> tuple[str, str]:
|
|
|
2017
2019
|
if rest and len(first) < 25:
|
|
2018
2020
|
text = rest
|
|
2019
2021
|
url = re.search(r"https?://[^\s;,'\"()]+", quoted.group(1) if quoted else "")
|
|
2022
|
+
if url and url.group(0) in text:
|
|
2023
|
+
text = text[:text.index(url.group(0))].rstrip()
|
|
2020
2024
|
text = (f"{code.group(1)} · " if code else "") + text
|
|
2021
|
-
return (text if len(text) <=
|
|
2025
|
+
return (text if len(text) <= 400 else text[:399].rstrip() + "…"), (url.group(0) if url else "")
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
def breakable(name: str) -> str:
|
|
2029
|
+
"""An escaped name that may wrap at a domain's dots (`…-api.unorouter.com`),
|
|
2030
|
+
never inside a version number (`llama-3.2`)."""
|
|
2031
|
+
return re.sub(r"\.(?=[a-z])", ".<wbr>", name)
|
|
2022
2032
|
|
|
2023
2033
|
|
|
2024
2034
|
def open_actions() -> tuple[dict[str, dict], list[tuple[str, dict]]]:
|
|
@@ -2128,7 +2138,7 @@ def actions_page(groups: dict, auto: list) -> str:
|
|
|
2128
2138
|
f"<span class='acts'>{got}</span></div>")
|
|
2129
2139
|
alias = esc(r["alias"])
|
|
2130
2140
|
return (f"<div class='a-row' data-row><a href='#' class='model' data-alias='{alias}'>"
|
|
2131
|
-
f"<span class='alias'>{alias}</span></a><span>{chip(r['label'], r['css'])}</span>"
|
|
2141
|
+
f"<span class='alias'>{breakable(alias)}</span></a><span>{chip(r['label'], r['css'])}</span>"
|
|
2132
2142
|
f"<span class='why' title='{esc(r['title'])}'><span class='said'>{esc(r['said'])}</span>{link(r['url'])}</span>"
|
|
2133
2143
|
f"<span class='acts'><button class='linkish' data-act='retry' data-alias='{alias}'"
|
|
2134
2144
|
" title='One call now; an answer clears the alert'>Retry</button>"
|
|
@@ -2155,7 +2165,7 @@ def actions_page(groups: dict, auto: list) -> str:
|
|
|
2155
2165
|
tags = load_json(DATA / "config" / "provider-tags.json") # a retired model's connection, by served name
|
|
2156
2166
|
gone = "".join(
|
|
2157
2167
|
f"<div class='a-row' data-row><a href='#' class='model' data-alias='{esc(alias)}'>"
|
|
2158
|
-
f"<span class='alias'>{esc(alias)}</span></a><span class='when'>{esc(r['at'][:10])}</span>"
|
|
2168
|
+
f"<span class='alias'>{breakable(esc(alias))}</span></a><span class='when'>{esc(r['at'][:10])}</span>"
|
|
2159
2169
|
f"<span class='why' title='{esc(r.get('why', ''))}'><span class='said'>"
|
|
2160
2170
|
f"{esc(tags.get(r.get('served', ''), '').strip('?') or r.get('endpoint') or endpoints.default())}"
|
|
2161
2171
|
f" · {esc(said(r.get('why', '').removeprefix('auto: '))[0])}</span></span>"
|
package/ui/bench.css
CHANGED
|
@@ -132,18 +132,21 @@ main { max-width: var(--page, 104rem); margin: 0 auto; padding: 0 var(--gutter)
|
|
|
132
132
|
background: var(--warn-ground); border-radius: var(--radius); font-size: .9375rem; overflow-wrap: anywhere; }
|
|
133
133
|
.a-held .chip { flex: none; background: var(--ink); color: var(--paper); }
|
|
134
134
|
.a-held a { color: var(--ink); font-weight: 500; }
|
|
135
|
-
|
|
135
|
+
/* The same fixed columns in every group and the retired list, so state,
|
|
136
|
+
words and actions line up down the whole page whatever each group's
|
|
137
|
+
longest name; a long name wraps in its column. */
|
|
138
|
+
#tab-needs > .panel { container-type: inline-size; }
|
|
139
|
+
.a-rows { display: grid; grid-template-columns: minmax(0, 32%) 8.25rem minmax(0, 1fr) 6.25rem;
|
|
136
140
|
gap: 0 1rem; align-items: baseline; }
|
|
137
141
|
.a-row { display: contents; }
|
|
138
142
|
.a-row > * { padding: .3rem 0; border-top: 1px solid color-mix(in oklab, var(--line) 55%, transparent); }
|
|
139
143
|
.a-row:first-child > * { border-top: 0; }
|
|
140
|
-
.a-row .alias, .a-row .what { font-weight: 500; }
|
|
141
|
-
.a-row .what { max-width: 24rem; } /* a notice's sentence wraps, leaving its detail room */
|
|
144
|
+
.a-row .alias, .a-row .what { font-weight: 500; overflow-wrap: anywhere; } /* a name breaks at its dots first (<wbr>) */
|
|
142
145
|
.a-row .why, .a-row .when { color: var(--muted); font-size: .875rem; overflow-wrap: anywhere; }
|
|
143
|
-
/* One line a model: the provider's words cut to the column, whole on hover;
|
|
146
|
+
/* One line a model: the provider's words cut to the column, whole on hover; the link gives way last. */
|
|
144
147
|
.a-row .why:has(> .said) { display: flex; gap: .5rem; min-width: 0; }
|
|
145
|
-
.a-row .said { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
146
|
-
.a-row .why > a { flex: none; }
|
|
148
|
+
.a-row .said { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
149
|
+
.a-row .why > a { flex: none; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
147
150
|
.a-row .acts { display: flex; gap: .9rem; font-size: .875rem; }
|
|
148
151
|
.linkish.danger { color: var(--err); }
|
|
149
152
|
.a-retired { margin-top: .75rem; border-top: 1px solid var(--line); padding-top: .5rem; }
|
|
@@ -542,11 +545,18 @@ dialog .pick { display: flex; gap: 1.5rem; }
|
|
|
542
545
|
button.c-row { grid-template-columns: minmax(0, 1fr); } /* the seats get the width; the whole row opens */
|
|
543
546
|
.c-row .open-hint { display: none; }
|
|
544
547
|
.a-line { margin: 1rem 1rem 0; }
|
|
545
|
-
|
|
548
|
+
}
|
|
549
|
+
/* A narrow page (a phone, or a window with the sidebar beside it): name and
|
|
550
|
+
state on one line, the words and actions under them. No gap between name
|
|
551
|
+
and state, so the line above a model runs unbroken. */
|
|
552
|
+
@container (max-width: 46rem) {
|
|
553
|
+
.a-rows { grid-template-columns: minmax(0, 1fr) max-content; column-gap: 0; }
|
|
554
|
+
.a-row > :nth-child(2) { padding-left: .75rem; }
|
|
546
555
|
.a-row .why, .a-row .acts { grid-column: 1 / -1; padding-top: 0; border-top: 0; }
|
|
547
556
|
.a-row .acts { padding-bottom: .5rem; }
|
|
548
557
|
.a-row .why:has(> .said) { flex-wrap: wrap; }
|
|
549
|
-
.a-row .said { white-space: normal; } /*
|
|
558
|
+
.a-row .said { white-space: normal; } /* the height to read it whole */
|
|
559
|
+
.a-row .why > a { max-width: none; }
|
|
550
560
|
}
|
|
551
561
|
@media (prefers-reduced-motion: reduce) {
|
|
552
562
|
* { transition: none !important; }
|