Markus Clauss DIRU Vetsuisse commited on
Commit
9b9404c
·
1 Parent(s): f6aef1b

Add roundtable visualization with moderator and session management

Browse files

- Integrated consilium_roundtable component for visual discussion
- Added Dr. Müller as moderator agent who rates consensus (1-10)
- Implemented session-based state management for isolated discussions
- Created round-by-round visibility with 'Next Round' button
- Added avatar support with DiceBear API and local SVG fallbacks
- Enhanced consensus tracking with automatic score calculation
- Updated UI to show real-time participant states (thinking/speaking/waiting)
- Added visual consensus indicator (🟢/🟡/🔴 based on score)

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

README.md CHANGED
@@ -9,22 +9,26 @@ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- # 🇨🇭🥨🏰 Apertus Dialekt-Konsil
13
 
14
- Ein KI-gestütztes Konsil mit drei süddeutschen Dialekt-Sprechern, die zu einem Konsens kommen.
15
 
16
  ## Teilnehmer
17
 
18
- - 🇨🇭 **Hans** aus Zürich - spricht Schweizerdeutsch
19
  - 🥨 **Sepp** aus München - spricht Bayrisch
20
  - 🏰 **Karl** aus Stuttgart - spricht Schwäbisch
 
21
 
22
  ## Features
23
 
 
24
  - **Authentische Dialekte**: Jeder Sprecher verwendet seinen regionalen Dialekt
25
- - **Persönlichkeiten**: Unterschiedliche Charaktereigenschaften und Denkweisen
26
- - **Konsens-Findung**: Automatische Erkennung wenn Einigung erreicht wird
27
- - **Interaktive Diskussion**: Mehrere Runden mit Bezug aufeinander
 
 
28
 
29
  ## Setup
30
 
 
9
  pinned: false
10
  ---
11
 
12
+ # 🇨🇭🥨🏰 Apertus Dialekt-Konsil mit Roundtable
13
 
14
+ Ein KI-gestütztes Konsil mit drei süddeutschen Dialekt-Sprechern und einem Moderator, visualisiert am runden Tisch.
15
 
16
  ## Teilnehmer
17
 
18
+ - 🇨🇭 **Ueli** aus Basel - spricht Baseldytsch
19
  - 🥨 **Sepp** aus München - spricht Bayrisch
20
  - 🏰 **Karl** aus Stuttgart - spricht Schwäbisch
21
+ - 🎓 **Dr. Müller** - Neutraler Moderator
22
 
23
  ## Features
24
 
25
+ - **Visueller Roundtable**: Interaktive Darstellung der Teilnehmer am runden Tisch
26
  - **Authentische Dialekte**: Jeder Sprecher verwendet seinen regionalen Dialekt
27
+ - **Moderator-Agent**: Fasst jede Runde zusammen und bewertet Konsensgrad (1-10)
28
+ - **Session Management**: Isolierte Diskussionen pro Nutzer
29
+ - **Round-by-Round**: Verfolgen Sie jede Diskussionsrunde einzeln
30
+ - **Avatar-Support**: Visuelle Repräsentation aller Teilnehmer
31
+ - **Konsens-Tracking**: Automatische Bewertung und Fortschrittsverfolgung
32
 
33
  ## Setup
34
 
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
- 🇨🇭 Apertus Dialekt-Konsil
3
  Ein AI-Konsil mit Schweizerdeutsch, Bayrisch und Schwäbisch Sprechern
4
- Basierend auf Consilium MCP, angepasst für deutsche Dialekte
5
  """
6
 
7
  import gradio as gr
@@ -10,6 +10,8 @@ from huggingface_hub import InferenceClient
10
  import json
11
  import time
12
  from datetime import datetime
 
 
13
 
14
  # Configuration
15
  HF_TOKEN = os.environ.get('HF_TOKEN', '')
@@ -30,6 +32,7 @@ COUNCIL_MEMBERS = {
30
  "dialect": "Baseldytsch",
31
  "personality": "kultiviert, humorvoll, fasnachts-begeistert",
32
  "speaking_style": "Verwendet typische Basler Ausdrücke wie 'Sali zämme', 'gäll', 'Bebbi', 'Fasnacht', 'Läckerli'",
 
33
  "system_prompt": """Du bist Ueli aus Basel und sprichst Baseldytsch.
34
  Du bist kultiviert, humorvoll und liebst die Basler Fasnacht.
35
  Verwende typische Basler Ausdrücke und Redewendungen.
@@ -40,6 +43,7 @@ Beispiele: 'Sali zämme', 'Das isch e gueti Sach, gäll', 'Mir Baasler wisse was
40
  "dialect": "Bayrisch",
41
  "personality": "gesellig, direkt, traditionsbewusst",
42
  "speaking_style": "Verwendet bayrische Ausdrücke wie 'Servus', 'mei', 'a bisserl', 'g'scheid', 'Gaudi'",
 
43
  "system_prompt": """Du bist Sepp aus München und sprichst Bayrisch.
44
  Du bist gesellig, direkt und traditionsbewusst.
45
  Verwende typische bayrische Ausdrücke und Redewendungen.
@@ -50,6 +54,7 @@ Beispiele: 'Servus beinand', 'Des is fei a guade Idee', 'Mir miassn schaun dass
50
  "dialect": "Schwäbisch",
51
  "personality": "sparsam, fleißig, erfinderisch",
52
  "speaking_style": "Verwendet schwäbische Ausdrücke wie 'Griaß Godd', 'hald', 'gell', 'schaffe', 'Spätzle'",
 
53
  "system_prompt": """Du bist Karl aus Stuttgart und sprichst Schwäbisch.
54
  Du bist sparsam, fleißig und erfinderisch.
55
  Verwende typische schwäbische Ausdrücke und Redewendungen.
@@ -57,11 +62,31 @@ Beispiele: 'Griaß Godd mitanand', 'Des isch hald so a Sach', 'Mir müsset schaf
57
  }
58
  }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  class DialektKonsil:
61
- def __init__(self):
 
62
  self.conversation_history = []
63
  self.current_topic = ""
64
- self.consensus_reached = False
 
 
 
 
 
65
 
66
  def generate_response(self, member_name, topic, previous_responses):
67
  """Generate response for a council member"""
@@ -113,157 +138,287 @@ Deine Antwort:"""
113
  except Exception as e:
114
  return f"*hüstel* Entschuldigung, ich muss kurz nachdenken... ({str(e)})"
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  def run_discussion_round(self, topic):
117
  """Run one round of discussion"""
118
 
 
119
  responses = []
120
 
121
  # Each member speaks once per round
122
  for member_name in COUNCIL_MEMBERS.keys():
 
 
 
 
 
 
 
 
 
123
  response = self.generate_response(member_name, topic, responses)
124
 
125
  responses.append({
126
  "speaker": member_name,
127
  "text": response,
128
  "emoji": COUNCIL_MEMBERS[member_name]["emoji"],
129
- "timestamp": datetime.now().strftime("%H:%M:%S")
 
130
  })
131
 
 
 
 
 
 
 
 
132
  # Add to conversation history
133
  self.conversation_history.append(responses[-1])
134
 
 
 
135
  # Small delay for realism
136
  time.sleep(0.5)
137
 
138
- return responses
139
-
140
- def check_consensus(self):
141
- """Check if consensus has been reached"""
142
-
143
- if len(self.conversation_history) < 6: # Need at least 2 rounds
144
- return False, "Diskussion läuft noch..."
145
-
146
- # Simple consensus check - look for agreement keywords
147
- recent_texts = " ".join([r["text"].lower() for r in self.conversation_history[-3:]])
148
-
149
- agreement_words = [
150
- "einig", "zustimm", "einverstand", "passt", "guet", "guat",
151
- "recht", "richtig", "jo", "ja", "genau", "stimmt", "daccord"
152
- ]
153
-
154
- agreement_count = sum(1 for word in agreement_words if word in recent_texts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- if agreement_count >= 3:
157
- return True, "✅ Konsens erreicht!"
158
- elif len(self.conversation_history) > 12: # After 4 rounds
159
- return True, "🤝 Teilweiser Konsens - weitere Diskussion nötig"
160
- else:
161
- return False, "💭 Diskussion läuft weiter..."
162
 
163
- # Create Gradio Interface
164
- konsil = DialektKonsil()
 
 
 
 
 
165
 
166
- def start_discussion(topic, rounds=3):
167
- """Start a new discussion"""
 
 
 
 
168
 
169
- if not topic:
170
- return " Bitte geben Sie ein Thema ein!", ""
171
 
172
- if not client:
173
- return " HF_TOKEN nicht konfiguriert. Bitte in Space Settings setzen.", ""
174
-
175
- konsil.current_topic = topic
176
- konsil.conversation_history = []
177
- konsil.consensus_reached = False
178
 
179
- output = f"## 🗣️ Dialekt-Konsil zum Thema: {topic}\n\n"
180
- output += "**Teilnehmer:**\n"
181
- for name, member in COUNCIL_MEMBERS.items():
182
- output += f"- {member['emoji']} {name} ({member['dialect']})\n"
183
 
184
- output += "\n---\n\n"
 
 
 
 
185
 
186
- # Run discussion rounds
187
- for round_num in range(rounds):
188
- output += f"### 🔄 Runde {round_num + 1}\n\n"
 
 
 
 
189
 
190
- responses = konsil.run_discussion_round(topic)
191
 
192
- for resp in responses:
193
- output += f"**{resp['emoji']} {resp['speaker']}** ({resp['timestamp']}):\n"
194
- output += f"> {resp['text']}\n\n"
195
 
196
- # Check consensus after each round (except first)
197
- if round_num > 0:
198
- consensus, status = konsil.check_consensus()
199
- output += f"**Status:** {status}\n\n"
200
 
201
- if consensus:
202
- output += "\n### 🎉 Diskussion abgeschlossen!\n"
203
- break
204
 
205
- output += "---\n\n"
206
 
207
- # Final summary
208
- output += generate_summary()
209
 
210
- return output, create_chat_history()
 
211
 
212
- def generate_summary():
213
- """Generate a summary of the discussion"""
214
 
215
- if not konsil.conversation_history:
216
- return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
- summary = "\n## 📋 Zusammenfassung\n\n"
 
219
 
220
- # Count contributions
221
- contributions = {}
222
- for entry in konsil.conversation_history:
223
- speaker = entry["speaker"]
224
- if speaker not in contributions:
225
- contributions[speaker] = 0
226
- contributions[speaker] += 1
227
 
228
- summary += "**Beiträge:**\n"
229
- for speaker, count in contributions.items():
230
- emoji = COUNCIL_MEMBERS[speaker]["emoji"]
231
- summary += f"- {emoji} {speaker}: {count} Wortmeldungen\n"
232
 
233
- # Consensus status
234
- consensus, status = konsil.check_consensus()
235
- summary += f"\n**Ergebnis:** {status}\n"
236
 
237
- return summary
 
238
 
239
- def create_chat_history():
240
- """Create formatted chat history"""
 
 
241
 
242
- if not konsil.conversation_history:
243
- return ""
244
 
245
- history = ""
246
- for entry in konsil.conversation_history:
247
- history += f"[{entry['timestamp']}] {entry['emoji']} {entry['speaker']}: {entry['text']}\n"
 
 
 
248
 
249
- return history
250
 
251
  def create_interface():
252
- """Create Gradio interface"""
253
-
254
- with gr.Blocks(title="Apertus Dialekt-Konsil", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
255
 
256
  gr.Markdown("""
257
- # 🇨🇭🥨🏰 Apertus Dialekt-Konsil
258
 
259
- Ein KI-gestütztes Konsil mit drei süddeutschen Dialekt-Sprechern:
260
  - 🇨🇭 **Ueli** aus Basel (Baseldytsch)
261
  - 🥨 **Sepp** aus München (Bayrisch)
262
  - 🏰 **Karl** aus Stuttgart (Schwäbisch)
 
263
 
264
- Geben Sie ein Thema ein und beobachten Sie, wie die drei zu einem Konsens kommen!
265
  """)
266
 
 
 
 
267
  with gr.Row():
268
  with gr.Column(scale=2):
269
  topic_input = gr.Textbox(
@@ -272,34 +427,42 @@ def create_interface():
272
  lines=2
273
  )
274
 
275
- rounds_slider = gr.Slider(
276
- minimum=1,
277
- maximum=5,
278
- value=3,
279
- step=1,
280
- label="🔄 Anzahl Diskussionsrunden"
281
- )
282
-
283
- start_btn = gr.Button(
284
- "🚀 Diskussion starten",
285
- variant="primary",
286
- size="lg"
 
 
 
 
 
 
 
 
 
 
287
  )
288
 
 
289
  with gr.Row():
290
- with gr.Column():
291
- output_display = gr.Markdown(
292
- label="Diskussionsverlauf",
293
- value="*Warten auf Thema...*"
294
- )
295
 
 
296
  with gr.Row():
297
  with gr.Column():
298
- chat_history = gr.Textbox(
299
- label="📝 Chat-Protokoll",
300
- lines=10,
301
- max_lines=20,
302
- interactive=False
303
  )
304
 
305
  # Example topics
@@ -317,9 +480,28 @@ def create_interface():
317
 
318
  # Event handlers
319
  start_btn.click(
320
- start_discussion,
321
- inputs=[topic_input, rounds_slider],
322
- outputs=[output_display, chat_history]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  )
324
 
325
  gr.Markdown("""
@@ -327,16 +509,16 @@ def create_interface():
327
  ### 📌 Hinweise
328
 
329
  - Das Konsil verwendet das **Apertus-8B** Modell für authentische Dialekt-Generierung
330
- - Jeder Sprecher hat seine eigene Persönlichkeit und Sprechweise
331
- - Die Diskussion endet, wenn Konsens erreicht wird oder nach der festgelegten Rundenzahl
332
  - Benötigt HF_TOKEN in den Space Settings
333
 
334
  ### 🎯 Features
335
 
 
336
  - **Authentische Dialekte**: Schweizerdeutsch, Bayrisch, Schwäbisch
337
- - **Persönlichkeiten**: Jeder Charakter hat eigene Eigenschaften
338
- - **Konsens-Findung**: Automatische Erkennung von Übereinstimmungen
339
- - **Zusammenfassung**: Übersicht über Diskussionsverlauf
340
 
341
  *Powered by Apertus Swiss AI* 🇨🇭
342
  """)
@@ -345,7 +527,7 @@ def create_interface():
345
 
346
  # Launch the app
347
  if __name__ == "__main__":
348
- print("🇨🇭🥨🏰 Apertus Dialekt-Konsil")
349
  print(f"HF Token configured: {bool(HF_TOKEN)}")
350
 
351
  demo = create_interface()
 
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
 
10
  import json
11
  import time
12
  from datetime import datetime
13
+ import uuid
14
+ from gradio_consilium_roundtable import RoundTable
15
 
16
  # Configuration
17
  HF_TOKEN = os.environ.get('HF_TOKEN', '')
 
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": "https://api.dicebear.com/7.x/personas/svg?seed=Ueli&backgroundColor=b6e3f4",
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.
 
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": "https://api.dicebear.com/7.x/personas/svg?seed=Sepp&backgroundColor=ffd5dc",
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.
 
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": "https://api.dicebear.com/7.x/personas/svg?seed=Karl&backgroundColor=c1ffc1",
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
  }
63
  }
64
 
65
+ # Moderator Agent
66
+ MODERATOR = {
67
+ "name": "Dr. Müller (Moderator)",
68
+ "emoji": "🎓",
69
+ "avatar": "https://api.dicebear.com/7.x/personas/svg?seed=Moderator&backgroundColor=e0e0e0",
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
73
+ 2. Bewerte den Konsensgrad auf einer Skala von 1-10
74
+ 3. Identifiziere Gemeinsamkeiten und Unterschiede
75
+ 4. Schlage Kompromisse vor wenn nötig
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())
82
  self.conversation_history = []
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"""
 
138
  except Exception as e:
139
  return f"*hüstel* Entschuldigung, ich muss kurz nachdenken... ({str(e)})"
140
 
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"
148
+
149
+ prompt = f"""{MODERATOR['system_prompt']}
150
+
151
+ {context}
152
+
153
+ Aufgabe:
154
+ 1. Fasse die Hauptpunkte zusammen (2-3 Sätze)
155
+ 2. Bewerte den Konsensgrad von 1-10 (1=völlig uneinig, 10=vollständige Einigung)
156
+ 3. Identifiziere Gemeinsamkeiten
157
+ 4. Nenne Unterschiede falls vorhanden
158
+
159
+ Format deiner Antwort:
160
+ ZUSAMMENFASSUNG: [Deine Zusammenfassung]
161
+ KONSENSGRAD: [Zahl von 1-10]
162
+ GEMEINSAMKEITEN: [Was alle teilen]
163
+ UNTERSCHIEDE: [Wo Uneinigkeit herrscht]"""
164
+
165
+ try:
166
+ completion = client.chat.completions.create(
167
+ model="swiss-ai/Apertus-8B-Instruct-2509",
168
+ messages=[
169
+ {"role": "system", "content": MODERATOR['system_prompt']},
170
+ {"role": "user", "content": prompt}
171
+ ],
172
+ max_tokens=250,
173
+ temperature=0.7
174
+ )
175
+
176
+ response = completion.choices[0].message.content.strip()
177
+
178
+ # Parse consensus score
179
+ consensus_score = 5 # Default
180
+ if "KONSENSGRAD:" in response:
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
+
188
+ return response, consensus_score
189
+
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,
214
  "text": response,
215
  "emoji": COUNCIL_MEMBERS[member_name]["emoji"],
216
+ "timestamp": datetime.now().strftime("%H:%M:%S"),
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,
248
+ "emoji": MODERATOR["emoji"],
249
+ "timestamp": datetime.now().strftime("%H:%M:%S"),
250
+ "round": self.round_number,
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
+ members = []
261
+ for name, member in COUNCIL_MEMBERS.items():
262
+ state = self.members_state[name]
263
+ members.append({
264
+ "name": name.split(" ")[0], # Just first name
265
+ "avatar": member["avatar"],
266
+ "status": state["status"],
267
+ "message": state.get("message", ""),
268
+ "thinking": state.get("thinking", False)
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
+ members.append({
279
+ "name": "Moderator",
280
+ "avatar": MODERATOR["avatar"],
281
+ "status": "moderating" if moderator_message else "waiting",
282
+ "message": moderator_message,
283
+ "thinking": False
284
+ })
285
 
286
+ return {
287
+ "members": members,
288
+ "consensus_score": self.consensus_score,
289
+ "round": self.round_number,
290
+ "topic": self.current_topic
291
+ }
292
 
293
+ def format_history(self):
294
+ """Format conversation history for display"""
295
 
296
+ if not self.conversation_history:
297
+ return "*Keine Diskussion gestartet*"
 
 
 
 
298
 
299
+ output = f"## 🗣️ Dialekt-Konsil: {self.current_topic}\n\n"
 
 
 
300
 
301
+ current_round = 0
302
+ for entry in self.conversation_history:
303
+ if entry.get("round", 0) > current_round:
304
+ current_round = entry["round"]
305
+ output += f"\n### 🔄 Runde {current_round}\n\n"
306
 
307
+ if entry.get("is_moderator"):
308
+ output += f"**{entry['emoji']} {entry['speaker']}** - Zusammenfassung:\n"
309
+ output += f"```\n{entry['text']}\n```\n"
310
+ output += f"**Konsensgrad: {self.consensus_score}/10**\n\n"
311
+ else:
312
+ output += f"**{entry['emoji']} {entry['speaker']}** ({entry['timestamp']}):\n"
313
+ output += f"> {entry['text']}\n\n"
314
 
315
+ return output
316
 
317
+ # Session management
318
+ sessions = {}
 
319
 
320
+ def get_or_create_session(session_id=None):
321
+ """Get existing session or create new one"""
322
+ if not session_id:
323
+ session_id = str(uuid.uuid4())
324
 
325
+ if session_id not in sessions:
326
+ sessions[session_id] = DialektKonsil(session_id)
 
327
 
328
+ return sessions[session_id], session_id
329
 
330
+ def start_new_discussion(topic):
331
+ """Start a new discussion with a fresh session"""
332
 
333
+ if not topic:
334
+ return None, None, None, "❌ Bitte geben Sie ein Thema ein!"
335
 
336
+ if not client:
337
+ return None, None, None, " HF_TOKEN nicht konfiguriert. Bitte in Space Settings setzen."
338
 
339
+ # Create new session
340
+ konsil, session_id = get_or_create_session()
341
+ konsil.current_topic = topic
342
+ konsil.conversation_history = []
343
+ konsil.round_number = 0
344
+ konsil.consensus_score = 0
345
+
346
+ # Initialize all members to waiting
347
+ for name in COUNCIL_MEMBERS.keys():
348
+ konsil.members_state[name] = {
349
+ "status": "waiting",
350
+ "message": "",
351
+ "thinking": False
352
+ }
353
+
354
+ initial_state = konsil.get_current_state()
355
+
356
+ return (
357
+ initial_state,
358
+ session_id,
359
+ konsil.format_history(),
360
+ f"✅ Diskussion gestartet: {topic}"
361
+ )
362
 
363
+ def run_next_round(session_id):
364
+ """Run the next discussion round"""
365
 
366
+ if not session_id or session_id not in sessions:
367
+ return None, "❌ Keine aktive Session. Bitte starten Sie eine neue Diskussion."
 
 
 
 
 
368
 
369
+ konsil = sessions[session_id]
 
 
 
370
 
371
+ if not konsil.current_topic:
372
+ return None, "❌ Kein Thema gesetzt. Bitte starten Sie eine neue Diskussion."
 
373
 
374
+ if konsil.consensus_score >= 8:
375
+ return konsil.get_current_state(), f"✅ Konsens bereits erreicht (Score: {konsil.consensus_score}/10)"
376
 
377
+ # Run discussion round with yield for updates
378
+ state = None
379
+ for state in konsil.run_discussion_round(konsil.current_topic):
380
+ pass # Just get the final state
381
 
382
+ history = konsil.format_history()
 
383
 
384
+ if konsil.consensus_score >= 8:
385
+ status = f"🎉 Konsens erreicht! (Score: {konsil.consensus_score}/10)"
386
+ elif konsil.round_number >= 5:
387
+ status = f"🤝 Maximale Rundenzahl erreicht. Finaler Konsensgrad: {konsil.consensus_score}/10"
388
+ else:
389
+ status = f"Runde {konsil.round_number} abgeschlossen. Konsensgrad: {konsil.consensus_score}/10"
390
 
391
+ return state, history, status
392
 
393
  def create_interface():
394
+ """Create Gradio interface with roundtable visualization"""
395
+
396
+ with gr.Blocks(
397
+ title="Apertus Dialekt-Konsil",
398
+ theme=gr.themes.Soft(),
399
+ css="""
400
+ .roundtable-container {
401
+ max-width: 800px;
402
+ margin: 0 auto;
403
+ }
404
+ """
405
+ ) as demo:
406
 
407
  gr.Markdown("""
408
+ # 🇨🇭🥨🏰 Apertus Dialekt-Konsil mit Roundtable
409
 
410
+ Ein KI-gestütztes Konsil mit drei süddeutschen Dialekt-Sprechern und Moderator:
411
  - 🇨🇭 **Ueli** aus Basel (Baseldytsch)
412
  - 🥨 **Sepp** aus München (Bayrisch)
413
  - 🏰 **Karl** aus Stuttgart (Schwäbisch)
414
+ - 🎓 **Dr. Müller** als neutraler Moderator
415
 
416
+ Geben Sie ein Thema ein und beobachten Sie die Diskussion am runden Tisch!
417
  """)
418
 
419
+ # Hidden session ID
420
+ session_id = gr.State()
421
+
422
  with gr.Row():
423
  with gr.Column(scale=2):
424
  topic_input = gr.Textbox(
 
427
  lines=2
428
  )
429
 
430
+ with gr.Row():
431
+ start_btn = gr.Button(
432
+ "🚀 Neue Diskussion starten",
433
+ variant="primary",
434
+ size="lg"
435
+ )
436
+ next_round_btn = gr.Button(
437
+ "➡️ Nächste Runde",
438
+ variant="secondary",
439
+ size="lg"
440
+ )
441
+
442
+ # Status display
443
+ status_display = gr.Markdown(value="*Bereit für neue Diskussion*")
444
+
445
+ # Roundtable visualization
446
+ with gr.Row():
447
+ with gr.Column(scale=1, elem_classes="roundtable-container"):
448
+ roundtable = RoundTable(
449
+ value=None,
450
+ label="Diskussionsrunde",
451
+ height=500
452
  )
453
 
454
+ # Consensus score display
455
  with gr.Row():
456
+ consensus_display = gr.Markdown(
457
+ value="**Konsensgrad:** Noch keine Diskussion gestartet"
458
+ )
 
 
459
 
460
+ # Discussion history
461
  with gr.Row():
462
  with gr.Column():
463
+ history_display = gr.Markdown(
464
+ label="Diskussionsverlauf",
465
+ value="*Warten auf Diskussionsstart...*"
 
 
466
  )
467
 
468
  # Example topics
 
480
 
481
  # Event handlers
482
  start_btn.click(
483
+ start_new_discussion,
484
+ inputs=[topic_input],
485
+ outputs=[roundtable, session_id, history_display, status_display]
486
+ )
487
+
488
+ next_round_btn.click(
489
+ run_next_round,
490
+ inputs=[session_id],
491
+ outputs=[roundtable, history_display, status_display]
492
+ )
493
+
494
+ # Update consensus display when roundtable updates
495
+ def update_consensus(roundtable_state):
496
+ if roundtable_state and "consensus_score" in roundtable_state:
497
+ score = roundtable_state["consensus_score"]
498
+ return f"**Konsensgrad:** {score}/10 {'🟢' if score >= 8 else '🟡' if score >= 5 else '🔴'}"
499
+ return "**Konsensgrad:** Noch keine Bewertung"
500
+
501
+ roundtable.change(
502
+ update_consensus,
503
+ inputs=[roundtable],
504
+ outputs=[consensus_display]
505
  )
506
 
507
  gr.Markdown("""
 
509
  ### 📌 Hinweise
510
 
511
  - Das Konsil verwendet das **Apertus-8B** Modell für authentische Dialekt-Generierung
512
+ - Der Moderator bewertet nach jeder Runde den Konsensgrad (1-10)
513
+ - Die Diskussion endet bei Konsensgrad 8 oder nach 5 Runden
514
  - Benötigt HF_TOKEN in den Space Settings
515
 
516
  ### 🎯 Features
517
 
518
+ - **Visueller Roundtable**: Sehen Sie die Teilnehmer am runden Tisch
519
  - **Authentische Dialekte**: Schweizerdeutsch, Bayrisch, Schwäbisch
520
+ - **Neutraler Moderator**: Fasst zusammen und bewertet Konsens
521
+ - **Interaktive Runden**: Verfolgen Sie jede Diskussionsrunde einzeln
 
522
 
523
  *Powered by Apertus Swiss AI* 🇨🇭
524
  """)
 
527
 
528
  # Launch the app
529
  if __name__ == "__main__":
530
+ print("🇨🇭🥨🏰 Apertus Dialekt-Konsil mit Roundtable")
531
  print(f"HF Token configured: {bool(HF_TOKEN)}")
532
 
533
  demo = create_interface()
avatar_generator.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate simple SVG avatars for the Dialekt-Konsil participants
4
+ """
5
+
6
+ import os
7
+
8
+ def create_avatar_svg(name, color, emoji, filename):
9
+ """Create a simple SVG avatar with emoji and color background"""
10
+
11
+ svg_content = f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
12
+ <circle cx="50" cy="50" r="45" fill="{color}" />
13
+ <text x="50" y="55" font-size="40" text-anchor="middle" dominant-baseline="middle">{emoji}</text>
14
+ </svg>"""
15
+
16
+ with open(filename, 'w', encoding='utf-8') as f:
17
+ f.write(svg_content)
18
+
19
+ print(f"Created avatar for {name}: {filename}")
20
+
21
+ # Create avatar directory
22
+ os.makedirs("static/avatars", exist_ok=True)
23
+
24
+ # Create avatars for each participant
25
+ avatars = [
26
+ ("Ueli", "#b6e3f4", "🇨🇭", "static/avatars/ueli.svg"),
27
+ ("Sepp", "#ffd5dc", "🥨", "static/avatars/sepp.svg"),
28
+ ("Karl", "#c1ffc1", "🏰", "static/avatars/karl.svg"),
29
+ ("Moderator", "#e0e0e0", "🎓", "static/avatars/moderator.svg")
30
+ ]
31
+
32
+ for name, color, emoji, filename in avatars:
33
+ create_avatar_svg(name, color, emoji, filename)
34
+
35
+ print("\nAll avatars created successfully!")
36
+ print("These can be served as static files in the Gradio app")
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  gradio==5.44.0
2
  huggingface-hub
3
  transformers
4
- torch
 
 
1
  gradio==5.44.0
2
  huggingface-hub
3
  transformers
4
+ torch
5
+ gradio-consilium-roundtable
static/avatars/karl.svg ADDED
static/avatars/moderator.svg ADDED
static/avatars/sepp.svg ADDED
static/avatars/ueli.svg ADDED