diff --git a/apps/web/components/configure-view.tsx b/apps/web/components/configure-view.tsx index 98be696b..befd8d4f 100644 --- a/apps/web/components/configure-view.tsx +++ b/apps/web/components/configure-view.tsx @@ -116,16 +116,19 @@ export function ConfigureView() {
-
-

- {active.label} -

-

- {active.description} -

+
+
+

+ {active.label} +

+

+ {active.description} +

+
+
onDone: () => void onCancelNew?: () => void onCollapse?: () => void @@ -349,9 +354,18 @@ function AutomationCard({ const b = (await res.json().catch(() => ({}))) as { error?: string } throw new Error(b.error ?? "Couldn't save.") } + const b = (await res.json().catch(() => ({}))) as { + warnings?: { app: string }[] + } + return b.warnings ?? [] }, - onSuccess: () => { + onSuccess: (warnings) => { toast.success("Automation saved.") + if (warnings.length) + toast.warning( + `Heads up: ${warnings.map((w) => w.app).join(", ")} ${warnings.length === 1 ? "is" : "are"} connected personally and won't be available to this channel automation. Share the connection with the workspace in Connections.`, + { duration: 10000 }, + ) onDone() }, onError: (err) => @@ -619,6 +633,45 @@ function AutomationCard({ Cancel ) : null} + + {draft.deliverTo === "channel" && personalOnlyApps.length > 0 && ( + + + + {personalOnlyApps.map((app) => ( + + + {appCatalog[app]?.iconDomain ? ( + {appCatalog[app]?.name + ) : ( + + {app.slice(0, 1)} + + )} + + + {appCatalog[app]?.name ?? app} + + + ))} + + + + only connected to you ยท{" "} + + Share with the workspace + {" "} + to use here + + + )}
{id ? ( @@ -853,6 +906,11 @@ export default function CompanyBrainAutomations() { const { user, org } = useAuth() const queryClient = useQueryClient() const [drafts, setDrafts] = useState<{ key: number; draft: Draft }[]>([]) + const [showAllTemplates, setShowAllTemplates] = useState(false) + const [actionSlot, setActionSlot] = useState(null) + useEffect(() => { + setActionSlot(document.getElementById("configure-section-actions")) + }, []) const [openId, setOpenId] = useState(null) const draftKey = useRef(0) const addDraft = (draft: Draft) => @@ -886,11 +944,15 @@ export default function CompanyBrainAutomations() { const res = await fetch(`${BACKEND}/brain/mcp-connections/`, { credentials: "include", }) - if (!res.ok) return [] as string[] + if (!res.ok) return [] as { serverSlug: string; userId: string | null }[] const body = (await res.json()) as { - connections?: { serverSlug: string }[] + connections?: { + serverSlug: string + userId: string | null + status: string + }[] } - return (body.connections ?? []).map((c) => c.serverSlug) + return (body.connections ?? []).filter((c) => c.status === "active") }, enabled: isCompanyBrain, }) @@ -899,7 +961,39 @@ export default function CompanyBrainAutomations() { const channels = channelsQuery.data ?? [] const automations = listQuery.data ?? [] - const presets = sortPresets(new Set(appsQuery.data ?? [])) + const catalogQuery = useQuery({ + queryKey: ["company-brain-automations", "catalog", "v2"], + queryFn: async () => { + const res = await fetch(`${BACKEND}/brain/mcp-connections/catalog`, { + credentials: "include", + }) + if (!res.ok) + return {} as Record + const body = (await res.json()) as { + catalog?: { slug: string; name?: string; iconDomain?: string }[] + } + return Object.fromEntries( + (body.catalog ?? []).map((e) => [ + e.slug, + { name: e.name ?? e.slug, iconDomain: e.iconDomain }, + ]), + ) + }, + enabled: isCompanyBrain, + }) + + const connections = appsQuery.data ?? [] + const presets = sortPresets(new Set(connections.map((c) => c.serverSlug))) + const sharedApps = new Set( + connections.filter((c) => c.userId === null).map((c) => c.serverSlug), + ) + const personalOnlyApps = [ + ...new Set( + connections + .filter((c) => c.userId !== null && !sharedApps.has(c.serverSlug)) + .map((c) => c.serverSlug), + ), + ] const nameFor = (userId: string | null): string | undefined => { if (!userId) return undefined if (userId === user?.id) return "You" @@ -913,10 +1007,34 @@ export default function CompanyBrainAutomations() { } const usedTitles = new Set(automations.map((a) => a.title)) const availablePresets = presets.filter((p) => !usedTitles.has(p.label)) + const shownPresets = showAllTemplates + ? availablePresets + : availablePresets.slice(0, 3) + const hiddenTemplateCount = availablePresets.length - shownPresets.length const hasList = automations.length > 0 || drafts.length > 0 return (
+ {(() => { + const newAutomationButton = ( + + ) + return actionSlot ? ( + createPortal(newAutomationButton, actionSlot) + ) : ( +
{newAutomationButton}
+ ) + })()}
{automations.map((a) => openId === a.id ? ( @@ -925,6 +1043,8 @@ export default function CompanyBrainAutomations() { id={a.id} initial={toDraft(a)} channels={channels} + personalOnlyApps={personalOnlyApps} + appCatalog={catalogQuery.data ?? {}} onDone={() => { setOpenId(null) refresh() @@ -953,6 +1073,8 @@ export default function CompanyBrainAutomations() { id={null} initial={draft} channels={channels} + personalOnlyApps={personalOnlyApps} + appCatalog={catalogQuery.data ?? {}} onDone={() => { removeDraft(key) refresh() @@ -961,37 +1083,38 @@ export default function CompanyBrainAutomations() { /> ))} - {hasList ? ( -

- Templates -

- ) : null} +

+ {showAllTemplates ? "Templates" : "Ideas for your setup"} +

- {availablePresets.map((p) => ( + {shownPresets.map((p) => ( addDraft(presetToDraft(p))} /> ))} +
+ {hiddenTemplateCount > 0 || showAllTemplates ? ( -
+ ) : null}
)