Skip to content

Commit

Permalink
Add: dropdown to show all assistants used by a datasource and show mo…
Browse files Browse the repository at this point in the history
…dal on click (#8932)

* Add: dropdown to show all assistants used by a datasource and show modal when click

* Review fdbk
  • Loading branch information
Fraggle authored Nov 27, 2024
1 parent aca995d commit 68abb7c
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 17 deletions.
30 changes: 22 additions & 8 deletions front/components/spaces/SpaceResourcesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
DataTable,
FolderIcon,
PencilSquareIcon,
RobotIcon,
SearchInput,
Spinner,
TrashIcon,
Expand Down Expand Up @@ -37,15 +36,18 @@ import { useRef } from "react";
import { useState } from "react";
import * as React from "react";

import { AssistantDetails } from "@app/components/assistant/AssistantDetails";
import { ConnectorPermissionsModal } from "@app/components/ConnectorPermissionsModal";
import ConnectorSyncingChip from "@app/components/data_source/DataSourceSyncChip";
import { DeleteStaticDataSourceDialog } from "@app/components/data_source/DeleteStaticDataSourceDialog";
import type { DataSourceIntegration } from "@app/components/spaces/AddConnectionMenu";
import { AddConnectionMenu } from "@app/components/spaces/AddConnectionMenu";
import { EditSpaceManagedDataSourcesViews } from "@app/components/spaces/EditSpaceManagedDatasourcesViews";
import { EditSpaceStaticDatasourcesViews } from "@app/components/spaces/EditSpaceStaticDatasourcesViews";
import { UsedByButton } from "@app/components/spaces/UsedByButton";
import { getConnectorProviderLogoWithFallback } from "@app/lib/connector_providers";
import { getDataSourceNameFromView, isManaged } from "@app/lib/data_sources";
import { useAgentConfigurationSIdLookup } from "@app/lib/swr/assistants";
import { useDataSources } from "@app/lib/swr/data_sources";
import {
useDeleteFolderOrWebsite,
Expand Down Expand Up @@ -76,10 +78,12 @@ type RowData = {
};

const getTableColumns = ({
setAssistantName,
isManaged,
isWebsite,
space,
}: {
setAssistantName: (name: string | null) => void;
isManaged: boolean;
isWebsite: boolean;
space: SpaceType;
Expand Down Expand Up @@ -135,13 +139,11 @@ const getTableColumns = ({
cell: (info: CellContext<RowData, DataSourceWithAgentsUsageType>) => (
<>
{info.row.original.dataSourceView.usage ? (
<DataTable.CellContent
icon={RobotIcon}
title={`Used by ${info.row.original.dataSourceView.usage.agentNames.join(
", "
)}`}
>
{info.row.original.dataSourceView.usage.count}
<DataTable.CellContent>
<UsedByButton
usage={info.row.original.dataSourceView.usage}
onItemClick={setAssistantName}
/>
</DataTable.CellContent>
) : null}
</>
Expand Down Expand Up @@ -255,6 +257,12 @@ export const SpaceResourcesList = ({
onSelect,
integrations,
}: SpaceResourcesListProps) => {
const [assistantName, setAssistantName] = useState<string | null>(null); // To show the assistant details
const { sId: assistantSId } = useAgentConfigurationSIdLookup({
workspaceId: owner.sId,
agentConfigurationName: assistantName,
});

const [dataSourceSearch, setDataSourceSearch] = useState<string>("");
const [showConnectorPermissionsModal, setShowConnectorPermissionsModal] =
useState(false);
Expand Down Expand Up @@ -514,11 +522,17 @@ export const SpaceResourcesList = ({
)}
</>
)}
<AssistantDetails
owner={owner}
assistantId={assistantSId}
onClose={() => setAssistantName(null)}
/>
</div>
{rows.length > 0 && (
<DataTable
data={rows}
columns={getTableColumns({
setAssistantName,
isManaged: isManagedCategory,
isWebsite: isWebsite,
space,
Expand Down
55 changes: 55 additions & 0 deletions front/components/spaces/UsedByButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
RobotIcon,
ScrollArea,
} from "@dust-tt/sparkle";
import type { DataSourceWithAgentsUsageType } from "@dust-tt/types";

export const UsedByButton = ({
usage,
onItemClick,
}: {
usage: DataSourceWithAgentsUsageType;
onItemClick: (assistantSid: string) => void;
}) => {
return usage.count === 0 ? (
<Button
icon={RobotIcon}
variant="ghost"
isSelect={false}
size="sm"
label={`${usage.count}`}
disabled
/>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
icon={RobotIcon}
variant="ghost"
isSelect={true}
size="sm"
label={`${usage.count}`}
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="max-w-[300px]">
<ScrollArea className="border-1 h-[130px]">
{usage.agentNames.map((name) => (
<DropdownMenuItem
key={`assistant-picker-${name}`}
label={name}
onClick={(e) => {
e.stopPropagation();
onItemClick(name);
}}
/>
))}
</ScrollArea>
</DropdownMenuContent>
</DropdownMenu>
);
};
28 changes: 19 additions & 9 deletions front/lib/api/assistant/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1202,24 +1202,34 @@ async function _createAgentDataSourcesConfigData(
return agentDataSourcesConfigRows;
}

export async function agentNameIsAvailable(
export async function getAgentSIdFromName(
auth: Authenticator,
nameToCheck: string
) {
const owner = auth.workspace();
if (!owner) {
throw new Error("Unexpected `auth` without `workspace`.");
}
name: string
): Promise<string | null> {
const owner = auth.getNonNullableWorkspace();

const agent = await AgentConfiguration.findOne({
attributes: ["sId"],
where: {
workspaceId: owner.id,
name: nameToCheck,
name,
status: "active",
},
});

return !agent;
if (!agent) {
return null;
}

return agent.sId;
}

export async function agentNameIsAvailable(
auth: Authenticator,
nameToCheck: string
) {
const sId = await getAgentSIdFromName(auth, nameToCheck);
return !sId;
}

export async function setAgentScope(
Expand Down
25 changes: 25 additions & 0 deletions front/lib/swr/assistants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,31 @@ export function useProgressiveAgentConfigurations({
};
}

export function useAgentConfigurationSIdLookup({
workspaceId,
agentConfigurationName,
}: {
workspaceId: string;
agentConfigurationName: string | null;
}) {
const sIdFetcher: Fetcher<{
sId: string;
}> = fetcher;

const { data, error } = useSWRWithDefaults(
agentConfigurationName
? `/api/w/${workspaceId}/assistant/agent_configurations/lookup?handle=${agentConfigurationName}`
: null,
sIdFetcher
);

return {
sId: data ? data.sId : null,
isLoading: !error && !data,
isError: error,
};
}

export function useAgentConfiguration({
workspaceId,
agentConfigurationId,
Expand Down
64 changes: 64 additions & 0 deletions front/pages/api/w/[wId]/assistant/agent_configurations/lookup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { WithAPIErrorResponse } from "@dust-tt/types";
import { isLeft } from "fp-ts/lib/Either";
import * as t from "io-ts";
import * as reporter from "io-ts-reporters";
import type { NextApiRequest, NextApiResponse } from "next";

import { getAgentSIdFromName } from "@app/lib/api/assistant/configuration";
import { withSessionAuthenticationForWorkspace } from "@app/lib/api/auth_wrappers";
import type { Authenticator } from "@app/lib/auth";
import { apiError } from "@app/logger/withlogging";

const GetLookupRequestSchema = t.type({
handle: t.string,
});

type GetLookupResponseBody = {
sId: string;
};

async function handler(
req: NextApiRequest,
res: NextApiResponse<WithAPIErrorResponse<GetLookupResponseBody | void>>,
auth: Authenticator
): Promise<void> {
switch (req.method) {
case "GET":
const bodyValidation = GetLookupRequestSchema.decode(req.query);

if (isLeft(bodyValidation)) {
const pathError = reporter.formatValidationErrors(bodyValidation.left);

return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: `Invalid request body: ${pathError}`,
},
});
}
const sId = await getAgentSIdFromName(auth, bodyValidation.right.handle);
if (!sId) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "agent_configuration_not_found",
message: "The Assistant you're trying to access was not found.",
},
});
}

return res.status(200).json({ sId });

default:
return apiError(req, res, {
status_code: 405,
api_error: {
type: "method_not_supported_error",
message: "The method passed is not supported, GET is expected.",
},
});
}
}

export default withSessionAuthenticationForWorkspace(handler);

0 comments on commit 68abb7c

Please sign in to comment.