Automatically carry over research mode from home page to chat

- Improve mobile friendliness with new research mode toggle, since chat input area is now taking up more space
- Remove clunky title from the suggestion card
- Fix fk lookup error for agent.creator
This commit is contained in:
sabaimran 2024-10-28 15:29:24 -07:00
parent ebaed53069
commit 1f1b182461
8 changed files with 62 additions and 55 deletions

View file

@ -42,6 +42,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
const [images, setImages] = useState<string[]>([]); const [images, setImages] = useState<string[]>([]);
const [processingMessage, setProcessingMessage] = useState(false); const [processingMessage, setProcessingMessage] = useState(false);
const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(null); const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(null);
const [isInResearchMode, setIsInResearchMode] = useState(false);
const chatInputRef = useRef<HTMLTextAreaElement>(null); const chatInputRef = useRef<HTMLTextAreaElement>(null);
const setQueryToProcess = props.setQueryToProcess; const setQueryToProcess = props.setQueryToProcess;
@ -70,6 +71,10 @@ function ChatBodyData(props: ChatBodyDataProps) {
if (storedMessage) { if (storedMessage) {
setProcessingMessage(true); setProcessingMessage(true);
setQueryToProcess(storedMessage); setQueryToProcess(storedMessage);
if (storedMessage.trim().startsWith("/research")) {
setIsInResearchMode(true);
}
} }
}, [setQueryToProcess, props.setImages]); }, [setQueryToProcess, props.setImages]);
@ -130,6 +135,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
isMobileWidth={props.isMobileWidth} isMobileWidth={props.isMobileWidth}
setUploadedFiles={props.setUploadedFiles} setUploadedFiles={props.setUploadedFiles}
ref={chatInputRef} ref={chatInputRef}
isResearchModeEnabled={isInResearchMode}
/> />
</div> </div>
</> </>

View file

@ -71,7 +71,7 @@ function TrainOfThoughtComponent(props: TrainOfThoughtComponentProps) {
</Button> </Button>
) : ( ) : (
<Button <Button
className="w-fit text-left justify-start content-start text-xs p-0" className="w-fit text-left justify-start content-start text-xs p-0 h-fit"
onClick={() => setCollapsed(true)} onClick={() => setCollapsed(true)}
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -283,13 +283,11 @@ export default function ChatHistory(props: ChatHistoryProps) {
} }
return ( return (
<ScrollArea className={`h-[80vh] relative`} ref={scrollAreaRef}> <ScrollArea className={`h-[73vh] relative`} ref={scrollAreaRef}>
<div> <div>
<div className={`${styles.chatHistory} ${props.customClassName}`}> <div className={`${styles.chatHistory} ${props.customClassName}`}>
<div ref={sentinelRef} style={{ height: "1px" }}> <div ref={sentinelRef} style={{ height: "1px" }}>
{fetchingData && ( {fetchingData && <InlineLoading className="opacity-50" />}
<InlineLoading message="Loading Conversation" className="opacity-50" />
)}
</div> </div>
{data && {data &&
data.chat && data.chat &&

View file

@ -37,12 +37,7 @@ import { Popover, PopoverContent } from "@/components/ui/popover";
import { PopoverTrigger } from "@radix-ui/react-popover"; import { PopoverTrigger } from "@radix-ui/react-popover";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { import { convertColorToTextClass, convertToBGClass } from "@/app/common/colorUtils";
convertColorToTextClass,
convertToBGClass,
convertColorToBorderClass,
convertColorToRingClass,
} from "@/app/common/colorUtils";
import LoginPrompt from "../loginPrompt/loginPrompt"; import LoginPrompt from "../loginPrompt/loginPrompt";
import { uploadDataForIndexing } from "../../common/chatFunctions"; import { uploadDataForIndexing } from "../../common/chatFunctions";
@ -63,6 +58,7 @@ interface ChatInputProps {
isMobileWidth?: boolean; isMobileWidth?: boolean;
isLoggedIn: boolean; isLoggedIn: boolean;
agentColor?: string; agentColor?: string;
isResearchModeEnabled?: boolean;
} }
export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((props, ref) => { export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((props, ref) => {
@ -86,7 +82,9 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
const [isDragAndDropping, setIsDragAndDropping] = useState(false); const [isDragAndDropping, setIsDragAndDropping] = useState(false);
const [showCommandList, setShowCommandList] = useState(false); const [showCommandList, setShowCommandList] = useState(false);
const [useResearchMode, setUseResearchMode] = useState(false); const [useResearchMode, setUseResearchMode] = useState<boolean>(
props.isResearchModeEnabled || false,
);
const chatInputRef = ref as React.MutableRefObject<HTMLTextAreaElement>; const chatInputRef = ref as React.MutableRefObject<HTMLTextAreaElement>;
useEffect(() => { useEffect(() => {
@ -128,6 +126,12 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
fetchImageData(); fetchImageData();
}, [imagePaths]); }, [imagePaths]);
useEffect(() => {
if (props.isResearchModeEnabled) {
setUseResearchMode(props.isResearchModeEnabled);
}
}, [props.isResearchModeEnabled]);
function onSendMessage() { function onSendMessage() {
if (imageUploaded) { if (imageUploaded) {
setImageUploaded(false); setImageUploaded(false);
@ -454,7 +458,7 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
onChange={handleFileChange} onChange={handleFileChange}
style={{ display: "none" }} style={{ display: "none" }}
/> />
<div className="flex items-end pb-4"> <div className="flex items-center">
<Button <Button
variant={"ghost"} variant={"ghost"}
className="!bg-none p-0 m-2 h-auto text-3xl rounded-full text-gray-300 hover:text-gray-500" className="!bg-none p-0 m-2 h-auto text-3xl rounded-full text-gray-300 hover:text-gray-500"
@ -464,7 +468,7 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
<Paperclip className="w-8 h-8" /> <Paperclip className="w-8 h-8" />
</Button> </Button>
</div> </div>
<div className="flex-grow flex flex-col w-full gap-1.5 relative pb-2"> <div className="flex-grow flex flex-col w-full gap-1.5 relative">
<div className="flex items-center gap-2 overflow-x-auto"> <div className="flex items-center gap-2 overflow-x-auto">
{imageUploaded && {imageUploaded &&
imagePaths.map((path, index) => ( imagePaths.map((path, index) => (
@ -490,8 +494,8 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
</div> </div>
<Textarea <Textarea
ref={chatInputRef} ref={chatInputRef}
className={`border ${props.agentColor ? convertColorToBorderClass(props.agentColor) : "border-orange-300"} focus:border-none className={`border-none focus:border-none
focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${convertColorToRingClass(props.agentColor)} focus:outline-none focus-visible:ring-transparent
w-full h-16 min-h-16 max-h-[128px] md:py-4 rounded-lg resize-none dark:bg-neutral-700 w-full h-16 min-h-16 max-h-[128px] md:py-4 rounded-lg resize-none dark:bg-neutral-700
${props.isMobileWidth ? "text-md" : "text-lg"}`} ${props.isMobileWidth ? "text-md" : "text-lg"}`}
placeholder="Type / to see a list of commands" placeholder="Type / to see a list of commands"
@ -510,7 +514,7 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
disabled={props.sendDisabled || recording} disabled={props.sendDisabled || recording}
/> />
</div> </div>
<div className="flex items-end pb-4"> <div className="flex items-center">
{recording ? ( {recording ? (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
@ -569,7 +573,7 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="ghost" variant="ghost"
className="float-right justify-center gap-1 flex items-center p-0 mr-2" className="float-right justify-center gap-1 flex items-center p-1.5 mr-2 h-fit"
onClick={() => { onClick={() => {
setUseResearchMode(!useResearchMode); setUseResearchMode(!useResearchMode);
chatInputRef?.current?.focus(); chatInputRef?.current?.focus();
@ -585,7 +589,7 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
)} )}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent className="text-xs">
Research Mode allows you to get more deeply researched, detailed Research Mode allows you to get more deeply researched, detailed
responses. Response times may be longer. responses. Response times may be longer.
</TooltipContent> </TooltipContent>

View file

@ -14,25 +14,21 @@ interface SuggestionCardProps {
export default function SuggestionCard(data: SuggestionCardProps) { export default function SuggestionCard(data: SuggestionCardProps) {
const bgColors = converColorToBgGradient(data.color); const bgColors = converColorToBgGradient(data.color);
const cardClassName = `${styles.card} ${bgColors} md:w-full md:h-fit sm:w-full h-fit md:w-[200px] md:h-[200px] cursor-pointer`; const cardClassName = `${styles.card} ${bgColors} md:w-full md:h-fit sm:w-full h-fit md:w-[200px] md:h-[180px] cursor-pointer md:p-2`;
const titleClassName = `${styles.title} pt-2 dark:text-white dark:font-bold`;
const descriptionClassName = `${styles.text} dark:text-white`; const descriptionClassName = `${styles.text} dark:text-white`;
const cardContent = ( const cardContent = (
<Card className={cardClassName}> <Card className={cardClassName}>
<CardHeader className="m-0 p-2 pb-1 relative"> <div className="flex">
<div className="flex flex-row md:flex-col"> <CardContent className="m-0 p-2">
{convertSuggestionTitleToIconClass(data.title, data.color.toLowerCase())} {convertSuggestionTitleToIconClass(data.title, data.color.toLowerCase())}
<CardTitle className={titleClassName}>{data.title}</CardTitle>
</div>
</CardHeader>
<CardContent className="m-0 p-2 pr-4 pt-1">
<CardDescription <CardDescription
className={`${descriptionClassName} sm:line-clamp-2 md:line-clamp-4`} className={`${descriptionClassName} sm:line-clamp-2 md:line-clamp-4 pt-1`}
> >
{data.body} {data.body}
</CardDescription> </CardDescription>
</CardContent> </CardContent>
</div>
</Card> </Card>
); );

View file

@ -1,12 +1,9 @@
.card { .card {
padding: 0.5rem;
margin: 0.05rem;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
.title { .title {
font-size: 1.0rem; font-size: 1rem;
} }
.text { .text {

View file

@ -47,24 +47,24 @@ const DEFAULT_COLOR = "orange";
export function convertSuggestionTitleToIconClass(title: string, color: string) { export function convertSuggestionTitleToIconClass(title: string, color: string) {
if (title === SuggestionType.Automation) if (title === SuggestionType.Automation)
return getIconFromIconName("Robot", color, "w-8", "h-8"); return getIconFromIconName("Robot", color, "w-6", "h-6");
if (title === SuggestionType.Paint) return getIconFromIconName("Palette", color, "w-8", "h-8"); if (title === SuggestionType.Paint) return getIconFromIconName("Palette", color, "w-6", "h-6");
if (title === SuggestionType.PopCulture) if (title === SuggestionType.PopCulture)
return getIconFromIconName("Confetti", color, "w-8", "h-8"); return getIconFromIconName("Confetti", color, "w-6", "h-6");
if (title === SuggestionType.Travel) return getIconFromIconName("Jeep", color, "w-8", "h-8"); if (title === SuggestionType.Travel) return getIconFromIconName("Jeep", color, "w-6", "h-6");
if (title === SuggestionType.Learning) return getIconFromIconName("Book", color, "w-8", "h-8"); if (title === SuggestionType.Learning) return getIconFromIconName("Book", color, "w-6", "h-6");
if (title === SuggestionType.Health) if (title === SuggestionType.Health)
return getIconFromIconName("Asclepius", color, "w-8", "h-8"); return getIconFromIconName("Asclepius", color, "w-6", "h-6");
if (title === SuggestionType.Fun) return getIconFromIconName("Island", color, "w-8", "h-8"); if (title === SuggestionType.Fun) return getIconFromIconName("Island", color, "w-6", "h-6");
if (title === SuggestionType.Home) return getIconFromIconName("House", color, "w-8", "h-8"); if (title === SuggestionType.Home) return getIconFromIconName("House", color, "w-6", "h-6");
if (title === SuggestionType.Language) if (title === SuggestionType.Language)
return getIconFromIconName("Translate", color, "w-8", "h-8"); return getIconFromIconName("Translate", color, "w-6", "h-6");
if (title === SuggestionType.Code) return getIconFromIconName("Code", color, "w-8", "h-8"); if (title === SuggestionType.Code) return getIconFromIconName("Code", color, "w-6", "h-6");
if (title === SuggestionType.Food) return getIconFromIconName("BowlFood", color, "w-8", "h-8"); if (title === SuggestionType.Food) return getIconFromIconName("BowlFood", color, "w-6", "h-6");
if (title === SuggestionType.Interviewing) if (title === SuggestionType.Interviewing)
return getIconFromIconName("Lectern", color, "w-8", "h-8"); return getIconFromIconName("Lectern", color, "w-6", "h-6");
if (title === SuggestionType.Finance) return getIconFromIconName("Wallet", color, "w-8", "h-8"); if (title === SuggestionType.Finance) return getIconFromIconName("Wallet", color, "w-6", "h-6");
else return getIconFromIconName("Lightbulb", color, "w-8", "h-8"); else return getIconFromIconName("Lightbulb", color, "w-6", "h-6");
} }
export const suggestionsData: Suggestion[] = [ export const suggestionsData: Suggestion[] = [

View file

@ -116,8 +116,8 @@ function ChatBodyData(props: ChatBodyDataProps) {
`What would you like to get done${nameSuffix}?`, `What would you like to get done${nameSuffix}?`,
`Hey${nameSuffix}! How can I help?`, `Hey${nameSuffix}! How can I help?`,
`Good ${timeOfDay}${nameSuffix}! What's on your mind?`, `Good ${timeOfDay}${nameSuffix}! What's on your mind?`,
`Ready to breeze through your ${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]}?`, `Ready to breeze through ${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]}?`,
`Want help navigating your ${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]} workload?`, `Let's navigate your ${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]} workload`,
]; ];
const greeting = greetings[Math.floor(Math.random() * greetings.length)]; const greeting = greetings[Math.floor(Math.random() * greetings.length)];
setGreeting(greeting); setGreeting(greeting);

View file

@ -657,6 +657,8 @@ class AgentAdapters:
@staticmethod @staticmethod
async def ais_agent_accessible(agent: Agent, user: KhojUser) -> bool: async def ais_agent_accessible(agent: Agent, user: KhojUser) -> bool:
agent = await Agent.objects.select_related("creator").aget(pk=agent.pk)
if agent.privacy_level == Agent.PrivacyLevel.PUBLIC: if agent.privacy_level == Agent.PrivacyLevel.PUBLIC:
return True return True
if agent.creator == user: if agent.creator == user:
@ -866,9 +868,13 @@ class ConversationAdapters:
agent = await AgentAdapters.aget_readonly_agent_by_slug(agent_slug, user) agent = await AgentAdapters.aget_readonly_agent_by_slug(agent_slug, user)
if agent is None: if agent is None:
raise HTTPException(status_code=400, detail="No such agent currently exists.") raise HTTPException(status_code=400, detail="No such agent currently exists.")
return await Conversation.objects.acreate(user=user, client=client_application, agent=agent, title=title) return await Conversation.objects.select_related("agent", "agent__creator", "agent__chat_model").acreate(
user=user, client=client_application, agent=agent, title=title
)
agent = await AgentAdapters.aget_default_agent() agent = await AgentAdapters.aget_default_agent()
return await Conversation.objects.acreate(user=user, client=client_application, agent=agent, title=title) return await Conversation.objects.select_related("agent", "agent__creator", "agent__chat_model").acreate(
user=user, client=client_application, agent=agent, title=title
)
@staticmethod @staticmethod
def create_conversation_session( def create_conversation_session(