mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-10-01 05:54:56 +02:00
[eric] share: minimal import modal, name lives in the title, one type+counts line, inline need chips, single-line trust note
This commit is contained in:
@@ -30,5 +30,5 @@ def scan_app_files(files: dict[str, bytes]) -> ReviewSummary:
|
||||
verdict = "warn" if findings else "clean"
|
||||
if runnable:
|
||||
verdict = "warn"
|
||||
findings.insert(0, "This app runs code on your computer when you open it. Only import apps you trust.")
|
||||
findings.insert(0, "This app runs code on your computer. Only import apps you trust.")
|
||||
return ReviewSummary(verdict=verdict, findings=findings, scanned_files=scanned)
|
||||
|
||||
@@ -56,26 +56,11 @@ const ImportModal: React.FC<Props> = ({ preflight, open, committing, onConfirm,
|
||||
<Box sx={{ px: 3, pb: 3 }}>
|
||||
<IncludesList summary={preflight.summary} />
|
||||
{preflight.review && preflight.review.findings.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 1.5,
|
||||
p: 1.25,
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
alignItems: 'flex-start',
|
||||
borderRadius: `${c.radius.md}px`,
|
||||
border: `1px solid ${c.status.warning}33`,
|
||||
bgcolor: `${c.status.warning}12`,
|
||||
}}
|
||||
>
|
||||
<ShieldOutlinedIcon sx={{ fontSize: 16, color: c.status.warning, mt: '2px', flexShrink: 0 }} />
|
||||
<Box>
|
||||
{preflight.review.findings.map((f, i) => (
|
||||
<Typography key={`rv-${i}`} sx={{ fontSize: '0.78rem', color: c.text.secondary, lineHeight: 1.5 }}>
|
||||
{f}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
<Box sx={{ mt: 1.75, display: 'flex', gap: 0.85, alignItems: 'center' }}>
|
||||
<ShieldOutlinedIcon sx={{ fontSize: 15, color: c.status.warning, flexShrink: 0 }} />
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, lineHeight: 1.4 }}>
|
||||
{preflight.review.findings.join(' ')}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{preflight.conflicts.length > 0 && (
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// The "what's inside this bundle" panel, shared by the Share and Import modals:
|
||||
// the root entity, the dependencies pulled in with it, and any environment
|
||||
// requirements (an Action the importer must enable themselves). Long bundles
|
||||
// (a dashboard pulls in every agent) read as a wall, so the contents collapse
|
||||
// to a one-line count by default and expand on demand. Requirements always
|
||||
// show, they're the part the importer has to act on.
|
||||
// The "what's inside this bundle" panel, shared by the Share and Import modals.
|
||||
// Kept deliberately spare: the bundle's name already lives in the modal title, so
|
||||
// here it's just one line of type + counts, the requirements as small icon chips,
|
||||
// and an optional expand for the full contents. No boxes, the modal's whitespace
|
||||
// does the grouping.
|
||||
import React, { useState } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -17,14 +16,6 @@ import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
|
||||
import { BundleSummary } from './shareTypes';
|
||||
|
||||
// One glyph per requirement kind, so a row of needs reads as icons + names
|
||||
// instead of a stack of explanatory sentences. The sentence moves to a hover.
|
||||
const REQ_ICON: Record<string, React.ReactNode> = {
|
||||
mcp_action: <ExtensionOutlinedIcon sx={{ fontSize: 14 }} />,
|
||||
api_key: <KeyOutlinedIcon sx={{ fontSize: 14 }} />,
|
||||
builtin_mode: <TuneOutlinedIcon sx={{ fontSize: 14 }} />,
|
||||
};
|
||||
|
||||
const KIND_LABEL: Record<string, string> = {
|
||||
skill: 'Skill',
|
||||
app: 'App',
|
||||
@@ -34,6 +25,13 @@ const KIND_LABEL: Record<string, string> = {
|
||||
session: 'Agent',
|
||||
};
|
||||
|
||||
// One glyph per requirement kind, so needs read as icons + names, not sentences.
|
||||
const REQ_ICON: Record<string, React.ReactNode> = {
|
||||
mcp_action: <ExtensionOutlinedIcon sx={{ fontSize: 14 }} />,
|
||||
api_key: <KeyOutlinedIcon sx={{ fontSize: 14 }} />,
|
||||
builtin_mode: <TuneOutlinedIcon sx={{ fontSize: 14 }} />,
|
||||
};
|
||||
|
||||
const pluralize = (label: string, n: number): string => (n === 1 ? label : `${label}s`);
|
||||
|
||||
const IncludesList: React.FC<{ summary: BundleSummary }> = ({ summary }) => {
|
||||
@@ -41,7 +39,7 @@ const IncludesList: React.FC<{ summary: BundleSummary }> = ({ summary }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const includes = summary.includes;
|
||||
|
||||
// One quiet line: "9 agents · 1 app", in the bundle's own type order.
|
||||
// "9 agents · 1 app", in the bundle's own type order.
|
||||
const order: string[] = [];
|
||||
const byType = new Map<string, number>();
|
||||
for (const it of includes) {
|
||||
@@ -51,121 +49,64 @@ const IncludesList: React.FC<{ summary: BundleSummary }> = ({ summary }) => {
|
||||
const countLine = order
|
||||
.map((t) => `${byType.get(t)} ${pluralize((KIND_LABEL[t] || t).toLowerCase(), byType.get(t) || 0)}`)
|
||||
.join(' · ');
|
||||
|
||||
const Row: React.FC<{ tag: string; name: string; detail?: string; faded?: boolean }> = ({
|
||||
tag,
|
||||
name,
|
||||
detail,
|
||||
faded,
|
||||
}) => (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, py: 0.55 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '0.62rem',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.06em',
|
||||
textTransform: 'uppercase',
|
||||
color: c.text.tertiary,
|
||||
minWidth: 64,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '0.85rem',
|
||||
color: faded ? c.text.muted : c.text.primary,
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
{detail && (
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, flexShrink: 0 }}>{detail}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
const rootLabel = KIND_LABEL[summary.root.type] || summary.root.type;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
border: `1px solid ${c.border.subtle}`,
|
||||
borderRadius: `${c.radius.lg}px`,
|
||||
bgcolor: c.bg.surface,
|
||||
px: 2,
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
<Row tag={KIND_LABEL[summary.root.type] || summary.root.type} name={summary.root.name} />
|
||||
|
||||
{includes.length > 0 && !expanded && (
|
||||
<Box
|
||||
onClick={() => setExpanded(true)}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 1,
|
||||
py: 0.55,
|
||||
cursor: 'pointer',
|
||||
'&:hover .show-toggle': { color: c.accent.primary },
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontSize: '0.82rem', color: c.text.muted }}>{countLine}</Typography>
|
||||
<Box className="show-toggle" sx={{ display: 'flex', alignItems: 'center', gap: 0.25, color: c.text.tertiary, transition: 'color 0.15s' }}>
|
||||
<Typography sx={{ fontSize: '0.72rem' }}>Show</Typography>
|
||||
<KeyboardArrowDownIcon sx={{ fontSize: 14 }} />
|
||||
<Box>
|
||||
{/* Lead: the bundle's type + a one-line count. The name is in the title. */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||
<Typography sx={{ fontSize: '0.85rem', color: c.text.muted }}>
|
||||
<Box component="span" sx={{ color: c.text.primary, fontWeight: 600 }}>{rootLabel}</Box>
|
||||
{countLine && ` · ${countLine}`}
|
||||
</Typography>
|
||||
{includes.length > 0 && (
|
||||
<Box
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 0.25, color: c.text.tertiary, cursor: 'pointer', '&:hover': { color: c.accent.primary } }}
|
||||
>
|
||||
<Typography sx={{ fontSize: '0.72rem' }}>{expanded ? 'Hide' : 'Show'}</Typography>
|
||||
<KeyboardArrowDownIcon sx={{ fontSize: 14, transform: expanded ? 'rotate(180deg)' : 'none', transition: 'transform 0.18s' }} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{expanded && includes.length > 0 && (
|
||||
<Box sx={{ mt: 0.5, maxHeight: 184, overflowY: 'auto' }}>
|
||||
{includes.map((it, i) => (
|
||||
<Box key={`inc-${i}`} sx={{ display: 'flex', gap: 1, py: 0.4 }}>
|
||||
<Typography sx={{ fontSize: '0.6rem', fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase', color: c.text.tertiary, minWidth: 56, flexShrink: 0, mt: '2px' }}>
|
||||
{KIND_LABEL[it.type] || it.type}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '0.82rem', color: c.text.secondary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{it.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{includes.length > 0 && expanded && (
|
||||
<>
|
||||
{includes.map((it, i) => (
|
||||
<Row key={`inc-${i}`} tag={KIND_LABEL[it.type] || it.type} name={it.name} detail={it.detail} />
|
||||
))}
|
||||
<Box
|
||||
onClick={() => setExpanded(false)}
|
||||
sx={{ py: 0.4, cursor: 'pointer', color: c.text.tertiary, '&:hover': { color: c.accent.primary } }}
|
||||
>
|
||||
<Typography sx={{ fontSize: '0.72rem' }}>Hide</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{summary.requirements.length > 0 && (
|
||||
<Box sx={{ mt: 0.75, pt: 0.75, borderTop: `1px solid ${c.border.subtle}` }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '0.62rem', fontWeight: 700, letterSpacing: '0.06em',
|
||||
textTransform: 'uppercase', color: c.text.tertiary, mb: 0.75,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 0.75, mt: 1.5 }}>
|
||||
<Typography sx={{ fontSize: '0.62rem', fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: c.text.tertiary, mr: 0.25 }}>
|
||||
Needs
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
|
||||
{summary.requirements.map((r, i) => (
|
||||
<Tooltip key={`req-${i}`} title={r.detail || ''} placement="top" arrow disableInteractive>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 0.5,
|
||||
px: 1, py: 0.4, borderRadius: '999px',
|
||||
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.elevated,
|
||||
color: c.text.tertiary, cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{REQ_ICON[r.kind] || <ExtensionOutlinedIcon sx={{ fontSize: 14 }} />}
|
||||
<Typography sx={{ fontSize: '0.76rem', color: c.text.secondary, whiteSpace: 'nowrap' }}>
|
||||
{r.label}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Box>
|
||||
{summary.requirements.map((r, i) => (
|
||||
<Tooltip key={`req-${i}`} title={r.detail || ''} placement="top" arrow disableInteractive>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 0.5,
|
||||
px: 1, py: 0.4, borderRadius: '999px',
|
||||
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.elevated,
|
||||
color: c.text.tertiary, cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{REQ_ICON[r.kind] || <ExtensionOutlinedIcon sx={{ fontSize: 14 }} />}
|
||||
<Typography sx={{ fontSize: '0.76rem', color: c.text.secondary, whiteSpace: 'nowrap' }}>
|
||||
{r.label}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user