daniellegauthier commited on
Commit
2dd7883
·
verified ·
1 Parent(s): 92b24cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -127
app.py CHANGED
@@ -7,7 +7,7 @@ import gradio as gr
7
  from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
8
  from sentence_transformers import SentenceTransformer, util
9
 
10
- # ---------- lightweight setup ----------
11
  def ensure_spacy():
12
  try:
13
  return spacy.load("en_core_web_sm")
@@ -25,22 +25,22 @@ def ensure_nltk():
25
  ensure_nltk()
26
  nlp = ensure_spacy()
27
 
28
- # ---------- models ----------
29
  sbert_model = SentenceTransformer("all-MiniLM-L6-v2")
30
  bert_sentiment = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
 
31
  emotion_model_name = "j-hartmann/emotion-english-distilroberta-base"
32
  emotion_tokenizer = AutoTokenizer.from_pretrained(emotion_model_name)
33
  emotion_model = AutoModelForSequenceClassification.from_pretrained(emotion_model_name)
34
 
35
- # ---------- constants ----------
36
- CSV_PATH_PLUS = "la matrice plus.csv" # pathways + colors + phrase parts
37
  CSV_PATH_COLOR = "la matrice.csv" # color lexicon
38
 
39
  SEQUENCE_ALIASES = {
40
  "Direct": "direct",
41
- "Fem": "feminine",
42
  "Knot": "knot",
43
- "Masc": "masc",
44
  "Pain": "pain",
45
  "Prayer": "prayer",
46
  "Precise": "precise",
@@ -50,12 +50,11 @@ SEQUENCE_ALIASES = {
50
  "Sad": "sad",
51
  }
52
 
53
-
54
  SEQUENCE_IMAGE_FILES = {
55
  "direct": "direct pathway.png",
56
  "feminine": "fem pathway.png",
57
  "knot": "knot pathway.png",
58
- "masc": "masc pathway.png",
59
  "pain": "pain pathway.png",
60
  "prayer": "prayer pathway.png",
61
  "precise": "precise pathway.png",
@@ -65,7 +64,6 @@ SEQUENCE_IMAGE_FILES = {
65
  "sad": "sad pathway.png"
66
  }
67
 
68
- # GNH dictionaries
69
  GNH_DOMAINS: Dict[str, str] = {
70
  "Mental Wellness": "mental health, emotional clarity, peace of mind",
71
  "Social Wellness": "relationships, community, friendship, social harmony",
@@ -98,7 +96,21 @@ GNH_COLORS: Dict[str, str] = {
98
  "Cultural Diversity": "#9370db",
99
  }
100
 
101
- # ---------- load pathway colors & phrase ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  def load_pathway_info(csv_path_plus: str):
103
  df = pd.read_csv(csv_path_plus)
104
  keys = set(SEQUENCE_ALIASES.values())
@@ -107,40 +119,26 @@ def load_pathway_info(csv_path_plus: str):
107
  seq_to_colors: Dict[str, List[str]] = {}
108
  seq_phrase: Dict[str, str] = {}
109
 
 
110
  cols_for_phrase = [c for c in df.columns if c not in ("color", "r", "g", "b")]
111
  for _, row in rows.iterrows():
112
  key = str(row["color"]).strip().lower()
113
- # colors in 'r' as comma/space separated list (supports 2–8)
114
- colors_field = str(row.get("r", "") or "")
115
- colors = [c.strip().lower() for c in re.split(r"[,\s]+", colors_field) if c.strip()]
116
- seq_to_colors[key] = list(dict.fromkeys(colors)) # dedupe
117
 
118
  vals = []
119
  for c in cols_for_phrase:
120
  v = row.get(c)
121
  if pd.notna(v):
122
- vs = str(v).strip()
123
- if vs and vs.lower() != "nan":
124
- vals.append(vs)
125
- phrase = " ".join(" ".join(vals).split())
126
  seq_phrase[key] = phrase
127
 
128
  return seq_to_colors, seq_phrase
129
 
130
- SEQ_TO_COLORS, SEQ_PHRASE = load_pathway_info(CSV_PATH_PLUS)
131
-
132
- # ---------- load color lexicon ----------
133
- def _find_col(df: pd.DataFrame, candidates: List[str]) -> str | None:
134
- names = {c.lower(): c for c in df.columns}
135
- for c in candidates:
136
- if c.lower() in names:
137
- return names[c.lower()]
138
- for want in candidates:
139
- for lc, orig in names.items():
140
- if want.replace(" ", "").replace("-", "") in lc.replace(" ", "").replace("-", ""):
141
- return orig
142
- return None
143
-
144
  def _split_words(s: str) -> List[str]:
145
  if not isinstance(s, str): return []
146
  parts = re.split(r"[,\;/\|\s]+", s.strip())
@@ -164,13 +162,14 @@ def load_color_lexicon(csv_path_color: str):
164
  }
165
  return lex
166
 
 
167
  COLOR_LEX = load_color_lexicon(CSV_PATH_COLOR)
168
 
169
  def sequence_to_image_path(seq_key: str) -> str | None:
170
  fname = SEQUENCE_IMAGE_FILES.get(seq_key)
171
  return fname if (fname and os.path.exists(fname)) else None
172
 
173
- # ---------- core scoring ----------
174
  def encode_text(t: str):
175
  return sbert_model.encode(t, convert_to_tensor=True)
176
 
@@ -218,19 +217,16 @@ def indicators_plot(indicators: Dict[str, float]):
218
  plt.tight_layout()
219
  return fig
220
 
221
- # ---------- chips / prompts ----------
222
- WORD_MODES = ["Matrice1", "Matrice", "English", "GNH Indicators"]
223
-
224
- def join_lex_words(color: str) -> str:
225
  d = COLOR_LEX.get(color.lower(), {})
226
- words = d.get("matrice1", []) + d.get("matrice", []) + d.get("english", [])
227
- return " ".join(dict.fromkeys(words))
228
 
229
  def nearest_gnh_domain_for_color(color: str) -> Tuple[str, float]:
230
- text = join_lex_words(color)
231
- if not text:
232
  return "Mental Wellness", 0.0
233
- v = encode_text(text)
234
  best, best_sim = None, -1.0
235
  for dom, desc in GNH_DOMAINS.items():
236
  sim = float(util.cos_sim(v, encode_text(desc)).item())
@@ -238,108 +234,148 @@ def nearest_gnh_domain_for_color(color: str) -> Tuple[str, float]:
238
  best, best_sim = dom, sim
239
  return best or "Mental Wellness", best_sim
240
 
241
- def chip_html_for(color: str, mode: str, max_words: int = 4) -> str:
242
- if not color: return ""
243
- if mode.lower().startswith("gnh"):
244
- domain, sim = nearest_gnh_domain_for_color(color)
245
- hex_color = GNH_COLORS.get(domain, "#cccccc")
246
- dot = f"<span style='display:inline-block;width:12px;height:12px;border-radius:50%;background:{hex_color};margin-right:6px;border:1px solid #999;vertical-align:middle'></span>"
247
- pill = f"<span style='display:inline-block;margin:2px 6px;padding:2px 8px;border-radius:12px;background:#eee;font-size:12px'>{domain} · {sim:.2f}</span>"
248
- return f"<div style='margin-bottom:6px'>{dot}<b>{color.capitalize()}</b>{pill}</div>"
249
- # lexicon modes
250
- key = "english" if mode.lower() == "english" else ("matrice1" if mode.lower()=="matrice1" else "matrice")
251
- words = COLOR_LEX.get(color.lower(), {}).get(key, [])[:max_words]
252
- pills = "".join(
253
- f"<span style='display:inline-block;margin:2px 6px;padding:2px 8px;border-radius:12px;background:#eee;font-size:12px'>{w}</span>"
254
- for w in words
255
- )
256
- dot = f"<span style='display:inline-block;width:12px;height:12px;border-radius:50%;background:{color};margin-right:6px;border:1px solid #999;vertical-align:middle'></span>"
257
- return f"<div style='margin-bottom:6px'>{dot}<b>{color.capitalize()}</b>{pills}</div>"
258
-
259
- def colors_for_sequence(seq_key: str) -> List[str]:
260
- return SEQ_TO_COLORS.get(seq_key, []) # 2–8 colors
261
-
262
  def labels_for_mode(colors: List[str], mode: str) -> List[str]:
263
  if mode.lower().startswith("gnh"):
264
- labs = []
265
- for c in colors:
266
- d, _ = nearest_gnh_domain_for_color(c)
267
- labs.append(d)
268
- return labs
269
  return [c.capitalize() for c in colors]
270
 
271
- # ---------- dynamic prompt UI (2–8 inputs) ----------
272
- MAX_COLORS = 8 # upper bound; raise if some pathways exceed this
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
  def update_prompt_ui(seq_choice: str, word_mode: str):
275
  key = SEQUENCE_ALIASES.get(seq_choice)
276
  colors = colors_for_sequence(key)
277
  labels = labels_for_mode(colors, word_mode)
 
278
 
279
- chips = "".join(chip_html_for(c, word_mode) for c in colors) or "No prompts available for this pathway."
280
-
281
- # Return updates for chips + each color input (visibility, label, placeholder)
282
- inputs_updates = []
283
  for i in range(MAX_COLORS):
284
  if i < len(colors):
285
  lab = labels[i] if i < len(labels) else f"Input {i+1}"
286
- ph = f"Describe {lab} meaning..." if lab else "—"
287
- inputs_updates.append(gr.update(visible=True, label=f"{lab} meaning", placeholder=ph, value=""))
288
  else:
289
- inputs_updates.append(gr.update(visible=False, value="", label=f"Input {i+1}", placeholder="—"))
290
- return (chips, *inputs_updates)
291
 
292
- # ---------- MAIN ANALYSIS ----------
293
- def analyze(text: str, seq_choice: str, word_mode: str, *color_inputs):
294
  """
295
- - user chooses pathway
296
- - show N color prompts (2–8)
297
- - compose updated pathway phrase embedding all inputs
298
- - analyze sentiment/emotion + GNH on (text + updated phrase)
299
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  key = SEQUENCE_ALIASES.get(seq_choice)
301
  if key not in SEQ_PHRASE:
302
- return (5.0, "neutral (0.0)", 5.0, "Please choose a valid pathway.", "{}", None, None,
303
- f"{seq_choice} (unavailable)", *update_prompt_ui(seq_choice, word_mode))
304
-
305
- sentiment = score_sentiment(text or "")
306
- emotion, emo_conf = classify_emotion(text or "")
307
- accomplishment = score_accomplishment(text or "")
308
 
309
  colors = colors_for_sequence(key)
310
  labels = labels_for_mode(colors, word_mode)
311
-
312
- # Updated phrase = base phrase + each "{Label}: {input}"
313
  base_phrase = SEQ_PHRASE.get(key, "")
314
- pieces = [base_phrase]
315
- for lab, user_text in zip(labels, list(color_inputs)[:len(colors)]):
316
- if isinstance(user_text, str) and user_text.strip():
317
- pieces.append(f"{lab}: {user_text.strip()}")
318
- updated_phrase = " // ".join([p for p in pieces if p])
319
-
320
- augmented_text = " ".join([t for t in [text, updated_phrase] if t and t.strip()])
321
- indicators = semantic_indicator_mapping(augmented_text, sentiment_score=sentiment)
 
 
 
 
322
  fig = indicators_plot(indicators)
323
  top5 = list(indicators.items())[:5]
324
  top5_str = "\n".join(f"{k}: {v}" for k, v in top5)
325
 
326
- cols = SEQ_TO_COLORS.get(key, [])
327
- emo_str = f"{emotion} ({emo_conf:.3f})"
328
- meta = f"{key} | colors: {', '.join(cols) if cols else '—'}"
329
  img_path = sequence_to_image_path(key)
 
 
330
 
331
- # keep UI prompts in sync after run
332
- chips_and_inputs = update_prompt_ui(seq_choice, word_mode)
333
 
334
  return (
335
  sentiment, emo_str, accomplishment,
336
  updated_phrase, top5_str, fig, img_path, meta,
337
- *chips_and_inputs
338
  )
339
 
340
- # ---------- Gradio UI ----------
341
  SEQ_CHOICES = list(SEQUENCE_ALIASES.keys())
342
- DEFAULT_SEQ = "Direct" if "Direct" in SEQ_CHOICES else SEQ_CHOICES[0]
343
 
344
  with gr.Blocks(title="RGB Root Matriz Color Plotter") as demo:
345
  gr.Markdown("## RGB Root Matriz Color Plotter\n"
@@ -351,51 +387,42 @@ with gr.Blocks(title="RGB Root Matriz Color Plotter") as demo:
351
 
352
  with gr.Row():
353
  seq = gr.Dropdown(choices=SEQ_CHOICES, value=DEFAULT_SEQ, label="Pathway")
354
- word_mode = gr.Radio(choices=WORD_MODES, value="Matrice1", label="Word Mode")
355
 
356
- chips_block = gr.HTML() # chips for all colors
357
 
358
- # up to MAX_COLORS inputs (shown/hidden dynamically)
359
- color_inputs = []
360
  for i in range(MAX_COLORS):
361
- tb = gr.Textbox(visible=False, label=f"Input {i+1}", placeholder="—")
362
- color_inputs.append(tb)
363
 
364
  run = gr.Button("Generate Pathway Analysis", variant="primary")
365
 
366
- # outputs
367
  with gr.Row():
368
  sent = gr.Number(label="Sentiment (1–10)")
369
  emo = gr.Text(label="Emotion")
370
  acc = gr.Number(label="Accomplishment (1–10)")
371
 
372
  with gr.Row():
373
- phrase_out = gr.Text(label="Updated Pathway Phrase (with your meanings)")
374
  gnh_top = gr.Text(label="Top GNH Indicators (Top 5)")
375
 
376
  gnh_plot = gr.Plot(label="GNH Similarity")
377
  img_out = gr.Image(label="Pathway image", type="filepath")
378
  meta_out = gr.Text(label="Chosen pathway / colors")
379
 
380
- # events
381
  def _update_ui(seq_choice, mode):
382
  return update_prompt_ui(seq_choice, mode)
383
 
384
- seq.change(fn=_update_ui, inputs=[seq, word_mode], outputs=[chips_block, *color_inputs])
385
- word_mode.change(fn=_update_ui, inputs=[seq, word_mode], outputs=[chips_block, *color_inputs])
386
 
387
  run.click(
388
  fn=analyze,
389
- inputs=[inp, seq, word_mode, *color_inputs],
390
- outputs=[sent, emo, acc, phrase_out, gnh_top, gnh_plot, img_out, meta_out, chips_block, *color_inputs],
391
  )
392
 
393
- # initialize prompts on load (instead of tb.update(...))
394
- demo.load(
395
- fn=_update_ui,
396
- inputs=[seq, word_mode],
397
- outputs=[chips_block, *color_inputs]
398
- )
399
 
400
  if __name__ == "__main__":
401
  demo.launch()
 
7
  from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
8
  from sentence_transformers import SentenceTransformer, util
9
 
10
+ # -------------------- setup --------------------
11
  def ensure_spacy():
12
  try:
13
  return spacy.load("en_core_web_sm")
 
25
  ensure_nltk()
26
  nlp = ensure_spacy()
27
 
 
28
  sbert_model = SentenceTransformer("all-MiniLM-L6-v2")
29
  bert_sentiment = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
30
+
31
  emotion_model_name = "j-hartmann/emotion-english-distilroberta-base"
32
  emotion_tokenizer = AutoTokenizer.from_pretrained(emotion_model_name)
33
  emotion_model = AutoModelForSequenceClassification.from_pretrained(emotion_model_name)
34
 
35
+ # -------------------- constants --------------------
36
+ CSV_PATH_PLUS = "la matrice plus.csv" # pathways + colors + template words
37
  CSV_PATH_COLOR = "la matrice.csv" # color lexicon
38
 
39
  SEQUENCE_ALIASES = {
40
  "Direct": "direct",
41
+ "Feminine": "feminine",
42
  "Knot": "knot",
43
+ "Masculine": "masculine",
44
  "Pain": "pain",
45
  "Prayer": "prayer",
46
  "Precise": "precise",
 
50
  "Sad": "sad",
51
  }
52
 
 
53
  SEQUENCE_IMAGE_FILES = {
54
  "direct": "direct pathway.png",
55
  "feminine": "fem pathway.png",
56
  "knot": "knot pathway.png",
57
+ "masculine": "masc pathway.png",
58
  "pain": "pain pathway.png",
59
  "prayer": "prayer pathway.png",
60
  "precise": "precise pathway.png",
 
64
  "sad": "sad pathway.png"
65
  }
66
 
 
67
  GNH_DOMAINS: Dict[str, str] = {
68
  "Mental Wellness": "mental health, emotional clarity, peace of mind",
69
  "Social Wellness": "relationships, community, friendship, social harmony",
 
96
  "Cultural Diversity": "#9370db",
97
  }
98
 
99
+ WORD_MODES = ["Matrice1", "Matrice", "English", "GNH Indicators"]
100
+ MAX_COLORS = 8
101
+
102
+ # -------------------- loaders --------------------
103
+ def _find_col(df: pd.DataFrame, candidates: List[str]) -> str | None:
104
+ names = {c.lower(): c for c in df.columns}
105
+ for c in candidates:
106
+ if c.lower() in names: return names[c.lower()]
107
+ for want in candidates:
108
+ ww = want.replace(" ", "").replace("-", "")
109
+ for lc, orig in names.items():
110
+ if ww in lc.replace(" ", "").replace("-", ""):
111
+ return orig
112
+ return None
113
+
114
  def load_pathway_info(csv_path_plus: str):
115
  df = pd.read_csv(csv_path_plus)
116
  keys = set(SEQUENCE_ALIASES.values())
 
119
  seq_to_colors: Dict[str, List[str]] = {}
120
  seq_phrase: Dict[str, str] = {}
121
 
122
+ # colors live in 'r' (list), template = concat of the other fields
123
  cols_for_phrase = [c for c in df.columns if c not in ("color", "r", "g", "b")]
124
  for _, row in rows.iterrows():
125
  key = str(row["color"]).strip().lower()
126
+ color_list = str(row.get("r", "") or "")
127
+ colors = [c.strip().lower() for c in re.split(r"[,\s]+", color_list) if c.strip()]
128
+ seq_to_colors[key] = list(dict.fromkeys(colors))
 
129
 
130
  vals = []
131
  for c in cols_for_phrase:
132
  v = row.get(c)
133
  if pd.notna(v):
134
+ s = str(v).strip()
135
+ if s and s.lower() != "nan":
136
+ vals.append(s)
137
+ phrase = " ".join(" ".join(vals).split()) # base template
138
  seq_phrase[key] = phrase
139
 
140
  return seq_to_colors, seq_phrase
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  def _split_words(s: str) -> List[str]:
143
  if not isinstance(s, str): return []
144
  parts = re.split(r"[,\;/\|\s]+", s.strip())
 
162
  }
163
  return lex
164
 
165
+ SEQ_TO_COLORS, SEQ_PHRASE = load_pathway_info(CSV_PATH_PLUS)
166
  COLOR_LEX = load_color_lexicon(CSV_PATH_COLOR)
167
 
168
  def sequence_to_image_path(seq_key: str) -> str | None:
169
  fname = SEQUENCE_IMAGE_FILES.get(seq_key)
170
  return fname if (fname and os.path.exists(fname)) else None
171
 
172
+ # -------------------- NLP helpers --------------------
173
  def encode_text(t: str):
174
  return sbert_model.encode(t, convert_to_tensor=True)
175
 
 
217
  plt.tight_layout()
218
  return fig
219
 
220
+ # -------------------- prompt building (legible placeholders) --------------------
221
+ def join_all_words(color: str) -> List[str]:
 
 
222
  d = COLOR_LEX.get(color.lower(), {})
223
+ return list(dict.fromkeys(d.get("matrice1", []) + d.get("matrice", []) + d.get("english", [])))
 
224
 
225
  def nearest_gnh_domain_for_color(color: str) -> Tuple[str, float]:
226
+ words = " ".join(join_all_words(color))
227
+ if not words:
228
  return "Mental Wellness", 0.0
229
+ v = encode_text(words)
230
  best, best_sim = None, -1.0
231
  for dom, desc in GNH_DOMAINS.items():
232
  sim = float(util.cos_sim(v, encode_text(desc)).item())
 
234
  best, best_sim = dom, sim
235
  return best or "Mental Wellness", best_sim
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  def labels_for_mode(colors: List[str], mode: str) -> List[str]:
238
  if mode.lower().startswith("gnh"):
239
+ return [nearest_gnh_domain_for_color(c)[0] for c in colors]
 
 
 
 
240
  return [c.capitalize() for c in colors]
241
 
242
+ def placeholder_for(color: str, mode: str) -> str:
243
+ """
244
+ Always show a meaningful placeholder driven by the chosen mode.
245
+ """
246
+ color_lc = color.lower()
247
+ if mode.lower().startswith("gnh"):
248
+ dom, _ = nearest_gnh_domain_for_color(color_lc)
249
+ return f"{dom}: {GNH_DOMAINS.get(dom, '')}"
250
+
251
+ # map mode -> CSV column key
252
+ mode_key = {
253
+ "matrice1": "matrice1",
254
+ "matrice": "matrice",
255
+ "english": "english",
256
+ }.get(mode.lower(), "matrice")
257
+
258
+ lex = COLOR_LEX.get(color_lc, {})
259
+ primary = lex.get(mode_key, [])
260
+
261
+ # If the chosen column has entries, use them.
262
+ if primary:
263
+ return ", ".join(primary[:12])
264
+
265
+ # Otherwise, try the other two lexicon columns (ordered).
266
+ fallback_order = [k for k in ("matrice1", "matrice", "english") if k != mode_key]
267
+ for fb in fallback_order:
268
+ words = lex.get(fb, [])
269
+ if words:
270
+ label = "Matrice1" if fb == "matrice1" else ("Matrice" if fb == "matrice" else "English")
271
+ return f"(from {label}) " + ", ".join(words[:12])
272
+
273
+ # Final fallback: mapped GNH domain description (still a “meaning”, just not from lexicon).
274
+ dom, _ = nearest_gnh_domain_for_color(color_lc)
275
+ return f"(mapped GNH) {dom}: {GNH_DOMAINS.get(dom, '')}"
276
+
277
+
278
+ def simple_color_legend(colors: List[str]) -> str:
279
+ if not colors:
280
+ return "No prompts available for this pathway."
281
+ parts = []
282
+ for c in colors:
283
+ dot = f"<span style='display:inline-block;width:10px;height:10px;border-radius:50%;background:{c};margin-right:8px;border:1px solid #999;vertical-align:middle'></span>"
284
+ parts.append(f"<div style='margin:4px 0'>{dot}<b>{c.capitalize()}</b></div>")
285
+ return "<div>" + "".join(parts) + "</div>"
286
+
287
+ def colors_for_sequence(seq_key: str) -> List[str]:
288
+ return SEQ_TO_COLORS.get(seq_key, [])
289
 
290
  def update_prompt_ui(seq_choice: str, word_mode: str):
291
  key = SEQUENCE_ALIASES.get(seq_choice)
292
  colors = colors_for_sequence(key)
293
  labels = labels_for_mode(colors, word_mode)
294
+ legend_html = simple_color_legend(colors)
295
 
296
+ updates = []
 
 
 
297
  for i in range(MAX_COLORS):
298
  if i < len(colors):
299
  lab = labels[i] if i < len(labels) else f"Input {i+1}"
300
+ ph = placeholder_for(colors[i], word_mode)
301
+ updates.append(gr.update(visible=True, label=f"{lab} meaning", placeholder=ph, value=""))
302
  else:
303
+ updates.append(gr.update(visible=False, value="", label=f"Input {i+1}", placeholder="—"))
304
+ return (legend_html, *updates)
305
 
306
+ # -------------------- template replacement --------------------
307
+ def render_phrase_template(base_phrase: str, colors: List[str], labels: List[str], inputs: List[str]) -> str:
308
  """
309
+ Replace occurrences of '<color>-pathway' (any spacing/hyphen variants) with the user's phrase for that color.
310
+ If user left it empty, keep the label (color name or mapped GNH indicator).
311
+ Finally, append a compact legend ' // Label: input'.
 
312
  """
313
+ text = base_phrase or ""
314
+ # build replacement map color -> replacement text
315
+ rep: Dict[str, str] = {}
316
+ for color, label, user in zip(colors, labels, inputs):
317
+ use = user.strip() if isinstance(user, str) and user.strip() else label
318
+ rep[color.lower()] = use
319
+
320
+ # replace each token case-insensitively
321
+ for color, replacement in rep.items():
322
+ # match 'brown-pathway', 'brown pathway', 'Brown- Pathway', etc.
323
+ pattern = re.compile(rf"\b{re.escape(color)}\s*-\s*pathway\b", re.IGNORECASE)
324
+ text = pattern.sub(replacement, text)
325
+
326
+ # if the template had no tokens, fall back to readable construction:
327
+ # "use A to B the C of D as a new E" is preserved, but we still append meanings
328
+ suffix_parts = []
329
+ for color, label, user in zip(colors, labels, inputs):
330
+ if isinstance(user, str) and user.strip():
331
+ suffix_parts.append(f"{label}: {user.strip()}")
332
+ if suffix_parts:
333
+ text = (text + " // " + " // ".join(suffix_parts)).strip()
334
+
335
+ return text
336
+
337
+ # -------------------- main analysis --------------------
338
+ def analyze(text: str, seq_choice: str, word_mode: str, *color_inputs):
339
  key = SEQUENCE_ALIASES.get(seq_choice)
340
  if key not in SEQ_PHRASE:
341
+ return (5.0, "neutral (0.0)", 5.0, "Choose a valid pathway.", "{}", None, None, f"{seq_choice} (unavailable)",
342
+ *update_prompt_ui(seq_choice, word_mode))
 
 
 
 
343
 
344
  colors = colors_for_sequence(key)
345
  labels = labels_for_mode(colors, word_mode)
 
 
346
  base_phrase = SEQ_PHRASE.get(key, "")
347
+
348
+ # updated phrase with template replacement
349
+ user_inputs = list(color_inputs)[:len(colors)]
350
+ updated_phrase = render_phrase_template(base_phrase, colors, labels, user_inputs)
351
+
352
+ # analysis on original + updated
353
+ combined_text = " ".join([t for t in [text, updated_phrase] if t and t.strip()])
354
+ sentiment = score_sentiment(combined_text)
355
+ emotion, emo_conf = classify_emotion(combined_text)
356
+ accomplishment = score_accomplishment(combined_text)
357
+
358
+ indicators = semantic_indicator_mapping(combined_text, sentiment_score=sentiment)
359
  fig = indicators_plot(indicators)
360
  top5 = list(indicators.items())[:5]
361
  top5_str = "\n".join(f"{k}: {v}" for k, v in top5)
362
 
 
 
 
363
  img_path = sequence_to_image_path(key)
364
+ meta = f"{key} | colors: {', '.join(colors) if colors else '—'}"
365
+ emo_str = f"{emotion} ({emo_conf:.3f})"
366
 
367
+ # keep prompt area synced
368
+ prompt_updates = update_prompt_ui(seq_choice, word_mode)
369
 
370
  return (
371
  sentiment, emo_str, accomplishment,
372
  updated_phrase, top5_str, fig, img_path, meta,
373
+ *prompt_updates
374
  )
375
 
376
+ # -------------------- UI --------------------
377
  SEQ_CHOICES = list(SEQUENCE_ALIASES.keys())
378
+ DEFAULT_SEQ = "Knot" if "Knot" in SEQ_CHOICES else SEQ_CHOICES[0]
379
 
380
  with gr.Blocks(title="RGB Root Matriz Color Plotter") as demo:
381
  gr.Markdown("## RGB Root Matriz Color Plotter\n"
 
387
 
388
  with gr.Row():
389
  seq = gr.Dropdown(choices=SEQ_CHOICES, value=DEFAULT_SEQ, label="Pathway")
390
+ word_mode = gr.Radio(choices=WORD_MODES, value="Matrice", label="Word Mode")
391
 
392
+ legend = gr.HTML()
393
 
394
+ color_boxes: List[gr.Textbox] = []
 
395
  for i in range(MAX_COLORS):
396
+ color_boxes.append(gr.Textbox(visible=False, label=f"Input {i+1}", placeholder="—"))
 
397
 
398
  run = gr.Button("Generate Pathway Analysis", variant="primary")
399
 
 
400
  with gr.Row():
401
  sent = gr.Number(label="Sentiment (1–10)")
402
  emo = gr.Text(label="Emotion")
403
  acc = gr.Number(label="Accomplishment (1–10)")
404
 
405
  with gr.Row():
406
+ phrase_out = gr.Text(label="Updated Pathway Phrase (template with your meanings)")
407
  gnh_top = gr.Text(label="Top GNH Indicators (Top 5)")
408
 
409
  gnh_plot = gr.Plot(label="GNH Similarity")
410
  img_out = gr.Image(label="Pathway image", type="filepath")
411
  meta_out = gr.Text(label="Chosen pathway / colors")
412
 
 
413
  def _update_ui(seq_choice, mode):
414
  return update_prompt_ui(seq_choice, mode)
415
 
416
+ seq.change(fn=_update_ui, inputs=[seq, word_mode], outputs=[legend, *color_boxes])
417
+ word_mode.change(fn=_update_ui, inputs=[seq, word_mode], outputs=[legend, *color_boxes])
418
 
419
  run.click(
420
  fn=analyze,
421
+ inputs=[inp, seq, word_mode, *color_boxes],
422
+ outputs=[sent, emo, acc, phrase_out, gnh_top, gnh_plot, img_out, meta_out, legend, *color_boxes],
423
  )
424
 
425
+ demo.load(fn=_update_ui, inputs=[seq, word_mode], outputs=[legend, *color_boxes])
 
 
 
 
 
426
 
427
  if __name__ == "__main__":
428
  demo.launch()