# Knowledge base

A scoped RAG assistant — pick a knowledge base from kai-scope-picker, and conversations, answers, and citations all filter to that scope.

A retrieval assistant where the user picks <strong>which</strong> 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.

## How it works

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, because the retriever owns them. Citations that arrive as `source` parts on a message render their own row inside the thread instead.

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

### Wire the scope to the data

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

```html
<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">

  await customElements.whenDefined('kai-scope-picker');

  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', parts: [{ type: 'text', text: e.detail.value }] },
      { id: aId, role: 'assistant', parts: [] },
    ];
    chat.loading = true;

    const { reply, sources } = await fetchAnswer(e.detail.value, activeScope);
    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,
      );
    }

    srcs.sources = sources; // citations from the active scope
    chat.loading = false;
  });
</script>
```

> **tip:** 
Tag each conversation with the scope it belongs to so the sidebar can filter without a server round-trip. A `ConversationSummary` carries `scope.filters.tags` for exactly this — store the knowledge-base name there.

## The scope payload

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

| field | type | notes |
|---|---|---|
| `tags` | `string[]?` | The knowledge bases / labels to query |
| `authors` | `string[]?` | 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".

## Next steps

- **[`kai-scope-picker` reference](/components/scope-picker/)** — `availableAuthors`, `availableTags`, `currentLabel`, and the `kai-scope-change` event.
- **[`kai-conversations` reference](/components/conversations/)** — grouping, `activeId`, and the `kai-conversation-select` event.
- **[RAG assistant](/examples/rag-assistant/)** — the single-scope version this example builds on, with numbered inline citations.
- **[Resizable split layout](/patterns/resizable-split/)** — the sidebar + main-pane layout used here.
