Nav Menu Upgrades and Minor UX Improvements (#869)

* Converted navigation menu into a dropdown menu
* Moved collapsed side panel menu icons into top row
* Auto refresh when conversation is deleted to update side panel and route back to main page if deletion is on current conversation
* Highlight the current conversation in the side panel
* Dynamic homepage messages with current day and time of day.
* `colorutils` upgraded to have more expansive tailwind color options and dynamic class name generation.
* Converted create agent button alert into shadcn `ToolTip`
* Colored lines and icons for agents in chat window
* Cleaned up border styling in dark mode
* fixed three dot menu in side panel to be more easier to click
* Add the KhojLogo import in the nav menu and use a default user profile icon when not authenticated
* Get rid of custom --box-shadow CSS variable
* Pass the agent metadat through the chat body data in order to style the send button
* Add login to the unauthenticated login view, redirecto to home if conversation history not loaded
* Set a max height for the input text area
* Simplify tailwind class names

---------

Co-authored-by: sabaimran <narmiabas@gmail.com>
This commit is contained in:
Raghav Tirumale
2024-07-27 04:42:00 -04:00
committed by GitHub
parent 8503d7a07b
commit 1685c60e3c
33 changed files with 1790 additions and 7771 deletions

View File

@@ -4,7 +4,6 @@ div.chatMessageContainer {
margin: 12px;
border-radius: 16px;
padding: 8px 16px 0 16px;
box-shadow: 0 4px 10px var(--box-shadow-color)
}
div.chatMessageWrapper {
@@ -79,7 +78,6 @@ div.chatButtons {
position: relative;
bottom: -12px;
background-color: hsla(var(--secondary));
box-shadow: 0 4px 10px var(--box-shadow-color);
}
div.chatFooter button {

View File

@@ -14,6 +14,8 @@ import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, Speak
import * as DomPurify from 'dompurify';
import { InlineLoading } from '../loading/loading';
import { convertColorToTextClass } from '@/app/common/colorUtils';
import { AgentData } from '@/app/agents/page';
const md = new markdownIt({
html: true,
@@ -72,13 +74,6 @@ export interface OnlineContextData {
peopleAlsoAsk: PeopleAlsoAsk[];
}
interface AgentData {
name: string;
avatar: string;
slug: string;
persona: string;
}
interface Intent {
type: string;
query: string;
@@ -97,6 +92,7 @@ export interface SingleChatMessage {
}
rawQuery?: string;
intent?: Intent;
agent?: AgentData;
}
export interface StreamMessage {
@@ -109,6 +105,7 @@ export interface StreamMessage {
completed: boolean;
rawQuery: string;
timestamp: string;
agent?: AgentData;
}
export interface ChatHistoryData {
@@ -147,16 +144,18 @@ interface ChatMessageProps {
customClassName?: string;
borderLeftColor?: string;
isLastMessage?: boolean;
agent?: AgentData;
}
interface TrainOfThoughtProps {
message: string;
primary: boolean;
agentColor: string;
}
function chooseIconFromHeader(header: string, iconColor: string) {
const compareHeader = header.toLowerCase();
const classNames = `inline mt-1 mr-2 ${iconColor}`;
const classNames = `inline mt-1 mr-2 ${iconColor} h-4 w-4`;
if (compareHeader.includes("understanding")) {
return <Brain className={`${classNames}`} />
}
@@ -192,7 +191,7 @@ 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(/\*\*(.*)\*\*/);
let header = extractedHeader ? extractedHeader[1] : "";
const iconColor = props.primary ? 'text-orange-400' : 'text-gray-500';
const iconColor = props.primary ? convertColorToTextClass(props.agentColor) : 'text-gray-500';
const icon = chooseIconFromHeader(header, iconColor);
let markdownRendered = DomPurify.sanitize(md.render(props.message));
return (
@@ -317,7 +316,7 @@ export default function ChatMessage(props: ChatMessageProps) {
}
function constructClasses(chatMessage: SingleChatMessage) {
let classes = [styles.chatMessageContainer];
let classes = [styles.chatMessageContainer, "shadow-md"];
classes.push(styles[chatMessage.by]);
if (props.customClassName) {
@@ -330,12 +329,9 @@ export default function ChatMessage(props: ChatMessageProps) {
function chatMessageWrapperClasses(chatMessage: SingleChatMessage) {
let classes = [styles.chatMessageWrapper];
classes.push(styles[chatMessage.by]);
if (chatMessage.by === "khoj") {
const dynamicBorderColor = `border-l-${props.borderLeftColor}`;
classes.push(`border-l-4 border-opacity-50 border-l-orange-400 ${dynamicBorderColor}`);
classes.push(`border-l-4 border-opacity-50 ${"border-l-" + props.borderLeftColor || "border-l-orange-400"}`);
}
return classes.join(' ');
}
@@ -437,7 +433,7 @@ export default function ChatMessage(props: ChatMessageProps) {
<div title={formatDate(props.chatMessage.created)} className={`text-gray-400 relative top-0 left-4`}>
{renderTimeStamp(props.chatMessage.created)}
</div>
<div className={styles.chatButtons}>
<div className={`${styles.chatButtons} shadow-sm`}>
{
(props.chatMessage.by === "khoj") &&
(