Markus Clauss DIRU Vetsuisse commited on
Commit
35a980a
·
1 Parent(s): 78a48c2

Fix roundtable format to match Consilium component structure

Browse files

- Adapted to Consilium's exact format: participants as array of names
- avatarImages as separate dictionary mapping names to URLs
- Messages with speaker/text structure
- Added proper thinking/currentSpeaker/showBubbles arrays
- Implemented Timer for auto-refresh during discussion
- Fixed session management and state updates
- Added generator pattern for streaming updates

Based on working Consilium app.py structure

🤖 Generated with Claude Code
Co-Authored-By: Claude <[email protected]>

Files changed (3) hide show
  1. app.py +163 -131
  2. app_simple.py +242 -0
  3. test_roundtable.py +90 -0
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  🇨🇭 Apertus Dialekt-Konsil mit Roundtable
3
  Ein AI-Konsil mit Schweizerdeutsch, Bayrisch und Schwäbisch Sprechern
4
- Mit visuellem Roundtable und Moderator
5
  """
6
 
7
  import gradio as gr
@@ -19,42 +19,49 @@ HF_TOKEN = os.environ.get('HF_TOKEN', '')
19
  # Initialize Apertus client with publicai provider
20
  client = None
21
  if HF_TOKEN:
22
- # Using Apertus model via publicai provider
23
  client = InferenceClient(
24
  provider="publicai",
25
  api_key=HF_TOKEN
26
  )
27
 
 
 
 
 
 
 
 
 
28
  # Dialekt-Persönlichkeiten
29
  COUNCIL_MEMBERS = {
30
- "Ueli (Baseldytsch)": {
 
31
  "emoji": "🇨🇭",
32
  "dialect": "Baseldytsch",
 
33
  "personality": "kultiviert, humorvoll, fasnachts-begeistert",
34
- "speaking_style": "Verwendet typische Basler Ausdrücke wie 'Sali zämme', 'gäll', 'Bebbi', 'Fasnacht', 'Läckerli'",
35
- "avatar": "🇨🇭",
36
  "system_prompt": """Du bist Ueli aus Basel und sprichst Baseldytsch.
37
  Du bist kultiviert, humorvoll und liebst die Basler Fasnacht.
38
  Verwende typische Basler Ausdrücke und Redewendungen.
39
  Beispiele: 'Sali zämme', 'Das isch e gueti Sach, gäll', 'Mir Baasler wisse was guet isch', 'Das brucht e bitzeli Köpfli'"""
40
  },
41
- "Sepp (Bayrisch)": {
 
42
  "emoji": "🥨",
43
  "dialect": "Bayrisch",
 
44
  "personality": "gesellig, direkt, traditionsbewusst",
45
- "speaking_style": "Verwendet bayrische Ausdrücke wie 'Servus', 'mei', 'a bisserl', 'g'scheid', 'Gaudi'",
46
- "avatar": "🥨",
47
  "system_prompt": """Du bist Sepp aus München und sprichst Bayrisch.
48
  Du bist gesellig, direkt und traditionsbewusst.
49
  Verwende typische bayrische Ausdrücke und Redewendungen.
50
  Beispiele: 'Servus beinand', 'Des is fei a guade Idee', 'Mir miassn schaun dass ma weiterkemma', 'A bisserl was geht immer'"""
51
  },
52
- "Karl (Schwäbisch)": {
 
53
  "emoji": "🏰",
54
  "dialect": "Schwäbisch",
 
55
  "personality": "sparsam, fleißig, erfinderisch",
56
- "speaking_style": "Verwendet schwäbische Ausdrücke wie 'Griaß Godd', 'hald', 'gell', 'schaffe', 'Spätzle'",
57
- "avatar": "🏰",
58
  "system_prompt": """Du bist Karl aus Stuttgart und sprichst Schwäbisch.
59
  Du bist sparsam, fleißig und erfinderisch.
60
  Verwende typische schwäbische Ausdrücke und Redewendungen.
@@ -62,11 +69,11 @@ Beispiele: 'Griaß Godd mitanand', 'Des isch hald so a Sach', 'Mir müsset schaf
62
  }
63
  }
64
 
65
- # Moderator Agent
66
  MODERATOR = {
67
- "name": "Dr. Müller (Moderator)",
 
68
  "emoji": "🎓",
69
- "avatar": "🎓",
70
  "system_prompt": """Du bist Dr. Müller, ein neutraler Moderator für das Dialekt-Konsil.
71
  Deine Aufgaben:
72
  1. Fasse jede Diskussionsrunde zusammen
@@ -76,6 +83,9 @@ Deine Aufgaben:
76
  Sei neutral, professionell und präzise in deinen Bewertungen."""
77
  }
78
 
 
 
 
79
  class DialektKonsil:
80
  def __init__(self, session_id=None):
81
  self.session_id = session_id or str(uuid.uuid4())
@@ -83,14 +93,18 @@ class DialektKonsil:
83
  self.current_topic = ""
84
  self.consensus_score = 0
85
  self.round_number = 0
86
- self.members_state = {
87
- name: {"status": "waiting", "message": "", "thinking": False}
88
- for name in COUNCIL_MEMBERS.keys()
 
 
 
 
 
89
  }
90
 
91
  def generate_response(self, member_name, topic, previous_responses):
92
  """Generate response for a council member"""
93
-
94
  member = COUNCIL_MEMBERS[member_name]
95
 
96
  # Build context from previous responses
@@ -100,7 +114,6 @@ class DialektKonsil:
100
  for resp in previous_responses:
101
  context += f"{resp['speaker']}: {resp['text']}\n"
102
 
103
- # Create prompt with dialect personality
104
  prompt = f"""{member['system_prompt']}
105
 
106
  {context}
@@ -126,11 +139,10 @@ Deine Antwort:"""
126
  top_p=0.9
127
  )
128
 
129
- # Extract response
130
  response = completion.choices[0].message.content.strip()
131
 
132
  # Clean up response
133
- if ":" in response[:50]: # Remove name prefix if model adds it
134
  response = response.split(":", 1)[1].strip()
135
 
136
  return response
@@ -141,7 +153,6 @@ Deine Antwort:"""
141
  def generate_moderator_summary(self, round_responses):
142
  """Generate moderator summary and consensus score"""
143
 
144
- # Build context from round responses
145
  context = "Diskussionsrunde abgeschlossen. Folgende Beiträge:\n"
146
  for resp in round_responses:
147
  context += f"{resp['speaker']}: {resp['text']}\n"
@@ -181,7 +192,7 @@ UNTERSCHIEDE: [Wo Uneinigkeit herrscht]"""
181
  try:
182
  score_line = [l for l in response.split('\n') if 'KONSENSGRAD:' in l][0]
183
  consensus_score = int(''.join(filter(str.isdigit, score_line)))
184
- consensus_score = max(1, min(10, consensus_score)) # Clamp between 1-10
185
  except:
186
  pass
187
 
@@ -190,24 +201,31 @@ UNTERSCHIEDE: [Wo Uneinigkeit herrscht]"""
190
  except Exception as e:
191
  return f"Technische Zusammenfassung: Die Diskussion läuft. ({str(e)})", 5
192
 
193
- def run_discussion_round(self, topic):
194
- """Run one round of discussion"""
 
 
 
 
 
195
 
196
  self.round_number += 1
197
  responses = []
198
 
 
 
 
199
  # Each member speaks once per round
200
  for member_name in COUNCIL_MEMBERS.keys():
201
- # Update state to thinking
202
- self.members_state[member_name] = {
203
- "status": "thinking",
204
- "message": "",
205
- "thinking": True
206
- }
207
- yield self.get_current_state()
208
 
209
  # Generate response
210
- response = self.generate_response(member_name, topic, responses)
211
 
212
  responses.append({
213
  "speaker": member_name,
@@ -217,31 +235,45 @@ UNTERSCHIEDE: [Wo Uneinigkeit herrscht]"""
217
  "round": self.round_number
218
  })
219
 
220
- # Update state with message
221
- self.members_state[member_name] = {
222
- "status": "speaking",
223
- "message": response,
224
- "thinking": False
225
- }
 
 
 
 
 
 
 
226
 
227
  # Add to conversation history
228
  self.conversation_history.append(responses[-1])
229
 
230
- yield self.get_current_state()
231
 
232
- # Small delay for realism
233
- time.sleep(0.5)
 
 
234
 
235
- # Set to waiting after speaking
236
- self.members_state[member_name] = {
237
- "status": "waiting",
238
- "message": response,
239
- "thinking": False
240
- }
241
 
242
- # Generate moderator summary
243
  moderator_summary, self.consensus_score = self.generate_moderator_summary(responses)
244
 
 
 
 
 
 
 
 
 
 
 
 
245
  self.conversation_history.append({
246
  "speaker": MODERATOR["name"],
247
  "text": moderator_summary,
@@ -251,48 +283,7 @@ UNTERSCHIEDE: [Wo Uneinigkeit herrscht]"""
251
  "is_moderator": True
252
  })
253
 
254
- yield self.get_current_state()
255
-
256
- def get_current_state(self):
257
- """Get current state for roundtable visualization"""
258
-
259
- # Prepare members data for roundtable
260
- participants = []
261
- for name, member in COUNCIL_MEMBERS.items():
262
- state = self.members_state[name]
263
- participants.append({
264
- "name": name.split(" ")[0], # Just first name
265
- "avatar": member["avatar"],
266
- "message": state.get("message", ""),
267
- "is_thinking": state.get("thinking", False),
268
- "is_current_speaker": state.get("status") == "speaking"
269
- })
270
-
271
- # Add moderator
272
- moderator_message = ""
273
- if self.conversation_history:
274
- last_moderator = [h for h in self.conversation_history if h.get("is_moderator")]
275
- if last_moderator:
276
- moderator_message = last_moderator[-1]["text"]
277
-
278
- participants.append({
279
- "name": "Moderator",
280
- "avatar": MODERATOR["avatar"],
281
- "message": moderator_message,
282
- "is_thinking": False,
283
- "is_current_speaker": False
284
- })
285
-
286
- # Create state in format expected by consilium_roundtable
287
- state_dict = {
288
- "participants": participants,
289
- "current_speaker": next((p["name"] for p in participants if p["is_current_speaker"]), None),
290
- "round": self.round_number,
291
- "topic": self.current_topic,
292
- "consensus_score": self.consensus_score
293
- }
294
-
295
- return json.dumps(state_dict)
296
 
297
  def format_history(self):
298
  """Format conversation history for display"""
@@ -313,14 +304,12 @@ UNTERSCHIEDE: [Wo Uneinigkeit herrscht]"""
313
  output += f"```\n{entry['text']}\n```\n"
314
  output += f"**Konsensgrad: {self.consensus_score}/10**\n\n"
315
  else:
316
- output += f"**{entry['emoji']} {entry['speaker']}** ({entry['timestamp']}):\n"
 
317
  output += f"> {entry['text']}\n\n"
318
 
319
  return output
320
 
321
- # Session management
322
- sessions = {}
323
-
324
  def get_or_create_session(session_id=None):
325
  """Get existing session or create new one"""
326
  if not session_id:
@@ -335,10 +324,26 @@ def start_new_discussion(topic):
335
  """Start a new discussion with a fresh session"""
336
 
337
  if not topic:
338
- return None, None, "*Keine Diskussion gestartet*", "❌ Bitte geben Sie ein Thema ein!"
 
 
 
 
 
 
 
 
339
 
340
  if not client:
341
- return None, None, "*Keine Diskussion gestartet*", "❌ HF_TOKEN nicht konfiguriert. Bitte in Space Settings setzen."
 
 
 
 
 
 
 
 
342
 
343
  # Create new session
344
  konsil, session_id = get_or_create_session()
@@ -347,41 +352,49 @@ def start_new_discussion(topic):
347
  konsil.round_number = 0
348
  konsil.consensus_score = 0
349
 
350
- # Initialize all members to waiting
351
- for name in COUNCIL_MEMBERS.keys():
352
- konsil.members_state[name] = {
353
- "status": "waiting",
354
- "message": "",
355
- "thinking": False
356
- }
357
-
358
- initial_state = konsil.get_current_state()
359
 
360
  return (
361
- initial_state,
362
  session_id,
363
  konsil.format_history(),
364
  f"✅ Diskussion gestartet: {topic}"
365
  )
366
 
367
  def run_next_round(session_id):
368
- """Run the next discussion round"""
369
 
370
  if not session_id or session_id not in sessions:
371
- return None, "*Keine Diskussion gestartet*", "❌ Keine aktive Session. Bitte starten Sie eine neue Diskussion."
 
 
 
 
 
 
 
 
372
 
373
  konsil = sessions[session_id]
374
 
375
  if not konsil.current_topic:
376
- return None, "*Keine Diskussion gestartet*", "❌ Kein Thema gesetzt. Bitte starten Sie eine neue Diskussion."
377
 
378
  if konsil.consensus_score >= 8:
379
- return konsil.get_current_state(), konsil.format_history(), f"✅ Konsens bereits erreicht (Score: {konsil.consensus_score}/10)"
380
 
381
- # Run discussion round with yield for updates
382
- state = None
383
- for state in konsil.run_discussion_round(konsil.current_topic):
384
- pass # Just get the final state
385
 
386
  history = konsil.format_history()
387
 
@@ -392,7 +405,7 @@ def run_next_round(session_id):
392
  else:
393
  status = f"Runde {konsil.round_number} abgeschlossen. Konsensgrad: {konsil.consensus_score}/10"
394
 
395
- return state, history, status
396
 
397
  def create_interface():
398
  """Create Gradio interface with roundtable visualization"""
@@ -449,18 +462,14 @@ def create_interface():
449
  # Roundtable visualization
450
  with gr.Row():
451
  with gr.Column(scale=1, elem_classes="roundtable-container"):
452
- # Create initial empty state
453
  initial_roundtable_state = json.dumps({
454
- "participants": [
455
- {"name": "Ueli", "avatar": "🇨🇭", "message": "", "is_thinking": False, "is_current_speaker": False},
456
- {"name": "Sepp", "avatar": "🥨", "message": "", "is_thinking": False, "is_current_speaker": False},
457
- {"name": "Karl", "avatar": "🏰", "message": "", "is_thinking": False, "is_current_speaker": False},
458
- {"name": "Moderator", "avatar": "🎓", "message": "", "is_thinking": False, "is_current_speaker": False}
459
- ],
460
- "current_speaker": None,
461
- "round": 0,
462
- "topic": "",
463
- "consensus_score": 0
464
  })
465
 
466
  roundtable = consilium_roundtable(
@@ -514,12 +523,18 @@ def create_interface():
514
  if roundtable_state:
515
  try:
516
  state = json.loads(roundtable_state) if isinstance(roundtable_state, str) else roundtable_state
517
- if "consensus_score" in state:
518
- score = state["consensus_score"]
519
- return f"**Konsensgrad:** {score}/10 {'🟢' if score >= 8 else '🟡' if score >= 5 else '🔴'}"
 
 
 
 
 
 
520
  except:
521
  pass
522
- return "**Konsensgrad:** Noch keine Bewertung"
523
 
524
  roundtable.change(
525
  update_consensus,
@@ -527,6 +542,22 @@ def create_interface():
527
  outputs=[consensus_display]
528
  )
529
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  gr.Markdown("""
531
  ---
532
  ### 📌 Hinweise
@@ -542,6 +573,7 @@ def create_interface():
542
  - **Authentische Dialekte**: Schweizerdeutsch, Bayrisch, Schwäbisch
543
  - **Neutraler Moderator**: Fasst zusammen und bewertet Konsens
544
  - **Interaktive Runden**: Verfolgen Sie jede Diskussionsrunde einzeln
 
545
 
546
  *Powered by Apertus Swiss AI* 🇨🇭
547
  """)
 
1
  """
2
  🇨🇭 Apertus Dialekt-Konsil mit Roundtable
3
  Ein AI-Konsil mit Schweizerdeutsch, Bayrisch und Schwäbisch Sprechern
4
+ Mit visuellem Roundtable und Moderator - Angepasst an Consilium Format
5
  """
6
 
7
  import gradio as gr
 
19
  # Initialize Apertus client with publicai provider
20
  client = None
21
  if HF_TOKEN:
 
22
  client = InferenceClient(
23
  provider="publicai",
24
  api_key=HF_TOKEN
25
  )
26
 
27
+ # Avatar URLs für die Dialekt-Sprecher
28
+ avatar_images = {
29
+ "Ueli": "https://api.dicebear.com/7.x/personas/svg?seed=Ueli&backgroundColor=b6e3f4",
30
+ "Sepp": "https://api.dicebear.com/7.x/personas/svg?seed=Sepp&backgroundColor=ffd5dc",
31
+ "Karl": "https://api.dicebear.com/7.x/personas/svg?seed=Karl&backgroundColor=c1ffc1",
32
+ "Dr. Müller": "https://api.dicebear.com/7.x/personas/svg?seed=Moderator&backgroundColor=e0e0e0"
33
+ }
34
+
35
  # Dialekt-Persönlichkeiten
36
  COUNCIL_MEMBERS = {
37
+ "Ueli": {
38
+ "full_name": "Ueli (Baseldytsch)",
39
  "emoji": "🇨🇭",
40
  "dialect": "Baseldytsch",
41
+ "location": "Basel",
42
  "personality": "kultiviert, humorvoll, fasnachts-begeistert",
 
 
43
  "system_prompt": """Du bist Ueli aus Basel und sprichst Baseldytsch.
44
  Du bist kultiviert, humorvoll und liebst die Basler Fasnacht.
45
  Verwende typische Basler Ausdrücke und Redewendungen.
46
  Beispiele: 'Sali zämme', 'Das isch e gueti Sach, gäll', 'Mir Baasler wisse was guet isch', 'Das brucht e bitzeli Köpfli'"""
47
  },
48
+ "Sepp": {
49
+ "full_name": "Sepp (Bayrisch)",
50
  "emoji": "🥨",
51
  "dialect": "Bayrisch",
52
+ "location": "München",
53
  "personality": "gesellig, direkt, traditionsbewusst",
 
 
54
  "system_prompt": """Du bist Sepp aus München und sprichst Bayrisch.
55
  Du bist gesellig, direkt und traditionsbewusst.
56
  Verwende typische bayrische Ausdrücke und Redewendungen.
57
  Beispiele: 'Servus beinand', 'Des is fei a guade Idee', 'Mir miassn schaun dass ma weiterkemma', 'A bisserl was geht immer'"""
58
  },
59
+ "Karl": {
60
+ "full_name": "Karl (Schwäbisch)",
61
  "emoji": "🏰",
62
  "dialect": "Schwäbisch",
63
+ "location": "Stuttgart",
64
  "personality": "sparsam, fleißig, erfinderisch",
 
 
65
  "system_prompt": """Du bist Karl aus Stuttgart und sprichst Schwäbisch.
66
  Du bist sparsam, fleißig und erfinderisch.
67
  Verwende typische schwäbische Ausdrücke und Redewendungen.
 
69
  }
70
  }
71
 
72
+ # Moderator
73
  MODERATOR = {
74
+ "name": "Dr. Müller",
75
+ "full_name": "Dr. Müller (Moderator)",
76
  "emoji": "🎓",
 
77
  "system_prompt": """Du bist Dr. Müller, ein neutraler Moderator für das Dialekt-Konsil.
78
  Deine Aufgaben:
79
  1. Fasse jede Diskussionsrunde zusammen
 
83
  Sei neutral, professionell und präzise in deinen Bewertungen."""
84
  }
85
 
86
+ # Session storage für isolierte Diskussionen
87
+ sessions = {}
88
+
89
  class DialektKonsil:
90
  def __init__(self, session_id=None):
91
  self.session_id = session_id or str(uuid.uuid4())
 
93
  self.current_topic = ""
94
  self.consensus_score = 0
95
  self.round_number = 0
96
+ # Roundtable state im Consilium Format
97
+ self.roundtable_state = {
98
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
99
+ "messages": [],
100
+ "currentSpeaker": None,
101
+ "thinking": [],
102
+ "showBubbles": [],
103
+ "avatarImages": avatar_images
104
  }
105
 
106
  def generate_response(self, member_name, topic, previous_responses):
107
  """Generate response for a council member"""
 
108
  member = COUNCIL_MEMBERS[member_name]
109
 
110
  # Build context from previous responses
 
114
  for resp in previous_responses:
115
  context += f"{resp['speaker']}: {resp['text']}\n"
116
 
 
117
  prompt = f"""{member['system_prompt']}
118
 
119
  {context}
 
139
  top_p=0.9
140
  )
141
 
 
142
  response = completion.choices[0].message.content.strip()
143
 
144
  # Clean up response
145
+ if ":" in response[:50]:
146
  response = response.split(":", 1)[1].strip()
147
 
148
  return response
 
153
  def generate_moderator_summary(self, round_responses):
154
  """Generate moderator summary and consensus score"""
155
 
 
156
  context = "Diskussionsrunde abgeschlossen. Folgende Beiträge:\n"
157
  for resp in round_responses:
158
  context += f"{resp['speaker']}: {resp['text']}\n"
 
192
  try:
193
  score_line = [l for l in response.split('\n') if 'KONSENSGRAD:' in l][0]
194
  consensus_score = int(''.join(filter(str.isdigit, score_line)))
195
+ consensus_score = max(1, min(10, consensus_score))
196
  except:
197
  pass
198
 
 
201
  except Exception as e:
202
  return f"Technische Zusammenfassung: Die Diskussion läuft. ({str(e)})", 5
203
 
204
+ def update_visual_state(self, **kwargs):
205
+ """Update the visual roundtable state"""
206
+ self.roundtable_state.update(kwargs)
207
+ return json.dumps(self.roundtable_state)
208
+
209
+ def run_discussion_round(self):
210
+ """Run one round of discussion with visual updates"""
211
 
212
  self.round_number += 1
213
  responses = []
214
 
215
+ # Reset messages for new round but keep participants
216
+ self.roundtable_state["messages"] = []
217
+
218
  # Each member speaks once per round
219
  for member_name in COUNCIL_MEMBERS.keys():
220
+ # Set thinking state
221
+ self.roundtable_state["thinking"] = [member_name]
222
+ self.roundtable_state["currentSpeaker"] = None
223
+ yield self.update_visual_state()
224
+
225
+ time.sleep(1)
 
226
 
227
  # Generate response
228
+ response = self.generate_response(member_name, self.current_topic, responses)
229
 
230
  responses.append({
231
  "speaker": member_name,
 
235
  "round": self.round_number
236
  })
237
 
238
+ # Set speaking state and add message
239
+ self.roundtable_state["thinking"] = []
240
+ self.roundtable_state["currentSpeaker"] = member_name
241
+ self.roundtable_state["messages"].append({
242
+ "speaker": member_name,
243
+ "text": f"{COUNCIL_MEMBERS[member_name]['emoji']} {response}"
244
+ })
245
+
246
+ # Show speech bubble
247
+ if member_name not in self.roundtable_state["showBubbles"]:
248
+ self.roundtable_state["showBubbles"].append(member_name)
249
+
250
+ yield self.update_visual_state()
251
 
252
  # Add to conversation history
253
  self.conversation_history.append(responses[-1])
254
 
255
+ time.sleep(2)
256
 
257
+ # Generate moderator summary
258
+ self.roundtable_state["thinking"] = ["Dr. Müller"]
259
+ self.roundtable_state["currentSpeaker"] = None
260
+ yield self.update_visual_state()
261
 
262
+ time.sleep(1)
 
 
 
 
 
263
 
 
264
  moderator_summary, self.consensus_score = self.generate_moderator_summary(responses)
265
 
266
+ # Add moderator message
267
+ self.roundtable_state["thinking"] = []
268
+ self.roundtable_state["currentSpeaker"] = "Dr. Müller"
269
+ self.roundtable_state["messages"].append({
270
+ "speaker": "Dr. Müller",
271
+ "text": f"🎓 {moderator_summary}"
272
+ })
273
+
274
+ if "Dr. Müller" not in self.roundtable_state["showBubbles"]:
275
+ self.roundtable_state["showBubbles"].append("Dr. Müller")
276
+
277
  self.conversation_history.append({
278
  "speaker": MODERATOR["name"],
279
  "text": moderator_summary,
 
283
  "is_moderator": True
284
  })
285
 
286
+ yield self.update_visual_state()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  def format_history(self):
289
  """Format conversation history for display"""
 
304
  output += f"```\n{entry['text']}\n```\n"
305
  output += f"**Konsensgrad: {self.consensus_score}/10**\n\n"
306
  else:
307
+ full_name = COUNCIL_MEMBERS[entry['speaker']]['full_name']
308
+ output += f"**{entry['emoji']} {full_name}** ({entry['timestamp']}):\n"
309
  output += f"> {entry['text']}\n\n"
310
 
311
  return output
312
 
 
 
 
313
  def get_or_create_session(session_id=None):
314
  """Get existing session or create new one"""
315
  if not session_id:
 
324
  """Start a new discussion with a fresh session"""
325
 
326
  if not topic:
327
+ initial_state = json.dumps({
328
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
329
+ "messages": [],
330
+ "currentSpeaker": None,
331
+ "thinking": [],
332
+ "showBubbles": [],
333
+ "avatarImages": avatar_images
334
+ })
335
+ return initial_state, None, "*Keine Diskussion gestartet*", "❌ Bitte geben Sie ein Thema ein!"
336
 
337
  if not client:
338
+ initial_state = json.dumps({
339
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
340
+ "messages": [],
341
+ "currentSpeaker": None,
342
+ "thinking": [],
343
+ "showBubbles": [],
344
+ "avatarImages": avatar_images
345
+ })
346
+ return initial_state, None, "*Keine Diskussion gestartet*", "❌ HF_TOKEN nicht konfiguriert. Bitte in Space Settings setzen."
347
 
348
  # Create new session
349
  konsil, session_id = get_or_create_session()
 
352
  konsil.round_number = 0
353
  konsil.consensus_score = 0
354
 
355
+ # Reset roundtable state
356
+ konsil.roundtable_state = {
357
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
358
+ "messages": [],
359
+ "currentSpeaker": None,
360
+ "thinking": [],
361
+ "showBubbles": [],
362
+ "avatarImages": avatar_images
363
+ }
364
 
365
  return (
366
+ json.dumps(konsil.roundtable_state),
367
  session_id,
368
  konsil.format_history(),
369
  f"✅ Diskussion gestartet: {topic}"
370
  )
371
 
372
  def run_next_round(session_id):
373
+ """Run the next discussion round with streaming updates"""
374
 
375
  if not session_id or session_id not in sessions:
376
+ empty_state = json.dumps({
377
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
378
+ "messages": [],
379
+ "currentSpeaker": None,
380
+ "thinking": [],
381
+ "showBubbles": [],
382
+ "avatarImages": avatar_images
383
+ })
384
+ return empty_state, "*Keine Diskussion gestartet*", "❌ Keine aktive Session. Bitte starten Sie eine neue Diskussion."
385
 
386
  konsil = sessions[session_id]
387
 
388
  if not konsil.current_topic:
389
+ return json.dumps(konsil.roundtable_state), "*Keine Diskussion gestartet*", "❌ Kein Thema gesetzt. Bitte starten Sie eine neue Diskussion."
390
 
391
  if konsil.consensus_score >= 8:
392
+ return json.dumps(konsil.roundtable_state), konsil.format_history(), f"✅ Konsens bereits erreicht (Score: {konsil.consensus_score}/10)"
393
 
394
+ # Run discussion round with generator for updates
395
+ final_state = None
396
+ for state in konsil.run_discussion_round():
397
+ final_state = state
398
 
399
  history = konsil.format_history()
400
 
 
405
  else:
406
  status = f"Runde {konsil.round_number} abgeschlossen. Konsensgrad: {konsil.consensus_score}/10"
407
 
408
+ return final_state, history, status
409
 
410
  def create_interface():
411
  """Create Gradio interface with roundtable visualization"""
 
462
  # Roundtable visualization
463
  with gr.Row():
464
  with gr.Column(scale=1, elem_classes="roundtable-container"):
465
+ # Initial state im richtigen Format
466
  initial_roundtable_state = json.dumps({
467
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
468
+ "messages": [],
469
+ "currentSpeaker": None,
470
+ "thinking": [],
471
+ "showBubbles": [],
472
+ "avatarImages": avatar_images
 
 
 
 
473
  })
474
 
475
  roundtable = consilium_roundtable(
 
523
  if roundtable_state:
524
  try:
525
  state = json.loads(roundtable_state) if isinstance(roundtable_state, str) else roundtable_state
526
+ # Try to extract consensus from moderator messages
527
+ messages = state.get("messages", [])
528
+ for msg in reversed(messages):
529
+ if msg.get("speaker") == "Dr. Müller" and "KONSENSGRAD:" in msg.get("text", ""):
530
+ import re
531
+ match = re.search(r'KONSENSGRAD:\s*(\d+)', msg["text"])
532
+ if match:
533
+ score = int(match.group(1))
534
+ return f"**Konsensgrad:** {score}/10 {'🟢' if score >= 8 else '🟡' if score >= 5 else '🔴'}"
535
  except:
536
  pass
537
+ return "**Konsensgrad:** Bewertung läuft..."
538
 
539
  roundtable.change(
540
  update_consensus,
 
542
  outputs=[consensus_display]
543
  )
544
 
545
+ # Auto-refresh für bessere Visualisierung während der Diskussion
546
+ def refresh_roundtable(session_id):
547
+ if session_id and session_id in sessions:
548
+ return json.dumps(sessions[session_id].roundtable_state)
549
+ return json.dumps({
550
+ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"],
551
+ "messages": [],
552
+ "currentSpeaker": None,
553
+ "thinking": [],
554
+ "showBubbles": [],
555
+ "avatarImages": avatar_images
556
+ })
557
+
558
+ # Timer für Auto-Update (alle 1 Sekunde)
559
+ gr.Timer(1.0).tick(refresh_roundtable, inputs=[session_id], outputs=[roundtable])
560
+
561
  gr.Markdown("""
562
  ---
563
  ### 📌 Hinweise
 
573
  - **Authentische Dialekte**: Schweizerdeutsch, Bayrisch, Schwäbisch
574
  - **Neutraler Moderator**: Fasst zusammen und bewertet Konsens
575
  - **Interaktive Runden**: Verfolgen Sie jede Diskussionsrunde einzeln
576
+ - **Speech Bubbles**: Live-Anzeige der Diskussionsbeiträge
577
 
578
  *Powered by Apertus Swiss AI* 🇨🇭
579
  """)
app_simple.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🇨🇭 Apertus Dialekt-Konsil - Simplified Version
3
+ Ein AI-Konsil mit Schweizerdeutsch, Bayrisch und Schwäbisch Sprechern
4
+ Vereinfachte Version ohne Roundtable-Komponente
5
+ """
6
+
7
+ import gradio as gr
8
+ import os
9
+ from huggingface_hub import InferenceClient
10
+ import time
11
+ from datetime import datetime
12
+
13
+ # Configuration
14
+ HF_TOKEN = os.environ.get('HF_TOKEN', '')
15
+
16
+ # Initialize Apertus client with publicai provider
17
+ client = None
18
+ if HF_TOKEN:
19
+ client = InferenceClient(
20
+ provider="publicai",
21
+ api_key=HF_TOKEN
22
+ )
23
+
24
+ # Dialekt-Persönlichkeiten
25
+ COUNCIL_MEMBERS = {
26
+ "Ueli": {
27
+ "emoji": "🇨🇭",
28
+ "dialect": "Baseldytsch",
29
+ "location": "Basel",
30
+ "system_prompt": """Du bist Ueli aus Basel und sprichst Baseldytsch.
31
+ Du bist kultiviert, humorvoll und liebst die Basler Fasnacht.
32
+ Verwende typische Basler Ausdrücke und Redewendungen.
33
+ Beispiele: 'Sali zämme', 'Das isch e gueti Sach, gäll', 'Mir Baasler wisse was guet isch'"""
34
+ },
35
+ "Sepp": {
36
+ "emoji": "🥨",
37
+ "dialect": "Bayrisch",
38
+ "location": "München",
39
+ "system_prompt": """Du bist Sepp aus München und sprichst Bayrisch.
40
+ Du bist gesellig, direkt und traditionsbewusst.
41
+ Verwende typische bayrische Ausdrücke und Redewendungen.
42
+ Beispiele: 'Servus beinand', 'Des is fei a guade Idee', 'A bisserl was geht immer'"""
43
+ },
44
+ "Karl": {
45
+ "emoji": "🏰",
46
+ "dialect": "Schwäbisch",
47
+ "location": "Stuttgart",
48
+ "system_prompt": """Du bist Karl aus Stuttgart und sprichst Schwäbisch.
49
+ Du bist sparsam, fleißig und erfinderisch.
50
+ Verwende typische schwäbische Ausdrücke und Redewendungen.
51
+ Beispiele: 'Griaß Godd mitanand', 'Des isch hald so a Sach', 'Mir müsset schaffe'"""
52
+ }
53
+ }
54
+
55
+ # Moderator
56
+ MODERATOR_PROMPT = """Du bist Dr. Müller, ein neutraler Moderator.
57
+ Fasse die Diskussion zusammen und bewerte den Konsensgrad von 1-10.
58
+ Sei präzise und neutral."""
59
+
60
+ class SimpleKonsil:
61
+ def __init__(self):
62
+ self.history = []
63
+ self.round = 0
64
+ self.consensus_score = 0
65
+ self.topic = ""
66
+
67
+ def generate_response(self, member_name, context=""):
68
+ """Generate response for a member"""
69
+ if not client:
70
+ return f"{COUNCIL_MEMBERS[member_name]['emoji']} {member_name}: *API nicht verfügbar*"
71
+
72
+ member = COUNCIL_MEMBERS[member_name]
73
+ prompt = f"{member['system_prompt']}\n\nThema: {self.topic}\n{context}\n\nDeine kurze Antwort (2-3 Sätze):"
74
+
75
+ try:
76
+ completion = client.chat.completions.create(
77
+ model="swiss-ai/Apertus-8B-Instruct-2509",
78
+ messages=[
79
+ {"role": "system", "content": member['system_prompt']},
80
+ {"role": "user", "content": f"Thema: {self.topic}\n{context}\nDeine kurze Antwort:"}
81
+ ],
82
+ max_tokens=100,
83
+ temperature=0.8
84
+ )
85
+ response = completion.choices[0].message.content.strip()
86
+ return f"{member['emoji']} **{member_name}** ({member['location']}): {response}"
87
+ except Exception as e:
88
+ return f"{member['emoji']} **{member_name}**: *Denkt nach... ({str(e)})*"
89
+
90
+ def run_round(self, topic):
91
+ """Run one discussion round"""
92
+ self.round += 1
93
+ self.topic = topic
94
+ round_output = f"\n### 🔄 Runde {self.round}\n\n"
95
+ context = ""
96
+
97
+ # Each member speaks
98
+ for member_name in COUNCIL_MEMBERS.keys():
99
+ response = self.generate_response(member_name, context)
100
+ round_output += f"{response}\n\n"
101
+ context += f"{member_name}: {response}\n"
102
+ self.history.append(response)
103
+ time.sleep(0.5)
104
+
105
+ # Moderator summary
106
+ if client:
107
+ try:
108
+ mod_prompt = f"{MODERATOR_PROMPT}\n\nDiskussion:\n{context}\n\nBewerte Konsens (1-10) und fasse zusammen:"
109
+ completion = client.chat.completions.create(
110
+ model="swiss-ai/Apertus-8B-Instruct-2509",
111
+ messages=[{"role": "user", "content": mod_prompt}],
112
+ max_tokens=150,
113
+ temperature=0.7
114
+ )
115
+ mod_response = completion.choices[0].message.content.strip()
116
+
117
+ # Extract consensus score
118
+ import re
119
+ score_match = re.search(r'\b([1-9]|10)\b', mod_response)
120
+ if score_match:
121
+ self.consensus_score = int(score_match.group())
122
+
123
+ round_output += f"🎓 **Moderator Dr. Müller**:\n> {mod_response}\n\n"
124
+ round_output += f"**Konsensgrad: {self.consensus_score}/10** "
125
+
126
+ if self.consensus_score >= 8:
127
+ round_output += "🟢"
128
+ elif self.consensus_score >= 5:
129
+ round_output += "🟡"
130
+ else:
131
+ round_output += "🔴"
132
+
133
+ except Exception as e:
134
+ round_output += f"🎓 **Moderator**: *Technische Schwierigkeiten*\n"
135
+
136
+ return round_output
137
+
138
+ # Create interface
139
+ def create_interface():
140
+ konsil = SimpleKonsil()
141
+
142
+ with gr.Blocks(title="Apertus Dialekt-Konsil", theme=gr.themes.Soft()) as demo:
143
+ gr.Markdown("""
144
+ # 🇨🇭🥨🏰 Apertus Dialekt-Konsil (Vereinfacht)
145
+
146
+ **Teilnehmer:**
147
+ - 🇨🇭 **Ueli** aus Basel (Baseldytsch)
148
+ - 🥨 **Sepp** aus München (Bayrisch)
149
+ - 🏰 **Karl** aus Stuttgart (Schwäbisch)
150
+ - 🎓 **Dr. Müller** als Moderator
151
+ """)
152
+
153
+ with gr.Row():
154
+ topic_input = gr.Textbox(
155
+ label="💭 Diskussionsthema",
156
+ placeholder="z.B. 'Was ist das beste Essen?'",
157
+ lines=2
158
+ )
159
+
160
+ with gr.Row():
161
+ start_btn = gr.Button("🚀 Diskussion starten", variant="primary")
162
+ next_round_btn = gr.Button("➡️ Nächste Runde", variant="secondary")
163
+ clear_btn = gr.Button("🔄 Neu starten")
164
+
165
+ output_display = gr.Markdown(value="*Bereit für Diskussion...*")
166
+
167
+ status_display = gr.Textbox(
168
+ label="Status",
169
+ value="Warten auf Thema...",
170
+ interactive=False
171
+ )
172
+
173
+ def start_discussion(topic):
174
+ if not topic:
175
+ return "*Bitte Thema eingeben!*", "❌ Kein Thema"
176
+
177
+ konsil.history = []
178
+ konsil.round = 0
179
+ konsil.consensus_score = 0
180
+ konsil.topic = topic
181
+
182
+ output = f"## 💬 Diskussion: {topic}\n"
183
+ output += konsil.run_round(topic)
184
+
185
+ status = f"Runde {konsil.round} - Konsens: {konsil.consensus_score}/10"
186
+ return output, status
187
+
188
+ def continue_discussion():
189
+ if not konsil.topic:
190
+ return "*Keine aktive Diskussion*", "❌ Starte zuerst eine Diskussion"
191
+
192
+ if konsil.consensus_score >= 8:
193
+ return "*Konsens erreicht!*", f"✅ Konsens bei {konsil.consensus_score}/10"
194
+
195
+ if konsil.round >= 5:
196
+ return "*Maximale Rundenzahl erreicht*", "🏁 Diskussion beendet"
197
+
198
+ output = konsil.run_round(konsil.topic)
199
+ status = f"Runde {konsil.round} - Konsens: {konsil.consensus_score}/10"
200
+
201
+ return output, status
202
+
203
+ def clear_all():
204
+ konsil.history = []
205
+ konsil.round = 0
206
+ konsil.consensus_score = 0
207
+ konsil.topic = ""
208
+ return "*Bereit für neue Diskussion...*", "Warten auf Thema..."
209
+
210
+ start_btn.click(
211
+ start_discussion,
212
+ inputs=[topic_input],
213
+ outputs=[output_display, status_display]
214
+ )
215
+
216
+ next_round_btn.click(
217
+ continue_discussion,
218
+ outputs=[output_display, status_display]
219
+ )
220
+
221
+ clear_btn.click(
222
+ clear_all,
223
+ outputs=[output_display, status_display]
224
+ )
225
+
226
+ gr.Examples(
227
+ examples=[
228
+ ["Was ist das beste Essen?"],
229
+ ["Sollten wir mehr Ferien haben?"],
230
+ ["Wie wichtig ist Pünktlichkeit?"]
231
+ ],
232
+ inputs=topic_input
233
+ )
234
+
235
+ return demo
236
+
237
+ if __name__ == "__main__":
238
+ print(f"Dialekt-Konsil (Vereinfacht)")
239
+ print(f"HF_TOKEN konfiguriert: {bool(HF_TOKEN)}")
240
+
241
+ demo = create_interface()
242
+ demo.launch()
test_roundtable.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test the consilium_roundtable component
4
+ """
5
+
6
+ import gradio as gr
7
+ import json
8
+ from gradio_consilium_roundtable import consilium_roundtable
9
+
10
+ def test_roundtable():
11
+ """Test the roundtable with different avatar formats"""
12
+
13
+ with gr.Blocks() as demo:
14
+ gr.Markdown("# Testing Consilium Roundtable Component")
15
+
16
+ # Test 1: Emoji avatars
17
+ test_state_emoji = {
18
+ "participants": [
19
+ {"name": "Ueli", "avatar": "🇨🇭", "message": "Hallo!", "is_thinking": False, "is_current_speaker": True},
20
+ {"name": "Sepp", "avatar": "🥨", "message": "Servus!", "is_thinking": False, "is_current_speaker": False},
21
+ {"name": "Karl", "avatar": "🏰", "message": "Grüß Gott!", "is_thinking": False, "is_current_speaker": False},
22
+ ],
23
+ "current_speaker": "Ueli",
24
+ "round": 1
25
+ }
26
+
27
+ # Test 2: No avatars
28
+ test_state_no_avatar = {
29
+ "participants": [
30
+ {"name": "Ueli", "message": "Hallo!", "is_thinking": False, "is_current_speaker": True},
31
+ {"name": "Sepp", "message": "Servus!", "is_thinking": False, "is_current_speaker": False},
32
+ {"name": "Karl", "message": "Grüß Gott!", "is_thinking": False, "is_current_speaker": False},
33
+ ],
34
+ "current_speaker": "Ueli",
35
+ "round": 1
36
+ }
37
+
38
+ # Test 3: URL avatars
39
+ test_state_url = {
40
+ "participants": [
41
+ {"name": "Ueli", "avatar": "https://via.placeholder.com/50", "message": "Hallo!", "is_thinking": False, "is_current_speaker": True},
42
+ {"name": "Sepp", "avatar": "https://via.placeholder.com/50", "message": "Servus!", "is_thinking": False, "is_current_speaker": False},
43
+ ],
44
+ "current_speaker": "Ueli",
45
+ "round": 1
46
+ }
47
+
48
+ gr.Markdown("## Test 1: Emoji Avatars")
49
+ rt1 = consilium_roundtable(
50
+ value=json.dumps(test_state_emoji),
51
+ label="Emoji Avatars"
52
+ )
53
+
54
+ gr.Markdown("## Test 2: No Avatars")
55
+ rt2 = consilium_roundtable(
56
+ value=json.dumps(test_state_no_avatar),
57
+ label="No Avatars"
58
+ )
59
+
60
+ gr.Markdown("## Test 3: URL Avatars")
61
+ rt3 = consilium_roundtable(
62
+ value=json.dumps(test_state_url),
63
+ label="URL Avatars"
64
+ )
65
+
66
+ # Button to update state
67
+ update_btn = gr.Button("Update States")
68
+
69
+ def update_all():
70
+ new_state = {
71
+ "participants": [
72
+ {"name": "Test1", "avatar": "🎯", "message": "Updated!", "is_thinking": True, "is_current_speaker": False},
73
+ {"name": "Test2", "avatar": "🎨", "message": "Changed!", "is_thinking": False, "is_current_speaker": True},
74
+ ],
75
+ "current_speaker": "Test2",
76
+ "round": 2
77
+ }
78
+ return json.dumps(new_state), json.dumps(new_state), json.dumps(new_state)
79
+
80
+ update_btn.click(
81
+ update_all,
82
+ outputs=[rt1, rt2, rt3]
83
+ )
84
+
85
+ return demo
86
+
87
+ if __name__ == "__main__":
88
+ print("Testing consilium_roundtable component...")
89
+ demo = test_roundtable()
90
+ demo.launch()