Build a Screenshot-to-React Service with Kimi K2.7 Code HighSpeed
In late June 2026, Moonshot AI added a HighSpeed serving tier for Kimi K2.7 Code, the one-trillion-parameter open-weight coding model it published under a Modified MIT license on June 12.

Introduction
In late June 2026, Moonshot AI added a HighSpeed serving tier for Kimi K2.7 Code, the one-trillion-parameter open-weight coding model it published under a Modified MIT license on June 12. Same weights, faster serving: roughly 180 output tokens per second, up to 260 on short contexts. That speed unlocks a workflow that was previously too sluggish to bother with: turning UI screenshots into working React components quickly enough that a developer keeps iterating instead of giving up. In this tutorial you build a service that accepts a screenshot and returns a typed React component styled with Tailwind CSS, plus a refinement endpoint for the second and third pass.
What Kimi K2.7 Code HighSpeed is
Moonshot AI built Kimi K2.7 Code as a Mixture-of-Experts system: one trillion total parameters with 32 billion active per token, a 256K context window, and a 400-million-parameter MoonViT vision encoder that lets the model read images natively. HighSpeed, served under the model id kimi-k2.7-code-highspeed, runs the identical weights on a faster serving stack.
For a chat assistant, 180 tokens per second is a nice-to-have. For component generation it decides whether the tool gets used. A realistic 150-line component is around 2,500 output tokens. At the 40 tokens per second typical of large models under load, each attempt costs you a full minute of staring. At 180, it lands in about 14 seconds. Nobody refines a design over a one-minute loop; everybody refines over a 14-second one.
How the model reads your screenshot
There is no OCR step and no DOM inspection. MoonViT encodes the screenshot into tokens that flow through the same transformer as your text prompt, so the model attends over visual layout the same way it attends over code. It sees that a card grid is a grid, that a sidebar is fixed, that a button row is right-aligned.
Understand what that buys you and what it does not. Structure is reliable: hierarchy, grouping, alignment, and component boundaries come out right. Exact values are estimates: a #1a56db button may come back as blue-600, and 18 pixels of padding may come back as p-4. You are getting a faithful skeleton with approximate skin, which is exactly the right starting point for a component you were going to adjust anyway.
Set up the project
Two packages and one key. The HighSpeed endpoint is OpenAI-compatible, so the stock openai client is the only transport this build needs.
python -m venv venv && source venv/bin/activate
pip install openai fastapi uvicorn python-multipart
export MOONSHOT_API_KEY="sk-your-key-from-platform-moonshot-ai"The key comes from the Moonshot AI platform console. Keep it in the environment. The Modified MIT license means you can later self-host the weights and repoint one constant, which is the quiet advantage of building against an open model.
Send the screenshot to the model
Images travel as base64 data URLs inside the message content. One content part carries the image, one carries the instruction.
import os
import base64
from openai import OpenAI
MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"
MODEL_ID = "kimi-k2.7-code-highspeed"
moonshot = OpenAI(
base_url=MOONSHOT_BASE_URL,
api_key=os.getenv("MOONSHOT_API_KEY"),
)
def image_to_data_url(image_bytes: bytes, mime: str = "image/png") -> str:
encoded = base64.b64encode(image_bytes).decode("utf-8")
return f"data:{mime};base64,{encoded}"
def generate_component(image_bytes: bytes, hint: str = "") -> str:
completion = moonshot.chat.completions.create(
model=MODEL_ID,
temperature=0.2,
messages=[
{"role": "system", "content": COMPONENT_CONTRACT},
{"role": "user", "content": [
{"type": "image_url",
"image_url": {"url": image_to_data_url(image_bytes)}},
{"type": "text",
"text": f"Reproduce this UI as one React component. {hint}"},
]},
],
)
return strip_fences(completion.choices[0].message.content)Temperature 0.2 rather than 0 is a deliberate choice for generation work: pure greedy decoding on long components occasionally locks into repetitive class strings, and a small amount of sampling avoids it without making output erratic.
Pin the output contract
The difference between a demo and a service is that a service never wonders what came back. The system prompt is a contract, and the model honors it far more reliably when the contract is short and testable.
COMPONENT_CONTRACT = """You convert UI screenshots into React components.
Rules:
- Return EXACTLY ONE .tsx file, nothing else. No explanations.
- TypeScript function component with a typed Props interface.
- Tailwind CSS classes only. No CSS files, no styled-components.
- No component libraries. Plain JSX elements only.
- Use semantic HTML: nav, main, section, button where they apply.
- Placeholder images come from https://placehold.co with the right sizes."""
def strip_fences(text: str) -> str:
cleaned = text.strip()
if cleaned.startswith("```"):
first_newline = cleaned.index("\n")
cleaned = cleaned[first_newline + 1:]
cleaned = cleaned.rsplit("```", 1)[0]
return cleaned.strip()strip_fences exists because a coding model wraps output in markdown fences whenever it thinks it is being helpful, and a .tsx file starting with three backticks will not compile. Guard for it once, at the boundary.
Wrap it in an endpoint
FastAPI turns the function into something your tooling can call: a Storybook script, a Slack bot, an internal CLI.
from fastapi import FastAPI, File, HTTPException, UploadFile
app = FastAPI(title="screenshot-to-react")
MAX_UPLOAD_BYTES = 4 * 1024 * 1024
@app.post("/components")
async def create_component(file: UploadFile = File(...), hint: str = ""):
if file.content_type not in ("image/png", "image/jpeg"):
raise HTTPException(422, "Upload a PNG or JPEG screenshot.")
image_bytes = await file.read()
if len(image_bytes) > MAX_UPLOAD_BYTES:
raise HTTPException(413, "Screenshot larger than 4 MB. Crop it.")
code = generate_component(image_bytes, hint)
return {"filename": "GeneratedComponent.tsx", "code": code}The size cap is not bureaucracy. A 4K screenshot burns vision tokens on pixels the model does not need, costs more, and generates slower. Crop to the region you want built and the results improve while the bill shrinks.
Add the refinement turn
The first pass gets you 80 percent. The valuable endpoint is the one that takes the generated code plus a correction and returns the adjusted component, because it keeps naming and structure stable instead of rerolling the dice.
@app.post("/components/refine")
async def refine_component(payload: dict):
code, instruction = payload["code"], payload["instruction"]
completion = moonshot.chat.completions.create(
model=MODEL_ID,
temperature=0.2,
messages=[
{"role": "system", "content": COMPONENT_CONTRACT},
{"role": "assistant", "content": code},
{"role": "user",
"content": f"Modify the component: {instruction}. "
"Return the complete updated file."},
],
)
return {"code": strip_fences(completion.choices[0].message.content)}At HighSpeed rates this round trip is fast enough to sit inside a designer's feedback session. "Make the sidebar collapsible" comes back before the conversation moves on, which is the whole reason to pay for the faster tier.
Kimi K2.7 Code HighSpeed vs standard K2.7 Code
Same model, two serving tiers, two different jobs.
| Dimension | K2.7 Code HighSpeed | K2.7 Code (standard) |
|---|---|---|
| Model id | kimi-k2.7-code-highspeed | kimi-k2.7-code |
| Output speed | ~180 tok/s, 260 short-context | Standard serving speed |
| Weights | Identical, Modified MIT | Identical, Modified MIT |
| Context | 256K | 256K |
| Right for | Interactive loops, UI iteration | Batch jobs, CI tasks, overnight runs |
The decision rule is simple: if a human is waiting on the response, HighSpeed. If a queue is waiting, standard, and spend the difference on more runs.
Where it falls short
A screenshot is one frozen state, so the model cannot know your hover styles, breakpoints, loading states, or what the empty version of the screen looks like. It approximates spacing and color instead of matching your design tokens, and it will cheerfully invent an icon set you do not use. Accessibility comes out partial: semantic tags appear because the contract demands them, but focus management and aria wiring remain your job. And a service that turns any screenshot into a working component is also a machine for cloning other people's interfaces, and someone on your team will have tried that before the week is out (the demo GIF never shows that part). Set the norms before the tool does.
Full working example
import os
import base64
from fastapi import FastAPI, File, HTTPException, UploadFile
from openai import OpenAI
MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"
MODEL_ID = "kimi-k2.7-code-highspeed"
MAX_UPLOAD_BYTES = 4 * 1024 * 1024
COMPONENT_CONTRACT = """You convert UI screenshots into React components.
Rules:
- Return EXACTLY ONE .tsx file, nothing else. No explanations.
- TypeScript function component with a typed Props interface.
- Tailwind CSS classes only. No CSS files, no styled-components.
- No component libraries. Plain JSX elements only.
- Use semantic HTML: nav, main, section, button where they apply.
- Placeholder images come from https://placehold.co with the right sizes."""
moonshot = OpenAI(
base_url=MOONSHOT_BASE_URL,
api_key=os.getenv("MOONSHOT_API_KEY"),
)
app = FastAPI(title="screenshot-to-react")
def image_to_data_url(image_bytes: bytes, mime: str = "image/png") -> str:
encoded = base64.b64encode(image_bytes).decode("utf-8")
return f"data:{mime};base64,{encoded}"
def strip_fences(text: str) -> str:
cleaned = text.strip()
if cleaned.startswith("```"):
cleaned = cleaned[cleaned.index("\n") + 1:]
cleaned = cleaned.rsplit("```", 1)[0]
return cleaned.strip()
def call_kimi(messages: list) -> str:
completion = moonshot.chat.completions.create(
model=MODEL_ID, temperature=0.2, messages=messages,
)
return strip_fences(completion.choices[0].message.content)
@app.post("/components")
async def create_component(file: UploadFile = File(...), hint: str = ""):
if file.content_type not in ("image/png", "image/jpeg"):
raise HTTPException(422, "Upload a PNG or JPEG screenshot.")
image_bytes = await file.read()
if len(image_bytes) > MAX_UPLOAD_BYTES:
raise HTTPException(413, "Screenshot larger than 4 MB. Crop it.")
code = call_kimi([
{"role": "system", "content": COMPONENT_CONTRACT},
{"role": "user", "content": [
{"type": "image_url",
"image_url": {"url": image_to_data_url(image_bytes)}},
{"type": "text",
"text": f"Reproduce this UI as one React component. {hint}"},
]},
])
return {"filename": "GeneratedComponent.tsx", "code": code}
@app.post("/components/refine")
async def refine_component(payload: dict):
code = call_kimi([
{"role": "system", "content": COMPONENT_CONTRACT},
{"role": "assistant", "content": payload["code"]},
{"role": "user",
"content": f"Modify the component: {payload['instruction']}. "
"Return the complete updated file."},
])
return {"code": code}Run it with uvicorn main:app --port 8000, post a screenshot to /components, and paste the result into your repo.
When to reach for it
Point it at the surfaces where speed beats polish: internal tools, admin panels, prototypes that need to exist by the afternoon demo, and marketing pages a designer will restyle anyway. Skip it for design-system-critical product UI, where the real work is matching your tokens and interaction patterns, not producing plausible JSX. The pattern to build toward is wiring /components into your Storybook workflow so every generated component lands as a story a human reviews. The model produces the first draft at 180 tokens per second. Deciding whether it belongs in your product is still the slow part, and it should be.
You might also like
Keep reading from the journal.
July 6, 2026AI
Test the merge, not the branch
Merge queues test the world that actually ships
June 19, 2026AI
Build a Text-to-Image Generation Service with the Reve 2.0 API
On June 3, 2026, Reve 2.0 jumped to second place on the text-to-image Arena leaderboard. A leaderboard position is a benchmark, not a product.
June 10, 2026AI
Running a frontier coder on hardware you rent
On June 1, 2026, MiniMax released MiniMax M3, the first open-weight model to pair frontier coding with a 1M-token context and native multimodality. It tops the open-weight SWE-Bench Pro leaderboard at 59.0%.