AdamTT commited on
Commit
bf8af24
·
verified ·
1 Parent(s): e9c4512

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +254 -109
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import time
2
- from typing import Dict, List, Tuple, Any
 
3
 
4
  import gradio as gr
5
  from huggingface_hub import HfApi
@@ -14,7 +15,7 @@ I18N: Dict[str, Dict[str, str]] = {
14
  "title": "Model Fit Finder (CPU)",
15
  "intro": (
16
  "Pick your NLP task and constraints. The Space will recommend an appropriate model type "
17
- "and list at least 3 concrete Hugging Face models, with short rationale."
18
  ),
19
  "ui_lang": "UI language",
20
  "tab_main": "Model advisor",
@@ -37,17 +38,21 @@ I18N: Dict[str, Dict[str, str]] = {
37
  "task_sim": "Semantic similarity / duplicates / search",
38
  "rec_type": "Recommended model type: {model_type}",
39
  "rationale": "Rationale:",
 
40
  "models_min3": "Models (min. 3):",
41
- "emb_note": "Note: embedding models do not generate text; they produce vectors for similarity/search.",
42
- "qa_note": "Note: extractive QA works best when you provide the relevant context text.",
43
- "instr_note": "Note: instruction-tuned models follow your prompts; smaller variants are CPU-friendly.",
 
 
 
44
  "bonus_note": "Popular model from Hub (selected by task tag and downloads).",
45
  },
46
  "PL": {
47
  "title": "Model Fit Finder (CPU)",
48
  "intro": (
49
  "Wybierz zadanie NLP i ograniczenia. Space zarekomenduje typ modelu "
50
- "i pokaże co najmniej 3 konkretne modele z Hugging Face wraz z uzasadnieniem."
51
  ),
52
  "ui_lang": "Język interfejsu",
53
  "tab_main": "Doradca modeli",
@@ -70,10 +75,14 @@ I18N: Dict[str, Dict[str, str]] = {
70
  "task_sim": "Semantyczne podobieństwo / duplikaty / wyszukiwanie",
71
  "rec_type": "Rekomendowany typ modelu: {model_type}",
72
  "rationale": "Uzasadnienie:",
 
73
  "models_min3": "Modele (min. 3):",
74
- "emb_note": "Uwaga: modele embeddingowe nie generują tekstu; produkują wektory do podobieństwa/wyszukiwania.",
75
- "qa_note": "Uwaga: QA extractive działa najlepiej, gdy podasz kontekst (tekst źródłowy).",
76
- "instr_note": "Uwaga: modele instrukcyjne wykonują polecenia; mniejsze warianty przyjazne dla CPU.",
 
 
 
77
  "bonus_note": "Popularny model z Hub (dobrany po tagu zadania i pobraniach).",
78
  },
79
  }
@@ -82,29 +91,80 @@ def t(ui_lang: str, key: str) -> str:
82
  return I18N.get(ui_lang, I18N["EN"]).get(key, I18N["EN"].get(key, key))
83
 
84
  # -----------------------
85
- # Stable baseline recommendations (min. 3 per type)
86
  # -----------------------
87
- RECOMMENDATIONS: Dict[str, List[Tuple[str, str]]] = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  "instruction": [
89
- ("google/flan-t5-small", "Light text2text, good CPU baseline for instruction following."),
90
- ("google/flan-t5-base", "Better quality, slower than small; still workable on CPU."),
91
- ("google-t5/t5-small", "Simple text2text fallback when you want a fast baseline."),
 
 
 
 
 
 
 
 
92
  ],
93
  "qa": [
94
- ("distilbert/distilbert-base-cased-distilled-squad", "Fast extractive QA on CPU; classic choice."),
95
- ("distilbert/distilbert-base-uncased-distilled-squad", "Very popular SQuAD QA default."),
96
- ("deepset/bert-base-cased-squad2", "SQuAD2; handles 'no answer' cases better."),
 
 
 
 
 
 
97
  ],
98
  "embeddings": [
99
- ("sentence-transformers/all-MiniLM-L6-v2", "Popular sentence embeddings; fast on CPU."),
100
- ("intfloat/e5-small-v2", "Strong retrieval embeddings; good quality/speed tradeoff."),
101
- ("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", "Multilingual; better for PL/mixed."),
 
 
 
 
 
 
 
102
  ],
103
  }
104
 
105
- # If you want Polish descriptions here as well, keep EN here and localize notes in output.
106
- # (Model IDs are universal; notes can be in EN and output can add localized note lines.)
107
-
108
  # -----------------------
109
  # Hub bonus models (cache)
110
  # -----------------------
@@ -112,10 +172,6 @@ _HUB_CACHE: Dict[Tuple[str, str], Tuple[float, List[str]]] = {}
112
  CACHE_TTL_SEC = 6 * 60 * 60 # 6h
113
 
114
  def _language_tag_predicate(tags: List[str], data_lang_value: str) -> bool:
115
- """
116
- data_lang_value is one of: EN, PL, MIXED (internal values).
117
- HF tags aren't perfectly consistent; we do best-effort filtering.
118
- """
119
  if data_lang_value == "MIXED":
120
  return True
121
  target = "en" if data_lang_value == "EN" else "pl"
@@ -123,15 +179,13 @@ def _language_tag_predicate(tags: List[str], data_lang_value: str) -> bool:
123
  tags_lower = {str(x).lower() for x in (tags or [])}
124
  return any(c in tags_lower for c in candidates)
125
 
126
- def hub_bonus_models(pipeline_tag: str, data_lang_value: str, limit: int = 12) -> List[str]:
127
  key = (pipeline_tag, data_lang_value)
128
  now = time.time()
129
-
130
  if key in _HUB_CACHE:
131
  ts, cached = _HUB_CACHE[key]
132
  if now - ts < CACHE_TTL_SEC:
133
  return cached
134
-
135
  try:
136
  models = api.list_models(filter=pipeline_tag, sort="downloads", direction=-1, limit=limit)
137
  out = []
@@ -146,102 +200,215 @@ def hub_bonus_models(pipeline_tag: str, data_lang_value: str, limit: int = 12) -
146
  return []
147
 
148
  # -----------------------
149
- # Internal "task ids" (do NOT depend on UI language)
150
  # -----------------------
151
- TASK_CHAT = "CHAT"
152
- TASK_QA = "QA"
153
- TASK_SIM = "SIM"
154
 
155
- def task_choices(ui_lang: str) -> List[Tuple[str, str]]:
156
- """Return Gradio dropdown choices as (label, value)."""
157
- return [
158
- (t(ui_lang, "task_chat"), TASK_CHAT),
159
- (t(ui_lang, "task_qa"), TASK_QA),
160
- (t(ui_lang, "task_sim"), TASK_SIM),
161
- ]
 
162
 
163
- def yesno_choices(ui_lang: str) -> List[Tuple[str, str]]:
164
- return [(t(ui_lang, "yes"), "YES"), (t(ui_lang, "no"), "NO")]
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
- def data_lang_choices(ui_lang: str) -> List[Tuple[str, str]]:
167
- return [(t(ui_lang, "en"), "EN"), (t(ui_lang, "pl"), "PL"), (t(ui_lang, "mixed"), "MIXED")]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- def priority_choices(ui_lang: str) -> List[Tuple[str, str]]:
170
- return [(t(ui_lang, "speed"), "SPEED"), (t(ui_lang, "quality"), "QUALITY")]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  # -----------------------
173
- # Recommendation logic
174
  # -----------------------
175
  def recommend(ui_lang: str, task_id: str, has_docs: str, data_lang_value: str, cpu_only: bool, priority: str) -> str:
 
 
176
  if task_id == TASK_SIM:
177
  model_type = "embeddings"
178
- why = (
179
- "You want semantic similarity / duplicate detection / search. Use embeddings + cosine similarity."
180
  if ui_lang == "EN"
181
- else "Chcesz podobieństwo semantyczne / duplikaty / wyszukiwanie. Użyj embeddingów + podobieństwa cosinusowego."
182
  )
183
  pipeline_tag = "sentence-similarity"
184
- note_key = "emb_note"
185
  elif task_id == TASK_QA:
186
  model_type = "qa"
187
- why = (
188
  "You have a context (document/text) and a question. Extractive QA finds answers in the context."
189
  if ui_lang == "EN"
190
- else "Masz kontekst (dokument/tekst) i pytanie. QA extractive znajduje odpowiedź w kontekście."
191
  )
192
  pipeline_tag = "question-answering"
193
- note_key = "qa_note"
 
 
194
  else:
195
  model_type = "instruction"
196
- why = (
197
  "You want instruction-following responses (chat/explain/summarize). Instruction-tuned models fit best."
198
  if ui_lang == "EN"
199
  else "Chcesz odpowiedzi sterowane poleceniem (chat/wyjaśnianie/streszczanie). Najlepsze są modele instrukcyjne."
200
  )
201
  pipeline_tag = "text-generation"
202
- note_key = "instr_note"
203
 
204
- recs = RECOMMENDATIONS[model_type].copy()
 
205
 
206
- # Add 1–2 "bonus" models from Hub, filtered by task tag + best-effort language tags.
207
- bonus = hub_bonus_models(pipeline_tag, data_lang_value, limit=12)
208
- existing = {mid for mid, _ in recs}
209
- bonus = [m for m in bonus if m not in existing]
210
- for m in bonus[:2]:
211
- recs.append((m, t(ui_lang, "bonus_note")))
212
 
 
213
  lines: List[str] = []
214
  lines.append(t(ui_lang, "rec_type").format(model_type=model_type))
215
  lines.append("")
216
  lines.append(t(ui_lang, "rationale"))
217
- lines.append(f"- {why}")
218
  lines.append("")
 
 
 
 
 
 
 
 
 
 
 
 
219
  lines.append(t(ui_lang, "models_min3"))
220
- for mid, note in recs[:5]:
221
- lines.append(f"- {mid} {note}")
 
 
 
 
 
222
  lines.append("")
223
- lines.append(t(ui_lang, note_key))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
 
 
225
  return "\n".join(lines)
226
 
227
  # -----------------------
228
  # Dynamic UI language updates
229
  # -----------------------
230
  def apply_language(ui_lang: str) -> Tuple[Any, ...]:
231
- """
232
- Returns gr.update objects for all UI text elements that should change when language changes.
233
- """
234
  return (
235
- gr.update(value=f"# {t(ui_lang, 'title')}\n{t(ui_lang, 'intro')}"), # header_md
236
- gr.update(label=t(ui_lang, "ui_lang")), # ui_lang radio label (cosmetic)
237
- gr.update(label=t(ui_lang, "task"), choices=task_choices(ui_lang)), # task dropdown
238
- gr.update(label=t(ui_lang, "has_docs"), choices=yesno_choices(ui_lang)), # has_docs
239
- gr.update(label=t(ui_lang, "data_lang"), choices=data_lang_choices(ui_lang)), # data_lang
240
- gr.update(label=t(ui_lang, "cpu_only")), # cpu_only
241
- gr.update(label=t(ui_lang, "priority"), choices=priority_choices(ui_lang)), # priority
242
- gr.update(value=t(ui_lang, "recommend_btn")), # button text
243
- gr.update(label=t(ui_lang, "result")), # output label
244
- gr.update(label=t(ui_lang, "tab_main")), # tab label (Gradio may not update tab titles live in all versions)
245
  )
246
 
247
  # -----------------------
@@ -250,38 +417,17 @@ def apply_language(ui_lang: str) -> Tuple[Any, ...]:
250
  with gr.Blocks(title=I18N["EN"]["title"]) as demo:
251
  header_md = gr.Markdown(f"# {t('EN', 'title')}\n{t('EN', 'intro')}")
252
 
253
- ui_lang = gr.Radio(
254
- choices=["EN", "PL"],
255
- value="EN",
256
- label=t("EN", "ui_lang"),
257
- )
258
 
259
- # Tab title live-update is not guaranteed across Gradio versions; we still keep the label update output.
260
  with gr.Tab(t("EN", "tab_main")) as tab_main:
261
- task = gr.Dropdown(
262
- choices=task_choices("EN"),
263
- value=TASK_SIM,
264
- label=t("EN", "task"),
265
- )
266
- has_docs = gr.Radio(
267
- choices=yesno_choices("EN"),
268
- value="YES",
269
- label=t("EN", "has_docs"),
270
- )
271
- data_lang = gr.Radio(
272
- choices=data_lang_choices("EN"),
273
- value="MIXED",
274
- label=t("EN", "data_lang"),
275
- )
276
  cpu_only = gr.Checkbox(value=True, label=t("EN", "cpu_only"))
277
- priority = gr.Radio(
278
- choices=priority_choices("EN"),
279
- value="SPEED",
280
- label=t("EN", "priority"),
281
- )
282
 
283
  recommend_btn = gr.Button(t("EN", "recommend_btn"))
284
- out = gr.Textbox(lines=18, label=t("EN", "result"))
285
 
286
  recommend_btn.click(
287
  fn=recommend,
@@ -289,7 +435,6 @@ with gr.Blocks(title=I18N["EN"]["title"]) as demo:
289
  outputs=[out],
290
  )
291
 
292
- # When UI language changes, update labels + choices.
293
  ui_lang.change(
294
  fn=apply_language,
295
  inputs=[ui_lang],
 
1
  import time
2
+ from dataclasses import dataclass
3
+ from typing import Dict, List, Tuple, Any, Optional
4
 
5
  import gradio as gr
6
  from huggingface_hub import HfApi
 
15
  "title": "Model Fit Finder (CPU)",
16
  "intro": (
17
  "Pick your NLP task and constraints. The Space will recommend an appropriate model type "
18
+ "and list at least 3 concrete Hugging Face models. Recommendations change based on your settings."
19
  ),
20
  "ui_lang": "UI language",
21
  "tab_main": "Model advisor",
 
38
  "task_sim": "Semantic similarity / duplicates / search",
39
  "rec_type": "Recommended model type: {model_type}",
40
  "rationale": "Rationale:",
41
+ "settings": "Settings used:",
42
  "models_min3": "Models (min. 3):",
43
+ "why_these": "Why these models:",
44
+ "warning": "Warning:",
45
+ "qa_need_docs": "Extractive QA needs a context document/text. With no documents, consider an instruction model or embeddings-based search.",
46
+ "note_emb": "Embedding models do not generate text; they produce vectors for similarity/search.",
47
+ "note_qa": "Extractive QA finds answers in the provided context.",
48
+ "note_instr": "Instruction-tuned models follow prompts; smaller variants are CPU-friendly.",
49
  "bonus_note": "Popular model from Hub (selected by task tag and downloads).",
50
  },
51
  "PL": {
52
  "title": "Model Fit Finder (CPU)",
53
  "intro": (
54
  "Wybierz zadanie NLP i ograniczenia. Space zarekomenduje typ modelu "
55
+ "i pokaże co najmniej 3 modele. Rekomendacje zmieniają się zależnie od ustawień."
56
  ),
57
  "ui_lang": "Język interfejsu",
58
  "tab_main": "Doradca modeli",
 
75
  "task_sim": "Semantyczne podobieństwo / duplikaty / wyszukiwanie",
76
  "rec_type": "Rekomendowany typ modelu: {model_type}",
77
  "rationale": "Uzasadnienie:",
78
+ "settings": "Użyte ustawienia:",
79
  "models_min3": "Modele (min. 3):",
80
+ "why_these": "Dlaczego te modele:",
81
+ "warning": "Ostrzeżenie:",
82
+ "qa_need_docs": "QA extractive wymaga kontekstu (dokumentu/tekstu). Bez dokumentów rozważ model instrukcyjny albo wyszukiwanie embeddingowe.",
83
+ "note_emb": "Modele embeddingowe nie generują tekstu; produkują wektory do podobieństwa/wyszukiwania.",
84
+ "note_qa": "QA extractive znajduje odpowiedzi w podanym kontekście.",
85
+ "note_instr": "Modele instrukcyjne wykonują polecenia; mniejsze warianty są przyjazne dla CPU.",
86
  "bonus_note": "Popularny model z Hub (dobrany po tagu zadania i pobraniach).",
87
  },
88
  }
 
91
  return I18N.get(ui_lang, I18N["EN"]).get(key, I18N["EN"].get(key, key))
92
 
93
  # -----------------------
94
+ # Internal stable values
95
  # -----------------------
96
+ TASK_CHAT = "CHAT"
97
+ TASK_QA = "QA"
98
+ TASK_SIM = "SIM"
99
+
100
+ def task_choices(ui_lang: str) -> List[Tuple[str, str]]:
101
+ return [
102
+ (t(ui_lang, "task_chat"), TASK_CHAT),
103
+ (t(ui_lang, "task_qa"), TASK_QA),
104
+ (t(ui_lang, "task_sim"), TASK_SIM),
105
+ ]
106
+
107
+ def yesno_choices(ui_lang: str) -> List[Tuple[str, str]]:
108
+ return [(t(ui_lang, "yes"), "YES"), (t(ui_lang, "no"), "NO")]
109
+
110
+ def data_lang_choices(ui_lang: str) -> List[Tuple[str, str]]:
111
+ return [(t(ui_lang, "en"), "EN"), (t(ui_lang, "pl"), "PL"), (t(ui_lang, "mixed"), "MIXED")]
112
+
113
+ def priority_choices(ui_lang: str) -> List[Tuple[str, str]]:
114
+ return [(t(ui_lang, "speed"), "SPEED"), (t(ui_lang, "quality"), "QUALITY")]
115
+
116
+ # -----------------------
117
+ # Candidate pool with metadata so settings can affect ranking
118
+ # -----------------------
119
+ @dataclass(frozen=True)
120
+ class Candidate:
121
+ model_id: str
122
+ # heuristics / tags:
123
+ size: str # "small" | "base" | "large"
124
+ languages: str # "EN" | "MULTI"
125
+ cpu_ok: bool
126
+ note_en: str
127
+ note_pl: str
128
+
129
+ CANDIDATES: Dict[str, List[Candidate]] = {
130
  "instruction": [
131
+ Candidate("google/flan-t5-small", "small", "EN", True,
132
+ "Very light instruction-following text2text model.", "Bardzo lekki model text2text do poleceń."),
133
+ Candidate("google/flan-t5-base", "base", "EN", True,
134
+ "Better quality than small; slower on CPU.", "Lepsza jakość niż small; wolniejszy na CPU."),
135
+ Candidate("google-t5/t5-small", "small", "EN", True,
136
+ "Fast fallback text2text baseline.", "Szybki fallback text2text."),
137
+ # multilingual-ish option (not perfect, but helps when user insists on PL/mixed for generation)
138
+ Candidate("google/mt5-small", "small", "MULTI", True,
139
+ "Multilingual T5 small for mixed-language tasks.", "Wielojęzyczny mT5 small dla zadań mix języków."),
140
+ Candidate("google/mt5-base", "base", "MULTI", True,
141
+ "Multilingual, higher quality than mt5-small; slower.", "Wielojęzyczny, lepsza jakość niż mt5-small; wolniejszy."),
142
  ],
143
  "qa": [
144
+ Candidate("distilbert/distilbert-base-cased-distilled-squad", "small", "EN", True,
145
+ "Fast extractive QA; classic CPU choice.", "Szybki QA extractive; klasyk na CPU."),
146
+ Candidate("distilbert/distilbert-base-uncased-distilled-squad", "small", "EN", True,
147
+ "Popular extractive QA default.", "Popularny domyślny QA extractive."),
148
+ Candidate("deepset/bert-base-cased-squad2", "base", "EN", True,
149
+ "SQuAD2 variant; better 'no answer' behavior.", "Wariant SQuAD2; lepiej obsługuje 'brak odpowiedzi'."),
150
+ # multilingual QA is trickier; we provide one common multilingual baseline
151
+ Candidate("deepset/xlm-roberta-base-squad2", "base", "MULTI", True,
152
+ "Multilingual extractive QA baseline (XLM-R).", "Wielojęzyczny QA extractive (XLM-R)."),
153
  ],
154
  "embeddings": [
155
+ Candidate("sentence-transformers/all-MiniLM-L6-v2", "small", "EN", True,
156
+ "Very fast sentence embeddings; great for similarity on CPU.", "Bardzo szybkie embeddingi; świetne do podobieństwa na CPU."),
157
+ Candidate("sentence-transformers/all-mpnet-base-v2", "base", "EN", True,
158
+ "Higher quality embeddings than MiniLM; slower.", "Lepsza jakość niż MiniLM; wolniejsze."),
159
+ Candidate("intfloat/e5-small-v2", "small", "EN", True,
160
+ "Strong retrieval embeddings, good speed/quality balance.", "Mocne embeddingi do wyszukiwania; dobry balans."),
161
+ Candidate("intfloat/e5-base-v2", "base", "EN", True,
162
+ "Higher quality e5; heavier on CPU.", "Lepsza jakość e5; cięższy na CPU."),
163
+ Candidate("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", "base", "MULTI", True,
164
+ "Multilingual embeddings; good for PL/mixed.", "Wielojęzyczne embeddingi; dobre dla PL/mix."),
165
  ],
166
  }
167
 
 
 
 
168
  # -----------------------
169
  # Hub bonus models (cache)
170
  # -----------------------
 
172
  CACHE_TTL_SEC = 6 * 60 * 60 # 6h
173
 
174
  def _language_tag_predicate(tags: List[str], data_lang_value: str) -> bool:
 
 
 
 
175
  if data_lang_value == "MIXED":
176
  return True
177
  target = "en" if data_lang_value == "EN" else "pl"
 
179
  tags_lower = {str(x).lower() for x in (tags or [])}
180
  return any(c in tags_lower for c in candidates)
181
 
182
+ def hub_bonus_models(pipeline_tag: str, data_lang_value: str, limit: int = 20) -> List[str]:
183
  key = (pipeline_tag, data_lang_value)
184
  now = time.time()
 
185
  if key in _HUB_CACHE:
186
  ts, cached = _HUB_CACHE[key]
187
  if now - ts < CACHE_TTL_SEC:
188
  return cached
 
189
  try:
190
  models = api.list_models(filter=pipeline_tag, sort="downloads", direction=-1, limit=limit)
191
  out = []
 
200
  return []
201
 
202
  # -----------------------
203
+ # Ranking rules (this is what makes settings matter)
204
  # -----------------------
205
+ def score_candidate(c: Candidate, data_lang_value: str, cpu_only: bool, priority: str) -> Tuple[int, List[str]]:
206
+ score = 0
207
+ reasons: List[str] = []
208
 
209
+ # CPU constraint
210
+ if cpu_only:
211
+ if c.cpu_ok:
212
+ score += 2
213
+ reasons.append("CPU-friendly" if True else "")
214
+ else:
215
+ score -= 100 # effectively exclude
216
+ reasons.append("Not CPU-friendly")
217
 
218
+ # Language preference
219
+ if data_lang_value in ("PL", "MIXED"):
220
+ if c.languages == "MULTI":
221
+ score += 4
222
+ reasons.append("Multilingual (better for PL/mixed)")
223
+ else:
224
+ score -= 1
225
+ reasons.append("EN-focused")
226
+ else: # EN
227
+ if c.languages == "EN":
228
+ score += 3
229
+ reasons.append("EN-optimized")
230
+ else:
231
+ score += 1
232
+ reasons.append("Multilingual")
233
 
234
+ # Priority: speed vs quality
235
+ if priority == "SPEED":
236
+ if c.size == "small":
237
+ score += 4
238
+ reasons.append("Smaller/faster")
239
+ elif c.size == "base":
240
+ score += 1
241
+ reasons.append("Medium size")
242
+ else:
243
+ score -= 1
244
+ reasons.append("Heavier/slower")
245
+ else: # QUALITY
246
+ if c.size == "base":
247
+ score += 4
248
+ reasons.append("Better quality baseline")
249
+ elif c.size == "small":
250
+ score += 2
251
+ reasons.append("Fast but may be lower quality")
252
+ else:
253
+ score += 3
254
+ reasons.append("High capacity")
255
 
256
+ return score, reasons
257
+
258
+ def pick_models(model_type: str, data_lang_value: str, cpu_only: bool, priority: str, k: int = 4) -> Tuple[List[Candidate], Dict[str, List[str]]]:
259
+ candidates = CANDIDATES[model_type]
260
+ scored: List[Tuple[int, Candidate, List[str]]] = []
261
+ for c in candidates:
262
+ s, reasons = score_candidate(c, data_lang_value, cpu_only, priority)
263
+ scored.append((s, c, reasons))
264
+
265
+ scored.sort(key=lambda x: x[0], reverse=True)
266
+
267
+ chosen: List[Candidate] = []
268
+ why: Dict[str, List[str]] = {}
269
+ for s, c, reasons in scored:
270
+ if s < -50:
271
+ continue
272
+ if c.model_id not in {x.model_id for x in chosen}:
273
+ chosen.append(c)
274
+ why[c.model_id] = reasons
275
+ if len(chosen) >= k:
276
+ break
277
+
278
+ # ensure min 3
279
+ if len(chosen) < 3:
280
+ # fallback: take top regardless of language
281
+ for s, c, reasons in scored:
282
+ if c.model_id not in {x.model_id for x in chosen} and s > -50:
283
+ chosen.append(c)
284
+ why[c.model_id] = reasons
285
+ if len(chosen) >= 3:
286
+ break
287
+
288
+ return chosen, why
289
 
290
  # -----------------------
291
+ # Main recommend function (now settings drive different outputs)
292
  # -----------------------
293
  def recommend(ui_lang: str, task_id: str, has_docs: str, data_lang_value: str, cpu_only: bool, priority: str) -> str:
294
+ warning: Optional[str] = None
295
+
296
  if task_id == TASK_SIM:
297
  model_type = "embeddings"
298
+ why_task = (
299
+ "You want semantic similarity / deduplication / search. Embeddings + cosine similarity fit best."
300
  if ui_lang == "EN"
301
+ else "Chcesz podobieństwo semantyczne / deduplikację / wyszukiwanie. Najlepsze embeddingi + cosine similarity."
302
  )
303
  pipeline_tag = "sentence-similarity"
304
+ note_key = "note_emb"
305
  elif task_id == TASK_QA:
306
  model_type = "qa"
307
+ why_task = (
308
  "You have a context (document/text) and a question. Extractive QA finds answers in the context."
309
  if ui_lang == "EN"
310
+ else "Masz kontekst (dokument/tekst) i pytanie. QA extractive znajduje odpowiedzi w kontekście."
311
  )
312
  pipeline_tag = "question-answering"
313
+ note_key = "note_qa"
314
+ if has_docs == "NO":
315
+ warning = t(ui_lang, "qa_need_docs")
316
  else:
317
  model_type = "instruction"
318
+ why_task = (
319
  "You want instruction-following responses (chat/explain/summarize). Instruction-tuned models fit best."
320
  if ui_lang == "EN"
321
  else "Chcesz odpowiedzi sterowane poleceniem (chat/wyjaśnianie/streszczanie). Najlepsze są modele instrukcyjne."
322
  )
323
  pipeline_tag = "text-generation"
324
+ note_key = "note_instr"
325
 
326
+ # Pick models based on settings
327
+ chosen, why_map = pick_models(model_type, data_lang_value, cpu_only, priority, k=4)
328
 
329
+ # Add 1–2 hub bonus models, but only if they diversify beyond chosen
330
+ bonus = hub_bonus_models(pipeline_tag, data_lang_value, limit=25)
331
+ chosen_ids = {c.model_id for c in chosen}
332
+ bonus = [m for m in bonus if m not in chosen_ids]
333
+ bonus = bonus[:2]
 
334
 
335
+ # Build output
336
  lines: List[str] = []
337
  lines.append(t(ui_lang, "rec_type").format(model_type=model_type))
338
  lines.append("")
339
  lines.append(t(ui_lang, "rationale"))
340
+ lines.append(f"- {why_task}")
341
  lines.append("")
342
+ lines.append(t(ui_lang, "settings"))
343
+ lines.append(f"- data language: {data_lang_value}")
344
+ lines.append(f"- priority: {priority}")
345
+ lines.append(f"- cpu only: {cpu_only}")
346
+ lines.append(f"- has documents: {has_docs}")
347
+ lines.append("")
348
+
349
+ if warning:
350
+ lines.append(t(ui_lang, "warning"))
351
+ lines.append(f"- {warning}")
352
+ lines.append("")
353
+
354
  lines.append(t(ui_lang, "models_min3"))
355
+ for c in chosen:
356
+ note = c.note_en if ui_lang == "EN" else c.note_pl
357
+ lines.append(f"- {c.model_id} — {note}")
358
+
359
+ for mid in bonus:
360
+ lines.append(f"- {mid} — {t(ui_lang, 'bonus_note')}")
361
+
362
  lines.append("")
363
+ lines.append(t(ui_lang, "why_these"))
364
+ for c in chosen:
365
+ reasons = why_map.get(c.model_id, [])
366
+ # Localize reason snippets lightly
367
+ if ui_lang == "PL":
368
+ localized = []
369
+ for r in reasons:
370
+ if r == "CPU-friendly":
371
+ localized.append("Działa na CPU")
372
+ elif r == "Multilingual (better for PL/mixed)":
373
+ localized.append("Wielojęzyczny (lepszy dla PL/mix)")
374
+ elif r == "EN-optimized":
375
+ localized.append("Optymalny dla EN")
376
+ elif r == "Smaller/faster":
377
+ localized.append("Mniejszy/szybszy")
378
+ elif r == "Better quality baseline":
379
+ localized.append("Lepsza jakość (baseline)")
380
+ elif r == "Fast but may be lower quality":
381
+ localized.append("Szybki, ale może gorsza jakość")
382
+ elif r == "Medium size":
383
+ localized.append("Średni rozmiar")
384
+ elif r == "Heavier/slower":
385
+ localized.append("Cięższy/wolniejszy")
386
+ else:
387
+ localized.append(r)
388
+ reasons_txt = ", ".join(localized)
389
+ else:
390
+ reasons_txt = ", ".join(reasons)
391
+ lines.append(f"- {c.model_id}: {reasons_txt}")
392
 
393
+ lines.append("")
394
+ lines.append(t(ui_lang, note_key))
395
  return "\n".join(lines)
396
 
397
  # -----------------------
398
  # Dynamic UI language updates
399
  # -----------------------
400
  def apply_language(ui_lang: str) -> Tuple[Any, ...]:
 
 
 
401
  return (
402
+ gr.update(value=f"# {t(ui_lang, 'title')}\n{t(ui_lang, 'intro')}"), # header
403
+ gr.update(label=t(ui_lang, "ui_lang")), # ui lang label
404
+ gr.update(label=t(ui_lang, "task"), choices=task_choices(ui_lang)), # task choices localized
405
+ gr.update(label=t(ui_lang, "has_docs"), choices=yesno_choices(ui_lang)),
406
+ gr.update(label=t(ui_lang, "data_lang"), choices=data_lang_choices(ui_lang)),
407
+ gr.update(label=t(ui_lang, "cpu_only")),
408
+ gr.update(label=t(ui_lang, "priority"), choices=priority_choices(ui_lang)),
409
+ gr.update(value=t(ui_lang, "recommend_btn")),
410
+ gr.update(label=t(ui_lang, "result")),
411
+ gr.update(label=t(ui_lang, "tab_main")),
412
  )
413
 
414
  # -----------------------
 
417
  with gr.Blocks(title=I18N["EN"]["title"]) as demo:
418
  header_md = gr.Markdown(f"# {t('EN', 'title')}\n{t('EN', 'intro')}")
419
 
420
+ ui_lang = gr.Radio(choices=["EN", "PL"], value="EN", label=t("EN", "ui_lang"))
 
 
 
 
421
 
 
422
  with gr.Tab(t("EN", "tab_main")) as tab_main:
423
+ task = gr.Dropdown(choices=task_choices("EN"), value=TASK_SIM, label=t("EN", "task"))
424
+ has_docs = gr.Radio(choices=yesno_choices("EN"), value="YES", label=t("EN", "has_docs"))
425
+ data_lang = gr.Radio(choices=data_lang_choices("EN"), value="MIXED", label=t("EN", "data_lang"))
 
 
 
 
 
 
 
 
 
 
 
 
426
  cpu_only = gr.Checkbox(value=True, label=t("EN", "cpu_only"))
427
+ priority = gr.Radio(choices=priority_choices("EN"), value="SPEED", label=t("EN", "priority"))
 
 
 
 
428
 
429
  recommend_btn = gr.Button(t("EN", "recommend_btn"))
430
+ out = gr.Textbox(lines=22, label=t("EN", "result"))
431
 
432
  recommend_btn.click(
433
  fn=recommend,
 
435
  outputs=[out],
436
  )
437
 
 
438
  ui_lang.change(
439
  fn=apply_language,
440
  inputs=[ui_lang],