mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
[codex] Polish agent selectors and settings layout (#283)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { isCatalogPersonaSelected } from "@/features/agents/lib/catalog";
|
||||
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
||||
import type { AgentPersona } from "@/shared/api/types";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { promptPreview } from "@/shared/lib/promptPreview";
|
||||
import {
|
||||
Sheet,
|
||||
@@ -74,7 +75,13 @@ export function PersonaCatalogDetailsSheet({
|
||||
isSelected,
|
||||
)}
|
||||
aria-pressed={isSelected}
|
||||
className="w-full rounded-xl border border-border/70 bg-card/70 p-4 text-left transition-colors hover:border-primary/40 hover:bg-primary/[0.03]"
|
||||
className={cn(
|
||||
"w-full rounded-xl border p-4 text-left transition-[background-color,border-color,box-shadow] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2",
|
||||
isSelected
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border/80 bg-background/60 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
isPending && "cursor-not-allowed opacity-70",
|
||||
)}
|
||||
data-state={isSelected ? "selected" : "available"}
|
||||
data-testid={`persona-catalog-detail-selection-target-${persona.id}`}
|
||||
disabled={isPending}
|
||||
|
||||
@@ -80,13 +80,13 @@ export function PersonaCatalogSection({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative flex flex-col gap-4 rounded-xl border bg-card/80 p-3 shadow-sm transition-colors",
|
||||
"group relative flex flex-col gap-4 rounded-xl border p-3 shadow-sm transition-[background-color,border-color,box-shadow]",
|
||||
isPending
|
||||
? "cursor-not-allowed opacity-70"
|
||||
: "cursor-pointer hover:border-primary/40 hover:bg-primary/[0.03]",
|
||||
: "cursor-pointer",
|
||||
isSelected
|
||||
? "border-primary/40 bg-primary/[0.04]"
|
||||
: "border-border/70",
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border/80 bg-background/60 hover:bg-accent hover:text-accent-foreground",
|
||||
)}
|
||||
data-testid={`persona-catalog-card-${persona.id}`}
|
||||
data-state={isSelected ? "selected" : "available"}
|
||||
@@ -98,7 +98,7 @@ export function PersonaCatalogSection({
|
||||
isSelected,
|
||||
)}
|
||||
aria-pressed={isSelected}
|
||||
className="absolute inset-0 z-0 rounded-xl focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
className="absolute inset-0 z-0 rounded-xl focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2"
|
||||
data-testid={`persona-catalog-card-target-${persona.id}`}
|
||||
disabled={isPending}
|
||||
onClick={() => {
|
||||
@@ -123,7 +123,12 @@ export function PersonaCatalogSection({
|
||||
{preview}
|
||||
</p>
|
||||
|
||||
<div className="mt-auto flex items-center justify-between gap-3 border-t border-border/60 pt-3">
|
||||
<div
|
||||
className={cn(
|
||||
"mt-auto flex items-center justify-between gap-3 border-t pt-3",
|
||||
isSelected ? "border-primary/20" : "border-border/60",
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
className="pointer-events-auto"
|
||||
data-testid={`persona-catalog-details-${persona.id}`}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
|
||||
import { personaCatalogCopy } from "./personaLibraryCopy";
|
||||
@@ -12,12 +14,13 @@ export function PersonaCatalogSelectionBadge({
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"whitespace-nowrap rounded-full px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.14em]",
|
||||
"inline-flex items-center gap-1 whitespace-nowrap rounded-full px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.14em]",
|
||||
isActive
|
||||
? "bg-primary/15 text-primary"
|
||||
: "bg-muted text-muted-foreground",
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
: "border border-border/70 bg-background/85 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isActive ? <Check className="h-3 w-3" /> : null}
|
||||
{isActive
|
||||
? personaCatalogCopy.selectedState
|
||||
: personaCatalogCopy.availableState}
|
||||
|
||||
@@ -34,11 +34,11 @@ function SelectionChipButton({
|
||||
<button
|
||||
aria-pressed={selected}
|
||||
className={cn(
|
||||
"inline-flex min-h-9 items-center gap-2 rounded-full border py-1.5 text-sm font-medium transition-colors",
|
||||
"inline-flex min-h-9 items-center gap-2 rounded-full border py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
showAvatar ? "pl-1.5 pr-3" : "px-3",
|
||||
selected
|
||||
? "border-foreground bg-foreground text-background shadow-sm"
|
||||
: "border-border/70 bg-muted/25 text-foreground hover:bg-muted/55",
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border/80 bg-background/60 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
disabled && "cursor-not-allowed opacity-50",
|
||||
)}
|
||||
disabled={disabled}
|
||||
@@ -51,8 +51,8 @@ function SelectionChipButton({
|
||||
className={cn(
|
||||
"h-6 w-6 rounded-full text-[10px]",
|
||||
selected
|
||||
? "bg-background/20 text-background"
|
||||
: "bg-primary/20 text-primary",
|
||||
? "bg-primary/20 text-primary ring-1 ring-primary/20"
|
||||
: "bg-background/80 text-muted-foreground ring-1 ring-border/70",
|
||||
)}
|
||||
iconClassName="h-3.5 w-3.5"
|
||||
label={label}
|
||||
@@ -109,9 +109,7 @@ export function AddChannelBotPersonasSection({
|
||||
<Bot
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
includeGeneric
|
||||
? "text-background/70"
|
||||
: "text-muted-foreground",
|
||||
includeGeneric ? "text-primary" : "text-current",
|
||||
)}
|
||||
/>
|
||||
Generic
|
||||
@@ -142,7 +140,7 @@ export function AddChannelBotPersonasSection({
|
||||
className={cn(
|
||||
"inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[10px] font-medium leading-none",
|
||||
isSelected
|
||||
? "bg-background/20 text-background/80"
|
||||
? "bg-primary/15 text-primary"
|
||||
: "bg-muted/60 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -31,10 +31,10 @@ function SelectionChipButton({
|
||||
<button
|
||||
aria-pressed={selected}
|
||||
className={cn(
|
||||
"inline-flex min-h-9 items-center gap-2 rounded-full border py-1.5 px-3 text-sm font-medium transition-colors",
|
||||
"inline-flex min-h-9 items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
selected
|
||||
? "border-foreground bg-foreground text-background shadow-sm"
|
||||
: "border-border/70 bg-muted/25 text-foreground hover:bg-muted/55",
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border/80 bg-background/60 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
disabled && "cursor-not-allowed opacity-50",
|
||||
)}
|
||||
disabled={disabled}
|
||||
@@ -109,18 +109,14 @@ export function AddChannelBotTeamsSection({
|
||||
<Users
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
allSelected
|
||||
? "text-background/70"
|
||||
: "text-muted-foreground",
|
||||
allSelected ? "text-primary" : "text-current",
|
||||
)}
|
||||
/>
|
||||
{team.name}
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs",
|
||||
allSelected
|
||||
? "text-background/60"
|
||||
: "text-muted-foreground",
|
||||
allSelected ? "text-primary/70" : "text-current/70",
|
||||
)}
|
||||
>
|
||||
({validIds.length})
|
||||
@@ -130,7 +126,7 @@ export function AddChannelBotTeamsSection({
|
||||
className={cn(
|
||||
"inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[10px] font-medium leading-none",
|
||||
allSelected
|
||||
? "bg-background/20 text-background/80"
|
||||
? "bg-primary/15 text-primary"
|
||||
: "bg-muted/60 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -198,169 +198,165 @@ export function DoctorSettingsPanel() {
|
||||
!prereqs.mcp.available);
|
||||
|
||||
return (
|
||||
<div className="space-y-4" data-testid="settings-doctor">
|
||||
<section className="rounded-xl border border-border/80 bg-card/80 p-5 shadow-sm">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<section className="space-y-5" data-testid="settings-doctor">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Stethoscope className="h-4 w-4 text-primary" />
|
||||
<h2 className="text-sm font-semibold tracking-tight">Doctor</h2>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Verify the local Sprout tools and ACP runtime commands used by the
|
||||
desktop app.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="shrink-0"
|
||||
disabled={isRefreshing}
|
||||
onClick={() => {
|
||||
void providersQuery.refetch();
|
||||
void prereqsQuery.refetch();
|
||||
}}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("h-4 w-4", isRefreshing && "animate-spin")}
|
||||
/>
|
||||
Re-run
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-border/70 bg-muted/20 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Stethoscope className="h-4 w-4 text-primary" />
|
||||
<h2 className="text-sm font-semibold tracking-tight">Doctor</h2>
|
||||
<TerminalSquare className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold tracking-tight">
|
||||
Local Sprout binaries
|
||||
</h3>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Verify the local Sprout tools and ACP runtime commands used by the
|
||||
desktop app.
|
||||
These checks replace the old binary status card from Create agent.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
{toolChecks.map((check) => (
|
||||
<CommandCheckRow
|
||||
availability={check.availability}
|
||||
id={check.id}
|
||||
isLoading={prereqsQuery.isLoading}
|
||||
key={check.id}
|
||||
label={check.label}
|
||||
purpose={check.purpose}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{hasMissingSproutTools ? (
|
||||
<p className="mt-4 rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-warning">
|
||||
Build the workspace binaries with{" "}
|
||||
<code className="font-mono">
|
||||
cargo build --release --workspace
|
||||
</code>{" "}
|
||||
or point agent creation at custom ACP and MCP commands.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{prereqsQuery.error instanceof Error ? (
|
||||
<p className="mt-4 rounded-xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{prereqsQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-border/70 bg-muted/20 p-4">
|
||||
<h3 className="text-sm font-semibold tracking-tight">
|
||||
Custom harness commands
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Verify non-default ACP or MCP binaries before using them in agent
|
||||
creation.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium" htmlFor="doctor-acp">
|
||||
ACP command
|
||||
</label>
|
||||
<Input
|
||||
data-testid="doctor-acp-command"
|
||||
id="doctor-acp"
|
||||
onChange={(event) => setAcpCommand(event.target.value)}
|
||||
value={acpCommand}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium" htmlFor="doctor-mcp">
|
||||
MCP command
|
||||
</label>
|
||||
<Input
|
||||
data-testid="doctor-mcp-command"
|
||||
id="doctor-mcp"
|
||||
onChange={(event) => setMcpCommand(event.target.value)}
|
||||
value={mcpCommand}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
Token minting always checks the default{" "}
|
||||
<code className="font-mono">sprout-admin</code> command.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="shrink-0"
|
||||
disabled={isRefreshing}
|
||||
onClick={() => {
|
||||
void providersQuery.refetch();
|
||||
void prereqsQuery.refetch();
|
||||
}}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("h-4 w-4", isRefreshing && "animate-spin")}
|
||||
/>
|
||||
Re-run
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-border/70 bg-muted/20 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<TerminalSquare className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold tracking-tight">
|
||||
Local Sprout binaries
|
||||
</h3>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
These checks replace the old binary status card from Create
|
||||
agent.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-border/70 bg-muted/20 p-4">
|
||||
<h3 className="text-sm font-semibold tracking-tight">
|
||||
ACP runtimes
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Installed runtimes that the desktop app can offer in Create agent.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
{toolChecks.map((check) => (
|
||||
<CommandCheckRow
|
||||
availability={check.availability}
|
||||
id={check.id}
|
||||
isLoading={prereqsQuery.isLoading}
|
||||
key={check.id}
|
||||
label={check.label}
|
||||
purpose={check.purpose}
|
||||
<div className="mt-4 space-y-2">
|
||||
{providersQuery.isLoading ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Looking for installed ACP runtimes...
|
||||
</p>
|
||||
) : providers.length > 0 ? (
|
||||
providers.map((provider) => (
|
||||
<ProviderRow
|
||||
command={provider.command}
|
||||
defaultArgs={provider.defaultArgs}
|
||||
key={provider.id}
|
||||
label={provider.label}
|
||||
providerId={provider.id}
|
||||
resolvedPath={provider.binaryPath}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{hasMissingSproutTools ? (
|
||||
<p className="mt-4 rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-warning">
|
||||
Build the workspace binaries with{" "}
|
||||
<code className="font-mono">
|
||||
cargo build --release --workspace
|
||||
</code>{" "}
|
||||
or point agent creation at custom ACP and MCP commands.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{prereqsQuery.error instanceof Error ? (
|
||||
<p className="mt-4 rounded-xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{prereqsQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
))
|
||||
) : (
|
||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-warning">
|
||||
No known ACP runtime was detected on your PATH yet. You can
|
||||
still use a custom command in Create agent.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-border/70 bg-muted/20 p-4">
|
||||
<h3 className="text-sm font-semibold tracking-tight">
|
||||
Custom harness commands
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Verify non-default ACP or MCP binaries before using them in
|
||||
agent creation.
|
||||
{providersQuery.error instanceof Error ? (
|
||||
<p className="mt-4 rounded-xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{providersQuery.error.message}
|
||||
</p>
|
||||
|
||||
<div className="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium" htmlFor="doctor-acp">
|
||||
ACP command
|
||||
</label>
|
||||
<Input
|
||||
data-testid="doctor-acp-command"
|
||||
id="doctor-acp"
|
||||
onChange={(event) => setAcpCommand(event.target.value)}
|
||||
value={acpCommand}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium" htmlFor="doctor-mcp">
|
||||
MCP command
|
||||
</label>
|
||||
<Input
|
||||
data-testid="doctor-mcp-command"
|
||||
id="doctor-mcp"
|
||||
onChange={(event) => setMcpCommand(event.target.value)}
|
||||
value={mcpCommand}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
Token minting always checks the default{" "}
|
||||
<code className="font-mono">sprout-admin</code> command.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-border/70 bg-muted/20 p-4">
|
||||
<h3 className="text-sm font-semibold tracking-tight">
|
||||
ACP runtimes
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Installed runtimes that the desktop app can offer in Create
|
||||
agent.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
{providersQuery.isLoading ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Looking for installed ACP runtimes...
|
||||
</p>
|
||||
) : providers.length > 0 ? (
|
||||
providers.map((provider) => (
|
||||
<ProviderRow
|
||||
command={provider.command}
|
||||
defaultArgs={provider.defaultArgs}
|
||||
key={provider.id}
|
||||
label={provider.label}
|
||||
providerId={provider.id}
|
||||
resolvedPath={provider.binaryPath}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-warning">
|
||||
No known ACP runtime was detected on your PATH yet. You can
|
||||
still use a custom command in Create agent.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{providersQuery.error instanceof Error ? (
|
||||
<p className="mt-4 rounded-xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{providersQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<SetupHelpCard />
|
||||
</div>
|
||||
<SetupHelpCard />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,10 +102,7 @@ export function NotificationSettingsCard({
|
||||
onSetNeedsActionNotificationsEnabled: (enabled: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className="rounded-xl border border-border/80 bg-card/80 p-4 shadow-sm"
|
||||
data-testid="settings-notifications"
|
||||
>
|
||||
<section className="min-w-0" data-testid="settings-notifications">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-sm font-semibold tracking-tight">
|
||||
|
||||
@@ -117,10 +117,7 @@ export function ProfileSettingsCard({
|
||||
const nip05Handle = profile?.nip05Handle ?? "Not set";
|
||||
|
||||
return (
|
||||
<section
|
||||
className="rounded-xl border border-border/80 bg-card/80 p-5 shadow-sm"
|
||||
data-testid="settings-profile"
|
||||
>
|
||||
<section className="min-w-0" data-testid="settings-profile">
|
||||
<div className="flex min-w-0 items-start gap-4">
|
||||
<ProfileAvatar
|
||||
avatarUrl={resolvedAvatarUrl}
|
||||
|
||||
@@ -104,10 +104,7 @@ function ThemeSettingsCard() {
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className="rounded-xl border border-border/80 bg-card/80 p-4 shadow-sm"
|
||||
data-testid="settings-theme"
|
||||
>
|
||||
<section className="min-w-0" data-testid="settings-theme">
|
||||
<div className="mb-3 min-w-0">
|
||||
<h2 className="text-sm font-semibold tracking-tight">Appearance</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -673,10 +673,7 @@ export function TokenSettingsCard({
|
||||
const hasReachedTokenLimit = activeTokens.length >= MAX_ACTIVE_TOKENS;
|
||||
|
||||
return (
|
||||
<section
|
||||
className="rounded-xl border border-border/80 bg-card/80 p-4 shadow-sm"
|
||||
data-testid="settings-tokens"
|
||||
>
|
||||
<section className="min-w-0" data-testid="settings-tokens">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user