tokenrace 1.0.0__py3-none-any.whl
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.
- tokenrace/__init__.py +3 -0
- tokenrace/_data/.streamlit/config.toml +11 -0
- tokenrace/_data/app.py +414 -0
- tokenrace/_data/assets/logo/tokenrace-favicon.png +0 -0
- tokenrace/_data/assets/logo/tokenrace-favicon.svg +14 -0
- tokenrace/_data/assets/logo/tokenrace-icon.png +0 -0
- tokenrace/_data/assets/logo/tokenrace-icon.svg +47 -0
- tokenrace/_data/assets/logo/tokenrace-mascot.png +0 -0
- tokenrace/_data/assets/logo/tokenrace-mascot.svg +57 -0
- tokenrace/_data/assets/style.css +37 -0
- tokenrace/_data/config/defaults.toml +27 -0
- tokenrace/_data/config/prompts.toml +39 -0
- tokenrace/_data/config/servers.toml +117 -0
- tokenrace/_data/locales/en.toml +194 -0
- tokenrace/_data/locales/it.toml +194 -0
- tokenrace/bench.py +238 -0
- tokenrace/cli.py +28 -0
- tokenrace/config.py +49 -0
- tokenrace/i18n.py +42 -0
- tokenrace-1.0.0.dist-info/METADATA +203 -0
- tokenrace-1.0.0.dist-info/RECORD +24 -0
- tokenrace-1.0.0.dist-info/WHEEL +4 -0
- tokenrace-1.0.0.dist-info/entry_points.txt +2 -0
- tokenrace-1.0.0.dist-info/licenses/LICENSE +21 -0
tokenrace/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Nessuna statistica d'uso inviata a Streamlit: l'app resta tutta locale.
|
|
2
|
+
[browser]
|
|
3
|
+
gatherUsageStats = false
|
|
4
|
+
|
|
5
|
+
[client]
|
|
6
|
+
# Nasconde il pulsante "Deploy" e lascia solo il menu essenziale.
|
|
7
|
+
toolbarMode = "minimal"
|
|
8
|
+
|
|
9
|
+
[theme]
|
|
10
|
+
primaryColor = "#FF5A1F"
|
|
11
|
+
baseRadius = "0.6rem"
|
tokenrace/_data/app.py
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""TokenRace - Local LLM Speed Test.
|
|
2
|
+
|
|
3
|
+
Interfaccia Streamlit. La logica di misura sta in tokenrace/bench.py,
|
|
4
|
+
i valori modificabili in config/, i testi tradotti in locales/.
|
|
5
|
+
|
|
6
|
+
Avvio: streamlit run app.py
|
|
7
|
+
Licenza: MIT
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import time
|
|
14
|
+
import uuid
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
import altair as alt # incluso con Streamlit
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import streamlit as st
|
|
20
|
+
|
|
21
|
+
from tokenrace import bench
|
|
22
|
+
from tokenrace.config import ROOT, load_defaults, load_prompts, load_servers
|
|
23
|
+
from tokenrace.i18n import Translator, language_info
|
|
24
|
+
|
|
25
|
+
# Metriche riassunte nella tabella di riepilogo (chiavi interne del CSV)
|
|
26
|
+
SUMMARY_METRICS = ["ttft", "tok_s_gen", "tok_s_eff", "total", "prompt_tok_s"]
|
|
27
|
+
# Colonne della tabella delle ripetizioni del test in corso
|
|
28
|
+
RUN_COLUMNS = ["run", "cold"] + SUMMARY_METRICS + ["tok_out"]
|
|
29
|
+
# Spiegazione (sezione [help]) di ciascuna colonna; le altre usano [colhelp]
|
|
30
|
+
COLUMN_HELP = {"ttft": "help.ttft", "tok_s_gen": "help.gen", "tok_s_eff": "help.eff",
|
|
31
|
+
"total": "help.total", "prompt_tok_s": "help.prompt_speed",
|
|
32
|
+
"ram_before": "help.ram_before", "ram_peak": "help.ram_peak"}
|
|
33
|
+
PRESET_IDS = ["short", "long", "cache"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Utilità
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def fmt(v, dec=2):
|
|
41
|
+
return "—" if v is None else f"{v:.{dec}f}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def reasoning_md(t, text: str) -> str:
|
|
45
|
+
if not text:
|
|
46
|
+
return ""
|
|
47
|
+
return f"> 💭 *{t('label.reasoning')}:* " + text[-3000:].replace("\n", " ") + "\n\n"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def status_badge(text: str, state: str) -> str:
|
|
51
|
+
"""Etichetta dello stato della ripetizione (state: running, done)."""
|
|
52
|
+
return (f'<div class="tr-status-wrap"><span class="tr-status tr-status-{state}">'
|
|
53
|
+
f'<span class="tr-dot"></span>{text}</span></div>')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def error_text(t, e: bench.ServerError) -> str:
|
|
57
|
+
return t(f"err.{e.code}", **e.params)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def pick_language(languages: list[str], default: str) -> str:
|
|
61
|
+
"""Lingua indicata nell'URL (?lang=), altrimenti quella predefinita."""
|
|
62
|
+
qp = st.query_params.get("lang")
|
|
63
|
+
env = os.environ.get("TOKENRACE_LANG") # impostata dal comando `tokenrace --lang`
|
|
64
|
+
return qp if qp in languages else env if env in languages else default
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def show_table(t, df: pd.DataFrame):
|
|
68
|
+
"""Mostra un DataFrame con colonne interne tradotte e spiegazioni nelle intestazioni."""
|
|
69
|
+
names, config = {}, {}
|
|
70
|
+
for c in df.columns:
|
|
71
|
+
base, _, stat = c.partition("·")
|
|
72
|
+
names[c] = t(f"col.{base}") + (f" · {t('stat.' + stat)}" if stat else "")
|
|
73
|
+
help_key = COLUMN_HELP.get(base, f"colhelp.{base}")
|
|
74
|
+
if base in ("server", "model"):
|
|
75
|
+
config[names[c]] = st.column_config.TextColumn(width="medium")
|
|
76
|
+
elif t.has(help_key):
|
|
77
|
+
config[names[c]] = st.column_config.Column(help=t(help_key))
|
|
78
|
+
st.dataframe(df.rename(columns=names), hide_index=True, width="stretch", column_config=config)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def make_row(ctx, rip: int, m: dict) -> dict:
|
|
82
|
+
p = ctx["params"]
|
|
83
|
+
return {
|
|
84
|
+
"ts": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
85
|
+
"server": ctx["server"].name,
|
|
86
|
+
"url": ctx["base_url"],
|
|
87
|
+
"model": ctx["model"],
|
|
88
|
+
"prompt_type": ctx["preset"],
|
|
89
|
+
"prompt_lang": ctx["lang"],
|
|
90
|
+
"run": rip,
|
|
91
|
+
"cold": rip == 1,
|
|
92
|
+
"temperature": p["temperature"],
|
|
93
|
+
"seed": p["seed"],
|
|
94
|
+
"max_tokens": p["max_tokens"],
|
|
95
|
+
"repetitions": p["repetitions"],
|
|
96
|
+
"no_reasoning": bool(p["no_reasoning"]),
|
|
97
|
+
"no_reasoning_fields": json.dumps(p["no_reasoning"]) if p["no_reasoning"] else "",
|
|
98
|
+
**{k: m[k] for k in ("ttft", "tok_s_gen", "tok_s_eff", "total", "tok_in", "tok_out",
|
|
99
|
+
"tok_reason", "tok_answer", "prompt_tok_s", "ram_before",
|
|
100
|
+
"ram_peak", "estimated", "split_estimated")},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# Sezioni della pagina
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
# Mascotte nell'intestazione, incorporata nella pagina come data URI
|
|
109
|
+
LOGO = "data:image/svg+xml;base64," + base64.b64encode(
|
|
110
|
+
(ROOT / "assets" / "logo" / "tokenrace-icon.svg").read_bytes()).decode()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def render_header(t, n_servers: int, languages: list[str], lang: str) -> str:
|
|
114
|
+
left, right = st.columns([6, 1], vertical_alignment="center")
|
|
115
|
+
left.markdown(
|
|
116
|
+
f"""<div class="tr-hero"><h1><img src="{LOGO}" alt="">Token<span>Race</span></h1>
|
|
117
|
+
<p>{t('app.tagline')}</p>
|
|
118
|
+
<span class="tr-pill">{t('app.servers_pill', n=n_servers)}</span></div>""",
|
|
119
|
+
unsafe_allow_html=True)
|
|
120
|
+
info = {code: language_info(code) for code in languages}
|
|
121
|
+
# Il valore iniziale arriva da st.session_state["lang_choice"], impostato in main()
|
|
122
|
+
choice = right.segmented_control(
|
|
123
|
+
t("label.language"), languages, key="lang_choice",
|
|
124
|
+
format_func=lambda c: f"{info[c]['flag']} {c.upper()}",
|
|
125
|
+
label_visibility="collapsed", help=t("help.language"))
|
|
126
|
+
return choice or lang
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def render_server_form(t, servers, measure):
|
|
130
|
+
ss = st.session_state
|
|
131
|
+
by_id = {s.id: s for s in servers}
|
|
132
|
+
with st.container(border=True):
|
|
133
|
+
st.markdown(f"##### {t('section.server')}")
|
|
134
|
+
c1, c2, c5, c3, c4 = st.columns([1.4, 1.9, 0.9, 2, 1.6], vertical_alignment="bottom")
|
|
135
|
+
server = by_id[c1.selectbox(t("label.server"), list(by_id), key="server",
|
|
136
|
+
format_func=lambda i: by_id[i].name, help=t("help.server"))]
|
|
137
|
+
base_url = c2.text_input(t("label.url"), value=server.url, key=f"url_{server.id}",
|
|
138
|
+
help=t("help.url"))
|
|
139
|
+
api_key = c5.text_input(t("label.api_key"), type="password", key=f"key_{server.id}",
|
|
140
|
+
help=t("help.api_key"))
|
|
141
|
+
error = None
|
|
142
|
+
if c4.button(t("label.refresh"), help=t("help.refresh"), width="stretch"):
|
|
143
|
+
try:
|
|
144
|
+
ss.models[base_url] = bench.fetch_models(base_url, api_key,
|
|
145
|
+
measure["models_timeout_s"])
|
|
146
|
+
if not ss.models[base_url]:
|
|
147
|
+
error = ("warning", t("msg.no_models"))
|
|
148
|
+
except bench.ServerError as e:
|
|
149
|
+
ss.models.pop(base_url, None)
|
|
150
|
+
error = ("error", error_text(t, e))
|
|
151
|
+
models = ss.models.get(base_url, [])
|
|
152
|
+
model = c3.selectbox(t("label.model"), models, key=f"model_{base_url}",
|
|
153
|
+
placeholder=t("label.model_placeholder"),
|
|
154
|
+
index=0 if models else None, help=t("help.model"))
|
|
155
|
+
note = t(f"server_notes.{server.id}")
|
|
156
|
+
start = f"**{t('label.start_cmd')}:** `{server.start}` · " if server.start else ""
|
|
157
|
+
st.caption(start + note)
|
|
158
|
+
if error:
|
|
159
|
+
getattr(st, error[0])(error[1])
|
|
160
|
+
return server, base_url, api_key, model
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def render_prompt_form(t, server, presets, defaults, model):
|
|
164
|
+
ss = st.session_state
|
|
165
|
+
d = defaults["params"]
|
|
166
|
+
with st.container(border=True):
|
|
167
|
+
st.markdown(f"##### {t('section.prompt')}")
|
|
168
|
+
r = st.columns([1.3, 1, 1, 1, 1, 1.2, 1], vertical_alignment="bottom")
|
|
169
|
+
# Una chiave per lingua: le etichette cambiano con la lingua, il valore scelto
|
|
170
|
+
# (short/long/cache) è tenuto a parte in ss.preset.
|
|
171
|
+
widget_key = f"preset_{t.lang}"
|
|
172
|
+
|
|
173
|
+
def on_preset():
|
|
174
|
+
ss.preset = ss[widget_key]
|
|
175
|
+
ss.prompt = presets[ss.preset]
|
|
176
|
+
|
|
177
|
+
r[0].selectbox(t("label.preset"), PRESET_IDS, index=PRESET_IDS.index(ss.preset),
|
|
178
|
+
key=widget_key, format_func=lambda i: t(f"preset.{i}"),
|
|
179
|
+
on_change=on_preset, help=t("help.preset"))
|
|
180
|
+
params = {
|
|
181
|
+
"temperature": r[1].number_input(t("label.temperature"), 0.0, 2.0, float(d["temperature"]),
|
|
182
|
+
0.1, key="temperature", help=t("help.temperature")),
|
|
183
|
+
"seed": int(r[2].number_input(t("label.seed"), value=int(d["seed"]), step=1, key="seed",
|
|
184
|
+
help=t("help.seed"))),
|
|
185
|
+
"max_tokens": int(r[3].number_input(t("label.max_tokens"), 1, 32768, int(d["max_tokens"]),
|
|
186
|
+
64, key="max_tokens", help=t("help.max_tokens"))),
|
|
187
|
+
"repetitions": int(r[4].number_input(t("label.repetitions"), 1, 50, int(d["repetitions"]),
|
|
188
|
+
1, key="repetitions", help=t("help.repetitions"))),
|
|
189
|
+
}
|
|
190
|
+
start = r[5].button(t("label.start"), type="primary", disabled=not model,
|
|
191
|
+
help=t("help.start"), width="stretch")
|
|
192
|
+
stop = r[6].button(t("label.stop"), help=t("help.stop"), width="stretch")
|
|
193
|
+
|
|
194
|
+
prompt = st.text_area(t("label.prompt"), key="prompt", height=90, help=t("help.prompt"))
|
|
195
|
+
n1, n2 = st.columns([3, 1.2], vertical_alignment="center")
|
|
196
|
+
n1.caption(f"**{t('preset.' + ss.preset)}**: {t('preset_descr.' + ss.preset)} · "
|
|
197
|
+
+ t("preset_descr.token_estimate",
|
|
198
|
+
n=f"{bench.estimate_tokens(prompt):,}".replace(",", ".")))
|
|
199
|
+
fields = json.dumps(server.no_reasoning)
|
|
200
|
+
state = t("msg.nr_tested") if server.no_reasoning_tested else t("msg.nr_untested")
|
|
201
|
+
nr_on = n2.toggle(t("label.no_reasoning"), key="no_reasoning",
|
|
202
|
+
help=t("help.no_reasoning") + "\n\n"
|
|
203
|
+
+ t("msg.nr_help_suffix", server=server.name, fields=fields) + f" ({state}).")
|
|
204
|
+
params["no_reasoning"] = server.no_reasoning if nr_on else None
|
|
205
|
+
if nr_on:
|
|
206
|
+
st.caption(t("msg.nr_sends", fields=fields, state=state))
|
|
207
|
+
if stop:
|
|
208
|
+
st.warning(t("msg.stopped"))
|
|
209
|
+
return prompt, params, start
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def run_test(t, ctx, prompt, run_id_label, measure, resp_box, status_box, live_box, reps_box):
|
|
213
|
+
"""Esegue le ripetizioni aggiornando risposta e misure in tempo reale."""
|
|
214
|
+
ss = st.session_state
|
|
215
|
+
p = ctx["params"]
|
|
216
|
+
rows = []
|
|
217
|
+
for rip in range(1, p["repetitions"] + 1):
|
|
218
|
+
text = prompt
|
|
219
|
+
if ctx["preset"] in ("short", "long"):
|
|
220
|
+
text = f"[{run_id_label}: {uuid.uuid4()}]\n{prompt}"
|
|
221
|
+
last_ui = [0.0]
|
|
222
|
+
status = t("msg.run_status", i=rip, n=p["repetitions"]) + (t("msg.cold") if rip == 1 else "")
|
|
223
|
+
status_box.markdown(status_badge(f"{status} · {t('msg.in_progress')}", "running"),
|
|
224
|
+
unsafe_allow_html=True)
|
|
225
|
+
|
|
226
|
+
def on_update(r_txt, c_txt, live, status=status):
|
|
227
|
+
now = time.perf_counter()
|
|
228
|
+
if now - last_ui[0] < measure["ui_refresh_s"]:
|
|
229
|
+
return
|
|
230
|
+
last_ui[0] = now
|
|
231
|
+
resp_box.markdown(reasoning_md(t, r_txt) + c_txt)
|
|
232
|
+
with live_box.container():
|
|
233
|
+
a, b = st.columns(2)
|
|
234
|
+
a.metric(t("metric.ttft"), f"{live['ttft']:.2f}", help=t("help.ttft"), border=True)
|
|
235
|
+
b.metric(t("metric.chunks"), live["chunks"], help=t("help.chunks"), border=True)
|
|
236
|
+
a.metric(t("metric.elapsed"), f"{live['elapsed']:.1f}", help=t("help.elapsed"),
|
|
237
|
+
border=True)
|
|
238
|
+
b.metric(t("metric.ram_peak"), f"{live['ram_peak']:.1f}", help=t("help.ram_peak"),
|
|
239
|
+
border=True)
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
m = bench.run_once(ctx["base_url"], ctx["model"], text, p["temperature"], p["seed"],
|
|
243
|
+
p["max_tokens"], on_update, p["no_reasoning"], ctx["api_key"],
|
|
244
|
+
ram_interval=measure["ram_interval_s"],
|
|
245
|
+
timeout=(measure["connect_timeout_s"], measure["read_timeout_s"]))
|
|
246
|
+
except bench.ServerError as e:
|
|
247
|
+
status_box.empty()
|
|
248
|
+
st.error(error_text(t, e))
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
resp_box.markdown(reasoning_md(t, m["reasoning"]) + m["content"])
|
|
252
|
+
status_box.markdown(status_badge(f"{status} · {t('msg.completed')}", "done"),
|
|
253
|
+
unsafe_allow_html=True)
|
|
254
|
+
row = make_row(ctx, rip, m)
|
|
255
|
+
ss.history.append(row)
|
|
256
|
+
rows.append(row)
|
|
257
|
+
|
|
258
|
+
with live_box.container():
|
|
259
|
+
a, b, c = st.columns(3)
|
|
260
|
+
a.metric(t("metric.ttft"), fmt(m["ttft"]), help=t("help.ttft"), border=True)
|
|
261
|
+
b.metric(t("metric.gen"), fmt(m["tok_s_gen"], 1), help=t("help.gen"), border=True)
|
|
262
|
+
c.metric(t("metric.eff"), fmt(m["tok_s_eff"], 1), help=t("help.eff"), border=True)
|
|
263
|
+
a.metric(t("metric.total"), fmt(m["total"]), help=t("help.total"), border=True)
|
|
264
|
+
b.metric(t("metric.tokens_io"), f"{m['tok_in']}/{m['tok_out']}", help=t("help.tokens_io"),
|
|
265
|
+
border=True)
|
|
266
|
+
c.metric(t("metric.prompt_speed"), fmt(m["prompt_tok_s"], 0), help=t("help.prompt_speed"),
|
|
267
|
+
border=True)
|
|
268
|
+
a.metric(t("metric.split"), f"{m['tok_reason']}/{m['tok_answer']}", help=t("help.split"),
|
|
269
|
+
border=True)
|
|
270
|
+
b.metric(t("metric.ram_before"), f"{m['ram_before']:.1f}", help=t("help.ram_before"),
|
|
271
|
+
border=True)
|
|
272
|
+
c.metric(t("metric.ram_peak"), f"{m['ram_peak']:.1f}", help=t("help.ram_peak"), border=True)
|
|
273
|
+
st.caption(t("msg.ram_note"))
|
|
274
|
+
if m["estimated"]:
|
|
275
|
+
st.warning(t("msg.estimated"))
|
|
276
|
+
if p["no_reasoning"] and m["tok_reason"] > 0:
|
|
277
|
+
st.warning(t("msg.reasoning_still"))
|
|
278
|
+
if m["split_estimated"]:
|
|
279
|
+
st.caption(t("msg.split_estimated"))
|
|
280
|
+
|
|
281
|
+
with reps_box.container():
|
|
282
|
+
show_table(t, pd.DataFrame(rows)[RUN_COLUMNS].round(2))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def render_results(t, primary_color: str):
|
|
286
|
+
ss = st.session_state
|
|
287
|
+
with st.container(border=True):
|
|
288
|
+
st.markdown(f"##### {t('section.results')}")
|
|
289
|
+
if not ss.history:
|
|
290
|
+
st.info(t("msg.no_results"))
|
|
291
|
+
return
|
|
292
|
+
df = pd.DataFrame(ss.history)
|
|
293
|
+
df["prompt_type"] = df["prompt_type"].map(lambda i: t(f"preset.{i}"))
|
|
294
|
+
keys = ["server", "model", "prompt_type"]
|
|
295
|
+
warm = df[~df["cold"]]
|
|
296
|
+
medians, mins, maxs = [], [], []
|
|
297
|
+
for m in SUMMARY_METRICS:
|
|
298
|
+
medians.append(warm.groupby(keys)[m].median().rename(f"{m}·median"))
|
|
299
|
+
mins.append(df.groupby(keys)[m].min().rename(f"{m}·min"))
|
|
300
|
+
maxs.append(df.groupby(keys)[m].max().rename(f"{m}·max"))
|
|
301
|
+
summary = pd.concat(medians + mins + maxs, axis=1).reset_index()
|
|
302
|
+
|
|
303
|
+
g1, g2 = st.columns([3, 2])
|
|
304
|
+
with g1:
|
|
305
|
+
st.markdown(t("label.summary"))
|
|
306
|
+
show_table(t, summary.round(2))
|
|
307
|
+
with g2:
|
|
308
|
+
st.markdown(t("label.chart"))
|
|
309
|
+
if warm.empty:
|
|
310
|
+
st.caption(t("msg.need_two"))
|
|
311
|
+
else:
|
|
312
|
+
# Una barra per server e modello; il tipo di prompt si aggiunge
|
|
313
|
+
# all'etichetta solo se nello storico ce n'è più di uno.
|
|
314
|
+
groups = keys if warm["prompt_type"].nunique() > 1 else keys[:2]
|
|
315
|
+
bars = warm.groupby(groups)["tok_s_eff"].median().rename("tok/s").reset_index()
|
|
316
|
+
axis = t("label.chart_axis")
|
|
317
|
+
bars[axis] = bars[groups].astype(str).agg(" · ".join, axis=1)
|
|
318
|
+
base = alt.Chart(bars).encode(
|
|
319
|
+
x=alt.X("tok/s:Q", title="tok/s"),
|
|
320
|
+
y=alt.Y(f"{axis}:N", sort="-x", title=None, axis=alt.Axis(labelLimit=0)),
|
|
321
|
+
tooltip=[axis, alt.Tooltip("tok/s:Q", format=".1f")])
|
|
322
|
+
chart = base.mark_bar(color=primary_color, cornerRadiusEnd=4) + base.mark_text(
|
|
323
|
+
align="left", dx=4, fontWeight="bold").encode(text=alt.Text("tok/s:Q", format=".1f"))
|
|
324
|
+
st.altair_chart(chart.properties(height=50 + 45 * len(bars)), width="stretch")
|
|
325
|
+
|
|
326
|
+
with st.expander(t("label.all_runs")):
|
|
327
|
+
show_table(t, df.round(3))
|
|
328
|
+
|
|
329
|
+
d1, d2, _ = st.columns([1, 1, 5])
|
|
330
|
+
csv = df.rename(columns=lambda c: t(f"col.{c}")).to_csv(index=False).encode("utf-8")
|
|
331
|
+
d1.download_button(t("label.download"), csv,
|
|
332
|
+
file_name=f"tokenrace_{datetime.now():%Y%m%d_%H%M%S}.csv",
|
|
333
|
+
mime="text/csv", help=t("help.download"), width="stretch")
|
|
334
|
+
if d2.button(t("label.clear"), help=t("help.clear"), width="stretch"):
|
|
335
|
+
ss.history = []
|
|
336
|
+
st.rerun()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
# Pagina
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
def main():
|
|
344
|
+
st.set_page_config(page_title="TokenRace", page_icon=str(ROOT / "assets" / "logo" / "tokenrace-favicon.png"),
|
|
345
|
+
layout="wide")
|
|
346
|
+
defaults = load_defaults()
|
|
347
|
+
ui, measure = defaults["ui"], defaults["measure"]
|
|
348
|
+
servers = load_servers()
|
|
349
|
+
css = (ROOT / "assets" / "style.css").read_text(encoding="utf-8")
|
|
350
|
+
st.markdown(f"<style>:root {{--tr-primary: {ui['primary_color']};}}\n{css}</style>",
|
|
351
|
+
unsafe_allow_html=True)
|
|
352
|
+
|
|
353
|
+
ss = st.session_state
|
|
354
|
+
ss.setdefault("history", [])
|
|
355
|
+
ss.setdefault("models", {})
|
|
356
|
+
|
|
357
|
+
# Lingua: scelta nell'intestazione e ricordata nell'URL (?lang=it).
|
|
358
|
+
# Se l'URL cambia da fuori (link, ricarica), ha la precedenza sulla scelta in memoria.
|
|
359
|
+
url_lang = pick_language(ui["languages"], ui["default_language"])
|
|
360
|
+
if url_lang != ss.get("applied_lang"):
|
|
361
|
+
ss.lang_choice = url_lang
|
|
362
|
+
lang = ss.get("lang_choice") or ss.get("applied_lang") or url_lang
|
|
363
|
+
t = Translator(lang, ui["fallback_language"])
|
|
364
|
+
render_header(t, len(servers), ui["languages"], lang)
|
|
365
|
+
st.query_params["lang"] = lang
|
|
366
|
+
ss.applied_lang = lang
|
|
367
|
+
|
|
368
|
+
all_prompts = load_prompts()
|
|
369
|
+
target = defaults["long_prompt"]["target_chars"]
|
|
370
|
+
prompt_texts = all_prompts.get(lang, all_prompts[ui["fallback_language"]])
|
|
371
|
+
presets = bench.build_prompts(prompt_texts, target)
|
|
372
|
+
ss.setdefault("preset", "short")
|
|
373
|
+
# Alla prima apertura, o se cambia la lingua e il prompt non è stato modificato,
|
|
374
|
+
# mostra il prompt pronto nella lingua scelta.
|
|
375
|
+
old_lang = ss.get("prompt_lang")
|
|
376
|
+
if "prompt" not in ss:
|
|
377
|
+
ss.prompt = presets[ss.preset]
|
|
378
|
+
elif old_lang and old_lang != lang and old_lang in all_prompts:
|
|
379
|
+
if ss.prompt == bench.build_prompts(all_prompts[old_lang], target)[ss.preset]:
|
|
380
|
+
ss.prompt = presets[ss.preset]
|
|
381
|
+
ss.prompt_lang = lang
|
|
382
|
+
|
|
383
|
+
server, base_url, api_key, model = render_server_form(t, servers, measure)
|
|
384
|
+
prompt, params, start = render_prompt_form(t, server, presets, defaults, model)
|
|
385
|
+
|
|
386
|
+
left, right = st.columns([3, 2])
|
|
387
|
+
with left.container(border=True):
|
|
388
|
+
st.markdown(f"##### {t('section.response')}")
|
|
389
|
+
resp_box = st.container(height=ui["response_height"], border=False).empty()
|
|
390
|
+
with right.container(border=True):
|
|
391
|
+
h1, h2 = st.columns([3, 2], vertical_alignment="center")
|
|
392
|
+
h1.markdown(f"##### {t('section.live')}")
|
|
393
|
+
status_box = h2.empty()
|
|
394
|
+
live_box = st.empty()
|
|
395
|
+
reps_box = st.empty()
|
|
396
|
+
|
|
397
|
+
if start and model:
|
|
398
|
+
ctx = {"server": server, "base_url": base_url, "api_key": api_key, "model": model,
|
|
399
|
+
"preset": ss.preset, "lang": lang, "params": params}
|
|
400
|
+
run_test(t, ctx, prompt, prompt_texts["run_id"], measure, resp_box, status_box, live_box,
|
|
401
|
+
reps_box)
|
|
402
|
+
else:
|
|
403
|
+
resp_box.caption(t("msg.response_placeholder"))
|
|
404
|
+
live_box.caption(t("msg.live_placeholder"))
|
|
405
|
+
|
|
406
|
+
render_results(t, ui["primary_color"])
|
|
407
|
+
repo = ui["repo_url"]
|
|
408
|
+
st.markdown(f'<div class="tr-footer">TokenRace · {t("app.footer")} · '
|
|
409
|
+
f'<a href="{repo}" target="_blank">{repo.removeprefix("https://")}</a></div>',
|
|
410
|
+
unsafe_allow_html=True)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
if __name__ == "__main__":
|
|
414
|
+
main()
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<defs><pattern id="c2" width="10" height="10" patternUnits="userSpaceOnUse">
|
|
3
|
+
<rect width="10" height="10" fill="#FFFFFF"/><rect width="5" height="5" fill="#1E1E1E"/><rect x="5" y="5" width="5" height="5" fill="#1E1E1E"/></pattern>
|
|
4
|
+
<clipPath id="cc"><circle cx="31" cy="33" r="27"/></clipPath></defs>
|
|
5
|
+
<circle cx="33" cy="33.5" r="30.5" fill="#FFFFFF"/>
|
|
6
|
+
<circle cx="34" cy="34" r="27" fill="#B93A0C" stroke="#2A1A12" stroke-width="4"/>
|
|
7
|
+
<circle cx="31" cy="33" r="27" fill="#FF5A1F"/>
|
|
8
|
+
<g clip-path="url(#cc)"><path d="M0 16 Q31 6 64 16 L64 26 Q31 16 0 26 Z" fill="url(#c2)" stroke="#2A1A12" stroke-width="3"/></g>
|
|
9
|
+
<circle cx="31" cy="33" r="27" fill="none" stroke="#2A1A12" stroke-width="4"/>
|
|
10
|
+
<ellipse cx="24" cy="35" rx="5" ry="6.5" fill="#FFFFFF" stroke="#2A1A12" stroke-width="2.5"/>
|
|
11
|
+
<ellipse cx="39" cy="35" rx="5" ry="6.5" fill="#FFFFFF" stroke="#2A1A12" stroke-width="2.5"/>
|
|
12
|
+
<circle cx="26" cy="36" r="3" fill="#2A1A12"/><circle cx="41" cy="36" r="3" fill="#2A1A12"/>
|
|
13
|
+
<path d="M22 46 Q31 55 41 45 Z" fill="#2A1A12" stroke="#2A1A12" stroke-width="2.5" stroke-linejoin="round"/>
|
|
14
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6 28 250 206">
|
|
2
|
+
<defs>
|
|
3
|
+
<filter id="sticker" x="-15%" y="-15%" width="130%" height="130%">
|
|
4
|
+
<feMorphology in="SourceAlpha" operator="dilate" radius="6" result="d"/>
|
|
5
|
+
<feFlood flood-color="#FFFFFF"/><feComposite in2="d" operator="in" result="w"/>
|
|
6
|
+
<feMerge><feMergeNode in="w"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
7
|
+
</filter>
|
|
8
|
+
<clipPath id="edge"><circle cx="153" cy="133" r="80"/></clipPath>
|
|
9
|
+
<pattern id="chk" width="12" height="12" patternUnits="userSpaceOnUse">
|
|
10
|
+
<rect width="12" height="12" fill="#FFFFFF"/>
|
|
11
|
+
<rect width="6" height="6" fill="#1E1E1E"/><rect x="6" y="6" width="6" height="6" fill="#1E1E1E"/>
|
|
12
|
+
</pattern>
|
|
13
|
+
<radialGradient id="face" cx="0.38" cy="0.32" r="0.8">
|
|
14
|
+
<stop offset="0" stop-color="#FF8A4F"/><stop offset="0.55" stop-color="#FF5A1F"/><stop offset="1" stop-color="#E5470F"/>
|
|
15
|
+
</radialGradient>
|
|
16
|
+
<clipPath id="coin"><circle cx="140" cy="128" r="80"/></clipPath>
|
|
17
|
+
<clipPath id="mouth"><path d="M114 150 Q141 186 170 147 Z"/></clipPath>
|
|
18
|
+
</defs><g filter="url(#sticker)"><g transform="rotate(4 140 140)">
|
|
19
|
+
<g stroke-linecap="round" fill="none">
|
|
20
|
+
<path d="M4 150 H34" stroke="#FF5A1F" stroke-width="9"/>
|
|
21
|
+
<path d="M14 174 H52" stroke="#2A1A12" stroke-width="9"/>
|
|
22
|
+
<path d="M8 198 H58" stroke="#FFA27A" stroke-width="9"/>
|
|
23
|
+
</g>
|
|
24
|
+
<g stroke="#2A1A12" stroke-width="5" stroke-linejoin="round">
|
|
25
|
+
<path d="M70 86 C50 76 34 84 14 72 L20 94 C38 100 52 96 70 102 Z" fill="url(#chk)"/>
|
|
26
|
+
<path d="M70 100 C54 108 40 118 22 116 L32 132 C48 130 58 120 72 112 Z" fill="url(#chk)"/>
|
|
27
|
+
</g>
|
|
28
|
+
<circle cx="153" cy="133" r="80" fill="#B93A0C" stroke="#2A1A12" stroke-width="7"/>
|
|
29
|
+
<g clip-path="url(#edge)" stroke="#2A1A12" stroke-width="3.5" stroke-opacity="0.8"><path d="M164.8 66.0 L167.6 50.3"/><path d="M174.0 68.3 L179.0 53.1"/><path d="M182.8 71.9 L189.8 57.5"/><path d="M191.0 76.6 L200.0 63.4"/><path d="M198.5 82.5 L209.2 70.6"/><path d="M205.1 89.3 L217.3 79.0"/><path d="M210.7 97.0 L224.2 88.5"/><path d="M215.1 105.3 L229.7 98.8"/><path d="M218.4 114.3 L233.7 109.8"/><path d="M220.3 123.5 L236.2 121.3"/><path d="M221.0 133.0 L237.0 133.0"/><path d="M220.3 142.5 L236.2 144.7"/><path d="M218.4 151.7 L233.7 156.2"/><path d="M215.1 160.7 L229.7 167.2"/><path d="M210.7 169.0 L224.2 177.5"/><path d="M205.1 176.7 L217.3 187.0"/><path d="M198.5 183.5 L209.2 195.4"/><path d="M191.0 189.4 L200.0 202.6"/><path d="M182.8 194.1 L189.8 208.5"/><path d="M174.0 197.7 L179.0 212.9"/><path d="M164.8 200.0 L167.6 215.7"/><path d="M155.4 201.0 L155.9 216.9"/><path d="M145.9 200.6 L144.2 216.5"/><path d="M136.5 199.0 L132.7 214.5"/><path d="M127.5 196.0 L121.5 210.9"/><path d="M119.0 191.9 L111.0 205.7"/><path d="M111.1 186.6 L101.3 199.2"/><path d="M104.1 180.2 L92.6 191.4"/><path d="M98.0 173.0 L85.0 182.4"/><path d="M93.0 164.9 L78.8 172.4"/><path d="M89.1 156.3 L74.1 161.7"/><path d="M86.5 147.1 L70.8 150.5"/><path d="M85.2 137.7 L69.2 138.9"/><path d="M85.2 128.3 L69.2 127.1"/></g>
|
|
30
|
+
<circle cx="140" cy="128" r="80" fill="url(#face)"/>
|
|
31
|
+
<circle cx="140" cy="128" r="68" fill="none" stroke="#FFFFFF" stroke-opacity="0.28" stroke-width="4"/>
|
|
32
|
+
<path d="M82 118 A60 60 0 0 1 100 84" fill="none" stroke="#FFFFFF" stroke-opacity="0.55" stroke-width="7" stroke-linecap="round"/><g clip-path="url(#coin)">
|
|
33
|
+
<path d="M50 86 Q140 60 230 86 L230 106 Q140 80 50 106 Z" fill="url(#chk)" stroke="#2A1A12" stroke-width="5"/>
|
|
34
|
+
</g><circle cx="140" cy="128" r="80" fill="none" stroke="#2A1A12" stroke-width="7"/>
|
|
35
|
+
<g stroke="#2A1A12" stroke-linecap="round">
|
|
36
|
+
<path d="M106 106 Q117 100 129 106" stroke-width="7" fill="none"/><path d="M153 106 Q165 100 177 106" stroke-width="7" fill="none"/>
|
|
37
|
+
</g>
|
|
38
|
+
<g stroke="#2A1A12" stroke-width="5">
|
|
39
|
+
<ellipse cx="118" cy="128" rx="13" ry="17" fill="#FFFFFF"/>
|
|
40
|
+
<ellipse cx="164" cy="128" rx="13" ry="17" fill="#FFFFFF"/>
|
|
41
|
+
</g>
|
|
42
|
+
<circle cx="123" cy="131" r="7.5" fill="#2A1A12"/><circle cx="169" cy="131" r="7.5" fill="#2A1A12"/>
|
|
43
|
+
<circle cx="126" cy="127" r="2.6" fill="#FFFFFF"/><circle cx="172" cy="127" r="2.6" fill="#FFFFFF"/>
|
|
44
|
+
<ellipse cx="98" cy="154" rx="9" ry="6" fill="#FFB4A0" opacity="0.75"/>
|
|
45
|
+
<ellipse cx="186" cy="152" rx="9" ry="6" fill="#FFB4A0" opacity="0.75"/>
|
|
46
|
+
<path d="M114 150 Q141 186 170 147 Z" fill="#2A1A12" stroke="#2A1A12" stroke-width="5" stroke-linejoin="round"/>
|
|
47
|
+
<g clip-path="url(#mouth)"><ellipse cx="145" cy="172" rx="14" ry="8" fill="#FF8FA3"/></g></g></g></svg>
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-8 -4 272 264">
|
|
2
|
+
<defs>
|
|
3
|
+
<filter id="sticker" x="-15%" y="-15%" width="130%" height="130%">
|
|
4
|
+
<feMorphology in="SourceAlpha" operator="dilate" radius="6" result="d"/>
|
|
5
|
+
<feFlood flood-color="#FFFFFF"/><feComposite in2="d" operator="in" result="w"/>
|
|
6
|
+
<feMerge><feMergeNode in="w"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
7
|
+
</filter>
|
|
8
|
+
<clipPath id="edge"><circle cx="153" cy="133" r="80"/></clipPath>
|
|
9
|
+
<pattern id="chk" width="12" height="12" patternUnits="userSpaceOnUse">
|
|
10
|
+
<rect width="12" height="12" fill="#FFFFFF"/>
|
|
11
|
+
<rect width="6" height="6" fill="#1E1E1E"/><rect x="6" y="6" width="6" height="6" fill="#1E1E1E"/>
|
|
12
|
+
</pattern>
|
|
13
|
+
<radialGradient id="face" cx="0.38" cy="0.32" r="0.8">
|
|
14
|
+
<stop offset="0" stop-color="#FF8A4F"/><stop offset="0.55" stop-color="#FF5A1F"/><stop offset="1" stop-color="#E5470F"/>
|
|
15
|
+
</radialGradient>
|
|
16
|
+
<clipPath id="coin"><circle cx="140" cy="128" r="80"/></clipPath>
|
|
17
|
+
<clipPath id="mouth"><path d="M114 150 Q141 186 170 147 Z"/></clipPath>
|
|
18
|
+
</defs><g filter="url(#sticker)"><g transform="rotate(6 140 140)">
|
|
19
|
+
<g stroke-linecap="round" fill="none">
|
|
20
|
+
<path d="M4 150 H34" stroke="#FF5A1F" stroke-width="9"/>
|
|
21
|
+
<path d="M14 174 H52" stroke="#2A1A12" stroke-width="9"/>
|
|
22
|
+
<path d="M8 198 H58" stroke="#FFA27A" stroke-width="9"/>
|
|
23
|
+
</g>
|
|
24
|
+
<g stroke="#2A1A12" stroke-width="5" stroke-linejoin="round">
|
|
25
|
+
<path d="M70 86 C50 76 34 84 14 72 L20 94 C38 100 52 96 70 102 Z" fill="url(#chk)"/>
|
|
26
|
+
<path d="M70 100 C54 108 40 118 22 116 L32 132 C48 130 58 120 72 112 Z" fill="url(#chk)"/>
|
|
27
|
+
</g>
|
|
28
|
+
<g stroke="#2A1A12" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="9">
|
|
29
|
+
<path d="M74 150 Q54 158 46 142"/>
|
|
30
|
+
<path d="M118 200 Q104 214 86 204"/>
|
|
31
|
+
</g>
|
|
32
|
+
<circle cx="45" cy="135" r="11" fill="#FFFFFF" stroke="#2A1A12" stroke-width="5"/>
|
|
33
|
+
<g transform="translate(78 196) rotate(35)" stroke="#2A1A12" stroke-width="5" stroke-linejoin="round"><path d="M-17 7 Q-17 -10 0 -10 Q21 -8 23 5 Q21 11 -17 7 Z" fill="#FFFFFF"/><path d="M-12 3 H18" stroke="#FF5A1F" stroke-width="4"/></g>
|
|
34
|
+
<path d="M160 204 Q172 218 174 234" stroke="#2A1A12" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
35
|
+
<g transform="translate(180 238) rotate(0)" stroke="#2A1A12" stroke-width="5" stroke-linejoin="round"><path d="M-17 7 Q-17 -10 0 -10 Q21 -8 23 5 Q21 11 -17 7 Z" fill="#FFFFFF"/><path d="M-12 3 H18" stroke="#FF5A1F" stroke-width="4"/></g>
|
|
36
|
+
<circle cx="153" cy="133" r="80" fill="#B93A0C" stroke="#2A1A12" stroke-width="7"/>
|
|
37
|
+
<g clip-path="url(#edge)" stroke="#2A1A12" stroke-width="3.5" stroke-opacity="0.8"><path d="M164.8 66.0 L167.6 50.3"/><path d="M174.0 68.3 L179.0 53.1"/><path d="M182.8 71.9 L189.8 57.5"/><path d="M191.0 76.6 L200.0 63.4"/><path d="M198.5 82.5 L209.2 70.6"/><path d="M205.1 89.3 L217.3 79.0"/><path d="M210.7 97.0 L224.2 88.5"/><path d="M215.1 105.3 L229.7 98.8"/><path d="M218.4 114.3 L233.7 109.8"/><path d="M220.3 123.5 L236.2 121.3"/><path d="M221.0 133.0 L237.0 133.0"/><path d="M220.3 142.5 L236.2 144.7"/><path d="M218.4 151.7 L233.7 156.2"/><path d="M215.1 160.7 L229.7 167.2"/><path d="M210.7 169.0 L224.2 177.5"/><path d="M205.1 176.7 L217.3 187.0"/><path d="M198.5 183.5 L209.2 195.4"/><path d="M191.0 189.4 L200.0 202.6"/><path d="M182.8 194.1 L189.8 208.5"/><path d="M174.0 197.7 L179.0 212.9"/><path d="M164.8 200.0 L167.6 215.7"/><path d="M155.4 201.0 L155.9 216.9"/><path d="M145.9 200.6 L144.2 216.5"/><path d="M136.5 199.0 L132.7 214.5"/><path d="M127.5 196.0 L121.5 210.9"/><path d="M119.0 191.9 L111.0 205.7"/><path d="M111.1 186.6 L101.3 199.2"/><path d="M104.1 180.2 L92.6 191.4"/><path d="M98.0 173.0 L85.0 182.4"/><path d="M93.0 164.9 L78.8 172.4"/><path d="M89.1 156.3 L74.1 161.7"/><path d="M86.5 147.1 L70.8 150.5"/><path d="M85.2 137.7 L69.2 138.9"/><path d="M85.2 128.3 L69.2 127.1"/></g>
|
|
38
|
+
<circle cx="140" cy="128" r="80" fill="url(#face)"/>
|
|
39
|
+
<circle cx="140" cy="128" r="68" fill="none" stroke="#FFFFFF" stroke-opacity="0.28" stroke-width="4"/>
|
|
40
|
+
<path d="M82 118 A60 60 0 0 1 100 84" fill="none" stroke="#FFFFFF" stroke-opacity="0.55" stroke-width="7" stroke-linecap="round"/><g clip-path="url(#coin)">
|
|
41
|
+
<path d="M50 86 Q140 60 230 86 L230 106 Q140 80 50 106 Z" fill="url(#chk)" stroke="#2A1A12" stroke-width="5"/>
|
|
42
|
+
</g><circle cx="140" cy="128" r="80" fill="none" stroke="#2A1A12" stroke-width="7"/>
|
|
43
|
+
<g stroke="#2A1A12" stroke-linecap="round">
|
|
44
|
+
<path d="M106 106 Q117 100 129 106" stroke-width="7" fill="none"/><path d="M153 106 Q165 100 177 106" stroke-width="7" fill="none"/>
|
|
45
|
+
</g>
|
|
46
|
+
<g stroke="#2A1A12" stroke-width="5">
|
|
47
|
+
<ellipse cx="118" cy="128" rx="13" ry="17" fill="#FFFFFF"/>
|
|
48
|
+
<ellipse cx="164" cy="128" rx="13" ry="17" fill="#FFFFFF"/>
|
|
49
|
+
</g>
|
|
50
|
+
<circle cx="123" cy="131" r="7.5" fill="#2A1A12"/><circle cx="169" cy="131" r="7.5" fill="#2A1A12"/>
|
|
51
|
+
<circle cx="126" cy="127" r="2.6" fill="#FFFFFF"/><circle cx="172" cy="127" r="2.6" fill="#FFFFFF"/>
|
|
52
|
+
<ellipse cx="98" cy="154" rx="9" ry="6" fill="#FFB4A0" opacity="0.75"/>
|
|
53
|
+
<ellipse cx="186" cy="152" rx="9" ry="6" fill="#FFB4A0" opacity="0.75"/>
|
|
54
|
+
<path d="M114 150 Q141 186 170 147 Z" fill="#2A1A12" stroke="#2A1A12" stroke-width="5" stroke-linejoin="round"/>
|
|
55
|
+
<g clip-path="url(#mouth)"><ellipse cx="145" cy="172" rx="14" ry="8" fill="#FF8FA3"/></g>
|
|
56
|
+
<path d="M214 148 Q240 152 242 126" stroke="#2A1A12" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
57
|
+
<circle cx="242" cy="119" r="11" fill="#FFFFFF" stroke="#2A1A12" stroke-width="5"/></g></g></svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Stile di TokenRace. Il colore principale arriva da config/defaults.toml
|
|
2
|
+
come variabile --tr-primary. */
|
|
3
|
+
|
|
4
|
+
.block-container {padding-top: 2rem; padding-bottom: 1rem; max-width: 1400px;}
|
|
5
|
+
h5 {margin: 0 0 0.6rem 0 !important; padding: 0 !important;}
|
|
6
|
+
[data-testid="stVerticalBlock"] {gap: 0.5rem;}
|
|
7
|
+
[data-testid="stWidgetLabel"] p {font-size: 0.8rem;}
|
|
8
|
+
.stTextInput input, .stNumberInput input, .stTextArea textarea,
|
|
9
|
+
[data-baseweb="select"] div {font-size: 0.85rem;}
|
|
10
|
+
[data-testid="stCaptionContainer"] {margin-top: -0.3rem;}
|
|
11
|
+
[data-testid="stMetricValue"] {font-size: 1.5rem;}
|
|
12
|
+
[data-testid="stMetricLabel"] p {font-size: 0.75rem;}
|
|
13
|
+
|
|
14
|
+
/* Intestazione */
|
|
15
|
+
.tr-hero {display: flex; align-items: baseline; gap: 0.8rem; flex-wrap: wrap;
|
|
16
|
+
margin-bottom: 0.6rem;}
|
|
17
|
+
.tr-hero h1 {font-size: 2rem; margin: 0; padding: 0; display: flex; align-items: center;}
|
|
18
|
+
.tr-hero h1 img {height: 2.6rem; margin-right: 0.45rem;}
|
|
19
|
+
.tr-hero h1 span {color: var(--tr-primary);}
|
|
20
|
+
.tr-hero p {margin: 0; opacity: 0.65; font-size: 0.95rem;}
|
|
21
|
+
.tr-pill {display: inline-block; padding: 0.1rem 0.6rem; border-radius: 999px;
|
|
22
|
+
background: color-mix(in srgb, var(--tr-primary) 12%, transparent);
|
|
23
|
+
color: var(--tr-primary); font-size: 0.75rem; font-weight: 600;}
|
|
24
|
+
|
|
25
|
+
/* Piè di pagina */
|
|
26
|
+
.tr-footer {text-align: center; opacity: 0.55; font-size: 0.8rem; margin-top: 1.5rem;}
|
|
27
|
+
|
|
28
|
+
/* Stato della ripetizione, in alto a destra del riquadro delle misure */
|
|
29
|
+
.tr-status-wrap {text-align: right; margin-bottom: 1rem;}
|
|
30
|
+
.tr-status {display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.15rem 0.7rem;
|
|
31
|
+
border-radius: 999px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;}
|
|
32
|
+
.tr-dot {width: 0.5rem; height: 0.5rem; border-radius: 50%; background: currentColor;}
|
|
33
|
+
.tr-status-running {color: var(--tr-primary);
|
|
34
|
+
background: color-mix(in srgb, var(--tr-primary) 12%, transparent);}
|
|
35
|
+
.tr-status-running .tr-dot {animation: tr-pulse 1s ease-in-out infinite;}
|
|
36
|
+
.tr-status-done {color: #1a9f53; background: color-mix(in srgb, #1a9f53 12%, transparent);}
|
|
37
|
+
@keyframes tr-pulse {0%, 100% {opacity: 1;} 50% {opacity: 0.25;}}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Valori predefiniti di TokenRace. Modificali liberamente.
|
|
2
|
+
|
|
3
|
+
[params]
|
|
4
|
+
# Valori iniziali dei campi del test (si possono cambiare anche dall'interfaccia)
|
|
5
|
+
temperature = 0.0
|
|
6
|
+
seed = 42
|
|
7
|
+
max_tokens = 512
|
|
8
|
+
repetitions = 3
|
|
9
|
+
|
|
10
|
+
[measure]
|
|
11
|
+
ram_interval_s = 0.5 # ogni quanto campionare la RAM di sistema
|
|
12
|
+
ui_refresh_s = 0.1 # ogni quanto aggiornare risposta e misure durante lo streaming
|
|
13
|
+
connect_timeout_s = 5 # attesa massima per collegarsi al server
|
|
14
|
+
read_timeout_s = 600 # attesa massima per la risposta
|
|
15
|
+
models_timeout_s = 5 # attesa massima per GET /models
|
|
16
|
+
|
|
17
|
+
[long_prompt]
|
|
18
|
+
target_chars = 32000 # lunghezza del contesto dei prompt "Lungo" e "Cache" (≈ 8.000 token)
|
|
19
|
+
|
|
20
|
+
[ui]
|
|
21
|
+
languages = ["en", "it"] # lingue disponibili: un file locales/<codice>.toml per ciascuna
|
|
22
|
+
default_language = "en" # lingua all'apertura (si cambia dal selettore o con ?lang=it)
|
|
23
|
+
fallback_language = "en" # usata per i testi che mancano in una traduzione
|
|
24
|
+
response_height = 430 # altezza in pixel del riquadro della risposta
|
|
25
|
+
# Il colore principale va cambiato in due posti: qui (per lo stile) e in .streamlit/config.toml
|
|
26
|
+
primary_color = "#FF5A1F"
|
|
27
|
+
repo_url = "https://github.com/ernext/tokenrace"
|