kvyb commited on
Commit
a2929ff
·
verified ·
1 Parent(s): 9920302

Add public chat client

Browse files
Files changed (2) hide show
  1. README.md +9 -4
  2. index.html +359 -17
README.md CHANGED
@@ -1,10 +1,15 @@
1
  ---
2
  title: Qwen3.8 27B Humanlike Chat
3
- emoji: 📊
4
- colorFrom: blue
5
  colorTo: pink
6
  sdk: static
7
- pinned: false
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
  ---
2
  title: Qwen3.8 27B Humanlike Chat
3
+ colorFrom: indigo
 
4
  colorTo: pink
5
  sdk: static
6
+ app_file: index.html
7
+ fullWidth: true
8
+ short_description: Chat with the Q8_0 Humanlike model through its public API.
9
+ models:
10
+ - LessThanThreeAI/Qwen3.8-27B-Humanlike-Chat-Q8_0-GGUF
11
  ---
12
 
13
+ # Qwen3.8 27B Humanlike Chat
14
+
15
+ Static chat client for the public OpenAI-compatible inference endpoint. Prompts and model responses are retained privately for evaluation.
index.html CHANGED
@@ -1,19 +1,361 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="description" content="Chat with Qwen3.8 27B Humanlike through its public OpenAI-compatible API.">
7
+ <title>Qwen3.8 27B Humanlike Chat</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light;
11
+ --paper: #f4efe4;
12
+ --ink: #18231e;
13
+ --muted: #657068;
14
+ --line: #c9c2b3;
15
+ --accent: #d9513d;
16
+ --accent-dark: #a63225;
17
+ --panel: rgba(255, 253, 247, 0.78);
18
+ --user: #18231e;
19
+ --assistant: #fffdf7;
20
+ }
21
+
22
+ * { box-sizing: border-box; }
23
+
24
+ body {
25
+ margin: 0;
26
+ min-height: 100vh;
27
+ color: var(--ink);
28
+ background:
29
+ radial-gradient(circle at 9% 12%, rgba(217, 81, 61, 0.13), transparent 28rem),
30
+ linear-gradient(120deg, transparent 0 49.7%, rgba(24, 35, 30, 0.045) 50%, transparent 50.3%),
31
+ var(--paper);
32
+ font-family: Georgia, "Times New Roman", serif;
33
+ }
34
+
35
+ button, input, textarea { font: inherit; }
36
+
37
+ .shell {
38
+ width: min(1180px, calc(100% - 32px));
39
+ margin: 0 auto;
40
+ padding: 32px 0;
41
+ }
42
+
43
+ header {
44
+ display: flex;
45
+ align-items: end;
46
+ justify-content: space-between;
47
+ gap: 24px;
48
+ padding-bottom: 20px;
49
+ border-bottom: 1px solid var(--ink);
50
+ }
51
+
52
+ .eyebrow, label, .label, code, pre, .status {
53
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
54
+ }
55
+
56
+ .eyebrow {
57
+ margin: 0 0 8px;
58
+ color: var(--accent-dark);
59
+ font-size: 0.72rem;
60
+ font-weight: 700;
61
+ letter-spacing: 0.16em;
62
+ text-transform: uppercase;
63
+ }
64
+
65
+ h1 {
66
+ max-width: 760px;
67
+ margin: 0;
68
+ font-size: clamp(2.2rem, 6vw, 5.4rem);
69
+ font-weight: 400;
70
+ line-height: 0.92;
71
+ letter-spacing: -0.045em;
72
+ }
73
+
74
+ .model-link {
75
+ color: var(--ink);
76
+ text-decoration-thickness: 1px;
77
+ text-underline-offset: 4px;
78
+ white-space: nowrap;
79
+ }
80
+
81
+ main {
82
+ display: grid;
83
+ grid-template-columns: minmax(0, 1.55fr) minmax(280px, 0.75fr);
84
+ gap: 24px;
85
+ padding-top: 24px;
86
+ }
87
+
88
+ .chat, aside section {
89
+ border: 1px solid var(--line);
90
+ background: var(--panel);
91
+ box-shadow: 6px 6px 0 rgba(24, 35, 30, 0.08);
92
+ }
93
+
94
+ .transcript {
95
+ height: min(58vh, 600px);
96
+ min-height: 360px;
97
+ overflow-y: auto;
98
+ padding: 22px;
99
+ }
100
+
101
+ .message {
102
+ width: fit-content;
103
+ max-width: 88%;
104
+ margin: 0 0 16px;
105
+ padding: 14px 16px;
106
+ border: 1px solid var(--line);
107
+ white-space: pre-wrap;
108
+ overflow-wrap: anywhere;
109
+ line-height: 1.5;
110
+ }
111
+
112
+ .message.user {
113
+ margin-left: auto;
114
+ color: var(--paper);
115
+ background: var(--user);
116
+ border-color: var(--user);
117
+ }
118
+
119
+ .message.assistant { background: var(--assistant); }
120
+ .message.error { color: #8c241a; border-color: #c77a70; }
121
+
122
+ form {
123
+ display: grid;
124
+ grid-template-columns: 1fr auto;
125
+ gap: 12px;
126
+ padding: 16px;
127
+ border-top: 1px solid var(--line);
128
+ }
129
+
130
+ textarea {
131
+ width: 100%;
132
+ min-height: 94px;
133
+ resize: vertical;
134
+ padding: 12px;
135
+ color: var(--ink);
136
+ background: #fffdf7;
137
+ border: 1px solid var(--line);
138
+ border-radius: 0;
139
+ }
140
+
141
+ textarea:focus, input:focus, button:focus-visible {
142
+ outline: 2px solid var(--accent);
143
+ outline-offset: 2px;
144
+ }
145
+
146
+ .controls {
147
+ display: flex;
148
+ flex-direction: column;
149
+ justify-content: space-between;
150
+ gap: 10px;
151
+ }
152
+
153
+ input[type="number"] {
154
+ width: 112px;
155
+ padding: 8px;
156
+ color: var(--ink);
157
+ background: #fffdf7;
158
+ border: 1px solid var(--line);
159
+ }
160
+
161
+ button {
162
+ padding: 11px 16px;
163
+ color: white;
164
+ background: var(--accent);
165
+ border: 1px solid var(--accent-dark);
166
+ cursor: pointer;
167
+ }
168
+
169
+ button:hover { background: var(--accent-dark); }
170
+ button:disabled { cursor: wait; opacity: 0.6; }
171
+
172
+ aside {
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 18px;
176
+ }
177
+
178
+ aside section { padding: 18px; }
179
+ aside h2 { margin: 0 0 12px; font-size: 1.1rem; }
180
+ aside p { margin: 0; color: var(--muted); line-height: 1.5; }
181
+
182
+ .endpoint {
183
+ display: block;
184
+ margin: 10px 0;
185
+ padding: 10px;
186
+ overflow-x: auto;
187
+ color: var(--ink);
188
+ background: #e9e2d5;
189
+ border: 1px solid var(--line);
190
+ font-size: 0.78rem;
191
+ }
192
+
193
+ pre {
194
+ margin: 10px 0 0;
195
+ padding: 12px;
196
+ overflow-x: auto;
197
+ color: #eaf0eb;
198
+ background: #18231e;
199
+ font-size: 0.7rem;
200
+ line-height: 1.45;
201
+ white-space: pre-wrap;
202
+ }
203
+
204
+ .notice { border-left: 5px solid var(--accent); }
205
+ .status { min-height: 1.2em; margin: 0; padding: 0 16px 14px; color: var(--muted); font-size: 0.75rem; }
206
+
207
+ @media (max-width: 780px) {
208
+ .shell { width: min(100% - 20px, 680px); padding: 20px 0; }
209
+ header { align-items: start; flex-direction: column; }
210
+ h1 { font-size: clamp(2.4rem, 15vw, 4.4rem); }
211
+ main { grid-template-columns: 1fr; }
212
+ .transcript { height: 52vh; min-height: 320px; }
213
+ form { grid-template-columns: 1fr; }
214
+ .controls { flex-direction: row; align-items: end; }
215
+ .message { max-width: 94%; }
216
+ }
217
+ </style>
218
+ </head>
219
+ <body>
220
+ <div class="shell">
221
+ <header>
222
+ <div>
223
+ <p class="eyebrow">Public research preview</p>
224
+ <h1>Qwen3.8 27B Humanlike Chat</h1>
225
+ </div>
226
+ <a class="model-link" href="https://huggingface.co/LessThanThreeAI/Qwen3.8-27B-Humanlike-Chat-Q8_0-GGUF">View model files</a>
227
+ </header>
228
+
229
+ <main>
230
+ <section class="chat" aria-label="Chat interface">
231
+ <div id="transcript" class="transcript" aria-live="polite">
232
+ <div class="message assistant">Ask anything. The first response can take longer while the inference worker starts.</div>
233
+ </div>
234
+ <form id="chat-form">
235
+ <label>
236
+ Message
237
+ <textarea id="prompt" required placeholder="Write a message..." aria-label="Message"></textarea>
238
+ </label>
239
+ <div class="controls">
240
+ <label>
241
+ Max tokens
242
+ <input id="max-tokens" type="number" min="1" max="20000" value="1024">
243
+ </label>
244
+ <button id="send" type="submit">Send</button>
245
+ </div>
246
+ </form>
247
+ <p id="status" class="status" aria-live="polite"></p>
248
+ </section>
249
+
250
+ <aside>
251
+ <section class="notice">
252
+ <h2>Retention notice</h2>
253
+ <p>Prompts and model responses are retained privately for evaluation. Authorization headers and IP addresses are not stored in conversation records.</p>
254
+ </section>
255
+
256
+ <section>
257
+ <p class="label">OPENAI-COMPATIBLE ENDPOINT</p>
258
+ <code class="endpoint">https://qwen38-humanlike-api.qwen38-public-api.workers.dev/v1</code>
259
+ <button id="copy-endpoint" type="button">Copy endpoint</button>
260
+ <pre>curl https://qwen38-humanlike-api.qwen38-public-api.workers.dev/v1/chat/completions \
261
+ -H 'Content-Type: application/json' \
262
+ -d '{"model":"LessThanThreeAI/Qwen3.8-27B-Humanlike-Chat-Q8_0-GGUF","messages":[{"role":"user","content":"Hello"}],"max_tokens":1024,"stream":true}'</pre>
263
+ </section>
264
+ </aside>
265
+ </main>
266
+ </div>
267
+
268
+ <script>
269
+ const API = "https://qwen38-humanlike-api.qwen38-public-api.workers.dev/v1";
270
+ const MODEL = "LessThanThreeAI/Qwen3.8-27B-Humanlike-Chat-Q8_0-GGUF";
271
+ const messages = [];
272
+ const form = document.querySelector("#chat-form");
273
+ const prompt = document.querySelector("#prompt");
274
+ const send = document.querySelector("#send");
275
+ const status = document.querySelector("#status");
276
+ const transcript = document.querySelector("#transcript");
277
+
278
+ function addMessage(role, text, extraClass = "") {
279
+ const element = document.createElement("div");
280
+ element.className = `message ${role} ${extraClass}`;
281
+ element.textContent = text;
282
+ transcript.append(element);
283
+ transcript.scrollTop = transcript.scrollHeight;
284
+ return element;
285
+ }
286
+
287
+ function applyEvent(line, output) {
288
+ if (!line.startsWith("data:")) return;
289
+ const payload = line.slice(5).trim();
290
+ if (!payload || payload === "[DONE]") return;
291
+ const event = JSON.parse(payload);
292
+ const content = event.choices?.[0]?.delta?.content || event.choices?.[0]?.text || "";
293
+ if (content) {
294
+ output.textContent += content;
295
+ transcript.scrollTop = transcript.scrollHeight;
296
+ }
297
+ }
298
+
299
+ form.addEventListener("submit", async (event) => {
300
+ event.preventDefault();
301
+ const text = prompt.value.trim();
302
+ if (!text) return;
303
+
304
+ messages.push({ role: "user", content: text });
305
+ addMessage("user", text);
306
+ prompt.value = "";
307
+ send.disabled = true;
308
+ status.textContent = "Waiting for model...";
309
+ const output = addMessage("assistant", "");
310
+
311
+ try {
312
+ const response = await fetch(`${API}/chat/completions`, {
313
+ method: "POST",
314
+ headers: { "content-type": "application/json" },
315
+ body: JSON.stringify({
316
+ model: MODEL,
317
+ messages,
318
+ max_tokens: Number(document.querySelector("#max-tokens").value),
319
+ stream: true,
320
+ }),
321
+ });
322
+ if (!response.ok) throw new Error((await response.text()) || `HTTP ${response.status}`);
323
+
324
+ if (!response.headers.get("content-type")?.includes("text/event-stream")) {
325
+ const result = await response.json();
326
+ output.textContent = result.choices?.[0]?.message?.content || "No response content.";
327
+ } else {
328
+ const reader = response.body.getReader();
329
+ const decoder = new TextDecoder();
330
+ let buffer = "";
331
+ while (true) {
332
+ const { value, done } = await reader.read();
333
+ buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
334
+ const lines = buffer.split("\n");
335
+ buffer = lines.pop() || "";
336
+ for (const line of lines) applyEvent(line.trim(), output);
337
+ if (done) break;
338
+ }
339
+ if (buffer.trim()) applyEvent(buffer.trim(), output);
340
+ }
341
+
342
+ messages.push({ role: "assistant", content: output.textContent });
343
+ status.textContent = "Complete";
344
+ } catch (error) {
345
+ output.classList.add("error");
346
+ output.textContent = `Request failed: ${error.message}`;
347
+ status.textContent = "The inference service did not complete the request.";
348
+ } finally {
349
+ send.disabled = false;
350
+ prompt.focus();
351
+ }
352
+ });
353
+
354
+ document.querySelector("#copy-endpoint").addEventListener("click", async (event) => {
355
+ await navigator.clipboard.writeText(API);
356
+ event.currentTarget.textContent = "Copied";
357
+ setTimeout(() => { event.currentTarget.textContent = "Copy endpoint"; }, 1500);
358
+ });
359
+ </script>
360
+ </body>
361
  </html>