uwe311 commited on
Commit
f86b2fc
·
verified ·
1 Parent(s): 9a2e9b0

Gradio UI: Colab-Ollama-Proxy mit Streaming

Browse files
Files changed (1) hide show
  1. app.py +229 -0
app.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import time
5
+ import re
6
+
7
+ # Default Ollama URL (Colab tunnel - changes on restart)
8
+ DEFAULT_OLLAMA_URL = "https://fairfield-configure-replies-paths.trycloudflare.com"
9
+ DEFAULT_MODEL = "qwen-coder-boosted"
10
+
11
+ def get_ollama_url(base_url):
12
+ """Get the Ollama API URL from the base URL."""
13
+ base = base_url.rstrip("/")
14
+ # If it's a tunnel URL, Ollama is on port 11434
15
+ if "trycloudflare.com" in base:
16
+ return base.replace("trycloudflare.com", "trycloudflare.com") + "/api"
17
+ return base + "/api"
18
+
19
+ def list_models(ollama_url):
20
+ """List available models from Ollama."""
21
+ try:
22
+ api_url = ollama_url.rstrip("/")
23
+ if "/api" not in api_url:
24
+ api_url += "/api"
25
+ resp = requests.get(f"{api_url}/tags", timeout=5)
26
+ if resp.status_code == 200:
27
+ models = resp.json().get("models", [])
28
+ return [m["name"] for m in models]
29
+ return [DEFAULT_MODEL]
30
+ except:
31
+ return [DEFAULT_MODEL]
32
+
33
+ def chat_stream(message, history, ollama_url, model_name, system_prompt, temperature):
34
+ """Stream chat response from Ollama."""
35
+ api_url = ollama_url.rstrip("/")
36
+ if "/api" not in api_url:
37
+ api_url += "/api"
38
+
39
+ # Build messages array
40
+ messages = []
41
+ if system_prompt:
42
+ messages.append({"role": "system", "content": system_prompt})
43
+
44
+ for user_msg, assistant_msg in history:
45
+ if user_msg:
46
+ messages.append({"role": "user", "content": user_msg})
47
+ if assistant_msg:
48
+ messages.append({"role": "assistant", "content": assistant_msg})
49
+
50
+ messages.append({"role": "user", "content": message})
51
+
52
+ payload = {
53
+ "model": model_name,
54
+ "messages": messages,
55
+ "stream": True,
56
+ "options": {
57
+ "temperature": temperature,
58
+ "num_predict": 32768
59
+ }
60
+ }
61
+
62
+ try:
63
+ resp = requests.post(f"{api_url}/chat", json=payload, stream=True, timeout=120)
64
+ resp.raise_for_status()
65
+
66
+ full_response = ""
67
+ for line in resp.iter_lines():
68
+ if line:
69
+ try:
70
+ chunk = json.loads(line)
71
+ if "message" in chunk and "content" in chunk["message"]:
72
+ content = chunk["message"]["content"]
73
+ full_response += content
74
+ yield full_response
75
+ if chunk.get("done", False):
76
+ break
77
+ except json.JSONDecodeError:
78
+ continue
79
+ except Exception as e:
80
+ yield f"❌ Fehler: {str(e)}\n\n**Tipp:** Prüfe die Ollama-URL oben – der Tunnel ändert sich bei jedem Colab-Neustart!"
81
+
82
+ def check_connection(ollama_url):
83
+ """Check if Ollama is reachable."""
84
+ api_url = ollama_url.rstrip("/")
85
+ if "/api" not in api_url:
86
+ api_url += "/api"
87
+ try:
88
+ resp = requests.get(f"{api_url}/tags", timeout=5)
89
+ if resp.status_code == 200:
90
+ models = resp.json().get("models", [])
91
+ model_list = "\n".join([f" • `{m['name']}` ({m.get('size', 0)/1e9:.1f}GB)" for m in models])
92
+ return f"✅ **Verbunden!** Modelle:\n{model_list}"
93
+ return f"⚠️ API antwortet mit Status {resp.status_code}"
94
+ except Exception as e:
95
+ return f"❌ **Keine Verbindung:** {str(e)}\n\nStarte den Colab-Tunnel neu und aktualisiere die URL oben."
96
+
97
+ # Build the Gradio UI
98
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="indigo"), title="Qwen Coder Boosted") as demo:
99
+ gr.Markdown("""
100
+ # 🚀 Qwen Coder Boosted
101
+ **Verbunden mit Colab-Ollama-Backend** – Zero GPU auf HF, Rechenpower auf Colab L4
102
+ """)
103
+
104
+ with gr.Row():
105
+ with gr.Column(scale=1):
106
+ ollama_url = gr.Textbox(
107
+ label="🌐 Ollama API URL",
108
+ value=DEFAULT_OLLAMA_URL,
109
+ info="Dein Colab-Tunnel oder localhost:11434",
110
+ scale=3
111
+ )
112
+ check_btn = gr.Button("🔌 Verbindung prüfen", variant="secondary", size="sm")
113
+ connection_status = gr.Markdown("💡 Gib die URL ein und klicke auf 'Verbindung prüfen'")
114
+
115
+ with gr.Column(scale=1):
116
+ model_name = gr.Dropdown(
117
+ label="🤖 Modell",
118
+ choices=[DEFAULT_MODEL],
119
+ value=DEFAULT_MODEL,
120
+ interactive=True,
121
+ scale=2
122
+ )
123
+ refresh_models = gr.Button("🔄 Modelle aktualisieren", variant="secondary", size="sm")
124
+
125
+ with gr.Accordion("⚙️ Erweiterte Einstellungen", open=False):
126
+ with gr.Row():
127
+ system_prompt = gr.Textbox(
128
+ label="System Prompt",
129
+ value="You are Qwen Coder Boosted, an expert coding assistant. Generate clean, working code. Always show complete files with [FILE:filename] markers.",
130
+ lines=3,
131
+ scale=3
132
+ )
133
+ temperature = gr.Slider(
134
+ label="Temperature",
135
+ minimum=0.1,
136
+ maximum=1.5,
137
+ value=0.6,
138
+ step=0.05,
139
+ scale=1
140
+ )
141
+
142
+ chatbot = gr.Chatbot(
143
+ label="💬 Chat",
144
+ height=500,
145
+ bubble_full_width=False,
146
+ show_copy_button=True
147
+ )
148
+
149
+ with gr.Row():
150
+ msg = gr.Textbox(
151
+ label="Deine Nachricht",
152
+ placeholder="Schreib einen Prompt... z.B. 'Baue eine To-Do-App mit HTML/CSS/JS'",
153
+ scale=8,
154
+ container=True
155
+ )
156
+ submit = gr.Button("📤 Senden", variant="primary", scale=1)
157
+ clear = gr.Button("🗑️ Leeren", variant="secondary", scale=1)
158
+
159
+ gr.Markdown("""
160
+ ---
161
+ ### 💡 Tipps
162
+ - **Tunnel-URL ändert sich** bei jedem Colab-Neustart – aktualisiere sie oben
163
+ - **Modelle:** `qwen-coder-boosted` (Custom), `qwen3-coder:30b-a3b-q4_K_M` (Base)
164
+ - **Max Tokens:** 32K (hardcodiert)
165
+ - **Temperature:** 0.6 (Standard für Coding)
166
+ """)
167
+
168
+ # Event handlers
169
+ def update_models(url):
170
+ api_url = url.rstrip("/")
171
+ if "/api" not in api_url:
172
+ api_url += "/api"
173
+ try:
174
+ resp = requests.get(f"{api_url}/tags", timeout=5)
175
+ if resp.status_code == 200:
176
+ models = [m["name"] for m in resp.json().get("models", [])]
177
+ return gr.Dropdown(choices=models, value=models[0] if models else DEFAULT_MODEL)
178
+ except:
179
+ pass
180
+ return gr.Dropdown(choices=[DEFAULT_MODEL], value=DEFAULT_MODEL)
181
+
182
+ refresh_models.click(
183
+ fn=update_models,
184
+ inputs=[ollama_url],
185
+ outputs=[model_name]
186
+ )
187
+
188
+ check_btn.click(
189
+ fn=check_connection,
190
+ inputs=[ollama_url],
191
+ outputs=[connection_status]
192
+ )
193
+
194
+ def respond(message, chat_history, url, model, sys_prompt, temp):
195
+ if not message.strip():
196
+ yield chat_history
197
+ return
198
+ chat_history.append((message, ""))
199
+ full_response = ""
200
+ for partial in chat_stream(message, chat_history[:-1], url, model, sys_prompt, temp):
201
+ full_response = partial
202
+ chat_history[-1] = (message, full_response)
203
+ yield chat_history
204
+
205
+ submit.click(
206
+ fn=respond,
207
+ inputs=[msg, chatbot, ollama_url, model_name, system_prompt, temperature],
208
+ outputs=[chatbot]
209
+ ).then(
210
+ fn=lambda: "",
211
+ outputs=[msg]
212
+ )
213
+
214
+ msg.submit(
215
+ fn=respond,
216
+ inputs=[msg, chatbot, ollama_url, model_name, system_prompt, temperature],
217
+ outputs=[chatbot]
218
+ ).then(
219
+ fn=lambda: "",
220
+ outputs=[msg]
221
+ )
222
+
223
+ clear.click(
224
+ fn=lambda: None,
225
+ outputs=[chatbot]
226
+ )
227
+
228
+ if __name__ == "__main__":
229
+ demo.launch()