Quickstart
This guide gets you from zero to a working chat in a Makepad app.
Prerequisites
This guide assumes you are familiar with Makepad and have a bare-bones app ready. You should also have read the aitk documentation, as Moly Kit builds on its types and patterns.
Installation
Add Moly Kit to your Cargo.toml:
[dependencies]
makepad-widgets = { git = "https://github.com/makepad/makepad", branch = "main" }
moly-kit = { git = "https://github.com/moly-ai/moly-ai.git", features = ["full"] }
Pin to a specific version with tag = "x.x.x" or rev = "<commit>" instead
of branch = "main" if you want to stay on a stable version.
Register widgets
Define your App and register Moly Kit's widgets in script_mod:
app_main!(App);
#[derive(Script, ScriptHook)]
pub struct App {
#[live]
ui: WidgetRef,
}
impl AppMain for App {
fn script_mod(vm: &mut ScriptVm) -> ScriptValue {
makepad_widgets::script_mod(vm);
moly_kit::widgets::script_mod(vm);
self::script_mod(vm)
}
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}
Place the Chat widget
Use the Chat widget in your DSL layout:
script_mod! {
use mod.prelude.widgets.*
use mod.widgets.*
mod.widgets.MyChatBase = #(MyChat::register_widget(vm))
load_all_resources() do #(App::script_component(vm)) {
ui: Root {
main_window := Window {
window +: { inner_size: vec2(800 600) }
pass +: { clear_color: #xfff }
body +: {
my_chat := mod.widgets.MyChatBase {
width: Fill
height: Fill
flow: Down
padding: 12
chat := Chat {}
}
}
}
}
}
}
Configure the Chat widget
The Chat widget needs a ChatController from aitk to function. Set it up in
on_after_new, which runs once when the widget is created:
use std::sync::{Arc, Mutex};
use makepad_widgets::*;
use moly_kit::prelude::*;
#[derive(Script, Widget)]
struct MyChat {
#[deref]
view: View,
#[rust]
controller: Option<Arc<Mutex<ChatController>>>,
}
impl Widget for MyChat {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
}
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
self.view.draw_walk(cx, scope, walk)
}
}
impl ScriptHook for MyChat {
fn on_after_new(&mut self, vm: &mut ScriptVm) {
let mut client = OpenAiClient::new("https://api.openai.com/v1".into());
client.set_key("your-api-key").unwrap();
let controller = ChatController::builder()
.with_client(client)
.with_basic_spawner()
.build_arc();
controller
.lock()
.unwrap()
.dispatch_mutation(ChatStateMutation::SetBotId(
Some(BotId::new("gpt-4.1-nano")),
));
self.controller = Some(controller.clone());
vm.with_cx_mut(|cx| {
self.chat(cx, ids!(chat))
.write()
.set_chat_controller(cx, Some(controller));
});
}
}
That's it. After this setup, the Chat widget handles user input, streaming
responses, and message rendering automatically.
What's happening
- Create a client:
OpenAiClientconnects to any OpenAI-compatible endpoint (OpenAI, Ollama, OpenRouter, etc.). - Build the controller:
ChatController::builder()creates the state manager that coordinates the conversation.with_basic_spawner()provides cross-platform async task execution. - Set the model:
SetBotIdis a state mutation that directly sets a specific model on the controller. - Give it to the widget:
set_chat_controllergives the controller toChatso it can drive the conversation.
Moly Kit doesn't duplicate methods from Chat into Makepad's autogenerated
ChatRef but provides read() and write() helpers to access the inner widget.
Next steps
This minimal setup uses a single client with a hardcoded model. The next chapter, Multiple Providers and Dynamic Models, shows how to support multiple providers, dynamically load available models, and use plugins to automate model selection.