Skip to content
kitn AI/UI

Skills & entity pills

An assistant that surfaces its capabilities as / skill pills. Type / in the composer to open the menu, pick a skill like Summarize or Code Review, and send it to get a reply tailored to that capability. The badges above the thread show the same skills as <kai-skills>.

<kai-chat> owns the thread and the composer. Pass a triggers array as a property — typing / opens a menu, and choosing a skill inserts an atomic pill into the composer. On kai-submit the pill is flattened into the message text (using its promptText), so you can route to the matching capability. <kai-skills> renders the same capabilities as badges from a separate skills property.

<kai-skills id="skills"></kai-skills>
<kai-chat id="chat" chat-title="Workspace assistant"></kai-chat>
<script type="module">
import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-chat');
const chat = document.getElementById('chat');
const skills = document.getElementById('skills');
// `/` skill triggers — each item becomes an atomic pill when chosen.
chat.triggers = [
{ char: '/', kind: 'skill', items: [
{ id: 'summarize', label: 'Summarize', description: 'Condense a thread into key points', promptText: 'Summarize this thread.' },
{ id: 'translate', label: 'Translate', description: 'Translate text into another language', promptText: 'Translate this text.' },
{ id: 'code-review', label: 'Code Review', description: 'Review a diff for bugs and style', promptText: 'Review this diff.' },
{ id: 'search-docs', label: 'Search Docs', description: 'Search the knowledge base', promptText: 'Search the docs.' },
] },
];
// Skill badges: { id, name }
skills.skills = [
{ id: 'summarize', name: 'Summarize' },
{ id: 'translate', name: 'Translate' },
{ id: 'code-review', name: 'Code Review' },
{ id: 'search-docs', name: 'Search Docs' },
];
// The submitted value carries the pill's prompt text — route on it.
chat.addEventListener('kai-submit', async (e) => {
const prompt = e.detail.value;
const aId = crypto.randomUUID();
chat.messages = [
...chat.messages,
{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: prompt }] },
{ id: aId, role: 'assistant', parts: [] },
];
chat.loading = true;
// Dispatch to the matching capability on your backend.
const reply = await runCapability(prompt);
let answer = '';
for await (const token of reply) {
answer += token;
chat.messages = chat.messages.map((m) =>
m.id === aId ? { ...m, parts: [{ type: 'text', text: answer }] } : m,
);
}
chat.loading = false;
});
</script>

triggers shape — each entry is { char, kind, items }; each item:

fieldtypenotes
idstringStable identifier; carried on the emitted entities
labelstringShown in the menu and on the pill (the sigil is rendered for you)
descriptionstring?Secondary text shown beside the label
promptTextstring?What the pill flattens to in the submitted text (defaults to label)

Use kind: 'agent' (or per-item kind: 'plugin') and char: '@' for an agent/plugin menu. Set triggers on <kai-prompt-input> directly when you compose the input yourself instead of using the full <kai-chat> — there, kai-submit and kai-value-change also carry the structured doc + entities.