daniellegauthier commited on
Commit
64742da
·
verified ·
1 Parent(s): 33221b5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -195
app.py CHANGED
@@ -7,9 +7,7 @@ import gradio as gr
7
  from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
8
  from sentence_transformers import SentenceTransformer, util
9
 
10
- # --------------------------
11
- # light setup
12
- # --------------------------
13
  def ensure_spacy():
14
  try:
15
  return spacy.load("en_core_web_sm")
@@ -27,24 +25,19 @@ def ensure_nltk():
27
  ensure_nltk()
28
  nlp = ensure_spacy()
29
 
30
- # --------------------------
31
- # models (cached)
32
- # --------------------------
33
  sbert_model = SentenceTransformer("all-MiniLM-L6-v2")
34
  bert_sentiment = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
35
-
36
  emotion_model_name = "j-hartmann/emotion-english-distilroberta-base"
37
  emotion_tokenizer = AutoTokenizer.from_pretrained(emotion_model_name)
38
  emotion_model = AutoModelForSequenceClassification.from_pretrained(emotion_model_name)
39
 
40
- # --------------------------
41
- # constants
42
- # --------------------------
43
- CSV_PATH_PLUS = "la matrice plus.csv" # pathways + colors
44
- CSV_PATH_COLOR = "la matrice.csv" # color lexicon
45
 
 
46
  SEQUENCE_ALIASES = {
47
- "Auto (recommend)": "auto",
48
  "Direct": "direct",
49
  "Fem": "feminine",
50
  "Knot": "knot",
@@ -58,6 +51,7 @@ SEQUENCE_ALIASES = {
58
  "Sad": "sad",
59
  }
60
 
 
61
  SEQUENCE_IMAGE_FILES = {
62
  "direct": "direct pathway.png",
63
  "feminine": "fem pathway.png",
@@ -72,6 +66,7 @@ SEQUENCE_IMAGE_FILES = {
72
  "sad": "sad pathway.png"
73
  }
74
 
 
75
  GNH_DOMAINS: Dict[str, str] = {
76
  "Mental Wellness": "mental health, emotional clarity, peace of mind",
77
  "Social Wellness": "relationships, community, friendship, social harmony",
@@ -104,24 +99,22 @@ GNH_COLORS: Dict[str, str] = {
104
  "Cultural Diversity": "#9370db",
105
  }
106
 
107
- # --------------------------
108
- # load pathway → colors & phrase (from la matrice plus.csv)
109
- # --------------------------
110
  def load_pathway_info(csv_path_plus: str):
111
  df = pd.read_csv(csv_path_plus)
112
- keys = set(SEQUENCE_ALIASES.values()) - {"auto"}
113
  rows = df[df["color"].astype(str).str.lower().isin(keys)].copy()
114
 
115
  seq_to_colors: Dict[str, List[str]] = {}
116
  seq_phrase: Dict[str, str] = {}
117
 
118
- # use 'r' for color list; phrase = join other non-null columns
119
  cols_for_phrase = [c for c in df.columns if c not in ("color", "r", "g", "b")]
120
  for _, row in rows.iterrows():
121
  key = str(row["color"]).strip().lower()
 
122
  colors_field = str(row.get("r", "") or "")
123
  colors = [c.strip().lower() for c in re.split(r"[,\s]+", colors_field) if c.strip()]
124
- seq_to_colors[key] = list(dict.fromkeys(colors)) # de-dupe
125
 
126
  vals = []
127
  for c in cols_for_phrase:
@@ -137,15 +130,12 @@ def load_pathway_info(csv_path_plus: str):
137
 
138
  SEQ_TO_COLORS, SEQ_PHRASE = load_pathway_info(CSV_PATH_PLUS)
139
 
140
- # --------------------------
141
- # load color lexicon (from la matrice.csv)
142
- # --------------------------
143
  def _find_col(df: pd.DataFrame, candidates: List[str]) -> str | None:
144
  names = {c.lower(): c for c in df.columns}
145
  for c in candidates:
146
  if c.lower() in names:
147
  return names[c.lower()]
148
- # fuzzy contains
149
  for want in candidates:
150
  for lc, orig in names.items():
151
  if want.replace(" ", "").replace("-", "") in lc.replace(" ", "").replace("-", ""):
@@ -181,9 +171,10 @@ def sequence_to_image_path(seq_key: str) -> str | None:
181
  fname = SEQUENCE_IMAGE_FILES.get(seq_key)
182
  return fname if (fname and os.path.exists(fname)) else None
183
 
184
- # --------------------------
185
- # core scoring
186
- # --------------------------
 
187
  def classify_emotion(text: str) -> Tuple[str, float]:
188
  inputs = emotion_tokenizer(text, return_tensors="pt", truncation=True)
189
  with torch.no_grad():
@@ -200,50 +191,21 @@ def score_sentiment(text: str) -> float:
200
  return round(min(10, max(1, scaled)), 2)
201
 
202
  def score_accomplishment(text: str) -> float:
203
- doc = nlp(text)
204
- score = 5.0
205
  key_phrases = {"finally","told","decided","quit","refused","stood","walked","walked away","returned","return"}
206
  for token in doc:
207
  if token.text.lower() in key_phrases: score += 1.5
208
  if token.tag_ in {"VBD","VBN"}: score += 0.5
209
  return round(min(10, max(1, score)), 2)
210
 
211
- # --------------------------
212
- # pathway-aware vector math
213
- # --------------------------
214
- def encode_text(t: str):
215
- return sbert_model.encode(t, convert_to_tensor=True)
216
-
217
- def composite_vector(base_text: str, extras: List[Tuple[str,float]], limits: List[Tuple[str,float]]):
218
- """
219
- v = base + Σ(w_i * encode(extra_i)) - Σ(w_j * encode(limit_j))
220
- `extras` and `limits` are (text, weight) pairs
221
- """
222
- v = encode_text(base_text)
223
- for s, w in extras:
224
- if s and w:
225
- v = v + float(w) * encode_text(s)
226
- for s, w in limits:
227
- if s and w:
228
- v = v - float(w) * encode_text(s)
229
- return v
230
-
231
- def best_sequence_for_vector(vec) -> Tuple[str, float]:
232
- best_key, best_sim = None, -1.0
233
- for key, phrase in SEQ_PHRASE.items():
234
- if not phrase: continue
235
- sim = float(util.cos_sim(vec, encode_text(phrase)).item())
236
- if sim > best_sim:
237
- best_key, best_sim = key, sim
238
- return best_key or "direct", best_sim
239
-
240
- def semantic_indicator_mapping_from_vec(vec, sentiment_score: float, sentiment_weight: float = 0.3) -> Dict[str, float]:
241
  out: Dict[str, float] = {}
242
- for label, desc in GNH_DOMAINS.items():
243
- sim = float(util.cos_sim(vec, encode_text(desc)).item())
244
  sim = max(0.0, min(1.0, sim))
245
  blended = (1 - sentiment_weight) * sim + sentiment_weight * (sentiment_score / 10.0)
246
- out[label] = round(blended, 3)
247
  return dict(sorted(out.items(), key=lambda kv: -kv[1]))
248
 
249
  def indicators_plot(indicators: Dict[str, float]):
@@ -252,161 +214,154 @@ def indicators_plot(indicators: Dict[str, float]):
252
  fig = plt.figure(figsize=(8,5))
253
  plt.barh(labels, values, color=colors)
254
  plt.gca().invert_yaxis()
255
- plt.title("GNH Indicator Similarity (Pathway-weighted)")
256
  plt.xlabel("Score")
257
  plt.tight_layout()
258
  return fig
259
 
260
- # --------------------------
261
- # helpers for UI chips
262
- # --------------------------
263
- def lex_chip_html(color: str, mode: str, max_words: int = 4) -> str:
264
- words = COLOR_LEX.get(color.lower(), {}).get(mode, [])[:max_words]
265
- if not words: return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  pills = "".join(
267
  f"<span style='display:inline-block;margin:2px 6px;padding:2px 8px;border-radius:12px;background:#eee;font-size:12px'>{w}</span>"
268
  for w in words
269
  )
270
- title = color.capitalize()
271
- color_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>"
272
- return f"<div style='margin-bottom:6px'>{color_dot}<b>{title}</b>{pills}</div>"
273
-
274
- def two_color_prompts_for_sequence(seq_key: str) -> List[str]:
275
- colors = SEQ_TO_COLORS.get(seq_key, [])
276
- # ensure we always return up to two, pad with empty if fewer
277
- colors = colors[:2] + [""] * max(0, 2 - len(colors))
278
- return colors
279
-
280
- # --------------------------
281
- # gradio app
282
- # --------------------------
283
- SEQ_CHOICES = list(SEQUENCE_ALIASES.keys())
284
- WORD_MODES = ["Matrice1", "Matrice", "English"] # toggle chips source
285
-
286
- def update_color_prompt_ui(seq_choice: str, word_mode: str):
287
- """Update color chips + textbox labels when pathway or word mode changes."""
288
- key = SEQUENCE_ALIASES.get(seq_choice, "auto")
289
- if key == "auto":
290
- # no fixed colors yet
291
- return (
292
- gr.update(value="Select a pathway to see color prompts."), # chips block
293
- gr.update(label="Color A", placeholder="—", value=""),
294
- gr.update(label="Color B", placeholder="—", value="")
295
- )
296
-
297
- mode = word_mode.lower()
298
- if mode == "matrice1": mode_key = "matrice1"
299
- elif mode == "matrice": mode_key = "matrice"
300
- else: mode_key = "english"
301
-
302
- c1, c2 = two_color_prompts_for_sequence(key)
303
- chips_html = ""
304
- if c1: chips_html += lex_chip_html(c1, mode_key)
305
- if c2: chips_html += lex_chip_html(c2, mode_key)
306
- if not chips_html:
307
- chips_html = "No color lexicon found for this pathway."
308
-
309
- tb1 = gr.update(label=f"{c1.capitalize() if c1 else 'Color A'} meaning", placeholder=f"Describe {c1} meaning..." if c1 else "—", value="")
310
- tb2 = gr.update(label=f"{c2.capitalize() if c2 else 'Color B'} meaning", placeholder=f"Describe {c2} meaning..." if c2 else "—", value="")
311
-
312
- return chips_html, tb1, tb2
313
-
314
- def analyze(
315
- text: str,
316
- seq_choice: str,
317
- word_mode: str,
318
- color_a_text: str,
319
- color_b_text: str,
320
- limit_seqs_labels: List[str],
321
- color_prompt_weight: float,
322
- limit_weight: float,
323
- ):
324
- if not text or not text.strip():
325
- return (5.0, "neutral (0.0)", 5.0, "—", "—", "{}", None, None, "—")
326
-
327
- sentiment = score_sentiment(text)
328
- emotion, emo_conf = classify_emotion(text)
329
- accomplishment = score_accomplishment(text)
330
-
331
- chosen_key = SEQUENCE_ALIASES.get(seq_choice, "auto")
332
-
333
- # build extras/limits from pathway + color texts
334
- extras: List[Tuple[str,float]] = []
335
- limits: List[Tuple[str,float]] = []
336
-
337
- # include the pathway phrase itself as a small bias, if not auto
338
- if chosen_key != "auto":
339
- phrase = SEQ_PHRASE.get(chosen_key, "")
340
- if phrase: extras.append((phrase, 0.5))
341
-
342
- # include color interpretation texts (user inputs)
343
- if color_a_text.strip(): extras.append((color_a_text.strip(), color_prompt_weight))
344
- if color_b_text.strip(): extras.append((color_b_text.strip(), color_prompt_weight))
345
-
346
- # limits: other sequences user selected to suppress
347
- limit_keys = [SEQUENCE_ALIASES.get(lbl, lbl).lower() for lbl in limit_seqs_labels]
348
- for k in limit_keys:
349
- p = SEQ_PHRASE.get(k, "")
350
- if p: limits.append((p, limit_weight))
351
-
352
- # composite vector & choose pathway (or evaluate chosen)
353
- context_vec = composite_vector(text, extras, limits)
354
-
355
- if chosen_key == "auto":
356
- final_key, final_sim = best_sequence_for_vector(context_vec)
357
- else:
358
- final_key = chosen_key
359
- phrase = SEQ_PHRASE.get(final_key, "")
360
- final_sim = float(util.cos_sim(context_vec, encode_text(phrase)).item()) if phrase else 0.0
361
-
362
- # outputs
363
- final_phrase = SEQ_PHRASE.get(final_key, "—")
364
- img_path = sequence_to_image_path(final_key)
365
-
366
- indicators = semantic_indicator_mapping_from_vec(context_vec, sentiment_score=sentiment)
367
  fig = indicators_plot(indicators)
368
  top5 = list(indicators.items())[:5]
369
  top5_str = "\n".join(f"{k}: {v}" for k, v in top5)
370
 
 
371
  emo_str = f"{emotion} ({emo_conf:.3f})"
372
- meta = f"{final_key} (relevance={final_sim:.3f})"
373
- # show the two pathway colors for transparency
374
- cols = SEQ_TO_COLORS.get(final_key, [])
375
- meta += f" | colors: {', '.join(cols) if cols else '—'}"
376
 
377
- # also return updated chips/labels if we were in Auto and just decided a pathway
378
- chips, tb1, tb2 = update_color_prompt_ui(seq_choice if chosen_key!="auto" else next(k for k,v in SEQUENCE_ALIASES.items() if v==final_key), word_mode)
379
 
380
  return (
381
  sentiment, emo_str, accomplishment,
382
- final_phrase, top5_str, fig, img_path, meta,
383
- chips, tb1, tb2
384
  )
385
 
 
 
 
 
386
  with gr.Blocks(title="RGB Root Matriz Color Plotter") as demo:
387
  gr.Markdown("## RGB Root Matriz Color Plotter\n"
388
  "Type a phrase. Choose a **Sequence** or keep **Auto** to recommend a pathway. "
389
  "You’ll get sentiment, emotion, accomplishment, GNH bars, and the pathway phrase + image from the dataset.")
390
 
391
  with gr.Row():
392
- inp = gr.Textbox(lines=4, label="Input obstacle / situation", placeholder="Describe the situation...")
393
 
394
  with gr.Row():
395
- seq = gr.Dropdown(choices=list(SEQUENCE_ALIASES.keys()), value="Auto (recommend)", label="Primary Pathway")
396
- word_mode = gr.Radio(choices=["Matrice1","Matrice","English"], value="Matrice1", label="Word Mode")
397
 
398
- # Color Interpretations section (chips + two inputs)
399
- chips_block = gr.HTML("Select a pathway to see color prompts.")
400
- color_a = gr.Textbox(label="Color A meaning", placeholder="—")
401
- color_b = gr.Textbox(label="Color B meaning", placeholder="—")
402
 
403
- # knobs
404
- with gr.Row():
405
- limit_seqs = gr.CheckboxGroup(choices=[c for c in SEQUENCE_ALIASES.keys() if c != "Auto (recommend)"],
406
- label="Limit pathways (optional)")
407
- with gr.Row():
408
- color_w = gr.Slider(0.0, 1.5, value=0.8, step=0.05, label="Color prompt weight")
409
- limit_w = gr.Slider(0.0, 1.5, value=0.6, step=0.05, label="Limit weight")
410
 
411
  run = gr.Button("Generate Pathway Analysis", variant="primary")
412
 
@@ -417,23 +372,33 @@ with gr.Blocks(title="RGB Root Matriz Color Plotter") as demo:
417
  acc = gr.Number(label="Accomplishment (1–10)")
418
 
419
  with gr.Row():
420
- phrase_out = gr.Text(label="Pathway phrase")
421
  gnh_top = gr.Text(label="Top GNH Indicators (Top 5)")
422
 
423
- gnh_plot = gr.Plot(label="GNH Similarity (Pathway-weighted)")
424
  img_out = gr.Image(label="Pathway image", type="filepath")
425
- meta_out = gr.Text(label="Chosen pathway / colors / relevance")
426
 
427
- # wire interactions
428
- seq.change(fn=update_color_prompt_ui, inputs=[seq, word_mode], outputs=[chips_block, color_a, color_b])
429
- word_mode.change(fn=update_color_prompt_ui, inputs=[seq, word_mode], outputs=[chips_block, color_a, color_b])
 
 
 
 
 
 
 
 
 
430
 
431
  run.click(
432
  fn=analyze,
433
- inputs=[inp, seq, word_mode, color_a, color_b, limit_seqs, color_w, limit_w],
434
- outputs=[sent, emo, acc, phrase_out, gnh_top, gnh_plot, img_out, meta_out, chips_block, color_a, color_b],
435
  )
436
 
437
  if __name__ == "__main__":
438
  demo.launch()
439
 
 
 
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
  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 + narrative pieces
37
+ CSV_PATH_COLOR = "la matrice.csv" # color lexicon
 
 
38
 
39
+ # only explicit pathways (no Auto here)
40
  SEQUENCE_ALIASES = {
 
41
  "Direct": "direct",
42
  "Fem": "feminine",
43
  "Knot": "knot",
 
51
  "Sad": "sad",
52
  }
53
 
54
+
55
  SEQUENCE_IMAGE_FILES = {
56
  "direct": "direct pathway.png",
57
  "feminine": "fem pathway.png",
 
66
  "sad": "sad pathway.png"
67
  }
68
 
69
+ # GNH dictionaries
70
  GNH_DOMAINS: Dict[str, str] = {
71
  "Mental Wellness": "mental health, emotional clarity, peace of mind",
72
  "Social Wellness": "relationships, community, friendship, social harmony",
 
99
  "Cultural Diversity": "#9370db",
100
  }
101
 
102
+ # ---------- load pathway → colors & phrase (plus) ----------
 
 
103
  def load_pathway_info(csv_path_plus: str):
104
  df = pd.read_csv(csv_path_plus)
105
+ keys = set(SEQUENCE_ALIASES.values())
106
  rows = df[df["color"].astype(str).str.lower().isin(keys)].copy()
107
 
108
  seq_to_colors: Dict[str, List[str]] = {}
109
  seq_phrase: Dict[str, str] = {}
110
 
 
111
  cols_for_phrase = [c for c in df.columns if c not in ("color", "r", "g", "b")]
112
  for _, row in rows.iterrows():
113
  key = str(row["color"]).strip().lower()
114
+ # colors list is in column 'r' (comma/space separated), supports 2–8
115
  colors_field = str(row.get("r", "") or "")
116
  colors = [c.strip().lower() for c in re.split(r"[,\s]+", colors_field) if c.strip()]
117
+ seq_to_colors[key] = list(dict.fromkeys(colors)) # dedupe, keep order
118
 
119
  vals = []
120
  for c in cols_for_phrase:
 
130
 
131
  SEQ_TO_COLORS, SEQ_PHRASE = load_pathway_info(CSV_PATH_PLUS)
132
 
133
+ # ---------- load color lexicon (color CSV) ----------
 
 
134
  def _find_col(df: pd.DataFrame, candidates: List[str]) -> str | None:
135
  names = {c.lower(): c for c in df.columns}
136
  for c in candidates:
137
  if c.lower() in names:
138
  return names[c.lower()]
 
139
  for want in candidates:
140
  for lc, orig in names.items():
141
  if want.replace(" ", "").replace("-", "") in lc.replace(" ", "").replace("-", ""):
 
171
  fname = SEQUENCE_IMAGE_FILES.get(seq_key)
172
  return fname if (fname and os.path.exists(fname)) else None
173
 
174
+ # ---------- core scoring ----------
175
+ def encode_text(t: str):
176
+ return sbert_model.encode(t, convert_to_tensor=True)
177
+
178
  def classify_emotion(text: str) -> Tuple[str, float]:
179
  inputs = emotion_tokenizer(text, return_tensors="pt", truncation=True)
180
  with torch.no_grad():
 
191
  return round(min(10, max(1, scaled)), 2)
192
 
193
  def score_accomplishment(text: str) -> float:
194
+ doc = nlp(text); score = 5.0
 
195
  key_phrases = {"finally","told","decided","quit","refused","stood","walked","walked away","returned","return"}
196
  for token in doc:
197
  if token.text.lower() in key_phrases: score += 1.5
198
  if token.tag_ in {"VBD","VBN"}: score += 0.5
199
  return round(min(10, max(1, score)), 2)
200
 
201
+ def semantic_indicator_mapping(text: str, sentiment_score: float, sentiment_weight: float = 0.3) -> Dict[str, float]:
202
+ v = encode_text(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  out: Dict[str, float] = {}
204
+ for dom, desc in GNH_DOMAINS.items():
205
+ sim = float(util.cos_sim(v, encode_text(desc)).item())
206
  sim = max(0.0, min(1.0, sim))
207
  blended = (1 - sentiment_weight) * sim + sentiment_weight * (sentiment_score / 10.0)
208
+ out[dom] = round(blended, 3)
209
  return dict(sorted(out.items(), key=lambda kv: -kv[1]))
210
 
211
  def indicators_plot(indicators: Dict[str, float]):
 
214
  fig = plt.figure(figsize=(8,5))
215
  plt.barh(labels, values, color=colors)
216
  plt.gca().invert_yaxis()
217
+ plt.title("GNH Indicator Similarity")
218
  plt.xlabel("Score")
219
  plt.tight_layout()
220
  return fig
221
 
222
+ # ---------- chips / prompts ----------
223
+ WORD_MODES = ["Matrice1", "Matrice", "English", "GNH Indicators"]
224
+
225
+ def join_lex_words(color: str) -> str:
226
+ d = COLOR_LEX.get(color.lower(), {})
227
+ words = d.get("matrice1", []) + d.get("matrice", []) + d.get("english", [])
228
+ return " ".join(dict.fromkeys(words))
229
+
230
+ def nearest_gnh_domain_for_color(color: str) -> Tuple[str, float]:
231
+ text = join_lex_words(color)
232
+ if not text:
233
+ return "Mental Wellness", 0.0
234
+ v = encode_text(text)
235
+ best, best_sim = None, -1.0
236
+ for dom, desc in GNH_DOMAINS.items():
237
+ sim = float(util.cos_sim(v, encode_text(desc)).item())
238
+ if sim > best_sim:
239
+ best, best_sim = dom, sim
240
+ return best or "Mental Wellness", best_sim
241
+
242
+ def chip_html_for(color: str, mode: str, max_words: int = 4) -> str:
243
+ if not color: return ""
244
+ if mode.lower().startswith("gnh"):
245
+ domain, sim = nearest_gnh_domain_for_color(color)
246
+ hex_color = GNH_COLORS.get(domain, "#cccccc")
247
+ 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>"
248
+ 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>"
249
+ return f"<div style='margin-bottom:6px'>{dot}<b>{color.capitalize()}</b>{pill}</div>"
250
+ # word modes
251
+ key = "english" if mode.lower() == "english" else ("matrice1" if mode.lower()=="matrice1" else "matrice")
252
+ words = COLOR_LEX.get(color.lower(), {}).get(key, [])[:max_words]
253
  pills = "".join(
254
  f"<span style='display:inline-block;margin:2px 6px;padding:2px 8px;border-radius:12px;background:#eee;font-size:12px'>{w}</span>"
255
  for w in words
256
  )
257
+ 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>"
258
+ return f"<div style='margin-bottom:6px'>{dot}<b>{color.capitalize()}</b>{pills}</div>"
259
+
260
+ def colors_for_sequence(seq_key: str) -> List[str]:
261
+ return SEQ_TO_COLORS.get(seq_key, []) # 2–8 colors
262
+
263
+ def labels_for_mode(colors: List[str], mode: str) -> List[str]:
264
+ if mode.lower().startswith("gnh"):
265
+ labs = []
266
+ for c in colors:
267
+ d, _ = nearest_gnh_domain_for_color(c)
268
+ labs.append(d)
269
+ return labs
270
+ return [c.capitalize() for c in colors]
271
+
272
+ # ---------- dynamic prompt UI (2–8 inputs) ----------
273
+ MAX_COLORS = 8 # upper bound for inputs we render
274
+
275
+ def update_prompt_ui(seq_choice: str, word_mode: str):
276
+ key = SEQUENCE_ALIASES.get(seq_choice)
277
+ colors = colors_for_sequence(key)
278
+ labels = labels_for_mode(colors, word_mode)
279
+
280
+ # chips HTML for all colors
281
+ chips = "".join(chip_html_for(c, word_mode) for c in colors) or "No prompts available for this pathway."
282
+
283
+ # build visibility/labels/placeholders for up to MAX_COLORS textboxes
284
+ inputs_updates = []
285
+ for i in range(MAX_COLORS):
286
+ if i < len(colors):
287
+ lab = f"{labels[i]} meaning" if labels[i] else f"Input {i+1} meaning"
288
+ ph = f"Describe {labels[i]} meaning..." if labels[i] else "—"
289
+ inputs_updates.append(gr.update(visible=True, label=lab, placeholder=ph, value=""))
290
+ else:
291
+ inputs_updates.append(gr.update(visible=False, value="", label=f"Input {i+1}", placeholder="—"))
292
+ return (chips, *inputs_updates)
293
+
294
+ # ---------- MAIN ANALYSIS ----------
295
+ def analyze(text: str, seq_choice: str, word_mode: str, *color_inputs):
296
+ """
297
+ - user chooses pathway
298
+ - we show N color prompts (2–8)
299
+ - compose updated pathway phrase that embeds all non-empty inputs
300
+ - analyze sentiment/emotion + GNH on (text + updated phrase)
301
+ """
302
+ key = SEQUENCE_ALIASES.get(seq_choice)
303
+ if key not in SEQ_PHRASE:
304
+ return (5.0, "neutral (0.0)", 5.0, "Please choose a valid pathway.", "{}", None, None,
305
+ f"{seq_choice} (unavailable)")
306
+
307
+ sentiment = score_sentiment(text or "")
308
+ emotion, emo_conf = classify_emotion(text or "")
309
+ accomplishment = score_accomplishment(text or "")
310
+
311
+ colors = colors_for_sequence(key)
312
+ labels = labels_for_mode(colors, word_mode)
313
+
314
+ # updated phrase = base phrase + each "{Label}: {input}"
315
+ base_phrase = SEQ_PHRASE.get(key, "")
316
+ pieces = [base_phrase]
317
+ for lab, user_text in zip(labels, list(color_inputs)[:len(colors)]):
318
+ if isinstance(user_text, str) and user_text.strip():
319
+ pieces.append(f"{lab}: {user_text.strip()}")
320
+ updated_phrase = " // ".join([p for p in pieces if p])
321
+
322
+ augmented_text = " ".join([t for t in [text, updated_phrase] if t and t.strip()])
323
+ indicators = semantic_indicator_mapping(augmented_text, sentiment_score=sentiment)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  fig = indicators_plot(indicators)
325
  top5 = list(indicators.items())[:5]
326
  top5_str = "\n".join(f"{k}: {v}" for k, v in top5)
327
 
328
+ cols = SEQ_TO_COLORS.get(key, [])
329
  emo_str = f"{emotion} ({emo_conf:.3f})"
330
+ meta = f"{key} | colors: {', '.join(cols) if cols else '—'}"
331
+ img_path = sequence_to_image_path(key)
 
 
332
 
333
+ # refresh prompt area to keep labels/visibility consistent after run
334
+ chips_and_inputs = update_prompt_ui(seq_choice, word_mode)
335
 
336
  return (
337
  sentiment, emo_str, accomplishment,
338
+ updated_phrase, top5_str, fig, img_path, meta,
339
+ *chips_and_inputs
340
  )
341
 
342
+ # ---------- Gradio UI ----------
343
+ SEQ_CHOICES = list(SEQUENCE_ALIASES.keys())
344
+ DEFAULT_SEQ = "Direct" if "Direct" in SEQ_CHOICES else SEQ_CHOICES[0]
345
+
346
  with gr.Blocks(title="RGB Root Matriz Color Plotter") as demo:
347
  gr.Markdown("## RGB Root Matriz Color Plotter\n"
348
  "Type a phrase. Choose a **Sequence** or keep **Auto** to recommend a pathway. "
349
  "You’ll get sentiment, emotion, accomplishment, GNH bars, and the pathway phrase + image from the dataset.")
350
 
351
  with gr.Row():
352
+ inp = gr.Textbox(lines=4, label="Your situation / obstacle", placeholder="Describe the situation...")
353
 
354
  with gr.Row():
355
+ seq = gr.Dropdown(choices=SEQ_CHOICES, value=DEFAULT_SEQ, label="Pathway")
356
+ word_mode = gr.Radio(choices=["Matrice1", "Matrice", "English", "GNH Indicators"], value="Matrice1", label="Word Mode")
357
 
358
+ chips_block = gr.HTML() # chips for all colors
 
 
 
359
 
360
+ # up to MAX_COLORS inputs (shown/hidden dynamically)
361
+ color_inputs = []
362
+ for i in range(MAX_COLORS):
363
+ tb = gr.Textbox(visible=False, label=f"Input {i+1}", placeholder="—")
364
+ color_inputs.append(tb)
 
 
365
 
366
  run = gr.Button("Generate Pathway Analysis", variant="primary")
367
 
 
372
  acc = gr.Number(label="Accomplishment (1–10)")
373
 
374
  with gr.Row():
375
+ phrase_out = gr.Text(label="Updated Pathway Phrase (with your meanings)")
376
  gnh_top = gr.Text(label="Top GNH Indicators (Top 5)")
377
 
378
+ gnh_plot = gr.Plot(label="GNH Similarity")
379
  img_out = gr.Image(label="Pathway image", type="filepath")
380
+ meta_out = gr.Text(label="Chosen pathway / colors")
381
 
382
+ # initialize prompt area for default selection
383
+ init_updates = update_prompt_ui(DEFAULT_SEQ, "Matrice1")
384
+ chips_block.value = init_updates[0]
385
+ for tb, up in zip(color_inputs, init_updates[1:1+MAX_COLORS]):
386
+ tb.update(**up)
387
+
388
+ # re-render prompts on changes
389
+ def _update_ui(seq_choice, mode):
390
+ return update_prompt_ui(seq_choice, mode)
391
+
392
+ seq.change(fn=_update_ui, inputs=[seq, word_mode], outputs=[chips_block, *color_inputs])
393
+ word_mode.change(fn=_update_ui, inputs=[seq, word_mode], outputs=[chips_block, *color_inputs])
394
 
395
  run.click(
396
  fn=analyze,
397
+ inputs=[inp, seq, word_mode, *color_inputs],
398
+ outputs=[sent, emo, acc, phrase_out, gnh_top, gnh_plot, img_out, meta_out, chips_block, *color_inputs],
399
  )
400
 
401
  if __name__ == "__main__":
402
  demo.launch()
403
 
404
+