@softize/opus 8.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { ChatEvent } from '../../../core/index.ts'
|
|
3
|
+
import { cn } from '../../lib/cn.ts'
|
|
4
|
+
import { Composer } from './composer.tsx'
|
|
5
|
+
import { Markdown } from './markdown.tsx'
|
|
6
|
+
|
|
7
|
+
export interface ChatMessage {
|
|
8
|
+
role: 'user' | 'assistant'
|
|
9
|
+
content: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Artefato produzido na conversa (evento `artifact` do protocolo). */
|
|
13
|
+
export interface ChatArtifact {
|
|
14
|
+
kind: string
|
|
15
|
+
ref: string
|
|
16
|
+
title?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Item do transcript no modo CONTROLADO (o app é o dono do estado — ex.: sala
|
|
20
|
+
* server-autoritativa com replay, como o ChatRail do Maestro). */
|
|
21
|
+
export type ChatTranscriptItem =
|
|
22
|
+
| { role: 'user' | 'assistant' | 'error'; content: string }
|
|
23
|
+
| { role: 'artifact'; artifact: ChatArtifact }
|
|
24
|
+
|
|
25
|
+
/** Feedback humano do tool em uso (default pt-BR; override por prop). */
|
|
26
|
+
function defaultHumanizeTool(name: string): string {
|
|
27
|
+
const n = name.toLowerCase()
|
|
28
|
+
if (/read|glob|grep|search|list|view|query/.test(n)) return 'Consultando…'
|
|
29
|
+
if (/edit|write|create|update/.test(n)) return 'Escrevendo…'
|
|
30
|
+
if (/fetch|web/.test(n)) return 'Pesquisando…'
|
|
31
|
+
return 'Trabalhando…'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A fala do usuário no topo do turno — o card que GRUDA (sticky) enquanto o turno está
|
|
36
|
+
* em vista.
|
|
37
|
+
*
|
|
38
|
+
* Ela nasce com TETO de altura, e o motivo é concreto: um brief longo (o caso normal na
|
|
39
|
+
* cabine do Maestro, onde a primeira mensagem é o enunciado inteiro da tarefa) ocupava a
|
|
40
|
+
* sala toda — e, por ser sticky, ficava lá, cobrindo a resposta que a pessoa está
|
|
41
|
+
* esperando ler. Quanto mais caprichado o enunciado, menos se via do trabalho.
|
|
42
|
+
*
|
|
43
|
+
* Truncar sozinho não serve: esta é a ÚNICA cópia do texto na tela, então o corte precisa
|
|
44
|
+
* vir com o gesto de abrir. Colapsado mostra o começo com um esmaecido no pé; aberto
|
|
45
|
+
* mostra tudo e continua sticky.
|
|
46
|
+
*
|
|
47
|
+
* O botão só aparece quando há de fato o que revelar — medido no DOM, não adivinhado por
|
|
48
|
+
* contagem de caracteres, que erra com markdown (uma tabela ocupa muito mais que o texto
|
|
49
|
+
* dela sugere). A medida só vale COLAPSADO: aberto, `scrollHeight === clientHeight` e a
|
|
50
|
+
* pergunta "transborda?" passaria a responder não, sumindo com o botão de fechar.
|
|
51
|
+
*/
|
|
52
|
+
function UserTurn({ content }: { content: string }): React.ReactElement {
|
|
53
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
54
|
+
const [overflows, setOverflows] = React.useState(false)
|
|
55
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
56
|
+
const [cap, setCap] = React.useState<number>()
|
|
57
|
+
|
|
58
|
+
// Teto do EXPANDIDO, em px, medido no scroll container — não em `vh`.
|
|
59
|
+
//
|
|
60
|
+
// A primeira tentativa usou `60vh`, e a revisão mostrou por que não serve: o bloco que
|
|
61
|
+
// contém o sticky é o scroll container, cuja altura vem do CONSUMIDOR (`h-full`), não da
|
|
62
|
+
// tela. Num contêiner mais baixo que 60vh — um `<Chat>` embutido num painel de 400px numa
|
|
63
|
+
// tela de 1080, por exemplo — o card pinado fica mais alto que o próprio scroll port, e o
|
|
64
|
+
// botão de fechar sai da região visível e NÃO volta enquanto o turno estiver em vista.
|
|
65
|
+
// Era o mesmo defeito, alcançado por outra porta.
|
|
66
|
+
//
|
|
67
|
+
// CONTRATO que isto passa a cobrar: o `<Chat>` precisa de altura LIMITADA pelo pai. Já
|
|
68
|
+
// era assim de fato (sem isso o `overflow-y-auto` e o `sticky` não significam nada), mas
|
|
69
|
+
// agora tem dente: com altura auto, o container é dimensionado pelo CONTEÚDO, e aí capar
|
|
70
|
+
// encolhe o conteúdo, que baixa o teto, que encolhe de novo — espiral, e provável
|
|
71
|
+
// "ResizeObserver loop completed with undelivered notifications". Com `vh` não existia,
|
|
72
|
+
// porque a viewport não depende do conteúdo.
|
|
73
|
+
React.useLayoutEffect(() => {
|
|
74
|
+
const scroll = ref.current?.closest('[data-slot="chat-scroll"]')
|
|
75
|
+
if (!(scroll instanceof HTMLElement)) return
|
|
76
|
+
const apply = (): void => setCap(Math.round(scroll.clientHeight * 0.6))
|
|
77
|
+
apply()
|
|
78
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
79
|
+
const ro = new ResizeObserver(apply)
|
|
80
|
+
ro.observe(scroll)
|
|
81
|
+
return () => ro.disconnect()
|
|
82
|
+
}, [])
|
|
83
|
+
|
|
84
|
+
React.useLayoutEffect(() => {
|
|
85
|
+
if (expanded) return
|
|
86
|
+
const el = ref.current
|
|
87
|
+
if (el === null) return
|
|
88
|
+
const measure = (): void => setOverflows(el.scrollHeight > el.clientHeight + 1)
|
|
89
|
+
measure()
|
|
90
|
+
// A largura muda o quebra-linha, e com ela a resposta: um texto que cabe no rail
|
|
91
|
+
// aberto transborda com ele estreito. Sem observar, o botão some ou sobra na hora
|
|
92
|
+
// errada — e o rail do Maestro é redimensionável.
|
|
93
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
94
|
+
const ro = new ResizeObserver(measure)
|
|
95
|
+
ro.observe(el)
|
|
96
|
+
return () => ro.disconnect()
|
|
97
|
+
}, [content, expanded])
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div data-slot="chat-user" className="break-words rounded-xl bg-card px-3 py-2 text-sm leading-snug ring-1 ring-edge shadow-card">
|
|
101
|
+
<div
|
|
102
|
+
ref={ref}
|
|
103
|
+
data-slot="chat-user-body"
|
|
104
|
+
// Teto NOS DOIS estados, e o do aberto não é zelo: soltar a altura recriava o
|
|
105
|
+
// defeito que este componente veio matar, agora atrás de um clique — o card segue
|
|
106
|
+
// `sticky` com fundo opaco e volta a pintar por cima das falas seguintes. Pior, o
|
|
107
|
+
// botão de fechar é o último filho: com card mais alto que a viewport ele fica
|
|
108
|
+
// abaixo da dobra, e o sticky só consegue subir a altura da RESPOSTA (centenas de
|
|
109
|
+
// px contra milhares de card, no caso-alvo de brief enorme + resposta curta). A
|
|
110
|
+
// pessoa abria o brief e ficava sem como fechar. Com teto, o card inteiro cabe e o
|
|
111
|
+
// botão está sempre alcançável; o brief se lê rolando DENTRO dele.
|
|
112
|
+
className={cn(expanded ? 'overflow-y-auto' : 'max-h-40 overflow-hidden')}
|
|
113
|
+
style={expanded ? { maxHeight: cap } : undefined}
|
|
114
|
+
>
|
|
115
|
+
<Markdown content={content} />
|
|
116
|
+
{/* Esmaecido DENTRO do body, colado no fim do texto: como irmão do botão ele caía
|
|
117
|
+
~28px abaixo do corte (mt-1 + linha do botão + py-2), e a maior parte dos 40px
|
|
118
|
+
pintava a faixa do botão em vez do texto truncado. */}
|
|
119
|
+
{overflows && !expanded && (
|
|
120
|
+
<div className="pointer-events-none sticky bottom-0 -mt-10 h-10 bg-gradient-to-t from-card to-transparent" />
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
{overflows && (
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={() => setExpanded((v) => !v)}
|
|
127
|
+
className="mt-1 text-xs text-muted-foreground underline-offset-2 hover:underline"
|
|
128
|
+
>
|
|
129
|
+
{expanded ? 'Mostrar menos' : 'Mostrar mais'}
|
|
130
|
+
</button>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface ChatProps {
|
|
137
|
+
/** Modo AUTOGERENCIADO: envia a conversa (o histórico, já com a nova mensagem) e devolve
|
|
138
|
+
* a resposta — `Promise<string>` (request/response) OU `AsyncIterable<ChatEvent>`
|
|
139
|
+
* (streaming; docs/chat-event-protocol.md). No Opus, o backend liga em
|
|
140
|
+
* `runtime.aiFor(base).run(...)` ou `.runStream(...)`. Ignorado no modo controlado. */
|
|
141
|
+
send?: (messages: ChatMessage[]) => Promise<string> | AsyncIterable<ChatEvent>
|
|
142
|
+
/** Modo CONTROLADO: o transcript vem daqui e o app é o dono do estado (transporte
|
|
143
|
+
* próprio — SSE com replay, etc.). Presente → `onSend`/`busy`/`activity` assumem. */
|
|
144
|
+
messages?: ChatTranscriptItem[]
|
|
145
|
+
/** Modo controlado: recebe o texto enviado. Retornar `false` devolve o texto ao
|
|
146
|
+
* composer (falha de envio — nada de perder o que o usuário digitou). */
|
|
147
|
+
onSend?: (text: string) => void | boolean | Promise<void | boolean>
|
|
148
|
+
/** Modo controlado: trava o composer enquanto o turno corre. */
|
|
149
|
+
busy?: boolean
|
|
150
|
+
/** Modo controlado: indicador vivo — `undefined` esconde, `null` mostra "Pensando…",
|
|
151
|
+
* string mostra o rótulo (já humanizado pelo app). */
|
|
152
|
+
activity?: string | null
|
|
153
|
+
/** Avisos do app acima do composer (credencial, agente desatualizado…). */
|
|
154
|
+
notice?: React.ReactNode
|
|
155
|
+
/** Seletores discretos na barra do composer (o `actions` do `<Composer>`): agente,
|
|
156
|
+
* app, escopo… O lugar deles é a barra — `notice` é pra aviso, não pra controle. */
|
|
157
|
+
composerActions?: React.ReactNode
|
|
158
|
+
/** Texto do estado vazio (centrado, some quando a conversa começa). Não entra no
|
|
159
|
+
* transcript — é apresentação, não fala do assistente. */
|
|
160
|
+
greeting?: string
|
|
161
|
+
/** Histórico inicial do modo autogerenciado (reidratação). Troque a `key` do componente
|
|
162
|
+
* ao trocar de conversa — o estado interno reinicia com estas mensagens. */
|
|
163
|
+
initialMessages?: ChatMessage[]
|
|
164
|
+
/** Modo autogerenciado: conversa que COMEÇA pelo assistente — roda uma vez no mount
|
|
165
|
+
* quando o transcript nasce vazio (agente proativo: relatório recém-criado, sessão
|
|
166
|
+
* nova…). Mesmo contrato de retorno do `send`, sem mensagem do usuário. */
|
|
167
|
+
kickoff?: () => Promise<string> | AsyncIterable<ChatEvent>
|
|
168
|
+
/** Render do evento `artifact` (card, iframe, preview…). Default: link com o título. */
|
|
169
|
+
renderArtifact?: (artifact: ChatArtifact) => React.ReactNode
|
|
170
|
+
/** Rótulo humano do tool em uso no indicador vivo (modo autogerenciado). */
|
|
171
|
+
humanizeTool?: (name: string, detail?: string) => string
|
|
172
|
+
placeholder?: string
|
|
173
|
+
className?: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isAsyncIterable(value: unknown): value is AsyncIterable<ChatEvent> {
|
|
177
|
+
return typeof value === 'object' && value !== null && Symbol.asyncIterator in value
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Agrupa o transcript em turnos (mensagem do usuário + o que veio em resposta) —
|
|
181
|
+
* o cabeçalho do turno fica sticky enquanto o turno está em vista. */
|
|
182
|
+
function groupTurns(items: ChatTranscriptItem[]): Array<{ user: ChatTranscriptItem | null; rest: ChatTranscriptItem[] }> {
|
|
183
|
+
const turns: Array<{ user: ChatTranscriptItem | null; rest: ChatTranscriptItem[] }> = []
|
|
184
|
+
for (const item of items) {
|
|
185
|
+
if (item.role === 'user') turns.push({ user: item, rest: [] })
|
|
186
|
+
else {
|
|
187
|
+
if (turns.length === 0) turns.push({ user: null, rest: [] })
|
|
188
|
+
turns[turns.length - 1].rest.push(item)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return turns
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Chat da casa: lista de mensagens + composer (Enter envia / Shift+Enter quebra linha),
|
|
196
|
+
* transcript em turnos com o cabeçalho sticky (a pergunta fica à vista enquanto a
|
|
197
|
+
* resposta rola), assistente em Markdown direto no corpo, tool como indicador vivo
|
|
198
|
+
* (nunca mensagem) e artefatos via `renderArtifact`. Dois modos:
|
|
199
|
+
*
|
|
200
|
+
* - **Autogerenciado** (`send`): o componente é dono da conversa; o `send` devolve
|
|
201
|
+
* `Promise<string>` ou `AsyncIterable<ChatEvent>` (streaming).
|
|
202
|
+
* - **Controlado** (`messages` + `onSend` + `busy`/`activity`): o app é dono do estado —
|
|
203
|
+
* o caso de transporte próprio (SSE server-autoritativo com replay, como o Maestro).
|
|
204
|
+
*
|
|
205
|
+
* Dê altura ao container (ex.: `h-full`).
|
|
206
|
+
*/
|
|
207
|
+
export function Chat({
|
|
208
|
+
send,
|
|
209
|
+
messages,
|
|
210
|
+
onSend,
|
|
211
|
+
busy: busyProp,
|
|
212
|
+
activity: activityProp,
|
|
213
|
+
notice,
|
|
214
|
+
composerActions,
|
|
215
|
+
greeting,
|
|
216
|
+
initialMessages,
|
|
217
|
+
kickoff,
|
|
218
|
+
renderArtifact,
|
|
219
|
+
humanizeTool = defaultHumanizeTool,
|
|
220
|
+
placeholder = 'Escreva uma mensagem…',
|
|
221
|
+
className,
|
|
222
|
+
}: ChatProps): React.ReactElement {
|
|
223
|
+
const controlled = messages !== undefined
|
|
224
|
+
const [ownItems, setOwnItems] = React.useState<ChatTranscriptItem[]>(() => [...(initialMessages ?? [])])
|
|
225
|
+
const [input, setInput] = React.useState('')
|
|
226
|
+
const [ownBusy, setOwnBusy] = React.useState(false)
|
|
227
|
+
const [ownActivity, setOwnActivity] = React.useState<string | null>(null)
|
|
228
|
+
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
229
|
+
|
|
230
|
+
const items = controlled ? messages : ownItems
|
|
231
|
+
const busy = controlled ? (busyProp ?? false) : ownBusy
|
|
232
|
+
// Indicador: no controlado o app manda (undefined esconde); no autogerenciado segue o busy.
|
|
233
|
+
const indicator = controlled ? activityProp : ownBusy ? ownActivity : undefined
|
|
234
|
+
|
|
235
|
+
React.useEffect(() => {
|
|
236
|
+
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight })
|
|
237
|
+
}, [items, indicator])
|
|
238
|
+
|
|
239
|
+
const kickoffRan = React.useRef(false)
|
|
240
|
+
React.useEffect(() => {
|
|
241
|
+
if (controlled || kickoff === undefined || kickoffRan.current) return
|
|
242
|
+
if (ownItems.length > 0) return
|
|
243
|
+
kickoffRan.current = true
|
|
244
|
+
void consume(kickoff)
|
|
245
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
246
|
+
}, [])
|
|
247
|
+
|
|
248
|
+
async function submit(): Promise<void> {
|
|
249
|
+
const text = input.trim()
|
|
250
|
+
if (text === '' || busy) return
|
|
251
|
+
setInput('')
|
|
252
|
+
|
|
253
|
+
if (controlled) {
|
|
254
|
+
const ok = await onSend?.(text)
|
|
255
|
+
if (ok === false) setInput(text)
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const next: ChatTranscriptItem[] = [...ownItems, { role: 'user', content: text }]
|
|
260
|
+
setOwnItems(next)
|
|
261
|
+
const transcript = next.filter((i): i is ChatMessage => i.role === 'user' || i.role === 'assistant')
|
|
262
|
+
await consume(() => send!(transcript))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Turno autogerenciado (send ou kickoff): consome string/stream pro transcript próprio.
|
|
266
|
+
async function consume(run: () => Promise<string> | AsyncIterable<ChatEvent>): Promise<void> {
|
|
267
|
+
setOwnBusy(true)
|
|
268
|
+
setOwnActivity(null)
|
|
269
|
+
try {
|
|
270
|
+
const result = run()
|
|
271
|
+
if (isAsyncIterable(result)) {
|
|
272
|
+
let streaming = false
|
|
273
|
+
for await (const event of result) {
|
|
274
|
+
if (event.type === 'text') {
|
|
275
|
+
setOwnActivity(null)
|
|
276
|
+
setOwnItems((list) => {
|
|
277
|
+
const last = list[list.length - 1]
|
|
278
|
+
if (streaming && last !== undefined && last.role === 'assistant') {
|
|
279
|
+
return [...list.slice(0, -1), { role: 'assistant', content: last.content + event.delta }]
|
|
280
|
+
}
|
|
281
|
+
streaming = true
|
|
282
|
+
return [...list, { role: 'assistant', content: event.delta }]
|
|
283
|
+
})
|
|
284
|
+
} else if (event.type === 'tool') {
|
|
285
|
+
streaming = false
|
|
286
|
+
setOwnActivity(humanizeTool(event.name, event.detail))
|
|
287
|
+
} else if (event.type === 'artifact') {
|
|
288
|
+
streaming = false
|
|
289
|
+
setOwnItems((list) => [
|
|
290
|
+
...list,
|
|
291
|
+
{ role: 'artifact', artifact: { kind: event.kind, ref: event.ref, title: event.title } },
|
|
292
|
+
])
|
|
293
|
+
} else if (event.type === 'done') {
|
|
294
|
+
if (!event.ok) {
|
|
295
|
+
setOwnItems((list) => [...list, { role: 'error', content: event.error ?? 'Desculpe, algo falhou.' }])
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
const reply = await result
|
|
301
|
+
setOwnItems((list) => [...list, { role: 'assistant', content: reply }])
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
setOwnItems((list) => [...list, { role: 'error', content: 'Desculpe, algo falhou.' }])
|
|
305
|
+
} finally {
|
|
306
|
+
setOwnBusy(false)
|
|
307
|
+
setOwnActivity(null)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const indicatorVisible = indicator !== undefined
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div data-slot="chat" className={cn('flex h-full flex-col', className)}>
|
|
315
|
+
<div ref={scrollRef} data-slot="chat-scroll" className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-4">
|
|
316
|
+
{items.length === 0 && !indicatorVisible && greeting !== undefined && (
|
|
317
|
+
<div data-slot="chat-empty" className="m-auto max-w-[280px] text-center text-muted-foreground">
|
|
318
|
+
<div className="mb-2 text-4xl">✦</div>
|
|
319
|
+
<p className="text-sm leading-relaxed">{greeting}</p>
|
|
320
|
+
</div>
|
|
321
|
+
)}
|
|
322
|
+
{groupTurns(items).map((turn, ti) => (
|
|
323
|
+
// Hierarquia do espaço: as falas de um mesmo turno são um raciocínio contínuo
|
|
324
|
+
// (o agente narra o que vai fazendo), então andam juntas; quem separa é o gap
|
|
325
|
+
// MAIOR entre turnos. Com o mesmo gap nos dois níveis, um turno de oito falas
|
|
326
|
+
// curtas virava uma parede uniforme, sem começo nem fim visíveis.
|
|
327
|
+
<div key={ti} className="flex flex-col gap-1">
|
|
328
|
+
{turn.user !== null && turn.user.role === 'user' && (
|
|
329
|
+
// Cabeçalho do turno: gruda no topo enquanto o turno está em vista (igual
|
|
330
|
+
// Claude) — a mensagem do usuário num card, não num balão preenchido.
|
|
331
|
+
<div className="sticky top-0 z-10 py-1.5">
|
|
332
|
+
<UserTurn content={turn.user.content} />
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
{turn.rest.map((item, i) => {
|
|
336
|
+
if (item.role === 'artifact') {
|
|
337
|
+
return (
|
|
338
|
+
<div key={i} data-slot="chat-artifact" className="flex justify-start">
|
|
339
|
+
{renderArtifact !== undefined ? (
|
|
340
|
+
renderArtifact(item.artifact)
|
|
341
|
+
) : (
|
|
342
|
+
<a
|
|
343
|
+
href={item.artifact.ref}
|
|
344
|
+
target="_blank"
|
|
345
|
+
rel="noreferrer"
|
|
346
|
+
className="rounded-xl border bg-card px-3.5 py-2 text-sm underline-offset-2 hover:underline"
|
|
347
|
+
>
|
|
348
|
+
{item.artifact.title ?? item.artifact.ref}
|
|
349
|
+
</a>
|
|
350
|
+
)}
|
|
351
|
+
</div>
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
if (item.role === 'error') {
|
|
355
|
+
return (
|
|
356
|
+
<div
|
|
357
|
+
key={i}
|
|
358
|
+
className="max-w-[88%] self-start rounded-xl border border-destructive/30 bg-destructive/10 px-3.5 py-2.5 text-sm text-destructive"
|
|
359
|
+
>
|
|
360
|
+
{item.content}
|
|
361
|
+
</div>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
// O assistente responde direto no corpo, em Markdown (convenção da casa).
|
|
365
|
+
return (
|
|
366
|
+
<div
|
|
367
|
+
key={i}
|
|
368
|
+
className="break-words px-1 text-sm leading-snug"
|
|
369
|
+
>
|
|
370
|
+
<Markdown content={item.content} />
|
|
371
|
+
</div>
|
|
372
|
+
)
|
|
373
|
+
})}
|
|
374
|
+
</div>
|
|
375
|
+
))}
|
|
376
|
+
{indicatorVisible && (
|
|
377
|
+
<div data-slot="chat-activity" className="flex items-center gap-2 py-1 text-xs text-muted-foreground">
|
|
378
|
+
<span className="flex gap-1">
|
|
379
|
+
{[0, 1, 2].map((i) => (
|
|
380
|
+
<span
|
|
381
|
+
key={i}
|
|
382
|
+
className="h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground/50"
|
|
383
|
+
style={{ animationDelay: `${i * 150}ms` }}
|
|
384
|
+
/>
|
|
385
|
+
))}
|
|
386
|
+
</span>
|
|
387
|
+
<span>{indicator ?? 'Pensando…'}</span>
|
|
388
|
+
</div>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
391
|
+
{/* Composer da casa (pílula elevada, enviar dentro). Extraído no <Composer> — o Chat
|
|
392
|
+
liga texto/envio e repassa `composerActions` pros seletores da conversa (agente,
|
|
393
|
+
app…); o <Composer> sozinho segue sendo o caminho de quem não tem chat. */}
|
|
394
|
+
<div className="shrink-0 p-3">
|
|
395
|
+
{notice}
|
|
396
|
+
<Composer
|
|
397
|
+
value={input}
|
|
398
|
+
onChange={setInput}
|
|
399
|
+
onSubmit={() => void submit()}
|
|
400
|
+
busy={busy}
|
|
401
|
+
placeholder={placeholder}
|
|
402
|
+
actions={composerActions}
|
|
403
|
+
/>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
)
|
|
407
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { CheckIcon } from "lucide-react"
|
|
3
|
+
import { Checkbox as CheckboxPrimitive } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../lib/cn.ts'
|
|
6
|
+
|
|
7
|
+
function Checkbox({
|
|
8
|
+
className,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
11
|
+
return (
|
|
12
|
+
<CheckboxPrimitive.Root
|
|
13
|
+
data-slot="checkbox"
|
|
14
|
+
className={cn(
|
|
15
|
+
"peer size-4 shrink-0 rounded-[4px] border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
>
|
|
20
|
+
<CheckboxPrimitive.Indicator
|
|
21
|
+
data-slot="checkbox-indicator"
|
|
22
|
+
className="grid place-content-center text-current transition-none"
|
|
23
|
+
>
|
|
24
|
+
<CheckIcon className="size-3.5" />
|
|
25
|
+
</CheckboxPrimitive.Indicator>
|
|
26
|
+
</CheckboxPrimitive.Root>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { Checkbox }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
|
|
2
|
+
|
|
3
|
+
function Collapsible({
|
|
4
|
+
...props
|
|
5
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
|
6
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function CollapsibleTrigger({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
|
12
|
+
return (
|
|
13
|
+
<CollapsiblePrimitive.CollapsibleTrigger
|
|
14
|
+
data-slot="collapsible-trigger"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function CollapsibleContent({
|
|
21
|
+
...props
|
|
22
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
|
23
|
+
return (
|
|
24
|
+
<CollapsiblePrimitive.CollapsibleContent
|
|
25
|
+
data-slot="collapsible-content"
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Command as CommandPrimitive } from "cmdk"
|
|
3
|
+
import { SearchIcon } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../lib/cn.ts'
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from './dialog.tsx'
|
|
13
|
+
|
|
14
|
+
function Command({
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof CommandPrimitive>) {
|
|
18
|
+
return (
|
|
19
|
+
<CommandPrimitive
|
|
20
|
+
data-slot="command"
|
|
21
|
+
className={cn(
|
|
22
|
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function CommandDialog({
|
|
31
|
+
title = "Command Palette",
|
|
32
|
+
description = "Search for a command to run...",
|
|
33
|
+
children,
|
|
34
|
+
className,
|
|
35
|
+
showCloseButton = true,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof Dialog> & {
|
|
38
|
+
title?: string
|
|
39
|
+
description?: string
|
|
40
|
+
className?: string
|
|
41
|
+
showCloseButton?: boolean
|
|
42
|
+
}) {
|
|
43
|
+
return (
|
|
44
|
+
<Dialog {...props}>
|
|
45
|
+
<DialogHeader className="sr-only">
|
|
46
|
+
<DialogTitle>{title}</DialogTitle>
|
|
47
|
+
<DialogDescription>{description}</DialogDescription>
|
|
48
|
+
</DialogHeader>
|
|
49
|
+
<DialogContent
|
|
50
|
+
className={cn("overflow-hidden p-0", className)}
|
|
51
|
+
showCloseButton={showCloseButton}
|
|
52
|
+
>
|
|
53
|
+
<Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
54
|
+
{children}
|
|
55
|
+
</Command>
|
|
56
|
+
</DialogContent>
|
|
57
|
+
</Dialog>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function CommandInput({
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="command-input-wrapper"
|
|
68
|
+
className="flex h-9 items-center gap-2 border-b px-3"
|
|
69
|
+
>
|
|
70
|
+
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
|
71
|
+
<CommandPrimitive.Input
|
|
72
|
+
data-slot="command-input"
|
|
73
|
+
className={cn(
|
|
74
|
+
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
75
|
+
className
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function CommandList({
|
|
84
|
+
className,
|
|
85
|
+
...props
|
|
86
|
+
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
87
|
+
return (
|
|
88
|
+
<CommandPrimitive.List
|
|
89
|
+
data-slot="command-list"
|
|
90
|
+
className={cn(
|
|
91
|
+
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function CommandEmpty({
|
|
100
|
+
...props
|
|
101
|
+
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
102
|
+
return (
|
|
103
|
+
<CommandPrimitive.Empty
|
|
104
|
+
data-slot="command-empty"
|
|
105
|
+
className="py-6 text-center text-sm"
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function CommandGroup({
|
|
112
|
+
className,
|
|
113
|
+
...props
|
|
114
|
+
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
115
|
+
return (
|
|
116
|
+
<CommandPrimitive.Group
|
|
117
|
+
data-slot="command-group"
|
|
118
|
+
className={cn(
|
|
119
|
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
120
|
+
className
|
|
121
|
+
)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function CommandSeparator({
|
|
128
|
+
className,
|
|
129
|
+
...props
|
|
130
|
+
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
131
|
+
return (
|
|
132
|
+
<CommandPrimitive.Separator
|
|
133
|
+
data-slot="command-separator"
|
|
134
|
+
className={cn("-mx-1 h-px bg-border", className)}
|
|
135
|
+
{...props}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function CommandItem({
|
|
141
|
+
className,
|
|
142
|
+
...props
|
|
143
|
+
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
144
|
+
return (
|
|
145
|
+
<CommandPrimitive.Item
|
|
146
|
+
data-slot="command-item"
|
|
147
|
+
className={cn(
|
|
148
|
+
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
/>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function CommandShortcut({
|
|
157
|
+
className,
|
|
158
|
+
...props
|
|
159
|
+
}: React.ComponentProps<"span">) {
|
|
160
|
+
return (
|
|
161
|
+
<span
|
|
162
|
+
data-slot="command-shortcut"
|
|
163
|
+
className={cn(
|
|
164
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
165
|
+
className
|
|
166
|
+
)}
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
Command,
|
|
174
|
+
CommandDialog,
|
|
175
|
+
CommandInput,
|
|
176
|
+
CommandList,
|
|
177
|
+
CommandEmpty,
|
|
178
|
+
CommandGroup,
|
|
179
|
+
CommandItem,
|
|
180
|
+
CommandShortcut,
|
|
181
|
+
CommandSeparator,
|
|
182
|
+
}
|