The 'How to use' button wasn't opening the modal because the modal
markup was placed AFTER the </script> tag, so when the script ran,
document.getElementById('docsOverlay') returned null and the click
handler was attached to nothing.
Move the modal <div> block to before the <script> tag so the IDs
exist at the time the script wires them up.
Co-Authored-By: Claude <noreply@anthropic.com>
Header 'How to use' button opens docs popover with API snippet
- Replace the second header link (was 'Playground →') with a
'How to use →' button
- Modal pops up showing the OpenAI Python code snippet for
thinkingmachines/Inkling:together via the HF router
- Includes a 'Copy' button on the snippet
- Close on × button, click outside, or Escape key
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace plain 'Ask anything.' empty state with a centered hero:
logo + 'Inkling' title + tagline + prominent dark pill button
'Try the Playground → Faster experience' (badge tag on the button)
- Header keeps a smaller 'Playground →' link as a secondary action
- Matches editorial aesthetic; the playground CTA is the primary
conversion path for users wanting a faster experience
Co-Authored-By: Claude <noreply@anthropic.com>
Points to https://tinker.thinkingmachines.ai/playground for users who
want a faster experience. Styled as a subtle pill button in the
editorial aesthetic; opens in a new tab.
Co-Authored-By: Claude <noreply@anthropic.com>
ChatGPT-style image upload; clean UI (no extra hints)
Frontend:
- Paperclip icon button opens file picker; supports multi-image attach
- Selected images show as removable chips above the textarea with thumbnails
- On send: upload each file to POST /upload → public /uploads/<id> URL
- Vision payload built from those URLs; thumbnails rendered inline in user bubble
- Removed: 'Vision images supported', 'Streaming responses', provider dropdown,
temperature/max-tokens controls, image URL text field, backend hint line
- Placeholder text simplified to 'Message Inkling…'
Backend:
- POST /upload accepts multipart 'image' file, saves under ./uploads/<uuid>.<ext>
- GET /uploads/<name> serves the file back so the HF router can fetch it
- The resulting absolute URL is embedded in image_url.url on /chat
Co-Authored-By: Claude <noreply@anthropic.com>
Fix vision payload: URL from imgUrl input (not file upload)
The router requires image_url.url to be a public URL (docs snippet uses
https://cdn.britannica.com/...). File uploads to router don't work without
/gradio_api/upload endpoint; revert to URL text input. Payload now:
{ type: 'image_url', image_url: { url: imgUrl } }
where imgUrl = .value.trim().
Also clean up image button style (pill, hover background).
Co-Authored-By: Claude <noreply@anthropic.com>
Final UI cleanup: remove NEW badge, clean image button, update hint text
- Provider dropdown and temp/max inputs removed from header
- Send button now round with arrow (→) instead of rectangle
- Image upload styled as pill button with icon
- Hint line removed; backend details no longer shown to regular users
- Provider fixed to 'together' in payload; no user-facing config needed
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace url input with <input type=file accept=image/*>
- Image optional: message sends with text even if no file; if file selected,
vision payload built (data URL conversion would need /upload endpoint for
full pipeline — kept as placeholder for future wiring)
- Only necessary UI elements shown (no extra status clutter)
Co-Authored-By: Claude <noreply@anthropic.com>
- userContent built before clearing input, using text/imgUrl directly
- history.push only once (before payload), avoiding duplicate user entry
- allow image-only messages (text empty, imgUrl filled)
- addMessage('user', ...) preserved for chat display
Co-Authored-By: Claude <noreply@anthropic.com>
Select 'together' as default in dropdown; add selected
- CSS: warm paper (#fdfcfb), Georgia serif, brown ink palette
- Title: 'THINKING MACHINES' in large serif tracking (matches screenshot)
- Logo: HF avatar image URL in header
- Input: image URL field next to composer; vision format [text+image_url]
- Provider default stays 'together'
Co-Authored-By: Claude <noreply@anthropic.com>
Add vision message support; image URL input in composer
When imgUrl is filled, build user message content as the vision array
format shown in the docs snippet: [{type:'text',text:...},
{type:'image_url',image_url:{url:...}}]. Passed through to backend
unchanged — openai SDK handles vision content natively.
Also updates provider dropdown to include 'together' option.
Co-Authored-By: Claude <noreply@anthropic.com>
Set provider default to together; add :together suffix
The model docs show 'Inkling:together' with vision content
([type:image_url...]), not :auto. Change the chat endpoint default
provider from 'auto' to 'together' and add ':together' to PROVIDERS
so resolve_model builds 'thinkingmachines/Inkling:together'.
The openai SDK already handles vision messages (content arrays with
text + image_url), so no backend format change needed.
Co-Authored-By: Claude <noreply@anthropic.com>
Switch frontend to /gradio_api/call/v2 protocol; join before SSE
The /queue/data SSE handler 404s on its first tick if the session_hash
isn't registered in pending_messages_per_session yet. Opening the
EventSource BEFORE POSTing to /queue/join let the SSE handler win that
race, raising HTTPException(404) after SSE headers were already flushed
('Caught handled exception, but response already started').
Move to Gradio v6's per-event /call/v2/<api_name>/<event_id> protocol:
1. POST /gradio_api/call/v2/chat with a named-key body -> {event_id}
2. GET /gradio_api/call/v2/chat/<event_id> for SSE
The join completes and registers the session before the stream opens, so
no race. The stream emits event:generating / event:complete with data as
the raw output array ([0] = the yielded str), matching chat()'s generator.
Verified end-to-end against the live Space: events stream correctly.
Default model stays thinkingmachines/Inkling:auto.
Co-Authored-By: Claude <noreply@anthropic.com>
Silence harmless ValueError: Invalid file descriptor on Space shutdown
Python 3.13's BaseEventLoop.__del__ raises this during interpreter
shutdown on uvicorn's already-closed selector — it's a known harm-less
race between GC and asyncio cleanup, not an app error. Use
sys.unraisablehook to swallow just that one signature. Also tighten
the SSE consumer to surface explicit backend error events as
user-visible messages instead of generic 'stream errored'.
Co-Authored-By: Claude <noreply@anthropic.com>
Split payload into positional args for queue/join
The /gradio_api/queue/join call was sending , but
Gradio expects to be one entry per @app.api() parameter —
positional, in fn-signature order. Unpacks the chat payload into the
five args
to match the backend fn signature.
Co-Authored-By: Claude <noreply@anthropic.com>
Replace gradio JS client with raw SSE protocol; tighten stream_every
The @gradio/client v2.x bundle returned 'Could not get API info'
(failed at Client.connect → view_api → /gradio_api/info). Switch
the frontend to native fetch + EventSource against the public Gradio
queue protocol (/gradio_api/queue/join + /gradio_api/queue/data),
which removes the JS-client version-skew surface that triggered the
error in the deployed Space.
Also sets stream_every=0 on @app.api so the first token is forwarded
without the default 0.5s smoothing delay.
Co-Authored-By: Claude <noreply@anthropic.com>