mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-07 05:40:17 +00:00
Merge branch 'master' into features/advanced-reasoning
This commit is contained in:
@@ -35,8 +35,10 @@ import {
|
|||||||
DotsThreeVertical,
|
DotsThreeVertical,
|
||||||
Pencil,
|
Pencil,
|
||||||
Trash,
|
Trash,
|
||||||
|
ArrowRight,
|
||||||
|
ArrowLeft,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import { set, z } from "zod";
|
import { set, z, ZodError } from "zod";
|
||||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -245,12 +247,18 @@ function AgentCard(props: AgentCardProps) {
|
|||||||
let agentsApiUrl = `/api/agents`;
|
let agentsApiUrl = `/api/agents`;
|
||||||
let method = props.editCard ? "PATCH" : "POST";
|
let method = props.editCard ? "PATCH" : "POST";
|
||||||
|
|
||||||
|
let valuesToSend: any = values;
|
||||||
|
|
||||||
|
if (props.editCard) {
|
||||||
|
valuesToSend = { ...values, slug: props.data.slug };
|
||||||
|
}
|
||||||
|
|
||||||
fetch(agentsApiUrl, {
|
fetch(agentsApiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(values),
|
body: JSON.stringify(valuesToSend),
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
@@ -537,9 +545,36 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
const [progressValue, setProgressValue] = useState(0);
|
const [progressValue, setProgressValue] = useState(0);
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
||||||
const [allFileOptions, setAllFileOptions] = useState<string[]>([]);
|
const [allFileOptions, setAllFileOptions] = useState<string[]>([]);
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
|
||||||
const [showSubscribeDialog, setShowSubscribeDialog] = useState(true);
|
const [showSubscribeDialog, setShowSubscribeDialog] = useState(true);
|
||||||
|
|
||||||
|
const privacyOptions = ["public", "private", "protected"];
|
||||||
|
|
||||||
|
const basicFields = [
|
||||||
|
{ name: "name", label: "Name" },
|
||||||
|
{ name: "persona", label: "Personality" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const advancedFields = [
|
||||||
|
{ name: "files", label: "Knowledge Base" },
|
||||||
|
{ name: "input_tools", label: "Input Tools" },
|
||||||
|
{ name: "output_modes", label: "Output Modes" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const customizationFields = [
|
||||||
|
{ name: "color", label: "Color" },
|
||||||
|
{ name: "icon", label: "Icon" },
|
||||||
|
{ name: "chat_model", label: "Chat Model" },
|
||||||
|
{ name: "privacy_level", label: "Privacy Level" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formGroups = [
|
||||||
|
{ fields: basicFields, label: "Basic Settings" },
|
||||||
|
{ fields: customizationFields, label: "Customization & Access" },
|
||||||
|
{ fields: advancedFields, label: "Advanced Settings" },
|
||||||
|
];
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -563,7 +598,9 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
const currentFiles = props.form.getValues("files") || [];
|
const currentFiles = props.form.getValues("files") || [];
|
||||||
const fileOptions = props.filesOptions || [];
|
const fileOptions = props.filesOptions || [];
|
||||||
const concatenatedFiles = [...currentFiles, ...fileOptions];
|
const concatenatedFiles = [...currentFiles, ...fileOptions];
|
||||||
setAllFileOptions((prev) => [...prev, ...concatenatedFiles]);
|
const fullAllFileOptions = [...allFileOptions, ...concatenatedFiles];
|
||||||
|
const dedupedAllFileOptions = Array.from(new Set(fullAllFileOptions));
|
||||||
|
setAllFileOptions(dedupedAllFileOptions);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -614,6 +651,25 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
uploadFiles(event.target.files);
|
uploadFiles(event.target.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleNext = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (currentStep < formGroups.length - 1) {
|
||||||
|
setCurrentStep(currentStep + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrevious = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (currentStep > 0) {
|
||||||
|
setCurrentStep(currentStep - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (values: any) => {
|
||||||
|
props.onSubmit(values);
|
||||||
|
setIsSaving(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleAgentFileChange = (files: string[]) => {
|
const handleAgentFileChange = (files: string[]) => {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const currentFiles = props.form.getValues("files") || [];
|
const currentFiles = props.form.getValues("files") || [];
|
||||||
@@ -624,7 +680,30 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const privacyOptions = ["public", "private", "protected"];
|
const areRequiredFieldsCompletedForCurrentStep = (formGroup: {
|
||||||
|
fields: { name: string }[];
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
EditAgentSchema.parse(props.form.getValues());
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
const errors: { [key: string]: string } = (error as ZodError).errors.reduce(
|
||||||
|
(acc: any, curr: any) => {
|
||||||
|
acc[curr.path[0]] = curr.message;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const field of formGroup.fields) {
|
||||||
|
if (errors[field.name]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!props.isSubscribed && showSubscribeDialog) {
|
if (!props.isSubscribed && showSubscribeDialog) {
|
||||||
return (
|
return (
|
||||||
@@ -658,16 +737,12 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderFormField = (fieldName: string) => {
|
||||||
|
switch (fieldName) {
|
||||||
|
case "name":
|
||||||
return (
|
return (
|
||||||
<Form {...props.form}>
|
|
||||||
<form
|
|
||||||
onSubmit={props.form.handleSubmit((values) => {
|
|
||||||
props.onSubmit(values);
|
|
||||||
setIsSaving(true);
|
|
||||||
})}
|
|
||||||
className="space-y-6"
|
|
||||||
>
|
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@@ -684,29 +759,11 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
<FormField
|
case "chat_model":
|
||||||
control={props.form.control}
|
return (
|
||||||
name="persona"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="space-y-1 grid gap-2">
|
|
||||||
<FormLabel>Personality</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
What is the personality, thought process, or tuning of this agent?
|
|
||||||
Get creative; this is how you can influence the agent constitution.
|
|
||||||
</FormDescription>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
placeholder="You are an excellent biologist, at the top of your field in marine biology."
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="chat_model"
|
name="chat_model"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@@ -723,7 +780,10 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent className="items-start space-y-1 inline-flex flex-col">
|
<SelectContent className="items-start space-y-1 inline-flex flex-col">
|
||||||
{props.modelOptions.map((modelOption) => (
|
{props.modelOptions.map((modelOption) => (
|
||||||
<SelectItem key={modelOption.id} value={modelOption.name}>
|
<SelectItem
|
||||||
|
key={modelOption.id}
|
||||||
|
value={modelOption.name}
|
||||||
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
{modelOption.name}
|
{modelOption.name}
|
||||||
</div>
|
</div>
|
||||||
@@ -735,7 +795,11 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
case "privacy_level":
|
||||||
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="privacy_level"
|
name="privacy_level"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@@ -746,7 +810,10 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
<FormDescription>
|
<FormDescription>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant={"ghost" as const} className="p-0 h-fit">
|
<Button
|
||||||
|
variant={"ghost" as const}
|
||||||
|
className="p-0 h-fit"
|
||||||
|
>
|
||||||
<span className="items-center flex gap-1 text-sm">
|
<span className="items-center flex gap-1 text-sm">
|
||||||
<Info className="inline" />
|
<Info className="inline" />
|
||||||
<p className="text-sm">Learn more</p>
|
<p className="text-sm">Learn more</p>
|
||||||
@@ -785,18 +852,18 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="grid">
|
);
|
||||||
<FormLabel className="mb-2">Look & Feel</FormLabel>
|
case "color":
|
||||||
<div className="flex gap-1 justify-left flex-col md:flex-row">
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="color"
|
name="color"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="space-y-3">
|
<FormItem className="space-y-3">
|
||||||
<Select
|
<FormLabel>Color</FormLabel>
|
||||||
onValueChange={field.onChange}
|
<FormDescription>Choose a color for your agent.</FormDescription>
|
||||||
defaultValue={field.value}
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className="w-[200px]">
|
<SelectTrigger className="w-[200px]">
|
||||||
<SelectValue placeholder="Color" />
|
<SelectValue placeholder="Color" />
|
||||||
@@ -820,16 +887,18 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
case "icon":
|
||||||
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="icon"
|
name="icon"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="space-y-3">
|
<FormItem className="space-y-3">
|
||||||
<Select
|
<FormLabel>Icon</FormLabel>
|
||||||
onValueChange={field.onChange}
|
<FormDescription>Choose an icon for your agent.</FormDescription>
|
||||||
defaultValue={field.value}
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className="w-[200px]">
|
<SelectTrigger className="w-[200px]">
|
||||||
<SelectValue placeholder="Icon" />
|
<SelectValue placeholder="Icon" />
|
||||||
@@ -855,16 +924,36 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
);
|
||||||
</div>
|
case "persona":
|
||||||
<FormItem className="flex flex-col">
|
return (
|
||||||
<FormLabel className="text-md">Advanced Settings</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
These are optional settings that you can use to customize your agent.
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
|
control={props.form.control}
|
||||||
|
name="persona"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1 grid gap-2">
|
||||||
|
<FormLabel>Personality</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
What is the personality, thought process, or tuning of this
|
||||||
|
agent? Get creative; this is how you can influence the agent
|
||||||
|
constitution.
|
||||||
|
</FormDescription>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="You are an excellent biologist, at the top of your field in marine biology."
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "files":
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="files"
|
name="files"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@@ -938,7 +1027,9 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center w-full h-full">
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
<Plus className="h-6 w-6 mr-2" />
|
<Plus className="h-6 w-6 mr-2" />
|
||||||
<span>Drag and drop files here</span>
|
<span>
|
||||||
|
Drag and drop files here
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -954,15 +1045,19 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
key={file}
|
key={file}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
const currentFiles =
|
const currentFiles =
|
||||||
props.form.getValues("files") || [];
|
props.form.getValues("files") ||
|
||||||
const newFiles = currentFiles.includes(
|
[];
|
||||||
file,
|
const newFiles =
|
||||||
)
|
currentFiles.includes(file)
|
||||||
? currentFiles.filter(
|
? currentFiles.filter(
|
||||||
(item) => item !== file,
|
(item) =>
|
||||||
|
item !== file,
|
||||||
)
|
)
|
||||||
: [...currentFiles, file];
|
: [...currentFiles, file];
|
||||||
props.form.setValue("files", newFiles);
|
props.form.setValue(
|
||||||
|
"files",
|
||||||
|
newFiles,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Check
|
<Check
|
||||||
@@ -985,7 +1080,11 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
case "input_tools":
|
||||||
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="input_tools"
|
name="input_tools"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@@ -1018,7 +1117,9 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
"input_tools",
|
"input_tools",
|
||||||
) || [];
|
) || [];
|
||||||
const newInputTools =
|
const newInputTools =
|
||||||
currentInputTools.includes(key)
|
currentInputTools.includes(
|
||||||
|
key,
|
||||||
|
)
|
||||||
? currentInputTools.filter(
|
? currentInputTools.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item !== key,
|
item !== key,
|
||||||
@@ -1037,12 +1138,23 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 h-4 w-4",
|
"mr-2 h-4 w-4",
|
||||||
field.value &&
|
field.value &&
|
||||||
field.value.includes(key)
|
field.value.includes(
|
||||||
|
key,
|
||||||
|
)
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0",
|
: "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<b>{key}</b>: {value}
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center space-x-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>{key}</b>
|
||||||
|
</p>
|
||||||
|
<p>{value}</p>
|
||||||
|
</div>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@@ -1054,7 +1166,11 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
case "output_modes":
|
||||||
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
key={fieldName}
|
||||||
control={props.form.control}
|
control={props.form.control}
|
||||||
name="output_modes"
|
name="output_modes"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@@ -1087,7 +1203,9 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
"output_modes",
|
"output_modes",
|
||||||
) || [];
|
) || [];
|
||||||
const newOutputModes =
|
const newOutputModes =
|
||||||
currentOutputModes.includes(key)
|
currentOutputModes.includes(
|
||||||
|
key,
|
||||||
|
)
|
||||||
? currentOutputModes.filter(
|
? currentOutputModes.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item !== key,
|
item !== key,
|
||||||
@@ -1106,12 +1224,23 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 h-4 w-4",
|
"mr-2 h-4 w-4",
|
||||||
field.value &&
|
field.value &&
|
||||||
field.value.includes(key)
|
field.value.includes(
|
||||||
|
key,
|
||||||
|
)
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0",
|
: "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<b>{key}</b>: {value}
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center space-x-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>{key}</b>
|
||||||
|
</p>
|
||||||
|
<p>{value}</p>
|
||||||
|
</div>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@@ -1123,6 +1252,55 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...props.form}>
|
||||||
|
<form onSubmit={props.form.handleSubmit(handleSubmit)} className="space-y-6">
|
||||||
|
<div className="space-y-6">{formGroups[currentStep].label}</div>
|
||||||
|
{currentStep < formGroups.length &&
|
||||||
|
formGroups[currentStep].fields.map((field) => renderFormField(field.name))}
|
||||||
|
<div className="flex justify-between mt-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={"outline"}
|
||||||
|
onClick={handlePrevious}
|
||||||
|
disabled={currentStep === 0}
|
||||||
|
className={`items-center ${isSaving ? "bg-stone-100 dark:bg-neutral-900" : ""} text-white ${colorOptionClassName}`}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
{currentStep < formGroups.length - 1 ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={"outline"}
|
||||||
|
onClick={handleNext}
|
||||||
|
disabled={
|
||||||
|
!areRequiredFieldsCompletedForCurrentStep(formGroups[currentStep])
|
||||||
|
}
|
||||||
|
className={`items-center ${isSaving ? "bg-stone-100 dark:bg-neutral-900" : ""} text-white ${colorOptionClassName}`}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ArrowRight className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant={"outline"}
|
||||||
|
disabled={isSaving || !props.isSubscribed}
|
||||||
|
className={`items-center ${isSaving ? "bg-stone-100 dark:bg-neutral-900" : ""} text-white ${colorOptionClassName}`}
|
||||||
|
>
|
||||||
|
<FloppyDisk className="h-4 w-4 mr-2" />
|
||||||
|
{isSaving ? "Booting..." : "Save"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{props.errors && (
|
{props.errors && (
|
||||||
<Alert className="bg-secondary border-none my-4">
|
<Alert className="bg-secondary border-none my-4">
|
||||||
<AlertDescription className="flex items-center gap-1">
|
<AlertDescription className="flex items-center gap-1">
|
||||||
@@ -1134,28 +1312,6 @@ function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<fieldset>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant={"ghost"}
|
|
||||||
disabled={isSaving || !props.isSubscribed}
|
|
||||||
className={`items-center ${isSaving ? "bg-stone-100 dark:bg-neutral-900" : ""} text-white ${colorOptionClassName}`}
|
|
||||||
>
|
|
||||||
<FloppyDisk className="h-4 w-4 mr-2" />
|
|
||||||
{isSaving ? "Booting..." : "Save"}
|
|
||||||
</Button>
|
|
||||||
{!!!props.create && props.form.getValues("privacy_level") !== "private" && (
|
|
||||||
<ShareLink
|
|
||||||
buttonTitle="Share"
|
|
||||||
title="Share Agent"
|
|
||||||
description="Share a link to this agent with others. They'll be able to chat with it, and ask questions to all of its knowledge base."
|
|
||||||
buttonVariant={"ghost" as const}
|
|
||||||
buttonClassName={`${colorOptionClassName}`}
|
|
||||||
includeIcon={true}
|
|
||||||
url={`${window.location.origin}/agents?agent=${props.slug}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -696,10 +696,12 @@ class AgentAdapters:
|
|||||||
files: List[str],
|
files: List[str],
|
||||||
input_tools: List[str],
|
input_tools: List[str],
|
||||||
output_modes: List[str],
|
output_modes: List[str],
|
||||||
|
slug: Optional[str] = None,
|
||||||
):
|
):
|
||||||
chat_model_option = await ChatModelOptions.objects.filter(chat_model=chat_model).afirst()
|
chat_model_option = await ChatModelOptions.objects.filter(chat_model=chat_model).afirst()
|
||||||
|
|
||||||
agent, created = await Agent.objects.filter(name=name, creator=user).aupdate_or_create(
|
# Slug will be None for new agents, which will trigger a new agent creation with a generated, immutable slug
|
||||||
|
agent, created = await Agent.objects.filter(slug=slug, creator=user).aupdate_or_create(
|
||||||
defaults={
|
defaults={
|
||||||
"name": name,
|
"name": name,
|
||||||
"creator": user,
|
"creator": user,
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ class CrossEncoderModel:
|
|||||||
payload = {"inputs": {"query": query, "passages": [hit.additional[key] for hit in hits]}}
|
payload = {"inputs": {"query": query, "passages": [hit.additional[key] for hit in hits]}}
|
||||||
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
|
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
|
||||||
response = requests.post(target_url, json=payload, headers=headers)
|
response = requests.post(target_url, json=payload, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
return response.json()["scores"]
|
return response.json()["scores"]
|
||||||
|
|
||||||
cross_inp = [[query, hit.additional[key]] for hit in hits]
|
cross_inp = [[query, hit.additional[key]] for hit in hits]
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ async def read_webpages(
|
|||||||
conversation_history: dict,
|
conversation_history: dict,
|
||||||
location: LocationData,
|
location: LocationData,
|
||||||
user: KhojUser,
|
user: KhojUser,
|
||||||
subscribed: bool = False,
|
|
||||||
send_status_func: Optional[Callable] = None,
|
send_status_func: Optional[Callable] = None,
|
||||||
uploaded_image_url: str = None,
|
uploaded_image_url: str = None,
|
||||||
agent: Agent = None,
|
agent: Agent = None,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class ModifyAgentBody(BaseModel):
|
|||||||
files: Optional[List[str]] = []
|
files: Optional[List[str]] = []
|
||||||
input_tools: Optional[List[str]] = []
|
input_tools: Optional[List[str]] = []
|
||||||
output_modes: Optional[List[str]] = []
|
output_modes: Optional[List[str]] = []
|
||||||
|
slug: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@api_agents.get("", response_class=Response)
|
@api_agents.get("", response_class=Response)
|
||||||
@@ -192,6 +193,7 @@ async def create_agent(
|
|||||||
body.files,
|
body.files,
|
||||||
body.input_tools,
|
body.input_tools,
|
||||||
body.output_modes,
|
body.output_modes,
|
||||||
|
body.slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
agents_packet = {
|
agents_packet = {
|
||||||
@@ -233,7 +235,7 @@ async def update_agent(
|
|||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
|
|
||||||
selected_agent = await AgentAdapters.aget_agent_by_name(body.name, user)
|
selected_agent = await AgentAdapters.aget_agent_by_slug(body.slug, user)
|
||||||
|
|
||||||
if not selected_agent:
|
if not selected_agent:
|
||||||
return Response(
|
return Response(
|
||||||
@@ -253,6 +255,7 @@ async def update_agent(
|
|||||||
body.files,
|
body.files,
|
||||||
body.input_tools,
|
body.input_tools,
|
||||||
body.output_modes,
|
body.output_modes,
|
||||||
|
body.slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
agents_packet = {
|
agents_packet = {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ def chat_history(
|
|||||||
|
|
||||||
agent_metadata = None
|
agent_metadata = None
|
||||||
if conversation.agent:
|
if conversation.agent:
|
||||||
if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE:
|
if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE and conversation.agent.creator != user:
|
||||||
conversation.agent = None
|
conversation.agent = None
|
||||||
else:
|
else:
|
||||||
agent_metadata = {
|
agent_metadata = {
|
||||||
@@ -853,6 +853,8 @@ async def chat(
|
|||||||
return
|
return
|
||||||
|
|
||||||
# # Gather Context
|
# # Gather Context
|
||||||
|
# # Extract Document References
|
||||||
|
# try:
|
||||||
# async for result in extract_references_and_questions(
|
# async for result in extract_references_and_questions(
|
||||||
# request,
|
# request,
|
||||||
# meta_log,
|
# meta_log,
|
||||||
@@ -872,8 +874,15 @@ async def chat(
|
|||||||
# compiled_references.extend(result[0])
|
# compiled_references.extend(result[0])
|
||||||
# inferred_queries.extend(result[1])
|
# inferred_queries.extend(result[1])
|
||||||
# defiltered_query = result[2]
|
# defiltered_query = result[2]
|
||||||
|
# except Exception as e:
|
||||||
# if not is_none_or_empty(compiled_references):
|
# error_message = f"Error searching knowledge base: {e}. Attempting to respond without document references."
|
||||||
|
# logger.warning(error_message)
|
||||||
|
# async for result in send_event(
|
||||||
|
# ChatEvent.STATUS, "Document search failed. I'll try respond without document references"
|
||||||
|
# ):
|
||||||
|
# yield result
|
||||||
|
#
|
||||||
|
# # if not is_none_or_empty(compiled_references):
|
||||||
# try:
|
# try:
|
||||||
# headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
|
# headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
|
||||||
# # Strip only leading # from headings
|
# # Strip only leading # from headings
|
||||||
@@ -910,12 +919,13 @@ async def chat(
|
|||||||
yield result[ChatEvent.STATUS]
|
yield result[ChatEvent.STATUS]
|
||||||
else:
|
else:
|
||||||
online_results = result
|
online_results = result
|
||||||
except ValueError as e:
|
except Exception as e:
|
||||||
error_message = f"Error searching online: {e}. Attempting to respond without online results"
|
error_message = f"Error searching online: {e}. Attempting to respond without online results"
|
||||||
logger.warning(error_message)
|
logger.warning(error_message)
|
||||||
async for result in send_llm_response(error_message):
|
async for result in send_event(
|
||||||
|
ChatEvent.STATUS, "Online search failed. I'll try respond without online references"
|
||||||
|
):
|
||||||
yield result
|
yield result
|
||||||
return
|
|
||||||
|
|
||||||
## Gather Webpage References
|
## Gather Webpage References
|
||||||
if ConversationCommand.Webpage in conversation_commands and pending_research:
|
if ConversationCommand.Webpage in conversation_commands and pending_research:
|
||||||
@@ -925,7 +935,6 @@ async def chat(
|
|||||||
meta_log,
|
meta_log,
|
||||||
location,
|
location,
|
||||||
user,
|
user,
|
||||||
subscribed,
|
|
||||||
partial(send_event, ChatEvent.STATUS),
|
partial(send_event, ChatEvent.STATUS),
|
||||||
uploaded_image_url=uploaded_image_url,
|
uploaded_image_url=uploaded_image_url,
|
||||||
agent=agent,
|
agent=agent,
|
||||||
@@ -945,11 +954,15 @@ async def chat(
|
|||||||
webpages.append(webpage["link"])
|
webpages.append(webpage["link"])
|
||||||
async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"):
|
async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"):
|
||||||
yield result
|
yield result
|
||||||
except ValueError as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Error directly reading webpages: {e}. Attempting to respond without online results",
|
f"Error reading webpages: {e}. Attempting to respond without webpage results",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
|
async for result in send_event(
|
||||||
|
ChatEvent.STATUS, "Webpage read failed. I'll try respond without webpage references"
|
||||||
|
):
|
||||||
|
yield result
|
||||||
|
|
||||||
## Gather Code Results
|
## Gather Code Results
|
||||||
if ConversationCommand.Code in conversation_commands and pending_research:
|
if ConversationCommand.Code in conversation_commands and pending_research:
|
||||||
|
|||||||
@@ -345,13 +345,13 @@ async def aget_relevant_information_sources(
|
|||||||
final_response = [ConversationCommand.Default]
|
final_response = [ConversationCommand.Default]
|
||||||
else:
|
else:
|
||||||
final_response = [ConversationCommand.General]
|
final_response = [ConversationCommand.General]
|
||||||
return final_response
|
except Exception:
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Invalid response for determining relevant tools: {response}")
|
logger.error(f"Invalid response for determining relevant tools: {response}")
|
||||||
if len(agent_tools) == 0:
|
if len(agent_tools) == 0:
|
||||||
final_response = [ConversationCommand.Default]
|
final_response = [ConversationCommand.Default]
|
||||||
else:
|
else:
|
||||||
final_response = agent_tools
|
final_response = agent_tools
|
||||||
|
return final_response
|
||||||
|
|
||||||
|
|
||||||
async def aget_relevant_output_modes(
|
async def aget_relevant_output_modes(
|
||||||
|
|||||||
@@ -227,7 +227,6 @@ async def execute_information_collection(
|
|||||||
conversation_history,
|
conversation_history,
|
||||||
location,
|
location,
|
||||||
user,
|
user,
|
||||||
subscribed,
|
|
||||||
send_status_func,
|
send_status_func,
|
||||||
uploaded_image_url=uploaded_image_url,
|
uploaded_image_url=uploaded_image_url,
|
||||||
agent=agent,
|
agent=agent,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import math
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional, Tuple, Type, Union
|
from typing import List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
import torch
|
import torch
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from sentence_transformers import util
|
from sentence_transformers import util
|
||||||
@@ -231,8 +232,12 @@ def setup(
|
|||||||
|
|
||||||
def cross_encoder_score(query: str, hits: List[SearchResponse], search_model_name: str) -> List[SearchResponse]:
|
def cross_encoder_score(query: str, hits: List[SearchResponse], search_model_name: str) -> List[SearchResponse]:
|
||||||
"""Score all retrieved entries using the cross-encoder"""
|
"""Score all retrieved entries using the cross-encoder"""
|
||||||
|
try:
|
||||||
with timer("Cross-Encoder Predict Time", logger, state.device):
|
with timer("Cross-Encoder Predict Time", logger, state.device):
|
||||||
cross_scores = state.cross_encoder_model[search_model_name].predict(query, hits)
|
cross_scores = state.cross_encoder_model[search_model_name].predict(query, hits)
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
logger.error(f"Failed to rerank documents using the inference endpoint. Error: {e}.", exc_info=True)
|
||||||
|
cross_scores = [0.0] * len(hits)
|
||||||
|
|
||||||
# Convert cross-encoder scores to distances and pass in hits for reranking
|
# Convert cross-encoder scores to distances and pass in hits for reranking
|
||||||
for idx in range(len(cross_scores)):
|
for idx in range(len(cross_scores)):
|
||||||
|
|||||||
Reference in New Issue
Block a user