Skip to content

Commit

Permalink
[Meru] Slack Integration (#3947)
Browse files Browse the repository at this point in the history
* [Meru] Slack Integration

* Clean up after self review

* Clean up double #
  • Loading branch information
lasryaric authored Feb 26, 2024
1 parent c04465d commit bc4f07d
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 236 deletions.
2 changes: 1 addition & 1 deletion connectors/src/api/slack_channels_linked_with_agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const _getSlackChannelsLinkedWithAgentHandler = async (
res.status(200).json({
slackChannels: slackChannels.map((c) => ({
slackChannelId: c.slackChannelId,
slackChannelName: c.slackChannelName,
slackChannelName: "#" + c.slackChannelName,
// We know that agentConfigurationId is not null because of the where clause above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
agentConfigurationId: c.agentConfigurationId!,
Expand Down
234 changes: 187 additions & 47 deletions front/components/assistant/Sharing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,25 @@ import {
IconButton,
LinkIcon,
LockIcon,
Modal,
Page,
PlanetIcon,
ShapesIcon,
SlackLogo,
SliderToggle,
UserGroupIcon,
} from "@dust-tt/sparkle";
import type {
AgentConfigurationScope,
AgentConfigurationType,
DataSourceType,
WorkspaceType,
} from "@dust-tt/types";
import { isBuilder } from "@dust-tt/types";
import { useState } from "react";

import type { SlackChannel } from "@app/components/assistant/SlackIntegration";
import { SlackIntegration } from "@app/components/assistant/SlackIntegration";
import { assistantUsageMessage } from "@app/components/assistant/Usage";
import { useAgentConfiguration, useAgentUsage } from "@app/lib/swr";

Expand Down Expand Up @@ -102,13 +109,19 @@ export function SharingButton({
newScope,
setNewScope,
baseUrl,
slackDataSource,
slackChannelSelected,
setNewLinkedSlackChannels,
}: {
owner: WorkspaceType;
agentConfigurationId: string | null;
initialScope: NonGlobalScope;
newScope: NonGlobalScope;
setNewScope: (scope: NonGlobalScope) => void;
baseUrl: string;
slackDataSource: DataSourceType | null;
slackChannelSelected: SlackChannel[];
setNewLinkedSlackChannels: (channels: SlackChannel[]) => void;
}) {
const { agentUsage, isAgentUsageLoading, isAgentUsageError } = useAgentUsage({
workspaceId: owner.sId,
Expand All @@ -118,6 +131,7 @@ export function SharingButton({
workspaceId: owner.sId,
agentConfigurationId,
});
const [slackDrawerOpened, setSlackDrawerOpened] = useState(false);
const assistantName = agentConfiguration?.name;

const usageText = assistantName
Expand All @@ -134,65 +148,125 @@ export function SharingButton({
const [copyLinkSuccess, setCopyLinkSuccess] = useState<boolean>(false);

return (
<DropdownMenu>
<DropdownMenu.Button>
<Button
size="sm"
label="Sharing"
icon={ShapesIcon}
variant="tertiary"
<>
{slackDataSource && (
<SlackIntegrationDrawer
existingSelection={slackChannelSelected}
slackDataSource={slackDataSource}
owner={owner}
onSave={(slackChannels: SlackChannel[]) => {
setNewLinkedSlackChannels(slackChannels);
}}
assistantHandle="@Dust"
show={slackDrawerOpened}
onClose={() => setSlackDrawerOpened(false)}
/>
</DropdownMenu.Button>
<DropdownMenu.Items width={319} overflow="visible">
<div className="-mx-1 flex flex-col gap-y-4 pb-2 pt-3">
<div className="flex flex-col gap-y-2">
<SharingDropdown
owner={owner}
agentConfiguration={agentConfiguration}
initialScope={initialScope}
newScope={newScope}
setNewScope={setNewScope}
/>
<div className="text-sm text-element-700">
<div>
{SCOPE_INFO[newScope].text}{" "}
{agentUsage &&
agentUsage.usersWithAgentInListCount > 0 &&
newScope !== "private"
? usageText
: null}
)}
<DropdownMenu>
<DropdownMenu.Button>
<Button
size="sm"
label="Sharing"
icon={ShapesIcon}
variant="tertiary"
/>
</DropdownMenu.Button>
<DropdownMenu.Items width={319} overflow="visible">
<div className="-mx-1 flex flex-col gap-y-4 pb-2 pt-3">
<div className="flex flex-col gap-y-2">
<SharingDropdown
owner={owner}
agentConfiguration={agentConfiguration}
initialScope={initialScope}
newScope={newScope}
setNewScope={setNewScope}
/>
<div className="text-sm text-element-700">
<div>
{SCOPE_INFO[newScope].text}{" "}
{agentUsage &&
agentUsage.usersWithAgentInListCount > 0 &&
newScope !== "private"
? usageText
: null}
</div>
</div>
</div>
</div>
{agentConfigurationId && (

<div className="flex flex-row justify-between">
<div>
<div className="text-base font-bold text-element-800">Link</div>
<div className="text-base font-bold text-element-800">
Slack integration
</div>
<div className="text-sm text-element-700">
Shareable direct URL
{slackChannelSelected.length === 0 ? (
<>Set as default assistant for specific channels.</>
) : (
<>
Default assistant for{" "}
{slackChannelSelected
.map((c) => c.slackChannelName)
.join(", ")}
</>
)}
</div>

<div className="pt-3">
{slackChannelSelected.length > 0 && (
<Button
size="xs"
variant="secondary"
label="Manage channels"
onClick={() => setSlackDrawerOpened(true)}
/>
)}
</div>
</div>
<div>
<Button
size="sm"
icon={copyLinkSuccess ? ClipboardCheckIcon : LinkIcon}
label={copyLinkSuccess ? "Copied!" : "Copy link"}
variant="secondary"
className="w-full"
onClick={async () => {
await navigator.clipboard.writeText(shareLink);
setCopyLinkSuccess(true);
setTimeout(() => {
setCopyLinkSuccess(false);
}, 1000);
<div className="">
<SliderToggle
selected={slackChannelSelected.length > 0}
onClick={() => {
if (slackChannelSelected.length > 0) {
setNewLinkedSlackChannels([]);
} else {
setSlackDrawerOpened(true);
}
}}
/>
</div>
</div>
)}
</div>
</DropdownMenu.Items>
</DropdownMenu>
{agentConfigurationId && (
<div className="flex flex-row justify-between">
<div>
<div className="text-base font-bold text-element-800">
Link
</div>
<div className="text-sm text-element-700">
Shareable direct URL
</div>
</div>
<div>
<Button
size="sm"
icon={copyLinkSuccess ? ClipboardCheckIcon : LinkIcon}
label={copyLinkSuccess ? "Copied!" : "Copy link"}
variant="secondary"
className="w-full"
onClick={async () => {
await navigator.clipboard.writeText(shareLink);
setCopyLinkSuccess(true);
setTimeout(() => {
setCopyLinkSuccess(false);
}, 1000);
}}
/>
</div>
</div>
)}
</div>
</DropdownMenu.Items>
</DropdownMenu>
</>
);
}

Expand Down Expand Up @@ -380,3 +454,69 @@ function ScopeChangeModal({
</Dialog>
);
}

function SlackIntegrationDrawer({
slackDataSource,
owner,
existingSelection,
onSave,
assistantHandle,
show,
onClose,
}: {
show: boolean;
onClose: () => void;
slackDataSource: DataSourceType;
owner: WorkspaceType;
existingSelection: SlackChannel[];
onSave: (channels: SlackChannel[]) => void;
assistantHandle?: string;
}) {
const [slackIntegrationOpened, setSlackIntegrationOpened] = useState(false);
return (
<>
<SlackIntegration
slackDataSource={slackDataSource}
owner={owner}
existingSelection={existingSelection}
onSave={(slackChannels) => {
onSave(slackChannels);
setSlackIntegrationOpened(false);
}}
onClose={() => setSlackIntegrationOpened(false)}
show={slackIntegrationOpened}
assistantHandle={assistantHandle}
/>
<Modal
isOpen={show}
title={`Slack Integration`}
onClose={onClose}
hasChanged={false}
variant="side-sm"
>
<div className="pt-8">
<Page.Vertical gap="lg" align="stretch">
<div className=" flex flex-col gap-y-2">
<div className="grow text-sm font-medium text-element-800">
<SlackLogo className="h-8 w-8" />
</div>
<div className="text-sm font-normal text-element-900">
Set this assistant as the default assistant on one or several of
your Slack channels. It will answer by default when the{" "}
<span className="font-bold">{assistantHandle}</span> Slack bot
is mentionned in these channels.
</div>
<div className="justify-end pt-2">
<Button
hasMagnifying={false}
label="Select channels"
onClick={() => setSlackIntegrationOpened(true)}
/>
</div>
</div>
</Page.Vertical>
</div>
</Modal>
</>
);
}
Loading

0 comments on commit bc4f07d

Please sign in to comment.