Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions app/src/features/bots/BotHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,16 @@ export function BotHeader({
);

return (
<header className="flex items-center gap-3 border-line border-b px-5 py-2.5">
// In a narrow window the handle gives way first, then the button labels.
<header className="@container flex items-center gap-3 border-line border-b px-5 py-2.5">
<BotAvatar color={bot.color} size={36} mood={moodOf(bot, crew.paused)} />
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-2">
<h1 className="truncate font-semibold text-lg tracking-tight">{bot.name}</h1>
<span className="font-mono text-muted text-sm" data-selectable>
<span
className="min-w-0 shrink-[100] truncate font-mono text-muted text-sm"
data-selectable
>
@{bot.handle}
</span>
</div>
Expand All @@ -99,16 +103,16 @@ export function BotHeader({
</div>
</div>
{bot.paused ? (
<Button icon={Play} onClick={() => setPaused(false)}>
{words.resume}
<Button icon={Play} title={words.resume} onClick={() => setPaused(false)}>
<Label>{words.resume}</Label>
</Button>
) : (
<Button icon={Pause} onClick={() => setPaused(true)}>
{words.pause}
<Button icon={Pause} title={words.pause} onClick={() => setPaused(true)}>
<Label>{words.pause}</Label>
</Button>
)}
<Button icon={RotateCw} disabled={stopped} onClick={restart}>
{words.restart}
<Button icon={RotateCw} title={words.restart} disabled={stopped} onClick={restart}>
<Label>{words.restart}</Label>
</Button>
<span className="relative">
<Button
Expand Down Expand Up @@ -191,3 +195,8 @@ export function BotHeader({
</header>
);
}

/** A button's words, read aloud always but shown only where they fit. */
function Label({ children }: { children: string }) {
return <span className="sr-only @3xl:not-sr-only">{children}</span>;
}
6 changes: 3 additions & 3 deletions app/src/features/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ export const ChatComposer = memo(function ChatComposer({
placeholder={t.chat.composer.placeholder(bot.name)}
className="block max-h-60 w-full resize-none bg-transparent px-4 pt-3.5 pb-1.5 leading-relaxed outline-none placeholder:text-muted"
/>
{/* The meter and the effort open over the row's right edge. In a
narrow chat, what is on the right moves to a line of its own. */}
<div className="relative flex flex-wrap items-center gap-x-1 gap-y-1 px-2.5 pb-2.5">
{/* The meter and the effort open over the row's right edge. The row
never wraps: in a narrow chat the labels give way instead. */}
<div className="relative flex items-center gap-1 px-2.5 pb-2.5">
<button
type="button"
aria-label={t.chat.composer.attach}
Expand Down
6 changes: 3 additions & 3 deletions app/src/features/chat/ModePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function ModePicker({ bot, onLater }: { bot: Bot; onLater(text: string):
};

return (
<div ref={root} className="relative">
<div ref={root} className="relative shrink-0">
{open && (
<div
role="menu"
Expand Down Expand Up @@ -112,11 +112,11 @@ export function ModePicker({ bot, onLater }: { bot: Bot; onLater(text: string):
}`}
>
<Icon aria-hidden size={15} />
<span className="whitespace-nowrap">{m.names[current]}</span>
<span className="hidden whitespace-nowrap @sm:inline">{m.names[current]}</span>
<ChevronDown
aria-hidden
size={14}
className={`opacity-60 transition-transform duration-200 ${open ? "rotate-180" : ""}`}
className={`hidden opacity-60 transition-transform duration-200 @sm:block ${open ? "rotate-180" : ""}`}
/>
</button>
{confirming && (
Expand Down
2 changes: 1 addition & 1 deletion app/src/features/chat/ModelPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function ModelPicker({ bot, onLater }: { bot: Bot; onLater(text: string):
<ChevronDown
aria-hidden
size={14}
className={`shrink-0 opacity-60 transition-transform duration-200 ${open ? "rotate-180" : ""}`}
className={`hidden shrink-0 opacity-60 transition-transform duration-200 @xs:block ${open ? "rotate-180" : ""}`}
/>
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ Layout, como um app de mensagens:
- **Modelo do bot**, no compositor, ao lado do botão de enviar: mostra o modelo em uso ("Opus 5.5"; antes do primeiro turno, o nome escolhido ou "Padrão") e abre um menu para cima, "Modelo", com Padrão do plano, Fable, Opus, Sonnet e Haiku. Cada um tem uma linha que fala do bot pelo nome ("Scout fica rápido e capaz, bom para quase tudo"; o padrão diz qual modelo é hoje), e o pé do menu lembra que modelos mais capazes gastam o limite do plano mais rápido. A janela de criar e editar bot tem a mesma escolha. Com o bot ocupado, a mesma linha acima do compositor avisa que ele troca quando terminar.
- **Esforço do bot** (7.4), no compositor, ao lado do modelo: um botão com o nível em uso ("Médio") abre para cima "Esforço", um controle deslizante de cinco pontos, de "Mais rápido" a "Mais inteligente" (Baixo, Médio, Alto, Extra alto, Máximo), como o dos apps do Claude. O ponto **recomendado** vem marcado: é o nível que o modelo do bot usa sozinho (`effortDefault`). Embaixo, o nome do nível e uma linha que fala do bot pelo nome ("Scout equilibra rapidez e raciocínio, bom para quase tudo"); o pé lembra que mais esforço gasta o limite do plano mais rápido e, fora do recomendado, traz "Usar o recomendado". As palavras seguem o controle na hora, mas o bot só é avisado quando o controle fica 400 ms num ponto ou o menu fecha, para não reiniciar a cada passo. Parar no ponto recomendado grava `default`: o bot segue o nível do modelo, também depois de trocar de modelo. Enquanto o Claude Code não disse o nível do modelo, nada vem marcado e o botão diz só "Esforço". Num modelo sem níveis (Haiku) o botão fica apagado e o menu explica, sem o controle. Com o bot ocupado, a linha acima do compositor avisa que ele muda quando terminar.
- **Espaço da conversa** (8.6), no compositor, antes do esforço: um anel que enche com a parte da janela em uso e passa à cor de aviso a partir de 80% do caminho até a compactação automática. Um clique abre "Espaço da conversa": o uso ("556k / 1M (56%)"), uma barra com a marca de onde a conversa se compacta sozinha, quanto falta para isso ("Faltam 411k para ela se compactar sozinha, em 967k"), uma linha dizendo que conversa mais cheia gasta mais do plano a cada mensagem, e **Compactar agora** (`bots.compact`), com a linha do que isso faz. Durante a compactação o botão diz "Compactando…" e o anel pulsa; com o bot ocupado, a linha acima do compositor avisa que ele compacta quando terminar; com o bot parado, o botão fica desligado e diz por quê. Sem `Bot.context`, o anel não aparece. No chat, a compactação vira um aviso no idioma do dono: feita a pedido, feita sozinha porque a conversa encheu, ou o motivo de não ter dado.
- Num chat estreito, o que fica à direita no compositor (espaço, esforço, modelo e enviar) desce para uma linha própria, o esforço mostra só o ícone e o nome do modelo é cortado, em vez de tudo sair da caixa.
- Num chat estreito, a linha de baixo do compositor nunca se divide em duas: o modo e o esforço mostram só o ícone, as setinhas dos menus somem e o nome do modelo é cortado por último, em vez de tudo sair da caixa. No cabeçalho, o @ do bot é cortado antes do nome, e Pausar e Reiniciar mostram só o ícone (o nome fica na dica e para o leitor de tela).
- **Área principal com uma crew:** a timeline (messages entre os bots e do dono) e as tasks. Embaixo do nome, "Trabalha em <pasta>", que abre a pasta no Explorer; o menu da crew tem "Abrir pasta de trabalho" e "Mudar pasta de trabalho…", que abre o seletor de pastas do Windows e confirma antes de mudar, avisando que cada bot reinicia quando terminar o que está fazendo.
- **Nova crew:** nome; "Para que é esta equipe?", o objetivo que vira as instruções do chefe, com a explicação de que a equipe começa com um Chefe que planeja o trabalho e sugere os bots de que precisa; "Pasta de trabalho", com "Escolher pasta…" (o seletor do Windows; sem escolha, "Uma pasta nova dentro do Botloft", a `shared\`); e "Modelo do Chefe". Criada, o app abre o chat do chefe.
- **Chefe:** uma coroa ao lado do nome na barra lateral e nos cartões da crew, e "Chefe" no cabeçalho do bot, com a dica "Lidera <crew>: planeja o trabalho e sugere bots novos". O menu do bot tem "Tornar chefe da equipe" ou "Deixar de ser chefe".
Expand Down
Loading