Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Meru][Assistant Builder] Improved description suggestion #4559

Merged
merged 5 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 127 additions & 73 deletions front/components/assistant_builder/NamingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Page,
PencilSquareIcon,
SparklesIcon,
Spinner2,
} from "@dust-tt/sparkle";
import type {
APIError,
Expand All @@ -14,16 +15,24 @@ import type {
WorkspaceType,
} from "@dust-tt/types";
import { Err, Ok } from "@dust-tt/types";
import React, { useCallback, useEffect, useRef, useState } from "react";
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";

import { AvatarPicker } from "@app/components/assistant_builder/AssistantBuilderAvatarPicker";
import {
DROID_AVATAR_URLS,
SPIRIT_AVATAR_URLS,
} from "@app/components/assistant_builder/shared";
import type { AssistantBuilderState } from "@app/components/assistant_builder/types";
import { SendNotificationsContext } from "@app/components/sparkle/Notification";
import { isDevelopmentOrDustWorkspace } from "@app/lib/development";
import { debounce } from "@app/lib/utils/debounce";
import { as } from "fp-ts/lib/Option";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔


export function removeLeadingAt(handle: string) {
return handle.startsWith("@") ? handle.slice(1) : handle;
Expand Down Expand Up @@ -134,6 +143,7 @@ export default function NamingScreen({
setEdited: (edited: boolean) => void;
assistantHandleError: string | null;
}) {
const sendNotification = useContext(SendNotificationsContext);
const [isAvatarModalOpen, setIsAvatarModalOpen] = useState(false);

// Name suggestions handling
Expand Down Expand Up @@ -168,35 +178,74 @@ export default function NamingScreen({
]);

// Description suggestions handling
const [descriptionSuggestions, setDescriptionSuggestions] =
useState<BuilderSuggestionsType>({
status: "unavailable",
reason: "irrelevant",
});

const [descriptionSuggestionsIndex, setDescriptionSuggestionIndex] =
useState(0);
const [generatingDescription, setGeneratingDescription] = useState(false);
const [descriptionIsGenerated, setDescriptionIsGenerated] = useState(false);

const updateDescriptionSuggestions = useCallback(async () => {
const descriptionSuggestions = await getDescriptionSuggestions({
const suggestDescription = useCallback(
async (fromUserClick?: boolean) => {
setGeneratingDescription(true);
const notifyError = fromUserClick ? sendNotification : console.log;
const descriptionSuggestions = await getDescriptionSuggestions({
owner,
instructions: builderState.instructions || "",
name: builderState.handle || "",
});
if (descriptionSuggestions.isOk()) {
const suggestion =
descriptionSuggestions.value.status === "ok" &&
descriptionSuggestions.value.suggestions.length > 0
? descriptionSuggestions.value.suggestions[0]
: null;
if (suggestion) {
setBuilderState((state) => ({
...state,
description: suggestion,
}));
setDescriptionIsGenerated(true);
} else {
const errorMessage =
descriptionSuggestions.value.status === "unavailable"
? descriptionSuggestions.value.reason ||
"No suggestions available"
: "No suggestions available";
notifyError({
type: "error",
title: "Error generating description suggestion.",
description: errorMessage,
});
}
} else {
notifyError({
type: "error",
title: "Error generating description suggestion.",
description: descriptionSuggestions.error.message,
});
}
setGeneratingDescription(false);
},
[
owner,
instructions: builderState.instructions || "",
name: builderState.handle || "",
});
if (descriptionSuggestions.isOk()) {
setDescriptionSuggestions(descriptionSuggestions.value);
}
}, [owner, builderState.instructions, builderState.handle]);
builderState.instructions,
builderState.handle,
setBuilderState,
sendNotification,
]
);

useEffect(() => {
if (isDevelopmentOrDustWorkspace(owner)) {
void updateDescriptionSuggestions();
if (
!builderState.description?.trim() &&
builderState.instructions?.trim() &&
!generatingDescription
) {
void suggestDescription();
}
}
}, [owner, updateDescriptionSuggestions]);

const suggestionsAvailable =
descriptionSuggestions.status === "ok" &&
descriptionSuggestions.suggestions.length > 0;
// Here we only want to run this effect once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<>
Expand Down Expand Up @@ -299,62 +348,54 @@ export default function NamingScreen({
</div>
<div className="flex flex-col gap-4">
<div>
<Page.SectionHeader title="Description" />
<div className="flex gap-1">
<Page.SectionHeader title="Description" />
{generatingDescription && <Spinner2 size="sm" />}
</div>
<div className="text-sm font-normal text-element-700">
Describe for others the assistant’s purpose.
Describe for others the assistant’s purpose.{" "}
</div>
</div>

<div className="flex items-center gap-2">
<div className="flex-grow">
<Input
placeholder={
suggestionsAvailable
? "Click on sparkles to generate a description"
: "Answer questions about sales, translate from English to French…"
generatingDescription
? "Generating description..."
: "Click on sparkles to generate a description"
}
value={builderState.description}
value={generatingDescription ? "" : builderState.description}
onChange={(value) => {
setEdited(true);
setDescriptionIsGenerated(false);
setBuilderState((state) => ({
...state,
description: value,
}));
}}
error={null} // TODO ?
name="assistantDescription"
className="text-sm"
disabled={generatingDescription}
/>
</div>
{isDevelopmentOrDustWorkspace(owner) && (
<IconButton
icon={SparklesIcon}
size="md"
disabled={generatingDescription}
onClick={async () => {
if (!suggestionsAvailable) return;
setEdited(true);
setBuilderState((state) => ({
...state,
description:
descriptionSuggestions.suggestions[
descriptionSuggestionsIndex
],
}));
if (descriptionSuggestionsIndex === 1) {
await updateDescriptionSuggestions();
setDescriptionSuggestionIndex(0);
} else {
setDescriptionSuggestionIndex(
descriptionSuggestionsIndex + 1
);
if (
!builderState.description?.trim() ||
descriptionIsGenerated ||
confirm(
"Heads up! This will overwrite your current description. Are you sure you want to proceed?"
)
) {
await suggestDescription();
}
}}
disabled={!suggestionsAvailable}
tooltip={
suggestionsAvailable
? "Click to generate a description"
: "Description generation not yet available"
}
tooltip="Click to generate a description"
/>
)}
</div>
Expand All @@ -373,23 +414,19 @@ async function getNamingSuggestions({
instructions: string;
description: string;
}): Promise<Result<BuilderSuggestionsType, APIError>> {
const res = await fetch(`/api/w/${owner.sId}/assistant/builder/suggestions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "name",
inputs: { instructions, description },
}),
});
if (!res.ok) {
return new Err({
type: "internal_server_error",
message: "Failed to get suggestions",
});
}
return new Ok(await res.json());
return await fetchWithErr(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return await fetchWithErr(
return fetchWithErr(

`/api/w/${owner.sId}/assistant/builder/suggestions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "name",
inputs: { instructions, description },
}),
}
);
}

async function getDescriptionSuggestions({
Expand All @@ -401,7 +438,7 @@ async function getDescriptionSuggestions({
instructions: string;
name: string;
}): Promise<Result<BuilderSuggestionsType, APIError>> {
const res = await fetch(`/api/w/${owner.sId}/assistant/builder/suggestions`, {
return fetchWithErr(`/api/w/${owner.sId}/assistant/builder/suggestions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Expand All @@ -411,11 +448,28 @@ async function getDescriptionSuggestions({
inputs: { instructions, name },
}),
});
if (!res.ok) {
}

async function fetchWithErr<T>(
input: RequestInfo | URL,
init?: RequestInit
): Promise<Result<T, APIError>> {
try {
const res = await fetch(input, init);
if (!res.ok) {
return new Err({
type: "internal_server_error",
message: `Failed to fetch: ${
res.statusText
}\nResponse: ${await res.text()}`,
});
}

return new Ok((await res.json()) as T);
} catch (e) {
return new Err({
type: "internal_server_error",
message: "Failed to get suggestions",
message: `Failed to fetch.\nError: ${e}`,
});
}
return new Ok(await res.json());
}
10 changes: 2 additions & 8 deletions types/src/front/lib/actions/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,14 @@ export const DustProdActionRegistry = createActionRegistry({
workspaceId: PRODUCTION_DUST_APPS_WORKSPACE_ID,
appId: "aba0057f4c",
appHash:
"14d9afdf184226e1fc3f0fd7c231300581bc81a37eeb8c06016cc7b2fb9fecc3",
"51922001984d1c9a3b84ce1b0275b4925d13fc574a73f1b23fe865d2d1b56eb1",
},
config: {
CREATE_SUGGESTIONS: {
provider_id: "openai",
model_id: "gpt-3.5-turbo",
function_call: "send_suggestions",
use_cache: true,
},
IS_FINISHED: {
provider_id: "openai",
model_id: "gpt-3.5-turbo",
function_call: "send_instructions_finished",
use_cache: true,
use_cache: false,
},
},
},
Expand Down
Loading