purplejamie commited on
Commit
0d04b7a
·
verified ·
1 Parent(s): 974b164

Upload 18 files

Browse files

Upload Calm.i Chatbot Update

.gitattributes ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ assets/background_image/hintergrund.jpg filter=lfs diff=lfs merge=lfs -text
2
+ assets/home_screen/home_screen.png filter=lfs diff=lfs merge=lfs -text
3
+ assets/home_screen/home_screen2.png filter=lfs diff=lfs merge=lfs -text
4
+ assets/home_screen/home_screen3.png filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Calm.i
3
+ emoji: 🏢
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 6.1.0
8
+ app_file: app.py
9
+ pinned: false
10
+ short_description: chatbot for stress - sleep and panicmanagement
11
+ ---
12
+
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,1743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import gradio as gr
4
+ from openai import OpenAI
5
+ import PyPDF2
6
+ import chromadb
7
+ from chromadb.utils import embedding_functions
8
+ import json
9
+ from concurrent.futures import ThreadPoolExecutor
10
+
11
+ from theme import CustomTheme
12
+
13
+ # Konfiguration
14
+ CONTEXT_SIZE = 30
15
+ CHUNK_SIZE = 100
16
+ CHUNK_OVERLAP = 10
17
+
18
+ # OpenAI Client initialisieren
19
+ client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
20
+
21
+ # ChromaDB initialisieren
22
+ chroma_client = chromadb.PersistentClient(path="./chroma_db")
23
+ openai_ef = embedding_functions.OpenAIEmbeddingFunction(
24
+ api_key=os.environ.get("OPENAI_API_KEY"),
25
+ model_name="text-embedding-3-small"
26
+ )
27
+
28
+ # Globale Variablen für personalisierte Inhalte
29
+ user_profile = {}
30
+ personalized_content = {
31
+ "schlafplan": "",
32
+ "mediensammlung": "",
33
+ "timemanagement": ""
34
+ }
35
+
36
+
37
+ def chunk_text(text, chunk_size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
38
+ """Teilt Text in überlappende Chunks"""
39
+ chunks = []
40
+ start = 0
41
+ while start < len(text):
42
+ end = start + chunk_size
43
+ chunk = text[start:end]
44
+ if chunk.strip():
45
+ chunks.append(chunk)
46
+ start = end - overlap
47
+ return chunks
48
+
49
+
50
+ def load_documents_to_vectordb(path="./stress/"):
51
+ """Lädt Dokumente und speichert sie in ChromaDB"""
52
+ try:
53
+ chroma_client.delete_collection(name="documents")
54
+ except:
55
+ pass
56
+
57
+ collection = chroma_client.create_collection(
58
+ name="documents",
59
+ embedding_function=openai_ef
60
+ )
61
+
62
+ doc_id = 0
63
+
64
+ for filename in os.listdir(path):
65
+ filepath = os.path.join(path, filename)
66
+
67
+ if os.path.isdir(filepath):
68
+ continue
69
+
70
+ print(f"Verarbeite: {filename}")
71
+
72
+ try:
73
+ text = ""
74
+
75
+ if filename.endswith('.pdf'):
76
+ with open(filepath, 'rb') as f:
77
+ pdf_reader = PyPDF2.PdfReader(f)
78
+ for page_num, page in enumerate(pdf_reader.pages, 1):
79
+ page_text = page.extract_text()
80
+ if page_text.strip():
81
+ page_chunks = chunk_text(page_text)
82
+
83
+ for i, chunk in enumerate(page_chunks):
84
+ collection.add(
85
+ documents=[chunk],
86
+ metadatas=[{
87
+ "filename": filename,
88
+ "page": page_num,
89
+ "chunk_id": i,
90
+ "total_chunks": len(page_chunks),
91
+ "source_type": "pdf"
92
+ }],
93
+ ids=[f"doc_{doc_id}"]
94
+ )
95
+ doc_id += 1
96
+
97
+ print(f" → PDF mit {len(pdf_reader.pages)} Seiten verarbeitet")
98
+
99
+ elif filename.endswith(('.txt', '.md')):
100
+ with open(filepath, 'r', encoding='utf-8') as f:
101
+ text = f.read()
102
+
103
+ if text.strip():
104
+ chunks = chunk_text(text)
105
+
106
+ for i, chunk in enumerate(chunks):
107
+ collection.add(
108
+ documents=[chunk],
109
+ metadatas=[{
110
+ "filename": filename,
111
+ "page": "N/A",
112
+ "chunk_id": i,
113
+ "total_chunks": len(chunks),
114
+ "source_type": "text"
115
+ }],
116
+ ids=[f"doc_{doc_id}"]
117
+ )
118
+ doc_id += 1
119
+
120
+ print(f" → {len(chunks)} Chunks erstellt")
121
+
122
+ except Exception as e:
123
+ print(f"Fehler beim Laden von {filename}: {e}")
124
+
125
+ print(f"\nGesamt: {doc_id} Chunks in VectorDB gespeichert")
126
+ return collection
127
+
128
+
129
+ def get_relevant_context(query, collection, n_results=CONTEXT_SIZE):
130
+ """Sucht die relevantesten Dokument-Chunks für eine Anfrage"""
131
+ results = collection.query(
132
+ query_texts=[query],
133
+ n_results=n_results
134
+ )
135
+
136
+ context = ""
137
+ if results['documents']:
138
+ for i, (doc, metadata) in enumerate(zip(results['documents'][0], results['metadatas'][0])):
139
+ if metadata.get('page'):
140
+ source_info = f"{metadata['filename']}, Seite {metadata['page']}"
141
+ else:
142
+ source_info = f"{metadata['filename']}"
143
+
144
+ context += f"\n--- Quelle {i+1}: {source_info} (Chunk {metadata['chunk_id']+1}/{metadata['total_chunks']}) ---\n"
145
+ context += doc + "\n"
146
+
147
+ return context
148
+
149
+
150
+ def generate_personalized_content(user_info, content_type, schlafplan_context="", user_wunsch=""):
151
+ """Generiert personalisierten Content basierend auf Nutzerprofil"""
152
+
153
+ # Basis-Kontext für konsistente Zeiten
154
+ schlafplan_referenz = ""
155
+ if schlafplan_context:
156
+ schlafplan_referenz = f"""
157
+
158
+ WICHTIG - Verwende exakt diese Schlafenszeiten aus dem bereits erstellten Schlafplan:
159
+ {schlafplan_context}
160
+
161
+ Alle Zeitangaben müssen mit diesen Schlafenszeiten übereinstimmen!
162
+ """
163
+
164
+ # Wunsch-Kontext wenn User Änderungen möchte
165
+ wunsch_kontext = ""
166
+ if user_wunsch:
167
+ wunsch_kontext = f"""
168
+
169
+ WICHTIG - ÄNDERUNGSWUNSCH:
170
+ Der Nutzer möchte folgende Änderung: {user_wunsch}
171
+ Berücksichtige diesen Wunsch bei der Erstellung und passe den Plan entsprechend an!
172
+ """
173
+
174
+ prompts = {
175
+ "schlafplan": f"""Erstelle einen personalisierten Schlafplan basierend auf folgenden Informationen:
176
+
177
+ Nutzerprofil:
178
+ {json.dumps(user_info, indent=2, ensure_ascii=False)}
179
+ {wunsch_kontext}
180
+
181
+ Erstelle einen konkreten, umsetzbaren Schlafplan mit:
182
+ 1. Individuellen Schlafenszeiten basierend auf dem Chronotyp (z.B. "Schlafenszeit: 22:30 Uhr, Aufstehzeit: 6:30 Uhr")
183
+ 2. Spezifischen Abendroutinen
184
+ 3. Angepassten Tipps für die genannten Schlafprobleme
185
+ 4. Konkreten Empfehlungen für die persönliche Situation
186
+
187
+ WICHTIG: Nenne die exakten Uhrzeiten für Schlafenszeit und Aufstehzeit klar und deutlich am Anfang!
188
+
189
+ Formatiere die Antwort in Markdown mit klarer Struktur und praktischen Beispielen.""",
190
+
191
+ "mediensammlung": f"""Erstelle eine personalisierte Medien und Tools basierend auf:
192
+
193
+ Nutzerprofil:
194
+ {json.dumps(user_info, indent=2, ensure_ascii=False)}
195
+ {schlafplan_referenz}
196
+ {wunsch_kontext}
197
+
198
+ Empfehle:
199
+ 1. Spezifische Atemübungen für die genannten Probleme
200
+ 2. Passende Musik-Playlists und Genres
201
+ 3. Geeignete Meditations-Apps und -Techniken
202
+ 4. Konkrete Videos/Podcasts mit Links (falls möglich)
203
+ 5. Individuell angepasste Entspannungstechniken
204
+ 6. Buchempfehlungen zu Stressbewältigung, Achtsamkeit, Schlaf oder den genannten Themen (mit Autor und kurzer Beschreibung warum das Buch passt)
205
+
206
+ Formatiere in Markdown mit praktischen Links und Zeitangaben.""",
207
+
208
+ "timemanagement": f"""Erstelle einen personalisierten Time-Management-Plan:
209
+
210
+ Nutzerprofil:
211
+ {json.dumps(user_info, indent=2, ensure_ascii=False)}
212
+ {schlafplan_referenz}
213
+ {wunsch_kontext}
214
+
215
+ Entwickle:
216
+ 1. **Tagesplan als Markdown-Tabelle** mit folgender Struktur:
217
+ | Uhrzeit | Tätigkeit | Hinweise |
218
+ |---------|-----------|----------|
219
+ (Fülle mit konkreten Zeiten vom Aufstehen bis Schlafenszeit basierend auf dem Schlafplan)
220
+
221
+ 2. Priorisierungssystem für die persönliche Situation
222
+ 3. Konkrete Techniken gegen Prokrastination
223
+ 4. Pausen- und Energiemanagement-Plan
224
+ 5. Realistische Ziele und Tracking-Methoden
225
+
226
+ WICHTIG:
227
+ - Der Tagesplan muss mit den Schlafenszeiten aus dem Schlafplan übereinstimmen!
228
+ - Die Tabelle MUSS in Markdown-Format sein mit | als Trennzeichen
229
+
230
+ Formatiere in Markdown mit der Tabelle am Anfang und den weiteren Tipps darunter."""
231
+ }
232
+
233
+ try:
234
+ response = client.chat.completions.create(
235
+ model="gpt-4o-mini",
236
+ messages=[
237
+ {"role": "system", "content": "Du bist ein Experte für personalisierte Selbsthilfe-Pläne. Erstelle praktische, umsetzbare und wissenschaftlich fundierte Empfehlungen."},
238
+ {"role": "user", "content": prompts[content_type]}
239
+ ],
240
+ temperature=0.7,
241
+ max_tokens=2000
242
+ )
243
+ return response.choices[0].message.content
244
+ except Exception as e:
245
+ print(f"Fehler bei Personalisierung: {e}")
246
+ return "Personalisierung konnte nicht erstellt werden. Bitte versuche es später erneut."
247
+
248
+
249
+ def collect_user_info(message, history):
250
+ """Sammelt Nutzerinformationen für Personalisierung"""
251
+ global user_profile, personalized_content, logo_inline_html
252
+
253
+ # Fragen-Flow
254
+ questions = [
255
+ "Hallo! Ich bin calm.i\n\nUm dir die bestmögliche Unterstützung zu bieten, würde ich dich gerne besser kennenlernen. Das dauert nur 2-3 Minuten.\n\n**Frage 1/7:** Wie heißt du? (Oder wie möchtest du genannt werden?)",
256
+
257
+ "**Frage 2/7:** Bist du eher ein Morgenmensch oder Nachtmensch?\n\n(Wann fühlst du dich am produktivsten?)",
258
+
259
+ "**Frage 3/7:** Wie sieht dein aktueller Schlaf aus?\n\n- Wie viele Stunden schläfst du durchschnittlich?\n- Hast du Einschlaf- oder Durchschlafprobleme?",
260
+
261
+ "**Frage 4/7:** Was sind deine größten Stressquellen im Alltag?\n\n(z.B. Arbeit, Studium, Familie, Gesundheit...)",
262
+
263
+ "**Frage 5/7:** Hast du bereits Erfahrungen mit Entspannungstechniken?\n\n(z.B. Meditation, Atemübungen, Progressive Muskelentspannung...)",
264
+
265
+ "**Frage 6/7:** Wie viel Zeit hast du täglich für Selbstfürsorge?\n\n(Realistisch betrachtet - 10 Min, 30 Min, 1 Stunde?)",
266
+
267
+ "**Frage 7/7:** Gibt es spezielle Themen, bei denen du Unterstützung brauchst?\n\n(z.B. Zeitmanagement, Schlafprobleme, Panikattacken, Prüfungsangst...)"
268
+ ]
269
+
270
+ # Initialisierung beim ersten Aufruf (User sagt "ja" oder ähnliches)
271
+ if 'question_count' not in user_profile:
272
+ message_lower = message.lower().strip()
273
+ # Prüfe ob Nutzer den Test machen möchte
274
+ if any(word in message_lower for word in ['ja', 'los', 'geht', 'gerne', 'okay', 'ok', 'klar', 'start']):
275
+ user_profile['question_count'] = 0
276
+ user_profile['answers'] = []
277
+ return questions[0] # Starte mit Frage 1/7 (Name)
278
+ # Prüfe ob Nutzer den Test überspringen möchte
279
+ elif any(word in message_lower for word in ['nicht', 'später', 'nein', 'skip', 'überspringen']):
280
+ user_profile['personalization_done'] = True
281
+ user_profile['test_skipped'] = True
282
+ user_profile['question_count'] = -1 # Markiere als übersprungen
283
+ user_profile['answers'] = []
284
+ return f"Kein Problem! Du kannst den Test jederzeit später machen, indem du mich einfach darum bittest.\n\nWie kann ich dir heute helfen?"
285
+ else:
286
+ # Unklare Antwort - frage nochmal
287
+ user_profile['question_count'] = -1 # Setze auf -1 damit wir wissen dass gefragt wurde
288
+ user_profile['answers'] = []
289
+ return f"Möchtest du den kurzen Fragebogen jetzt ausfüllen?\n\nAntworte einfach mit **\"Ja\"** um zu starten oder **\"Jetzt nicht\"** um direkt zu chatten."
290
+
291
+ # Warte auf Entscheidung für Test (wenn unklare Antwort beim ersten Mal)
292
+ if user_profile['question_count'] == -1:
293
+ message_lower = message.lower().strip()
294
+ # Prüfe ob Nutzer den Test machen möchte
295
+ if any(word in message_lower for word in ['ja', 'los', 'geht', 'gerne', 'okay', 'ok', 'klar', 'start', 'test']):
296
+ user_profile['question_count'] = 0
297
+ return questions[0] # Starte mit Frage 1/7 (Name)
298
+ # Prüfe ob Nutzer den Test überspringen möchte
299
+ elif any(word in message_lower for word in ['nicht', 'später', 'nein', 'skip', 'überspringen']):
300
+ user_profile['personalization_done'] = True
301
+ user_profile['test_skipped'] = True
302
+ return f"Kein Problem! Du kannst den Test jederzeit später machen, indem du mich einfach darum bittest.\n\nWie kann ich dir heute helfen?"
303
+ else:
304
+ # Unklare Antwort - frage nochmal
305
+ return f"Möchtest du den kurzen Fragebogen jetzt ausfüllen?\n\nAntworte einfach mit **\"Ja\"** um zu starten oder **\"Jetzt nicht\"** um direkt zu chatten."
306
+
307
+ # Speichere Antwort (nur wenn Test läuft)
308
+ if user_profile['question_count'] >= 0 and user_profile['question_count'] < len(questions):
309
+ user_profile['answers'].append(message)
310
+
311
+ # Nächste Frage oder Abschluss
312
+ user_profile['question_count'] += 1
313
+
314
+ if user_profile['question_count'] < len(questions):
315
+ return questions[user_profile['question_count']]
316
+ elif user_profile['question_count'] == len(questions):
317
+ # Alle Fragen beantwortet - generiere personalisierten Content DIREKT
318
+ user_info = {
319
+ "name": user_profile['answers'][0],
320
+ "chronotyp": user_profile['answers'][1],
321
+ "schlaf": user_profile['answers'][2],
322
+ "stressquellen": user_profile['answers'][3],
323
+ "erfahrungen": user_profile['answers'][4],
324
+ "zeit": user_profile['answers'][5],
325
+ "themen": user_profile['answers'][6]
326
+ }
327
+
328
+ # Generiere Schlafplan ZUERST (als Basis für konsistente Zeiten)
329
+ print("Generiere personalisierten Schlafplan...")
330
+ personalized_content["schlafplan"] = generate_personalized_content(user_info, "schlafplan")
331
+
332
+ # Dann Medien und Tools und Time Management PARALLEL mit Schlafplan-Kontext
333
+ print("Generiere Medien und Tools und Time Management parallel...")
334
+ with ThreadPoolExecutor(max_workers=2) as executor:
335
+ future_medien = executor.submit(generate_personalized_content, user_info, "mediensammlung", personalized_content["schlafplan"])
336
+ future_time = executor.submit(generate_personalized_content, user_info, "timemanagement", personalized_content["schlafplan"])
337
+
338
+ personalized_content["mediensammlung"] = future_medien.result()
339
+ personalized_content["timemanagement"] = future_time.result()
340
+
341
+ user_profile['personalization_done'] = True
342
+
343
+ return f"""**Fertig, {user_info['name']}!**
344
+
345
+ Deine personalisierten Inhalte sind jetzt verfügbar:
346
+
347
+ **Schlafplan** - Speziell für dich erstellt
348
+ **Medien und Tools** - Angepasst an deine Bedürfnisse
349
+ **Time Management** - Passend zu deinem Tagesrhythmus
350
+
351
+ **Schau sie dir in den entsprechenden Tabs an!**
352
+
353
+ Wie kann ich dir jetzt helfen?"""
354
+ else:
355
+ # Normaler Chat nach Personalisierung
356
+ return None # Signal für normalen Chat
357
+
358
+
359
+ def response(message, history):
360
+ """Generiert eine Antwort mit OpenAI Streaming und RAG"""
361
+ global user_profile
362
+
363
+ print(f"\n=== RESPONSE CALLED ===")
364
+ print(f"Message: {message}")
365
+ print(f"Question count: {user_profile.get('question_count', 'not set')}")
366
+ print(f"Personalization done: {user_profile.get('personalization_done', 'not set')}")
367
+
368
+ # Standard Tab-Werte (keine Änderung)
369
+ tab_unchanged = gr.update()
370
+
371
+ # Flag um AI-Aufruf zu verhindern
372
+ skip_ai = False
373
+
374
+ # Prüfe ob Personalisierung läuft
375
+ if 'personalization_done' not in user_profile or not user_profile['personalization_done']:
376
+ personalization_response = collect_user_info(message, history)
377
+ print(f"Personalization response: {personalization_response[:100] if personalization_response else 'None'}...")
378
+ if personalization_response:
379
+ skip_ai = True
380
+ # Nach der 7. Frage: Aktualisiere Tabs automatisch
381
+ if personalized_content.get("schlafplan"):
382
+ schlafplan_text = f'<div class="resource-content">\n\n{personalized_content["schlafplan"]}\n\n</div>'
383
+ medien_text = f'<div class="resource-content">\n\n{personalized_content["mediensammlung"]}\n\n</div>'
384
+ time_text = f'<div class="resource-content">\n\n{personalized_content["timemanagement"]}\n\n</div>'
385
+ yield personalization_response, schlafplan_text, medien_text, time_text
386
+ return
387
+ else:
388
+ yield personalization_response, tab_unchanged, tab_unchanged, tab_unchanged
389
+ return
390
+
391
+ # Normaler Chat-Modus
392
+ message_lower = message.lower()
393
+
394
+ # Prüfe ob User den Test später machen möchte (nur wenn noch nicht gemacht)
395
+ if user_profile.get('test_skipped') and any(word in message_lower for word in ['test', 'fragebogen', 'personalisier', 'fragen', 'profil']):
396
+ # Reset für Test - setze question_count zurück aber nicht auf 0 (sonst wird message als Antwort gespeichert)
397
+ user_profile.clear() # Lösche alles
398
+ user_profile['question_count'] = 0
399
+ user_profile['answers'] = []
400
+ user_profile['personalization_done'] = False
401
+
402
+ # Zeige die erste Frage (gleicher Test wie beim Start!)
403
+ first_q = "Hallo! Ich bin calm.i\n\nUm dir die bestmögliche Unterstützung zu bieten, würde ich dich gerne besser kennenlernen. Das dauert nur 2-3 Minuten.\n\n**Frage 1/7:** Wie heißt du? (Oder wie möchtest du genannt werden?)"
404
+ yield first_q, tab_unchanged, tab_unchanged, tab_unchanged
405
+ return
406
+
407
+ # Prüfe ob User Test wiederholen möchte (nachdem Test schon gemacht wurde)
408
+ if user_profile.get('personalization_done') and not user_profile.get('test_skipped'):
409
+ if any(word in message_lower for word in ['test wiederholen', 'fragebogen wiederholen', 'neu starten', 'test nochmal', 'von vorne']):
410
+ # Reset für Test-Wiederholung
411
+ user_profile.clear() # Lösche alles
412
+ user_profile['question_count'] = 0
413
+ user_profile['answers'] = []
414
+ user_profile['personalization_done'] = False
415
+
416
+ # Zeige die erste Frage (gleicher Test wie beim Start!)
417
+ first_q = "Hallo! Ich bin calm.i\n\nUm dir die bestmögliche Unterstützung zu bieten, würde ich dich gerne besser kennenlernen. Das dauert nur 2-3 Minuten.\n\n**Frage 1/7:** Wie heißt du? (Oder wie möchtest du genannt werden?)"
418
+ yield first_q, tab_unchanged, tab_unchanged, tab_unchanged
419
+ return
420
+
421
+ # Prüfe ob User Anpassungen an personalisierten Inhalten möchte
422
+ if user_profile.get('personalization_done') and user_profile.get('answers'):
423
+ # Erkenne welche Art von Änderung (auch ohne explizite Plan-Nennung)
424
+ mentions_name = any(word in message_lower for word in ['name', 'heiße', 'heisse', 'genannt', 'nenne'])
425
+ mentions_chronotyp = any(word in message_lower for word in ['morgenmensch', 'nachtmensch', 'eule', 'lerche', 'chronotyp'])
426
+ mentions_sleep = any(word in message_lower for word in ['schlaf', 'stunden', 'schlafenszeit', 'aufstehzeit', 'einschlafen', 'durchschlafen'])
427
+ mentions_stress = any(word in message_lower for word in ['stress', 'stressquell', 'arbeit', 'familie', 'studium'])
428
+ mentions_time_available = any(word in message_lower for word in ['verfügbar', 'minuten täglich', 'zeit für'])
429
+ mentions_topics = any(word in message_lower for word in ['thema', 'themen', 'fokus', 'unterstützung', 'hilfe bei'])
430
+
431
+ # Erkenne explizite Plan-Nennung
432
+ mentions_schlafplan = any(word in message_lower for word in ['schlafplan'])
433
+ mentions_medien = any(word in message_lower for word in ['mediensammlung', 'medien', 'musik', 'meditation', 'buch', 'bücher'])
434
+ mentions_timeplan = any(word in message_lower for word in ['time management', 'zeitmanagement', 'tagesplan'])
435
+
436
+ # Erkenne ob es eine Änderungsanfrage ist (erweitert!)
437
+ is_update_request = any(word in message_lower for word in ['änder', 'anpass', 'aktualisier', 'modifizier', 'neu', 'anders', 'update', 'jetzt', 'meinte', 'eigentlich', 'korrektur', 'falsch'])
438
+
439
+ # ODER: Wenn User direkt etwas über sich aussagt (z.B. "mein name ist", "ich bin")
440
+ is_direct_statement = any(phrase in message_lower for phrase in ['mein name ist', 'ich heiße', 'ich heisse', 'nenne mich', 'ich bin jetzt', 'ich schlafe jetzt'])
441
+
442
+ # Wenn eine Änderung erkannt wird
443
+ if (is_update_request or is_direct_statement) and (mentions_name or mentions_chronotyp or mentions_sleep or mentions_stress or mentions_time_available or mentions_topics or mentions_schlafplan or mentions_medien or mentions_timeplan):
444
+ # Baue user_info mit aktuellen Daten
445
+ user_info = {
446
+ "name": user_profile['answers'][0],
447
+ "chronotyp": user_profile['answers'][1],
448
+ "schlaf": user_profile['answers'][2],
449
+ "stressquellen": user_profile['answers'][3],
450
+ "erfahrungen": user_profile['answers'][4],
451
+ "zeit": user_profile['answers'][5],
452
+ "themen": user_profile['answers'][6]
453
+ }
454
+
455
+ # Entscheide welche Pläne aktualisiert werden müssen
456
+ update_schlafplan = False
457
+ update_medien = False
458
+ update_time = False
459
+
460
+ # Wenn spezifischer Plan genannt wird
461
+ if mentions_schlafplan:
462
+ update_schlafplan = True
463
+ elif mentions_medien:
464
+ update_medien = True
465
+ elif mentions_timeplan:
466
+ update_time = True
467
+ else:
468
+ # Automatische Zuordnung basierend auf Änderungstyp
469
+ if mentions_name:
470
+ # Name betrifft alle Pläne
471
+ update_schlafplan = update_medien = update_time = True
472
+ elif mentions_chronotyp or mentions_sleep:
473
+ # Chronotyp/Schlaf betrifft Schlafplan und Time Management
474
+ update_schlafplan = True
475
+ update_time = True
476
+ elif mentions_stress or mentions_topics:
477
+ # Stress/Themen betreffen Medien und Tools
478
+ update_medien = True
479
+ elif mentions_time_available:
480
+ # Verfügbare Zeit betrifft Time Management
481
+ update_time = True
482
+
483
+ # Aktualisiere die betroffenen Pläne
484
+ updated_plans = []
485
+
486
+ print(f"\n=== UPDATING PLANS ===")
487
+ print(f"Update Schlafplan: {update_schlafplan}")
488
+ print(f"Update Medien: {update_medien}")
489
+ print(f"Update Time: {update_time}")
490
+ print(f"User message: {message}")
491
+
492
+ # Zeige welche Pläne aktualisiert werden
493
+ plans_to_update = []
494
+ if update_schlafplan:
495
+ plans_to_update.append("Schlafplan")
496
+ if update_medien:
497
+ plans_to_update.append("Medien und Tools")
498
+ if update_time:
499
+ plans_to_update.append("Time Management")
500
+
501
+ if len(plans_to_update) > 1:
502
+ plans_list = ", ".join(plans_to_update)
503
+ yield f"Ich aktualisiere: **{plans_list}**. Das kann einen Moment dauern...", tab_unchanged, tab_unchanged, tab_unchanged
504
+ elif len(plans_to_update) == 1:
505
+ yield f"Ich aktualisiere deinen **{plans_to_update[0]}**...", tab_unchanged, tab_unchanged, tab_unchanged
506
+
507
+ if update_schlafplan:
508
+ print("Generiere Schlafplan...")
509
+ new_schlafplan = generate_personalized_content(user_info, "schlafplan", "", message)
510
+ print(f"Schlafplan generiert! Länge: {len(new_schlafplan)} Zeichen")
511
+ personalized_content["schlafplan"] = new_schlafplan
512
+ schlafplan_text = f'<div class="resource-content">\n\n{new_schlafplan}\n\n</div>'
513
+ updated_plans.append("Schlafplan")
514
+ # Zeige Fortschritt
515
+ if update_medien or update_time:
516
+ yield f"✓ Schlafplan fertig! Arbeite weiter...", schlafplan_text, tab_unchanged, tab_unchanged
517
+ else:
518
+ schlafplan_text = tab_unchanged
519
+
520
+ if update_medien:
521
+ print("Generiere Medien und Tools...")
522
+ new_medien = generate_personalized_content(user_info, "mediensammlung", personalized_content.get("schlafplan", ""), message)
523
+ print(f"Medien und Tools generiert! Länge: {len(new_medien)} Zeichen")
524
+ personalized_content["mediensammlung"] = new_medien
525
+ medien_text = f'<div class="resource-content">\n\n{new_medien}\n\n</div>'
526
+ updated_plans.append("Medien und Tools")
527
+ # Zeige Fortschritt
528
+ if update_time:
529
+ yield f"✓ Medien und Tools fertig! Arbeite weiter...", schlafplan_text, medien_text, tab_unchanged
530
+ else:
531
+ medien_text = tab_unchanged
532
+
533
+ if update_time:
534
+ print("Generiere Time Management...")
535
+ new_time = generate_personalized_content(user_info, "timemanagement", personalized_content.get("schlafplan", ""), message)
536
+ print(f"Time Management generiert! Länge: {len(new_time)} Zeichen")
537
+ personalized_content["timemanagement"] = new_time
538
+ time_text = f'<div class="resource-content">\n\n{new_time}\n\n</div>'
539
+ updated_plans.append("Time Management")
540
+ else:
541
+ time_text = tab_unchanged
542
+
543
+ # Ausgabe
544
+ if len(updated_plans) == 1:
545
+ response_msg = f"Dein **{updated_plans[0]}** wurde aktualisiert! Schau ihn dir im entsprechenden Tab an."
546
+ elif len(updated_plans) > 1:
547
+ plans_str = ", ".join(updated_plans[:-1]) + f" und {updated_plans[-1]}"
548
+ response_msg = f"Deine Pläne (**{plans_str}**) wurden aktualisiert! Schau sie dir in den entsprechenden Tabs an."
549
+ else:
550
+ response_msg = "Ich habe deine Anfrage verstanden, aber konnte keinen Plan zuordnen. Kannst du präziser sagen, welchen Plan ich anpassen soll?"
551
+
552
+ yield response_msg, schlafplan_text, medien_text, time_text
553
+ return
554
+
555
+ context = get_relevant_context(message, collection, n_results=CONTEXT_SIZE)
556
+
557
+ # Füge Nutzerprofil zum Kontext hinzu
558
+ user_context = ""
559
+ if user_profile.get('answers'):
560
+ user_context = f"""
561
+
562
+ NUTZERPROFIL:
563
+ - Name: {user_profile['answers'][0]}
564
+ - Chronotyp: {user_profile['answers'][1]}
565
+ - Schlaf: {user_profile['answers'][2]}
566
+ - Stressquellen: {user_profile['answers'][3]}
567
+ - Erfahrungen: {user_profile['answers'][4]}
568
+ - Verfügbare Zeit: {user_profile['answers'][5]}
569
+ - Fokusthemen: {user_profile['answers'][6]}
570
+ """
571
+
572
+ system_prompt = f"""Du bist calm.i, ein einfühlsamer Chatbot für Stress- und Selbstfürsorge.
573
+
574
+ KONTEXT:
575
+ ---------------------
576
+ {context}
577
+ ---------------------
578
+ {user_context}
579
+
580
+ KRITISCH - HÖCHSTE PRIORITÄT:
581
+ Falls es sich um einen AKUTEN NOTFALL wie eine Panikattacke handelt:
582
+ - Antworte SOFORT mit konkreten Lösungsvorschlägen und begrüße den Nutzer NICHT
583
+ - KEINE einleitende Empathie
584
+ - verweise auf die Notfallnummern,
585
+ - MAXIMAL 3 kurze, direkte Handlungsanweisungen
586
+ - Erst danach kannst du einfühlsam nachfragen
587
+ - Falls es sich um ein Thema handelt, welches sich nicht direkt
588
+ um Stress, Schlaf, Panik oder andere mentale Probleme handelt:
589
+ erzähle, dass du für dieses Thema nicht zuständig bist
590
+
591
+ DEINE ROLLE
592
+ - Du kennst den Nutzer bereits durch das Profil
593
+ - Beziehe dich auf seine spezifische Situation
594
+ - Nutze seinen Namen und seine Präferenzen
595
+ - Du bist eine ruhige, verständnisvolle Begleitung
596
+
597
+ KLARE GRENZEN
598
+ - Du bist KEIN Ersatz für Therapie oder ärztliche Behandlung
599
+ - Bei Lebensgefahr: Telefonseelsorge 0800 1110111, Notruf 112
600
+
601
+ SPRACHE & STIL
602
+ - Deutsch, Du-Form
603
+ - verwende empathische Ausdrücke, welche variieren
604
+ - Warm, ruhig, wertschätzend
605
+ - Kurze, klare Sätze
606
+ - Validiere Gefühle
607
+ """
608
+
609
+ # WICHTIG: Wenn Personalisierung läuft, AI NICHT aufrufen!
610
+ if 'personalization_done' not in user_profile or not user_profile['personalization_done']:
611
+ print("ERROR: AI sollte nicht aufgerufen werden während Personalisierung läuft!")
612
+ return
613
+
614
+ messages = [{"role": "system", "content": system_prompt}]
615
+
616
+ for msg in history:
617
+ if msg["role"] == "user":
618
+ messages.append({"role": "user", "content": msg["content"]})
619
+ elif msg["role"] == "assistant":
620
+ messages.append({"role": "assistant", "content": msg["content"]})
621
+
622
+ messages.append({"role": "user", "content": message})
623
+
624
+ stream = client.chat.completions.create(
625
+ model="gpt-4o-mini",
626
+ messages=messages,
627
+ temperature=0.1,
628
+ stream=True,
629
+ stream_options={"include_usage": True}
630
+ )
631
+
632
+ answer = ""
633
+
634
+ for chunk in stream:
635
+ if hasattr(chunk, 'usage') and chunk.usage:
636
+ print(f"\nToken-Usage: {chunk.usage.total_tokens}")
637
+
638
+ if chunk.choices and len(chunk.choices) > 0 and chunk.choices[0].delta.content:
639
+ text = chunk.choices[0].delta.content
640
+ time.sleep(0.05)
641
+ answer += text
642
+ yield answer, tab_unchanged, tab_unchanged, tab_unchanged
643
+
644
+
645
+ # VectorDB beim Start initialisieren
646
+ print("Initialisiere VectorDB...")
647
+ try:
648
+ collection = chroma_client.get_collection(
649
+ name="documents",
650
+ embedding_function=openai_ef
651
+ )
652
+ print(f"VectorDB geladen mit {collection.count()} Chunks")
653
+ except:
654
+ print("Erstelle neue VectorDB und lade Dokumente...")
655
+ collection = load_documents_to_vectordb()
656
+
657
+
658
+ theme = CustomTheme()
659
+
660
+ def main():
661
+ import base64
662
+
663
+ background_image_base64 = ""
664
+ try:
665
+ with open("./assets/background_image/hintergrund.jpg", "rb") as img_file:
666
+ background_image_base64 = base64.b64encode(img_file.read()).decode('utf-8')
667
+ print("Hintergrundbild geladen")
668
+ except FileNotFoundError:
669
+ print("Warnung: hintergrund.jpg nicht gefunden")
670
+
671
+ home_screen_base64 = ""
672
+ try:
673
+ with open("./assets/home_screen/home_screen.png", "rb") as img_file:
674
+ home_screen_base64 = base64.b64encode(img_file.read()).decode('utf-8')
675
+ print("Home-Screen Bild geladen")
676
+ except FileNotFoundError:
677
+ print("Warnung: home_screen.png nicht gefunden")
678
+
679
+ logo_base64 = ""
680
+ try:
681
+ with open("./assets/logo/logo.png", "rb") as logo_file:
682
+ logo_base64 = base64.b64encode(logo_file.read()).decode('utf-8')
683
+ print("Logo geladen")
684
+ except FileNotFoundError:
685
+ print("Warnung: logo.png nicht gefunden")
686
+
687
+ # Erstelle Logo-HTML für Inline-Verwendung
688
+ global logo_inline_html
689
+ logo_inline_html = f'<img src="data:image/png;base64,{logo_base64}" class="logo-inline" alt="">' if logo_base64 else ""
690
+
691
+ # Lade Tab-Icons
692
+ tab_icons = {}
693
+ icon_names = ["home", "chat", "schlafplan", "mediensammlung", "timemanagement", "notfallplan"]
694
+ for icon_name in icon_names:
695
+ try:
696
+ with open(f"./assets/tab_icons/{icon_name}.png", "rb") as icon_file:
697
+ tab_icons[icon_name] = base64.b64encode(icon_file.read()).decode('utf-8')
698
+ print(f"Icon '{icon_name}' geladen")
699
+ except FileNotFoundError:
700
+ print(f"Warnung: {icon_name}.png nicht gefunden")
701
+ tab_icons[icon_name] = ""
702
+
703
+ custom_css = f"""
704
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700&display=swap');
705
+
706
+ [class*="gradio-container"] {{
707
+ background: url("data:image/jpeg;base64,{background_image_base64}") no-repeat center center fixed !important;
708
+ background-size: cover !important;
709
+ }}
710
+
711
+ /* Home-Tab: Vollbild-Bild */
712
+ .home-screen-container {{
713
+ position: fixed !important;
714
+ top: 0 !important;
715
+ left: 0 !important;
716
+ width: 100vw !important;
717
+ height: 100vh !important;
718
+ z-index: 50 !important;
719
+ overflow: visible !important;
720
+ pointer-events: none !important;
721
+ }}
722
+
723
+ .home-screen-image {{
724
+ width: 100% !important;
725
+ height: 100% !important;
726
+ object-fit: cover !important;
727
+ pointer-events: none !important;
728
+ }}
729
+
730
+ .home-chat-button {{
731
+ pointer-events: auto !important;
732
+ }}
733
+
734
+ #home {{
735
+ position: relative !important;
736
+ z-index: 1 !important;
737
+ }}
738
+
739
+ /* Tab-Menü über dem Bild und klickbar */
740
+ .tabs, [role="tablist"] {{
741
+ position: relative !important;
742
+ z-index: 100 !important;
743
+ pointer-events: auto !important;
744
+ }}
745
+
746
+ /* Home-Tab Chat-Button */
747
+ .home-chat-button {{
748
+ position: fixed !important;
749
+ bottom: -670px !important;
750
+ left: 63.5% !important;
751
+ transform: translateX(-50%) !important;
752
+ font-family: 'Nunito', sans-serif !important;
753
+ font-weight: 700 !important;
754
+ font-size: 18px !important;
755
+ color: #805EA4 !important;
756
+ background-color: #FBF8FC !important;
757
+ border: none !important;
758
+ border-radius: 15px !important;
759
+ padding: 15px 30px !important;
760
+ cursor: pointer !important;
761
+ z-index: 200 !important;
762
+ pointer-events: auto !important;
763
+ display: block !important;
764
+ visibility: visible !important;
765
+ transition: background-color 0.3s ease !important;
766
+ }}
767
+
768
+ .home-chat-button:hover {{
769
+ background-color: #7E5A9B !important;
770
+ color: #FFFFFF !important;
771
+ }}
772
+
773
+ footer {{
774
+ display: none !important;
775
+ visibility: hidden !important;
776
+ opacity: 0 !important;
777
+ }}
778
+
779
+ .footer {{
780
+ display: none !important;
781
+ }}
782
+
783
+ div.gradio-container footer,
784
+ div.gradio-container .footer,
785
+ [class*="footer"] {{
786
+ display: none !important;
787
+ visibility: hidden !important;
788
+ height: 0 !important;
789
+ overflow: hidden !important;
790
+ }}
791
+
792
+ .gradio-container a[href*="gradio"] {{
793
+ display: none !important;
794
+ }}
795
+
796
+ .contain a {{
797
+ display: none !important;
798
+ }}
799
+
800
+ footer svg {{
801
+ display: none !important;
802
+ }}
803
+
804
+ .gradio-container > div > div:last-child {{
805
+ display: none !important;
806
+ }}
807
+
808
+ [class*="Footer"] {{
809
+ display: none !important;
810
+ }}
811
+
812
+ .wrap.svelte-* footer {{
813
+ display: none !important;
814
+ }}
815
+
816
+ .footer-wrapper,
817
+ .gradio-footer,
818
+ #footer,
819
+ [data-testid="footer"] {{
820
+ display: none !important;
821
+ }}
822
+
823
+ #logo-container {{
824
+ position: fixed !important;
825
+ top: 50px !important;
826
+ left: 20px !important;
827
+ z-index: 2000 !important;
828
+ background: white !important;
829
+ border-radius: 50% !important;
830
+ padding: 1px !important;
831
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
832
+ }}
833
+
834
+ #logo-container img {{
835
+ width: 80px !important;
836
+ height: 80px !important;
837
+ border-radius: 50% !important;
838
+ object-fit: cover !important;
839
+ display: block !important;
840
+ }}
841
+
842
+ #CHATBOT {{
843
+ background: transparent !important;
844
+ backdrop-filter: none !important;
845
+ border: none !important;
846
+ box-shadow: none !important;
847
+ border-radius: 25px !important;
848
+ margin-top: 20px !important;
849
+ }}
850
+
851
+ #CHATBOT .message-buttons,
852
+ #CHATBOT .action-buttons,
853
+ #CHATBOT button[title="Copy message"],
854
+ #CHATBOT button[title="Retry"],
855
+ #CHATBOT button[title="Undo"],
856
+ .message-row button,
857
+ .message .copy-button,
858
+ .message-buttons-row,
859
+ #CHATBOT .chatbot-header button,
860
+ .chatbot-header button,
861
+ [class*="chatbot"] button[aria-label],
862
+ button[title="Clear"],
863
+ button[title*="Copy"],
864
+ .chatbot-controls,
865
+ #component-* button[aria-label="Clear"],
866
+ #component-* button[aria-label*="Copy"],
867
+ .chatbot button,
868
+ #CHATBOT ~ * button,
869
+ [id^="component-"] button[aria-label] {{
870
+ display: none !important;
871
+ visibility: hidden !important;
872
+ opacity: 0 !important;
873
+ pointer-events: none !important;
874
+ }}
875
+
876
+ .message.bot {{
877
+ background: #7E5A9B !important;
878
+ font-family: 'Nunito', sans-serif !important;
879
+ font-weight: 700 !important;
880
+ color: #F4F1F7 !important;
881
+ font-size: 16px !important;
882
+ border-radius: 25px !important;
883
+ padding: 15px 20px !important;
884
+ }}
885
+ .message.bot p, .message.bot span, .message.bot div, .message.bot strong, .message.bot b {{
886
+ font-family: 'Nunito', sans-serif !important;
887
+ font-weight: 700 !important;
888
+ color: #F4F1F7 !important;
889
+ font-size: 16px !important;
890
+ }}
891
+ .message.bot * {{
892
+ color: #F4F1F7 !important;
893
+ }}
894
+ .message.user {{
895
+ background: #7E5A9B !important;
896
+ font-family: 'Nunito', sans-serif !important;
897
+ font-weight: 700 !important;
898
+ color: #F4F1F7 !important;
899
+ font-size: 16px !important;
900
+ border-radius: 25px !important;
901
+ padding: 15px 20px !important;
902
+ }}
903
+ .message.user p, .message.user span, .message.user div {{
904
+ font-family: 'Nunito', sans-serif !important;
905
+ font-weight: 700 !important;
906
+ color: #F4F1F7 !important;
907
+ font-size: 16px !important;
908
+ }}
909
+
910
+ textarea, input[type="text"], .input-text {{
911
+ font-family: 'Nunito', sans-serif !important;
912
+ font-weight: 700 !important;
913
+ color: #2F2F2F !important;
914
+ font-size: 16px !important;
915
+ border-radius: 25px !important;
916
+ }}
917
+
918
+ .textbox textarea {{
919
+ font-family: 'Nunito', sans-serif !important;
920
+ font-weight: 700 !important;
921
+ color: #2F2F2F !important;
922
+ font-size: 16px !important;
923
+ border-radius: 25px !important;
924
+ }}
925
+
926
+ .avatar-container img {{
927
+ object-fit: cover !important;
928
+ width: 100% !important;
929
+ height: 100% !important;
930
+ transform: scale(1.5) !important;
931
+ }}
932
+ img.avatar-image {{
933
+ object-fit: cover !important;
934
+ width: 100% !important;
935
+ height: 100% !important;
936
+ transform: scale(1.5) !important;
937
+ }}
938
+ .message img {{
939
+ object-fit: cover !important;
940
+ width: 100% !important;
941
+ height: 100% !important;
942
+ transform: scale(1.5) !important;
943
+ }}
944
+
945
+ .resource-content {{
946
+ background: rgba(255, 255, 255, 0.95) !important;
947
+ padding: 30px !important;
948
+ border-radius: 25px !important;
949
+ margin: 20px auto !important;
950
+ max-width: 800px !important;
951
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
952
+ }}
953
+
954
+ .resource-content h1, .resource-content h2, .resource-content h3 {{
955
+ font-family: 'Nunito', sans-serif !important;
956
+ color: #411F61 !important;
957
+ }}
958
+
959
+ .resource-content p, .resource-content li {{
960
+ font-family: 'Nunito', sans-serif !important;
961
+ font-size: 16px !important;
962
+ line-height: 1.6 !important;
963
+ color: #2F2F2F !important;
964
+ }}
965
+
966
+ /* Accordion/Dropdown Styling */
967
+ .crisis-dropdown {{
968
+ margin: 15px 0 !important;
969
+ border: 2px solid #7E5A9B !important;
970
+ border-radius: 15px !important;
971
+ overflow: hidden !important;
972
+ background: #FBF8FC !important;
973
+ }}
974
+
975
+ .crisis-dropdown summary {{
976
+ padding: 15px 20px !important;
977
+ font-family: 'Nunito', sans-serif !important;
978
+ font-weight: 700 !important;
979
+ font-size: 18px !important;
980
+ color: #411F61 !important;
981
+ cursor: pointer !important;
982
+ list-style: none !important;
983
+ background: #FBF8FC !important;
984
+ transition: background-color 0.3s ease !important;
985
+ }}
986
+
987
+ .crisis-dropdown summary::-webkit-details-marker {{
988
+ display: none !important;
989
+ }}
990
+
991
+ .crisis-dropdown summary::before {{
992
+ content: '▸' !important;
993
+ display: inline-block !important;
994
+ margin-right: 12px !important;
995
+ color: #7E5A9B !important;
996
+ font-size: 22px !important;
997
+ line-height: 1 !important;
998
+ transition: transform 0.3s ease, color 0.3s ease !important;
999
+ transform-origin: center !important;
1000
+ }}
1001
+
1002
+ .crisis-dropdown[open] summary::before {{
1003
+ content: '▾' !important;
1004
+ }}
1005
+
1006
+ .crisis-dropdown summary:hover::before {{
1007
+ color: #FFFFFF !important;
1008
+ }}
1009
+
1010
+ .crisis-dropdown summary:hover {{
1011
+ background: #7E5A9B !important;
1012
+ color: #FFFFFF !important;
1013
+ }}
1014
+
1015
+ .crisis-dropdown .dropdown-content {{
1016
+ padding: 20px !important;
1017
+ background: #FFFFFF !important;
1018
+ border-top: 2px solid #7E5A9B !important;
1019
+ }}
1020
+
1021
+ .crisis-dropdown .dropdown-content p,
1022
+ .crisis-dropdown .dropdown-content li {{
1023
+ margin: 8px 0 !important;
1024
+ }}
1025
+
1026
+ html {{
1027
+ overflow-y: scroll !important;
1028
+ }}
1029
+
1030
+ body {{
1031
+ overflow-y: scroll !important;
1032
+ }}
1033
+
1034
+ button.primary {{
1035
+ background: #7E5A9B !important;
1036
+ border-color: #7E5A9B !important;
1037
+ }}
1038
+
1039
+ button.primary:hover {{
1040
+ background: #6B3FA0 !important;
1041
+ border-color: #6B3FA0 !important;
1042
+ }}
1043
+
1044
+ /* Tab-Container: Entfernt Gradio-Standard-Borders und erstellt Stacking Context */
1045
+ .tabs {{
1046
+ border-bottom: none !important;
1047
+ border: none !important;
1048
+ box-shadow: none !important;
1049
+ position: relative !important;
1050
+ }}
1051
+
1052
+ [class*="tabs"] {{
1053
+ border-bottom: none !important;
1054
+ border: none !important;
1055
+ box-shadow: none !important;
1056
+ }}
1057
+
1058
+ div[role="tablist"] {{
1059
+ border-bottom: none !important;
1060
+ border: none !important;
1061
+ box-shadow: none !important;
1062
+ position: relative !important;
1063
+ }}
1064
+
1065
+ .tab-nav {{
1066
+ border-bottom: none !important;
1067
+ box-shadow: none !important;
1068
+ }}
1069
+
1070
+ [class*="tab"][class*="nav"] {{
1071
+ border-bottom: none !important;
1072
+ box-shadow: none !important;
1073
+ }}
1074
+
1075
+ .gradio-tabs {{
1076
+ border-bottom: none !important;
1077
+ box-shadow: none !important;
1078
+ }}
1079
+
1080
+ .tab-container {{
1081
+ border-bottom: none !important;
1082
+ border: none !important;
1083
+ box-shadow: none !important;
1084
+ }}
1085
+
1086
+ [class*="tab-container"] {{
1087
+ border-bottom: none !important;
1088
+ border: none !important;
1089
+ box-shadow: none !important;
1090
+ }}
1091
+
1092
+ /* Svelte-generierte Tab-Klassen */
1093
+ [class*="svelte"][role="tablist"],
1094
+ div[role="tablist"][class*="svelte"] {{
1095
+ border-bottom: none !important;
1096
+ border: none !important;
1097
+ box-shadow: none !important;
1098
+ }}
1099
+
1100
+ /* Alle Elemente im Tab-Bereich */
1101
+ .tabs > div,
1102
+ .tabs > div > div,
1103
+ .gradio-tabs > div {{
1104
+ border-bottom: none !important;
1105
+ border: none !important;
1106
+ box-shadow: none !important;
1107
+ }}
1108
+
1109
+ /* Gradio Tab-Nav Wrapper */
1110
+ .tab-nav > div,
1111
+ [class*="tab-nav"] {{
1112
+ border-bottom: none !important;
1113
+ border: none !important;
1114
+ box-shadow: none !important;
1115
+ }}
1116
+
1117
+ .tabs button {{
1118
+ border-bottom: none !important;
1119
+ position: relative !important;
1120
+ }}
1121
+
1122
+ .tabs button[aria-selected="true"] {{
1123
+ border-bottom: none !important;
1124
+ }}
1125
+
1126
+ .tabs button[aria-selected="true"]::after {{
1127
+ content: '' !important;
1128
+ position: absolute !important;
1129
+ bottom: 0 !important;
1130
+ left: 0 !important;
1131
+ right: 0 !important;
1132
+ height: 3px !important;
1133
+ background: #7b5ea0 !important;
1134
+ border-radius: 3px !important;
1135
+ z-index: 2 !important;
1136
+ }}
1137
+
1138
+ * {{
1139
+ --color-accent: #7b5ea0 !important;
1140
+ --color-accent-soft: #6B3FA0 !important;
1141
+ }}
1142
+
1143
+ #load-status {{
1144
+ text-align: center !important;
1145
+ padding: 10px !important;
1146
+ font-family: 'Nunito', sans-serif !important;
1147
+ font-size: 18px !important;
1148
+ font-weight: 700 !important;
1149
+ color: #7E5A9B !important;
1150
+ margin-top: 10px !important;
1151
+ }}
1152
+
1153
+ /* Tab Icons */
1154
+ .tabs button {{
1155
+ display: flex !important;
1156
+ align-items: center !important;
1157
+ gap: 8px !important;
1158
+ font-family: 'Nunito', sans-serif !important;
1159
+ color: #7E5A9B !important;
1160
+ }}
1161
+
1162
+ .tab-icon {{
1163
+ width: 20px !important;
1164
+ height: 20px !important;
1165
+ display: inline-block !important;
1166
+ margin-right: 4px !important;
1167
+ background: transparent !important;
1168
+ background-color: transparent !important;
1169
+ }}
1170
+
1171
+ .logo-inline {{
1172
+ width: 32px !important;
1173
+ height: 32px !important;
1174
+ display: inline-block !important;
1175
+ vertical-align: text-top !important;
1176
+ margin: 0 2px !important;
1177
+ border-radius: 50% !important;
1178
+ }}
1179
+
1180
+ #CHATBOT .logo-inline,
1181
+ .message .logo-inline,
1182
+ .chatbot .logo-inline {{
1183
+ width: 16px !important;
1184
+ height: 16px !important;
1185
+ vertical-align: middle !important;
1186
+ margin: 0 6px !important;
1187
+ border: 0.5px solid white !important;
1188
+ }}
1189
+
1190
+ /* Chat-Tab: Chatfenster Layout */
1191
+ #chat {{
1192
+ display: flex !important;
1193
+ flex-direction: column !important;
1194
+ height: calc(100vh - 120px) !important;
1195
+ position: relative !important;
1196
+ }}
1197
+
1198
+ /* Chatbot-Nachrichten Bereich scrollbar */
1199
+ #CHATBOT {{
1200
+ flex: 1 !important;
1201
+ overflow-y: auto !important;
1202
+ margin-bottom: 100px !important;
1203
+ max-height: calc(100vh - 180px) !important;
1204
+ padding-bottom: 80px !important;
1205
+ }}
1206
+
1207
+ /* Chat-Nachrichten Container - mehr Platz für Nachrichten */
1208
+ .bubble-wrap {{
1209
+ max-height: none !important;
1210
+ height: auto !important;
1211
+ overflow-y: auto !important;
1212
+ padding-bottom: 100px !important;
1213
+ }}
1214
+
1215
+ [role="log"] {{
1216
+ max-height: none !important;
1217
+ height: auto !important;
1218
+ overflow-y: auto !important;
1219
+ padding-bottom: 100px !important;
1220
+ }}
1221
+
1222
+ /* Eingabefeld: Entferne weißen Strich */
1223
+ .gr-group,
1224
+ .gr-group *,
1225
+ .gr-group::before,
1226
+ .gr-group::after,
1227
+ .gr-group .styler,
1228
+ .gr-group .styler::before,
1229
+ .gr-group .styler::after {{
1230
+ border-top: none !important;
1231
+ border-color: transparent !important;
1232
+ box-shadow: none !important;
1233
+ }}
1234
+
1235
+ /* Tab-Menü: Entferne weißen Strich unter den Tabs */
1236
+ .tabs,
1237
+ .tabs *,
1238
+ .tabs::before,
1239
+ .tabs::after,
1240
+ .tabitem,
1241
+ .tabitem::before,
1242
+ .tabitem::after,
1243
+ .tab-nav,
1244
+ .tab-nav::before,
1245
+ .tab-nav::after,
1246
+ .tab-nav *,
1247
+ [role="tablist"],
1248
+ [role="tablist"]::before,
1249
+ [role="tablist"]::after,
1250
+ .svelte-1p9262q,
1251
+ .svelte-1p9262q::before,
1252
+ .svelte-1p9262q::after,
1253
+ .svelte-7xavid,
1254
+ .svelte-7xavid::before,
1255
+ .svelte-7xavid::after,
1256
+ div[class*="svelte"]::before,
1257
+ div[class*="svelte"]::after {{
1258
+ border: none !important;
1259
+ border-top: none !important;
1260
+ border-bottom: none !important;
1261
+ border-color: transparent !important;
1262
+ box-shadow: none !important;
1263
+ }}
1264
+
1265
+ /* Entferne alle horizontalen Linien/Striche */
1266
+ hr, .divider, [class*="divider"], [class*="separator"] {{
1267
+ display: none !important;
1268
+ border: none !important;
1269
+ background: transparent !important;
1270
+ }}
1271
+
1272
+ /* Gradio Tab Container - alle Borders entfernen */
1273
+ .gradio-container *::before,
1274
+ .gradio-container *::after {{
1275
+ border: none !important;
1276
+ border-color: transparent !important;
1277
+ }}
1278
+
1279
+ /* Spezifisch für Tab-Buttons */
1280
+ button[role="tab"],
1281
+ button[role="tab"]::before,
1282
+ button[role="tab"]::after {{
1283
+ border: none !important;
1284
+ border-bottom: none !important;
1285
+ box-shadow: none !important;
1286
+ }}
1287
+ """
1288
+
1289
+ custom_js = f"""
1290
+ function removeFooter() {{
1291
+ const footers = document.querySelectorAll('footer, .footer, [class*="footer"], [class*="Footer"]');
1292
+ footers.forEach(footer => {{
1293
+ footer.style.display = 'none';
1294
+ footer.style.visibility = 'hidden';
1295
+ footer.remove();
1296
+ }});
1297
+
1298
+ const gradioLinks = document.querySelectorAll('a[href*="gradio"]');
1299
+ gradioLinks.forEach(link => {{
1300
+ link.style.display = 'none';
1301
+ link.remove();
1302
+ }});
1303
+ }}
1304
+
1305
+ function removeChatButtons() {{
1306
+ const chatButtons = document.querySelectorAll(`
1307
+ #CHATBOT button,
1308
+ .chatbot button,
1309
+ button[aria-label="Clear"],
1310
+ button[aria-label*="Copy"],
1311
+ button[title="Clear"],
1312
+ button[title*="Copy"],
1313
+ [id*="chatbot"] button[aria-label],
1314
+ [id*="component-"] button[aria-label]
1315
+ `);
1316
+ chatButtons.forEach(btn => {{
1317
+ if (!btn.closest('form')) {{
1318
+ btn.style.display = 'none';
1319
+ btn.style.visibility = 'hidden';
1320
+ btn.style.opacity = '0';
1321
+ btn.remove();
1322
+ }}
1323
+ }});
1324
+ }}
1325
+
1326
+ // Home-Tab Hintergrundbild setzen
1327
+ const homeScreenBase64 = '{home_screen_base64}';
1328
+
1329
+ function setHomeBackground() {{
1330
+ // Versuche verschiedene Selektoren
1331
+ let homeTab = document.querySelector('#home');
1332
+
1333
+ if (!homeTab) {{
1334
+ // Fallback: Finde Tab-Panel mit id="home"
1335
+ homeTab = document.querySelector('[id="home"]');
1336
+ }}
1337
+
1338
+ if (homeTab && homeScreenBase64) {{
1339
+ homeTab.style.background = 'url("data:image/png;base64,' + homeScreenBase64 + '") no-repeat center center';
1340
+ homeTab.style.backgroundSize = 'cover';
1341
+ homeTab.style.minHeight = 'calc(100vh - 100px)';
1342
+ homeTab.style.position = 'relative';
1343
+
1344
+ // Auch den gradio-container Hintergrund für Home überschreiben
1345
+ const container = document.querySelector('[class*="gradio-container"]');
1346
+ const homeButton = document.querySelector('button[id*="home"]');
1347
+
1348
+ // Prüfe ob Home-Tab aktiv ist
1349
+ if (homeButton && homeButton.classList.contains('selected')) {{
1350
+ if (container) {{
1351
+ container.style.background = 'url("data:image/png;base64,' + homeScreenBase64 + '") no-repeat center center fixed';
1352
+ container.style.backgroundSize = 'cover';
1353
+ }}
1354
+ }}
1355
+ }}
1356
+
1357
+ console.log('Home Tab gefunden:', homeTab);
1358
+ }}
1359
+
1360
+ // Tab-Wechsel beobachten und Hintergrund wechseln
1361
+ const defaultBackground = 'url("data:image/jpeg;base64,{background_image_base64}") no-repeat center center fixed';
1362
+
1363
+ function observeTabChanges() {{
1364
+ const tabs = document.querySelectorAll('[role="tab"]');
1365
+ const container = document.querySelector('[class*="gradio-container"]');
1366
+
1367
+ tabs.forEach(tab => {{
1368
+ tab.addEventListener('click', () => {{
1369
+ setTimeout(() => {{
1370
+ const isHomeTab = tab.textContent.includes('Home') || tab.id.includes('home');
1371
+ if (container) {{
1372
+ if (isHomeTab) {{
1373
+ container.style.background = 'url("data:image/png;base64,' + homeScreenBase64 + '") no-repeat center center fixed';
1374
+ }} else {{
1375
+ container.style.background = defaultBackground;
1376
+ }}
1377
+ container.style.backgroundSize = 'cover';
1378
+ }}
1379
+ }}, 50);
1380
+ }});
1381
+ }});
1382
+ }}
1383
+
1384
+ function fixChatInputPosition() {{
1385
+ // Finde das Textarea im Chat
1386
+ const textarea = document.querySelector('textarea[placeholder="Type a message..."]');
1387
+ if (!textarea) return;
1388
+
1389
+ // Finde den nächsten Container mit class "gr-group" (das ist nur die Eingabezeile)
1390
+ let container = textarea.closest('.gr-group');
1391
+ if (!container) {{
1392
+ // Fallback: gehe nur 4 Ebenen hoch (nur bis zur Eingabezeile)
1393
+ container = textarea;
1394
+ for (let i = 0; i < 4; i++) {{
1395
+ if (container.parentElement) {{
1396
+ container = container.parentElement;
1397
+ }}
1398
+ }}
1399
+ }}
1400
+
1401
+ // Style nur den Eingabe-Container
1402
+ container.style.position = 'fixed';
1403
+ container.style.bottom = '15px';
1404
+ container.style.left = '240px';
1405
+ container.style.right = 'auto';
1406
+ container.style.width = 'calc(100% - 460px)';
1407
+ container.style.background = 'transparent';
1408
+ container.style.padding = '15px 20px';
1409
+ container.style.zIndex = '1000';
1410
+
1411
+ // Mache das Textarea größer
1412
+ textarea.style.fontSize = '16px';
1413
+ textarea.style.padding = '12px 15px';
1414
+ textarea.style.minHeight = '50px';
1415
+
1416
+ // Mache Container-Elemente transparent (aber nicht das Eingabefeld selbst)
1417
+ const styler = container.querySelector('.styler');
1418
+ if (styler) {{
1419
+ styler.style.background = 'transparent';
1420
+ styler.style.border = 'none';
1421
+ styler.style.boxShadow = 'none';
1422
+ styler.style.setProperty('--layout-gap', '0px');
1423
+ styler.style.setProperty('--form-gap-width', '0px');
1424
+ }}
1425
+
1426
+ const row = container.querySelector('.row');
1427
+ if (row) {{
1428
+ row.style.background = 'transparent';
1429
+ row.style.border = 'none';
1430
+ row.style.boxShadow = 'none';
1431
+ }}
1432
+
1433
+ const form = container.querySelector('.form');
1434
+ if (form) {{
1435
+ form.style.background = 'transparent';
1436
+ form.style.border = 'none';
1437
+ form.style.boxShadow = 'none';
1438
+ }}
1439
+
1440
+ const block = container.querySelector('.block');
1441
+ if (block) {{
1442
+ block.style.background = 'transparent';
1443
+ block.style.border = 'none';
1444
+ block.style.boxShadow = 'none';
1445
+ }}
1446
+
1447
+ const label = container.querySelector('label');
1448
+ if (label) {{
1449
+ label.style.background = 'transparent';
1450
+ label.style.border = 'none';
1451
+ label.style.boxShadow = 'none';
1452
+ }}
1453
+
1454
+ // Passe Chatbot-Höhe an
1455
+ const chatbot = document.querySelector('#CHATBOT');
1456
+ if (chatbot) {{
1457
+ chatbot.style.maxHeight = 'calc(100vh - 80px)';
1458
+ chatbot.style.height = 'calc(100vh - 80px)';
1459
+ chatbot.style.marginBottom = '0px';
1460
+ chatbot.style.paddingBottom = '120px';
1461
+ chatbot.style.overflowY = 'auto';
1462
+ }}
1463
+
1464
+ // Passe die bubble-wrap (Nachrichten-Container) Höhe an
1465
+ const bubbleWrap = document.querySelector('.bubble-wrap');
1466
+ if (bubbleWrap) {{
1467
+ bubbleWrap.style.maxHeight = 'calc(100vh - 80px)';
1468
+ bubbleWrap.style.height = 'auto';
1469
+ bubbleWrap.style.overflowY = 'auto';
1470
+ bubbleWrap.style.paddingBottom = '140px';
1471
+ }}
1472
+
1473
+ // Passe role="log" Container an
1474
+ const logContainer = document.querySelector('[role="log"]');
1475
+ if (logContainer) {{
1476
+ logContainer.style.maxHeight = 'calc(100vh - 80px)';
1477
+ logContainer.style.paddingBottom = '140px';
1478
+ logContainer.style.overflowY = 'auto';
1479
+ }}
1480
+ }}
1481
+
1482
+ function addTabIcons() {{
1483
+ const tabIcons = {{
1484
+ 'Home': '{tab_icons.get("home", "")}',
1485
+ 'Chat': '{tab_icons.get("chat", "")}',
1486
+ 'Schlafplan': '{tab_icons.get("schlafplan", "")}',
1487
+ 'Medien und Tools': '{tab_icons.get("mediensammlung", "")}',
1488
+ 'Time Management': '{tab_icons.get("timemanagement", "")}',
1489
+ 'Notfallplan': '{tab_icons.get("notfallplan", "")}'
1490
+ }};
1491
+
1492
+ const tabs = document.querySelectorAll('.tabs button');
1493
+ tabs.forEach(tab => {{
1494
+ const tabText = tab.textContent.trim();
1495
+
1496
+ if (tabIcons[tabText] && tabIcons[tabText] !== '' && !tab.querySelector('.tab-icon')) {{
1497
+ const icon = document.createElement('img');
1498
+ icon.className = 'tab-icon';
1499
+ icon.src = 'data:image/png;base64,' + tabIcons[tabText];
1500
+ tab.insertBefore(icon, tab.firstChild);
1501
+ }}
1502
+ }});
1503
+ }}
1504
+
1505
+ function changeTabColors() {{
1506
+ const tabs = document.querySelectorAll('.tabs button');
1507
+ tabs.forEach(tab => {{
1508
+ if (tab.hasAttribute('style')) {{
1509
+ const style = tab.getAttribute('style');
1510
+ const newStyle = style.replace(/border-bottom-color:\\s*rgb\\(255,\\s*165,\\s*0\\)/g, 'border-bottom-color: #411F61')
1511
+ .replace(/border-bottom-color:\\s*orange/g, 'border-bottom-color: #411F61');
1512
+ tab.setAttribute('style', newStyle);
1513
+ }}
1514
+
1515
+ const observer = new MutationObserver(mutations => {{
1516
+ mutations.forEach(mutation => {{
1517
+ if (mutation.attributeName === 'style') {{
1518
+ const style = tab.getAttribute('style');
1519
+ if (style && (style.includes('rgb(255, 165, 0)') || style.includes('orange'))) {{
1520
+ const newStyle = style.replace(/border-bottom-color:\\s*rgb\\(255,\\s*165,\\s*0\\)/g, 'border-bottom-color: #411F61')
1521
+ .replace(/border-bottom-color:\\s*orange/g, 'border-bottom-color: #411F61');
1522
+ tab.setAttribute('style', newStyle);
1523
+ }}
1524
+ }}
1525
+ }});
1526
+ }});
1527
+
1528
+ observer.observe(tab, {{ attributes: true, attributeFilter: ['style'] }});
1529
+ }});
1530
+ }}
1531
+
1532
+ function initCustomizations() {{
1533
+ removeFooter();
1534
+ removeChatButtons();
1535
+ changeTabColors();
1536
+ addTabIcons();
1537
+ fixChatInputPosition();
1538
+ setHomeBackground();
1539
+ observeTabChanges();
1540
+ }}
1541
+
1542
+ if (document.readyState === 'loading') {{
1543
+ document.addEventListener('DOMContentLoaded', initCustomizations);
1544
+ }} else {{
1545
+ initCustomizations();
1546
+ }}
1547
+
1548
+ setTimeout(initCustomizations, 100);
1549
+ setTimeout(initCustomizations, 500);
1550
+ setTimeout(initCustomizations, 1000);
1551
+ setTimeout(initCustomizations, 2000);
1552
+
1553
+ const observer = new MutationObserver(() => {{
1554
+ removeFooter();
1555
+ removeChatButtons();
1556
+ fixChatInputPosition();
1557
+ }});
1558
+
1559
+ observer.observe(document.body, {{
1560
+ childList: true,
1561
+ subtree: true
1562
+ }});
1563
+ """
1564
+
1565
+ with gr.Blocks(title="calm.i") as demo:
1566
+ if logo_base64:
1567
+ gr.HTML(f'<div id="logo-container"><img src="data:image/png;base64,{logo_base64}" alt="calm.i Logo"></div>')
1568
+
1569
+ # Vordefinierte Markdown-Komponenten für personalisierte Inhalte (werden später gerendert)
1570
+ schlafplan_display = gr.Markdown(value="""
1571
+ <div class="resource-content">
1572
+ Dein persönlicher Schlafplan
1573
+
1574
+ Dieser Bereich wird personalisiert, sobald du die Fragen im Chat beantwortet hast.
1575
+
1576
+ Gehe zum **Chat-Tab** und starte das Gespräch mit mir!
1577
+ </div>
1578
+ """, render=False)
1579
+ medien_display = gr.Markdown(value="""
1580
+ <div class="resource-content">
1581
+ Deine persönliche Medien und Tools
1582
+
1583
+ Dieser Bereich wird personalisiert, sobald du die Fragen im Chat beantwortet hast.
1584
+
1585
+ Gehe zum **Chat-Tab** und starte das Gespräch mit mir!
1586
+ </div>
1587
+ """, render=False)
1588
+ time_display = gr.Markdown(value="""
1589
+ <div class="resource-content">
1590
+ Dein persönlicher Time-Management-Plan
1591
+
1592
+ Dieser Bereich wird personalisiert, sobald du die Fragen im Chat beantwortet hast.
1593
+
1594
+ Gehe zum **Chat-Tab** und starte das Gespräch mit mir!
1595
+ </div>
1596
+ """, render=False)
1597
+
1598
+ with gr.Tabs() as tabs:
1599
+ with gr.Tab("Home", id="home"):
1600
+ gr.HTML(f'''
1601
+ <div class="home-screen-container">
1602
+ <img src="data:image/png;base64,{home_screen_base64}" class="home-screen-image" alt="Home Screen">
1603
+ </div>
1604
+ <button class="home-chat-button" onclick="document.querySelectorAll('[role=tab]')[1].click()">Jetzt mit Calm.i chatten</button>
1605
+ ''')
1606
+
1607
+ with gr.Tab("Chat", id="chat"):
1608
+ chatbot_chat = gr.Chatbot(
1609
+ value=[{"role": "assistant", "content": f"Hallo! Ich bin calm.i {logo_inline_html}\n\nSchön, dass du hier bist! Ich kann dir mit Stress, Schlaf und Selbstfürsorge helfen.\n\n**Möchtest du einen kurzen Fragebogen (2-3 Minuten) ausfüllen?** Damit kann ich personalisierte Pläne für dich erstellen (Schlafplan, Medien und Tools, Time Management).\n\n**Antworte mit:**\n- \"Ja\" oder \"Los geht's\" → um den Test jetzt zu machen\n- \"Jetzt nicht\" oder \"Später\" → um direkt zu chatten\n\n---\n\n**!!! Wichtiger Hinweis !!!**\n*Calm.i ist kein Ersatz für therapeutische Behandlung! Es handelt sich um einen Chatbot, der mit Tipps und individuellen Planungstools eine Unterstützung darstellen kann. Bitte wenden Sie sich im Zweifel umgehend an professionelle Hilfe!*"}],
1610
+ show_label=False,
1611
+ avatar_images=("./assets/chat_icons/human.png", "./assets/chat_icons/robot.png"),
1612
+ elem_id="CHATBOT"
1613
+ )
1614
+
1615
+ chatinterface = gr.ChatInterface(
1616
+ fn=response,
1617
+ chatbot=chatbot_chat,
1618
+ additional_outputs=[schlafplan_display, medien_display, time_display]
1619
+ )
1620
+
1621
+ with gr.Tab("Schlafplan"):
1622
+ schlafplan_display.render()
1623
+
1624
+ with gr.Tab("Medien und Tools"):
1625
+ medien_display.render()
1626
+
1627
+ with gr.Tab("Time Management"):
1628
+ time_display.render()
1629
+
1630
+ with gr.Tab("Notfallplan"):
1631
+ gr.Markdown("""
1632
+ <div class="resource-content">
1633
+
1634
+ # Notfallplan
1635
+
1636
+ ## Bei akuten Krisen stehen dir diese Hilfen zur Verfügung
1637
+
1638
+ ### Wichtigste Kontakte
1639
+
1640
+ **Bei akuter Lebensgefahr:**
1641
+ - **Notruf: 112** (Feuerwehr & Rettungsdienst)
1642
+ - **Polizei: 110**
1643
+
1644
+ **Telefonseelsorge** (24/7, anonym, kostenlos)
1645
+ - **0800 111 0 111** (Evangelisch)
1646
+ - **0800 111 0 222** (Katholisch)
1647
+
1648
+ **Ärztlicher Bereitschaftsdienst**
1649
+ - **116 117** (Bei nicht lebensbedrohlichen Problemen außerhalb der Praxiszeiten)
1650
+
1651
+ <details class="crisis-dropdown">
1652
+ <summary>Nummer gegen Kummer</summary>
1653
+ <div class="dropdown-content">
1654
+
1655
+ - **Kinder- & Jugendtelefon: 116 111** (Mo-Sa 14-20 Uhr)
1656
+ - **Elterntelefon: 0800 111 0 550** (Mo-Fr 9-17 Uhr, Di+Do auch bis 19 Uhr)
1657
+
1658
+ </div>
1659
+ </details>
1660
+
1661
+ <details class="crisis-dropdown">
1662
+ <summary>Muslimisches Seelsorge-Telefon</summary>
1663
+ <div class="dropdown-content">
1664
+
1665
+ - **030 443 509 821** (24/7, mehrsprachig)
1666
+
1667
+ </div>
1668
+ </details>
1669
+
1670
+ <details class="crisis-dropdown">
1671
+ <summary>Info-Telefon Depression</summary>
1672
+ <div class="dropdown-content">
1673
+
1674
+ - **0800 334 4533** (Mo, Di, Do 13-17 Uhr, Mi+Fr 8:30-12:30 Uhr)
1675
+
1676
+ </div>
1677
+ </details>
1678
+
1679
+ <details class="crisis-dropdown">
1680
+ <summary>Psychiatrische Notdienste</summary>
1681
+ <div class="dropdown-content">
1682
+
1683
+ Google: "Psychiatrischer Notdienst [deine Stadt]"
1684
+
1685
+ </div>
1686
+ </details>
1687
+
1688
+ <details class="crisis-dropdown">
1689
+ <summary>Bei Suizidgedanken</summary>
1690
+ <div class="dropdown-content">
1691
+
1692
+ **Du bist nicht allein. Es gibt Hilfe.**
1693
+
1694
+ 1. **Rufe sofort** eine der oben genannten Nummern an
1695
+ 2. **Wende dich** an eine Vertrauensperson (Familie, Freund*in)
1696
+ 3. **Gehe** zur nächsten psychiatrischen Notaufnahme oder rufe 112
1697
+
1698
+ </div>
1699
+ </details>
1700
+
1701
+ <details class="crisis-dropdown">
1702
+ <summary>Online-Beratung</summary>
1703
+ <div class="dropdown-content">
1704
+
1705
+ - **[U25] Deutschland:** [www.u25-deutschland.de](https://www.u25-deutschland.de) (E-Mail-Beratung für unter 25-Jährige)
1706
+ - **Deutsche Gesellschaft für Suizidprävention:** [www.suizidprophylaxe.de](https://www.suizidprophylaxe.de)
1707
+
1708
+ </div>
1709
+ </details>
1710
+
1711
+ <details class="crisis-dropdown">
1712
+ <summary>Wichtig zu wissen</summary>
1713
+ <div class="dropdown-content">
1714
+
1715
+ - **Anonymität:** Telefonseelsorge und viele Beratungen sind anonym
1716
+ - **Kostenlos:** Alle Notfallnummern sind kostenlos
1717
+ - **Keine Scheu:** Lieber einmal zu viel anrufen als zu wenig
1718
+ - **Du bist wichtig:** Dein Leben hat Wert, auch wenn es sich gerade nicht so anfühlt
1719
+
1720
+ </div>
1721
+ </details>
1722
+
1723
+ ---
1724
+
1725
+ **Dieser Chatbot ersetzt keine professionelle Hilfe!**
1726
+
1727
+ Bei akuten Krisen wende dich bitte an die oben genannten Stellen.
1728
+
1729
+ </div>
1730
+ """)
1731
+
1732
+
1733
+ demo.launch(
1734
+ server_name="0.0.0.0",
1735
+ server_port=7860,
1736
+ inbrowser=True,
1737
+ css=custom_css,
1738
+ js=custom_js
1739
+ )
1740
+
1741
+
1742
+ if __name__ == "__main__":
1743
+ main()
assets/background_image/hintergrund.jpg ADDED

Git LFS Details

  • SHA256: 93697278850cd318a0fa4216690daf7284ef28ab625b53c5eb72f6e1d4de59c4
  • Pointer size: 131 Bytes
  • Size of remote file: 305 kB
assets/chat_icons/human.png ADDED
assets/chat_icons/robot.png ADDED
assets/home_screen/home_screen.png ADDED

Git LFS Details

  • SHA256: b9f41fdbcaa20db1ac98a233f0e9f7b9b995d2429b42ea110dff7eaf8f3150b1
  • Pointer size: 131 Bytes
  • Size of remote file: 903 kB
assets/home_screen/home_screen2.png ADDED

Git LFS Details

  • SHA256: ad312dfef66e84b631f5143f65b8b937b5599b05977b98dfa2a92003b9bff5ac
  • Pointer size: 131 Bytes
  • Size of remote file: 900 kB
assets/home_screen/home_screen3.png ADDED

Git LFS Details

  • SHA256: f3c3a0d556b17fb4ec59b4703a82276a6b491db757455746f31c58f831b9bd6d
  • Pointer size: 131 Bytes
  • Size of remote file: 903 kB
assets/logo/logo.png ADDED
assets/tab_icons/chat.png ADDED
assets/tab_icons/home.png ADDED
assets/tab_icons/mediensammlung.png ADDED
assets/tab_icons/notfallplan.png ADDED
assets/tab_icons/schlafplan.png ADDED
assets/tab_icons/timemanagement.png ADDED
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ openai
2
+ gradio==6.1.0
3
+ PyPDF2
4
+ chromadb
style.css ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [class*="gradio-container"] {
2
+ background: url("/file/hintergrund.jpg") no-repeat center center fixed !important;
3
+ background-size: cover !important;
4
+ }
5
+ /* Chatbot Box */
6
+ #CHATBOT {
7
+ background: rgba(65, 31, 97, 0.7) !important;
8
+ backdrop-filter: blur(6px);
9
+ border-radius: 12px;
10
+ }
11
+
12
+ /* Nachrichten */
13
+ .message.bot {
14
+ background: #6B1DAF !important;
15
+ font-family: "Lucida Handwriting" !important;
16
+ }
17
+
18
+ .message.user {
19
+ background: #AC73DE !important;
20
+ }
21
+
22
+ /* Tabellen kompakter machen */
23
+ .resource-content table {
24
+ border-collapse: collapse;
25
+ width: 100%;
26
+ margin: 10px 0;
27
+ }
28
+
29
+ .resource-content table th,
30
+ .resource-content table td {
31
+ padding: 4px 8px;
32
+ border: 1px solid rgba(255, 255, 255, 0.3);
33
+ line-height: 1.2;
34
+ }
35
+
36
+ .resource-content table th {
37
+ background: rgba(107, 29, 175, 0.5);
38
+ }
39
+
40
+ .resource-content table tr {
41
+ margin: 0;
42
+ padding: 0;
43
+ }
theme.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gradio.themes.base import Base
2
+
3
+
4
+ class CustomTheme(Base):
5
+ def __init__(self):
6
+ super().__init__()
7
+
8
+
9
+ super().set(
10
+ body_background_fill="#231332",
11
+ body_background_fill_dark="blue",
12
+ input_background_fill="#fff5dd",
13
+ input_background_fill_dark="#231332"
14
+ )