@softize/opus 12.8.1 → 12.9.0
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 +7 -0
- package/package.json +1 -1
- package/src/ui/components/primitives/chat.tsx +24 -4
- package/src/ui/docs/content/chat.md +24 -0
- package/src/ui/meta.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 12.9.0 — 2026-09-03
|
|
11
|
+
|
|
12
|
+
`Chat` aceita `empty`, um nó React para o estado sem mensagens: o app compõe o vazio com os
|
|
13
|
+
slots de `Empty` (marca, nome do agente, saudação administrável) e entrega no mesmo lugar do
|
|
14
|
+
`greeting`, que continua valendo como atalho de uma frase. `empty` vence quando os dois existem
|
|
15
|
+
e some quando a conversa começa; o slot ganha `data-variant="composed"`. Mudança aditiva.
|
|
16
|
+
|
|
10
17
|
## 12.8.1 — 2026-09-02
|
|
11
18
|
|
|
12
19
|
Num monorepo pnpm em que o workspace do contrato e o da SPA resolvem cópias distintas do Opus
|
package/package.json
CHANGED
|
@@ -71,6 +71,11 @@ export interface ChatProps {
|
|
|
71
71
|
/** Texto do estado vazio (centrado, some quando a conversa começa). Não entra no
|
|
72
72
|
* transcript — é apresentação, não fala do assistente. */
|
|
73
73
|
greeting?: string
|
|
74
|
+
/** Estado vazio composto pelo app (ex.: `EmptyHeader` com a marca, o nome e a saudação do
|
|
75
|
+
* agente). Ocupa o mesmo lugar do `greeting` e vence quando os dois existem; `null` ou
|
|
76
|
+
* `false` deixam o `greeting` valer; some quando a conversa começa. Prefira `greeting`
|
|
77
|
+
* quando bastar uma frase. */
|
|
78
|
+
empty?: React.ReactNode
|
|
74
79
|
/** Histórico inicial do modo autogerenciado (reidratação). Troque a `key` do componente
|
|
75
80
|
* ao trocar de conversa — o estado interno reinicia com estas mensagens. */
|
|
76
81
|
initialMessages?: ChatMessage[]
|
|
@@ -107,6 +112,7 @@ interface ChatTranscriptProps {
|
|
|
107
112
|
items: ChatTranscriptItem[]
|
|
108
113
|
indicator: string | null | undefined
|
|
109
114
|
greeting: string | undefined
|
|
115
|
+
empty: React.ReactNode | undefined
|
|
110
116
|
renderArtifact: ChatProps['renderArtifact']
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -120,11 +126,15 @@ const ChatTranscript = React.memo(function ChatTranscript({
|
|
|
120
126
|
items,
|
|
121
127
|
indicator,
|
|
122
128
|
greeting,
|
|
129
|
+
empty,
|
|
123
130
|
renderArtifact,
|
|
124
131
|
}: ChatTranscriptProps): React.ReactElement {
|
|
125
132
|
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
126
133
|
const turns = React.useMemo(() => groupTurns(items), [items])
|
|
127
134
|
const indicatorVisible = indicator !== undefined
|
|
135
|
+
// `null`/`false` em `empty` significam "sem nó" (o dado ainda não chegou): o greeting
|
|
136
|
+
// continua como fallback em vez de um wrapper composto vazio.
|
|
137
|
+
const composedEmpty = empty !== undefined && empty !== null && empty !== false
|
|
128
138
|
React.useEffect(() => {
|
|
129
139
|
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight })
|
|
130
140
|
}, [items, indicator])
|
|
@@ -135,11 +145,19 @@ const ChatTranscript = React.memo(function ChatTranscript({
|
|
|
135
145
|
data-slot="chat-scroll"
|
|
136
146
|
className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-4"
|
|
137
147
|
>
|
|
138
|
-
{items.length === 0 && !indicatorVisible &&
|
|
139
|
-
<div data-slot="chat-empty" className="m-auto
|
|
140
|
-
|
|
141
|
-
<p className="text-sm leading-relaxed">{greeting}</p>
|
|
148
|
+
{items.length === 0 && !indicatorVisible && composedEmpty ? (
|
|
149
|
+
<div data-slot="chat-empty" data-variant="composed" className="m-auto flex justify-center text-center">
|
|
150
|
+
{empty}
|
|
142
151
|
</div>
|
|
152
|
+
) : (
|
|
153
|
+
items.length === 0 &&
|
|
154
|
+
!indicatorVisible &&
|
|
155
|
+
greeting !== undefined && (
|
|
156
|
+
<div data-slot="chat-empty" className="m-auto max-w-[17.5rem] text-center text-muted-foreground">
|
|
157
|
+
<div className="mb-2 text-4xl">✦</div>
|
|
158
|
+
<p className="text-sm leading-relaxed">{greeting}</p>
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
143
161
|
)}
|
|
144
162
|
{turns.map((turn, ti) => (
|
|
145
163
|
// Hierarquia do espaço: as falas de um mesmo turno são um raciocínio contínuo
|
|
@@ -235,6 +253,7 @@ export function Chat({
|
|
|
235
253
|
composerActions,
|
|
236
254
|
composerClassName,
|
|
237
255
|
greeting,
|
|
256
|
+
empty,
|
|
238
257
|
initialMessages,
|
|
239
258
|
kickoff,
|
|
240
259
|
renderArtifact,
|
|
@@ -374,6 +393,7 @@ export function Chat({
|
|
|
374
393
|
items={items}
|
|
375
394
|
indicator={indicator}
|
|
376
395
|
greeting={greeting}
|
|
396
|
+
empty={empty}
|
|
377
397
|
renderArtifact={renderArtifact}
|
|
378
398
|
/>
|
|
379
399
|
{/* Composer da casa (pílula elevada, enviar dentro). Extraído no <Composer> — o Chat
|
|
@@ -15,6 +15,30 @@ Um chat mínimo: lista de mensagens + composer. A conversa é gerenciada por den
|
|
|
15
15
|
</div>
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Estado vazio composto
|
|
19
|
+
|
|
20
|
+
Quando uma frase não basta — marca, nome do agente e saudação administrável, por exemplo — o
|
|
21
|
+
app compõe o vazio com os slots de `Empty` e entrega em `empty`. O nó ocupa o mesmo lugar do
|
|
22
|
+
`greeting`, vence quando os dois existem e some quando a conversa começa. Sem a moldura `Empty`
|
|
23
|
+
em volta: o chat já é a estrutura, e a moldura tracejada é para região de criação.
|
|
24
|
+
|
|
25
|
+
```tsx preview
|
|
26
|
+
<div className="h-72 rounded-lg border">
|
|
27
|
+
<Chat
|
|
28
|
+
empty={
|
|
29
|
+
<EmptyHeader>
|
|
30
|
+
<EmptyMedia variant="icon">
|
|
31
|
+
<Sparkles />
|
|
32
|
+
</EmptyMedia>
|
|
33
|
+
<EmptyTitle>Copilot</EmptyTitle>
|
|
34
|
+
<EmptyDescription>Pergunte sobre vendas, estoque, oficina ou pessoas.</EmptyDescription>
|
|
35
|
+
</EmptyHeader>
|
|
36
|
+
}
|
|
37
|
+
send={async () => 'Pronto.'}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
```
|
|
41
|
+
|
|
18
42
|
## O backend (o agente)
|
|
19
43
|
|
|
20
44
|
O `send` posta a conversa no seu endpoint, que resolve o usuário e roda o agente sobre as actions `ai:enabled` — **como o usuário logado**:
|
package/src/ui/meta.ts
CHANGED
|
@@ -75,7 +75,7 @@ export const componentMeta = {
|
|
|
75
75
|
name: 'chat',
|
|
76
76
|
ancestry: 'opus',
|
|
77
77
|
whenToUse:
|
|
78
|
-
'Chat da casa (lista de mensagens + composer) que gerencia a conversa por dentro (estado/loading/auto-scroll; Enter envia, Shift+Enter quebra linha). A inteligência vem da prop `send` — resposta inteira (Promise<string>) OU streaming (AsyncIterable<ChatEvent>: texto incremental, indicador vivo do tool, artefato via renderArtifact). No Opus, o backend liga em `runtime.aiFor(base).run(...)` ou `.runStream(...)`. Dê altura ao container (ex.: `h-full`).',
|
|
78
|
+
'Chat da casa (lista de mensagens + composer) que gerencia a conversa por dentro (estado/loading/auto-scroll; Enter envia, Shift+Enter quebra linha). A inteligência vem da prop `send` — resposta inteira (Promise<string>) OU streaming (AsyncIterable<ChatEvent>: texto incremental, indicador vivo do tool, artefato via renderArtifact). No Opus, o backend liga em `runtime.aiFor(base).run(...)` ou `.runStream(...)`. Estado vazio por `greeting` (frase) ou `empty` (nó composto com os slots de Empty). Dê altura ao container (ex.: `h-full`).',
|
|
79
79
|
},
|
|
80
80
|
'checkbox': {
|
|
81
81
|
name: 'checkbox',
|