molykit
CRITICAL: Use for MolyKit AI chat toolkit. Triggers on: BotClient, OpenAI, SSE streaming, AI chat, molykit, PlatformSend, spawn(), ThreadToken, cross-platform async, Chat widget, Messages, PromptInput, Avatar, LLM
Install
mkdir -p .claude/skills/molykit && curl -L -o skill.zip "https://mcp.directory/api/skills/download/4264" && unzip -o skill.zip -d .claude/skills/molykit && rm skill.zipInstalls to .claude/skills/molykit
About this skill
MolyKit Skill
Best practices for building AI chat interfaces with Makepad using MolyKit - a toolkit for cross-platform AI chat applications.
Source codebase: /Users/zhangalex/Work/Projects/FW/robius/moly/moly-kit
Triggers
Use this skill when:
- Building AI chat interfaces with Makepad
- Integrating OpenAI or other LLM APIs
- Implementing cross-platform async for native and WASM
- Creating chat widgets (messages, prompts, avatars)
- Handling SSE streaming responses
- Keywords: molykit, moly-kit, ai chat, bot client, openai makepad, chat widget, sse streaming
Overview
MolyKit provides:
- Cross-platform async utilities (PlatformSend, spawn(), ThreadToken)
- Ready-to-use chat widgets (Chat, Messages, PromptInput, Avatar)
- BotClient trait for AI provider integration
- OpenAI-compatible client with SSE streaming
- Protocol types for messages, bots, and tool calls
- MCP (Model Context Protocol) support
Cross-Platform Async Patterns
PlatformSend - Send Only on Native
/// Implies Send only on native platforms, not on WASM
/// - On native: implemented by types that implement Send
/// - On WASM: implemented by ALL types
pub trait PlatformSend: PlatformSendInner {}
/// Boxed future type for cross-platform use
pub type BoxPlatformSendFuture<'a, T> = Pin<Box<dyn PlatformSendFuture<Output = T> + 'a>>;
/// Boxed stream type for cross-platform use
pub type BoxPlatformSendStream<'a, T> = Pin<Box<dyn PlatformSendStream<Item = T> + 'a>>;
Platform-Agnostic Spawning
/// Runs a future independently
/// - Uses tokio on native (requires Send)
/// - Uses wasm-bindgen-futures on WASM (no Send required)
pub fn spawn(fut: impl PlatformSendFuture<Output = ()> + 'static);
// Usage
spawn(async move {
let result = fetch_data().await;
Cx::post_action(DataReady(result));
SignalToUI::set_ui_signal();
});
Task Cancellation with AbortOnDropHandle
/// Handle that aborts its future when dropped
pub struct AbortOnDropHandle(AbortHandle);
// Usage - task cancelled when widget dropped
#[rust]
task_handle: Option<AbortOnDropHandle>,
fn start_task(&mut self) {
let (future, handle) = abort_on_drop(async move {
// async work...
});
self.task_handle = Some(handle);
spawn(async move { let _ = future.await; });
}
ThreadToken for Non-Send Types on WASM
/// Store non-Send value in thread-local, access via token
pub struct ThreadToken<T: 'static>;
impl<T> ThreadToken<T> {
pub fn new(value: T) -> Self;
pub fn peek<R>(&self, f: impl FnOnce(&T) -> R) -> R;
pub fn peek_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R;
}
// Usage - wrap non-Send type for use across Send boundaries
let token = ThreadToken::new(non_send_value);
spawn(async move {
token.peek(|value| {
// use value...
});
});
BotClient Trait
Implementing AI Provider Integration
pub trait BotClient: Send {
/// Send message with streamed response
fn send(
&mut self,
bot_id: &BotId,
messages: &[Message],
tools: &[Tool],
) -> BoxPlatformSendStream<'static, ClientResult<MessageContent>>;
/// Get available bots/models
fn bots(&self) -> BoxPlatformSendFuture<'static, ClientResult<Vec<Bot>>>;
/// Clone for passing around
fn clone_box(&self) -> Box<dyn BotClient>;
}
// Usage
let client = OpenAIClient::new("https://api.openai.com/v1".into());
client.set_key("sk-...")?;
let context = BotContext::from(client);
BotContext - Sharable Wrapper
/// Sharable wrapper with loaded bots for sync UI access
pub struct BotContext(Arc<Mutex<InnerBotContext>>);
impl BotContext {
pub fn load(&mut self) -> BoxPlatformSendFuture<ClientResult<()>>;
pub fn bots(&self) -> Vec<Bot>;
pub fn get_bot(&self, id: &BotId) -> Option<Bot>;
pub fn client(&self) -> Box<dyn BotClient>;
}
// Usage
let mut context = BotContext::from(client);
spawn(async move {
if let Err(errors) = context.load().await.into_result() {
// handle errors
}
Cx::post_action(BotsLoaded);
});
Protocol Types
Message Structure
pub struct Message {
pub from: EntityId, // User, System, Bot(BotId), App
pub metadata: MessageMetadata,
pub content: MessageContent,
}
pub struct MessageContent {
pub text: String, // Main content (markdown)
pub reasoning: String, // AI reasoning/thinking
pub citations: Vec<String>, // Source URLs
pub attachments: Vec<Attachment>,
pub tool_calls: Vec<ToolCall>,
pub tool_results: Vec<ToolResult>,
}
pub struct MessageMetadata {
pub is_writing: bool, // Still being streamed
pub created_at: DateTime<Utc>,
}
Bot Identification
/// Globally unique bot ID: <len>;<id>@<provider>
pub struct BotId(Arc<str>);
impl BotId {
pub fn new(id: &str, provider: &str) -> Self;
pub fn id(&self) -> &str; // provider-local id
pub fn provider(&self) -> &str; // provider domain
}
// Example: BotId::new("gpt-4", "api.openai.com")
// -> "5;gpt-4@api.openai.com"
Widget Patterns
Slot Widget - Runtime Content Replacement
live_design! {
pub Slot = {{Slot}} {
width: Fill, height: Fit,
slot = <View> {} // default content
}
}
// Usage - replace content at runtime
let mut slot = widget.slot(id!(content));
if let Some(custom) = client.content_widget(cx, ...) {
slot.replace(custom);
} else {
slot.restore(); // back to default
slot.default().as_standard_message_content().set_content(cx, &content);
}
Avatar Widget - Text/Image Toggle
live_design! {
pub Avatar = {{Avatar}} <View> {
grapheme = <RoundedView> {
visible: false,
label = <Label> { text: "P" }
}
dependency = <RoundedView> {
visible: false,
image = <Image> {}
}
}
}
impl Widget for Avatar {
fn draw_walk(&mut self, cx: &mut Cx2d, ...) -> DrawStep {
if let Some(avatar) = &self.avatar {
match avatar {
Picture::Grapheme(g) => {
self.view(id!(grapheme)).set_visible(cx, true);
self.view(id!(dependency)).set_visible(cx, false);
self.label(id!(label)).set_text(cx, &g);
}
Picture::Dependency(d) => {
self.view(id!(dependency)).set_visible(cx, true);
self.view(id!(grapheme)).set_visible(cx, false);
self.image(id!(image)).load_image_dep_by_path(cx, d.as_str());
}
}
}
self.deref.draw_walk(cx, scope, walk)
}
}
PromptInput Widget
#[derive(Live, Widget)]
pub struct PromptInput {
#[deref] deref: CommandTextInput,
#[live] pub send_icon: LiveValue,
#[live] pub stop_icon: LiveValue,
#[rust] pub task: Task, // Send or Stop
#[rust] pub interactivity: Interactivity,
}
impl PromptInput {
pub fn submitted(&self, actions: &Actions) -> bool;
pub fn reset(&mut self, cx: &mut Cx);
pub fn set_send(&mut self);
pub fn set_stop(&mut self);
pub fn enable(&mut self);
pub fn disable(&mut self);
}
Messages Widget - Conversation View
#[derive(Live, Widget)]
pub struct Messages {
#[deref] deref: View,
#[rust] pub messages: Vec<Message>,
#[rust] pub bot_context: Option<BotContext>,
}
impl Messages {
pub fn set_messages(&mut self, messages: Vec<Message>, scroll_to_bottom: bool);
pub fn scroll_to_bottom(&mut self, cx: &mut Cx, triggered_by_stream: bool);
pub fn is_at_bottom(&self) -> bool;
}
UiRunner Pattern for Async-to-UI
impl Widget for PromptInput {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.deref.handle_event(cx, event, scope);
self.ui_runner().handle(cx, event, scope, self);
if self.button(id!(attach)).clicked(event.actions()) {
let ui = self.ui_runner();
Attachment::pick_multiple(move |result| match result {
Ok(attachments) => {
ui.defer_with_redraw(move |me, cx, _| {
me.attachment_list_ref().write().attachments.extend(attachments);
});
}
Err(_) => {}
});
}
}
}
SSE Streaming
/// Parse SSE byte stream into message stream
pub fn parse_sse<S, B, E>(s: S) -> impl Stream<Item = Result<String, E>>
where
S: Stream<Item = Result<B, E>>,
B: AsRef<[u8]>,
{
// Split on "\n\n", extract "data:" content
// Filter comments and [DONE] messages
}
// Usage in BotClient::send
fn send(&mut self, ...) -> BoxPlatformSendStream<...> {
let stream = stream! {
let response = client.post(url).send().await?;
let events = parse_sse(response.bytes_stream());
for await event in events {
let completion: Completion = serde_json::from_str(&event)?;
content.text.push_str(&completion.delta.content);
yield ClientResult::new_ok(content.clone());
}
};
Box::pin(stream)
}
Best Practices
- Use PlatformSend for cross-platform: Same code works on native and WASM
- Use spawn() not tokio::spawn: Platform-agnostic task spawning
- Use AbortOnDropHandle: Cancel tasks when widget drops
- Use ThreadToken for non-Send on WASM: Thread-local storage with token access
- Use Slot for custom content: Allow BotClient to provide custom widgets
- Use read()/write() pattern: Safe borrow access via WidgetRef
- Use UiRunner::defer_with_redraw: Update widget from async context
- Handle ClientResult partial success: May have value AND errors
Reference Files
llms.txt- Complete MolyKit API reference
More by ZhangHanDong
View all skills by ZhangHanDong →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversBuild persistent semantic networks for enterprise & engineering data management. Enable data persistence and memory acro
Optimize your codebase for AI with Repomix—transform, compress, and secure repos for easier analysis with modern AI tool
Serena is a free AI code generator toolkit providing robust code editing and retrieval, turning LLMs into powerful artif
n8n offers conversational workflow automation, enabling seamless software workflow creation and management without platf
The fullstack MCP framework for developing MCP apps for ChatGPT, Claude, and building MCP servers for AI agents. Connect
Desktop Commander MCP unifies code management with advanced source control, git, and svn support—streamlining developmen
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.