Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

297 lines
11 KiB
TypeScript

import { isValidElement, useId, useState, type ReactNode } from "react";
import { Gauge, Wrench } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from "@tutti-os/ui-system";
import { MoreHorizontalIcon } from "@tutti-os/ui-system/icons";
import { AgentProbeUsageFreshness } from "../AgentProbeUsageFreshness";
import { AgentUsageMeter } from "../AgentUsageMeter";
import { SettingsLinedIcon } from "../../../app/renderer/components/icons/SettingsLinedIcon";
import { resolveAgentGuiSessionProviderFlatIconUrl } from "../../../agentGuiSessionProviderIconUrls";
import styles from "../AgentGUINode.styles";
import type { AgentComposerSlashStatusLimit } from "../AgentComposer";
import type { AgentGUIViewLabels } from "../AgentGUINodeView";
interface AgentGUIConfigMenuProps {
accountContent?: ReactNode;
systemActionsContent?: ReactNode;
environmentSetupVisible: boolean;
labels: AgentGUIViewLabels;
providerScopedActionsVisible: boolean;
slashStatusLimits: readonly AgentComposerSlashStatusLimit[];
slashStatusLimitsLoading: boolean;
slashStatusLimitsResolvedEmpty: boolean;
slashStatusUsageCapturedAtUnixMs: number | null;
slashStatusUsageDidFail: boolean;
slashStatusUsageErrorMessage?: string | null;
slashStatusUsageAttempted: boolean;
provider?: string | null;
providerIconUrl?: string | null;
providerMaskIconUrl?: string | null;
providerLabel?: string | null;
providerAuthAccountLabel?: string | null;
onAgentConfigMenuOpen?: () => void;
onAgentConfigMenuClose?: () => void;
onAgentUsageRefresh?: () => void;
usageRefreshInline?: boolean;
onOpenAgentEnvSetup: () => void;
onOpenAgentSettings: () => void;
}
export function AgentGUIConfigAccountFallbackSuppressed(): null {
return null;
}
export function AgentGUIConfigMenu({
accountContent,
systemActionsContent,
environmentSetupVisible,
labels,
providerScopedActionsVisible,
slashStatusLimits,
slashStatusLimitsLoading,
slashStatusLimitsResolvedEmpty,
slashStatusUsageCapturedAtUnixMs,
slashStatusUsageDidFail,
slashStatusUsageErrorMessage,
slashStatusUsageAttempted,
provider,
providerIconUrl,
providerMaskIconUrl: targetMaskIconUrl,
providerLabel,
providerAuthAccountLabel,
onAgentConfigMenuOpen,
onAgentConfigMenuClose,
onAgentUsageRefresh,
usageRefreshInline = false,
onOpenAgentEnvSetup,
onOpenAgentSettings
}: AgentGUIConfigMenuProps): React.JSX.Element {
const [open, setOpen] = useState(false);
const usageDescriptionId = useId();
const builtInMaskIconUrl = resolveAgentGuiSessionProviderFlatIconUrl(
provider ?? undefined
);
const providerImageIconUrl = builtInMaskIconUrl
? null
: (providerIconUrl?.trim() ?? null);
const providerMaskIconUrl =
builtInMaskIconUrl ??
(providerImageIconUrl ? null : (targetMaskIconUrl?.trim() ?? null));
const providerDisplayName = providerLabel?.trim() || provider?.trim();
const providerDisplayTitle = providerDisplayName
? labels.slashStatusProviderAccount(providerDisplayName)
: null;
const accountTitle = providerDisplayTitle ?? labels.slashStatusAccount;
const accountFallbackSuppressed =
isValidElement(accountContent) &&
accountContent.type === AgentGUIConfigAccountFallbackSuppressed;
const hasAccountContent =
!accountFallbackSuppressed &&
Boolean(accountContent) &&
typeof accountContent !== "boolean";
const providerAccountFallbackVisible =
!accountFallbackSuppressed && !hasAccountContent;
const usageFreshness = (
<AgentProbeUsageFreshness
ariaDescribedBy={usageDescriptionId}
capturedAtUnixMs={slashStatusUsageCapturedAtUnixMs}
didFail={slashStatusUsageDidFail}
disabled={!onAgentUsageRefresh}
isLoading={slashStatusLimitsLoading}
labels={{
justUpdated: labels.slashStatusUsageJustUpdated,
minutesAgo: labels.slashStatusUsageMinutesAgo,
hoursAgo: labels.slashStatusUsageHoursAgo,
updating: labels.slashStatusUsageUpdating,
refreshFailed: labels.slashStatusUsageRefreshFailed,
refreshAria: labels.slashStatusUsageRefreshAria
}}
onRefresh={() => onAgentUsageRefresh?.()}
presentation={usageRefreshInline ? "button" : "menu-item"}
testId="agent-gui-config-usage-refresh"
/>
);
return (
<DropdownMenu
open={open}
onOpenChange={(nextOpen) => {
setOpen(nextOpen);
// Refresh the underlying probe on open, the same way the window-title
// info tooltip does; otherwise a stale/empty fetch can sit here until
// something unrelated refreshes it.
if (nextOpen) {
onAgentConfigMenuOpen?.();
} else {
onAgentConfigMenuClose?.();
}
}}
>
<DropdownMenuTrigger asChild>
<button
type="button"
aria-label={labels.agentConfig}
className={`${styles.providerRailConfigButton} nodrag tsh-desktop-no-drag`}
title={labels.agentConfig}
>
<MoreHorizontalIcon aria-hidden="true" width={18} height={18} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="right"
align="end"
className="nodrag w-[300px] max-w-[calc(100vw-32px)] p-1 text-xs [-webkit-app-region:no-drag]"
data-testid="agent-gui-config-menu"
style={{ zIndex: "var(--z-panel-popover)" }}
>
<DropdownMenuGroup className="min-w-0">
{hasAccountContent ? (
<>
{accountContent}
<DropdownMenuSeparator />
</>
) : null}
{providerAccountFallbackVisible &&
providerScopedActionsVisible &&
providerAuthAccountLabel ? (
<>
<DropdownMenuLabel className="flex min-w-0 flex-col gap-2 p-2 text-[var(--text-primary)]">
<div className="flex min-w-0 items-center gap-2">
{providerMaskIconUrl ? (
<span
aria-hidden="true"
className="size-4 shrink-0 bg-current"
style={{
mask: `url("${providerMaskIconUrl}") center / contain no-repeat`,
WebkitMask: `url("${providerMaskIconUrl}") center / contain no-repeat`
}}
/>
) : providerImageIconUrl ? (
<img
alt=""
aria-hidden="true"
className="size-4 shrink-0 object-contain"
src={providerImageIconUrl}
/>
) : null}
<span className="text-[13px] font-semibold leading-4">
{accountTitle}
</span>
</div>
<span className="text-[13px] leading-5 text-[var(--text-secondary)]">
{providerAuthAccountLabel}
</span>
</DropdownMenuLabel>
{slashStatusLimits.length > 0 ||
slashStatusUsageAttempted ||
slashStatusLimitsLoading ? (
<DropdownMenuSeparator />
) : null}
</>
) : null}
{providerAccountFallbackVisible &&
providerScopedActionsVisible &&
(slashStatusLimits.length > 0 ||
slashStatusUsageAttempted ||
slashStatusLimitsLoading) ? (
<>
<DropdownMenuLabel
className="flex min-w-0 flex-col gap-2 p-2 text-[var(--text-primary)]"
id={usageRefreshInline ? undefined : usageDescriptionId}
>
<div className="flex min-w-0 items-center justify-between gap-2">
<div
className="flex min-w-0 items-center gap-2"
id={usageRefreshInline ? usageDescriptionId : undefined}
>
<Gauge
aria-hidden="true"
className="shrink-0"
size={16}
strokeWidth={1.8}
/>
<span className="shrink-0 text-[13px] font-semibold leading-4">
{labels.slashStatusLimits}
</span>
{slashStatusLimits.length === 0 &&
!slashStatusLimitsLoading &&
!slashStatusUsageErrorMessage ? (
<span
className="min-w-0 truncate text-[var(--text-tertiary)]"
data-testid="agent-gui-config-usage-unavailable"
>
{slashStatusLimitsResolvedEmpty
? labels.slashStatusEmptyValue
: labels.slashStatusLimitsUnavailable}
</span>
) : null}
</div>
{usageRefreshInline ? usageFreshness : null}
</div>
{slashStatusLimits.length > 0
? slashStatusLimits.map((limit) => (
<AgentUsageMeter
key={limit.id}
label={limit.label}
value={`${limit.value}${limit.reset ? ` (${limit.reset})` : ""}`}
percent={
typeof limit.percentRemaining === "number" &&
Number.isFinite(limit.percentRemaining)
? limit.percentRemaining
: null
}
/>
))
: null}
{slashStatusUsageErrorMessage ? (
<div
className="rounded-[6px] border border-[var(--on-danger-hover)] bg-[var(--on-danger)] px-2 py-1.5 text-[12px] leading-4 text-[var(--state-danger)]"
data-testid="agent-gui-config-usage-error"
role="alert"
>
{slashStatusUsageErrorMessage}
</div>
) : null}
</DropdownMenuLabel>
{usageRefreshInline ? null : usageFreshness}
<DropdownMenuSeparator />
</>
) : null}
<DropdownMenuGroup>
{providerScopedActionsVisible && environmentSetupVisible ? (
<DropdownMenuItem
data-testid="agent-gui-config-env-setup"
className="h-7"
onSelect={() => onOpenAgentEnvSetup()}
>
<Wrench aria-hidden="true" size={16} strokeWidth={1.8} />
<span>{labels.agentEnvSetup}</span>
</DropdownMenuItem>
) : null}
<DropdownMenuItem
data-testid="agent-gui-config-settings"
className="h-7"
onSelect={() => onOpenAgentSettings()}
>
<SettingsLinedIcon aria-hidden="true" width={16} height={16} />
<span>{labels.agentSettingsMenu}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
{systemActionsContent ? (
<>
<DropdownMenuSeparator />
{systemActionsContent}
</>
) : null}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}