useAIFocus
The useAIFocus hook allows users to explicitly mark which items (documents, notes, files, etc.) should be prioritized and included as context in AI conversations. Unlike ambient context that’s always present, focus items are user-controlled and explicit.
Syntax
function useAIFocus(): {
// Actions
setFocus: (id: string, item: FocusItem) => void;
clearFocus: (id: string) => void;
clearAllFocus: () => void;
getFocus: (id: string) => FocusItem | undefined;
// Reactive State
focusedIds: string[];
allFocusItems: FocusItem[];
hasFocusedItems: boolean;
focusItemsRecord: Record<string, FocusItem>;
}Return Value
Actions
| Method | Parameters | Description |
|---|---|---|
setFocus(id, item) | id: string, item: FocusItem | Add an item to focus |
clearFocus(id) | id: string | Remove a specific item from focus by ID |
clearAllFocus() | None | Remove all items from focus |
getFocus(id) | id: string | Retrieve a specific focused item by ID |
Reactive State
| Property | Type | Description |
|---|---|---|
focusedIds | string[] | Array of focused item IDs - re-renders when focus changes |
allFocusItems | FocusItem[] | Array of all focused items - re-renders when focus changes |
hasFocusedItems | boolean | Whether any items are currently focused |
focusItemsRecord | Record<string, FocusItem> | Object mapping IDs to focus items |
FocusItem Interface
interface FocusItem {
id: string; // Unique identifier
label?: string; // Display label for UI (chips, badges)
description?: string; // Semantic description to help the AI understand context
data?: Record<string, unknown>; // Structured payload sent to the AI model
}Examples
Basic Usage
import React from "react";
import { ChatContainer, useAIFocus } from "ai-chat-bootstrap";
function DocumentSelector({ documents }: { documents: Document[] }) {
const { setFocus, clearFocus, focusedIds } = useAIFocus();
const handleDocumentToggle = (doc: Document) => {
if (focusedIds.includes(doc.id)) {
clearFocus(doc.id);
} else {
setFocus(doc.id, {
id: doc.id,
label: doc.title,
description: `Document: ${doc.title}`,
data: {
type: 'document',
title: doc.title,
content: doc.content,
lastModified: doc.lastModified
}
});
}
};
return (
<div className="space-y-2">
{documents.map((doc) => (
<button
key={doc.id}
onClick={() => handleDocumentToggle(doc)}
className={`p-3 rounded-lg border ${
focusedIds.includes(doc.id)
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
{doc.title}
</button>
))}
</div>
);
}Complete Implementation
export function ChatWithFocus() {
const { allFocusItems } = useAIFocus();
return (
<div className="grid grid-cols-2 gap-4">
<DocumentSelector documents={myDocuments} />
<ChatContainer
transport={{ api: "/api/chat" }}
messages={{
systemPrompt:
"You are a helpful assistant with access to the user's focused documents.",
}}
header={{
title: "AI Assistant",
subtitle: `${allFocusItems.length} items in focus`,
}}
threads={{ enabled: true }}
/>
</div>
);
}Focus Management Actions
function FocusControls() {
const { focusedIds, clearAllFocus, clearFocus, allFocusItems } = useAIFocus();
return (
<div className="space-y-2">
<div className="flex justify-between items-center">
<span>{focusedIds.length} items focused</span>
<button onClick={clearAllFocus}>Clear All</button>
</div>
{allFocusItems.map((item) => (
<div key={item.id} className="flex justify-between items-center">
<span>{item.label || item.id}</span>
<button onClick={() => clearFocus(item.id)}>Remove</button>
</div>
))}
</div>
);
}Different Item Types
function MultiTypeSelector() {
const { setFocus, clearFocus, focusedIds } = useAIFocus();
const focusDocument = (doc) => {
setFocus(doc.id, {
id: doc.id,
label: doc.title,
description: `Document: ${doc.title}`,
data: {
type: 'document',
title: doc.title,
content: doc.content,
wordCount: doc.content.split(' ').length
}
});
};
const focusImage = (img) => {
setFocus(img.id, {
id: img.id,
label: img.filename,
description: `Image: ${img.filename}`,
data: {
type: 'image',
filename: img.filename,
url: img.url,
alt: img.alt,
dimensions: img.dimensions
}
});
};
const focusNote = (note) => {
setFocus(note.id, {
id: note.id,
label: note.title,
description: `Note: ${note.title}`,
data: {
type: 'note',
title: note.title,
content: note.content,
tags: note.tags,
createdAt: note.createdAt
}
});
};
return <div>...</div>;
}Backend Integration
Focus items are included in the client-built enrichedSystemPrompt, so you should not rebuild or concatenate them again server-side:
import { createAzure } from "@ai-sdk/azure";
import { createAIChatHandler } from "ai-chat-bootstrap/server";
const azure = createAzure({
resourceName: process.env.AZURE_RESOURCE_NAME!,
apiKey: process.env.AZURE_API_KEY!,
apiVersion: process.env.AZURE_API_VERSION ?? "preview",
});
const model = azure(process.env.AZURE_DEPLOYMENT_ID!);
export const POST = createAIChatHandler({
model,
});Note: Avoid concatenating focus items again; duplication wastes tokens and can degrade model performance.
How Focus Works
- User Selection: Users actively choose which items to focus on through your UI
- Visual Feedback: Focused items are typically shown as chips, badges, or highlighted elements
- AI Context: Focused items are automatically included in chat requests to provide relevant context
- Dynamic Management: Users can add/remove items from focus at any time
Common Use Cases
- Document Editor: Users select which documents are relevant to their current editing task
- Note-Taking App: Users focus on specific notes when asking questions or generating content
- File Explorer: Users mark files they’re currently working with for AI assistance
- Research Tool: Users focus on specific sources or references for contextual analysis
TypeScript Types
interface FocusItem {
id: string;
label?: string;
description?: string;
data?: Record<string, unknown>;
}
interface UseAIFocusReturn {
// Actions
setFocus: (id: string, item: FocusItem) => void;
clearFocus: (id: string) => void;
clearAllFocus: () => void;
getFocus: (id: string) => FocusItem | undefined;
// Reactive State
focusedIds: string[];
allFocusItems: FocusItem[];
hasFocusedItems: boolean;
focusItemsRecord: Record<string, FocusItem>;
}See Also
- Focus Items guide - Complete tutorial with live examples
- useAIContext - For automatic context sharing
- useAIChat - Main chat hook
useAIFocusItem (Reactive Focus)
useAIFocusItem is a companion hook that provides a reactive way to keep a focus item synchronized with changing data (title/content/etc.) while it is focused. It feels similar to useAIContext, but remains explicitly user‑curated: the item only stays in focus while you choose to keep it there.
When to Use
| Use Case | Prefer |
|---|---|
| User explicitly selects an item once and wants a stable snapshot | setFocus (manual) |
| Item content changes as the user edits and AI should see latest | useAIFocusItem |
| Need historical reproducibility of the exact original data | Manual snapshot |
| Low-friction always-fresh focus for an open editor | useAIFocusItem |
| Performance sensitive; avoid rapid updates | Debounced useAIFocusItem or manual |
API
useAIFocusItem(
id: string,
build: () => Omit<FocusItem, 'id'> | null | undefined,
deps?: unknown[]
): voidParameters
| Name | Type | Description |
|---|---|---|
id | string | Stable focus item identifier |
build | () => Omit<FocusItem,'id'> | null | Function returning the latest focus payload; return null to remove focus |
deps | unknown[] | Dependency array that triggers re-evaluation of build |
Behavior
- Runs on mount and whenever any dependency changes.
- If
build()returns an object →setFocus(id, {...})is called. - If
build()returnsnull/undefined→clearFocus(id)is called. - Does NOT automatically clear on unmount (so navigating between list + editor does not implicitly drop focus). Explicitly return
nullor callclearFocuswhen you want to remove it.
Basic Example
import { useAIFocusItem } from 'ai-chat-bootstrap';
function SourceEditor({ source, isFocused }: { source: Source; isFocused: boolean }) {
useAIFocusItem(
source.id,
() => {
if (!isFocused) return null; // remove from focus when toggle off
return {
label: source.title || '(Untitled)',
data: {
type: 'source',
excerpt: source.body.slice(0, 500),
origin: source.origin,
}
};
},
[isFocused, source.title, source.body, source.origin]
);
return <Editor value={source.body} /* ... */ />;
}Integrating with a Toggle (Checkbox)
function FocusableRow({ source }: { source: Source }) {
const { focusItemsRecord } = useAIFocus();
const [manual, setManual] = React.useState(!!focusItemsRecord[source.id]);
useAIFocusItem(
source.id,
() => (manual ? {
label: source.title || '(Untitled)',
data: { type: 'source', excerpt: source.body.slice(0, 500) }
} : null),
[manual, source.title, source.body]
);
return (
<div className="flex items-center gap-2">
<span className="truncate flex-1">{source.title || '(Untitled)'}</span>
<Checkbox checked={manual} onCheckedChange={c => setManual(!!c)} />
</div>
);
}Debouncing Updates (Optional)
If the underlying data changes very rapidly (e.g. on each keystroke) you may want to debounce the dependencies upstream:
const debouncedBody = useDebouncedValue(source.body, 250);
useAIFocusItem(
source.id,
() => ({
label: source.title || '(Untitled)',
data: { type: 'source', excerpt: debouncedBody.slice(0, 500) }
}),
[source.title, debouncedBody]
);