Des agents qui affichent des composants riches et interactifs en ligne — tableaux, graphiques, formulaires, diffs — pas seulement du texte. Aucun code frontend requis.
Les interfaces de chatbot traditionnelles affichent tout sous forme de bulles de texte. Mais les agents produisent des données structurées — résultats de requêtes, rapports de validation, statuts de déploiement, diffs de comparaison. Afficher tout cela en markdown est une mauvaise expérience.
L'UI Générative de Haira permet aux outils de contrôler déclarativement la façon dont leur sortie s'affiche. Une requête de base de données s'affiche en tableau. Un résultat de validation s'affiche en carte de statut. Un pipeline de déploiement s'affiche en suivi de progression. Tout cela en ligne dans le chat, sans écrire de code frontend.
Les outils retournent des composants UI en utilisant le module ui. Le runtime fait deux choses avec chaque résultat :
Aucun chemin n'est sacrifié. L'utilisateur voit un beau tableau, l'agent voit des données structurées.
import "ui"
tool query_database(query: string) -> any {
"""Executes a SQL query and displays results."""
rows, err = postgres.query(db, query)
if err != nil {
return ui.status_card("error", "Query Failed", conv.to_string(err))
}
return ui.table("Query Results", headers, rows)
}Activez l'UI sur un agent avec une seule ligne :
agent DataExplorer {
provider: OpenAI
tools: [query_database, search_data]
ui: ui
memory: conversation(max_turns: 30)
}import "ui"
ui.status_card("success", "Deploy Complete", "All 3 services updated")
ui.table("Results", ["Name", "Email"], [["Alice", "a@co"], ["Bob", "b@co"]])
ui.key_value("Server Info", {"Region": "us-east-1", "Status": "healthy"})
ui.chart("bar", "Revenue", ["Q1", "Q2", "Q3", "Q4"], [dataset])
ui.confirm("Delete this record?", "Yes, delete", "Cancel")
ui.group(
ui.status_card("success", "Query Complete", "42 rows"),
ui.table("Results", headers, rows)
)Chaque workflow Haira obtient automatiquement une interface web. Définissez un workflow, Haira génère le formulaire :
@webui(title: "File Summarizer", description: "Upload a file and get an AI summary")
@post("/summarize")
workflow Summarize(document: file, context: string) -> { summary: string } {
content, err = io.read_file(document)
if err != nil { return { summary: "Failed to read file." } }
reply, err = Summarizer.ask("Summarize: ${content}")
if err != nil { return { summary: "AI error." } }
return { summary: reply }
}Le décorateur @webui définit le titre et la description. Les paramètres file s'affichent en champs d'envoi. Les workflows en streaming (-> stream) obtiennent une interface de chat complète.
Les workflows en streaming bénéficient de l'expérience la plus riche — streaming de tokens en temps réel, cartes d'exécution d'outils et composants UI en ligne :
@webhook("/chat")
workflow Chat(message: string, session_id: string) -> stream {
return Assistant.stream(message, session: session_id)
}
fn main() {
http.Server([Chat]).listen(8080)
}L'interface de chat communique via le protocole ARP — gérant les deltas de texte, les événements du cycle de vie des outils et le rendu de composants riches via WebSocket ou SSE.
Pour des besoins spécifiques à un domaine, déposez des Web Components TypeScript dans un répertoire components/ :
export class HairaGanttChart extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: "open" });
}
setProps(props) {
// Render your custom UI
}
}
export default {
tag: "haira-gantt-chart",
component: HairaGanttChart,
};Les composants personnalisés héritent du thème Haira via des propriétés CSS personnalisées et peuvent dispatcher des événements haira-action qui deviennent des messages de chat. Le compilateur les découvre, les regroupe et les intègre au moment de la compilation.
ui.*Pas de dépôt frontend séparé. Pas de client API à maintenir. Pas de bibliothèque de composants à installer. Un seul fichier .haira, un seul binaire, une interface complète.