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 +10 -6
- app.py +310 -128
- avatar_generator.py +36 -0
- requirements.txt +2 -1
- static/avatars/karl.svg +4 -0
- static/avatars/moderator.svg +4 -0
- static/avatars/sepp.svg +4 -0
- static/avatars/ueli.svg +4 -0
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,
|
| 15 |
|
| 16 |
## Teilnehmer
|
| 17 |
|
| 18 |
-
- 🇨🇭 **
|
| 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 |
-
- **
|
| 26 |
-
- **
|
| 27 |
-
- **
|
|
|
|
|
|
|
| 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 |
-
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
"
|
| 151 |
-
"
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
konsil.current_topic = topic
|
| 176 |
-
konsil.conversation_history = []
|
| 177 |
-
konsil.consensus_reached = False
|
| 178 |
|
| 179 |
-
|
| 180 |
-
output += "**Teilnehmer:**\n"
|
| 181 |
-
for name, member in COUNCIL_MEMBERS.items():
|
| 182 |
-
output += f"- {member['emoji']} {name} ({member['dialect']})\n"
|
| 183 |
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
-
|
| 191 |
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
output += f"> {resp['text']}\n\n"
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
break
|
| 204 |
|
| 205 |
-
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
| 209 |
|
| 210 |
-
|
|
|
|
| 211 |
|
| 212 |
-
|
| 213 |
-
|
| 214 |
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
-
|
|
|
|
| 219 |
|
| 220 |
-
|
| 221 |
-
|
| 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 |
-
|
| 229 |
-
for speaker, count in contributions.items():
|
| 230 |
-
emoji = COUNCIL_MEMBERS[speaker]["emoji"]
|
| 231 |
-
summary += f"- {emoji} {speaker}: {count} Wortmeldungen\n"
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
summary += f"\n**Ergebnis:** {status}\n"
|
| 236 |
|
| 237 |
-
|
|
|
|
| 238 |
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
| 241 |
|
| 242 |
-
|
| 243 |
-
return ""
|
| 244 |
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
-
return history
|
| 250 |
|
| 251 |
def create_interface():
|
| 252 |
-
"""Create Gradio interface"""
|
| 253 |
-
|
| 254 |
-
with gr.Blocks(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
)
|
| 288 |
|
|
|
|
| 289 |
with gr.Row():
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
value="*Warten auf Thema...*"
|
| 294 |
-
)
|
| 295 |
|
|
|
|
| 296 |
with gr.Row():
|
| 297 |
with gr.Column():
|
| 298 |
-
|
| 299 |
-
label="
|
| 300 |
-
|
| 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 |
-
|
| 321 |
-
inputs=[topic_input
|
| 322 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
-
|
| 331 |
-
- Die Diskussion endet
|
| 332 |
- Benötigt HF_TOKEN in den Space Settings
|
| 333 |
|
| 334 |
### 🎯 Features
|
| 335 |
|
|
|
|
| 336 |
- **Authentische Dialekte**: Schweizerdeutsch, Bayrisch, Schwäbisch
|
| 337 |
-
- **
|
| 338 |
-
- **
|
| 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
|
|