Alibrown commited on
Commit
c20e16c
·
verified ·
1 Parent(s): 82a7a9e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -163
app.py CHANGED
@@ -1,179 +1,231 @@
1
- import os
2
  import streamlit as st
3
- import tempfile
4
- import requests
5
- import json
6
- # Entfernte Imports: PIL.Image, io, base64, pandas, zipfile, PyPDF2
7
- # Da kein File Manager mehr benötigt wird.
8
-
9
- # ----------------------------------------------------
10
- # 🚨 KRITISCHE FIXES FÜR DEN PERMISSION ERROR
11
- # Zwingt Streamlit, seine temporären/Konfigurationsdateien
12
- # in den beschreibbaren /tmp-Bereich zu schreiben.
13
- # ----------------------------------------------------
14
-
15
- # 1. Temporären, beschreibbaren Pfad erstellen
16
- TEMP_STREAMLIT_HOME = os.path.join(tempfile.gettempdir(), "st_config_workaround")
17
- os.makedirs(TEMP_STREAMLIT_HOME, exist_ok=True)
18
-
19
- # 2. Umgebungsvariablen setzen
20
- os.environ["STREAMLIT_HOME"] = TEMP_STREAMLIT_HOME
21
- os.environ["STREAMLIT_GATHER_USAGE_STATS"] = "false"
22
-
23
- # 3. Minimale config.toml erstellen, um Schreibversuche zu unterbinden
24
- CONFIG_PATH = os.path.join(TEMP_STREAMLIT_HOME, "config.toml")
25
- CONFIG_CONTENT = """
26
- [browser]
27
- gatherUsageStats = false
28
- """
29
-
30
- if not os.path.exists(CONFIG_PATH):
31
- try:
32
- with open(CONFIG_PATH, "w") as f:
33
- f.write(CONFIG_CONTENT)
34
- except Exception as e:
35
- print(f"WARNUNG: Konnte config.toml nicht schreiben: {e}")
36
-
37
- # ----------------------------------------------------
38
- # Ende der Workarounds
39
- # ----------------------------------------------------
40
-
41
- # --- Konfiguration ---
42
- # Hinweis: Layout-Einstellung 'wide' bleibt erhalten
43
- st.set_page_config(page_title="OpenRouter Minimal Chat UI", layout="wide")
44
- OPENROUTER_API_BASE = "https://openrouter.ai/api/v1"
45
-
46
- # --- Page Title ---
47
- st.title("💸 OpenRouter Minimal Chat Interface")
48
  st.markdown("""
49
- **Willkommen im OpenRouter Minimal Chat Interface!**
50
- Chatte mit **kostenlosen (Free-Tier)** Modellen über die OpenRouter API. Nur Text-Chat.
 
 
 
51
  """)
52
 
53
- # --- Session State Management ---
54
  if "messages" not in st.session_state:
55
  st.session_state.messages = []
56
- # st.session_state.uploaded_content wurde entfernt.
57
-
58
- # --- Context-Length Fetch (Wird beibehalten, da für den Slider wichtig) ---
59
- def fetch_model_contexts(api_key):
60
- """Lädt alle Modelle + deren context_length."""
61
- if not api_key:
62
- return {}
63
- headers = {"Authorization": f"Bearer {api_key}"}
64
- try:
65
- res = requests.get(f"{OPENROUTER_API_BASE}/models", headers=headers, timeout=10)
66
- contexts = {}
67
- if res.status_code == 200:
68
- for m in res.json().get("data", []):
69
- mid = m.get("id")
70
- ctx = m.get("context_length", 4096)
71
- contexts[mid] = ctx
72
- return contexts
73
- except Exception as e:
74
- return {}
75
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- # --- Sidebar ---
78
  with st.sidebar:
79
- st.header("⚙️ API Settings")
80
- api_key = st.text_input("OpenRouter API Key", type="password")
81
-
82
- # --- Manuelle Modelle ---
83
- FREE_MODEL_LIST = [
84
- "cognitivecomputations/dolphin-mistral-24b-venice-edition:free",
85
- "deepseek/deepseek-chat-v3",
86
- "google/gemma-2-9b-it",
87
- "mistralai/mistral-7b-instruct-v0.2",
88
- "qwen/qwen2-72b-instruct",
89
- "nousresearch/nous-hermes-2-mixtral-8x7b-dpo",
90
  ]
91
- model = st.selectbox("Wähle ein Modell", FREE_MODEL_LIST, index=0)
92
-
93
- # Kontextlänge holen (mit Fallback)
94
- model_contexts = fetch_model_contexts(api_key)
95
- default_ctx = model_contexts.get(model, 4096)
96
-
97
  temperature = st.slider("Temperature", 0.0, 1.0, 0.7)
98
- max_tokens = st.slider(
99
- f"Max Output Tokens (max {default_ctx})",
100
- 1,
101
- min(default_ctx, 32000),
102
- min(512, default_ctx)
103
- )
104
- st.caption(f"🔢 Model Context Length (Fallback 4096): {default_ctx}")
105
-
106
- if st.button("🔄 Chat Reset"):
107
- st.session_state.messages = []
108
- st.success("Chat-Verlauf gelöscht.")
109
- st.experimental_rerun()
110
-
111
- st.markdown("""
112
- ---
113
- **Minimal UI:** Nur Text-Chat.
114
- """)
115
-
116
- # --- Datei Upload Sektion komplett entfernt ---
117
-
118
-
119
- # --- Chat Verlauf anzeigen ---
120
- for msg in st.session_state.messages:
121
- with st.chat_message(msg["role"]):
122
- st.markdown(msg["content"])
123
-
124
-
125
- # --- API Request Funktion (Unverändert) ---
126
- def call_openrouter(model, messages, temp, max_tok, key):
127
- headers = {
128
- "Authorization": f"Bearer {key}",
129
- "Content-Type": "application/json",
130
- "Referer": "https://aicodecraft.io",
131
- "X-Title": "OpenRouter-Minimal-Interface",
132
- }
133
- payload = {
134
- "model": model,
135
- "messages": messages,
136
- "temperature": temp,
137
- "max_tokens": max_tok,
138
- }
139
 
140
- res = requests.post(f"{OPENROUTER_API_BASE}/chat/completions", headers=headers, data=json.dumps(payload))
141
 
142
- if res.status_code == 200:
143
- try:
144
- return res.json()["choices"][0]["message"]["content"]
145
- except (KeyError, IndexError):
146
- raise Exception("Fehlerhafte API-Antwort: Konnte Antworttext nicht extrahieren.")
147
- else:
148
- try:
149
- err = res.json()
150
- msg = err.get("error", {}).get("message", res.text)
151
- except:
152
- msg = res.text
153
- raise Exception(f"API Error {res.status_code}: {msg}")
 
 
154
 
 
 
 
 
155
 
156
- # --- Chat Input ---
157
- if prompt := st.chat_input("Deine Nachricht..."):
158
  if not api_key:
159
- st.warning("Bitte trage deinen OpenRouter API Key in der Sidebar ein.")
160
  st.stop()
161
-
162
- # Nachricht hinzufügen und anzeigen
163
- st.session_state.messages.append({"role": "user", "content": prompt})
164
- with st.chat_message("user"):
165
- st.markdown(prompt)
166
-
167
- # API Nachrichten vorbereiten (für Chatverlauf)
168
- messages = [{"role": m["role"], "content": m["content"]} for m in st.session_state.messages]
169
-
170
- # Antwort generieren
171
- with st.chat_message("assistant"):
172
- with st.spinner(f"Fragend {model}..."):
173
- try:
174
- reply = call_openrouter(model, messages, temperature, max_tokens, api_key)
175
- st.markdown(reply)
176
- st.session_state.messages.append({"role": "assistant", "content": reply})
177
- except Exception as e:
178
- st.error(str(e))
179
- st.session_state.messages.append({"role": "assistant", "content": f"❌ {str(e)}"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import google.generativeai as genai
3
+ from PIL import Image
4
+ import io
5
+ import base64
6
+ import pandas as pd
7
+ import zipfile
8
+ import PyPDF2
9
+
10
+ # Konfiguration der Seite
11
+ st.set_page_config(page_title="Gemini AI Chat", layout="wide")
12
+
13
+ st.title("🤖 Gemini AI Chat Interface")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  st.markdown("""
15
+ **Welcome to the Gemini AI Chat Interface!**
16
+ Chat seamlessly with Google's advanced Gemini AI models, supporting multiple input types.
17
+ 🔗 [GitHub Profile](https://github.com/volkansah) |
18
+ 📂 [Project Repository](https://github.com/volkansah/gemini-ai-chat) |
19
+ 💬 [Soon](https://aicodecraft.io)
20
  """)
21
 
22
+ # Session State Management
23
  if "messages" not in st.session_state:
24
  st.session_state.messages = []
25
+ if "uploaded_content" not in st.session_state:
26
+ st.session_state.uploaded_content = None
27
+
28
+ # Funktionen zur Dateiverarbeitung
29
+ def encode_image(image):
30
+ buffered = io.BytesIO()
31
+ image.save(buffered, format="JPEG")
32
+ return base64.b64encode(buffered.getvalue()).decode('utf-8')
33
+
34
+ def process_file(uploaded_file):
35
+ """Verarbeitet die hochgeladene Datei und extrahiert den Inhalt."""
36
+ file_type = uploaded_file.name.split('.')[-1].lower()
37
+
38
+ # Text-basierte Erweiterungen für ZIP-Verarbeitung
39
+ text_extensions = ('.txt', '.csv', '.py', '.html', '.js', '.css',
40
+ '.php', '.json', '.xml', '.c', '.cpp', '.java',
41
+ '.cs', '.rb', '.go', '.ts', '.swift', '.kt', '.rs', '.sh', '.sql', '.xlsx')
42
+
43
+ if file_type in ["jpg", "jpeg", "png"]:
44
+ return {"type": "image", "content": Image.open(uploaded_file).convert('RGB')}
45
+
46
+ if file_type in ["txt"] + [ext.strip('.') for ext in text_extensions if ext not in ('.csv', '.xlsx')]:
47
+ return {"type": "text", "content": uploaded_file.read().decode("utf-8", errors='ignore')}
48
+
49
+ if file_type in ["csv", "xlsx"]:
50
+ try:
51
+ # Versuch, Datei als CSV oder Excel zu lesen
52
+ if file_type == "csv":
53
+ df = pd.read_csv(uploaded_file)
54
+ else: # xlsx
55
+ df = pd.read_excel(uploaded_file)
56
+ return {"type": "text", "content": df.to_string()}
57
+ except Exception as e:
58
+ return {"type": "error", "content": f"Failed to read tabular data: {e}"}
59
+
60
+ if file_type == "pdf":
61
+ try:
62
+ reader = PyPDF2.PdfReader(uploaded_file)
63
+ return {"type": "text", "content": "".join(page.extract_text() for page in reader.pages if page.extract_text())}
64
+ except Exception as e:
65
+ return {"type": "error", "content": f"Failed to read PDF: {e}"}
66
+
67
+ if file_type == "zip":
68
+ try:
69
+ with zipfile.ZipFile(uploaded_file) as z:
70
+ newline = "\n"
71
+ content = f"ZIP Contents (Processing text files only):{newline}"
72
+
73
+ for file_info in z.infolist():
74
+ if not file_info.is_dir():
75
+ try:
76
+ # Prüfen, ob die Datei eine Text-Erweiterung hat
77
+ if file_info.filename.lower().endswith(text_extensions):
78
+ with z.open(file_info.filename) as file:
79
+ # Decode mit 'ignore', falls es Probleme gibt
80
+ file_content = file.read().decode('utf-8', errors='ignore')
81
+ content += f"{newline}📄 {file_info.filename}:{newline}{file_content}{newline}"
82
+ else:
83
+ content += f"{newline}⚠️ Binärdatei/Unbekannte Datei ignoriert: {file_info.filename}{newline}"
84
+ except Exception as e:
85
+ content += f"{newline}❌ Fehler beim Lesen von {file_info.filename}: {str(e)}{newline}"
86
+
87
+ return {"type": "text", "content": content}
88
+ except Exception as e:
89
+ return {"type": "error", "content": f"Failed to process ZIP: {e}"}
90
+
91
+ return {"type": "error", "content": "Unsupported file format"}
92
 
93
+ # Sidebar für Einstellungen
94
  with st.sidebar:
95
+ api_key = st.text_input("Google AI API Key", type="password")
96
+
97
+ # Modell-Liste bereinigt und Vision hervorgehoben
98
+ model_list = [
99
+ "gemini-1.5-flash",
100
+ "gemini-1.5-pro",
101
+ "gemini-1.5-pro-vision-latest", # Vision-Modell für Bilder
102
+ "gemini-1.0-pro-vision-latest",
103
+ "gemini-2.0-flash",
104
+ "gemini-2.0-flash-lite",
105
+ "gemini-2.0-pro-exp-02-05",
106
  ]
107
+ model = st.selectbox("Model", model_list)
108
+ st.caption("❗ Für Bildanalyse **(Vision)**-Modell wählen.")
109
+
 
 
 
110
  temperature = st.slider("Temperature", 0.0, 1.0, 0.7)
111
+ max_tokens = st.slider("Max Tokens", 1, 100000, 1000)
112
+
113
+ # Datei-Upload-Kontrolle
114
+ uploaded_file = st.file_uploader("Upload File (Image/Text/PDF/ZIP)",
115
+ type=["jpg", "jpeg", "png", "txt", "pdf", "zip",
116
+ "csv", "xlsx", "html", "css", "php", "js", "py"])
117
+
118
+ # Logik zur Dateiverarbeitung und Vorschau
119
+ if uploaded_file and st.session_state.uploaded_content is None:
120
+ # Nur verarbeiten, wenn eine neue Datei hochgeladen wird und kein Inhalt im State ist
121
+ processed = process_file(uploaded_file)
122
+ st.session_state.uploaded_content = processed
123
+
124
+ # Vorschau anzeigen, wenn Inhalt vorhanden
125
+ if st.session_state.uploaded_content:
126
+ processed = st.session_state.uploaded_content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ st.subheader("Current File Attachment:")
129
 
130
+ if processed["type"] == "image":
131
+ st.image(processed["content"], caption="Attached Image", use_container_width=False, width=300)
132
+ elif processed["type"] == "text":
133
+ st.text_area("File Preview", processed["content"], height=150)
134
+ elif processed["type"] == "error":
135
+ st.error(f"Error processing file: {processed['content']}")
136
+
137
+ # NEU: Clear Button
138
+ if st.button(" Clear Uploaded File Attachment"):
139
+ st.session_state.uploaded_content = None
140
+ # Da st.file_uploader selbst nicht einfach resettet,
141
+ # informieren wir den Nutzer, dass der Zustand gelöscht ist.
142
+ st.info("Attachment cleared! Reload the page to reset the upload field completely.")
143
+
144
 
145
+ # Chat-Historie anzeigen
146
+ for message in st.session_state.messages:
147
+ with st.chat_message(message["role"]):
148
+ st.markdown(message["content"])
149
 
150
+ # Chat-Eingabe verarbeiten
151
+ if prompt := st.chat_input("Your message..."):
152
  if not api_key:
153
+ st.warning("API Key benötigt!")
154
  st.stop()
155
+
156
+ # NEU: Spinner hinzugefügt
157
+ with st.spinner("Gemini is thinking..."):
158
+ try:
159
+ # API konfigurieren
160
+ genai.configure(api_key=api_key)
161
+
162
+ # Modell auswählen
163
+ model_instance = genai.GenerativeModel(model)
164
+
165
+ # Inhalt vorbereiten
166
+ content = [{"text": prompt}]
167
+
168
+ # Dateiinhalt hinzufügen
169
+ if st.session_state.uploaded_content:
170
+ if st.session_state.uploaded_content["type"] == "image":
171
+ # Überprüfung, ob ein Vision-Modell ausgewählt ist
172
+ if "vision" not in model.lower() and "pro" not in model.lower():
173
+ st.error("Bitte ein Vision- oder Pro-Modell für Bilder auswählen!")
174
+ st.stop()
175
+
176
+ content.append({
177
+ "inline_data": {
178
+ "mime_type": "image/jpeg",
179
+ "data": encode_image(st.session_state.uploaded_content["content"])
180
+ }
181
+ })
182
+ elif st.session_state.uploaded_content["type"] == "text":
183
+ # Text-Inhalt dem Prompt hinzufügen
184
+ content[0]["text"] += f"\n\n[Attached File Content]\n{st.session_state.uploaded_content['content']}"
185
+
186
+ # Nachricht zur Historie hinzufügen und anzeigen
187
+ st.session_state.messages.append({"role": "user", "content": prompt})
188
+ with st.chat_message("user"):
189
+ st.markdown(prompt)
190
+
191
+ # Antwort generieren
192
+ response = model_instance.generate_content(
193
+ content,
194
+ generation_config=genai.types.GenerationConfig(
195
+ temperature=temperature,
196
+ max_output_tokens=max_tokens
197
+ )
198
+ )
199
+
200
+ # Überprüfen, ob die Antwort gültig ist
201
+ if not response.candidates:
202
+ st.error("API Error: Keine gültige Antwort erhalten. Überprüfe die Eingabe oder das Modell.")
203
+ else:
204
+ # Antwort anzeigen und zur Historie hinzufügen
205
+ response_text = response.text
206
+ with st.chat_message("assistant"):
207
+ st.markdown(response_text)
208
+ st.session_state.messages.append({"role": "assistant", "content": response_text})
209
+
210
+ except Exception as e:
211
+ st.error(f"API Error: {str(e)}")
212
+ # Zusätzliche Überprüfung für Visionsfehler
213
+ if st.session_state.uploaded_content and st.session_state.uploaded_content["type"] == "image" and "vision" not in model.lower() and "pro" not in model.lower():
214
+ st.error("Detail-Fehler: Für Bilder MUSS ein Vision-fähiger Modell (z.B. 1.5 Pro) ausgewählt werden.")
215
+
216
+ # Instructions in the sidebar
217
+ with st.sidebar:
218
+ st.markdown("""
219
+ ---
220
+ ## 📝 Instructions:
221
+ 1. Enter your Google AI API key
222
+ 2. Select a model (use **Pro/Vision** models for image analysis)
223
+ 3. Adjust parameters (Temperature/Tokens)
224
+ 4. Upload a file (optional, supports **Image, Text, PDF, ZIP, CSV/XLSX**)
225
+ 5. Type your message and press Enter
226
+
227
+ ### About
228
+ 🔗 [GitHub Profile](https://github.com/volkansah) |
229
+ 📂 [Project Repository](https://github.com/volkansah/gemini-ai-chat) |
230
+ 💬 [Soon](https://aicodecraft.io)
231
+ """)