SessionTask UI System
Build reactive, AI-powered interfaces using pure Kotlin. Stream content, manage complex tasks, and create interactive human-in-the-loop workflows.
Reality Check: Kotlin to Browser
fun analyze(task: SessionTask) {
task.header("Analysis Results", level = 2)
val results = task.newTask() // Sub-task
task.add("Scanning repository...")
// Stream findings
findings.forEach { f ->
results.add("<div class='finding'>${f.msg}</div>")
}
results.complete()
task.add("✓ Done")
task.complete()
}
Analysis Results
UI Component Library
SessionTask
The primary canvas. Manages HTML buffers, unique message IDs, and lifecycle states (spinner to complete).
TabbedDisplay
Dynamic tabbed interface. Supports programmatically adding, updating, or streaming content into tabs.
Discussable
Human-in-the-loop workflow. Generate, review, and revise content with a blocking acceptance loop.
Retryable
Extends TabbedDisplay for LLM operations. Adds a "Recycle" button to re-run generations in new tabs.
Agent Patterns
Utilities like displayMapInTabs and AddApplyFileDiffLinks for rapid agent UI development.
File Management
Save files to session directories, generate download links, and create live-updating log streams.
API Reference
// Basic content
task.add("Hello **World**!", markdown = true)
task.header("Title", level = 2)
task.echo("User prompt style")
// Specialized output
task.hideable("Dismissible message")
task.verbose("Debug info (hidden by default)")
task.error(exception) // Renders red box + stack trace
task.image(bufferedImage) // Auto-saves and renders <img>
// Markdown & Mermaid
val html = MarkdownUtil.renderMarkdown(raw, ui = task.ui)
task.add(html)
// Server-side link triggers
val link = task.ui.hrefLink("Click Me") {
task.add("Link clicked!")
}
task.add("Action: $link")
// User input
val input = task.ui.textInput { response ->
task.add("You typed: $response")
}
task.add(input)
// Expandable sections
task.expandable("Logs", "<pre>...</pre>")
// Sub-task management
val sub = task.newTask() // Reserves spot in stream
val detached = task.ui.newTask(false) // Manual placement
task.add("<div>${detached.placeholder}</div>")
// Tabbed Layouts
val tabs = TabbedDisplay(task)
tabs["Summary"] = "Static content"
val liveTab = tabs.newTask("Live") // Stream into tab
liveTab.add("Updating...")
// Agent Utilities
task.add(AgentPatterns.displayMapInTabs(fileMap))
// Dynamic content updates
val statusBuffer = task.add("Starting process...")
for (i in 1..5) {
Thread.sleep(500)
statusBuffer?.setLength(0)
statusBuffer?.append("Processing step $i/5...")
task.update() // Push changes to client
}
statusBuffer?.setLength(0)
statusBuffer?.append("<strong>Done!</strong>")
task.update()
task.complete()
Lifecycle Management
Always call task.complete(). Failing to do so leaves the loading spinner active indefinitely, degrading user experience.
Thread Safety
SessionTask methods are thread-safe. Use task.ui.pool for heavy computations to avoid blocking the UI thread.
Dynamic Updates
Use the StringBuilder returned by add() and call task.update() to refresh existing elements.
Blocking Operations
Discussable is blocking. Use task.ui.scheduledThreadPoolExecutor for delayed or periodic execution.
Advanced Workflow Patterns
val finalResult = Discussable(
task = task,
heading = "Drafting Email",
userMessage = { "Draft an email" },
initialResponse = { prompt ->
MyObject(llm.generate(prompt))
},
outputFn = { design ->
design.toHtml()
},
reviseResponse = { history ->
llm.chat(history)
}
).call() // Blocks until Accept
Flow: Generate → Review → Revise → Accept
val retryable = Retryable(task) { buffer ->
// Runs on init and each recycle
val result = performExpensiveOperation()
result // Return HTML content
}
// Each retry creates a new tab
// User clicks ♻ to regenerate
Use case: LLM generations that may need multiple attempts