Skip to content

Commit

Permalink
feat(frontend): citations & sources (#2523)
Browse files Browse the repository at this point in the history
# Description

Please include a summary of the changes and the related issue. Please
also include relevant motivation and context.

## Checklist before requesting a review

Please delete options that are not relevant.

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have ideally added tests that prove my fix is effective or that
my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged

## Screenshots (if appropriate):

---------

Co-authored-by: Stan Girard <girard.stanislas@gmail.com>
  • Loading branch information
Zewed and StanGirard authored May 1, 2024
1 parent 2ed446f commit d2cab93
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 194 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
@use "@/styles/Radius.module.scss";
@use "@/styles/Spacings.module.scss";
@use "@/styles/Typography.module.scss";

.message_row_container {
display: flex;
flex-direction: column;
padding-right: Spacings.$spacing05;
width: 85%;
padding-block: Spacings.$spacing03;
gap: Spacings.$spacing03;

.message_row_content {
align-self: flex-end;
Expand Down Expand Up @@ -49,14 +51,63 @@
background-color: var(--background-special-0);
margin-left: 1px;
position: relative;
}

.metadata_wrapper {
display: flex;
flex-direction: column;
left: Spacings.$spacing02;
top: calc(100% + #{Spacings.$spacing03});
gap: Spacings.$spacing03;

.sources_and_citations_wrapper {
display: flex;
flex-direction: column;
gap: Spacings.$spacing03;
max-width: 100%;

.sources {
display: flex;
gap: Spacings.$spacing03;
flex-wrap: wrap;
max-width: 100%;
}

.citations {
display: flex;
flex-direction: column;
gap: Spacings.$spacing02;
padding: Spacings.$spacing03;
border: 1px solid var(--primary-0);
border-radius: Radius.$big;
font-size: Typography.$small;

.file_name_wrapper {
display: flex;
gap: Spacings.$spacing03;
align-items: center;

.box_title {
padding-block: Spacings.$spacing02;
font-weight: 600;
}

.source {
font-size: Typography.$tiny;
}
}

.box_title {
padding-block: Spacings.$spacing02;
font-weight: 600;
}
}
}

.icons_wrapper {
visibility: hidden;
position: absolute;
display: flex;
gap: Spacings.$spacing03;
left: Spacings.$spacing02;
top: calc(100% + #{Spacings.$spacing03});

.sources_icon_wrapper {
cursor: pointer;
Expand All @@ -69,7 +120,7 @@
}

&:hover {
.message_row_content {
.metadata_wrapper {
.icons_wrapper {
visibility: visible;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { useChat } from "@/app/chat/[chatId]/hooks/useChat";
import { useChatApi } from "@/lib/api/chat/useChatApi";
import { CopyButton } from "@/lib/components/ui/CopyButton";
import Icon from "@/lib/components/ui/Icon/Icon";
import { useChatContext } from "@/lib/context";
import { useDevice } from "@/lib/hooks/useDevice";
import { Source } from "@/lib/types/MessageMetadata";

import styles from "./MessageRow.module.scss";
import { Citation } from "./components/Citation/Citation";
import { MessageContent } from "./components/MessageContent/MessageContent";
import { QuestionBrain } from "./components/QuestionBrain/QuestionBrain";
import { QuestionPrompt } from "./components/QuestionPrompt/QuestionPrompt";
import { SourceCitations } from "./components/Source/Source";
import { useMessageRow } from "./hooks/useMessageRow";
import { SourceFile } from "./types/types";

type MessageRowProps = {
speaker: "user" | "assistant";
Expand All @@ -39,28 +40,52 @@ export const MessageRow = React.forwardRef(
promptName,
children,
brainId,
index,
messageId,
thumbs: initialThumbs,
lastMessage,
metadata,
}: MessageRowProps,
ref: React.Ref<HTMLDivElement>
) => {
const { handleCopy, isUserSpeaker } = useMessageRow({
speaker,
text,
});
const { setSourcesMessageIndex, sourcesMessageIndex } = useChatContext();
const { isMobile } = useDevice();
const { updateChatMessage } = useChatApi();
const { chatId } = useChat();
const [thumbs, setThumbs] = useState<boolean | undefined | null>(
initialThumbs
);
const [sourceFiles, setSourceFiles] = useState<SourceFile[]>([]);
const [selectedSourceFile, setSelectedSourceFile] =
useState<SourceFile | null>(null);

const handleSourceFileClick = (sourceFile: SourceFile) => {
setSelectedSourceFile((prev) =>
prev && prev.filename === sourceFile.filename ? null : sourceFile
);
};

useEffect(() => {
setThumbs(initialThumbs);
}, [initialThumbs]);
setSourceFiles(
metadata?.sources?.reduce((acc, source) => {
const existingSource = acc.find((s) => s.filename === source.name);
if (existingSource) {
existingSource.citations.push(source.citation);
} else {
acc.push({
filename: source.name,
file_url: source.source_url,
citations: [source.citation],
selected: false,
});
}

return acc;
}, [] as SourceFile[]) ?? []
);
}, [initialThumbs, metadata]);

const messageContent = text ?? "";

Expand Down Expand Up @@ -100,48 +125,76 @@ export const MessageRow = React.forwardRef(
}
};

const renderIcons = () => {
const renderMetadata = () => {
if (!isUserSpeaker && messageContent !== "🧠") {
return (
<div
className={`${styles.icons_wrapper} ${
className={`${styles.metadata_wrapper} ${
lastMessage ? styles.sticky : ""
}`}
>
<CopyButton handleCopy={handleCopy} size="normal" />
{!isMobile && (
<div className={styles.sources_icon_wrapper}>
<Icon
name="file"
handleHover={true}
color={sourcesMessageIndex === index ? "primary" : "black"}
size="normal"
onClick={() => {
setSourcesMessageIndex(
sourcesMessageIndex === index ? undefined : index
);
}}
/>
<div className={styles.sources_and_citations_wrapper}>
<div className={styles.sources}>
{sourceFiles.map((sourceFile, i) => (
<div
key={i}
onClick={() => handleSourceFileClick(sourceFile)}
>
<SourceCitations
sourceFile={sourceFile}
isSelected={
!!selectedSourceFile &&
selectedSourceFile.filename === sourceFile.filename
}
/>
</div>
))}
</div>
)}
<Icon
name="thumbsUp"
handleHover={true}
color={thumbs ? "primary" : "black"}
size="normal"
onClick={async () => {
await thumbsUp();
}}
/>
<Icon
name="thumbsDown"
handleHover={true}
color={thumbs === false ? "primary" : "black"}
size="normal"
onClick={async () => {
await thumbsDown();
}}
/>

{selectedSourceFile && (
<div className={styles.citations}>
<div className={styles.file_name_wrapper}>
<span className={styles.box_title}>Source:</span>
<a
href={selectedSourceFile.file_url}
target="_blank"
rel="noopener noreferrer"
>
<span className={styles.source}>
{selectedSourceFile.filename}
</span>
</a>
</div>
{selectedSourceFile.citations.map((citation, i) => (
<div key={i}>
<Citation citation={citation} />
</div>
))}
</div>
)}
</div>

<div className={styles.icons_wrapper}>
<CopyButton handleCopy={handleCopy} size="normal" />
<Icon
name="thumbsUp"
handleHover={true}
color={thumbs ? "primary" : "black"}
size="normal"
onClick={async () => {
await thumbsUp();
}}
/>
<Icon
name="thumbsDown"
handleHover={true}
color={thumbs === false ? "primary" : "black"}
size="normal"
onClick={async () => {
await thumbsDown();
}}
/>
</div>
</div>
);
}
Expand All @@ -159,10 +212,10 @@ export const MessageRow = React.forwardRef(
{children ?? (
<>
<MessageContent text={messageContent} isUser={isUserSpeaker} />
{renderIcons()}
</>
)}
</div>
{renderMetadata()}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@use "@/styles/Radius.module.scss";
@use "@/styles/Spacings.module.scss";
@use "@/styles/Typography.module.scss";

.citation_wrapper {
padding: Spacings.$spacing03;
border-radius: Radius.$normal;
background-color: var(--background-special-0);
width: 100%;
font-size: Typography.$tiny;
font-style: italic;
cursor: pointer;

.citation_header {
display: flex;
justify-content: space-between;
overflow: hidden;
width: 100%;

.citation {
&.folded {
@include Typography.EllipsisOverflow;
}
}

.icon {
visibility: hidden;
}
}

&:hover {
background-color: var(--background-special-1);

.icon {
visibility: visible;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useState } from "react";

import Icon from "@/lib/components/ui/Icon/Icon";

import styles from "./Citation.module.scss";

type CitationProps = {
citation: string;
};
export const Citation = ({ citation }: CitationProps): JSX.Element => {
const [isExpanded, setIsExpanded] = useState<boolean>(false);

const contentIndex = citation.indexOf("Content:");
const cleanedCitation = citation.substring(contentIndex);
const [, content] = cleanedCitation.split("Content:");

const handleIconClick = (event: React.MouseEvent) => {
event.stopPropagation();
setIsExpanded(!isExpanded);
};

return (
<div
className={styles.citation_wrapper}
onClick={(event) => handleIconClick(event)}
>
<div className={styles.citation_header}>
<span
className={`${styles.citation} ${!isExpanded ? styles.folded : ""}`}
>
{content}
</span>
<div className={styles.icon}>
<Icon
name={isExpanded ? "fold" : "unfold"}
size="normal"
color="black"
handleHover={true}
/>
</div>
</div>
</div>
);
};
Loading

0 comments on commit d2cab93

Please sign in to comment.