mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-10 05:39:11 +00:00
Format web app code with prettier recommendations
Too many of these had accumulated earlier from being ignored. Changed to make build logs less noisy
This commit is contained in:
@@ -344,14 +344,16 @@ export default function Agents() {
|
|||||||
/>
|
/>
|
||||||
<span className="font-bold">How it works</span> Use any of these
|
<span className="font-bold">How it works</span> Use any of these
|
||||||
specialized personas to tune your conversation to your needs.
|
specialized personas to tune your conversation to your needs.
|
||||||
{
|
{!isSubscribed && (
|
||||||
!isSubscribed && (
|
|
||||||
<span>
|
<span>
|
||||||
{" "}
|
{" "}
|
||||||
<Link href="/settings" className="font-bold">Upgrade your plan</Link> to leverage custom models. You will fallback to the default model when chatting.
|
<Link href="/settings" className="font-bold">
|
||||||
|
Upgrade your plan
|
||||||
|
</Link>{" "}
|
||||||
|
to leverage custom models. You will fallback to the
|
||||||
|
default model when chatting.
|
||||||
</span>
|
</span>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<div className="pt-6 md:pt-8">
|
<div className="pt-6 md:pt-8">
|
||||||
|
|||||||
@@ -90,11 +90,9 @@ export interface UserConfig {
|
|||||||
|
|
||||||
export function useUserConfig(detailed: boolean = false) {
|
export function useUserConfig(detailed: boolean = false) {
|
||||||
const url = `/api/settings?detailed=${detailed}`;
|
const url = `/api/settings?detailed=${detailed}`;
|
||||||
const {
|
const { data, error, isLoading } = useSWR<UserConfig>(url, fetcher, {
|
||||||
data,
|
revalidateOnFocus: false,
|
||||||
error,
|
});
|
||||||
isLoading,
|
|
||||||
} = useSWR<UserConfig>(url, fetcher, { revalidateOnFocus: false });
|
|
||||||
|
|
||||||
if (error || !data || data?.detail === "Forbidden") {
|
if (error || !data || data?.detail === "Forbidden") {
|
||||||
return { data: null, error, isLoading };
|
return { data: null, error, isLoading };
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { PopoverProps } from "@radix-ui/react-popover"
|
import { PopoverProps } from "@radix-ui/react-popover";
|
||||||
|
|
||||||
import { Check, CaretUpDown } from "@phosphor-icons/react";
|
import { Check, CaretUpDown } from "@phosphor-icons/react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { useIsMobileWidth, useMutationObserver } from "@/app/common/utils";
|
import { useIsMobileWidth, useMutationObserver } from "@/app/common/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -17,11 +17,7 @@ import {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
import {
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
|
|
||||||
import { ModelOptions, useUserConfig } from "./auth";
|
import { ModelOptions, useUserConfig } from "./auth";
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
|
||||||
@@ -35,7 +31,7 @@ interface ModelSelectorProps extends PopoverProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ModelSelector({ ...props }: ModelSelectorProps) {
|
export function ModelSelector({ ...props }: ModelSelectorProps) {
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false);
|
||||||
const [peekedModel, setPeekedModel] = useState<ModelOptions | undefined>(undefined);
|
const [peekedModel, setPeekedModel] = useState<ModelOptions | undefined>(undefined);
|
||||||
const [selectedModel, setSelectedModel] = useState<ModelOptions | undefined>(undefined);
|
const [selectedModel, setSelectedModel] = useState<ModelOptions | undefined>(undefined);
|
||||||
const { data: userConfig, error, isLoading: isLoadingUserConfig } = useUserConfig(true);
|
const { data: userConfig, error, isLoading: isLoadingUserConfig } = useUserConfig(true);
|
||||||
@@ -48,14 +44,18 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
if (userConfig) {
|
if (userConfig) {
|
||||||
setModels(userConfig.chat_model_options);
|
setModels(userConfig.chat_model_options);
|
||||||
if (!props.initialModel) {
|
if (!props.initialModel) {
|
||||||
const selectedChatModelOption = userConfig.chat_model_options.find(model => model.id === userConfig.selected_chat_model_config);
|
const selectedChatModelOption = userConfig.chat_model_options.find(
|
||||||
|
(model) => model.id === userConfig.selected_chat_model_config,
|
||||||
|
);
|
||||||
if (!selectedChatModelOption && userConfig.chat_model_options.length > 0) {
|
if (!selectedChatModelOption && userConfig.chat_model_options.length > 0) {
|
||||||
setSelectedModel(userConfig.chat_model_options[0]);
|
setSelectedModel(userConfig.chat_model_options[0]);
|
||||||
} else {
|
} else {
|
||||||
setSelectedModel(selectedChatModelOption);
|
setSelectedModel(selectedChatModelOption);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const model = userConfig.chat_model_options.find(model => model.name === props.initialModel);
|
const model = userConfig.chat_model_options.find(
|
||||||
|
(model) => model.name === props.initialModel,
|
||||||
|
);
|
||||||
setSelectedModel(model);
|
setSelectedModel(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,15 +68,11 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
}, [selectedModel, userConfig, props.onSelect]);
|
}, [selectedModel, userConfig, props.onSelect]);
|
||||||
|
|
||||||
if (isLoadingUserConfig) {
|
if (isLoadingUserConfig) {
|
||||||
return (
|
return <Skeleton className="w-full h-10" />;
|
||||||
<Skeleton className="w-full h-10" />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return <div className="text-sm text-error">{error.message}</div>;
|
||||||
<div className="text-sm text-error">{error.message}</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -92,30 +88,32 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
disabled={props.disabled ?? false}
|
disabled={props.disabled ?? false}
|
||||||
>
|
>
|
||||||
<p className="truncate">
|
<p className="truncate">
|
||||||
{selectedModel ? selectedModel.name?.substring(0, 20) : "Select a model..."}
|
{selectedModel
|
||||||
|
? selectedModel.name?.substring(0, 20)
|
||||||
|
: "Select a model..."}
|
||||||
</p>
|
</p>
|
||||||
<CaretUpDown className="opacity-50" />
|
<CaretUpDown className="opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="end" className="w-[250px] p-0">
|
<PopoverContent align="end" className="w-[250px] p-0">
|
||||||
{
|
{isMobileWidth ? (
|
||||||
isMobileWidth ?
|
|
||||||
<div>
|
<div>
|
||||||
<Command loop>
|
<Command loop>
|
||||||
<CommandList className="h-[var(--cmdk-list-height)]">
|
<CommandList className="h-[var(--cmdk-list-height)]">
|
||||||
<CommandInput placeholder="Search Models..." />
|
<CommandInput placeholder="Search Models..." />
|
||||||
<CommandEmpty>No Models found.</CommandEmpty>
|
<CommandEmpty>No Models found.</CommandEmpty>
|
||||||
<CommandGroup key={"models"} heading={"Models"}>
|
<CommandGroup key={"models"} heading={"Models"}>
|
||||||
{models && models.length > 0 && models
|
{models &&
|
||||||
.map((model) => (
|
models.length > 0 &&
|
||||||
|
models.map((model) => (
|
||||||
<ModelItem
|
<ModelItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
model={model}
|
model={model}
|
||||||
isSelected={selectedModel?.id === model.id}
|
isSelected={selectedModel?.id === model.id}
|
||||||
onPeek={(model) => setPeekedModel(model)}
|
onPeek={(model) => setPeekedModel(model)}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setSelectedModel(model)
|
setSelectedModel(model);
|
||||||
setOpen(false)
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
isActive={props.isActive}
|
isActive={props.isActive}
|
||||||
/>
|
/>
|
||||||
@@ -124,7 +122,7 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</div>
|
</div>
|
||||||
:
|
) : (
|
||||||
<HoverCard>
|
<HoverCard>
|
||||||
<HoverCardContent
|
<HoverCardContent
|
||||||
side="left"
|
side="left"
|
||||||
@@ -133,7 +131,9 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
className="min-h-[280px]"
|
className="min-h-[280px]"
|
||||||
>
|
>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<h4 className="font-medium leading-none">{peekedModel?.name}</h4>
|
<h4 className="font-medium leading-none">
|
||||||
|
{peekedModel?.name}
|
||||||
|
</h4>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{peekedModel?.description}
|
{peekedModel?.description}
|
||||||
</div>
|
</div>
|
||||||
@@ -156,16 +156,17 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
<CommandInput placeholder="Search Models..." />
|
<CommandInput placeholder="Search Models..." />
|
||||||
<CommandEmpty>No Models found.</CommandEmpty>
|
<CommandEmpty>No Models found.</CommandEmpty>
|
||||||
<CommandGroup key={"models"} heading={"Models"}>
|
<CommandGroup key={"models"} heading={"Models"}>
|
||||||
{models && models.length > 0 && models
|
{models &&
|
||||||
.map((model) => (
|
models.length > 0 &&
|
||||||
|
models.map((model) => (
|
||||||
<ModelItem
|
<ModelItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
model={model}
|
model={model}
|
||||||
isSelected={selectedModel?.id === model.id}
|
isSelected={selectedModel?.id === model.id}
|
||||||
onPeek={(model) => setPeekedModel(model)}
|
onPeek={(model) => setPeekedModel(model)}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setSelectedModel(model)
|
setSelectedModel(model);
|
||||||
setOpen(false)
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
isActive={props.isActive}
|
isActive={props.isActive}
|
||||||
/>
|
/>
|
||||||
@@ -175,23 +176,23 @@ export function ModelSelector({ ...props }: ModelSelectorProps) {
|
|||||||
</Command>
|
</Command>
|
||||||
</div>
|
</div>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
}
|
)}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModelItemProps {
|
interface ModelItemProps {
|
||||||
model: ModelOptions,
|
model: ModelOptions;
|
||||||
isSelected: boolean,
|
isSelected: boolean;
|
||||||
onSelect: () => void,
|
onSelect: () => void;
|
||||||
onPeek: (model: ModelOptions) => void
|
onPeek: (model: ModelOptions) => void;
|
||||||
isActive?: boolean
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModelItem({ model, isSelected, onSelect, onPeek, isActive }: ModelItemProps) {
|
function ModelItem({ model, isSelected, onSelect, onPeek, isActive }: ModelItemProps) {
|
||||||
const ref = React.useRef<HTMLDivElement>(null)
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useMutationObserver(ref, (mutations) => {
|
useMutationObserver(ref, (mutations) => {
|
||||||
mutations.forEach((mutation) => {
|
mutations.forEach((mutation) => {
|
||||||
@@ -200,10 +201,10 @@ function ModelItem({ model, isSelected, onSelect, onPeek, isActive }: ModelItemP
|
|||||||
mutation.attributeName === "aria-selected" &&
|
mutation.attributeName === "aria-selected" &&
|
||||||
ref.current?.getAttribute("aria-selected") === "true"
|
ref.current?.getAttribute("aria-selected") === "true"
|
||||||
) {
|
) {
|
||||||
onPeek(model)
|
onPeek(model);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
@@ -213,10 +214,9 @@ function ModelItem({ model, isSelected, onSelect, onPeek, isActive }: ModelItemP
|
|||||||
className="data-[selected=true]:bg-muted data-[selected=true]:text-secondary-foreground"
|
className="data-[selected=true]:bg-muted data-[selected=true]:text-secondary-foreground"
|
||||||
disabled={!isActive && model.tier !== "free"}
|
disabled={!isActive && model.tier !== "free"}
|
||||||
>
|
>
|
||||||
{model.name} {model.tier === "standard" && <span className="text-green-500 ml-2">(Futurist)</span>}
|
{model.name}{" "}
|
||||||
<Check
|
{model.tier === "standard" && <span className="text-green-500 ml-2">(Futurist)</span>}
|
||||||
className={cn("ml-auto", isSelected ? "opacity-100" : "opacity-0")}
|
<Check className={cn("ml-auto", isSelected ? "opacity-100" : "opacity-0")} />
|
||||||
/>
|
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
export interface LocationData {
|
export interface LocationData {
|
||||||
city?: string;
|
city?: string;
|
||||||
@@ -78,16 +78,16 @@ export const useMutationObserver = (
|
|||||||
characterData: true,
|
characterData: true,
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
}
|
},
|
||||||
) => {
|
) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const observer = new MutationObserver(callback)
|
const observer = new MutationObserver(callback);
|
||||||
observer.observe(ref.current, options)
|
observer.observe(ref.current, options);
|
||||||
return () => observer.disconnect()
|
return () => observer.disconnect();
|
||||||
}
|
}
|
||||||
}, [ref, callback, options])
|
}, [ref, callback, options]);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function useIsDarkMode() {
|
export function useIsDarkMode() {
|
||||||
const [darkMode, setDarkMode] = useState(false);
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
|||||||
@@ -1061,12 +1061,27 @@ export function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
className="h-6 px-2 text-xs"
|
className="h-6 px-2 text-xs"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const filteredFiles = allFileOptions.filter(file =>
|
const filteredFiles =
|
||||||
file.toLowerCase().includes(fileSearchValue.toLowerCase())
|
allFileOptions.filter((file) =>
|
||||||
|
file
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(
|
||||||
|
fileSearchValue.toLowerCase(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const currentFiles =
|
||||||
|
props.form.getValues("files") ||
|
||||||
|
[];
|
||||||
|
const newFiles = [
|
||||||
|
...new Set([
|
||||||
|
...currentFiles,
|
||||||
|
...filteredFiles,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
props.form.setValue(
|
||||||
|
"files",
|
||||||
|
newFiles,
|
||||||
);
|
);
|
||||||
const currentFiles = props.form.getValues("files") || [];
|
|
||||||
const newFiles = [...new Set([...currentFiles, ...filteredFiles])];
|
|
||||||
props.form.setValue("files", newFiles);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Select All
|
Select All
|
||||||
@@ -1078,12 +1093,28 @@ export function AgentModificationForm(props: AgentModificationFormProps) {
|
|||||||
className="h-6 px-2 text-xs"
|
className="h-6 px-2 text-xs"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const filteredFiles = allFileOptions.filter(file =>
|
const filteredFiles =
|
||||||
file.toLowerCase().includes(fileSearchValue.toLowerCase())
|
allFileOptions.filter((file) =>
|
||||||
|
file
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(
|
||||||
|
fileSearchValue.toLowerCase(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const currentFiles =
|
||||||
|
props.form.getValues("files") ||
|
||||||
|
[];
|
||||||
|
const newFiles =
|
||||||
|
currentFiles.filter(
|
||||||
|
(file) =>
|
||||||
|
!filteredFiles.includes(
|
||||||
|
file,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
props.form.setValue(
|
||||||
|
"files",
|
||||||
|
newFiles,
|
||||||
);
|
);
|
||||||
const currentFiles = props.form.getValues("files") || [];
|
|
||||||
const newFiles = currentFiles.filter(file => !filteredFiles.includes(file));
|
|
||||||
props.form.setValue("files", newFiles);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Deselect All
|
Deselect All
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ function renameConversation(conversationId: string, newTitle: string) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => { })
|
.then((data) => {})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
@@ -171,7 +171,7 @@ function deleteConversation(conversationId: string) {
|
|||||||
mutate("/api/chat/sessions");
|
mutate("/api/chat/sessions");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((data) => { })
|
.then((data) => {})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
@@ -245,9 +245,7 @@ export function FilesMenu(props: FilesMenuProps) {
|
|||||||
Context
|
Context
|
||||||
<p>
|
<p>
|
||||||
<span className="text-muted-foreground text-xs">
|
<span className="text-muted-foreground text-xs">
|
||||||
{
|
{error ? "Failed to load files" : "Failed to load selected files"}
|
||||||
error ? "Failed to load files" : "Failed to load selected files"
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</h4>
|
</h4>
|
||||||
@@ -257,7 +255,7 @@ export function FilesMenu(props: FilesMenuProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!files) return <InlineLoading />;
|
if (!files) return <InlineLoading />;
|
||||||
@@ -443,10 +441,7 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
|
|||||||
<div>
|
<div>
|
||||||
{props.sideBarOpen && (
|
{props.sideBarOpen && (
|
||||||
<ScrollArea>
|
<ScrollArea>
|
||||||
<ScrollAreaScrollbar
|
<ScrollAreaScrollbar orientation="vertical" className="h-full w-2.5" />
|
||||||
orientation="vertical"
|
|
||||||
className="h-full w-2.5"
|
|
||||||
/>
|
|
||||||
<div className="p-0 m-0">
|
<div className="p-0 m-0">
|
||||||
{props.subsetOrganizedData != null &&
|
{props.subsetOrganizedData != null &&
|
||||||
Object.keys(props.subsetOrganizedData)
|
Object.keys(props.subsetOrganizedData)
|
||||||
@@ -471,7 +466,9 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
|
|||||||
agent_name={chatHistory.agent_name}
|
agent_name={chatHistory.agent_name}
|
||||||
agent_color={chatHistory.agent_color}
|
agent_color={chatHistory.agent_color}
|
||||||
agent_icon={chatHistory.agent_icon}
|
agent_icon={chatHistory.agent_icon}
|
||||||
agent_is_hidden={chatHistory.agent_is_hidden}
|
agent_is_hidden={
|
||||||
|
chatHistory.agent_is_hidden
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@@ -709,7 +706,7 @@ function ChatSession(props: ChatHistory) {
|
|||||||
className="flex items-center gap-2 no-underline"
|
className="flex items-center gap-2 no-underline"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className={`${styles.session} ${props.compressed ? styles.compressed : 'max-w-[15rem] md:max-w-[22rem]'}`}
|
className={`${styles.session} ${props.compressed ? styles.compressed : "max-w-[15rem] md:max-w-[22rem]"}`}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -48,13 +48,12 @@ async function openChat(userData: UserProfile | null | undefined) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Menu items.
|
// Menu items.
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: "Home",
|
title: "Home",
|
||||||
url: "/",
|
url: "/",
|
||||||
icon: HouseSimple
|
icon: HouseSimple,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Agents",
|
title: "Agents",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ interface TrainOfThoughtFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TrainOfThoughtGroup {
|
interface TrainOfThoughtGroup {
|
||||||
type: 'video' | 'text';
|
type: "video" | "text";
|
||||||
frames?: TrainOfThoughtFrame[];
|
frames?: TrainOfThoughtFrame[];
|
||||||
textEntries?: TrainOfThoughtObject[];
|
textEntries?: TrainOfThoughtObject[];
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,9 @@ interface TrainOfThoughtComponentProps {
|
|||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractTrainOfThoughtGroups(trainOfThought?: TrainOfThoughtObject[]): TrainOfThoughtGroup[] {
|
function extractTrainOfThoughtGroups(
|
||||||
|
trainOfThought?: TrainOfThoughtObject[],
|
||||||
|
): TrainOfThoughtGroup[] {
|
||||||
if (!trainOfThought) return [];
|
if (!trainOfThought) return [];
|
||||||
|
|
||||||
const groups: TrainOfThoughtGroup[] = [];
|
const groups: TrainOfThoughtGroup[] = [];
|
||||||
@@ -94,8 +96,8 @@ function extractTrainOfThoughtGroups(trainOfThought?: TrainOfThoughtObject[]): T
|
|||||||
// If we have accumulated text entries, add them as a text group
|
// If we have accumulated text entries, add them as a text group
|
||||||
if (currentTextEntries.length > 0) {
|
if (currentTextEntries.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
type: 'text',
|
type: "text",
|
||||||
textEntries: [...currentTextEntries]
|
textEntries: [...currentTextEntries],
|
||||||
});
|
});
|
||||||
currentTextEntries = [];
|
currentTextEntries = [];
|
||||||
}
|
}
|
||||||
@@ -116,8 +118,8 @@ function extractTrainOfThoughtGroups(trainOfThought?: TrainOfThoughtObject[]): T
|
|||||||
// If we have accumulated video frames, add them as a video group
|
// If we have accumulated video frames, add them as a video group
|
||||||
if (currentVideoFrames.length > 0) {
|
if (currentVideoFrames.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
type: 'video',
|
type: "video",
|
||||||
frames: [...currentVideoFrames]
|
frames: [...currentVideoFrames],
|
||||||
});
|
});
|
||||||
currentVideoFrames = [];
|
currentVideoFrames = [];
|
||||||
}
|
}
|
||||||
@@ -130,14 +132,14 @@ function extractTrainOfThoughtGroups(trainOfThought?: TrainOfThoughtObject[]): T
|
|||||||
// Add any remaining frames/entries
|
// Add any remaining frames/entries
|
||||||
if (currentVideoFrames.length > 0) {
|
if (currentVideoFrames.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
type: 'video',
|
type: "video",
|
||||||
frames: currentVideoFrames
|
frames: currentVideoFrames,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (currentTextEntries.length > 0) {
|
if (currentTextEntries.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
type: 'text',
|
type: "text",
|
||||||
textEntries: currentTextEntries
|
textEntries: currentTextEntries,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,10 +179,10 @@ function TrainOfThoughtComponent(props: TrainOfThoughtComponentProps) {
|
|||||||
// Convert string array to TrainOfThoughtObject array if needed
|
// Convert string array to TrainOfThoughtObject array if needed
|
||||||
let trainOfThoughtObjects: TrainOfThoughtObject[];
|
let trainOfThoughtObjects: TrainOfThoughtObject[];
|
||||||
|
|
||||||
if (typeof props.trainOfThought[0] === 'string') {
|
if (typeof props.trainOfThought[0] === "string") {
|
||||||
trainOfThoughtObjects = (props.trainOfThought as string[]).map((data, index) => ({
|
trainOfThoughtObjects = (props.trainOfThought as string[]).map((data, index) => ({
|
||||||
type: 'text',
|
type: "text",
|
||||||
data: data
|
data: data,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
trainOfThoughtObjects = props.trainOfThought as TrainOfThoughtObject[];
|
trainOfThoughtObjects = props.trainOfThought as TrainOfThoughtObject[];
|
||||||
@@ -221,18 +223,27 @@ function TrainOfThoughtComponent(props: TrainOfThoughtComponentProps) {
|
|||||||
<motion.div initial="closed" animate="open" exit="closed" variants={variants}>
|
<motion.div initial="closed" animate="open" exit="closed" variants={variants}>
|
||||||
{trainOfThoughtGroups.map((group, groupIndex) => (
|
{trainOfThoughtGroups.map((group, groupIndex) => (
|
||||||
<div key={`train-group-${groupIndex}`}>
|
<div key={`train-group-${groupIndex}`}>
|
||||||
{group.type === 'video' && group.frames && group.frames.length > 0 && (
|
{group.type === "video" &&
|
||||||
|
group.frames &&
|
||||||
|
group.frames.length > 0 && (
|
||||||
<TrainOfThoughtVideoPlayer
|
<TrainOfThoughtVideoPlayer
|
||||||
frames={group.frames}
|
frames={group.frames}
|
||||||
autoPlay={false}
|
autoPlay={false}
|
||||||
playbackSpeed={1500}
|
playbackSpeed={1500}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group.type === 'text' && group.textEntries && group.textEntries.map((entry, entryIndex) => {
|
{group.type === "text" &&
|
||||||
|
group.textEntries &&
|
||||||
|
group.textEntries.map((entry, entryIndex) => {
|
||||||
const lastIndex = trainOfThoughtGroups.length - 1;
|
const lastIndex = trainOfThoughtGroups.length - 1;
|
||||||
const isLastGroup = groupIndex === lastIndex;
|
const isLastGroup = groupIndex === lastIndex;
|
||||||
const isLastEntry = entryIndex === group.textEntries!.length - 1;
|
const isLastEntry =
|
||||||
const isPrimaryEntry = isLastGroup && isLastEntry && props.lastMessage && !props.completed;
|
entryIndex === group.textEntries!.length - 1;
|
||||||
|
const isPrimaryEntry =
|
||||||
|
isLastGroup &&
|
||||||
|
isLastEntry &&
|
||||||
|
props.lastMessage &&
|
||||||
|
!props.completed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TrainOfThought
|
<TrainOfThought
|
||||||
@@ -300,7 +311,8 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
// ResizeObserver to handle content height changes (e.g., images loading)
|
// ResizeObserver to handle content height changes (e.g., images loading)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const contentWrapper = scrollableContentWrapperRef.current;
|
const contentWrapper = scrollableContentWrapperRef.current;
|
||||||
const scrollViewport = scrollAreaRef.current?.querySelector<HTMLElement>(scrollAreaSelector);
|
const scrollViewport =
|
||||||
|
scrollAreaRef.current?.querySelector<HTMLElement>(scrollAreaSelector);
|
||||||
|
|
||||||
if (!contentWrapper || !scrollViewport) return;
|
if (!contentWrapper || !scrollViewport) return;
|
||||||
|
|
||||||
@@ -308,14 +320,18 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
// Check current scroll position to decide if auto-scroll is warranted
|
// Check current scroll position to decide if auto-scroll is warranted
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollViewport;
|
const { scrollTop, scrollHeight, clientHeight } = scrollViewport;
|
||||||
const bottomThreshold = 50;
|
const bottomThreshold = 50;
|
||||||
const currentlyNearBottom = (scrollHeight - (scrollTop + clientHeight)) <= bottomThreshold;
|
const currentlyNearBottom =
|
||||||
|
scrollHeight - (scrollTop + clientHeight) <= bottomThreshold;
|
||||||
|
|
||||||
if (currentlyNearBottom) {
|
if (currentlyNearBottom) {
|
||||||
// Only auto-scroll if there are incoming messages being processed
|
// Only auto-scroll if there are incoming messages being processed
|
||||||
if (props.incomingMessages && props.incomingMessages.length > 0) {
|
if (props.incomingMessages && props.incomingMessages.length > 0) {
|
||||||
const lastMessage = props.incomingMessages[props.incomingMessages.length - 1];
|
const lastMessage = props.incomingMessages[props.incomingMessages.length - 1];
|
||||||
// If the last message is not completed, or it just completed (indicated by incompleteIncomingMessageIndex still being set)
|
// If the last message is not completed, or it just completed (indicated by incompleteIncomingMessageIndex still being set)
|
||||||
if (!lastMessage.completed || (lastMessage.completed && incompleteIncomingMessageIndex !== null)) {
|
if (
|
||||||
|
!lastMessage.completed ||
|
||||||
|
(lastMessage.completed && incompleteIncomingMessageIndex !== null)
|
||||||
|
) {
|
||||||
scrollToBottom(true); // Use instant scroll
|
scrollToBottom(true); // Use instant scroll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -463,7 +479,12 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Optimistically set, the scroll listener will verify
|
// Optimistically set, the scroll listener will verify
|
||||||
if (instant || scrollAreaEl && (scrollAreaEl.scrollHeight - (scrollAreaEl.scrollTop + scrollAreaEl.clientHeight)) < 5) {
|
if (
|
||||||
|
instant ||
|
||||||
|
(scrollAreaEl &&
|
||||||
|
scrollAreaEl.scrollHeight - (scrollAreaEl.scrollTop + scrollAreaEl.clientHeight) <
|
||||||
|
5)
|
||||||
|
) {
|
||||||
setIsNearBottom(true);
|
setIsNearBottom(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -626,12 +647,15 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
conversationId={props.conversationId}
|
conversationId={props.conversationId}
|
||||||
turnId={messageTurnId}
|
turnId={messageTurnId}
|
||||||
/>
|
/>
|
||||||
{message.trainOfThought && message.trainOfThought.length > 0 && (
|
{message.trainOfThought &&
|
||||||
|
message.trainOfThought.length > 0 && (
|
||||||
<TrainOfThoughtComponent
|
<TrainOfThoughtComponent
|
||||||
trainOfThought={message.trainOfThought}
|
trainOfThought={message.trainOfThought}
|
||||||
lastMessage={index === incompleteIncomingMessageIndex}
|
lastMessage={
|
||||||
|
index === incompleteIncomingMessageIndex
|
||||||
|
}
|
||||||
agentColor={data?.agent?.color || "orange"}
|
agentColor={data?.agent?.color || "orange"}
|
||||||
key={`${index}trainOfThought-${message.trainOfThought.length}-${message.trainOfThought.map(t => t.length).join('-')}`}
|
key={`${index}trainOfThought-${message.trainOfThought.length}-${message.trainOfThought.map((t) => t.length).join("-")}`}
|
||||||
keyId={`${index}trainOfThought`}
|
keyId={`${index}trainOfThought`}
|
||||||
completed={message.completed}
|
completed={message.completed}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -817,18 +817,22 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{props.chatMessage.by === "khoj" && props.onRetryMessage && props.isLastMessage && (
|
{props.chatMessage.by === "khoj" &&
|
||||||
|
props.onRetryMessage &&
|
||||||
|
props.isLastMessage && (
|
||||||
<button
|
<button
|
||||||
title="Retry"
|
title="Retry"
|
||||||
className={`${styles.retryButton}`}
|
className={`${styles.retryButton}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const turnId = props.chatMessage.turnId || props.turnId;
|
const turnId = props.chatMessage.turnId || props.turnId;
|
||||||
const query = props.chatMessage.rawQuery || props.chatMessage.intent?.query;
|
const query =
|
||||||
|
props.chatMessage.rawQuery ||
|
||||||
|
props.chatMessage.intent?.query;
|
||||||
console.log("Retry button clicked for turnId:", turnId);
|
console.log("Retry button clicked for turnId:", turnId);
|
||||||
console.log("ChatMessage data:", {
|
console.log("ChatMessage data:", {
|
||||||
rawQuery: props.chatMessage.rawQuery,
|
rawQuery: props.chatMessage.rawQuery,
|
||||||
intent: props.chatMessage.intent,
|
intent: props.chatMessage.intent,
|
||||||
message: props.chatMessage.message
|
message: props.chatMessage.message,
|
||||||
});
|
});
|
||||||
console.log("Extracted query:", query);
|
console.log("Extracted query:", query);
|
||||||
if (query) {
|
if (query) {
|
||||||
@@ -836,7 +840,9 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
|
|||||||
} else {
|
} else {
|
||||||
console.error("No original query found for retry");
|
console.error("No original query found for retry");
|
||||||
// Fallback: try to get from a previous user message or show an input dialog
|
// Fallback: try to get from a previous user message or show an input dialog
|
||||||
const fallbackQuery = prompt("Enter the original query to retry:");
|
const fallbackQuery = prompt(
|
||||||
|
"Enter the original query to retry:",
|
||||||
|
);
|
||||||
if (fallbackQuery) {
|
if (fallbackQuery) {
|
||||||
props.onRetryMessage?.(fallbackQuery, turnId);
|
props.onRetryMessage?.(fallbackQuery, turnId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,29 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { ArrowsDownUp, CaretCircleDown, CheckCircle, Circle, CircleNotch, PersonSimpleTaiChi, Sparkle } from "@phosphor-icons/react";
|
import {
|
||||||
|
ArrowsDownUp,
|
||||||
|
CaretCircleDown,
|
||||||
|
CheckCircle,
|
||||||
|
Circle,
|
||||||
|
CircleNotch,
|
||||||
|
PersonSimpleTaiChi,
|
||||||
|
Sparkle,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { ModelSelector } from "@/app/common/modelSelector";
|
import { ModelSelector } from "@/app/common/modelSelector";
|
||||||
import { FilesMenu } from "../allConversations/allConversations";
|
import { FilesMenu } from "../allConversations/allConversations";
|
||||||
@@ -14,21 +33,39 @@ import { mutate } from "swr";
|
|||||||
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
||||||
import { AgentData } from "../agentCard/agentCard";
|
import { AgentData } from "../agentCard/agentCard";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { getAvailableIcons, getIconForSlashCommand, getIconFromIconName } from "@/app/common/iconUtils";
|
import {
|
||||||
|
getAvailableIcons,
|
||||||
|
getIconForSlashCommand,
|
||||||
|
getIconFromIconName,
|
||||||
|
} from "@/app/common/iconUtils";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { TooltipContent } from "@radix-ui/react-tooltip";
|
import { TooltipContent } from "@radix-ui/react-tooltip";
|
||||||
import { useAuthenticatedData } from "@/app/common/auth";
|
import { useAuthenticatedData } from "@/app/common/auth";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import {
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { convertColorToTextClass, tailwindColors } from "@/app/common/colorUtils";
|
import { convertColorToTextClass, tailwindColors } from "@/app/common/colorUtils";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
|
||||||
interface ChatSideBarProps {
|
interface ChatSideBarProps {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -40,15 +77,10 @@ interface ChatSideBarProps {
|
|||||||
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||||
|
|
||||||
export function ChatSidebar({ ...props }: ChatSideBarProps) {
|
export function ChatSidebar({ ...props }: ChatSideBarProps) {
|
||||||
|
|
||||||
if (props.isMobileWidth) {
|
if (props.isMobileWidth) {
|
||||||
return (
|
return (
|
||||||
<Sheet
|
<Sheet open={props.isOpen} onOpenChange={props.onOpenChange}>
|
||||||
open={props.isOpen}
|
<SheetContent className="w-[300px] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden">
|
||||||
onOpenChange={props.onOpenChange}>
|
|
||||||
<SheetContent
|
|
||||||
className="w-[300px] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
|
||||||
>
|
|
||||||
<ChatSidebarInternal {...props} />
|
<ChatSidebarInternal {...props} />
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
@@ -110,14 +142,14 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
fetch(createAgentUrl, {
|
fetch(createAgentUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data),
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data: AgentData | AgentError) => {
|
.then((data: AgentData | AgentError) => {
|
||||||
console.log("Success:", data);
|
console.log("Success:", data);
|
||||||
if ('detail' in data) {
|
if ("detail" in data) {
|
||||||
setError(`Error creating agent: ${data.detail}`);
|
setError(`Error creating agent: ${data.detail}`);
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
return;
|
return;
|
||||||
@@ -142,37 +174,27 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
}, [customAgentName, customAgentIcon, customAgentColor]);
|
}, [customAgentName, customAgentIcon, customAgentColor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
<Button className="w-full" variant="secondary">
|
||||||
className="w-full"
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
Create Agent
|
Create Agent
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
{
|
{doneCreating && createdSlug ? (
|
||||||
doneCreating && createdSlug ? (
|
<DialogTitle>Created {customAgentName}</DialogTitle>
|
||||||
<DialogTitle>
|
|
||||||
Created {customAgentName}
|
|
||||||
</DialogTitle>
|
|
||||||
) : (
|
) : (
|
||||||
<DialogTitle>
|
<DialogTitle>Create a New Agent</DialogTitle>
|
||||||
Create a New Agent
|
)}
|
||||||
</DialogTitle>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<DialogClose />
|
<DialogClose />
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
If these settings have been helpful, create a dedicated agent you can re-use across conversations.
|
If these settings have been helpful, create a dedicated agent you can re-use
|
||||||
|
across conversations.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
{
|
{doneCreating && createdSlug ? (
|
||||||
doneCreating && createdSlug ? (
|
|
||||||
<div className="flex flex-col items-center justify-center gap-4 py-8">
|
<div className="flex flex-col items-center justify-center gap-4 py-8">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0 }}
|
initial={{ scale: 0 }}
|
||||||
@@ -180,13 +202,10 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
transition={{
|
transition={{
|
||||||
type: "spring",
|
type: "spring",
|
||||||
stiffness: 260,
|
stiffness: 260,
|
||||||
damping: 20
|
damping: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CheckCircle
|
<CheckCircle className="w-16 h-16 text-green-500" weight="fill" />
|
||||||
className="w-16 h-16 text-green-500"
|
|
||||||
weight="fill"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.p
|
<motion.p
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
@@ -208,7 +227,7 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
) :
|
) : (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="agent_name">Name</Label>
|
<Label htmlFor="agent_name">Name</Label>
|
||||||
@@ -222,8 +241,14 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Select onValueChange={setCustomAgentColor} defaultValue={customAgentColor}>
|
<Select
|
||||||
<SelectTrigger className="w-full dark:bg-muted" disabled={isCreating}>
|
onValueChange={setCustomAgentColor}
|
||||||
|
defaultValue={customAgentColor}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="w-full dark:bg-muted"
|
||||||
|
disabled={isCreating}
|
||||||
|
>
|
||||||
<SelectValue placeholder="Color" />
|
<SelectValue placeholder="Color" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="items-center space-y-1 inline-flex flex-col">
|
<SelectContent className="items-center space-y-1 inline-flex flex-col">
|
||||||
@@ -242,8 +267,14 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Select onValueChange={setCustomAgentIcon} defaultValue={customAgentIcon}>
|
<Select
|
||||||
<SelectTrigger className="w-full dark:bg-muted" disabled={isCreating}>
|
onValueChange={setCustomAgentIcon}
|
||||||
|
defaultValue={customAgentIcon}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="w-full dark:bg-muted"
|
||||||
|
disabled={isCreating}
|
||||||
|
>
|
||||||
<SelectValue placeholder="Icon" />
|
<SelectValue placeholder="Icon" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="items-center space-y-1 inline-flex flex-col">
|
<SelectContent className="items-center space-y-1 inline-flex flex-col">
|
||||||
@@ -265,39 +296,29 @@ function AgentCreationForm(props: IAgentCreationProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
{
|
{error && <div className="text-red-500 text-sm">{error}</div>}
|
||||||
error && (
|
{!doneCreating && (
|
||||||
<div className="text-red-500 text-sm">
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!doneCreating && (
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => createAgent()}
|
onClick={() => createAgent()}
|
||||||
disabled={isCreating || !isValid}
|
disabled={isCreating || !isValid}
|
||||||
>
|
>
|
||||||
{
|
{isCreating ? (
|
||||||
isCreating ?
|
|
||||||
<CircleNotch className="animate-spin" />
|
<CircleNotch className="animate-spin" />
|
||||||
:
|
) : (
|
||||||
<PersonSimpleTaiChi />
|
<PersonSimpleTaiChi />
|
||||||
}
|
)}
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
<DialogClose />
|
<DialogClose />
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog >
|
</Dialog>
|
||||||
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
||||||
@@ -305,7 +326,14 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
const { data: agentConfigurationOptions, error: agentConfigurationOptionsError } =
|
const { data: agentConfigurationOptions, error: agentConfigurationOptionsError } =
|
||||||
useSWR<AgentConfigurationOptions>("/api/agents/options", fetcher);
|
useSWR<AgentConfigurationOptions>("/api/agents/options", fetcher);
|
||||||
|
|
||||||
const { data: agentData, isLoading: agentDataLoading, error: agentDataError } = useSWR<AgentData>(`/api/agents/conversation?conversation_id=${props.conversationId}`, fetcher);
|
const {
|
||||||
|
data: agentData,
|
||||||
|
isLoading: agentDataLoading,
|
||||||
|
error: agentDataError,
|
||||||
|
} = useSWR<AgentData>(
|
||||||
|
`/api/agents/conversation?conversation_id=${props.conversationId}`,
|
||||||
|
fetcher,
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
data: authenticatedData,
|
data: authenticatedData,
|
||||||
error: authenticationError,
|
error: authenticationError,
|
||||||
@@ -317,7 +345,9 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
const [inputTools, setInputTools] = useState<string[] | undefined>();
|
const [inputTools, setInputTools] = useState<string[] | undefined>();
|
||||||
const [outputModes, setOutputModes] = useState<string[] | undefined>();
|
const [outputModes, setOutputModes] = useState<string[] | undefined>();
|
||||||
const [hasModified, setHasModified] = useState<boolean>(false);
|
const [hasModified, setHasModified] = useState<boolean>(false);
|
||||||
const [isDefaultAgent, setIsDefaultAgent] = useState<boolean>(!agentData || agentData?.slug?.toLowerCase() === "khoj");
|
const [isDefaultAgent, setIsDefaultAgent] = useState<boolean>(
|
||||||
|
!agentData || agentData?.slug?.toLowerCase() === "khoj",
|
||||||
|
);
|
||||||
const [displayInputTools, setDisplayInputTools] = useState<string[] | undefined>();
|
const [displayInputTools, setDisplayInputTools] = useState<string[] | undefined>();
|
||||||
const [displayOutputModes, setDisplayOutputModes] = useState<string[] | undefined>();
|
const [displayOutputModes, setDisplayOutputModes] = useState<string[] | undefined>();
|
||||||
|
|
||||||
@@ -330,12 +360,20 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
setInputTools(agentData.input_tools);
|
setInputTools(agentData.input_tools);
|
||||||
setDisplayInputTools(agentData.input_tools);
|
setDisplayInputTools(agentData.input_tools);
|
||||||
if (agentData.input_tools === undefined || agentData.input_tools.length === 0) {
|
if (agentData.input_tools === undefined || agentData.input_tools.length === 0) {
|
||||||
setDisplayInputTools(agentConfigurationOptions?.input_tools ? Object.keys(agentConfigurationOptions.input_tools) : []);
|
setDisplayInputTools(
|
||||||
|
agentConfigurationOptions?.input_tools
|
||||||
|
? Object.keys(agentConfigurationOptions.input_tools)
|
||||||
|
: [],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setOutputModes(agentData.output_modes);
|
setOutputModes(agentData.output_modes);
|
||||||
setDisplayOutputModes(agentData.output_modes);
|
setDisplayOutputModes(agentData.output_modes);
|
||||||
if (agentData.output_modes === undefined || agentData.output_modes.length === 0) {
|
if (agentData.output_modes === undefined || agentData.output_modes.length === 0) {
|
||||||
setDisplayOutputModes(agentConfigurationOptions?.output_modes ? Object.keys(agentConfigurationOptions.output_modes) : []);
|
setDisplayOutputModes(
|
||||||
|
agentConfigurationOptions?.output_modes
|
||||||
|
? Object.keys(agentConfigurationOptions.output_modes)
|
||||||
|
: [],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentData.name?.toLowerCase() === "khoj" || agentData.is_hidden === true) {
|
if (agentData.name?.toLowerCase() === "khoj" || agentData.is_hidden === true) {
|
||||||
@@ -367,8 +405,12 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
const promptChanged = !!customPrompt && customPrompt !== agentData.persona;
|
const promptChanged = !!customPrompt && customPrompt !== agentData.persona;
|
||||||
|
|
||||||
// Order independent check to ensure input tools or output modes haven't been changed.
|
// Order independent check to ensure input tools or output modes haven't been changed.
|
||||||
const toolsChanged = JSON.stringify(inputTools?.sort() || []) !== JSON.stringify(agentData.input_tools?.sort());
|
const toolsChanged =
|
||||||
const modesChanged = JSON.stringify(outputModes?.sort() || []) !== JSON.stringify(agentData.output_modes?.sort());
|
JSON.stringify(inputTools?.sort() || []) !==
|
||||||
|
JSON.stringify(agentData.input_tools?.sort());
|
||||||
|
const modesChanged =
|
||||||
|
JSON.stringify(outputModes?.sort() || []) !==
|
||||||
|
JSON.stringify(agentData.output_modes?.sort());
|
||||||
|
|
||||||
setHasModified(modelChanged || promptChanged || toolsChanged || modesChanged);
|
setHasModified(modelChanged || promptChanged || toolsChanged || modesChanged);
|
||||||
|
|
||||||
@@ -394,7 +436,9 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
function handleSave() {
|
function handleSave() {
|
||||||
if (hasModified) {
|
if (hasModified) {
|
||||||
if (!isDefaultAgent && agentData?.is_hidden === false) {
|
if (!isDefaultAgent && agentData?.is_hidden === false) {
|
||||||
alert("This agent is not a hidden agent. It cannot be modified from this interface.");
|
alert(
|
||||||
|
"This agent is not a hidden agent. It cannot be modified from this interface.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,12 +453,14 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
chat_model: selectedModel,
|
chat_model: selectedModel,
|
||||||
input_tools: inputTools,
|
input_tools: inputTools,
|
||||||
output_modes: outputModes,
|
output_modes: outputModes,
|
||||||
...(isDefaultAgent ? {} : { slug: agentData?.slug })
|
...(isDefaultAgent ? {} : { slug: agentData?.slug }),
|
||||||
};
|
};
|
||||||
|
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
|
||||||
const url = !isDefaultAgent ? `/api/agents/hidden` : `/api/agents/hidden?conversation_id=${props.conversationId}`;
|
const url = !isDefaultAgent
|
||||||
|
? `/api/agents/hidden`
|
||||||
|
: `/api/agents/hidden?conversation_id=${props.conversationId}`;
|
||||||
|
|
||||||
// There are four scenarios here.
|
// There are four scenarios here.
|
||||||
// 1. If the agent is a default agent, then we need to create a new agent just to associate with this conversation.
|
// 1. If the agent is a default agent, then we need to create a new agent just to associate with this conversation.
|
||||||
@@ -424,13 +470,13 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: mode,
|
method: mode,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data),
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
res.json()
|
res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
mutate(`/api/agents/conversation?conversation_id=${props.conversationId}`);
|
mutate(`/api/agents/conversation?conversation_id=${props.conversationId}`);
|
||||||
@@ -456,43 +502,47 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
<Sidebar
|
<Sidebar
|
||||||
collapsible="none"
|
collapsible="none"
|
||||||
className={`ml-auto opacity-30 rounded-lg p-2 transition-all transform duration-300 ease-in-out
|
className={`ml-auto opacity-30 rounded-lg p-2 transition-all transform duration-300 ease-in-out
|
||||||
${props.isOpen
|
${
|
||||||
|
props.isOpen
|
||||||
? "translate-x-0 opacity-100 w-[300px] relative"
|
? "translate-x-0 opacity-100 w-[300px] relative"
|
||||||
: "translate-x-full opacity-100 w-0 p-0 m-0"}
|
: "translate-x-full opacity-100 w-0 p-0 m-0"
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
variant="floating">
|
variant="floating"
|
||||||
|
>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
{
|
{agentData && !isEditable ? (
|
||||||
agentData && !isEditable ? (
|
|
||||||
<div className="flex items-center relative text-sm">
|
<div className="flex items-center relative text-sm">
|
||||||
<a className="text-lg font-bold flex flex-row items-center" href={`/agents?agent=${agentData.slug}`}>
|
<a
|
||||||
|
className="text-lg font-bold flex flex-row items-center"
|
||||||
|
href={`/agents?agent=${agentData.slug}`}
|
||||||
|
>
|
||||||
{getIconFromIconName(agentData.icon, agentData.color)}
|
{getIconFromIconName(agentData.icon, agentData.color)}
|
||||||
{agentData.name}
|
{agentData.name}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center relative text-sm justify-between">
|
<div className="flex items-center relative text-sm justify-between">
|
||||||
<p>
|
<p>Chat Options</p>
|
||||||
Chat Options
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarGroup key={"knowledge"} className="border-b last:border-none">
|
<SidebarGroup key={"knowledge"} className="border-b last:border-none">
|
||||||
<SidebarGroupContent className="gap-0">
|
<SidebarGroupContent className="gap-0">
|
||||||
<SidebarMenu className="p-0 m-0">
|
<SidebarMenu className="p-0 m-0">
|
||||||
{
|
{agentData && agentData.has_files ? (
|
||||||
agentData && agentData.has_files ? (
|
|
||||||
<SidebarMenuItem key={"agent_knowledge"} className="list-none">
|
<SidebarMenuItem key={"agent_knowledge"} className="list-none">
|
||||||
<div className="flex items-center space-x-2 rounded-full">
|
<div className="flex items-center space-x-2 rounded-full">
|
||||||
<div className="text-muted-foreground"><Sparkle /></div>
|
<div className="text-muted-foreground">
|
||||||
<div className="text-muted-foreground text-sm">Using custom knowledge base</div>
|
<Sparkle />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground text-sm">
|
||||||
|
Using custom knowledge base
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
) : null
|
) : null}
|
||||||
}
|
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
@@ -506,39 +556,41 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
value={customPrompt || ""}
|
value={customPrompt || ""}
|
||||||
onChange={(e) => handleCustomPromptChange(e.target.value)}
|
onChange={(e) => handleCustomPromptChange(e.target.value)}
|
||||||
readOnly={!isEditable}
|
readOnly={!isEditable}
|
||||||
disabled={!isEditable} />
|
disabled={!isEditable}
|
||||||
|
/>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
{
|
{!agentDataLoading && agentData && (
|
||||||
!agentDataLoading && agentData && (
|
|
||||||
<SidebarGroup key={"model"}>
|
<SidebarGroup key={"model"}>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarGroupLabel>
|
<SidebarGroupLabel>
|
||||||
Model
|
Model
|
||||||
{
|
{!isSubscribed && (
|
||||||
!isSubscribed && (
|
<a
|
||||||
<a href="/settings" className="hover:font-bold text-accent-foreground m-2 bg-accent bg-opacity-10 p-1 rounded-lg">
|
href="/settings"
|
||||||
|
className="hover:font-bold text-accent-foreground m-2 bg-accent bg-opacity-10 p-1 rounded-lg"
|
||||||
|
>
|
||||||
Upgrade
|
Upgrade
|
||||||
</a>
|
</a>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</SidebarGroupLabel>
|
</SidebarGroupLabel>
|
||||||
<SidebarMenu className="p-0 m-0">
|
<SidebarMenu className="p-0 m-0">
|
||||||
<SidebarMenuItem key={"model"} className="list-none">
|
<SidebarMenuItem key={"model"} className="list-none">
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onSelect={(model) => handleModelSelect(model.name)}
|
onSelect={(model) => handleModelSelect(model.name)}
|
||||||
initialModel={isDefaultAgent ? undefined : agentData?.chat_model}
|
initialModel={
|
||||||
|
isDefaultAgent ? undefined : agentData?.chat_model
|
||||||
|
}
|
||||||
isActive={props.isActive}
|
isActive={props.isActive}
|
||||||
/>
|
/>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
<Popover defaultOpen={false}>
|
<Popover defaultOpen={false}>
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel asChild>
|
<SidebarGroupLabel asChild>
|
||||||
@@ -550,14 +602,18 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu className="p-1 m-0">
|
<SidebarMenu className="p-1 m-0">
|
||||||
{
|
{Object.entries(
|
||||||
Object.entries(agentConfigurationOptions?.input_tools ?? {}).map(([key, value]) => {
|
agentConfigurationOptions?.input_tools ?? {},
|
||||||
|
).map(([key, value]) => {
|
||||||
return (
|
return (
|
||||||
<SidebarMenuItem key={key} className="list-none">
|
<SidebarMenuItem key={key} className="list-none">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger key={key} asChild>
|
<TooltipTrigger key={key} asChild>
|
||||||
<div className="flex items-center space-x-2 py-1 justify-between">
|
<div className="flex items-center space-x-2 py-1 justify-between">
|
||||||
<Label htmlFor={key} className="flex items-center gap-2 text-accent-foreground p-1 cursor-pointer">
|
<Label
|
||||||
|
htmlFor={key}
|
||||||
|
className="flex items-center gap-2 text-accent-foreground p-1 cursor-pointer"
|
||||||
|
>
|
||||||
{getIconForSlashCommand(key)}
|
{getIconForSlashCommand(key)}
|
||||||
<p className="text-sm my-auto flex items-center">
|
<p className="text-sm my-auto flex items-center">
|
||||||
{key}
|
{key}
|
||||||
@@ -566,11 +622,22 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={key}
|
id={key}
|
||||||
className={`${isEditable ? "cursor-pointer" : ""}`}
|
className={`${isEditable ? "cursor-pointer" : ""}`}
|
||||||
checked={isValueChecked(key, displayInputTools ?? [])}
|
checked={isValueChecked(
|
||||||
|
key,
|
||||||
|
displayInputTools ?? [],
|
||||||
|
)}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
let updatedInputTools = handleCheckToggle(key, displayInputTools ?? [])
|
let updatedInputTools =
|
||||||
setInputTools(updatedInputTools);
|
handleCheckToggle(
|
||||||
setDisplayInputTools(updatedInputTools);
|
key,
|
||||||
|
displayInputTools ?? [],
|
||||||
|
);
|
||||||
|
setInputTools(
|
||||||
|
updatedInputTools,
|
||||||
|
);
|
||||||
|
setDisplayInputTools(
|
||||||
|
updatedInputTools,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
>
|
>
|
||||||
@@ -578,23 +645,30 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent sideOffset={5} side="left" align="start" className="text-sm bg-background text-foreground shadow-sm border border-slate-500 border-opacity-20 p-2 rounded-lg">
|
<TooltipContent
|
||||||
|
sideOffset={5}
|
||||||
|
side="left"
|
||||||
|
align="start"
|
||||||
|
className="text-sm bg-background text-foreground shadow-sm border border-slate-500 border-opacity-20 p-2 rounded-lg"
|
||||||
|
>
|
||||||
{value}
|
{value}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
)
|
{Object.entries(
|
||||||
}
|
agentConfigurationOptions?.output_modes ?? {},
|
||||||
{
|
).map(([key, value]) => {
|
||||||
Object.entries(agentConfigurationOptions?.output_modes ?? {}).map(([key, value]) => {
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenuItem key={key} className="list-none">
|
<SidebarMenuItem key={key} className="list-none">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger key={key} asChild>
|
<TooltipTrigger key={key} asChild>
|
||||||
<div className="flex items-center space-x-2 py-1 justify-between">
|
<div className="flex items-center space-x-2 py-1 justify-between">
|
||||||
<Label htmlFor={key} className="flex items-center gap-2 p-1 rounded-lg cursor-pointer">
|
<Label
|
||||||
|
htmlFor={key}
|
||||||
|
className="flex items-center gap-2 p-1 rounded-lg cursor-pointer"
|
||||||
|
>
|
||||||
{getIconForSlashCommand(key)}
|
{getIconForSlashCommand(key)}
|
||||||
<p className="text-sm my-auto flex items-center">
|
<p className="text-sm my-auto flex items-center">
|
||||||
{key}
|
{key}
|
||||||
@@ -603,11 +677,23 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={key}
|
id={key}
|
||||||
className={`${isEditable ? "cursor-pointer" : ""}`}
|
className={`${isEditable ? "cursor-pointer" : ""}`}
|
||||||
checked={isValueChecked(key, displayOutputModes ?? [])}
|
checked={isValueChecked(
|
||||||
|
key,
|
||||||
|
displayOutputModes ?? [],
|
||||||
|
)}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
let updatedOutputModes = handleCheckToggle(key, displayOutputModes ?? [])
|
let updatedOutputModes =
|
||||||
setOutputModes(updatedOutputModes);
|
handleCheckToggle(
|
||||||
setDisplayOutputModes(updatedOutputModes);
|
key,
|
||||||
|
displayOutputModes ??
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
setOutputModes(
|
||||||
|
updatedOutputModes,
|
||||||
|
);
|
||||||
|
setDisplayOutputModes(
|
||||||
|
updatedOutputModes,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
>
|
>
|
||||||
@@ -615,17 +701,19 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent sideOffset={5} side="left" align="start" className="text-sm bg-background text-foreground shadow-sm border border-slate-500 border-opacity-20 p-2 rounded-lg">
|
<TooltipContent
|
||||||
|
sideOffset={5}
|
||||||
|
side="left"
|
||||||
|
align="start"
|
||||||
|
className="text-sm bg-background text-foreground shadow-sm border border-slate-500 border-opacity-20 p-2 rounded-lg"
|
||||||
|
>
|
||||||
{value}
|
{value}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
)
|
|
||||||
}
|
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
@@ -645,28 +733,30 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
{
|
{props.isOpen && (
|
||||||
props.isOpen && (
|
|
||||||
<SidebarFooter key={"actions"}>
|
<SidebarFooter key={"actions"}>
|
||||||
<SidebarMenu className="p-0 m-0">
|
<SidebarMenu className="p-0 m-0">
|
||||||
|
{agentData && !isEditable && agentData.is_creator ? (
|
||||||
{
|
|
||||||
(agentData && !isEditable && agentData.is_creator) ? (
|
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
onClick={() => window.location.href = `/agents?agent=${agentData?.slug}`}
|
onClick={() =>
|
||||||
|
(window.location.href = `/agents?agent=${agentData?.slug}`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Manage
|
Manage
|
||||||
</Button>
|
</Button>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
) :
|
) : (
|
||||||
<>
|
<>
|
||||||
{
|
{!hasModified &&
|
||||||
!hasModified && isEditable && customPrompt && !isDefaultAgent && selectedModel && (
|
isEditable &&
|
||||||
|
customPrompt &&
|
||||||
|
!isDefaultAgent &&
|
||||||
|
selectedModel && (
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<AgentCreationForm
|
<AgentCreationForm
|
||||||
@@ -677,8 +767,7 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
/>
|
/>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -699,25 +788,20 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
|||||||
onClick={() => handleSave()}
|
onClick={() => handleSave()}
|
||||||
disabled={!isEditable || !hasModified || isSaving}
|
disabled={!isEditable || !hasModified || isSaving}
|
||||||
>
|
>
|
||||||
{
|
{isSaving ? (
|
||||||
isSaving ?
|
|
||||||
<CircleNotch className="animate-spin" />
|
<CircleNotch className="animate-spin" />
|
||||||
:
|
) : (
|
||||||
<ArrowsDownUp />
|
<ArrowsDownUp />
|
||||||
}
|
)}
|
||||||
{
|
{isSaving ? "Saving" : "Save"}
|
||||||
isSaving ? "Saving" : "Save"
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,9 +147,7 @@ const Mermaid: React.FC<MermaidProps> = ({ chart }) => {
|
|||||||
<span>{mermaidError}</span>
|
<span>{mermaidError}</span>
|
||||||
</div>
|
</div>
|
||||||
<code className="block bg-secondary text-secondary-foreground p-4 mt-3 rounded-lg font-mono text-sm whitespace-pre-wrap overflow-x-auto max-h-[400px] border border-gray-200">
|
<code className="block bg-secondary text-secondary-foreground p-4 mt-3 rounded-lg font-mono text-sm whitespace-pre-wrap overflow-x-auto max-h-[400px] border border-gray-200">
|
||||||
{
|
{chart}
|
||||||
chart
|
|
||||||
}
|
|
||||||
</code>
|
</code>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Moon, Sun, UserCircle, Question, ArrowRight, Code, BuildingOffice } from "@phosphor-icons/react";
|
import {
|
||||||
|
Moon,
|
||||||
|
Sun,
|
||||||
|
UserCircle,
|
||||||
|
Question,
|
||||||
|
ArrowRight,
|
||||||
|
Code,
|
||||||
|
BuildingOffice,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
import { useIsDarkMode, useIsMobileWidth } from "@/app/common/utils";
|
import { useIsDarkMode, useIsMobileWidth } from "@/app/common/utils";
|
||||||
import LoginPrompt from "../loginPrompt/loginPrompt";
|
import LoginPrompt from "../loginPrompt/loginPrompt";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -69,7 +77,7 @@ export default function FooterMenu({ sideBarIsOpen }: NavMenuProps) {
|
|||||||
icon: <BuildingOffice className="w-6 h-6" />,
|
icon: <BuildingOffice className="w-6 h-6" />,
|
||||||
link: "https://khoj.dev/teams",
|
link: "https://khoj.dev/teams",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu className="border-none p-0 m-0">
|
<SidebarMenu className="border-none p-0 m-0">
|
||||||
@@ -131,8 +139,7 @@ export default function FooterMenu({ sideBarIsOpen }: NavMenuProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{
|
{menuItems.map((menuItem, index) => (
|
||||||
menuItems.map((menuItem, index) => (
|
|
||||||
<DropdownMenuItem key={index}>
|
<DropdownMenuItem key={index}>
|
||||||
<Link href={menuItem.link} className="no-underline w-full">
|
<Link href={menuItem.link} className="no-underline w-full">
|
||||||
<div className="flex flex-rows">
|
<div className="flex flex-rows">
|
||||||
@@ -141,8 +148,7 @@ export default function FooterMenu({ sideBarIsOpen }: NavMenuProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
{!userData ? (
|
{!userData ? (
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
"use client";
|
||||||
|
|
||||||
import { useIsDarkMode } from '@/app/common/utils'
|
import { useIsDarkMode } from "@/app/common/utils";
|
||||||
|
|
||||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [darkMode, setDarkMode] = useIsDarkMode();
|
const [darkMode, setDarkMode] = useIsDarkMode();
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
|||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open || (noMatchingFiles && (!!inputText))} onOpenChange={setOpen}>
|
<Popover open={open || (noMatchingFiles && !!inputText)} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -528,7 +528,11 @@ function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[200px] p-0">
|
<PopoverContent className="w-[200px] p-0">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search files..." value={inputText} onInput={(e) => setInputText(e.currentTarget.value)} />
|
<CommandInput
|
||||||
|
placeholder="Search files..."
|
||||||
|
value={inputText}
|
||||||
|
onInput={(e) => setInputText(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No files found.</CommandEmpty>
|
<CommandEmpty>No files found.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
@@ -614,7 +618,6 @@ export default function Search() {
|
|||||||
setSelectedFileFilter("INITIALIZE");
|
setSelectedFileFilter("INITIALIZE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|
||||||
function handleSearchInputChange(value: string) {
|
function handleSearchInputChange(value: string) {
|
||||||
|
|||||||
@@ -23,8 +23,15 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
AlertDialog, AlertDialogAction, AlertDialogCancel,
|
AlertDialog,
|
||||||
AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
||||||
|
|
||||||
@@ -72,7 +79,7 @@ import { KhojLogoType } from "../components/logo/khojLogo";
|
|||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
|
||||||
import JSZip from "jszip";
|
import JSZip from "jszip";
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from "file-saver";
|
||||||
|
|
||||||
interface DropdownComponentProps {
|
interface DropdownComponentProps {
|
||||||
items: ModelOptions[];
|
items: ModelOptions[];
|
||||||
@@ -81,7 +88,12 @@ interface DropdownComponentProps {
|
|||||||
callbackFunc: (value: string) => Promise<void>;
|
callbackFunc: (value: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownComponent: React.FC<DropdownComponentProps> = ({ items, selected, isActive, callbackFunc }) => {
|
const DropdownComponent: React.FC<DropdownComponentProps> = ({
|
||||||
|
items,
|
||||||
|
selected,
|
||||||
|
isActive,
|
||||||
|
callbackFunc,
|
||||||
|
}) => {
|
||||||
const [position, setPosition] = useState(selected?.toString() ?? "0");
|
const [position, setPosition] = useState(selected?.toString() ?? "0");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -114,7 +126,10 @@ const DropdownComponent: React.FC<DropdownComponentProps> = ({ items, selected,
|
|||||||
value={item.id.toString()}
|
value={item.id.toString()}
|
||||||
disabled={!isActive && item.tier !== "free"}
|
disabled={!isActive && item.tier !== "free"}
|
||||||
>
|
>
|
||||||
{item.name} {item.tier === "standard" && <span className="text-green-500 ml-2">(Futurist)</span>}
|
{item.name}{" "}
|
||||||
|
{item.tier === "standard" && (
|
||||||
|
<span className="text-green-500 ml-2">(Futurist)</span>
|
||||||
|
)}
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
@@ -524,13 +539,14 @@ export default function SettingsView() {
|
|||||||
|
|
||||||
const updateModel = (modelType: string) => async (id: string) => {
|
const updateModel = (modelType: string) => async (id: string) => {
|
||||||
// Get the selected model from the options
|
// Get the selected model from the options
|
||||||
const modelOptions = modelType === "chat"
|
const modelOptions =
|
||||||
|
modelType === "chat"
|
||||||
? userConfig?.chat_model_options
|
? userConfig?.chat_model_options
|
||||||
: modelType === "paint"
|
: modelType === "paint"
|
||||||
? userConfig?.paint_model_options
|
? userConfig?.paint_model_options
|
||||||
: userConfig?.voice_model_options;
|
: userConfig?.voice_model_options;
|
||||||
|
|
||||||
const selectedModel = modelOptions?.find(model => model.id.toString() === id);
|
const selectedModel = modelOptions?.find((model) => model.id.toString() === id);
|
||||||
const modelName = selectedModel?.name;
|
const modelName = selectedModel?.name;
|
||||||
|
|
||||||
// Check if the model is free tier or if the user is active
|
// Check if the model is free tier or if the user is active
|
||||||
@@ -551,7 +567,8 @@ export default function SettingsView() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`Failed to switch ${modelType} model to ${modelName}`);
|
if (!response.ok)
|
||||||
|
throw new Error(`Failed to switch ${modelType} model to ${modelName}`);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: `✅ Switched ${modelType} model to ${modelName}`,
|
title: `✅ Switched ${modelType} model to ${modelName}`,
|
||||||
@@ -570,7 +587,7 @@ export default function SettingsView() {
|
|||||||
setIsExporting(true);
|
setIsExporting(true);
|
||||||
|
|
||||||
// Get total conversation count
|
// Get total conversation count
|
||||||
const statsResponse = await fetch('/api/chat/stats');
|
const statsResponse = await fetch("/api/chat/stats");
|
||||||
const stats = await statsResponse.json();
|
const stats = await statsResponse.json();
|
||||||
const total = stats.num_conversations;
|
const total = stats.num_conversations;
|
||||||
setTotalConversations(total);
|
setTotalConversations(total);
|
||||||
@@ -586,7 +603,7 @@ export default function SettingsView() {
|
|||||||
conversations.push(...data);
|
conversations.push(...data);
|
||||||
|
|
||||||
setExportedConversations((page + 1) * 10);
|
setExportedConversations((page + 1) * 10);
|
||||||
setExportProgress(((page + 1) * 10 / total) * 100);
|
setExportProgress((((page + 1) * 10) / total) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add conversations to zip
|
// Add conversations to zip
|
||||||
@@ -605,7 +622,7 @@ export default function SettingsView() {
|
|||||||
toast({
|
toast({
|
||||||
title: "Export Failed",
|
title: "Export Failed",
|
||||||
description: "Failed to export chats. Please try again.",
|
description: "Failed to export chats. Please try again.",
|
||||||
variant: "destructive"
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsExporting(false);
|
setIsExporting(false);
|
||||||
@@ -1123,7 +1140,10 @@ export default function SettingsView() {
|
|||||||
<CardFooter className="flex flex-wrap gap-4">
|
<CardFooter className="flex flex-wrap gap-4">
|
||||||
{!userConfig.is_active && (
|
{!userConfig.is_active && (
|
||||||
<p className="text-gray-400">
|
<p className="text-gray-400">
|
||||||
{userConfig.chat_model_options.some(model => model.tier === "free")
|
{userConfig.chat_model_options.some(
|
||||||
|
(model) =>
|
||||||
|
model.tier === "free",
|
||||||
|
)
|
||||||
? "Free models available"
|
? "Free models available"
|
||||||
: "Subscribe to switch model"}
|
: "Subscribe to switch model"}
|
||||||
</p>
|
</p>
|
||||||
@@ -1154,7 +1174,10 @@ export default function SettingsView() {
|
|||||||
<CardFooter className="flex flex-wrap gap-4">
|
<CardFooter className="flex flex-wrap gap-4">
|
||||||
{!userConfig.is_active && (
|
{!userConfig.is_active && (
|
||||||
<p className="text-gray-400">
|
<p className="text-gray-400">
|
||||||
{userConfig.paint_model_options.some(model => model.tier === "free")
|
{userConfig.paint_model_options.some(
|
||||||
|
(model) =>
|
||||||
|
model.tier === "free",
|
||||||
|
)
|
||||||
? "Free models available"
|
? "Free models available"
|
||||||
: "Subscribe to switch model"}
|
: "Subscribe to switch model"}
|
||||||
</p>
|
</p>
|
||||||
@@ -1185,7 +1208,10 @@ export default function SettingsView() {
|
|||||||
<CardFooter className="flex flex-wrap gap-4">
|
<CardFooter className="flex flex-wrap gap-4">
|
||||||
{!userConfig.is_active && (
|
{!userConfig.is_active && (
|
||||||
<p className="text-gray-400">
|
<p className="text-gray-400">
|
||||||
{userConfig.voice_model_options.some(model => model.tier === "free")
|
{userConfig.voice_model_options.some(
|
||||||
|
(model) =>
|
||||||
|
model.tier === "free",
|
||||||
|
)
|
||||||
? "Free models available"
|
? "Free models available"
|
||||||
: "Subscribe to switch model"}
|
: "Subscribe to switch model"}
|
||||||
</p>
|
</p>
|
||||||
@@ -1219,9 +1245,13 @@ export default function SettingsView() {
|
|||||||
</p>
|
</p>
|
||||||
{exportProgress > 0 && (
|
{exportProgress > 0 && (
|
||||||
<div className="w-full mt-4">
|
<div className="w-full mt-4">
|
||||||
<Progress value={exportProgress} className="w-full" />
|
<Progress
|
||||||
|
value={exportProgress}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
<p className="text-sm text-gray-500 mt-2">
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
Exported {exportedConversations} of {totalConversations} conversations
|
Exported {exportedConversations} of{" "}
|
||||||
|
{totalConversations} conversations
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1233,7 +1263,9 @@ export default function SettingsView() {
|
|||||||
disabled={isExporting}
|
disabled={isExporting}
|
||||||
>
|
>
|
||||||
<Download className="h-5 w-5 mr-2" />
|
<Download className="h-5 w-5 mr-2" />
|
||||||
{isExporting ? "Exporting..." : "Export Chats"}
|
{isExporting
|
||||||
|
? "Exporting..."
|
||||||
|
: "Export Chats"}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -1245,7 +1277,11 @@ export default function SettingsView() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="overflow-hidden">
|
<CardContent className="overflow-hidden">
|
||||||
<p className="pb-4 text-gray-400">
|
<p className="pb-4 text-gray-400">
|
||||||
This will delete all your account data, including conversations, agents, and any assets you{"'"}ve generated. Be sure to export before you do this if you want to keep your information.
|
This will delete all your account data,
|
||||||
|
including conversations, agents, and any
|
||||||
|
assets you{"'"}ve generated. Be sure to
|
||||||
|
export before you do this if you want to
|
||||||
|
keep your information.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="flex flex-wrap gap-4">
|
<CardFooter className="flex flex-wrap gap-4">
|
||||||
@@ -1261,36 +1297,56 @@ export default function SettingsView() {
|
|||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
Are you absolutely sure?
|
||||||
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
This action is irreversible. This will permanently delete your account
|
This action is irreversible.
|
||||||
and remove all your data from our servers.
|
This will permanently delete
|
||||||
|
your account and remove all your
|
||||||
|
data from our servers.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
className="bg-red-500 hover:bg-red-600"
|
className="bg-red-500 hover:bg-red-600"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/self', {
|
const response =
|
||||||
method: 'DELETE'
|
await fetch(
|
||||||
});
|
"/api/self",
|
||||||
if (!response.ok) throw new Error('Failed to delete account');
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(
|
||||||
|
"Failed to delete account",
|
||||||
|
);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Account Deleted",
|
title: "Account Deleted",
|
||||||
description: "Your account has been successfully deleted.",
|
description:
|
||||||
|
"Your account has been successfully deleted.",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redirect to home page after successful deletion
|
// Redirect to home page after successful deletion
|
||||||
window.location.href = "/";
|
window.location.href =
|
||||||
|
"/";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting account:', error);
|
console.error(
|
||||||
|
"Error deleting account:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description: "Failed to delete account. Please try again or contact support.",
|
description:
|
||||||
variant: "destructive"
|
"Failed to delete account. Please try again or contact support.",
|
||||||
|
variant:
|
||||||
|
"destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
import { Check } from "lucide-react"
|
import { Check } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<
|
const Checkbox = React.forwardRef<
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
@@ -14,7 +14,7 @@ const Checkbox = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer h-4 w-4 shrink-0 rounded-sm border border-secondary-foreground ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-gray-500 data-[state=checked]:text-primary-foreground",
|
"peer h-4 w-4 shrink-0 rounded-sm border border-secondary-foreground ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-gray-500 data-[state=checked]:text-primary-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -24,7 +24,7 @@ const Checkbox = React.forwardRef<
|
|||||||
<Check className="h-4 w-4" />
|
<Check className="h-4 w-4" />
|
||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
))
|
));
|
||||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||||
|
|
||||||
export { Checkbox }
|
export { Checkbox };
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const HoverCard = HoverCardPrimitive.Root
|
const HoverCard = HoverCardPrimitive.Root;
|
||||||
|
|
||||||
const HoverCardTrigger = HoverCardPrimitive.Trigger
|
const HoverCardTrigger = HoverCardPrimitive.Trigger;
|
||||||
|
|
||||||
const HoverCardContent = React.forwardRef<
|
const HoverCardContent = React.forwardRef<
|
||||||
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||||
@@ -19,11 +19,11 @@ const HoverCardContent = React.forwardRef<
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
|
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
import { ButtonProps, buttonVariants } from "@/components/ui/button";
|
||||||
|
|
||||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
<nav
|
<nav
|
||||||
@@ -11,40 +11,27 @@ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
|||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
Pagination.displayName = "Pagination"
|
Pagination.displayName = "Pagination";
|
||||||
|
|
||||||
const PaginationContent = React.forwardRef<
|
const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
|
||||||
HTMLUListElement,
|
({ className, ...props }, ref) => (
|
||||||
React.ComponentProps<"ul">
|
<ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<ul
|
);
|
||||||
ref={ref}
|
PaginationContent.displayName = "PaginationContent";
|
||||||
className={cn("flex flex-row items-center gap-1", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
PaginationContent.displayName = "PaginationContent"
|
|
||||||
|
|
||||||
const PaginationItem = React.forwardRef<
|
const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
|
||||||
HTMLLIElement,
|
({ className, ...props }, ref) => <li ref={ref} className={cn("", className)} {...props} />,
|
||||||
React.ComponentProps<"li">
|
);
|
||||||
>(({ className, ...props }, ref) => (
|
PaginationItem.displayName = "PaginationItem";
|
||||||
<li ref={ref} className={cn("", className)} {...props} />
|
|
||||||
))
|
|
||||||
PaginationItem.displayName = "PaginationItem"
|
|
||||||
|
|
||||||
type PaginationLinkProps = {
|
type PaginationLinkProps = {
|
||||||
isActive?: boolean
|
isActive?: boolean;
|
||||||
} & Pick<ButtonProps, "size"> &
|
} & Pick<ButtonProps, "size"> &
|
||||||
React.ComponentProps<"a">
|
React.ComponentProps<"a">;
|
||||||
|
|
||||||
const PaginationLink = ({
|
const PaginationLink = ({ className, isActive, size = "icon", ...props }: PaginationLinkProps) => (
|
||||||
className,
|
|
||||||
isActive,
|
|
||||||
size = "icon",
|
|
||||||
...props
|
|
||||||
}: PaginationLinkProps) => (
|
|
||||||
<a
|
<a
|
||||||
aria-current={isActive ? "page" : undefined}
|
aria-current={isActive ? "page" : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -53,12 +40,12 @@ const PaginationLink = ({
|
|||||||
size,
|
size,
|
||||||
}),
|
}),
|
||||||
"no-underline",
|
"no-underline",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
PaginationLink.displayName = "PaginationLink"
|
PaginationLink.displayName = "PaginationLink";
|
||||||
|
|
||||||
const PaginationPrevious = ({
|
const PaginationPrevious = ({
|
||||||
className,
|
className,
|
||||||
@@ -73,13 +60,10 @@ const PaginationPrevious = ({
|
|||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
<span>Previous</span>
|
<span>Previous</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
);
|
||||||
PaginationPrevious.displayName = "PaginationPrevious"
|
PaginationPrevious.displayName = "PaginationPrevious";
|
||||||
|
|
||||||
const PaginationNext = ({
|
const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to next page"
|
aria-label="Go to next page"
|
||||||
size="default"
|
size="default"
|
||||||
@@ -89,13 +73,10 @@ const PaginationNext = ({
|
|||||||
<span>Next</span>
|
<span>Next</span>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
);
|
||||||
PaginationNext.displayName = "PaginationNext"
|
PaginationNext.displayName = "PaginationNext";
|
||||||
|
|
||||||
const PaginationEllipsis = ({
|
const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span">) => (
|
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
@@ -104,8 +85,8 @@ const PaginationEllipsis = ({
|
|||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
<span className="sr-only">More pages</span>
|
<span className="sr-only">More pages</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
PaginationEllipsis.displayName = "PaginationEllipsis"
|
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Pagination,
|
Pagination,
|
||||||
@@ -115,4 +96,4 @@ export {
|
|||||||
PaginationLink,
|
PaginationLink,
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
const TooltipProvider = TooltipPrimitive.Provider;
|
||||||
|
|
||||||
const Tooltip = TooltipPrimitive.Root
|
const Tooltip = TooltipPrimitive.Root;
|
||||||
|
|
||||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||||
|
|
||||||
const TooltipContent = React.forwardRef<
|
const TooltipContent = React.forwardRef<
|
||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
@@ -20,11 +20,11 @@ const TooltipContent = React.forwardRef<
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
|
|||||||
Reference in New Issue
Block a user