Jairo Gudino commited on
Commit
056827d
·
1 Parent(s): be6cb23

Initial commit

Browse files
Files changed (2) hide show
  1. app.py +289 -370
  2. requirements.txt +6 -5
app.py CHANGED
@@ -5,7 +5,6 @@
5
  import os
6
  import io
7
  import gc
8
- import sys
9
  import time
10
  import warnings
11
  import pandas as pd
@@ -16,28 +15,14 @@ from contextlib import redirect_stdout, redirect_stderr
16
  # CrewAI
17
  from crewai import Agent, Task, Crew
18
 
19
- # === DEBUG INFO - TEMPORAL ===
20
- print("=== STARTING APP ===")
21
- print(f"Python version: {sys.version}")
22
- print(f"OPENAI_API_KEY present: {bool(os.getenv('OPENAI_API_KEY'))}")
23
- if os.getenv('OPENAI_API_KEY'):
24
- key_length = len(os.getenv('OPENAI_API_KEY'))
25
- key_start = os.getenv('OPENAI_API_KEY')[:10]
26
- print(f"API key length: {key_length}")
27
- print(f"API key starts with: {key_start}...")
28
- else:
29
- print("API key is None or empty")
30
- print("=== DEBUG END ===")
31
-
32
  # --- Configuración de entorno ---
 
 
33
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
34
  if not OPENAI_API_KEY:
35
- print("WARNING: OPENAI_API_KEY not found!")
36
- # Para HuggingFace, esto puede ser normal durante el build
37
- else:
38
- os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
39
- print(f"API key configured successfully")
40
 
 
41
  os.environ["USER_AGENT"] = os.getenv("USER_AGENT", "ConsensusApp")
42
  os.environ['CREWAI_DO_NOT_TELEMETRY'] = 'true'
43
  os.environ['CREWAI_SHARE_CREW'] = 'false'
@@ -45,12 +30,12 @@ os.environ['CREWAI_SHARE_CREW'] = 'false'
45
  # Suprimir warnings ruidosos
46
  warnings.filterwarnings("ignore", category=UserWarning, module="crewai")
47
  warnings.filterwarnings("ignore", category=DeprecationWarning)
48
- warnings.filterwarnings("ignore", category=FutureWarning)
49
 
50
  # =========================
51
  # Datos base y utilidades
52
  # =========================
53
 
 
54
  PARTY_TO_ORIENTATION = {
55
  "FDP": "RIGHT", "jf": "RIGHT", "CVP": "CENTER", "Grüne": "LEFT", "SVP": "RIGHT",
56
  "PdA": "LEFT", "SP": "LEFT", "glp": "CENTER", "JUSO": "LEFT", "BDP": "CENTER",
@@ -59,11 +44,19 @@ PARTY_TO_ORIENTATION = {
59
  "JBDP": "CENTER", "Die": "CENTER", "JFS": "RIGHT", "SD": "RIGHT", "JM": "CENTER"
60
  }
61
 
 
62
  LLM_BASE = "gpt-4o-mini"
63
  LLM_SUPERVISOR = "gpt-4o-mini"
64
  LLM_DIRECTOR = "gpt-4o-mini"
65
 
66
  def get_majority_decision_groups(df: pd.DataFrame) -> Dict:
 
 
 
 
 
 
 
67
  result = {}
68
 
69
  def calc_decision_and_percentages(series):
@@ -81,46 +74,30 @@ def get_majority_decision_groups(df: pd.DataFrame) -> Dict:
81
  return {"decision": decision, "percent_favor": favor_pct, "percent_against": against_pct}
82
 
83
  # Por idioma
84
- try:
85
- for lang, group in df.groupby('language')['label']:
86
- result[lang] = calc_decision_and_percentages(group)
87
- except Exception as e:
88
- print(f"Error processing languages: {e}")
89
 
90
  # Por partido
91
- try:
92
- for party, group in df.groupby('party_reply')['label']:
93
- result[party] = calc_decision_and_percentages(group)
94
- except Exception as e:
95
- print(f"Error processing parties: {e}")
96
 
97
  # Por orientación
98
- try:
99
- df_or = df.copy()
100
- df_or['orientation'] = df_or['party_reply'].map(PARTY_TO_ORIENTATION)
101
- df_or = df_or.dropna(subset=['orientation'])
102
- if not df_or.empty:
103
- for orient, group in df_or.groupby('orientation')['label']:
104
- result[f"{orient}_MAJORITY"] = calc_decision_and_percentages(group)
105
- except Exception as e:
106
- print(f"Error processing orientations: {e}")
107
 
108
  # Global
109
- try:
110
- result['FINAL_MAJORITY'] = calc_decision_and_percentages(df['label'])
111
- except Exception as e:
112
- print(f"Error processing final majority: {e}")
113
-
114
  return result
115
 
 
116
  def _silent_kickoff(crew: Crew):
117
  buf_out, buf_err = io.StringIO(), io.StringIO()
118
  with redirect_stdout(buf_out), redirect_stderr(buf_err):
119
- try:
120
- return crew.kickoff()
121
- except Exception as e:
122
- print(f"Crew kickoff error: {e}")
123
- return None
124
 
125
  # =========================
126
  # Sistema Multiagente (CrewAI)
@@ -131,51 +108,42 @@ class DemocraticConsensusCrewAI:
131
  self.question = question
132
  self.results = {"base_agents": {}, "supervisors": {}, "director": ""}
133
 
134
- try:
135
- self.base_agent = Agent(
136
- role="Democratic Base Representative",
137
- goal="Generate democratic consensus for any given group of opinions, weighting all perspectives equally.",
138
- backstory=("You are a neutral representative. Given any group's opinions and a majority decision, "
139
- "you craft a fair, balanced consensus that aligns with that majority, acknowledging minorities as nuances."),
140
- verbose=False,
141
- allow_delegation=False,
142
- llm=LLM_BASE
143
- )
144
- self.supervisor_agent = Agent(
145
- role="Democratic Supervisor",
146
- goal="Refine and consolidate consensuses for a language or an orientation umbrella.",
147
- backstory=("You refine base consensuses for a given umbrella (language or orientation), "
148
- "keeping the specified majority as the core, integrating minority views as exceptions."),
149
- verbose=False,
150
- allow_delegation=False,
151
- llm=LLM_SUPERVISOR
152
- )
153
- self.director_agent = Agent(
154
- role="Digital President - Final Decision Maker",
155
- goal="Synthesize all supervisor consensuses into a final consensus that aligns with the absolute majority.",
156
- backstory=("You aggregate all umbrellas and issue a final democratic position respecting the global majority."),
157
- verbose=False,
158
- allow_delegation=False,
159
- llm=LLM_DIRECTOR
160
- )
161
- except Exception as e:
162
- print(f"Error initializing agents: {e}")
163
- # Crear agentes básicos como fallback
164
- self.base_agent = None
165
- self.supervisor_agent = None
166
- self.director_agent = None
167
 
 
168
  def create_base_tasks(self, large_strings_by_language: Dict, large_strings_by_party: Dict, majorities_decision: Dict):
169
- if not self.base_agent:
170
- return []
171
-
172
  tasks = []
173
- try:
174
- # Por idioma
175
- for language, opinions_text in large_strings_by_language.items():
176
- maj = majorities_decision.get(language, {}).get('decision', 'AMBIGUOUS')
177
- t = Task(
178
- description=f"""
179
  MANDATORY INSTRUCTION: The consensus you generate MUST be based around the MAJORITY DECISION: {maj}
180
 
181
  Analyze the following {language} language opinions to answer the question: '{self.question}'
@@ -190,16 +158,16 @@ CRITICAL REQUIREMENTS:
190
 
191
  Return only the final consensus text that supports {maj}.
192
  """.strip(),
193
- agent=self.base_agent,
194
- expected_output=f"[BASE][LANG={language}][MAJ={maj}]"
195
- )
196
- tasks.append(t)
197
 
198
- # Por partido
199
- for party, opinions_text in large_strings_by_party.items():
200
- maj = majorities_decision.get(party, {}).get('decision', 'AMBIGUOUS')
201
- t = Task(
202
- description=f"""
203
  MANDATORY INSTRUCTION: The consensus you generate MUST be based around the MAJORITY DECISION: {maj}
204
 
205
  Analyze the following {party} party opinions to answer the question: '{self.question}'
@@ -214,28 +182,21 @@ CRITICAL REQUIREMENTS:
214
 
215
  Return only the final consensus text that supports {maj}.
216
  """.strip(),
217
- agent=self.base_agent,
218
- expected_output=f"[BASE][PARTY={party}][MAJ={maj}]"
219
- )
220
- tasks.append(t)
221
- except Exception as e:
222
- print(f"Error creating base tasks: {e}")
223
-
224
  return tasks
225
 
226
  def create_supervisor_tasks(self, base_results: Dict, majorities_decision: Dict):
227
- if not self.supervisor_agent:
228
- return []
229
-
230
  tasks = []
231
- try:
232
- # Idiomas
233
- for lang in ["German", "French", "Italian"]:
234
- key = ('LANGUAGE', lang)
235
- if key in base_results:
236
- maj = majorities_decision.get(lang, {}).get('decision', 'AMBIGUOUS')
237
- t = Task(
238
- description=f"""
239
  MANDATORY INSTRUCTION: Your refined consensus MUST be based around the MAJORITY DECISION: {maj}
240
 
241
  Refine and consolidate the democratic consensus for {lang} language regarding: '{self.question}'
@@ -249,23 +210,23 @@ CRITICAL REQUIREMENTS:
249
 
250
  Return only the refined consensus in English supporting {maj}.
251
  """.strip(),
252
- agent=self.supervisor_agent,
253
- expected_output=f"[SUPERVISOR][LANG={lang}][MAJ={maj}]"
254
- )
255
- tasks.append(t)
256
-
257
- # Orientaciones (unifica partidos)
258
- orientation_consensuses = {"LEFT": [], "CENTER": [], "RIGHT": []}
259
- for (kind, name), consensus in base_results.items():
260
- if kind == 'PARTY' and name in PARTY_TO_ORIENTATION:
261
- orientation_consensuses[PARTY_TO_ORIENTATION[name]].append(consensus)
262
-
263
- for orientation in ["LEFT", "CENTER", "RIGHT"]:
264
- if orientation_consensuses[orientation]:
265
- combined = "\n\n".join([f"Consensus {i+1}: {c}" for i, c in enumerate(orientation_consensuses[orientation], 1)])
266
- maj = majorities_decision.get(f"{orientation}_MAJORITY", {}).get('decision', 'AMBIGUOUS')
267
- t = Task(
268
- description=f"""
269
  MANDATORY INSTRUCTION: Your unified consensus MUST be based around the MAJORITY DECISION: {maj}
270
 
271
  Create a unified democratic consensus for {orientation} orientation regarding: '{self.question}'
@@ -279,24 +240,17 @@ CRITICAL REQUIREMENTS:
279
 
280
  Return only the meta-consensus in English supporting {maj}.
281
  """.strip(),
282
- agent=self.supervisor_agent,
283
- expected_output=f"[SUPERVISOR][ORIENT={orientation}][MAJ={maj}]"
284
- )
285
- tasks.append(t)
286
- except Exception as e:
287
- print(f"Error creating supervisor tasks: {e}")
288
-
289
  return tasks
290
 
291
  def create_director_task(self, supervisor_results: Dict, majorities_decision: Dict):
292
- if not self.director_agent:
293
- return None
294
-
295
- try:
296
- all_consensuses = "\n\n".join([f"{k}: {v}" for k, v in supervisor_results.items()])
297
- final_majority = majorities_decision.get('FINAL_MAJORITY', {}).get('decision', 'AMBIGUOUS')
298
- t = Task(
299
- description=f"""
300
  SUPREME MANDATORY INSTRUCTION: Your FINAL consensus MUST be based around the ABSOLUTE MAJORITY DECISION: {final_majority}
301
 
302
  As Digital President, synthesize the FINAL DEMOCRATIC CONSENSUS for the question: '{self.question}'
@@ -311,82 +265,58 @@ CRITICAL REQUIREMENTS:
311
 
312
  Return only the final consensus supporting {final_majority}.
313
  """.strip(),
314
- agent=self.director_agent,
315
- expected_output=f"[DIRECTOR][MAJ={final_majority}]"
316
- )
317
- return t
318
- except Exception as e:
319
- print(f"Error creating director task: {e}")
320
- return None
321
 
322
  def run_democratic_consensus_system(self, large_strings_by_language: Dict, large_strings_by_party: Dict, majorities_decision: Dict = {}) -> Dict:
323
- try:
324
- # Nivel 1: Base
325
- base_tasks = self.create_base_tasks(large_strings_by_language, large_strings_by_party, majorities_decision)
326
- base_results = {}
327
- if base_tasks and self.base_agent:
328
- base_crew = Crew(agents=[self.base_agent], tasks=base_tasks, verbose=0, share_crew=False)
329
- base_out = _silent_kickoff(base_crew)
330
- if base_out and hasattr(base_out, 'tasks_output'):
331
- for i, task in enumerate(base_tasks):
332
- raw = base_out.tasks_output[i].raw if i < len(base_out.tasks_output) else ""
333
- tag = task.expected_output
334
- if "[LANG=" in tag:
335
- lang = tag.split("[LANG=")[1].split("]")[0]
336
- base_results[('LANGUAGE', lang)] = raw
337
- elif "[PARTY=" in tag:
338
- party = tag.split("[PARTY=")[1].split("]")[0]
339
- base_results[('PARTY', party)] = raw
340
- self.results["base_agents"] = base_results
341
-
342
- # Nivel 2: Supervisor
343
- supervisor_tasks = self.create_supervisor_tasks(base_results, majorities_decision)
344
- supervisor_results = {}
345
- if supervisor_tasks and self.supervisor_agent:
346
- sup_crew = Crew(agents=[self.supervisor_agent], tasks=supervisor_tasks, verbose=0, share_crew=False)
347
- sup_out = _silent_kickoff(sup_crew)
348
- if sup_out and hasattr(sup_out, 'tasks_output'):
349
- for i, task in enumerate(supervisor_tasks):
350
- raw = sup_out.tasks_output[i].raw if i < len(sup_out.tasks_output) else ""
351
- tag = task.expected_output
352
- if "[LANG=" in tag:
353
- lang = tag.split("[LANG=")[1].split("]")[0]
354
- supervisor_results[('LANGUAGE', lang)] = raw
355
- elif "[ORIENT=" in tag:
356
- orient = tag.split("[ORIENT=")[1].split("]")[0]
357
- supervisor_results[('ORIENTATION', orient)] = raw
358
- self.results["supervisors"] = supervisor_results
359
-
360
- # Nivel 3: Director
361
- director_task = self.create_director_task(supervisor_results, majorities_decision)
362
- final_consensus = "No final consensus generated"
363
- if director_task and self.director_agent:
364
- dir_crew = Crew(agents=[self.director_agent], tasks=[director_task], verbose=0, share_crew=False)
365
- dir_out = _silent_kickoff(dir_crew)
366
- if dir_out and hasattr(dir_out, 'tasks_output') and dir_out.tasks_output:
367
- final_consensus = dir_out.tasks_output[0].raw
368
- self.results["director"] = final_consensus
369
-
370
- except Exception as e:
371
- print(f"Error in consensus system: {e}")
372
- # Generar resultados de fallback
373
- self.results = {
374
- "base_agents": {},
375
- "supervisors": {
376
- ('LANGUAGE', 'German'): "Error: Unable to generate consensus due to system issues.",
377
- ('LANGUAGE', 'French'): "Error: Unable to generate consensus due to system issues.",
378
- ('LANGUAGE', 'Italian'): "Error: Unable to generate consensus due to system issues.",
379
- ('ORIENTATION', 'LEFT'): "Error: Unable to generate consensus due to system issues.",
380
- ('ORIENTATION', 'CENTER'): "Error: Unable to generate consensus due to system issues.",
381
- ('ORIENTATION', 'RIGHT'): "Error: Unable to generate consensus due to system issues."
382
- },
383
- "director": "Error: Unable to generate final consensus due to system issues."
384
- }
385
 
386
  # Limpieza
387
  gc.collect()
388
  return self.results
389
 
 
390
  def run_democratic_consensus_system(large_strings_by_language: Dict, large_strings_by_party: Dict, question: str = "", majorities_decision: Dict = {}) -> Dict:
391
  system = DemocraticConsensusCrewAI(question=question)
392
  return system.run_democratic_consensus_system(large_strings_by_language, large_strings_by_party, majorities_decision)
@@ -396,6 +326,11 @@ def run_democratic_consensus_system(large_strings_by_language: Dict, large_strin
396
  # =========================
397
 
398
  def process_excel_and_generate_consensus(excel_file_path):
 
 
 
 
 
399
  if not excel_file_path:
400
  return ("Error: No file uploaded", "", "", "", "", "", "", "", "",
401
  "", "", "", "", "", "", "")
@@ -411,21 +346,22 @@ def process_excel_and_generate_consensus(excel_file_path):
411
  "", "", "", "", "", "", "")
412
 
413
  # Extraer pregunta si existe
414
- question = "Question not found in Excel"
415
- if 'question_y' in df.columns and not df['question_y'].dropna().empty:
416
  question = str(df['question_y'].dropna().iloc[0])
417
- elif 'question' in df.columns and not df['question'].dropna().empty:
418
  question = str(df['question'].dropna().iloc[0])
 
 
419
 
420
  # Strings por idioma y partido
421
  large_strings_by_language = (
422
  df.groupby("language")["comment"]
423
- .apply(lambda g: "'; ".join([f"Opinion {i+1}: '{str(c)}" for i, c in enumerate(g.dropna())]))
424
  .to_dict()
425
  )
426
  large_strings_by_party = (
427
  df.groupby("party_reply")["comment"]
428
- .apply(lambda g: "'; ".join([f"Opinion {i+1}: '{str(c)}" for i, c in enumerate(g.dropna())]))
429
  .to_dict()
430
  )
431
 
@@ -467,7 +403,6 @@ def process_excel_and_generate_consensus(excel_file_path):
467
  left_percentage, center_percentage, right_percentage, final_percentage)
468
 
469
  except Exception as e:
470
- print(f"Error processing Excel: {e}")
471
  return (f"Error processing file: {e}", "", "", "", "", "", "", "", "",
472
  "", "", "", "", "", "", "")
473
 
@@ -476,184 +411,168 @@ def process_excel_and_generate_consensus(excel_file_path):
476
  # =========================
477
 
478
  def create_consensus_app():
479
- try:
480
- demo = gr.Blocks(
481
- title="🏛️ Consensus Statements",
482
- theme=gr.themes.Default(),
483
- css="""
484
  .flag-container { display: flex; justify-content: center; align-items: center; gap: 15px; margin-bottom: 10px; }
485
  .flag-image { width: 50px; height: auto; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
 
 
486
  .consensus-box { padding: 15px; border-radius: 15px; margin: 10px; min-height: 300px; max-height: 500px;
487
  overflow-y: auto; word-wrap: break-word; display: flex; align-items: flex-start; justify-content: center;
488
  text-align: center; font-family: 'Georgia', 'Times New Roman', serif; font-weight: normal; }
489
  .consensus-box-content { font-size: 1.0em; line-height: 1.4; padding: 5px; width: 100%; }
 
490
  .consensus-box-german { background: linear-gradient(135deg, #FFB6C1, #FFC0CB); color: #333333; }
491
  .consensus-box-french { background: linear-gradient(135deg, #E6E6FA, #DDA0DD); color: #333333; }
492
  .consensus-box-italian { background: linear-gradient(135deg, #F0E68C, #F5DEB3); color: #333333; }
493
  .consensus-box-left { background: linear-gradient(135deg, #98FB98, #90EE90); color: #333333; }
494
  .consensus-box-center { background: linear-gradient(135deg, #87CEEB, #B0E0E6); color: #333333; }
495
  .consensus-box-right { background: linear-gradient(135deg, #FFDAB9, #FFE4B5); color: #333333; }
 
 
496
  .final-consensus-box { background: linear-gradient(135deg, #A5D6A7, #66BB6A); color: #333333; padding: 30px; border-radius: 15px;
497
  margin: 20px 0; box-shadow: 0 6px 10px rgba(0,0,0,0.15); min-height: 150px; max-height: 400px; overflow-y: auto;
498
  display: flex; align-items: flex-start; justify-content: center; text-align: center; font-family: 'Georgia', 'Times New Roman', serif;
499
  font-weight: normal; font-size: 1.1em; }
 
500
  .question-header { background: linear-gradient(135deg, #1e3a5f, #2d5a87); color: white; padding: 15px; border-radius: 10px;
501
  text-align: center; margin-bottom: 20px; font-family: 'Georgia', 'Times New Roman', serif; font-weight: normal; }
 
502
  .upload-section { text-align: center; margin: 20px 0; }
503
  .gradio-container { font-family: 'Georgia', 'Times New Roman', serif; }
 
 
504
  .custom-button-color { background-color: #2e8b57 !important; color: #ffffff !important; border-color: #2e8b57 !important; }
505
  """
506
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
 
508
- with demo:
509
- # Título y banderas
510
- gr.HTML("""
511
- <div class="flag-container">
512
- <img src="https://upload.wikimedia.org/wikipedia/commons/b/ba/Flag_of_Germany.svg" class="flag-image" alt="German Flag">
513
- <img src="https://upload.wikimedia.org/wikipedia/commons/c/c3/Flag_of_France.svg" class="flag-image" alt="French Flag">
514
- <img src="https://upload.wikimedia.org/wikipedia/commons/0/03/Flag_of_Italy.svg" class="flag-image" alt="Italian Flag">
515
- </div>
516
- <h1 style='text-align: center; font-size: 1.8em; margin-bottom: 15px;'>Consensus Statements</h1>
517
- """)
518
-
519
- # Carga
520
- with gr.Row(elem_classes=["upload-section"]):
521
- with gr.Column():
522
- excel_input = gr.File(label="📊 UPLOAD EXCEL HERE!", file_types=[".xlsx", ".xls"], type="filepath")
523
- process_btn = gr.Button("🚀 Generate Consensus", elem_classes=["custom-button-color"], size="lg")
524
-
525
- status_output = gr.Textbox(label="Status", interactive=False, value="Ready to process Excel file...")
526
- question_display = gr.HTML(value='<div class="question-header">Upload an Excel file to see the question here</div>',
527
- elem_classes=["question-header"])
528
-
529
- # Estado
530
- all_data = gr.State()
531
-
532
- # Cajas
533
- with gr.Row():
534
- with gr.Column():
535
- german_output = gr.HTML(
536
- value='<div class="consensus-box-content">Consensus Statement:<br>German-speaking cantons<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
537
- elem_classes=["consensus-box", "consensus-box-german"]
538
- )
539
- with gr.Column():
540
- french_output = gr.HTML(
541
- value='<div class="consensus-box-content">Consensus Statement:<br>French-speaking cantons<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
542
- elem_classes=["consensus-box", "consensus-box-french"]
543
- )
544
- with gr.Column():
545
- italian_output = gr.HTML(
546
- value='<div class="consensus-box-content">Consensus Statement:<br>Italian-speaking cantons<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
547
- elem_classes=["consensus-box", "consensus-box-italian"]
548
- )
549
-
550
- with gr.Row():
551
- with gr.Column():
552
- left_output = gr.HTML(
553
- value='<div class="consensus-box-content">Consensus Statement:<br>LEFT<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
554
- elem_classes=["consensus-box", "consensus-box-left"]
555
- )
556
- with gr.Column():
557
- center_output = gr.HTML(
558
- value='<div class="consensus-box-content">Consensus Statement:<br>CENTER<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
559
- elem_classes=["consensus-box", "consensus-box-center"]
560
- )
561
- with gr.Column():
562
- right_output = gr.HTML(
563
- value='<div class="consensus-box-content">Consensus Statement:<br>RIGHT<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
564
- elem_classes=["consensus-box", "consensus-box-right"]
565
- )
566
-
567
- final_output = gr.HTML(
568
- value='<div class="consensus-box-content">FINAL - CONSENSUS STATEMENT:<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
569
- elem_classes=["final-consensus-box"]
570
- )
571
 
572
- # Flujo
573
- def process_and_store_data(excel_file_path):
574
- data = process_excel_and_generate_consensus(excel_file_path)
575
- return data, data
576
-
577
- def update_display(data):
578
- if not data:
579
- return ("Error processing file", "", "", "", "", "", "", "", "")
580
- (status, question, german, french, italian, left, center, right, final,
581
- german_percentage, french_percentage, italian_percentage,
582
- left_percentage, center_percentage, right_percentage, final_percentage) = data
583
-
584
- question_html = f'<div class="question-header">Question: {question}</div>'
585
- german_html = f'<div class="consensus-box-content">Consensus Statement:<br>German-speaking cantons<br><br>{german}<br><b>SUPPORTING THIS DECISION: {german_percentage}</b></div>'
586
- french_html = f'<div class="consensus-box-content">Consensus Statement:<br>French-speaking cantons<br><br>{french}<br><b>SUPPORTING THIS DECISION: {french_percentage}</b></div>'
587
- italian_html = f'<div class="consensus-box-content">Consensus Statement:<br>Italian-speaking cantons<br><br>{italian}<br><b>SUPPORTING THIS DECISION: {italian_percentage}</b></div>'
588
- left_html = f'<div class="consensus-box-content">Consensus Statement:<br>LEFT<br><br>{left}<br><b>SUPPORTING THIS DECISION: {left_percentage}</b></div>'
589
- center_html = f'<div class="consensus-box-content">Consensus Statement:<br>CENTER<br><br>{center}<br><b>SUPPORTING THIS DECISION: {center_percentage}</b></div>'
590
- right_html = f'<div class="consensus-box-content">Consensus Statement:<br>RIGHT<br><br>{right}<br><b>SUPPORTING THIS DECISION: {right_percentage}</b></div>'
591
- final_html = f'<div class="consensus-box-content">FINAL - CONSENSUS STATEMENT:<br><br>{final}<br><b>SUPPORTING THIS DECISION: {final_percentage}</b></div>'
592
-
593
- return (status, question_html, german_html, french_html, italian_html,
594
- left_html, center_html, right_html, final_html)
595
-
596
- # Encadenamiento
597
- process_btn.click(
598
- fn=process_and_store_data,
599
- inputs=[excel_input],
600
- outputs=[all_data, all_data]
601
- ).then(
602
- fn=update_display,
603
- inputs=[all_data],
604
- outputs=[status_output, question_display, german_output, french_output, italian_output,
605
- left_output, center_output, right_output, final_output]
606
- )
607
 
608
- return demo
609
- except Exception as e:
610
- print(f"Error creating Gradio app: {e}")
611
- # Crear una interfaz de fallback muy simple
612
- def simple_interface():
613
- return "Error: Unable to create full interface. Please check system configuration."
614
-
615
- return gr.Interface(
616
- fn=simple_interface,
617
- inputs=[],
618
- outputs="text",
619
- title="Consensus App - Error Mode"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  )
621
 
 
 
622
  # =========================
623
- # Main - Para HuggingFace Spaces
624
  # =========================
625
  if __name__ == "__main__":
626
- try:
627
- print("Creating Gradio app...")
628
- app = create_consensus_app()
629
-
630
- print("Launching app...")
631
- # Configuración específica para HuggingFace Spaces
 
632
  app.launch(
633
- share=False, # HuggingFace maneja el sharing
634
- server_name="0.0.0.0",
635
- server_port=7860,
636
- debug=False,
637
- inbrowser=False,
638
- show_error=True,
639
- quiet=False
640
- )
641
- except Exception as e:
642
- print(f"Critical error launching app: {e}")
643
- # Último recurso: crear interfaz básica
644
- def emergency_interface():
645
- return f"Application Error: {str(e)}"
646
-
647
- emergency_app = gr.Interface(
648
- fn=emergency_interface,
649
- inputs=[],
650
- outputs="text",
651
- title="Emergency Mode"
652
- )
653
- emergency_app.launch(
654
  share=False,
655
- server_name="0.0.0.0",
656
  server_port=7860,
657
  debug=False,
658
  inbrowser=False
 
 
 
 
 
 
 
 
 
659
  )
 
5
  import os
6
  import io
7
  import gc
 
8
  import time
9
  import warnings
10
  import pandas as pd
 
15
  # CrewAI
16
  from crewai import Agent, Task, Crew
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  # --- Configuración de entorno ---
19
+ # IMPORTANTE: En HuggingFace Spaces, las variables de entorno se configuran en la interfaz web
20
+ # NO pongas tu API key directamente en el código
21
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
22
  if not OPENAI_API_KEY:
23
+ raise ValueError("OPENAI_API_KEY environment variable is required")
 
 
 
 
24
 
25
+ os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
26
  os.environ["USER_AGENT"] = os.getenv("USER_AGENT", "ConsensusApp")
27
  os.environ['CREWAI_DO_NOT_TELEMETRY'] = 'true'
28
  os.environ['CREWAI_SHARE_CREW'] = 'false'
 
30
  # Suprimir warnings ruidosos
31
  warnings.filterwarnings("ignore", category=UserWarning, module="crewai")
32
  warnings.filterwarnings("ignore", category=DeprecationWarning)
 
33
 
34
  # =========================
35
  # Datos base y utilidades
36
  # =========================
37
 
38
+ # Mapeo de partidos políticos a orientaciones
39
  PARTY_TO_ORIENTATION = {
40
  "FDP": "RIGHT", "jf": "RIGHT", "CVP": "CENTER", "Grüne": "LEFT", "SVP": "RIGHT",
41
  "PdA": "LEFT", "SP": "LEFT", "glp": "CENTER", "JUSO": "LEFT", "BDP": "CENTER",
 
44
  "JBDP": "CENTER", "Die": "CENTER", "JFS": "RIGHT", "SD": "RIGHT", "JM": "CENTER"
45
  }
46
 
47
+ # Modelos por nivel
48
  LLM_BASE = "gpt-4o-mini"
49
  LLM_SUPERVISOR = "gpt-4o-mini"
50
  LLM_DIRECTOR = "gpt-4o-mini"
51
 
52
  def get_majority_decision_groups(df: pd.DataFrame) -> Dict:
53
+ """
54
+ Calcula decisiones por mayoría y porcentajes (FAVOR/AGAINST) por:
55
+ - language
56
+ - party_reply
57
+ - orientación (derivada de party_reply via PARTY_TO_ORIENTATION)
58
+ - FINAL_MAJORITY global
59
+ """
60
  result = {}
61
 
62
  def calc_decision_and_percentages(series):
 
74
  return {"decision": decision, "percent_favor": favor_pct, "percent_against": against_pct}
75
 
76
  # Por idioma
77
+ for lang, group in df.groupby('language')['label']:
78
+ result[lang] = calc_decision_and_percentages(group)
 
 
 
79
 
80
  # Por partido
81
+ for party, group in df.groupby('party_reply')['label']:
82
+ result[party] = calc_decision_and_percentages(group)
 
 
 
83
 
84
  # Por orientación
85
+ df_or = df.copy()
86
+ df_or['orientation'] = df_or['party_reply'].map(PARTY_TO_ORIENTATION)
87
+ df_or = df_or.dropna(subset=['orientation'])
88
+ if not df_or.empty:
89
+ for orient, group in df_or.groupby('orientation')['label']:
90
+ result[f"{orient}_MAJORITY"] = calc_decision_and_percentages(group)
 
 
 
91
 
92
  # Global
93
+ result['FINAL_MAJORITY'] = calc_decision_and_percentages(df['label'])
 
 
 
 
94
  return result
95
 
96
+ # Silenciar salidas ruidosas de CrewAI
97
  def _silent_kickoff(crew: Crew):
98
  buf_out, buf_err = io.StringIO(), io.StringIO()
99
  with redirect_stdout(buf_out), redirect_stderr(buf_err):
100
+ return crew.kickoff()
 
 
 
 
101
 
102
  # =========================
103
  # Sistema Multiagente (CrewAI)
 
108
  self.question = question
109
  self.results = {"base_agents": {}, "supervisors": {}, "director": ""}
110
 
111
+ # Un agente por nivel
112
+ self.base_agent = Agent(
113
+ role="Democratic Base Representative",
114
+ goal="Generate democratic consensus for any given group of opinions, weighting all perspectives equally.",
115
+ backstory=("You are a neutral representative. Given any group's opinions and a majority decision, "
116
+ "you craft a fair, balanced consensus that aligns with that majority, acknowledging minorities as nuances."),
117
+ verbose=False,
118
+ allow_delegation=False,
119
+ llm=LLM_BASE
120
+ )
121
+ self.supervisor_agent = Agent(
122
+ role="Democratic Supervisor",
123
+ goal="Refine and consolidate consensuses for a language or an orientation umbrella.",
124
+ backstory=("You refine base consensuses for a given umbrella (language or orientation), "
125
+ "keeping the specified majority as the core, integrating minority views as exceptions."),
126
+ verbose=False,
127
+ allow_delegation=False,
128
+ llm=LLM_SUPERVISOR
129
+ )
130
+ self.director_agent = Agent(
131
+ role="Digital President - Final Decision Maker",
132
+ goal="Synthesize all supervisor consensuses into a final consensus that aligns with the absolute majority.",
133
+ backstory=("You aggregate all umbrellas and issue a final democratic position respecting the global majority."),
134
+ verbose=False,
135
+ allow_delegation=False,
136
+ llm=LLM_DIRECTOR
137
+ )
 
 
 
 
 
 
138
 
139
+ # Creación de tareas
140
  def create_base_tasks(self, large_strings_by_language: Dict, large_strings_by_party: Dict, majorities_decision: Dict):
 
 
 
141
  tasks = []
142
+ # Por idioma
143
+ for language, opinions_text in large_strings_by_language.items():
144
+ maj = majorities_decision.get(language, {}).get('decision', 'AMBIGUOUS')
145
+ t = Task(
146
+ description=f"""
 
147
  MANDATORY INSTRUCTION: The consensus you generate MUST be based around the MAJORITY DECISION: {maj}
148
 
149
  Analyze the following {language} language opinions to answer the question: '{self.question}'
 
158
 
159
  Return only the final consensus text that supports {maj}.
160
  """.strip(),
161
+ agent=self.base_agent,
162
+ expected_output=f"[BASE][LANG={language}][MAJ={maj}]"
163
+ )
164
+ tasks.append(t)
165
 
166
+ # Por partido
167
+ for party, opinions_text in large_strings_by_party.items():
168
+ maj = majorities_decision.get(party, {}).get('decision', 'AMBIGUOUS')
169
+ t = Task(
170
+ description=f"""
171
  MANDATORY INSTRUCTION: The consensus you generate MUST be based around the MAJORITY DECISION: {maj}
172
 
173
  Analyze the following {party} party opinions to answer the question: '{self.question}'
 
182
 
183
  Return only the final consensus text that supports {maj}.
184
  """.strip(),
185
+ agent=self.base_agent,
186
+ expected_output=f"[BASE][PARTY={party}][MAJ={maj}]"
187
+ )
188
+ tasks.append(t)
 
 
 
189
  return tasks
190
 
191
  def create_supervisor_tasks(self, base_results: Dict, majorities_decision: Dict):
 
 
 
192
  tasks = []
193
+ # Idiomas
194
+ for lang in ["German", "French", "Italian"]:
195
+ key = ('LANGUAGE', lang)
196
+ if key in base_results:
197
+ maj = majorities_decision.get(lang, {}).get('decision', 'AMBIGUOUS')
198
+ t = Task(
199
+ description=f"""
 
200
  MANDATORY INSTRUCTION: Your refined consensus MUST be based around the MAJORITY DECISION: {maj}
201
 
202
  Refine and consolidate the democratic consensus for {lang} language regarding: '{self.question}'
 
210
 
211
  Return only the refined consensus in English supporting {maj}.
212
  """.strip(),
213
+ agent=self.supervisor_agent,
214
+ expected_output=f"[SUPERVISOR][LANG={lang}][MAJ={maj}]"
215
+ )
216
+ tasks.append(t)
217
+
218
+ # Orientaciones (unifica partidos)
219
+ orientation_consensuses = {"LEFT": [], "CENTER": [], "RIGHT": []}
220
+ for (kind, name), consensus in base_results.items():
221
+ if kind == 'PARTY' and name in PARTY_TO_ORIENTATION:
222
+ orientation_consensuses[PARTY_TO_ORIENTATION[name]].append(consensus)
223
+
224
+ for orientation in ["LEFT", "CENTER", "RIGHT"]:
225
+ if orientation_consensuses[orientation]:
226
+ combined = "\n\n".join([f"Consensus {i+1}: {c}" for i, c in enumerate(orientation_consensuses[orientation], 1)])
227
+ maj = majorities_decision.get(f"{orientation}_MAJORITY", {}).get('decision', 'AMBIGUOUS')
228
+ t = Task(
229
+ description=f"""
230
  MANDATORY INSTRUCTION: Your unified consensus MUST be based around the MAJORITY DECISION: {maj}
231
 
232
  Create a unified democratic consensus for {orientation} orientation regarding: '{self.question}'
 
240
 
241
  Return only the meta-consensus in English supporting {maj}.
242
  """.strip(),
243
+ agent=self.supervisor_agent,
244
+ expected_output=f"[SUPERVISOR][ORIENT={orientation}][MAJ={maj}]"
245
+ )
246
+ tasks.append(t)
 
 
 
247
  return tasks
248
 
249
  def create_director_task(self, supervisor_results: Dict, majorities_decision: Dict):
250
+ all_consensuses = "\n\n".join([f"{k}: {v}" for k, v in supervisor_results.items()])
251
+ final_majority = majorities_decision.get('FINAL_MAJORITY', {}).get('decision', 'AMBIGUOUS')
252
+ t = Task(
253
+ description=f"""
 
 
 
 
254
  SUPREME MANDATORY INSTRUCTION: Your FINAL consensus MUST be based around the ABSOLUTE MAJORITY DECISION: {final_majority}
255
 
256
  As Digital President, synthesize the FINAL DEMOCRATIC CONSENSUS for the question: '{self.question}'
 
265
 
266
  Return only the final consensus supporting {final_majority}.
267
  """.strip(),
268
+ agent=self.director_agent,
269
+ expected_output=f"[DIRECTOR][MAJ={final_majority}]"
270
+ )
271
+ return t
 
 
 
272
 
273
  def run_democratic_consensus_system(self, large_strings_by_language: Dict, large_strings_by_party: Dict, majorities_decision: Dict = {}) -> Dict:
274
+ # Nivel 1: Base
275
+ base_tasks = self.create_base_tasks(large_strings_by_language, large_strings_by_party, majorities_decision)
276
+ base_results = {}
277
+ if base_tasks:
278
+ base_crew = Crew(agents=[self.base_agent], tasks=base_tasks, verbose=0, share_crew=False)
279
+ base_out = _silent_kickoff(base_crew)
280
+ for i, task in enumerate(base_tasks):
281
+ raw = base_out.tasks_output[i].raw if i < len(base_out.tasks_output) else ""
282
+ tag = task.expected_output
283
+ if "[LANG=" in tag:
284
+ lang = tag.split("[LANG=")[1].split("]")[0]
285
+ base_results[('LANGUAGE', lang)] = raw
286
+ elif "[PARTY=" in tag:
287
+ party = tag.split("[PARTY=")[1].split("]")[0]
288
+ base_results[('PARTY', party)] = raw
289
+ self.results["base_agents"] = base_results
290
+
291
+ # Nivel 2: Supervisor
292
+ supervisor_tasks = self.create_supervisor_tasks(base_results, majorities_decision)
293
+ supervisor_results = {}
294
+ if supervisor_tasks:
295
+ sup_crew = Crew(agents=[self.supervisor_agent], tasks=supervisor_tasks, verbose=0, share_crew=False)
296
+ sup_out = _silent_kickoff(sup_crew)
297
+ for i, task in enumerate(supervisor_tasks):
298
+ raw = sup_out.tasks_output[i].raw if i < len(sup_out.tasks_output) else ""
299
+ tag = task.expected_output
300
+ if "[LANG=" in tag:
301
+ lang = tag.split("[LANG=")[1].split("]")[0]
302
+ supervisor_results[('LANGUAGE', lang)] = raw
303
+ elif "[ORIENT=" in tag:
304
+ orient = tag.split("[ORIENT=")[1].split("]")[0]
305
+ supervisor_results[('ORIENTATION', orient)] = raw
306
+ self.results["supervisors"] = supervisor_results
307
+
308
+ # Nivel 3: Director
309
+ director_task = self.create_director_task(supervisor_results, majorities_decision)
310
+ dir_crew = Crew(agents=[self.director_agent], tasks=[director_task], verbose=0, share_crew=False)
311
+ dir_out = _silent_kickoff(dir_crew)
312
+ final_consensus = dir_out.tasks_output[0].raw if dir_out.tasks_output else "No final consensus generated"
313
+ self.results["director"] = final_consensus
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
  # Limpieza
316
  gc.collect()
317
  return self.results
318
 
319
+ # Wrapper
320
  def run_democratic_consensus_system(large_strings_by_language: Dict, large_strings_by_party: Dict, question: str = "", majorities_decision: Dict = {}) -> Dict:
321
  system = DemocraticConsensusCrewAI(question=question)
322
  return system.run_democratic_consensus_system(large_strings_by_language, large_strings_by_party, majorities_decision)
 
326
  # =========================
327
 
328
  def process_excel_and_generate_consensus(excel_file_path):
329
+ """
330
+ Lee el Excel subido, calcula mayorías, corre el sistema de consenso y
331
+ devuelve los textos + porcentajes para mostrarlos en la UI.
332
+ Se espera que el Excel tenga: ['language', 'party_reply', 'label', 'comment'].
333
+ """
334
  if not excel_file_path:
335
  return ("Error: No file uploaded", "", "", "", "", "", "", "", "",
336
  "", "", "", "", "", "", "")
 
346
  "", "", "", "", "", "", "")
347
 
348
  # Extraer pregunta si existe
349
+ if 'question_y' in df.columns and df['question_y'].dropna().size:
 
350
  question = str(df['question_y'].dropna().iloc[0])
351
+ elif 'question' in df.columns and df['question'].dropna().size:
352
  question = str(df['question'].dropna().iloc[0])
353
+ else:
354
+ question = "Question not found in Excel"
355
 
356
  # Strings por idioma y partido
357
  large_strings_by_language = (
358
  df.groupby("language")["comment"]
359
+ .apply(lambda g: "'; ".join([f"Opinion {i+1}: '{c}" for i, c in enumerate(g.dropna().astype(str))]))
360
  .to_dict()
361
  )
362
  large_strings_by_party = (
363
  df.groupby("party_reply")["comment"]
364
+ .apply(lambda g: "'; ".join([f"Opinion {i+1}: '{c}" for i, c in enumerate(g.dropna().astype(str))]))
365
  .to_dict()
366
  )
367
 
 
403
  left_percentage, center_percentage, right_percentage, final_percentage)
404
 
405
  except Exception as e:
 
406
  return (f"Error processing file: {e}", "", "", "", "", "", "", "", "",
407
  "", "", "", "", "", "", "")
408
 
 
411
  # =========================
412
 
413
  def create_consensus_app():
414
+ with gr.Blocks(
415
+ title="🏛️ Consensus Statements",
416
+ theme=gr.themes.Base(),
417
+ css="""
418
+ /* Estilos de banderas */
419
  .flag-container { display: flex; justify-content: center; align-items: center; gap: 15px; margin-bottom: 10px; }
420
  .flag-image { width: 50px; height: auto; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
421
+
422
+ /* Estilos de las cajas de consenso */
423
  .consensus-box { padding: 15px; border-radius: 15px; margin: 10px; min-height: 300px; max-height: 500px;
424
  overflow-y: auto; word-wrap: break-word; display: flex; align-items: flex-start; justify-content: center;
425
  text-align: center; font-family: 'Georgia', 'Times New Roman', serif; font-weight: normal; }
426
  .consensus-box-content { font-size: 1.0em; line-height: 1.4; padding: 5px; width: 100%; }
427
+
428
  .consensus-box-german { background: linear-gradient(135deg, #FFB6C1, #FFC0CB); color: #333333; }
429
  .consensus-box-french { background: linear-gradient(135deg, #E6E6FA, #DDA0DD); color: #333333; }
430
  .consensus-box-italian { background: linear-gradient(135deg, #F0E68C, #F5DEB3); color: #333333; }
431
  .consensus-box-left { background: linear-gradient(135deg, #98FB98, #90EE90); color: #333333; }
432
  .consensus-box-center { background: linear-gradient(135deg, #87CEEB, #B0E0E6); color: #333333; }
433
  .consensus-box-right { background: linear-gradient(135deg, #FFDAB9, #FFE4B5); color: #333333; }
434
+
435
+ /* Estilo para la caja del consenso final */
436
  .final-consensus-box { background: linear-gradient(135deg, #A5D6A7, #66BB6A); color: #333333; padding: 30px; border-radius: 15px;
437
  margin: 20px 0; box-shadow: 0 6px 10px rgba(0,0,0,0.15); min-height: 150px; max-height: 400px; overflow-y: auto;
438
  display: flex; align-items: flex-start; justify-content: center; text-align: center; font-family: 'Georgia', 'Times New Roman', serif;
439
  font-weight: normal; font-size: 1.1em; }
440
+
441
  .question-header { background: linear-gradient(135deg, #1e3a5f, #2d5a87); color: white; padding: 15px; border-radius: 10px;
442
  text-align: center; margin-bottom: 20px; font-family: 'Georgia', 'Times New Roman', serif; font-weight: normal; }
443
+
444
  .upload-section { text-align: center; margin: 20px 0; }
445
  .gradio-container { font-family: 'Georgia', 'Times New Roman', serif; }
446
+
447
+ /* Botón personalizado */
448
  .custom-button-color { background-color: #2e8b57 !important; color: #ffffff !important; border-color: #2e8b57 !important; }
449
  """
450
+ ) as demo:
451
+ # Título y banderas
452
+ gr.HTML("""
453
+ <div class="flag-container">
454
+ <img src="https://upload.wikimedia.org/wikipedia/commons/b/ba/Flag_of_Germany.svg" class="flag-image" alt="German Flag">
455
+ <img src="https://upload.wikimedia.org/wikipedia/commons/c/c3/Flag_of_France.svg" class="flag-image" alt="French Flag">
456
+ <img src="https://upload.wikimedia.org/wikipedia/commons/0/03/Flag_of_Italy.svg" class="flag-image" alt="Italian Flag">
457
+ </div>
458
+ <h1 style='text-align: center; font-size: 1.8em; margin-bottom: 15px;'>Consensus Statements</h1>
459
+ """)
460
+
461
+ # Carga
462
+ with gr.Row(elem_classes=["upload-section"]):
463
+ with gr.Column():
464
+ excel_input = gr.File(label="📊 UPLOAD EXCEL HERE!", file_types=[".xlsx", ".xls"], type="filepath")
465
+ process_btn = gr.Button("🚀 Generate Consensus", elem_classes=["custom-button-color"], size="lg")
466
+
467
+ status_output = gr.Textbox(label="Status", interactive=False, value="Ready to process Excel file...")
468
+ question_display = gr.HTML(value='<div class="question-header">Upload an Excel file to see the question here</div>',
469
+ elem_classes=["question-header"])
470
+
471
+ # Estado
472
+ all_data = gr.State()
473
+
474
+ # Cajas
475
+ with gr.Row():
476
+ with gr.Column():
477
+ german_output = gr.HTML(
478
+ value='<div class="consensus-box-content">Consensus Statement:<br>German-speaking cantons<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
479
+ elem_classes=["consensus-box", "consensus-box-german"]
480
+ )
481
+ with gr.Column():
482
+ french_output = gr.HTML(
483
+ value='<div class="consensus-box-content">Consensus Statement:<br>French-speaking cantons<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
484
+ elem_classes=["consensus-box", "consensus-box-french"]
485
+ )
486
+ with gr.Column():
487
+ italian_output = gr.HTML(
488
+ value='<div class="consensus-box-content">Consensus Statement:<br>Italian-speaking cantons<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
489
+ elem_classes=["consensus-box", "consensus-box-italian"]
490
+ )
491
 
492
+ with gr.Row():
493
+ with gr.Column():
494
+ left_output = gr.HTML(
495
+ value='<div class="consensus-box-content">Consensus Statement:<br>LEFT<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
496
+ elem_classes=["consensus-box", "consensus-box-left"]
497
+ )
498
+ with gr.Column():
499
+ center_output = gr.HTML(
500
+ value='<div class="consensus-box-content">Consensus Statement:<br>CENTER<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
501
+ elem_classes=["consensus-box", "consensus-box-center"]
502
+ )
503
+ with gr.Column():
504
+ right_output = gr.HTML(
505
+ value='<div class="consensus-box-content">Consensus Statement:<br>RIGHT<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
506
+ elem_classes=["consensus-box", "consensus-box-right"]
507
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
 
509
+ final_output = gr.HTML(
510
+ value='<div class="consensus-box-content">FINAL - CONSENSUS STATEMENT:<br><br>Upload Excel file to see results<br><b>SUPPORTING THIS DECISION: --</b></div>',
511
+ elem_classes=["final-consensus-box"]
512
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
 
514
+ # Flujo
515
+ def process_and_store_data(excel_file_path):
516
+ data = process_excel_and_generate_consensus(excel_file_path)
517
+ return data, data
518
+
519
+ def update_display(data):
520
+ if not data:
521
+ return ("Error processing file", "", "", "", "", "", "", "", "")
522
+ (status, question, german, french, italian, left, center, right, final,
523
+ german_percentage, french_percentage, italian_percentage,
524
+ left_percentage, center_percentage, right_percentage, final_percentage) = data
525
+
526
+ question_html = f'<div class="question-header">Question: {question}</div>'
527
+ german_html = f'<div class="consensus-box-content">Consensus Statement:<br>German-speaking cantons<br><br>{german}<br><b>SUPPORTING THIS DECISION: {german_percentage}</b></div>'
528
+ french_html = f'<div class="consensus-box-content">Consensus Statement:<br>French-speaking cantons<br><br>{french}<br><b>SUPPORTING THIS DECISION: {french_percentage}</b></div>'
529
+ italian_html = f'<div class="consensus-box-content">Consensus Statement:<br>Italian-speaking cantons<br><br>{italian}<br><b>SUPPORTING THIS DECISION: {italian_percentage}</b></div>'
530
+ left_html = f'<div class="consensus-box-content">Consensus Statement:<br>LEFT<br><br>{left}<br><b>SUPPORTING THIS DECISION: {left_percentage}</b></div>'
531
+ center_html = f'<div class="consensus-box-content">Consensus Statement:<br>CENTER<br><br>{center}<br><b>SUPPORTING THIS DECISION: {center_percentage}</b></div>'
532
+ right_html = f'<div class="consensus-box-content">Consensus Statement:<br>RIGHT<br><br>{right}<br><b>SUPPORTING THIS DECISION: {right_percentage}</b></div>'
533
+ final_html = f'<div class="consensus-box-content">FINAL - CONSENSUS STATEMENT:<br><br>{final}<br><b>SUPPORTING THIS DECISION: {final_percentage}</b></div>'
534
+
535
+ return (status, question_html, german_html, french_html, italian_html,
536
+ left_html, center_html, right_html, final_html)
537
+
538
+ # Encadenamiento
539
+ process_btn.click(
540
+ fn=process_and_store_data,
541
+ inputs=[excel_input],
542
+ outputs=[all_data, all_data]
543
+ ).then(
544
+ fn=update_display,
545
+ inputs=[all_data],
546
+ outputs=[status_output, question_display, german_output, french_output, italian_output,
547
+ left_output, center_output, right_output, final_output]
548
  )
549
 
550
+ return demo
551
+
552
  # =========================
553
+ # Main - Compatible con local y HuggingFace Spaces
554
  # =========================
555
  if __name__ == "__main__":
556
+ app = create_consensus_app()
557
+
558
+ # Detectar si estamos en HuggingFace Spaces o local
559
+ is_spaces = os.getenv("SPACE_ID") is not None
560
+
561
+ if is_spaces:
562
+ # Configuración para HuggingFace Spaces
563
  app.launch(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  share=False,
565
+ server_name="0.0.0.0",
566
  server_port=7860,
567
  debug=False,
568
  inbrowser=False
569
+ )
570
+ else:
571
+ # Configuración para desarrollo local
572
+ app.launch(
573
+ share=True,
574
+ server_name="127.0.0.1", # Solo acceso local
575
+ server_port=None, # Gradio encontrará un puerto libre automáticamente
576
+ debug=True,
577
+ inbrowser=True
578
  )
requirements.txt CHANGED
@@ -1,5 +1,6 @@
1
- gradio==4.44.0
2
- pandas>=2.0.0
3
- openpyxl>=3.1.0
4
- crewai>=0.28.0,<1.0.0
5
- openai>=1.0.0,<2.0.0
 
 
1
+ crewai==0.201.1
2
+ gradio==5.47.2
3
+ gradio_client==1.13.3
4
+ openai==1.109.1
5
+ openpyxl==3.1.5
6
+ pandas==2.3.2