Skip to content

Commit

Permalink
[Meru][Assistant Builder] Improved description suggestion (#4559)
Browse files Browse the repository at this point in the history
* update description suggestion app

* clean dust app + don't use cache

* improve description suggestions behaviour

* review

* clean
  • Loading branch information
philipperolet authored Apr 4, 2024
1 parent a412772 commit f1e99cf
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 72 deletions.
178 changes: 114 additions & 64 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,14 +15,21 @@ 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";

Expand Down Expand Up @@ -134,6 +142,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 +177,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 +347,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,7 +413,7 @@ async function getNamingSuggestions({
instructions: string;
description: 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 @@ -383,13 +423,6 @@ async function getNamingSuggestions({
inputs: { instructions, description },
}),
});
if (!res.ok) {
return new Err({
type: "internal_server_error",
message: "Failed to get suggestions",
});
}
return new Ok(await res.json());
}

async function getDescriptionSuggestions({
Expand All @@ -401,7 +434,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 +444,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

0 comments on commit f1e99cf

Please sign in to comment.