- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Chat Web Search
Карточки веб-поиска и чтения страниц (web_search / web_fetch) с состояниями загрузки, ошибки, пустого результата и списка источников.
"use client";
import {
ChatWebSearch,
type ChatWebSearchResult,
} from "@/components/ui/chat-web-search";
const results: ChatWebSearchResult[] = [
{
url: "https://nextjs.org/docs/app/getting-started/cache-components",
title: "Cache Components — документация Next.js",
domain: "nextjs.org",
},
{
url: "https://react.dev/reference/rsc/server-components",
title: "Серверные компоненты — React",
domain: "react.dev",
publishedDate: "Март 2026",
},
{
url: "https://base-ui.com/react/overview/quick-start",
title: "Быстрый старт — Base UI",
domain: "base-ui.com",
},
{
url: "https://vercel.com/blog/whats-new-in-nextjs-16",
title: "Что нового в Next.js 16",
domain: "vercel.com",
snippet:
"Cache Components, стабильный Turbopack и более быстрый сервер разработки.",
},
{
url: "https://web.dev/articles/rendering-on-the-web",
title: "Рендеринг в вебе",
domain: "web.dev",
},
];
export default function Particle() {
return (
<ChatWebSearch
cached
query="cache components в Next.js 16"
results={results}
status="complete"
/>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/chat-web-search
Использование
import {
ChatWebSearch,
ChatWebFetch,
ChatWebFetchGroup,
} from "@/components/ui/chat-web-search";
<ChatWebSearch
status="complete"
query="Next.js 16 cache components"
cached
results={[
{
url: "https://nextjs.org/docs/app/getting-started/cache-components",
title: "Cache Components — Next.js",
domain: "nextjs.org",
},
]}
/>;Convenience-форма <ChatWebSearch> рендерит весь жизненный цикл web_search по одному пропу status. Для тонкой компоновки доступны под-компоненты ChatWebSearchHeader, ChatWebSearchResults, ChatWebSearchResult.
Чтение страниц — отдельные surface'ы той же семьи:
<ChatWebFetch
status="complete"
url="https://react.dev/reference/rsc/server-components"
title="React Server Components"
siteName="React"
publishedDate="Jun 2026"
/>
<ChatWebFetchGroup
pages={[
{ url: "https://nextjs.org", title: "Next.js", domain: "nextjs.org" },
{ url: "https://broken.example", domain: "broken.example", error: true },
]}
/>API
| Компонент | Описание |
|---|---|
ChatWebSearch | Корневая карточка web_search (status, variant, query, results, error, cached). |
ChatWebSearchHeader | Заголовок-лейбл: запрос + счётчик источников. |
ChatWebSearchResults | Скроллируемый инсет-список результатов (+ caption «from cache»). |
ChatWebSearchResult | Одна строка: фавикон + заголовок + домен (+ опционально snippet/дата). |
ChatWebFetch | Одно чтение страницы web_fetch (working / error / success). |
ChatWebFetchGroup | Сгруппированный список чтений: «Read N pages», упавшие строки grayscale. |
Состояния
ChatWebSearch
status: 'working' | 'error' | 'complete' управляет всем рендером:
- working —
Spinner+ лейбл («Searching: …» или «Preparing search query…»); контейнер помеченaria-busy+aria-live="polite", чтобы скринридер озвучивал прогресс. - error — destructive-карточка с заголовком и причиной; в
variant="timeline"— одна inline-строкаSearch failed · reason. - complete + пустой
results— карточка/строка «No results for …». - complete + результаты — заголовок (запрос + N sources) и инсет-список с фавиконами;
cachedдобавляет подпись.
"use client";
import { ChatWebSearch } from "@/components/ui/chat-web-search";
export default function Particle() {
return (
<div className="flex w-full flex-col gap-3">
<ChatWebSearch query="серверные компоненты react 2026" status="working" />
<ChatWebSearch
error="Сервис поиска временно недоступен. Пожалуйста, попробуйте ещё раз."
query="серверные компоненты react 2026"
status="error"
/>
<ChatWebSearch
query="редкий запрос без совпадений"
results={[]}
status="complete"
/>
</div>
);
}
ChatWebFetch
Тот же status управляет чтением одной страницы:
- working —
Spinner+ лейбл («Reading:<domain>» или «Preparing page…»); контейнер помеченaria-busy+aria-live="polite". - error — destructive-карточка (
Couldn't open · domain); опциональный бейджerrorCode(напримерHTTP 403) и причина под строкой. Вvariant="timeline"— inline-строка с приглушённым фавиконом. - complete +
url— кликабельная карточка-ссылка: фавикон,Read · siteName, дата, бейджtruncated, заголовок страницы. Anchor-атрибуты (rel,download,referrerPolicy) пробрасываются на ссылку. - complete без
url— карточка/строка «No page to read.» (переопределяется черезemptyLabel), чтобы завершённыйweb_fetchбез ссылки не исчезал из таймлайна.
variant="timeline" для ChatWebSearch и ChatWebFetch вместе в одной ленте chat-message:
Давайте я это уточню.
"use client";
import {
ChatMessage,
ChatMessageAvatar,
ChatMessageContent,
} from "@/components/ui/chat-message";
import {
ChatWebFetch,
ChatWebSearch,
type ChatWebSearchResult,
} from "@/components/ui/chat-web-search";
const results: ChatWebSearchResult[] = [
{
url: "https://nextjs.org/docs/app/api-reference/directives/use-cache",
title: "Директива use cache – Next.js",
domain: "nextjs.org",
},
{
url: "https://react.dev/reference/rsc/server-components",
title: "Серверные компоненты – React",
domain: "react.dev",
},
{
url: "https://base-ui.com/react/overview/quick-start",
title: "Быстрый старт – Base UI",
domain: "base-ui.com",
},
];
export default function Particle() {
return (
<div className="w-full max-w-2xl p-4">
<ChatMessage from="assistant">
<ChatMessageAvatar fallback="J" />
<ChatMessageContent>
<p>Давайте я это уточню.</p>
<ChatWebSearch
query="кэширование компонентов в Next.js"
results={results}
status="complete"
variant="timeline"
/>
<ChatWebFetch
publishedDate="Май 2026"
siteName="Next.js"
status="complete"
title="Cache Components — кэширование и ревалидация в Next.js 16"
url="https://nextjs.org/docs/app/getting-started/cache-components"
variant="timeline"
/>
</ChatMessageContent>
</ChatMessage>
</div>
);
}
ChatWebFetchGroup
- pages.length > 0 — заголовок «Read N pages» + инсет-список; упавшие строки (
error: true) рендерятся grayscale-фавиконом и зачёркнутымline-throughзаголовком. - pages.length === 0 — строка «No pages read.» (переопределяется через
emptyLabel).
ChatWebFetch (complete + truncated), ChatWebFetchGroup с упавшей строкой и ChatWebFetch в error с бейджем errorCode:
"use client";
import {
ChatWebFetch,
ChatWebFetchGroup,
type ChatWebFetchPage,
} from "@/components/ui/chat-web-search";
const pages: ChatWebFetchPage[] = [
{
url: "https://nextjs.org/blog/next-16",
title: "Next.js 16",
domain: "nextjs.org",
},
{
url: "https://react.dev/blog/2026/02/14/react-labs",
title: "React Labs: февраль 2026",
domain: "react.dev",
},
{
url: "https://paywalled.example.com/premium-article",
title: "Премиум-аналитика (заблокировано)",
domain: "paywalled.example.com",
error: true,
},
];
export default function Particle() {
return (
<div className="flex w-full flex-col gap-3">
<ChatWebFetch
publishedDate="Апр 2026"
siteName="Vercel"
status="complete"
title="Состояние фронтенда 2026 — что изменилось и почему это важно"
truncated
url="https://vercel.com/blog/state-of-frontend-2026"
/>
<ChatWebFetchGroup pages={pages} />
<ChatWebFetch
domain="archive.example.org"
error="Доступ запрещён — похоже, сайт блокирует автоматические запросы."
errorCode="HTTP 403"
status="error"
url="https://archive.example.org/locked"
/>
</div>
);
}
variant: 'card' | 'timeline' — card это самостоятельная карточка; timeline (flush) убирает рамку/фон/тень и ведущую иконку, чтобы жить внутри жёлоба chat-message.
Фавиконы берутся из result.faviconUrl, либо из getFaviconUrl(domain) (по умолчанию — Google s2 endpoint, можно переопределить или отключить). Все подписи переопределяются через label-пропсы.