Fix, Improve Behavior, Styling of Chat View on Web (#851)

### Behavior
- Close chat sessions side panel on click open a chat session
- Show agent profile card with description when hover on agent in chat view
- Show action bar on last chat message without hover
- Show chat message action buttons without hover on mobile interfaces
- Show chat message timestamp on hover in chat view
- Show text descriptions of chat message action buttons on hover
- Render inline png, webp images generated by Khoj in chat view

### Fixes
- Do not render references with broken links in chat view
- Fix closing side panel on mobile when click open a chat session
- Only open side panel as drawer in mobile view
- Constrain chat messages to stay within view port across screen sizes

### Styling: Spacing, Sizing, Mobile Friendly
- Make Khoj icon appropriately sized and side panel arrow bold
- Conversations list should resize to take max space on side panel
- Make loading message, styling configurable. Do not show agent when no data
- Improve Train of Thought icons spacing and loading circle
- Improve mobile friendly styling of chat session side panel
- Improve styling of chat input, references UI across screen sizes
- Center cursor in chat input. See upto 2 lines for multi-line context

### Miscellaneous
- Add code formatter for web interface with prettier
This commit is contained in:
Debanjum
2024-07-15 08:39:14 -07:00
committed by GitHub
19 changed files with 484 additions and 175 deletions

View File

@@ -1,3 +1,11 @@
{
"extends": "next/core-web-vitals"
"extends": [
"next",
"next/core-web-vitals",
"plugin:prettier/recommended"
],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
yarn run lint-staged
yarn test

View File

@@ -33,7 +33,6 @@ interface ChatBodyDataProps {
isLoggedIn: boolean;
}
function ChatBodyData(props: ChatBodyDataProps) {
const searchParams = useSearchParams();
const conversationId = searchParams.get('conversationId');
@@ -119,10 +118,8 @@ export default function Chat() {
welcomeConsole();
const handleWebSocketMessage = (event: MessageEvent) => {
let chunk = event.data;
let currentMessage = messages.find(message => !message.completed);
if (!currentMessage) {
@@ -165,7 +162,6 @@ export default function Chat() {
} finally {
// no-op
}
} else {
// Update the current message with the new chunk
if (chunk && chunk.includes("### compiled references:")) {
@@ -179,7 +175,6 @@ export default function Chat() {
// If the chunk is not a JSON object, just display it as is
currentMessage.rawResponse += chunk;
}
}
};
// Update the state with the new message, currentMessage
@@ -269,7 +264,9 @@ export default function Chat() {
<SidePanel
webSocketConnected={chatWS !== null}
conversationId={conversationId}
uploadedFiles={uploadedFiles} />
uploadedFiles={uploadedFiles}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={styles.chatBox}>
<NavMenu selected="Chat" title={title} />

View File

@@ -17,14 +17,10 @@ div.agentIndicator a {
align-items: center;
}
div.agentIndicator {
padding: 10px;
}
div.trainOfThought {
border: 1px var(--border-color) solid;
border-radius: 16px;
padding: 16px;
padding: 0 16px;
margin: 12px;
box-shadow: 0 4px 10px var(--box-shadow-color);
}

View File

@@ -10,10 +10,12 @@ import { ScrollArea } from "@/components/ui/scroll-area"
import renderMathInElement from 'katex/contrib/auto-render';
import 'katex/dist/katex.min.css';
import Loading, { InlineLoading } from '../loading/loading';
import { InlineLoading } from '../loading/loading';
import { Lightbulb } from "@phosphor-icons/react";
import ProfileCard from '../profileCard/profileCard';
interface ChatResponse {
status: string;
response: ChatHistoryData;
@@ -231,6 +233,10 @@ export default function ChatHistory(props: ChatHistoryProps) {
return data.agent.name;
}
function constructAgentPersona() {
if (!data || !data.agent || !data.agent.persona) return ``;
return data.agent.persona;
}
if (!props.conversationId && !props.publicConversationSlug) {
return null;
@@ -241,7 +247,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
<div ref={ref}>
<div className={styles.chatHistory} ref={chatHistoryRef}>
<div ref={sentinelRef} style={{ height: '1px' }}>
{fetchingData && <InlineLoading />}
{fetchingData && <InlineLoading message="Loading Conversation" className='opacity-50'/>}
</div>
{(data && data.chat) && data.chat.map((chatMessage, index) => (
<ChatMessage
@@ -250,6 +256,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
chatMessage={chatMessage}
customClassName='fullHistory'
borderLeftColor='orange-500'
isLastMessage={index === data.chat.length - 1}
/>
))}
{
@@ -270,7 +277,8 @@ export default function ChatHistory(props: ChatHistoryProps) {
}
}
customClassName='fullHistory'
borderLeftColor='orange-500' />
borderLeftColor='orange-500'
/>
{
message.trainOfThought &&
constructTrainOfThought(
@@ -294,6 +302,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
}
customClassName='fullHistory'
borderLeftColor='orange-500'
isLastMessage={true}
/>
</>
)
@@ -316,14 +325,21 @@ export default function ChatHistory(props: ChatHistoryProps) {
}
customClassName='fullHistory'
borderLeftColor='orange-500'
isLastMessage={true}
/>
}
<div className={`${styles.agentIndicator}`}>
<a className='no-underline mx-2 flex text-muted-foreground' href={constructAgentLink()} target="_blank" rel="noreferrer">
<Lightbulb color='orange' weight='fill' />
<span>{constructAgentName()}</span>
</a>
</div>
{data &&
<div className={`${styles.agentIndicator} pb-4`}>
<div className="relative group mx-2 cursor-pointer">
<ProfileCard
name={constructAgentName()}
link={constructAgentLink()}
avatar={<Lightbulb color='orange' weight='fill' className="mt-1 mx-1" />}
description={constructAgentPersona()}
/>
</div>
</div>
}
</div>
</div>
</ScrollArea>

View File

@@ -133,42 +133,43 @@ export default function ChatInputArea(props: ChatInputProps) {
}
function getIconForSlashCommand(command: string) {
const className = 'h-4 w-4 mr-2';
if (command.includes('summarize')) {
return <Gps className='h-4 w-4 mr-2' />
return <Gps className={className} />
}
if (command.includes('help')) {
return <Question className='h-4 w-4 mr-2' />
return <Question className={className} />
}
if (command.includes('automation')) {
return <Robot className='h-4 w-4 mr-2' />
return <Robot className={className} />
}
if (command.includes('webpage')) {
return <Browser className='h-4 w-4 mr-2' />
return <Browser className={className} />
}
if (command.includes('notes')) {
return <Notebook className='h-4 w-4 mr-2' />
return <Notebook className={className} />
}
if (command.includes('image')) {
return <Image className='h-4 w-4 mr-2' />
return <Image className={className} />
}
if (command.includes('default')) {
return <Shapes className='h-4 w-4 mr-2' />
return <Shapes className={className} />
}
if (command.includes('general')) {
return <ChatsTeardrop className='h-4 w-4 mr-2' />
return <ChatsTeardrop className={className} />
}
if (command.includes('online')) {
return <GlobeSimple className='h-4 w-4 mr-2' />
return <GlobeSimple className={className} />
}
return <ArrowRight className='h-4 w-4 mr-2' />
return <ArrowRight className={className} />
}
return (
@@ -270,7 +271,6 @@ export default function ChatInputArea(props: ChatInputProps) {
</div>
}
<div className={`${styles.actualInputArea} flex items-center justify-between`}>
<input
type="file"
multiple={true}
@@ -287,7 +287,7 @@ export default function ChatInputArea(props: ChatInputProps) {
</Button>
<div className="grid w-full gap-1.5 relative">
<Textarea
className='border-none min-h-[20px]'
className='border-none w-full h-16 min-h-16 md:py-4 rounded-lg text-lg'
placeholder="Type / to see a list of commands"
id="message"
value={message}

View File

@@ -3,12 +3,17 @@ div.chatMessageContainer {
flex-direction: column;
margin: 12px;
border-radius: 16px;
padding: 16px;
padding: 8px 16px 0 16px;
box-shadow: 0 4px 10px var(--box-shadow-color)
}
div.chatMessageWrapper {
padding-left: 24px;
padding-left: 1rem;
padding-bottom: 1rem;
max-width: 80vw;
}
div.chatMessageWrapper p:not(:last-child) {
margin-bottom: 16px;
}
div.khojfullHistory {
@@ -17,7 +22,7 @@ div.khojfullHistory {
}
div.youfullHistory {
max-width: 80%;
max-width: 100%;
}
div.chatMessageContainer.youfullHistory {
@@ -68,10 +73,11 @@ div.chatFooter {
div.chatButtons {
display: flex;
justify-content: flex-end;
width: min-content;
border: var(--border-color) 1px solid;
border-radius: 16px;
position: relative;
bottom: -28px;
bottom: -12px;
background-color: hsla(var(--secondary));
box-shadow: 0 4px 10px var(--box-shadow-color);
}
@@ -121,11 +127,6 @@ div.trainOfThought.primary p {
@media screen and (max-width: 768px) {
div.youfullHistory {
max-width: 100%;
max-width: 90%;
}
div.chatMessageWrapper {
padding-left: 8px;
}
}

View File

@@ -10,8 +10,7 @@ import 'katex/dist/katex.min.css';
import { TeaserReferencesSection, constructAllReferences } from '../referencePanel/referencePanel';
import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, ArrowRight, SpeakerHifi } from '@phosphor-icons/react';
import { MagnifyingGlass } from '@phosphor-icons/react/dist/ssr';
import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, SpeakerHigh, MagnifyingGlass } from '@phosphor-icons/react';
import * as DomPurify from 'dompurify';
@@ -76,6 +75,7 @@ interface AgentData {
name: string;
avatar: string;
slug: string;
persona: string;
}
interface Intent {
@@ -110,7 +110,6 @@ export interface StreamMessage {
timestamp: string;
}
export interface ChatHistoryData {
chat: SingleChatMessage[];
agent: AgentData;
@@ -131,11 +130,11 @@ function sendFeedback(uquery: string, kquery: string, sentiment: string) {
function FeedbackButtons({ uquery, kquery }: { uquery: string, kquery: string }) {
return (
<div className={`${styles.feedbackButtons} flex align-middle justify-center items-center`}>
<button className={styles.thumbsUpButton} onClick={() => sendFeedback(uquery, kquery, 'positive')}>
<ThumbsUp color='hsl(var(--muted-foreground))' />
<button title="Like" className={styles.thumbsUpButton} onClick={() => sendFeedback(uquery, kquery, 'positive')}>
<ThumbsUp alt="Like Message" color='hsl(var(--muted-foreground))' />
</button>
<button className={styles.thumbsDownButton} onClick={() => sendFeedback(uquery, kquery, 'negative')}>
<ThumbsDown color='hsl(var(--muted-foreground))' />
<button title="Dislike" className={styles.thumbsDownButton} onClick={() => sendFeedback(uquery, kquery, 'negative')}>
<ThumbsDown alt="Dislike Message" color='hsl(var(--muted-foreground))' />
</button>
</div>
)
@@ -146,6 +145,7 @@ interface ChatMessageProps {
isMobileWidth: boolean;
customClassName?: string;
borderLeftColor?: string;
isLastMessage?: boolean;
}
interface TrainOfThoughtProps {
@@ -155,38 +155,38 @@ interface TrainOfThoughtProps {
function chooseIconFromHeader(header: string, iconColor: string) {
const compareHeader = header.toLowerCase();
const classNames = `inline mt-1 mr-2 ${iconColor}`;
if (compareHeader.includes("understanding")) {
return <Brain className={`inline mr-2 ${iconColor}`} />
return <Brain className={`${classNames}`} />
}
if (compareHeader.includes("generating")) {
return <Cloud className={`inline mr-2 ${iconColor}`} />;
return <Cloud className={`${classNames}`} />;
}
if (compareHeader.includes("data sources")) {
return <Folder className={`inline mr-2 ${iconColor}`} />;
return <Folder className={`${classNames}`} />;
}
if (compareHeader.includes("notes")) {
return <Folder className={`inline mr-2 ${iconColor}`} />;
return <Folder className={`${classNames}`} />;
}
if (compareHeader.includes("read")) {
return <Book className={`inline mr-2 ${iconColor}`} />;
return <Book className={`${classNames}`} />;
}
if (compareHeader.includes("search")) {
return <MagnifyingGlass className={`inline mr-2 ${iconColor}`} />;
return <MagnifyingGlass className={`${classNames}`} />;
}
if (compareHeader.includes("summary") || compareHeader.includes("summarize")) {
return <Aperture className={`inline mr-2 ${iconColor}`} />;
return <Aperture className={`${classNames}`} />;
}
return <Brain className={`inline mr-2 ${iconColor}`} />;
return <Brain className={`${classNames}`} />;
}
export function TrainOfThought(props: TrainOfThoughtProps) {
// The train of thought comes in as a markdown-formatted string. It starts with a heading delimited by two asterisks at the start and end and a colon, followed by the message. Example: **header**: status. This function will parse the message and render it as a div.
let extractedHeader = props.message.match(/\*\*(.*)\*\*/);
@@ -215,8 +215,15 @@ export default function ChatMessage(props: ChatMessageProps) {
message = message.replace(/\\\(/g, 'LEFTPAREN').replace(/\\\)/g, 'RIGHTPAREN')
.replace(/\\\[/g, 'LEFTBRACKET').replace(/\\\]/g, 'RIGHTBRACKET');
if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image2") {
message = `![generated_image](${message})\n\n${props.chatMessage.intent["inferred-queries"][0]}`
if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image") {
message = `![generated image](data:image/png;base64,${message})`;
} else if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image2") {
message = `![generated image](${message})`;
} else if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image-v3") {
message = `![generated image](data:image/webp;base64,${message})`;
}
if (props.chatMessage.intent && props.chatMessage.intent.type.includes("text-to-image") && props.chatMessage.intent["inferred-queries"]?.length > 0) {
message += `\n\n**Inferred Query**\n\n${props.chatMessage.intent["inferred-queries"][0]}`;
}
let markdownRendered = md.render(message);
@@ -224,6 +231,8 @@ export default function ChatMessage(props: ChatMessageProps) {
// Replace placeholders with LaTeX delimiters
markdownRendered = markdownRendered.replace(/LEFTPAREN/g, '\\(').replace(/RIGHTPAREN/g, '\\)')
.replace(/LEFTBRACKET/g, '\\[').replace(/RIGHTBRACKET/g, '\\]');
// Sanitize and set the rendered markdown
setMarkdownRendered(DomPurify.sanitize(markdownRendered));
}, [props.chatMessage.message]);
@@ -266,28 +275,36 @@ export default function ChatMessage(props: ChatMessageProps) {
return null;
}
function formatDate(timestamp: string) {
// Format date in HH:MM, DD MMM YYYY format
let date = new Date(timestamp + "Z");
let time_string = date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: true }).toUpperCase();
let date_string = date.toLocaleString('en-IN', { year: 'numeric', month: 'short', day: '2-digit'}).replaceAll('-', ' ');
return `${time_string} on ${date_string}`;
}
function renderTimeStamp(timestamp: string) {
if (!timestamp.endsWith('Z')) {
timestamp = timestamp + 'Z';
}
const messageDateTime = new Date(timestamp);
const currentDataTime = new Date();
const timeDiff = currentDataTime.getTime() - messageDateTime.getTime();
const currentDateTime = new Date();
const timeDiff = currentDateTime.getTime() - messageDateTime.getTime();
if (timeDiff < 60000) {
if (timeDiff < 60e3) {
return "Just now";
}
if (timeDiff < 3600000) {
if (timeDiff < 3600e3) {
// Using Math.round for closer to actual time representation
return `${Math.round(timeDiff / 60000)}m ago`;
return `${Math.round(timeDiff / 60e3)}m ago`;
}
if (timeDiff < 86400000) {
return `${Math.round(timeDiff / 3600000)}h ago`;
if (timeDiff < 86400e3) {
return `${Math.round(timeDiff / 3600e3)}h ago`;
}
return `${Math.round(timeDiff / 86400000)}d ago`;
return `${Math.round(timeDiff / 86400e3)}d ago`;
}
function constructClasses(chatMessage: SingleChatMessage) {
@@ -332,29 +349,29 @@ export default function ChatMessage(props: ChatMessageProps) {
</div>
<div className={styles.chatFooter}>
{
isHovering &&
(isHovering || props.isMobileWidth || props.isLastMessage) &&
(
<>
<div className={`text-gray-400 relative top-2 left-2`}>
<div title={formatDate(props.chatMessage.created)} className={`text-gray-400 relative top-0 left-4`}>
{renderTimeStamp(props.chatMessage.created)}
</div>
<div className={styles.chatButtons}>
{
(props.chatMessage.by === "khoj") &&
(
<button onClick={(event) => console.log("speaker")}>
<SpeakerHifi color='hsl(var(--muted-foreground))' />
<button title="Speak" onClick={(event) => console.log("speaker")}>
<SpeakerHigh alt="Speak Message" color='hsl(var(--muted-foreground))' />
</button>
)
}
<button className={`${styles.copyButton}`} onClick={() => {
<button title="Copy" className={`${styles.copyButton}`} onClick={() => {
navigator.clipboard.writeText(props.chatMessage.message);
setCopySuccess(true);
}}>
{
copySuccess ?
<Copy color='green' />
: <Copy color='hsl(var(--muted-foreground))' />
<Copy alt="Copied Message" weight="fill" color='green' />
: <Copy alt="Copy Message" color='hsl(var(--muted-foreground))' />
}
</button>
{
@@ -373,7 +390,6 @@ export default function ChatMessage(props: ChatMessageProps) {
</>
)
}
</div>
</div>
)

View File

@@ -1,22 +1,23 @@
import { CircleNotch } from '@phosphor-icons/react';
export default function Loading() {
interface LoadingProps {
className?: string;
message?: string;
}
export default function Loading(props: LoadingProps) {
return (
// NOTE: We can display usage tips here for casual learning moments.
<div className={`bg-background opacity-50 flex items-center justify-center h-screen`}>
<div>Loading <span><CircleNotch className={`inline animate-spin h-5 w-5`} /></span></div>
<div className={props.className || "bg-background opacity-50 flex items-center justify-center h-screen"}>
<div>{props.message || "Loading" } <span><CircleNotch className="inline animate-spin h-5 w-5" /></span></div>
</div>
);
}
interface InlineLoadingProps {
className?: string;
}
export function InlineLoading(props: InlineLoadingProps) {
export function InlineLoading(props: LoadingProps) {
return (
<button className={`${props.className}`}>
<CircleNotch className={`animate-spin h-5 w-5 mr-3`} />
<span>{props.message} <CircleNotch className="inline animate-spin h-5 w-5 mx-3" /></span>
</button>
)
}

View File

@@ -2,7 +2,7 @@
import styles from './navMenu.module.css';
import Link from 'next/link';
import { useAuthenticatedData, UserProfile } from '@/app/common/auth';
import { useAuthenticatedData } from '@/app/common/auth';
import { useState, useEffect } from 'react';
import {

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { ArrowRight } from '@phosphor-icons/react';
interface ProfileCardProps {
name: string;
avatar: JSX.Element;
link: string;
description?: string; // Optional description field
}
const ProfileCard: React.FC<ProfileCardProps> = ({ name, avatar, link, description }) => {
return (
<div className="relative group flex">
{avatar}
<span>{name}</span>
<div className="absolute left-0 bottom-full w-80 h-30 p-2 pb-4 bg-white border border-gray-300 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="flex items-center">
{avatar}
<span className="mr-2 mt-1 flex">
{name}
<a href={link} target="_blank" rel="noreferrer" className="mt-1 ml-2 block">
<ArrowRight weight="bold"/>
</a>
</span>
</div>
{description && (
<p className="mt-2 ml-6 text-sm text-gray-600 line-clamp-2">
{description || 'A Khoj agent'}
</p>
)}
</div>
</div>
);
};
export default ProfileCard;

View File

@@ -92,12 +92,19 @@ interface OnlineReferenceCardProps extends OnlineReferenceData {
function GenericOnlineReferenceCard(props: OnlineReferenceCardProps) {
const [isHovering, setIsHovering] = useState(false);
if (!props.link) {
if (!props.link || props.link.split(' ').length > 1) {
return null;
}
const domain = new URL(props.link).hostname;
const favicon = `https://www.google.com/s2/favicons?domain=${domain}`;
let favicon = `https://www.google.com/s2/favicons?domain=globe`;
let domain = "unknown";
try {
domain = new URL(props.link).hostname;
favicon = `https://www.google.com/s2/favicons?domain=${domain}`;
} catch (error) {
console.warn(`Error parsing domain from link: ${props.link}`);
return null;
}
const handleMouseEnter = () => {
setIsHovering(true);
@@ -269,14 +276,14 @@ export function TeaserReferencesSection(props: TeaserReferenceSectionProps) {
}
return (
<div className={`${props.isMobileWidth ? 'p-0' : 'p-4'}`}>
<div className="pt-0 px-4 pb-4 md:px-6">
<h3 className="inline-flex items-center">
References
<p className="text-gray-400 m-2">
{numReferences} sources
</p>
</h3>
<div className={`flex ${props.isMobileWidth ? 'w-[90vw]' : 'w-auto'} space-x-4 mt-2`}>
<div className={`flex flex-wrap gap-2 w-auto mt-2`}>
{
notesDataToShow.map((note, index) => {
return <NotesContextReferenceCard showFullContent={false} {...note} key={`${note.title}-${index}`} />
@@ -313,15 +320,16 @@ export default function ReferencePanel(props: ReferencePanelDataProps) {
return (
<Sheet>
<SheetTrigger
className='text-balance w-[200px] overflow-hidden break-words p-0 bg-transparent border-none text-gray-400 align-middle justify-center items-center !m-0 inline-flex'>
View references <ArrowRight className='m-1' />
className='text-balance w-auto md:w-[200px] justify-start overflow-hidden break-words p-0 bg-transparent border-none text-gray-400 align-middle items-center !m-2 inline-flex'>
View references
<ArrowRight className='m-1' />
</SheetTrigger>
<SheetContent className="overflow-y-scroll">
<SheetHeader>
<SheetTitle>References</SheetTitle>
<SheetDescription>View all references for this response</SheetDescription>
</SheetHeader>
<div className="flex flex-col w-auto gap-2 mt-2">
<div className="flex flex-wrap gap-2 w-auto mt-2">
{
props.notesReferenceCardData.map((note, index) => {
return <NotesContextReferenceCard showFullContent={true} {...note} key={`${note.title}-${index}`} />

View File

@@ -2,13 +2,12 @@
import styles from "./sidePanel.module.css";
import { Suspense, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { UserProfile, useAuthenticatedData } from "@/app/common/auth";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import Link from "next/link";
import useSWR from "swr";
import Image from "next/image";
import {
Command,
@@ -54,6 +53,7 @@ interface ChatHistory {
agent_avatar: string;
compressed: boolean;
created: string;
showSidePanel: (isEnabled: boolean) => void;
}
import {
@@ -140,7 +140,6 @@ function deleteConversation(conversationId: string) {
});
}
interface FilesMenuProps {
conversationId: string | null;
uploadedFiles: string[];
@@ -333,33 +332,37 @@ interface SessionsAndFilesProps {
function SessionsAndFiles(props: SessionsAndFilesProps) {
return (
<>
<ScrollArea className="h-[40vh]">
<ScrollAreaScrollbar orientation="vertical" className="h-full w-2.5 border-l border-l-transparent p-[1px]" />
<div className={styles.sessionsList}>
{props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).map((timeGrouping) => (
<div key={timeGrouping} className={`my-4`}>
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem] `}>
{timeGrouping}
<div>
<ScrollArea>
<ScrollAreaScrollbar orientation="vertical" className="h-full w-2.5 border-l border-l-transparent p-[1px]" />
<div className={styles.sessionsList}>
{props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).map((timeGrouping) => (
<div key={timeGrouping} className={`my-4`}>
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem]`}>
{timeGrouping}
</div>
{props.subsetOrganizedData && props.subsetOrganizedData[timeGrouping].map((chatHistory) => (
<ChatSession
created={chatHistory.created}
compressed={true}
key={chatHistory.conversation_id}
conversation_id={chatHistory.conversation_id}
slug={chatHistory.slug}
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name}
showSidePanel={props.setEnabled}
/>
))}
</div>
{props.subsetOrganizedData && props.subsetOrganizedData[timeGrouping].map((chatHistory) => (
<ChatSession
created={chatHistory.created}
compressed={true}
key={chatHistory.conversation_id}
conversation_id={chatHistory.conversation_id}
slug={chatHistory.slug}
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name} />
))}
</div>
))}
</div>
</ScrollArea>
{
(props.data && props.data.length > 5) && (
<ChatSessionsModal data={props.organizedData} />
)
}
))}
</div>
</ScrollArea>
{
(props.data && props.data.length > 5) && (
<ChatSessionsModal data={props.organizedData} showSidePanel={props.setEnabled} />
)
}
</div>
<FilesMenu conversationId={props.conversationId} uploadedFiles={props.uploadedFiles} isMobileWidth={props.isMobileWidth} />
{props.userProfile &&
<UserProfileComponent userProfile={props.userProfile} webSocketConnected={props.webSocketConnected} collapsed={false} />
@@ -527,7 +530,7 @@ function ChatSession(props: ChatHistory) {
onMouseLeave={() => setIsHovered(false)}
key={props.conversation_id}
className={`${styles.session} ${props.compressed ? styles.compressed : '!max-w-full'} ${isHovered ? `${styles.sessionHover}` : ''}`}>
<Link href={`/chat?conversationId=${props.conversation_id}`}>
<Link href={`/chat?conversationId=${props.conversation_id}`} onClick={() => props.showSidePanel(false)}>
<p className={styles.session}>{props.slug || "New Conversation 🌱"}</p>
</Link>
<ChatSessionActionMenu conversationId={props.conversation_id} />
@@ -537,21 +540,22 @@ function ChatSession(props: ChatHistory) {
interface ChatSessionsModalProps {
data: GroupedChatHistory | null;
showSidePanel: (isEnabled: boolean) => void;
}
function ChatSessionsModal({ data }: ChatSessionsModalProps) {
function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
return (
<Dialog>
<DialogTrigger
className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
<span className="mr-2">See All <ArrowRight className="h-4 w-4" /></span>
<span className="mr-2">See All <ArrowRight className="inline h-4 w-4" weight="bold"/></span>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>All Conversations</DialogTitle>
<DialogDescription
className="p-0">
<ScrollArea className="h-[500px] p-4">
<ScrollArea className="h-[500px] py-4">
{data && Object.keys(data).map((timeGrouping) => (
<div key={timeGrouping}>
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem] `}>
@@ -565,7 +569,8 @@ function ChatSessionsModal({ data }: ChatSessionsModalProps) {
conversation_id={chatHistory.conversation_id}
slug={chatHistory.slug}
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name} />
agent_name={chatHistory.agent_name}
showSidePanel={showSidePanel}/>
))}
</div>
))}
@@ -646,11 +651,11 @@ interface SidePanelProps {
webSocketConnected?: boolean;
conversationId: string | null;
uploadedFiles: string[];
isMobileWidth: boolean;
}
export default function SidePanel(props: SidePanelProps) {
const [data, setData] = useState<ChatHistory[] | null>(null);
const [organizedData, setOrganizedData] = useState<GroupedChatHistory | null>(null);
const [subsetOrganizedData, setSubsetOrganizedData] = useState<GroupedChatHistory | null>(null);
@@ -659,9 +664,6 @@ export default function SidePanel(props: SidePanelProps) {
const authenticatedData = useAuthenticatedData();
const { data: chatSessions } = useChatSessionsFetchRequest(authenticatedData ? `/api/chat/sessions` : '');
const [isMobileWidth, setIsMobileWidth] = useState(false);
useEffect(() => {
if (chatSessions) {
setData(chatSessions);
@@ -693,35 +695,23 @@ export default function SidePanel(props: SidePanelProps) {
}
});
setSubsetOrganizedData(subsetOrganizedData);
setOrganizedData(groupedData);
}
}, [chatSessions]);
useEffect(() => {
if (window.innerWidth < 768) {
setIsMobileWidth(true);
}
window.addEventListener('resize', () => {
setIsMobileWidth(window.innerWidth < 768);
});
}, []);
return (
<div className={`${styles.panel} ${enabled ? styles.expanded : styles.collapsed}`}>
<div className="flex items-start justify-between">
<Image src="/khoj-logo.svg"
alt="logo"
width={40}
height={40}
/>
<div className="flex items-center justify-between">
<img src="/khoj-logo.svg" alt="logo" className="w-16"/>
{
authenticatedData &&
isMobileWidth ?
<Drawer>
<DrawerTrigger><ArrowRight className="h-4 w-4 mx-2" /></DrawerTrigger>
authenticatedData && props.isMobileWidth ?
<Drawer open={enabled} onOpenChange={(open) => {
if (!enabled) setEnabled(false);
setEnabled(open);
}
}>
<DrawerTrigger><ArrowRight className="h-4 w-4 mx-2" weight="bold"/></DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Sessions and Files</DrawerTitle>
@@ -737,7 +727,7 @@ export default function SidePanel(props: SidePanelProps) {
uploadedFiles={props.uploadedFiles}
userProfile={authenticatedData}
conversationId={props.conversationId}
isMobileWidth={isMobileWidth}
isMobileWidth={props.isMobileWidth}
/>
</div>
<DrawerFooter>
@@ -749,12 +739,12 @@ export default function SidePanel(props: SidePanelProps) {
</Drawer>
:
<button className={styles.button} onClick={() => setEnabled(!enabled)}>
{enabled ? <ArrowLeft className="h-4 w-4" /> : <ArrowRight className="h-4 w-4 mx-2" />}
{enabled ? <ArrowLeft className="h-4 w-4" weight="bold"/> : <ArrowRight className="h-4 w-4 mx-2" weight="bold"/>}
</button>
}
</div>
{
authenticatedData && enabled &&
authenticatedData && !props.isMobileWidth && enabled &&
<div className={`${styles.panelWrapper}`}>
<SessionsAndFiles
webSocketConnected={props.webSocketConnected}
@@ -765,7 +755,7 @@ export default function SidePanel(props: SidePanelProps) {
uploadedFiles={props.uploadedFiles}
userProfile={authenticatedData}
conversationId={props.conversationId}
isMobileWidth={isMobileWidth}
isMobileWidth={props.isMobileWidth}
/>
</div>
}

View File

@@ -6,11 +6,12 @@ div.session {
max-width: 14rem;
font-size: medium;
display: grid;
grid-template-columns: minmax(auto, 350px) 1fr;
grid-template-columns: minmax(auto, 400px) 1fr;
gap: 1rem;
}
div.compressed {
grid-template-columns: minmax(auto, 12rem) 1fr 1fr;
grid-template-columns: minmax(12rem, 100%) 1fr 1fr;
}
div.sessionHover {
@@ -83,11 +84,10 @@ div.profile {
div.panelWrapper {
display: grid;
grid-template-rows: auto 1fr auto auto;
grid-template-rows: 1fr auto auto;
height: 100%;
}
div.modalSessionsList {
position: fixed;
top: 0;
@@ -133,15 +133,16 @@ div.modalSessionsList div.session {
div.panelWrapper {
width: 100%;
padding: 0 1rem;
}
div.session.compressed {
max-width: 100%;
grid-template-columns: minmax(auto, 350px) 1fr;
grid-template-columns: minmax(auto, 300px) 1fr;
}
div.session {
max-width: 100%;
grid-template-columns: 200px 1fr;
grid-template-columns: minmax(auto, 70vw) 1fr;
}
}

View File

@@ -14,7 +14,8 @@
"watch": "nodemon --watch . --ext js,jsx,ts,tsx,css --ignore 'out/**/*' --exec 'yarn export'",
"windowswatch": "nodemon --watch . --ext js,jsx,ts,tsx,css --ignore 'out/**/*' --exec 'yarn windowsexport'",
"windowscollectstatic": "cd ..\\..\\.. && .\\.venv\\Scripts\\Activate.bat && py .\\src\\khoj\\manage.py collectstatic --noinput && .\\.venv\\Scripts\\deactivate.bat && cd ..",
"windowsexport": "yarn build && xcopy out ..\\..\\khoj\\interface\\built /E /Y && yarn windowscollectstatic"
"windowsexport": "yarn build && xcopy out ..\\..\\khoj\\interface\\built /E /Y && yarn windowscollectstatic",
"prepare": "husky"
},
"dependencies": {
"@phosphor-icons/react": "^2.1.7",
@@ -31,6 +32,7 @@
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
@@ -60,7 +62,18 @@
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.11",
"lint-staged": "^15.2.7",
"nodemon": "^3.1.3",
"prettier": "3.3.3",
"typescript": "^5"
},
"prettier": {
"tabWidth": 4
},
"lint-staged": {
"*": "yarn lint --fix"
}
}

View File

@@ -74,7 +74,10 @@ const config = {
},
},
},
plugins: [require("tailwindcss-animate")],
plugins: [
require("tailwindcss-animate"),
require('@tailwindcss/line-clamp'),
],
} satisfies Config
export default config

View File

@@ -496,6 +496,11 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@pkgr/core@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@radix-ui/number@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46"
@@ -1035,6 +1040,11 @@
"@swc/counter" "^0.1.3"
tslib "^2.4.0"
"@tailwindcss/line-clamp@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz#767cf8e5d528a5d90c9740ca66eb079f5e87d423"
integrity sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==
"@ts-morph/common@~0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.19.0.tgz#927fcd81d1bbc09c89c4a310a84577fb55f3694e"
@@ -1190,6 +1200,11 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-escapes@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f"
integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@@ -1214,7 +1229,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
ansi-styles@^6.1.0:
ansi-styles@^6.0.0, ansi-styles@^6.1.0, ansi-styles@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
@@ -1527,7 +1542,7 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^5.0.0:
chalk@^5.0.0, chalk@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
@@ -1566,6 +1581,14 @@ cli-spinners@^2.6.1:
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
cli-truncate@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a"
integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==
dependencies:
slice-ansi "^5.0.0"
string-width "^7.0.0"
client-only@0.0.1, client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
@@ -1623,6 +1646,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@^2.0.20:
version "2.0.20"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
commander@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
@@ -1638,6 +1666,11 @@ commander@^8.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commander@~12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -1714,7 +1747,7 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0"
is-data-view "^1.0.1"
debug@4, debug@^4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
debug@4, debug@^4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.4:
version "4.3.5"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==
@@ -1819,6 +1852,11 @@ electron-to-chromium@^1.4.796:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz#21b78709c5a13af5d5c688d135a22dcea7617acf"
integrity sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==
emoji-regex@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23"
integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -1995,6 +2033,11 @@ eslint-config-next@14.2.3:
eslint-plugin-react "^7.33.2"
eslint-plugin-react-hooks "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705"
eslint-config-prettier@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f"
integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==
eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9:
version "0.3.9"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
@@ -2069,6 +2112,14 @@ eslint-plugin-jsx-a11y@^6.7.1:
object.entries "^1.1.7"
object.fromentries "^2.0.7"
eslint-plugin-prettier@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1"
integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.8.6"
"eslint-plugin-react-hooks@^4.5.0 || 5.0.0-canary-7118f5dd7-20230705":
version "4.6.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
@@ -2193,6 +2244,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
eventemitter3@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
execa@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9"
@@ -2208,11 +2264,31 @@ execa@^7.0.0:
signal-exit "^3.0.7"
strip-final-newline "^3.0.0"
execa@~8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
dependencies:
cross-spawn "^7.0.3"
get-stream "^8.0.1"
human-signals "^5.0.0"
is-stream "^3.0.0"
merge-stream "^2.0.0"
npm-run-path "^5.1.0"
onetime "^6.0.0"
signal-exit "^4.1.0"
strip-final-newline "^3.0.0"
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.1.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1, fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@@ -2356,6 +2432,11 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-east-asian-width@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e"
integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
@@ -2377,6 +2458,11 @@ get-stream@^6.0.1:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-stream@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
get-symbol-description@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5"
@@ -2555,6 +2641,16 @@ human-signals@^4.3.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2"
integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==
human-signals@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
husky@^9.0.11:
version "9.0.11"
resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9"
integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -2697,6 +2793,18 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-fullwidth-code-point@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
is-fullwidth-code-point@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704"
integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==
dependencies:
get-east-asian-width "^1.0.0"
is-generator-function@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
@@ -2970,7 +3078,7 @@ lilconfig@^2.1.0:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
lilconfig@^3.0.0:
lilconfig@^3.0.0, lilconfig@~3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb"
integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==
@@ -2987,6 +3095,34 @@ linkify-it@^5.0.0:
dependencies:
uc.micro "^2.0.0"
lint-staged@^15.2.7:
version "15.2.7"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.7.tgz#97867e29ed632820c0fb90be06cd9ed384025649"
integrity sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==
dependencies:
chalk "~5.3.0"
commander "~12.1.0"
debug "~4.3.4"
execa "~8.0.1"
lilconfig "~3.1.1"
listr2 "~8.2.1"
micromatch "~4.0.7"
pidtree "~0.6.0"
string-argv "~0.3.2"
yaml "~2.4.2"
listr2@~8.2.1:
version "8.2.3"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.3.tgz#c494bb89b34329cf900e4e0ae8aeef9081d7d7a5"
integrity sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw==
dependencies:
cli-truncate "^4.0.0"
colorette "^2.0.20"
eventemitter3 "^5.0.1"
log-update "^6.0.0"
rfdc "^1.4.1"
wrap-ansi "^9.0.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
@@ -3027,6 +3163,17 @@ log-symbols@^5.1.0:
chalk "^5.0.0"
is-unicode-supported "^1.1.0"
log-update@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.0.0.tgz#0ddeb7ac6ad658c944c1de902993fce7c33f5e59"
integrity sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==
dependencies:
ansi-escapes "^6.2.0"
cli-cursor "^4.0.0"
slice-ansi "^7.0.0"
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -3085,7 +3232,7 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4, micromatch@^4.0.5:
micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5"
integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==
@@ -3470,6 +3617,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pidtree@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c"
integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -3552,6 +3704,18 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier-linter-helpers@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
dependencies:
fast-diff "^1.1.2"
prettier@3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
prompts@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
@@ -3751,6 +3915,11 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rfdc@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca"
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -3881,7 +4050,7 @@ signal-exit@^3.0.2, signal-exit@^3.0.7:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
signal-exit@^4.0.1:
signal-exit@^4.0.1, signal-exit@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
@@ -3903,6 +4072,22 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slice-ansi@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==
dependencies:
ansi-styles "^6.0.0"
is-fullwidth-code-point "^4.0.0"
slice-ansi@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9"
integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==
dependencies:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
source-map-js@^1.0.2, source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
@@ -3925,6 +4110,11 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string-argv@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
name string-width-cjs
version "4.2.3"
@@ -3944,6 +4134,15 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
string-width@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc"
integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==
dependencies:
emoji-regex "^10.3.0"
get-east-asian-width "^1.0.0"
strip-ansi "^7.1.0"
string.prototype.matchall@^4.0.11:
version "4.0.11"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a"
@@ -4005,7 +4204,7 @@ string_decoder@^1.1.1:
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
@@ -4074,6 +4273,14 @@ swr@^2.2.5:
client-only "^0.0.1"
use-sync-external-store "^1.2.0"
synckit@^0.8.6:
version "0.8.8"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7"
integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==
dependencies:
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
tailwind-merge@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.3.0.tgz#27d2134fd00a1f77eca22bcaafdd67055917d286"
@@ -4197,7 +4404,7 @@ tsconfig-paths@^4.2.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0:
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
@@ -4432,6 +4639,15 @@ wrap-ansi@^8.1.0:
string-width "^5.0.1"
strip-ansi "^7.0.1"
wrap-ansi@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e"
integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==
dependencies:
ansi-styles "^6.2.1"
string-width "^7.0.0"
strip-ansi "^7.1.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -4442,7 +4658,7 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^2.3.4:
yaml@^2.3.4, yaml@~2.4.2:
version "2.4.5"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e"
integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==

View File

@@ -212,6 +212,7 @@ def chat_history(
"name": conversation.agent.name,
"avatar": conversation.agent.avatar,
"isCreator": conversation.agent.creator == user,
"persona": conversation.agent.personality,
}
meta_log = conversation.conversation_log
@@ -266,6 +267,7 @@ def get_shared_chat(
"name": conversation.agent.name,
"avatar": conversation.agent.avatar,
"isCreator": conversation.agent.creator == user,
"persona": conversation.agent.personality,
}
meta_log = conversation.conversation_log