mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-08 20:58:45 +02:00
feat: show MCP probe progress in approval pill
- Thread mcpProbingServerIds from workspace state through App → ChatPane → InlineApprovalPill - Show animated spinner and 'probing…' label on pill button while any MCP server is being probed - Show per-server probing indicator in popover: spinning icon, 'probing…' badge, hidden toggle - Render approval pill during probing even when no MCP tools are known yet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -333,6 +333,7 @@ export default function App() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
availableModels={availableModels}
|
availableModels={availableModels}
|
||||||
|
mcpProbingServerIds={workspace.mcpProbingServerIds}
|
||||||
pattern={patternForSession}
|
pattern={patternForSession}
|
||||||
project={projectForSession}
|
project={projectForSession}
|
||||||
runtimeTools={sidecarCapabilities?.runtimeTools}
|
runtimeTools={sidecarCapabilities?.runtimeTools}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ interface ChatPaneProps {
|
|||||||
session: SessionRecord;
|
session: SessionRecord;
|
||||||
availableModels: ReadonlyArray<ModelDefinition>;
|
availableModels: ReadonlyArray<ModelDefinition>;
|
||||||
toolingSettings: WorkspaceToolingSettings;
|
toolingSettings: WorkspaceToolingSettings;
|
||||||
|
mcpProbingServerIds?: string[];
|
||||||
runtimeTools?: ReadonlyArray<RuntimeToolDefinition>;
|
runtimeTools?: ReadonlyArray<RuntimeToolDefinition>;
|
||||||
sessionUsage?: SessionUsageState;
|
sessionUsage?: SessionUsageState;
|
||||||
onSend: (content: string, attachments?: ChatMessageAttachment[], messageMode?: MessageMode) => Promise<void>;
|
onSend: (content: string, attachments?: ChatMessageAttachment[], messageMode?: MessageMode) => Promise<void>;
|
||||||
@@ -65,6 +66,7 @@ export function ChatPane({
|
|||||||
session,
|
session,
|
||||||
availableModels,
|
availableModels,
|
||||||
toolingSettings,
|
toolingSettings,
|
||||||
|
mcpProbingServerIds,
|
||||||
runtimeTools,
|
runtimeTools,
|
||||||
sessionUsage,
|
sessionUsage,
|
||||||
onSend,
|
onSend,
|
||||||
@@ -143,6 +145,8 @@ export function ChatPane({
|
|||||||
}
|
}
|
||||||
return counted.size;
|
return counted.size;
|
||||||
}, [approvalTools, effectiveAutoApproved, toolingSettings]);
|
}, [approvalTools, effectiveAutoApproved, toolingSettings]);
|
||||||
|
const isProbingMcp = (mcpProbingServerIds?.length ?? 0) > 0;
|
||||||
|
const hasApprovalContent = approvalTools.length > 0 || isProbingMcp;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
transcriptRef.current?.scrollTo({
|
transcriptRef.current?.scrollTo({
|
||||||
@@ -487,13 +491,14 @@ export function ChatPane({
|
|||||||
selection={toolSelection}
|
selection={toolSelection}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasToolCallApproval && onUpdateSessionApprovalSettings && approvalTools.length > 0 && (
|
{hasToolCallApproval && onUpdateSessionApprovalSettings && hasApprovalContent && (
|
||||||
<InlineApprovalPill
|
<InlineApprovalPill
|
||||||
approvalTools={approvalTools}
|
approvalTools={approvalTools}
|
||||||
disabled={isComposerDisabled}
|
disabled={isComposerDisabled}
|
||||||
effectiveAutoApproved={effectiveAutoApproved}
|
effectiveAutoApproved={effectiveAutoApproved}
|
||||||
effectiveAutoApprovedCount={effectiveAutoApprovedCount}
|
effectiveAutoApprovedCount={effectiveAutoApprovedCount}
|
||||||
isOverridden={isApprovalOverridden}
|
isOverridden={isApprovalOverridden}
|
||||||
|
mcpProbingServerIds={mcpProbingServerIds}
|
||||||
onUpdate={onUpdateSessionApprovalSettings}
|
onUpdate={onUpdateSessionApprovalSettings}
|
||||||
toolingSettings={toolingSettings}
|
toolingSettings={toolingSettings}
|
||||||
/>
|
/>
|
||||||
@@ -543,13 +548,14 @@ export function ChatPane({
|
|||||||
selection={toolSelection}
|
selection={toolSelection}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasToolCallApproval && onUpdateSessionApprovalSettings && approvalTools.length > 0 && (
|
{hasToolCallApproval && onUpdateSessionApprovalSettings && hasApprovalContent && (
|
||||||
<InlineApprovalPill
|
<InlineApprovalPill
|
||||||
approvalTools={approvalTools}
|
approvalTools={approvalTools}
|
||||||
disabled={isComposerDisabled}
|
disabled={isComposerDisabled}
|
||||||
effectiveAutoApproved={effectiveAutoApproved}
|
effectiveAutoApproved={effectiveAutoApproved}
|
||||||
effectiveAutoApprovedCount={effectiveAutoApprovedCount}
|
effectiveAutoApprovedCount={effectiveAutoApprovedCount}
|
||||||
isOverridden={isApprovalOverridden}
|
isOverridden={isApprovalOverridden}
|
||||||
|
mcpProbingServerIds={mcpProbingServerIds}
|
||||||
onUpdate={onUpdateSessionApprovalSettings}
|
onUpdate={onUpdateSessionApprovalSettings}
|
||||||
toolingSettings={toolingSettings}
|
toolingSettings={toolingSettings}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { ChevronDown, ChevronRight, Minus, Search, Sparkles } from 'lucide-react';
|
import { ChevronDown, ChevronRight, Loader2, Minus, Search, Sparkles } from 'lucide-react';
|
||||||
|
|
||||||
import { ProviderIcon } from '@renderer/components/ProviderIcons';
|
import { ProviderIcon } from '@renderer/components/ProviderIcons';
|
||||||
import { PopoverToggleRow } from '@renderer/components/ui';
|
import { PopoverToggleRow } from '@renderer/components/ui';
|
||||||
@@ -347,6 +347,7 @@ export function InlineApprovalPill({
|
|||||||
effectiveAutoApprovedCount,
|
effectiveAutoApprovedCount,
|
||||||
isOverridden,
|
isOverridden,
|
||||||
disabled,
|
disabled,
|
||||||
|
mcpProbingServerIds,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
}: {
|
}: {
|
||||||
approvalTools: ApprovalToolDefinition[];
|
approvalTools: ApprovalToolDefinition[];
|
||||||
@@ -355,6 +356,7 @@ export function InlineApprovalPill({
|
|||||||
effectiveAutoApprovedCount: number;
|
effectiveAutoApprovedCount: number;
|
||||||
isOverridden: boolean;
|
isOverridden: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
mcpProbingServerIds?: string[];
|
||||||
onUpdate: (settings: { autoApprovedToolNames?: string[] }) => void;
|
onUpdate: (settings: { autoApprovedToolNames?: string[] }) => void;
|
||||||
}){
|
}){
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -362,6 +364,12 @@ export function InlineApprovalPill({
|
|||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||||
const ref = useClickOutside<HTMLDivElement>(() => { setOpen(false); setSearch(''); }, open);
|
const ref = useClickOutside<HTMLDivElement>(() => { setOpen(false); setSearch(''); }, open);
|
||||||
|
|
||||||
|
const probingSet = useMemo(
|
||||||
|
() => new Set(mcpProbingServerIds ?? []),
|
||||||
|
[mcpProbingServerIds],
|
||||||
|
);
|
||||||
|
const isProbingAny = probingSet.size > 0;
|
||||||
|
|
||||||
const groups = useMemo(
|
const groups = useMemo(
|
||||||
() => groupApprovalToolsByProvider(approvalTools, toolingSettings),
|
() => groupApprovalToolsByProvider(approvalTools, toolingSettings),
|
||||||
[approvalTools, toolingSettings],
|
[approvalTools, toolingSettings],
|
||||||
@@ -439,6 +447,12 @@ export function InlineApprovalPill({
|
|||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGroupProbing(group: ApprovalToolGroup): boolean {
|
||||||
|
if (group.kind !== 'mcp') return false;
|
||||||
|
const serverId = group.id.replace(/^mcp:/, '');
|
||||||
|
return probingSet.has(serverId);
|
||||||
|
}
|
||||||
|
|
||||||
function toggleExpanded(groupId: string) {
|
function toggleExpanded(groupId: string) {
|
||||||
setExpandedGroups((prev) => {
|
setExpandedGroups((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
@@ -472,8 +486,15 @@ export function InlineApprovalPill({
|
|||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<ShieldCheck className="size-2.5" />
|
{isProbingAny ? (
|
||||||
<span>{effectiveAutoApprovedCount}/{totalItemCount} auto-approved</span>
|
<Loader2 className="size-2.5 animate-spin" aria-label="Probing MCP servers" />
|
||||||
|
) : (
|
||||||
|
<ShieldCheck className="size-2.5" />
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{effectiveAutoApprovedCount}/{totalItemCount} auto-approved
|
||||||
|
{isProbingAny && <span className="text-zinc-500"> · probing…</span>}
|
||||||
|
</span>
|
||||||
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -525,6 +546,7 @@ export function InlineApprovalPill({
|
|||||||
const isBuiltin = group.kind === 'builtin';
|
const isBuiltin = group.kind === 'builtin';
|
||||||
const isCollapsible = !isBuiltin;
|
const isCollapsible = !isBuiltin;
|
||||||
const expanded = isBuiltin || isGroupExpanded(group.id);
|
const expanded = isBuiltin || isGroupExpanded(group.id);
|
||||||
|
const probing = isGroupProbing(group);
|
||||||
const groupState = isGroupApproved(group);
|
const groupState = isGroupApproved(group);
|
||||||
const allApproved = groupState === 'all';
|
const allApproved = groupState === 'all';
|
||||||
const someApproved = groupState === 'some';
|
const someApproved = groupState === 'some';
|
||||||
@@ -547,20 +569,30 @@ export function InlineApprovalPill({
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
{group.tools.length > 0 ? (
|
{probing ? (
|
||||||
|
<Loader2 className="size-3 shrink-0 animate-spin text-indigo-400" aria-label="Probing server" />
|
||||||
|
) : group.tools.length > 0 ? (
|
||||||
<ChevronRight className={`size-3 shrink-0 text-zinc-600 transition ${expanded ? 'rotate-90' : ''}`} />
|
<ChevronRight className={`size-3 shrink-0 text-zinc-600 transition ${expanded ? 'rotate-90' : ''}`} />
|
||||||
) : (
|
) : (
|
||||||
<Server className="size-3 shrink-0 text-zinc-600" />
|
<Server className="size-3 shrink-0 text-zinc-600" />
|
||||||
)}
|
)}
|
||||||
<span className="min-w-0 flex-1 truncate text-[12px] font-medium text-zinc-300">{group.label}</span>
|
<span className="min-w-0 flex-1 truncate text-[12px] font-medium text-zinc-300">{group.label}</span>
|
||||||
<span className="shrink-0 rounded-full bg-zinc-800/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-zinc-500">
|
{probing ? (
|
||||||
{approvedLabel}
|
<span className="shrink-0 rounded-full bg-indigo-500/10 px-1.5 py-px text-[9px] font-medium text-indigo-400">
|
||||||
</span>
|
probing…
|
||||||
<GroupToggle
|
</span>
|
||||||
allApproved={allApproved}
|
) : (
|
||||||
someApproved={someApproved}
|
<span className="shrink-0 rounded-full bg-zinc-800/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-zinc-500">
|
||||||
onToggle={(e) => { e.stopPropagation(); toggleGroup(group); }}
|
{approvedLabel}
|
||||||
/>
|
</span>
|
||||||
|
)}
|
||||||
|
{!probing && (
|
||||||
|
<GroupToggle
|
||||||
|
allApproved={allApproved}
|
||||||
|
someApproved={someApproved}
|
||||||
|
onToggle={(e) => { e.stopPropagation(); toggleGroup(group); }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user