- 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 Compaction
Разделитель и time-based индикатор сжатия истории беседы в ленте сообщений.
import { ChatCompactionDivider } from "@/components/ui/chat-compaction";
export default function Particle() {
return (
<div className="flex w-full max-w-2xl flex-col gap-2 p-4">
<div className="self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground text-sm">
Подведи итог: к чему мы пришли по схеме API?
</div>
<div className="self-start rounded-lg rounded-bl-sm bg-muted px-3 py-2 text-sm">
Остановились на версионируемом REST-интерфейсе с курсорной пагинацией.
</div>
<ChatCompactionDivider />
<div className="self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground text-sm">
Отлично — теперь набросай заметки по миграции.
</div>
</div>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/chat-compaction
Использование
Один файл экспортирует два независимых под-компонента: ChatCompactionDivider (косметический разделитель) и ChatCompactionProgress (живой индикатор сжатия).
import {
ChatCompactionDivider,
ChatCompactionProgress,
} from "@/components/ui/chat-compaction";
<ChatConversation>
<ChatConversationContent>
<ChatMessage from="user">…</ChatMessage>
<ChatMessage from="assistant">…</ChatMessage>
<ChatCompactionDivider label="Earlier messages summarized" />
<ChatMessage from="user">…</ChatMessage>
</ChatConversationContent>
</ChatConversation>Разделитель
ChatCompactionDivider ставится сразу после сообщений, попавших под сжатие. Корень рендерит две тонкие линии (<Separator>) по краям приглушённой подписи — мини-разделитель в ленте. Никаких иконок и кнопок.
<ChatCompactionDivider label="Earlier messages summarized" />label по умолчанию "Earlier messages summarized"; aria-label берётся из строкового label, иначе fallback на дефолтную строку.
Прогресс
ChatCompactionProgress имитирует индикатор сжатия в claude.ai: точка-маркер слева, подпись и тонкая полоса прогресса с реальным процентом справа.
<ChatCompactionProgress isCompacting={running} />Индикатор time-based: процент равномерно растёт от 0 до 95 за estimatedDurationMs (по умолчанию 15000), затем мгновенно достигает 100 %, когда isCompacting становится false. Через 600 мс показа финальных 100 % компонент демонтируется. Пока isCompacting ещё не запускался и компонент не виден — рендерится null.
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { ChatCompactionProgress } from "@/components/ui/chat-compaction";
export default function Particle() {
const [isCompacting, setIsCompacting] = useState(false);
// Simulate a compaction cycle: run for ~4s, then settle.
useEffect(() => {
if (!isCompacting) return;
const timer = setTimeout(() => setIsCompacting(false), 4000);
return () => clearTimeout(timer);
}, [isCompacting]);
return (
<div className="flex w-full max-w-md flex-col gap-4 p-4">
<Button
disabled={isCompacting}
onClick={() => setIsCompacting(true)}
variant="outline"
>
{isCompacting ? "Сжатие…" : "Начать сжатие"}
</Button>
<ChatCompactionProgress
estimatedDurationMs={4000}
isCompacting={isCompacting}
/>
</div>
);
}
Состояния
| Состояние | Поведение |
|---|---|
| hidden | visible=false → возвращается null |
| compacting | isCompacting=true, процент ползёт 0→95 за estimatedDurationMs |
| finishing | isCompacting=false, процент мгновенно 100, dwell 600 мс |
| complete | процент 100, aria-live объявляет финал перед демонтажом |
Переопределение индикатора
Полоса прогресса собрана на DS-примитиве <Progress> / <ProgressTrack> / <ProgressIndicator>. Заливка индикатора по умолчанию — bg-foreground (намеренное отклонение от DS-дефолта bg-primary, сохранённое 1:1 с источником). Переопределяется через indicatorClassName:
{/* Вернуть DS-дефолт — тёплый --primary */}
<ChatCompactionProgress isCompacting={running} indicatorClassName="bg-primary" />Сценарий внутри ленты
Разделитель сверху, индикатор снизу — типичный сценарий внутри ленты с заданными label / estimatedDurationMs.
Суммирую последние сообщения…
"use client";
import { useEffect, useState } from "react";
import {
ChatCompactionDivider,
ChatCompactionProgress,
} from "@/components/ui/chat-compaction";
export default function Particle() {
const [isCompacting, setIsCompacting] = useState(true);
// Loop the demo cycle so the in-feed scenario stays animated.
useEffect(() => {
const timer = setTimeout(
() => setIsCompacting((prev) => !prev),
isCompacting ? 6000 : 1500,
);
return () => clearTimeout(timer);
}, [isCompacting]);
return (
<div className="flex w-full max-w-2xl flex-col gap-2 p-4">
<div className="self-start rounded-lg rounded-bl-sm bg-muted px-3 py-2 text-sm">
Ранее мы подробно разобрали процесс онбординга.
</div>
<ChatCompactionDivider label="Обсуждение онбординга суммировано" />
<div className="self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground text-sm">
Продолжим с того места, где остановились, и наметим следующий этап.
</div>
<div className="mt-2">
<ChatCompactionProgress
estimatedDurationMs={6000}
isCompacting={isCompacting}
label="Суммирую последние сообщения…"
/>
</div>
</div>
);
}