Raullen commited on
Commit
b37fe93
·
verified ·
1 Parent(s): f29937c

Initial Space: Gradio chat UI for DeepSeek V3/R1 + Qwen 3.5

Browse files
Files changed (4) hide show
  1. .gitignore +6 -0
  2. README.md +26 -6
  3. app.py +193 -0
  4. requirements.txt +2 -0
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .env
5
+ .env.local
6
+ .DS_Store
README.md CHANGED
@@ -1,12 +1,32 @@
1
  ---
2
- title: QuickSilverPro Chat
3
- emoji: 📊
4
- colorFrom: gray
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.13.0
8
  app_file: app.py
9
  pinned: false
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: QuickSilver Pro Chat
3
+ emoji:
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ short_description: Chat with DeepSeek R1 / V3 / Qwen 3.5 via QuickSilver Pro
12
  ---
13
 
14
+ # QuickSilver Pro Chat
15
+
16
+ Try **DeepSeek V3**, **DeepSeek R1**, and **Qwen 3.5-35B-A3B** via an OpenAI-compatible endpoint — no signup required.
17
+
18
+ Powered by [QuickSilver Pro](https://quicksilverpro.io), which serves the same top open-source models as OpenRouter / Together / Fireworks, at ~20% less per token.
19
+
20
+ - Full OpenAI-compatible API: drop-in replacement (`base_url` change only)
21
+ - **$1** in free credits for every new account
22
+ - Direct open-source model access — no proprietary routing, no "掺假"
23
+
24
+ ## Links
25
+
26
+ - **Get your own API key**: [quicksilverpro.io](https://quicksilverpro.io)
27
+ - **CLI**: `pip install quicksilverpro` ([GitHub](https://github.com/machinefi/qspro-cli))
28
+ - **Pricing**: [quicksilverpro.io/compare](https://quicksilverpro.io/compare)
29
+
30
+ ---
31
+
32
+ Built by [MachineFi Labs](https://quicksilverpro.io).
app.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ QuickSilver Pro Chat — Hugging Face Space.
3
+
4
+ A zero-friction try-it demo for QuickSilver Pro. Anyone on HF can chat with
5
+ DeepSeek V3 / R1 / Qwen 3.5 through our OpenAI-compatible endpoint, without
6
+ creating an account first. The goal is top-of-funnel discoverability: the
7
+ banner at the bottom sends them to quicksilverpro.io for their own key.
8
+
9
+ Single-tenant QSP key (stored as the `QSP_KEY` Space secret) with a monthly
10
+ budget cap configured on the QSP side. In-process per-session rate-limit
11
+ keeps casual spam from spiking the bill.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import time
18
+ from collections import deque
19
+ from typing import Iterable
20
+
21
+ import gradio as gr
22
+ from openai import OpenAI
23
+
24
+ # ────────────────────────── Configuration ──────────────────────────
25
+
26
+ QSP_KEY = os.environ.get("QSP_KEY", "").strip()
27
+ QSP_BASE = os.environ.get("QSP_BASE", "https://api.quicksilverpro.io/v1")
28
+
29
+ MODELS = [
30
+ ("deepseek-v3", "DeepSeek V3 — general-purpose, fast"),
31
+ ("deepseek-r1", "DeepSeek R1 — reasoning, slower, deeper"),
32
+ ("qwen3.5-35b", "Qwen 3.5-35B-A3B — 262K context, multilingual"),
33
+ ]
34
+ MODEL_CHOICES = [f"{m} — {desc}" for m, desc in MODELS]
35
+ DEFAULT_MODEL_LABEL = MODEL_CHOICES[0]
36
+
37
+ DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant."
38
+
39
+ # Per-session soft rate limit. Not a security boundary — the QSP-side budget
40
+ # cap on the shared key is. This just keeps one noisy session from blowing
41
+ # through the daily allowance in 90 seconds.
42
+ RATE_WINDOW_SEC = 60
43
+ RATE_MAX_MSGS = 8
44
+
45
+ _session_buckets: dict[str, deque] = {}
46
+
47
+
48
+ def _rate_limited(session_hash: str) -> bool:
49
+ now = time.time()
50
+ bucket = _session_buckets.setdefault(session_hash, deque())
51
+ while bucket and now - bucket[0] > RATE_WINDOW_SEC:
52
+ bucket.popleft()
53
+ if len(bucket) >= RATE_MAX_MSGS:
54
+ return True
55
+ bucket.append(now)
56
+ return False
57
+
58
+
59
+ # ────────────────────────── OpenAI client ──────────────────────────
60
+
61
+ if not QSP_KEY:
62
+ # Don't crash on import — let the UI render a clear error banner instead,
63
+ # so the Space owner sees "QSP_KEY secret not set" rather than a 500.
64
+ client = None
65
+ else:
66
+ client = OpenAI(base_url=QSP_BASE, api_key=QSP_KEY)
67
+
68
+
69
+ def _parse_model_label(label: str) -> str:
70
+ return label.split(" — ", 1)[0]
71
+
72
+
73
+ def respond(
74
+ message: str,
75
+ history: list[tuple[str, str]],
76
+ model_label: str,
77
+ system_prompt: str,
78
+ temperature: float,
79
+ max_tokens: int,
80
+ request: gr.Request | None = None,
81
+ ) -> Iterable[str]:
82
+ if client is None:
83
+ yield (
84
+ "⚠️ Space misconfigured: `QSP_KEY` secret is not set. "
85
+ "Owner: configure it in Settings → Variables and secrets."
86
+ )
87
+ return
88
+
89
+ session_hash = (request.session_hash if request else "anon") or "anon"
90
+ if _rate_limited(session_hash):
91
+ yield (
92
+ f"⏳ Rate limit reached ({RATE_MAX_MSGS} messages / "
93
+ f"{RATE_WINDOW_SEC}s). Take a breath, then try again."
94
+ )
95
+ return
96
+
97
+ model = _parse_model_label(model_label)
98
+ messages: list[dict[str, str]] = []
99
+ if system_prompt.strip():
100
+ messages.append({"role": "system", "content": system_prompt.strip()})
101
+ for user_msg, assistant_msg in history or []:
102
+ if user_msg:
103
+ messages.append({"role": "user", "content": user_msg})
104
+ if assistant_msg:
105
+ messages.append({"role": "assistant", "content": assistant_msg})
106
+ messages.append({"role": "user", "content": message})
107
+
108
+ try:
109
+ stream = client.chat.completions.create(
110
+ model=model,
111
+ messages=messages,
112
+ temperature=float(temperature),
113
+ max_tokens=int(max_tokens),
114
+ stream=True,
115
+ )
116
+ except Exception as e:
117
+ yield f"❌ API error: {type(e).__name__}: {str(e)[:300]}"
118
+ return
119
+
120
+ accumulated = ""
121
+ for chunk in stream:
122
+ try:
123
+ delta = chunk.choices[0].delta.content or ""
124
+ except (AttributeError, IndexError):
125
+ delta = ""
126
+ if delta:
127
+ accumulated += delta
128
+ yield accumulated
129
+
130
+
131
+ # ────────────────────────── UI ──────────────────────────
132
+
133
+ HEADER_MD = """
134
+ # ⚡ QuickSilver Pro Chat
135
+
136
+ Try **DeepSeek V3 / R1** and **Qwen 3.5-35B-A3B** via an OpenAI-compatible API — no signup needed here.
137
+
138
+ <sub>Running on [QuickSilver Pro](https://quicksilverpro.io) · Get your own key ($1 free credits): [quicksilverpro.io](https://quicksilverpro.io) · CLI: `pip install quicksilverpro`</sub>
139
+ """
140
+
141
+ FOOTER_MD = """
142
+ ---
143
+ <sub>Powered by <a href="https://quicksilverpro.io">QuickSilver Pro</a> — open-source LLM inference, OpenAI-compatible, ~20% below OpenRouter / Together / Fireworks. Built by <a href="https://quicksilverpro.io">MachineFi Labs</a>.</sub>
144
+ """
145
+
146
+ with gr.Blocks(
147
+ title="QuickSilver Pro Chat",
148
+ theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="purple"),
149
+ ) as demo:
150
+ gr.Markdown(HEADER_MD)
151
+
152
+ with gr.Row():
153
+ with gr.Column(scale=1):
154
+ model_dropdown = gr.Dropdown(
155
+ choices=MODEL_CHOICES,
156
+ value=DEFAULT_MODEL_LABEL,
157
+ label="Model",
158
+ interactive=True,
159
+ )
160
+ system_prompt = gr.Textbox(
161
+ label="System prompt",
162
+ value=DEFAULT_SYSTEM_PROMPT,
163
+ lines=3,
164
+ max_lines=8,
165
+ )
166
+ temperature = gr.Slider(
167
+ label="Temperature", minimum=0.0, maximum=2.0, step=0.1, value=0.7
168
+ )
169
+ max_tokens = gr.Slider(
170
+ label="Max tokens", minimum=64, maximum=4096, step=64, value=1024
171
+ )
172
+ with gr.Column(scale=3):
173
+ gr.ChatInterface(
174
+ fn=respond,
175
+ additional_inputs=[model_dropdown, system_prompt, temperature, max_tokens],
176
+ examples=[
177
+ ["Write a concise git commit message for: fixed off-by-one error in pagination"],
178
+ ["Explain closures in JavaScript in 2 sentences"],
179
+ ["What's the fastest sorting algorithm for 100k integers and why?"],
180
+ ["Translate 'Hello, how are you?' into formal Japanese, Hindi, and Russian"],
181
+ ],
182
+ cache_examples=False,
183
+ submit_btn="Send",
184
+ retry_btn="Retry",
185
+ undo_btn="Undo",
186
+ clear_btn="Clear",
187
+ )
188
+
189
+ gr.Markdown(FOOTER_MD)
190
+
191
+
192
+ if __name__ == "__main__":
193
+ demo.queue(default_concurrency_limit=4, max_size=64).launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.44.0
2
+ openai>=1.50.0