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
7 changes: 2 additions & 5 deletions app/src/features/account/ArchivedSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { Archive, Bot, Crew } from "../../lib/protocol.gen";
import { useApi, useApp } from "../../store/context";
import { Button } from "../../ui/Button";
import { Callout } from "../../ui/Callout";
import { IconBadge } from "../../ui/ChatCard";
import { BotAvatar } from "../bots/BotAvatar";
import { DeleteBot } from "../bots/DeleteBot";
import { DeleteCrew } from "../crews/DeleteCrew";
Expand Down Expand Up @@ -73,11 +74,7 @@ export function ArchivedSettings() {
{archive.crews.map((crew) => (
<Row
key={crew.id}
icon={
<span className="grid size-7 place-items-center rounded-full bg-sunken text-muted">
<Users aria-hidden size={14} />
</span>
}
icon={<IconBadge icon={Users} tone="quiet" />}
name={crew.name}
line={s.archivedCrew(botsOf(crew).length, fromNow(crew.archivedAt ?? 0))}
onDelete={() => setAsking({ crew })}
Expand Down
9 changes: 3 additions & 6 deletions app/src/features/bots/BotHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { useT } from "../../i18n";
import type { Bot, Crew } from "../../lib/protocol.gen";
import { useApi, useApp } from "../../store/context";
import { Badge } from "../../ui/Badge";
import { Button } from "../../ui/Button";
import { Menu } from "../../ui/Menu";
import { attempt } from "../../ui/toast";
Expand Down Expand Up @@ -91,13 +92,9 @@ export function BotHeader({
<BotStateBadge bot={bot} crewPaused={crew.paused} />
{chief && <ChiefBadge crew={crew} />}
{bot.permissionMode === "bypass_permissions" && (
<span
title={t.chat.mode.badgeHint}
className="inline-flex shrink-0 items-center gap-1 rounded-full bg-danger/10 px-2 py-0.5 font-medium text-danger text-xs"
>
<ShieldOff aria-hidden size={12} />
<Badge tone="danger" icon={ShieldOff} title={t.chat.mode.badgeHint}>
{t.chat.mode.badge}
</span>
</Badge>
)}
<span className="truncate text-muted">{bot.role || words.noRole}</span>
</div>
Expand Down
13 changes: 3 additions & 10 deletions app/src/features/bots/BotStateBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
} from "lucide-react";
import { type Messages, t, useT } from "../../i18n";
import type { Bot, BotState } from "../../lib/protocol.gen";
import { TONES, type Tone as ToneName } from "../../ui/tone";
import { ReadyIcon, type StateIcon, WorkingIcon } from "./StateIcons";

type Tone = "ok" | "work" | "warn" | "danger" | "quiet";
type Tone = Exclude<ToneName, "accent">;

export interface StateView {
label: string;
Expand Down Expand Up @@ -57,14 +58,6 @@ export function stateView(
return { ...looks[bot.state], ...words[bot.state] };
}

const toneText: Record<Tone, string> = {
ok: "text-ok",
work: "text-work",
warn: "text-warn",
danger: "text-danger",
quiet: "text-quiet",
};

export function BotStateBadge({
bot,
crewPaused,
Expand All @@ -80,7 +73,7 @@ export function BotStateBadge({
return (
<span
title={view.hint}
className={`inline-flex shrink-0 items-center gap-1 font-medium transition-colors duration-300 ${compact ? "text-xs" : "text-sm"} ${toneText[view.tone]}`}
className={`inline-flex shrink-0 items-center gap-1 font-medium transition-colors duration-300 ${compact ? "text-xs" : "text-sm"} ${TONES[view.tone].text}`}
>
<Icon
aria-hidden
Expand Down
9 changes: 3 additions & 6 deletions app/src/features/bots/ChiefBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { Crown } from "lucide-react";
import { useT } from "../../i18n";
import type { Bot, Crew } from "../../lib/protocol.gen";
import { Badge } from "../../ui/Badge";

export const isChief = (bot: Bot, crew: Crew) => crew.leadBotId === bot.id;

Expand All @@ -18,12 +19,8 @@ export function ChiefBadge({ crew, compact = false }: { crew: Crew; compact?: bo
);
}
return (
<span
title={c.hint(crew.name)}
className="inline-flex shrink-0 items-center gap-1 rounded-full bg-accent/10 px-2 py-0.5 font-medium text-accent-text text-xs"
>
<Crown aria-hidden size={12} />
<Badge tone="accent" icon={Crown} title={c.hint(crew.name)}>
{c.badge}
</span>
</Badge>
);
}
27 changes: 7 additions & 20 deletions app/src/features/browser/BrowserPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { useT } from "../../i18n";
import type { Bot } from "../../lib/protocol.gen";
import { SYSTEM } from "../../lib/system";
import { useApp } from "../../store/context";
import { Badge } from "../../ui/Badge";
import { Button } from "../../ui/Button";
import { Callout } from "../../ui/Callout";
import { EmptyState } from "../../ui/EmptyState";
import { SidePanel } from "../../ui/SidePanel";
import { BotAvatar } from "../bots/BotAvatar";
import { AddressBar } from "./AddressBar";
Expand Down Expand Up @@ -81,19 +83,15 @@ export function BrowserPanel({
<h2 className="flex items-center gap-2 font-semibold text-sm">
{t.heading}
{resting ? (
<span
title={t.restingWhy(bot.name)}
className="inline-flex items-center gap-1.5 rounded-full bg-sunken px-2 py-0.5 font-medium text-muted text-xs"
>
<Moon aria-hidden size={11} />
<Badge tone="quiet" icon={Moon} title={t.restingWhy(bot.name)}>
{t.resting}
</span>
</Badge>
) : (
live && (
<span className="inline-flex items-center gap-1.5 rounded-full bg-danger/10 px-2 py-0.5 font-medium text-danger text-xs">
<Badge tone="danger">
<span aria-hidden className="live-dot" />
{t.live}
</span>
</Badge>
)
)}
</h2>
Expand Down Expand Up @@ -211,17 +209,6 @@ function Overlay({ busy, title, body }: { busy: boolean; title: string; body: st
function Empty({ bot }: { bot: Bot }) {
const t = useT().browser;
return (
<div className="flex flex-col items-center gap-3 px-6 py-14 text-center">
<span className="relative">
<BotAvatar color={bot.color} size={44} mood="idle" />
<Globe
aria-hidden
size={18}
className="absolute -right-2 -bottom-1 rounded-full bg-panel p-0.5 text-muted"
/>
</span>
<p className="font-semibold text-sm">{t.emptyTitle(bot.name)}</p>
<p className="max-w-80 text-muted text-sm">{t.emptyBody}</p>
</div>
<EmptyState color={bot.color} icon={Globe} title={t.emptyTitle(bot.name)} body={t.emptyBody} />
);
}
51 changes: 14 additions & 37 deletions app/src/features/browser/HelpCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
// it" answer the request; once answered it shrinks to one line.

import { Ban, Check, Hand, TimerOff } from "lucide-react";
import { useContext, useId } from "react";
import { useContext } from "react";
import { useT } from "../../i18n";
import type { ApprovalItem, Bot } from "../../lib/protocol.gen";
import { Button } from "../../ui/Button";
import { NoteInput, RequestCard, SettledLine } from "../../ui/ChatCard";
import { useAnswer } from "../chat/useAnswer";
import { ShowBrowser } from "./showBrowser";

Expand All @@ -28,30 +29,15 @@ function read(approval: ApprovalItem): { task: string; site: string | null } {

function Answered({ approval, task }: { approval: ApprovalItem; task: string }) {
const words = useT().browser.help;
const line = "flex min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-sm";
switch (approval.status) {
case "allowed":
return (
<p className={line}>
<Check aria-hidden size={14} className="shrink-0 text-ok" />
<span className="truncate font-medium">{words.doneLine(task)}</span>
</p>
);
return <SettledLine icon={Check} tone="ok" text={words.doneLine(task)} />;
case "denied":
return (
<p className={line}>
<Ban aria-hidden size={14} className="shrink-0 text-danger" />
<span className="truncate font-medium">{words.wontLine(task)}</span>
{approval.note && <span className="truncate text-muted text-xs">“{approval.note}”</span>}
</p>
<SettledLine icon={Ban} tone="danger" text={words.wontLine(task)} note={approval.note} />
);
default:
return (
<p className={line}>
<TimerOff aria-hidden size={14} className="shrink-0 text-quiet" />
<span className="truncate font-medium">{words.expired(task)}</span>
</p>
);
return <SettledLine icon={TimerOff} tone="quiet" text={words.expired(task)} />;
}
}

Expand All @@ -63,23 +49,18 @@ export function HelpCard({ approval, bot }: { approval: ApprovalItem; bot: Bot }
allow: t.chat.approval.allowFailed,
deny: t.chat.approval.denyFailed,
});
const noteId = useId();
const { task, site } = read(approval);
if (approval.status !== "pending") {
return <Answered approval={approval} task={task} />;
}

return (
<section
aria-label={words.asks(bot.name, task)}
className="my-1 max-w-2xl rounded-2xl border border-accent/40 bg-panel px-4 py-3.5 shadow-sm animate-attention"
<RequestCard
label={words.asks(bot.name, task)}
icon={Hand}
tone="accent"
title={words.needs(bot.name)}
>
<p className="flex items-center gap-2.5 font-semibold text-sm">
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-accent/12 text-accent">
<Hand aria-hidden size={15} />
</span>
{words.needs(bot.name)}
</p>
<p className="mt-2 break-words text-sm" data-selectable>
{task}
</p>
Expand All @@ -89,15 +70,11 @@ export function HelpCard({ approval, bot }: { approval: ApprovalItem; bot: Bot }
</p>
)}
<p className="mt-1.5 text-muted text-xs">{words.why}</p>
<label htmlFor={noteId} className="sr-only">
{t.chat.approval.noteLabel(bot.name)}
</label>
<input
id={noteId}
<NoteInput
label={t.chat.approval.noteLabel(bot.name)}
value={note}
onChange={(event) => setNote(event.target.value)}
onChange={setNote}
placeholder={t.chat.approval.notePlaceholder}
className="mt-3 h-9 w-full rounded-xl border border-line-strong bg-canvas px-3 text-sm outline-none placeholder:text-muted focus:border-muted"
/>
<div className="mt-2.5 flex flex-wrap gap-2">
{showBrowser && (
Expand All @@ -117,6 +94,6 @@ export function HelpCard({ approval, bot }: { approval: ApprovalItem; bot: Bot }
{words.wontDo}
</Button>
</div>
</section>
</RequestCard>
);
}
52 changes: 15 additions & 37 deletions app/src/features/browser/SiteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// address, and Allow or Deny. Once answered it shrinks to one line.

import { Ban, Check, Globe, TimerOff } from "lucide-react";
import { useId } from "react";
import { useT } from "../../i18n";
import type { ApprovalItem, Bot } from "../../lib/protocol.gen";
import { Button } from "../../ui/Button";
import { NoteInput, RequestCard, SettledLine } from "../../ui/ChatCard";
import { useAnswer } from "../chat/useAnswer";

/** The request the daemon opens from the browser tools. */
Expand All @@ -23,30 +23,15 @@ function addressOf(input: string): string | null {

function Answered({ approval, site }: { approval: ApprovalItem; site: string }) {
const words = useT().browser.site;
const line = "flex min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-sm";
switch (approval.status) {
case "allowed":
return (
<p className={line}>
<Check aria-hidden size={14} className="shrink-0 text-ok" />
<span className="truncate font-medium">{words.allowed(site)}</span>
</p>
);
return <SettledLine icon={Check} tone="ok" text={words.allowed(site)} />;
case "denied":
return (
<p className={line}>
<Ban aria-hidden size={14} className="shrink-0 text-danger" />
<span className="shrink-0 font-medium">{words.denied(site)}</span>
{approval.note && <span className="truncate text-muted text-xs">“{approval.note}”</span>}
</p>
<SettledLine icon={Ban} tone="danger" text={words.denied(site)} note={approval.note} />
);
default:
return (
<p className={line}>
<TimerOff aria-hidden size={14} className="shrink-0 text-quiet" />
<span className="font-medium">{words.expired(site)}</span>
</p>
);
return <SettledLine icon={TimerOff} tone="quiet" text={words.expired(site)} />;
}
}

Expand All @@ -57,41 +42,34 @@ export function SiteCard({ approval, bot }: { approval: ApprovalItem; bot: Bot }
allow: t.chat.approval.allowFailed,
deny: t.chat.approval.denyFailed,
});
const noteId = useId();
const site = approval.summary;
if (approval.status !== "pending") {
return <Answered approval={approval} site={site} />;
}
const url = addressOf(approval.input);

return (
<section
aria-label={words.asks(bot.name, site)}
className="my-1 max-w-2xl rounded-2xl border border-warn/40 bg-panel px-4 py-3.5 shadow-sm animate-attention"
>
<p className="flex items-center gap-2.5 font-semibold text-sm">
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-warn/12 text-warn">
<Globe aria-hidden size={15} />
</span>
<RequestCard
label={words.asks(bot.name, site)}
icon={Globe}
tone="warn"
title={
<span>
{words.wants(bot.name)} <span className="font-mono">{site}</span>
</span>
</p>
}
>
{url && (
<p className="mt-2 break-all font-mono text-ink-soft text-xs" data-selectable>
{url}
</p>
)}
<p className="mt-1.5 text-muted text-xs">{words.why}</p>
<label htmlFor={noteId} className="sr-only">
{t.chat.approval.noteLabel(bot.name)}
</label>
<input
id={noteId}
<NoteInput
label={t.chat.approval.noteLabel(bot.name)}
value={note}
onChange={(event) => setNote(event.target.value)}
onChange={setNote}
placeholder={t.chat.approval.notePlaceholder}
className="mt-3 h-9 w-full rounded-xl border border-line-strong bg-canvas px-3 text-sm outline-none placeholder:text-muted focus:border-muted"
/>
<div className="mt-2.5 flex gap-2">
<Button variant="primary" icon={Check} disabled={busy} onClick={() => answer(true)}>
Expand All @@ -101,6 +79,6 @@ export function SiteCard({ approval, bot }: { approval: ApprovalItem; bot: Bot }
{t.chat.approval.deny}
</Button>
</div>
</section>
</RequestCard>
);
}
Loading
Loading