Shublub commited on
Commit
510cf95
·
verified ·
1 Parent(s): b755ded

Initial commit: Kimi K3 demo Space

Browse files
Files changed (2) hide show
  1. README.md +22 -4
  2. index.html +418 -18
README.md CHANGED
@@ -1,10 +1,28 @@
1
  ---
2
  title: Kimi K3 Demo
3
- emoji: 👁
4
- colorFrom: pink
5
- colorTo: indigo
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: Kimi K3 Demo
3
+ emoji: 🌙
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ license: other
9
  ---
10
 
11
+ # Kimi K3 Demo
12
+
13
+ A lightweight chat interface for [Moonshot AI's Kimi K3](https://huggingface.co/moonshotai/Kimi-K3) — a 2.8T-parameter native multimodal agentic model.
14
+
15
+ Built with vanilla HTML/CSS/JS. No backend required — connects directly to the Kimi API from your browser.
16
+
17
+ ## Usage
18
+
19
+ 1. Get an API key from [platform.kimi.ai](https://platform.kimi.ai)
20
+ 2. Enter it in the settings panel
21
+ 3. Chat with text, images, or both
22
+
23
+ ## Links
24
+
25
+ - [Model Card](https://huggingface.co/moonshotai/Kimi-K3)
26
+ - [Kimi K3 Tech Report](https://github.com/MoonshotAI/Kimi-K3/blob/main/k3_tech_report.pdf)
27
+ - [Kimi Code CLI](https://www.kimi.com/code)
28
+ - [API Platform](https://platform.kimi.ai)
index.html CHANGED
@@ -1,19 +1,419 @@
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.0">
6
+ <title>Kimi K3 Demo</title>
7
+ <style>
8
+ :root {
9
+ --primary: #4f6ef7;
10
+ --primary-hover: #3b5de7;
11
+ --bg: #0f0f1a;
12
+ --surface: #1a1a2e;
13
+ --surface-2: #252540;
14
+ --text: #e8e8f0;
15
+ --text-muted: #8888aa;
16
+ --border: #2a2a45;
17
+ --success: #34c759;
18
+ --radius: 12px;
19
+ }
20
+ * { box-sizing: border-box; margin: 0; padding: 0; }
21
+ body {
22
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
23
+ background: var(--bg);
24
+ color: var(--text);
25
+ display: flex;
26
+ justify-content: center;
27
+ min-height: 100vh;
28
+ }
29
+ .container { width: 100%; max-width: 860px; padding: 20px; display: flex; flex-direction: column; }
30
+ header { text-align: center; padding: 30px 0 20px; }
31
+ header h1 { font-size: 2.2em; font-weight: 700; letter-spacing: -0.5px; }
32
+ header h1 span { color: var(--primary); }
33
+ header p { color: var(--text-muted); margin-top: 6px; font-size: 0.95em; }
34
+ header .links { margin-top: 10px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
35
+ header .links a {
36
+ color: var(--primary); text-decoration: none; font-size: 0.9em; padding: 4px 12px;
37
+ border: 1px solid var(--border); border-radius: 20px; transition: 0.2s;
38
+ }
39
+ header .links a:hover { background: var(--surface-2); border-color: var(--primary); }
40
+
41
+ .settings {
42
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
43
+ padding: 16px 20px; margin-bottom: 16px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center;
44
+ }
45
+ .settings label { font-size: 0.85em; color: var(--text-muted); display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 200px; }
46
+ .settings input, .settings select {
47
+ background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
48
+ padding: 8px 12px; border-radius: 8px; font-size: 0.9em; width: 100%;
49
+ }
50
+ .settings input:focus, .settings select:focus { outline: none; border-color: var(--primary); }
51
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
52
+ .status-dot.off { background: #ff3b30; }
53
+ .status-dot.on { background: var(--success); }
54
+
55
+ .chat {
56
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
57
+ display: flex; flex-direction: column; flex: 1; min-height: 400px;
58
+ }
59
+ .messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; max-height: 500px; }
60
+ .msg { max-width: 85%; padding: 12px 16px; border-radius: var(--radius); line-height: 1.5; font-size: 0.92em; }
61
+ .msg.user { background: var(--primary); align-self: flex-end; border-bottom-right-radius: 4px; }
62
+ .msg.assistant { background: var(--surface-2); align-self: flex-start; border-bottom-left-radius: 4px; }
63
+ .msg.system { background: transparent; align-self: center; font-size: 0.82em; color: var(--text-muted); text-align: center; }
64
+ .msg .thinking {
65
+ background: rgba(79, 110, 247, 0.12); border-left: 3px solid var(--primary);
66
+ padding: 8px 12px; border-radius: 6px; margin: 8px 0; font-size: 0.88em; color: #9999cc;
67
+ }
68
+ .msg .thinking-label { font-weight: 600; color: var(--primary); margin-bottom: 4px; font-size: 0.85em; }
69
+ .msg img { max-width: 100%; max-height: 200px; border-radius: 8px; margin: 4px 0; }
70
+ .typing { display: flex; gap: 4px; padding: 4px 0; }
71
+ .typing span { width: 6px; height: 6px; background: var(--text-muted); border-radius: 50%; animation: bounce 1.4s infinite; }
72
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
73
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
74
+ @keyframes bounce { 0%,60%,100% { transform: translateY(0); } 30% { transform: translateY(-4px); } }
75
+
76
+ .input-row {
77
+ display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--border); align-items: center;
78
+ }
79
+ .input-row textarea {
80
+ flex: 1; background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
81
+ padding: 10px 14px; border-radius: 8px; font-size: 0.95em; resize: none; font-family: inherit;
82
+ min-height: 42px; max-height: 120px;
83
+ }
84
+ .input-row textarea:focus { outline: none; border-color: var(--primary); }
85
+ .input-row button {
86
+ background: var(--primary); color: white; border: none; padding: 10px 20px;
87
+ border-radius: 8px; font-size: 0.9em; cursor: pointer; font-weight: 600; transition: 0.2s; white-space: nowrap;
88
+ }
89
+ .input-row button:hover { background: var(--primary-hover); }
90
+ .input-row button:disabled { opacity: 0.5; cursor: not-allowed; }
91
+ .input-row .image-btn {
92
+ background: var(--surface-2); border: 1px solid var(--border); padding: 8px 10px;
93
+ border-radius: 8px; cursor: pointer; font-size: 1.1em; line-height: 1; transition: 0.2s;
94
+ }
95
+ .input-row .image-btn:hover { border-color: var(--primary); }
96
+ #imagePreview { display: none; max-width: 60px; max-height: 60px; border-radius: 6px; border: 1px solid var(--border); }
97
+
98
+ pre { background: #1a1a2e; border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px; overflow-x: auto; margin: 8px 0; font-size: 0.85em; }
99
+ code { background: var(--surface-2); padding: 2px 4px; border-radius: 4px; font-size: 0.9em; }
100
+ pre code { background: none; padding: 0; }
101
+
102
+ .footer { text-align: center; padding: 20px 0; font-size: 0.82em; color: var(--text-muted); }
103
+ .footer a { color: var(--primary); text-decoration: none; }
104
+ </style>
105
+ </head>
106
+ <body>
107
+ <div class="container">
108
+ <header>
109
+ <h1>🌙 <span>Kimi K3</span></h1>
110
+ <p>Moonshot AI · 2.8T-parameter native multimodal agentic model</p>
111
+ <div class="links">
112
+ <a href="https://huggingface.co/moonshotai/Kimi-K3" target="_blank">Model Card</a>
113
+ <a href="https://platform.kimi.ai" target="_blank">platform.kimi.ai</a>
114
+ <a href="https://github.com/MoonshotAI/Kimi-K3" target="_blank">GitHub</a>
115
+ </div>
116
+ </header>
117
+
118
+ <div class="settings">
119
+ <label>
120
+ Kimi API Key
121
+ <input type="password" id="apiKey" placeholder="sk-..." />
122
+ </label>
123
+ <label style="flex: 0.4; min-width: 100px;">
124
+ Reasoning
125
+ <select id="reasoningEffort">
126
+ <option value="max">max</option>
127
+ <option value="high">high</option>
128
+ <option value="low">low</option>
129
+ </select>
130
+ </label>
131
+ <div style="font-size: 0.85em; color: var(--text-muted);">
132
+ <span class="status-dot off" id="statusDot"></span>
133
+ <span id="statusText">Not configured</span>
134
+ </div>
135
+ </div>
136
+
137
+ <div class="chat">
138
+ <div class="messages" id="messages">
139
+ <div class="msg system">Welcome! Enter your Kimi API key above, then start chatting.</div>
140
+ <div class="msg system">Kimi K3 supports text and images. Use the 📷 button to attach an image.</div>
141
+ </div>
142
+ <div class="input-row">
143
+ <label class="image-btn" title="Attach image" id="imageLabel">📷</label>
144
+ <input type="file" id="imageInput" accept="image/*" style="display:none" />
145
+ <img id="imagePreview" />
146
+ <textarea id="messageInput" placeholder="Type a message..." rows="1"></textarea>
147
+ <button id="sendBtn">Send</button>
148
+ </div>
149
+ </div>
150
+
151
+ <div class="footer">
152
+ Built with <a href="https://huggingface.co/moonshotai/Kimi-K3" target="_blank">Kimi K3</a> ·
153
+ API provided by <a href="https://platform.kimi.ai" target="_blank">Moonshot AI</a>
154
+ </div>
155
+ </div>
156
+
157
+ <script>
158
+ const KIMI_API = "https://api.moonshot.ai/v1";
159
+ const MODEL = "kimi-k3";
160
+
161
+ const apiKeyInput = document.getElementById("apiKey");
162
+ const reasoningSelect = document.getElementById("reasoningEffort");
163
+ const messagesEl = document.getElementById("messages");
164
+ const messageInput = document.getElementById("messageInput");
165
+ const sendBtn = document.getElementById("sendBtn");
166
+ const imageInput = document.getElementById("imageInput");
167
+ const imagePreview = document.getElementById("imagePreview");
168
+ const imageLabel = document.getElementById("imageLabel");
169
+ const statusDot = document.getElementById("statusDot");
170
+ const statusText = document.getElementById("statusText");
171
+
172
+ let selectedImage = null;
173
+
174
+ apiKeyInput.addEventListener("input", () => {
175
+ if (apiKeyInput.value.startsWith("sk-")) {
176
+ statusDot.className = "status-dot on";
177
+ statusText.textContent = "API key set";
178
+ } else if (apiKeyInput.value) {
179
+ statusDot.className = "status-dot off";
180
+ statusText.textContent = "Invalid key format";
181
+ } else {
182
+ statusDot.className = "status-dot off";
183
+ statusText.textContent = "Not configured";
184
+ }
185
+ });
186
+
187
+ imageLabel.addEventListener("click", () => imageInput.click());
188
+ imageInput.addEventListener("change", (e) => {
189
+ const file = e.target.files[0];
190
+ if (file) {
191
+ selectedImage = file;
192
+ const reader = new FileReader();
193
+ reader.onload = (ev) => { imagePreview.src = ev.target.result; imagePreview.style.display = "block"; };
194
+ reader.readAsDataURL(file);
195
+ imageLabel.textContent = "✕";
196
+ } else {
197
+ clearImage();
198
+ }
199
+ });
200
+
201
+ imageLabel.addEventListener("click", (e) => {
202
+ if (imageLabel.textContent === "✕") {
203
+ e.preventDefault();
204
+ clearImage();
205
+ }
206
+ });
207
+
208
+ function clearImage() {
209
+ selectedImage = null;
210
+ imageInput.value = "";
211
+ imagePreview.style.display = "none";
212
+ imagePreview.src = "";
213
+ imageLabel.textContent = "📷";
214
+ }
215
+
216
+ function addMessage(role, content, thinking) {
217
+ const div = document.createElement("div");
218
+ div.className = `msg ${role}`;
219
+ if (role === "system") {
220
+ div.textContent = content;
221
+ } else {
222
+ if (thinking) {
223
+ const tBox = document.createElement("div");
224
+ tBox.className = "thinking";
225
+ tBox.innerHTML = `<div class="thinking-label">🧠 Reasoning</div>${escapeHtml(thinking)}`;
226
+ div.appendChild(tBox);
227
+ }
228
+ div.innerHTML += markedParse(content);
229
+ }
230
+ messagesEl.appendChild(div);
231
+ messagesEl.scrollTop = messagesEl.scrollHeight;
232
+ }
233
+
234
+ function showTyping() {
235
+ const div = document.createElement("div");
236
+ div.className = "msg assistant";
237
+ div.id = "typingIndicator";
238
+ div.innerHTML = '<div class="typing"><span></span><span></span><span></span></div>';
239
+ messagesEl.appendChild(div);
240
+ messagesEl.scrollTop = messagesEl.scrollHeight;
241
+ }
242
+
243
+ function removeTyping() {
244
+ const el = document.getElementById("typingIndicator");
245
+ if (el) el.remove();
246
+ }
247
+
248
+ function updateLastMessage(content, thinking) {
249
+ const msgs = messagesEl.querySelectorAll(".msg.assistant:not(.error)");
250
+ const last = msgs[msgs.length - 1];
251
+ if (last) {
252
+ last.innerHTML = "";
253
+ if (thinking) {
254
+ const tBox = document.createElement("div");
255
+ tBox.className = "thinking";
256
+ tBox.innerHTML = `<div class="thinking-label">🧠 Reasoning</div>${escapeHtml(thinking)}`;
257
+ last.appendChild(tBox);
258
+ }
259
+ last.innerHTML += markedParse(content);
260
+ messagesEl.scrollTop = messagesEl.scrollHeight;
261
+ }
262
+ }
263
+
264
+ function escapeHtml(str) {
265
+ const div = document.createElement("div");
266
+ div.textContent = str;
267
+ return div.innerHTML;
268
+ }
269
+
270
+ function markedParse(text) {
271
+ if (!text) return "";
272
+ let html = escapeHtml(text);
273
+
274
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
275
+ html = html.replace(codeBlockRegex, (_, lang, code) => {
276
+ const langAttr = lang ? ` class="language-${lang}"` : "";
277
+ return `<pre><code${langAttr}>${escapeHtml(code)}</code></pre>`;
278
+ });
279
+
280
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
281
+
282
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
283
+ html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
284
+
285
+ html = html.replace(/\n/g, '<br>');
286
+
287
+ html = html.replace(/<br><br>/g, '</p><p>');
288
+ html = '<p>' + html + '</p>';
289
+ html = html.replace(/<p><br><\/p>/g, '');
290
+ html = html.replace(/<p><\/p>/g, '');
291
+
292
+ return html;
293
+ }
294
+
295
+ async function sendMessage() {
296
+ const text = messageInput.value.trim();
297
+ if (!text && !selectedImage) return;
298
+ const apiKey = apiKeyInput.value.trim();
299
+ if (!apiKey || !apiKey.startsWith("sk-")) {
300
+ addMessage("system", "Please enter a valid Kimi API key (sk-...) in the settings.");
301
+ return;
302
+ }
303
+
304
+ addMessage("user", text || (selectedImage ? "[Image attached]" : ""));
305
+ messageInput.value = "";
306
+ const currentImage = selectedImage;
307
+ clearImage();
308
+ showTyping();
309
+
310
+ try {
311
+ const messages = [];
312
+
313
+ const userContent = [];
314
+ if (text) userContent.push({ type: "text", text });
315
+ if (currentImage) {
316
+ const b64 = await toBase64(currentImage);
317
+ userContent.push({ type: "image_url", image_url: { url: b64 } });
318
+ }
319
+ messages.push({ role: "user", content: userContent });
320
+
321
+ const response = await fetch(`${KIMI_API}/chat/completions`, {
322
+ method: "POST",
323
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
324
+ body: JSON.stringify({
325
+ model: MODEL,
326
+ messages,
327
+ stream: true,
328
+ max_tokens: 8192,
329
+ reasoning_effort: reasoningSelect.value,
330
+ }),
331
+ });
332
+
333
+ if (!response.ok) {
334
+ const err = await response.text();
335
+ throw new Error(`API error (${response.status}): ${err}`);
336
+ }
337
+
338
+ removeTyping();
339
+ addMessage("assistant", "");
340
+
341
+ const reader = response.body.getReader();
342
+ const decoder = new TextDecoder();
343
+ let buffer = "";
344
+ let reasoningContent = "";
345
+ let content = "";
346
+ let gotFirst = false;
347
+
348
+ while (true) {
349
+ const { done, value } = await reader.read();
350
+ if (done) break;
351
+ buffer += decoder.decode(value, { stream: true });
352
+
353
+ const lines = buffer.split("\n");
354
+ buffer = lines.pop() || "";
355
+
356
+ for (const line of lines) {
357
+ const trimmed = line.trim();
358
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
359
+ const data = trimmed.slice(6);
360
+ if (data === "[DONE]") continue;
361
+ try {
362
+ const parsed = JSON.parse(data);
363
+ const delta = parsed.choices?.[0]?.delta;
364
+ if (delta?.reasoning_content) {
365
+ reasoningContent += delta.reasoning_content;
366
+ }
367
+ if (delta?.content) {
368
+ content += delta.content;
369
+ }
370
+ if (!gotFirst || delta?.reasoning_content || delta?.content) {
371
+ updateLastMessage(content, reasoningContent);
372
+ gotFirst = true;
373
+ }
374
+ } catch {}
375
+ }
376
+ }
377
+
378
+ const final = messagesEl.querySelectorAll(".msg.assistant:not(.error)");
379
+ const last = final[final.length - 1];
380
+ if (last && last.innerHTML.trim() === "") {
381
+ last.remove();
382
+ }
383
+
384
+ } catch (err) {
385
+ removeTyping();
386
+ let msg = err.message;
387
+ if (msg.includes("401") || msg.includes("Unauthorized")) {
388
+ msg = "Invalid API key. Check your key at platform.kimi.ai.";
389
+ } else if (msg.includes("402")) {
390
+ msg = "Payment required. Add credits at platform.kimi.ai.";
391
+ }
392
+ addMessage("system", `Error: ${msg}`);
393
+ }
394
+ }
395
+
396
+ function toBase64(file) {
397
+ return new Promise((resolve, reject) => {
398
+ const reader = new FileReader();
399
+ reader.onload = () => resolve(reader.result);
400
+ reader.onerror = reject;
401
+ reader.readAsDataURL(file);
402
+ });
403
+ }
404
+
405
+ sendBtn.addEventListener("click", sendMessage);
406
+ messageInput.addEventListener("keydown", (e) => {
407
+ if (e.key === "Enter" && !e.shiftKey) {
408
+ e.preventDefault();
409
+ sendMessage();
410
+ }
411
+ });
412
+
413
+ messageInput.addEventListener("input", () => {
414
+ messageInput.style.height = "auto";
415
+ messageInput.style.height = Math.min(messageInput.scrollHeight, 120) + "px";
416
+ });
417
+ </script>
418
+ </body>
419
  </html>