Skip to content
kitn AI/UI

Knowledge base

A retrieval assistant where the user picks which knowledge base to query. Choosing a scope in <kai-scope-picker> filters the <kai-conversations> sidebar, retitles the <kai-chat> thread, and grounds answers in <kai-sources> drawn from that scope. Switch the dropdown, then ask — the citations change with it.

Four web components share one piece of state — the active scope:

  • <kai-scope-picker> emits kai-scope-change with the chosen filters. undefined filters mean “all knowledge”.
  • <kai-conversations> re-renders with only the threads tagged for that scope.
  • <kai-chat> drives the thread; its kai-submit handler fetches a scoped answer.
  • <kai-sources numbered> shows the citations behind that answer — a separate element below the chat.

<kai-resizable> wraps the sidebar and main pane so the user can drag the split.

The picker offers tags; map each tag to a knowledge base, then refresh the dependent elements when it changes.

<kai-resizable orientation="horizontal">
<kai-resizable-item size="28%" min="220px" max="360px">
<kai-scope-picker id="scope"></kai-scope-picker>
<kai-conversations id="threads"></kai-conversations>
</kai-resizable-item>
<kai-resizable-item>
<kai-chat id="chat"></kai-chat>
<kai-sources id="srcs" numbered show-favicon></kai-sources>
</kai-resizable-item>
</kai-resizable>
<script type="module">
import '@kitn.ai/ui/elements';
const scope = document.getElementById('scope');
const threads = document.getElementById('threads');
const chat = document.getElementById('chat');
const srcs = document.getElementById('srcs');
// Offer the knowledge bases as scope tags.
scope.availableTags = ['Product Docs', 'API Reference', 'Engineering Wiki'];
scope.currentLabel = 'All knowledge';
let activeScope = 'all';
// Seed every thread; filter the list to the active scope.
const ALL_THREADS = await fetchThreads();
const showThreads = () =>
threads.conversations =
activeScope === 'all'
? ALL_THREADS
: ALL_THREADS.filter((t) => t.scope.filters?.tags?.includes(activeScope));
showThreads();
// Scope change → refilter threads, reset the thread, clear stale citations.
scope.addEventListener('kai-scope-change', (e) => {
activeScope = e.detail.filters?.tags?.[0] ?? 'all';
showThreads();
chat.messages = [];
srcs.sources = [];
});
// Ask → stream a scoped answer, then reveal its citations.
chat.addEventListener('kai-submit', async (e) => {
const aId = crypto.randomUUID();
srcs.sources = [];
chat.messages = [
...chat.messages,
{ id: crypto.randomUUID(), role: 'user', content: e.detail.value },
{ id: aId, role: 'assistant', content: '' },
];
chat.loading = true;
const { reply, sources } = await fetchAnswer(e.detail.value, activeScope);
for await (const token of reply) {
chat.messages = chat.messages.map((m) =>
m.id === aId ? { ...m, content: m.content + token } : m,
);
}
srcs.sources = sources; // citations from the active scope
chat.loading = false;
});
</script>

kai-scope-change carries the same SearchFilters your retrieval backend already understands:

fieldtypenotes
tagsstring[]?The knowledge bases / labels to query
authorsstring[]?Restrict to specific authors
contentType'transcript' | 'markdown'?Restrict by document type
dateRange{ from, to }?Restrict by date

filters is undefined when the user clears the scope (the picker’s reset item) — treat that as “search everything”.