diff --git a/front/components/assistant_builder/InstructionScreen.tsx b/front/components/assistant_builder/InstructionScreen.tsx index b5f5b698bf1a9..84ed110251cff 100644 --- a/front/components/assistant_builder/InstructionScreen.tsx +++ b/front/components/assistant_builder/InstructionScreen.tsx @@ -418,7 +418,7 @@ function ModelList({ modelConfigs, onClick }: ModelListProps) { ); } -function AdvancedSettings({ +export function AdvancedSettings({ owner, plan, generationSettings, diff --git a/front/components/trackers/TrackerBuilder.tsx b/front/components/trackers/TrackerBuilder.tsx new file mode 100644 index 0000000000000..153662c1da7c5 --- /dev/null +++ b/front/components/trackers/TrackerBuilder.tsx @@ -0,0 +1,380 @@ +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Input, + Label, + Page, + TextArea, + useSendNotification, +} from "@dust-tt/sparkle"; +import type { + DataSourceViewType, + SpaceType, + SubscriptionType, + SupportedModel, + TrackerConfigurationStateType, + TrackerConfigurationType, + WorkspaceType, +} from "@dust-tt/types"; +import { + CLAUDE_3_5_SONNET_DEFAULT_MODEL_CONFIG, + TRACKER_FREQUENCY_TYPES, +} from "@dust-tt/types"; +import { useRouter } from "next/router"; +import { useState } from "react"; + +import { AdvancedSettings } from "@app/components/assistant_builder/InstructionScreen"; +import AppLayout from "@app/components/sparkle/AppLayout"; +import { AppLayoutSimpleSaveCancelTitle } from "@app/components/sparkle/AppLayoutTitle"; +import { isEmailValid } from "@app/lib/utils"; + +export const TrackerBuilder = ({ + owner, + subscription, + globalSpace, + dataSourceViews, + trackerToEdit, +}: { + owner: WorkspaceType; + subscription: SubscriptionType; + globalSpace: SpaceType; + dataSourceViews: DataSourceViewType[]; + trackerToEdit: TrackerConfigurationType | null; +}) => { + const router = useRouter(); + const sendNotification = useSendNotification(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const [tracker, setTracker] = useState({ + name: trackerToEdit?.name ?? null, + nameError: null, + description: trackerToEdit?.description ?? null, + descriptionError: null, + prompt: trackerToEdit?.prompt ?? null, + promptError: null, + frequency: trackerToEdit?.frequency ?? "daily", + frequencyError: null, + recipients: trackerToEdit?.recipients?.join(", ") ?? null, + recipientsError: null, + modelId: + trackerToEdit?.modelId ?? CLAUDE_3_5_SONNET_DEFAULT_MODEL_CONFIG.modelId, + providerId: + trackerToEdit?.providerId ?? + CLAUDE_3_5_SONNET_DEFAULT_MODEL_CONFIG.providerId, + temperature: trackerToEdit?.temperature ?? 0.5, + }); + + void dataSourceViews; // todo: use this + + const extractEmails = (text: string) => + text + .split(/[\n,]+/) + .map((e) => e.trim()) + .filter((e, i, self) => self.indexOf(e) === i); + + const onSubmit = async () => { + // Validate the form + setIsSubmitting(true); + let hasValidationError = false; + if (!tracker.name) { + setTracker((t) => ({ + ...t, + nameError: "Name is required.", + })); + hasValidationError = true; + } + if (!tracker.recipients?.length) { + setTracker((t) => ({ + ...t, + recipientsError: "At least one recipient is required.", + })); + hasValidationError = true; + } else { + const recipients = extractEmails(tracker.recipients); + if (recipients.map(isEmailValid).includes(false)) { + setTracker((t) => ({ + ...t, + recipientsError: + "Invalid email addresses: " + + recipients.filter((e) => !isEmailValid(e)).join(", "), + })); + hasValidationError = true; + } + } + if (!tracker.prompt) { + setTracker((t) => ({ + ...t, + promptError: "Prompt is required.", + })); + hasValidationError = true; + } + if (hasValidationError) { + setIsSubmitting(false); + return; + } + + let route = `/api/w/${owner.sId}/spaces/${globalSpace.sId}/trackers`; + let method = "POST"; + + if (trackerToEdit) { + route += `/${trackerToEdit.sId}`; + method = "PATCH"; + } + + const res = await fetch(route, { + method, + body: JSON.stringify({ + name: tracker.name, + description: tracker.description, + prompt: tracker.prompt, + modelId: tracker.modelId, + providerId: tracker.providerId, + temperature: tracker.temperature, + frequency: tracker.frequency, + recipients: tracker.recipients ? extractEmails(tracker.recipients) : [], + }), + headers: { + "Content-Type": "application/json", + }, + }); + + // Handle errors. + if (!res.ok) { + const resJson = await res.json(); + sendNotification({ + title: trackerToEdit + ? "Failed to update tracker" + : "Failed to create tracker", + description: resJson.error.message, + type: "error", + }); + setIsSubmitting(false); + return; + } + + sendNotification({ + title: trackerToEdit ? "Tracker updated" : "Tracker Created", + description: trackerToEdit + ? "Tracker updated successfully" + : "Tracker created successfully.", + type: "success", + }); + setIsSubmitting(false); + await router.push(`/w/${owner.sId}/assistant/labs/trackers`); + }; + + return ( + { + await router.push(`/w/${owner.sId}/assistant/labs/trackers`); + }} + onSave={onSubmit} + isSaving={isSubmitting} + /> + } + > +
+
+
+
+ { + setTracker((t) => ({ + ...t, + modelId: g.modelSettings.modelId, + providerId: g.modelSettings.providerId, + temperature: g.temperature, + })); + }} + /> +
+
+ +
+
+ +
+ Give your tracker a clear, memorable name and description that + will help you and your team identify its purpose. +
+
+
+
+ + setTracker((t) => ({ + ...t, + name: e.target.value, + nameError: null, + })) + } + placeholder="Descriptive name." + message={tracker.nameError} + messageStatus={tracker.nameError ? "error" : undefined} + /> +
+
+ + setTracker((t) => ({ + ...t, + description: e.target.value, + descriptionError: null, + })) + } + placeholder="Brief description of what you're tracking and why." + message={tracker.descriptionError} + messageStatus={tracker.descriptionError ? "error" : undefined} + /> +
+
+
+ +
+
+ +
+ Choose when and who receives update notifications. We'll bundle + all tracked changes into organized email summaries delivered on + your preferred schedule. +
+
+
+
+
+ + + +
+
+
+ + setTracker((t) => ({ + ...t, + recipients: e.target.value, + recipientsError: null, + })) + } + message={tracker.recipientsError} + messageStatus={tracker.recipientsError ? "error" : undefined} + /> +
+
+
+
+
+ +
+ Set up what you want to track and monitor. Tell us what to look + for, specify which documents to maintain current versions of, and + select which documents to watch for changes. +
+
+
+ +