Skip to content
Open
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
2 changes: 2 additions & 0 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"clsx": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"emoji-mart": "^5.6.0",
"i18next": "^25.10.5",
"jdenticon": "^3.3.0",
"lucide-react": "^1.0.0",
"motion": "^12.38.0",
Expand All @@ -72,6 +73,7 @@
"react": "^19.1.0",
"react-diff-view": "^3.3.2",
"react-dom": "^19.1.0",
"react-i18next": "^16.6.2",
"react-markdown": "^10.1.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
Expand Down
20 changes: 11 additions & 9 deletions desktop/src/features/agents/ui/AgentsView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { EllipsisVertical, OctagonX, Settings2 } from "lucide-react";
import {
consumePendingSnapshotImport,
Expand Down Expand Up @@ -39,6 +40,7 @@ import { PageHeader } from "@/shared/ui/PageHeader";
import { getInheritedAgentDefaults } from "./bakedEnvHelpers";

export function AgentsView() {
const { t } = useTranslation();
const { openPersonaProfilePanel, openProfilePanel } = useProfilePanel();
const { globalConfig } = useGlobalAgentConfig();
const { data: bakedEnv } = useBakedBuildEnvQuery({ enabled: true });
Expand Down Expand Up @@ -150,8 +152,8 @@ export function AgentsView() {
>
<Settings2 />
{hasSavedAgentDefaults
? "Agent defaults"
: "Set agent defaults"}
? t("agents.defaults")
: t("agents.setDefaults")}
</Button>
{runningAgentCount > 0 ? (
<Button
Expand All @@ -163,15 +165,15 @@ export function AgentsView() {
variant="outline"
>
<OctagonX />
Stop running agents
{t("agents.stopRunning")}
</Button>
) : null}
</div>

<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button
aria-label="Agent actions"
aria-label={t("agents.actions")}
className="hidden [@container(max-width:40rem)]:inline-flex"
data-testid="agent-actions-menu-trigger"
ref={compactActionsTriggerRef}
Expand All @@ -190,8 +192,8 @@ export function AgentsView() {
>
<Settings2 />
{hasSavedAgentDefaults
? "Agent defaults"
: "Set agent defaults"}
? t("agents.defaults")
: t("agents.setDefaults")}
</DropdownMenuItem>
{runningAgentCount > 0 ? (
<DropdownMenuItem
Expand All @@ -201,15 +203,15 @@ export function AgentsView() {
}}
>
<OctagonX />
Stop running agents
{t("agents.stopRunning")}
</DropdownMenuItem>
) : null}
</DropdownMenuContent>
</DropdownMenu>
</>
}
description="Set up and manage your agents."
title="Agents"
description={t("agents.description")}
title={t("agents.title")}
/>
<div className="flex flex-col gap-8">
<UnifiedAgentsSection
Expand Down
30 changes: 19 additions & 11 deletions desktop/src/features/agents/ui/UnifiedAgentsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { AlertTriangle, ChevronDown, ChevronRight } from "lucide-react";

import { resolveAgentCardModelLabel } from "@/features/agents/lib/agentCardModelLabel";
Expand Down Expand Up @@ -68,6 +69,7 @@ export const AGENT_CARD_GRID_COLUMNS_CLASS =
export const IDENTITY_CARD_GRID_CLASS = `${AGENT_CARD_COLUMN_CLASS} ${AGENT_CARD_GRID_COLUMNS_CLASS} grid justify-start gap-3 [@container(max-width:40rem)]:justify-center`;

export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
const { t } = useTranslation();
const {
actionErrorMessage,
actionNoticeMessage,
Expand Down Expand Up @@ -135,7 +137,7 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
{isDragOver ? (
<div className="pointer-events-none absolute -inset-1 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-background/80 backdrop-blur-sm">
<p className="text-sm font-medium text-primary">
Drop .agent.json or .agent.png to import
{t("agents.dropToImport")}
</p>
</div>
) : null}
Expand Down Expand Up @@ -203,7 +205,7 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
collapsed={collapsed}
defaultModel={defaultModel}
groupKey="__unknown__"
label="Unknown agents"
label={t("agents.unknown")}
restartingAgentPubkey={restartingAgentPubkey}
startingAgentPubkey={startingAgentPubkey}
onToggle={toggle}
Expand All @@ -218,7 +220,7 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) {
collapsed={collapsed}
defaultModel={defaultModel}
groupKey="__ungrouped__"
label="Custom agents"
label={t("agents.custom")}
restartingAgentPubkey={restartingAgentPubkey}
startingAgentPubkey={startingAgentPubkey}
onToggle={toggle}
Expand Down Expand Up @@ -281,6 +283,7 @@ function AgentPersonaCard({
onStartAgent: (pubkey: string) => void;
onStartPersona: (persona: AgentPersona) => void;
}) {
const { t } = useTranslation();
const title = persona.displayName;
const modelLabel = resolveAgentCardModelLabel({
agent,
Expand All @@ -303,7 +306,7 @@ function AgentPersonaCard({
avatarUrl,
Boolean(agent && !persona.avatarUrl && profileQuery.isPending),
)}
ariaLabel={`${title} agent profile`}
ariaLabel={t("agents.profile", { name: title })}
avatar={
agent ? (
<AgentRuntimeAvatarControl
Expand Down Expand Up @@ -356,7 +359,7 @@ function AgentPersonaCard({
agent?.personaOrphaned ? (
<Badge className="gap-1" variant="warning">
<AlertTriangle className="h-3 w-3" />
Configuration missing
{t("agents.configurationMissing")}
</Badge>
) : null
}
Expand Down Expand Up @@ -384,6 +387,7 @@ function StandaloneAgentCard({
onRestartAgent: (pubkey: string) => void;
onStartAgent: (pubkey: string) => void;
}) {
const { t } = useTranslation();
const title = agent.name;
const profileQuery = useUserProfileQuery(agent.pubkey);
const friendlyError = friendlyAgentLastError(
Expand All @@ -395,7 +399,7 @@ function StandaloneAgentCard({

return (
<AgentIdentityCard
ariaLabel={`${title} agent profile`}
ariaLabel={t("agents.profile", { name: title })}
avatar={
<AgentRuntimeAvatarControl
activeTestId={`agent-runtime-active-${agent.pubkey}`}
Expand Down Expand Up @@ -436,7 +440,7 @@ function StandaloneAgentCard({
agent.personaOrphaned ? (
<Badge className="gap-1" variant="warning">
<AlertTriangle className="h-3 w-3" />
Configuration missing
{t("agents.configurationMissing")}
</Badge>
) : null
}
Expand Down Expand Up @@ -465,27 +469,31 @@ function NewAgentCard({
onDiscover: () => void;
onImport: () => void;
}) {
const { t } = useTranslation();
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<CreateIdentityCard ariaLabel="New agent" dataTestId="new-agent-card" />
<CreateIdentityCard
ariaLabel={t("agents.newAgent")}
dataTestId="new-agent-card"
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
onCloseAutoFocus={(event) => event.preventDefault()}
>
<DropdownMenuItem disabled={isPending} onClick={onCreate}>
Create agent
{t("agents.createAgent")}
</DropdownMenuItem>
<DropdownMenuItem disabled={isPending} onClick={onDiscover}>
Discover agents
{t("agents.discoverAgents")}
</DropdownMenuItem>
<DropdownMenuItem
data-testid="import-agent-snapshot-menu-item"
disabled={isPending}
onClick={onImport}
>
Import
{t("agents.import")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
46 changes: 25 additions & 21 deletions desktop/src/features/communities/ui/CommunitySwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "lucide-react";
import * as React from "react";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";

import type { LeaveCommunityResult } from "@/features/communities/leaveCommunity";
import type { Community } from "@/features/communities/types";
Expand Down Expand Up @@ -39,13 +40,13 @@ import { writeTextToClipboard } from "@/shared/lib/clipboard";
import { useActiveCommunityIcon } from "@/features/communities/useCommunityIcons";
import { EditCommunityDialog } from "./EditCommunityDialog";

const CONNECTION_STATE_LABEL: Record<ConnectionState, string> = {
idle: "Not connected",
connecting: "Connecting…",
connected: "Connected",
reconnecting: "Reconnecting to relay…",
stalled: "Connection lost — relay is not responding",
disconnected: "Disconnected from relay",
const CONNECTION_STATE_LABEL_KEY: Record<ConnectionState, string> = {
idle: "sidebar.community.notConnected",
connecting: "sidebar.community.connecting",
connected: "sidebar.community.connected",
reconnecting: "sidebar.community.reconnecting",
stalled: "sidebar.community.stalled",
disconnected: "sidebar.community.disconnected",
};

type CommunitySwitcherProps = {
Expand Down Expand Up @@ -103,6 +104,7 @@ export function CommunitySwitcher({
onUpdateCommunity,
onRemoveCommunity,
}: CommunitySwitcherProps) {
const { t } = useTranslation();
const [editingCommunity, setEditingCommunity] =
React.useState<Community | null>(null);
const [dropdownOpen, setDropdownOpen] = React.useState(false);
Expand All @@ -111,7 +113,7 @@ export function CommunitySwitcher({
const profileMenuHoverTimer = React.useRef<number | null>(null);
const connectionState = useRelayConnection();
const degraded = isRelayConnectionDegraded(connectionState);
const connectionLabel = CONNECTION_STATE_LABEL[connectionState];
const connectionLabel = t(CONNECTION_STATE_LABEL_KEY[connectionState]);
const activeIconQuery = useActiveCommunityIcon(activeCommunity?.relayUrl);
const activeIcon = activeIconQuery.data ?? null;
const isProfileVariant = variant === "profile";
Expand Down Expand Up @@ -221,7 +223,7 @@ export function CommunitySwitcher({
: "min-w-0 flex-1 truncate font-medium"
}
>
{activeCommunity?.name ?? "No community"}
{activeCommunity?.name ?? t("sidebar.community.noCommunity")}
</span>
{variant === "profile-menu" ? (
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" />
Expand All @@ -246,8 +248,8 @@ export function CommunitySwitcher({
aria-haspopup="menu"
aria-label={
degraded
? `${activeCommunity?.name ?? "Community"} — ${connectionLabel}`
: "Community actions"
? `${activeCommunity?.name ?? t("sidebar.community.community")} — ${connectionLabel}`
: t("sidebar.community.actions")
}
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-popover-foreground outline-hidden transition-colors hover:bg-muted/50 focus:bg-muted/50 focus:outline-none focus-visible:bg-muted/50 focus-visible:outline-none data-[state=open]:bg-muted/50 data-[state=open]:text-popover-foreground"
data-testid="community-switcher"
Expand All @@ -269,7 +271,7 @@ export function CommunitySwitcher({
sideOffset={0}
>
<div
aria-label="Community actions"
aria-label={t("sidebar.community.actions")}
data-testid="profile-community-actions"
role="menu"
>
Expand All @@ -285,7 +287,7 @@ export function CommunitySwitcher({
type="button"
>
<Link2 className="h-4 w-4" />
<span>Copy community URL</span>
<span>{t("sidebar.community.copyUrl")}</span>
</button>
{canInvite && onInvite ? (
<button
Expand All @@ -298,7 +300,7 @@ export function CommunitySwitcher({
type="button"
>
<Ticket className="h-4 w-4" />
<span>Invite to community</span>
<span>{t("sidebar.community.invite")}</span>
</button>
) : null}
<button
Expand All @@ -311,7 +313,7 @@ export function CommunitySwitcher({
type="button"
>
<Settings2 className="h-4 w-4" />
<span>Community settings</span>
<span>{t("sidebar.community.settings")}</span>
</button>
<button
className="flex min-h-9 w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-destructive outline-hidden transition-colors hover:bg-destructive/10 focus:bg-destructive/10 focus:outline-none focus-visible:bg-destructive/10 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
Expand Down Expand Up @@ -344,7 +346,7 @@ export function CommunitySwitcher({
type="button"
>
<Plus className="h-4 w-4" />
<span>Add a community</span>
<span>{t("sidebar.community.add")}</span>
</button>
</div>
</PopoverContent>
Expand All @@ -362,8 +364,8 @@ export function CommunitySwitcher({
<button
aria-label={
degraded
? `${activeCommunity?.name ?? "Community"} — ${connectionLabel}`
: "Switch community"
? `${activeCommunity?.name ?? t("sidebar.community.community")} — ${connectionLabel}`
: t("sidebar.community.switch")
}
className="flex min-w-0 max-w-full items-center gap-1.5 rounded-md py-0.5 text-left text-xs text-sidebar-foreground/50 outline-hidden transition-colors hover:text-sidebar-foreground focus:outline-none focus-visible:outline-none data-[state=open]:text-sidebar-foreground"
data-testid="community-switcher"
Expand All @@ -375,7 +377,7 @@ export function CommunitySwitcher({
<SidebarMenuButton
aria-label={
degraded
? `${activeCommunity?.name ?? "Community"} — ${connectionLabel}`
? `${activeCommunity?.name ?? t("sidebar.community.community")} — ${connectionLabel}`
: undefined
}
className="h-auto gap-2 rounded-xl px-2.5 py-2 data-[state=open]:bg-sidebar-accent"
Expand Down Expand Up @@ -408,7 +410,9 @@ export function CommunitySwitcher({
</span>
<span className="min-w-0 flex-1 truncate">{community.name}</span>
<button
aria-label={`Edit ${community.name}`}
aria-label={t("sidebar.community.edit", {
name: community.name,
})}
className="flex h-5 w-5 shrink-0 items-center justify-center rounded opacity-0 hover:bg-accent group-hover:opacity-100 group-focus:opacity-100"
onClick={(e) => {
e.stopPropagation();
Expand All @@ -425,7 +429,7 @@ export function CommunitySwitcher({
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={onAddCommunity}>
<Plus className="h-4 w-4" />
<span>Add a community</span>
<span>{t("sidebar.community.add")}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
Loading