Update citations in shared chat display (#3487)

* update shared chat display

* Change Copy

* fix icon

* remove secret!

---------

Co-authored-by: Yuhong Sun <yuhongsun96@gmail.com>
This commit is contained in:
pablonyx
2024-12-19 17:48:29 -08:00
committed by GitHub
parent 59c774353a
commit 9011b8a139
17 changed files with 345 additions and 127 deletions

View File

@ -182,12 +182,15 @@ def get_chat_session(
description=chat_session.description, description=chat_session.description,
persona_id=chat_session.persona_id, persona_id=chat_session.persona_id,
persona_name=chat_session.persona.name if chat_session.persona else None, persona_name=chat_session.persona.name if chat_session.persona else None,
persona_icon_color=chat_session.persona.icon_color
if chat_session.persona
else None,
persona_icon_shape=chat_session.persona.icon_shape
if chat_session.persona
else None,
current_alternate_model=chat_session.current_alternate_model, current_alternate_model=chat_session.current_alternate_model,
messages=[ messages=[
translate_db_message_to_chat_message_detail( translate_db_message_to_chat_message_detail(msg) for msg in session_messages
msg, remove_doc_content=is_shared # if shared, don't leak doc content
)
for msg in session_messages
], ],
time_created=chat_session.time_created, time_created=chat_session.time_created,
shared_status=chat_session.shared_status, shared_status=chat_session.shared_status,

View File

@ -225,6 +225,8 @@ class ChatSessionDetailResponse(BaseModel):
description: str | None description: str | None
persona_id: int | None = None persona_id: int | None = None
persona_name: str | None persona_name: str | None
persona_icon_color: str | None
persona_icon_shape: int | None
messages: list[ChatMessageDetail] messages: list[ChatMessageDetail]
time_created: datetime time_created: datetime
shared_status: ChatSessionSharedStatus shared_status: ChatSessionSharedStatus

View File

@ -2105,6 +2105,7 @@ export function ChatPage({
} }
/> />
)} )}
{sharingModalVisible && chatSessionIdRef.current !== null && ( {sharingModalVisible && chatSessionIdRef.current !== null && (
<ShareChatSessionModal <ShareChatSessionModal
message={message} message={message}

View File

@ -16,6 +16,7 @@ interface DocumentDisplayProps {
isSelected: boolean; isSelected: boolean;
handleSelect: (documentId: string) => void; handleSelect: (documentId: string) => void;
tokenLimitReached: boolean; tokenLimitReached: boolean;
hideSelection?: boolean;
setPresentingDocument: Dispatch<SetStateAction<OnyxDocument | null>>; setPresentingDocument: Dispatch<SetStateAction<OnyxDocument | null>>;
} }
@ -62,6 +63,7 @@ export function ChatDocumentDisplay({
closeSidebar, closeSidebar,
document, document,
modal, modal,
hideSelection,
isSelected, isSelected,
handleSelect, handleSelect,
tokenLimitReached, tokenLimitReached,
@ -76,7 +78,9 @@ export function ChatDocumentDisplay({
const hasMetadata = const hasMetadata =
document.updated_at || Object.keys(document.metadata).length > 0; document.updated_at || Object.keys(document.metadata).length > 0;
return ( return (
<div className={`opacity-100 ${modal ? "w-[90vw]" : "w-full"}`}> <div
className={`max-w-[400px] opacity-100 ${modal ? "w-[90vw]" : "w-full"}`}
>
<div <div
className={`flex relative flex-col gap-0.5 rounded-xl mx-2 my-1 ${ className={`flex relative flex-col gap-0.5 rounded-xl mx-2 my-1 ${
isSelected ? "bg-gray-200" : "hover:bg-background-125" isSelected ? "bg-gray-200" : "hover:bg-background-125"
@ -115,7 +119,7 @@ export function ChatDocumentDisplay({
)} )}
</div> </div>
<div className="absolute top-2 right-2"> <div className="absolute top-2 right-2">
{!isInternet && ( {!isInternet && !hideSelection && (
<DocumentSelector <DocumentSelector
isSelected={isSelected} isSelected={isSelected}
handleSelect={() => handleSelect(document.document_id)} handleSelect={() => handleSelect(document.document_id)}

View File

@ -17,7 +17,7 @@ import { SourceSelector } from "../shared_chat_search/SearchFilters";
import { XIcon } from "@/components/icons/icons"; import { XIcon } from "@/components/icons/icons";
interface ChatFiltersProps { interface ChatFiltersProps {
filterManager: FilterManager; filterManager?: FilterManager;
closeSidebar: () => void; closeSidebar: () => void;
selectedMessage: Message | null; selectedMessage: Message | null;
selectedDocuments: OnyxDocument[] | null; selectedDocuments: OnyxDocument[] | null;
@ -27,6 +27,7 @@ interface ChatFiltersProps {
maxTokens: number; maxTokens: number;
initialWidth: number; initialWidth: number;
isOpen: boolean; isOpen: boolean;
isSharedChat?: boolean;
modal: boolean; modal: boolean;
ccPairs: CCPairBasicInfo[]; ccPairs: CCPairBasicInfo[];
tags: Tag[]; tags: Tag[];
@ -48,6 +49,7 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
selectedDocumentTokens, selectedDocumentTokens,
maxTokens, maxTokens,
initialWidth, initialWidth,
isSharedChat,
isOpen, isOpen,
ccPairs, ccPairs,
tags, tags,
@ -79,13 +81,14 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
const dedupedDocuments = removeDuplicateDocs(currentDocuments || []); const dedupedDocuments = removeDuplicateDocs(currentDocuments || []);
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75; const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
console.log("SELECTED MESSAGE is", selectedMessage);
const hasSelectedDocuments = selectedDocumentIds.length > 0; const hasSelectedDocuments = selectedDocumentIds.length > 0;
return ( return (
<div <div
id="onyx-chat-sidebar" id="onyx-chat-sidebar"
className={`relative max-w-full ${ className={`relative bg-background max-w-full ${
!modal ? "border-l h-full border-sidebar-border" : "" !modal ? "border-l h-full border-sidebar-border" : ""
}`} }`}
onClick={(e) => { onClick={(e) => {
@ -122,10 +125,10 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
<div className="overflow-y-auto -mx-1 sm:mx-0 flex-grow gap-y-0 default-scrollbar dark-scrollbar flex flex-col"> <div className="overflow-y-auto -mx-1 sm:mx-0 flex-grow gap-y-0 default-scrollbar dark-scrollbar flex flex-col">
{showFilters ? ( {showFilters ? (
<SourceSelector <SourceSelector
{...filterManager!}
modal={modal} modal={modal}
tagsOnLeft={true} tagsOnLeft={true}
filtersUntoggled={false} filtersUntoggled={false}
{...filterManager}
availableDocumentSets={documentSets} availableDocumentSets={documentSets}
existingSources={ccPairs.map((ccPair) => ccPair.source)} existingSources={ccPairs.map((ccPair) => ccPair.source)}
availableTags={tags} availableTags={tags}
@ -157,6 +160,7 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
)! )!
); );
}} }}
hideSelection={isSharedChat}
tokenLimitReached={tokenLimitReached} tokenLimitReached={tokenLimitReached}
/> />
</div> </div>

View File

@ -102,6 +102,8 @@ export interface BackendChatSession {
description: string; description: string;
persona_id: number; persona_id: number;
persona_name: string; persona_name: string;
persona_icon_color: string | null;
persona_icon_shape: number | null;
messages: BackendMessage[]; messages: BackendMessage[];
time_created: string; time_created: string;
shared_status: ChatSessionSharedStatus; shared_status: ChatSessionSharedStatus;

View File

@ -1,6 +1,6 @@
import { Citation } from "@/components/search/results/Citation"; import { Citation } from "@/components/search/results/Citation";
import { WebResultIcon } from "@/components/WebResultIcon"; import { WebResultIcon } from "@/components/WebResultIcon";
import { LoadedOnyxDocument } from "@/lib/search/interfaces"; import { LoadedOnyxDocument, OnyxDocument } from "@/lib/search/interfaces";
import { getSourceMetadata, SOURCE_METADATA_MAP } from "@/lib/sources"; import { getSourceMetadata, SOURCE_METADATA_MAP } from "@/lib/sources";
import { ValidSources } from "@/lib/types"; import { ValidSources } from "@/lib/types";
import React, { memo } from "react"; import React, { memo } from "react";
@ -9,7 +9,15 @@ import { SlackIcon } from "@/components/icons/icons";
import { SourceIcon } from "@/components/SourceIcon"; import { SourceIcon } from "@/components/SourceIcon";
export const MemoizedAnchor = memo( export const MemoizedAnchor = memo(
({ docs, updatePresentingDocument, children }: any) => { ({
docs,
updatePresentingDocument,
children,
}: {
docs?: OnyxDocument[] | null;
updatePresentingDocument: (doc: OnyxDocument) => void;
children: React.ReactNode;
}) => {
const value = children?.toString(); const value = children?.toString();
if (value?.startsWith("[") && value?.endsWith("]")) { if (value?.startsWith("[") && value?.endsWith("]")) {
const match = value.match(/\[(\d+)\]/); const match = value.match(/\[(\d+)\]/);
@ -21,9 +29,11 @@ export const MemoizedAnchor = memo(
? new URL(associatedDoc.link).origin + "/favicon.ico" ? new URL(associatedDoc.link).origin + "/favicon.ico"
: ""; : "";
const icon = ( const icon =
<SourceIcon sourceType={associatedDoc?.source_type} iconSize={18} /> (associatedDoc && (
); <SourceIcon sourceType={associatedDoc?.source_type} iconSize={18} />
)) ||
null;
return ( return (
<MemoizedLink <MemoizedLink

View File

@ -322,7 +322,7 @@ export const AIMessage = ({
const anchorCallback = useCallback( const anchorCallback = useCallback(
(props: any) => ( (props: any) => (
<MemoizedAnchor <MemoizedAnchor
updatePresentingDocument={setPresentingDocument} updatePresentingDocument={setPresentingDocument!}
docs={docs} docs={docs}
> >
{props.children} {props.children}
@ -378,6 +378,7 @@ export const AIMessage = ({
onMessageSelection && onMessageSelection &&
otherMessagesCanSwitchTo && otherMessagesCanSwitchTo &&
otherMessagesCanSwitchTo.length > 1; otherMessagesCanSwitchTo.length > 1;
return ( return (
<div <div
id="onyx-ai-message" id="onyx-ai-message"
@ -402,21 +403,16 @@ export const AIMessage = ({
<div className="max-w-message-max break-words"> <div className="max-w-message-max break-words">
{!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME ? ( {!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME ? (
<> <>
{query !== undefined && {query !== undefined && !retrievalDisabled && (
handleShowRetrieved !== undefined && <div className="mb-1">
!retrievalDisabled && ( <SearchSummary
<div className="mb-1"> index={index || 0}
<SearchSummary query={query}
index={index || 0} finished={toolCall?.tool_result != undefined}
query={query} handleSearchQueryEdit={handleSearchQueryEdit}
finished={toolCall?.tool_result != undefined} />
hasDocs={hasDocs || false} </div>
messageId={messageId} )}
handleShowRetrieved={handleShowRetrieved}
handleSearchQueryEdit={handleSearchQueryEdit}
/>
</div>
)}
{handleForceSearch && {handleForceSearch &&
content && content &&
query === undefined && query === undefined &&

View File

@ -43,18 +43,12 @@ export function ShowHideDocsButton({
export function SearchSummary({ export function SearchSummary({
index, index,
query, query,
hasDocs,
finished, finished,
messageId,
handleShowRetrieved,
handleSearchQueryEdit, handleSearchQueryEdit,
}: { }: {
index: number; index: number;
finished: boolean; finished: boolean;
query: string; query: string;
hasDocs: boolean;
messageId: number | null;
handleShowRetrieved: (messageId: number | null) => void;
handleSearchQueryEdit?: (query: string) => void; handleSearchQueryEdit?: (query: string) => void;
}) { }) {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);

View File

@ -34,7 +34,7 @@ async function generateShareLink(chatSessionId: string) {
return null; return null;
} }
async function generateCloneLink( async function generateSeedLink(
message?: string, message?: string,
assistantId?: number, assistantId?: number,
modelOverride?: LlmOverride modelOverride?: LlmOverride
@ -115,7 +115,7 @@ export function ShareChatSessionModal({
{shareLink ? ( {shareLink ? (
<div> <div>
<Text> <Text>
This chat session is currently shared. Anyone at your This chat session is currently shared. Anyone in your
organization can view the message history using the following organization can view the message history using the following
link: link:
</Text> </Text>
@ -157,10 +157,8 @@ export function ShareChatSessionModal({
) : ( ) : (
<div> <div>
<Callout type="warning" title="Warning" className="mb-4"> <Callout type="warning" title="Warning" className="mb-4">
Ensure that all content in the chat is safe to share with the Please make sure that all content in this chat is safe to
whole organization. The content of the retrieved documents share with the whole organization.
will not be visible, but the names of cited documents as well
as the AI and human messages will be visible.
</Callout> </Callout>
<div className="flex w-full justify-between"> <div className="flex w-full justify-between">
<Button <Button
@ -194,10 +192,9 @@ export function ShareChatSessionModal({
<Separator className="my-4" /> <Separator className="my-4" />
<div className="mb-4"> <div className="mb-4">
<Callout type="notice" title="Clone Chat"> <Callout type="notice" title="Seed New Chat">
Generate a link to clone this chat session with the current query. Generate a link to a new chat session with the same settings as
This allows others to start a new chat with the same initial this chat (including the assistant and model).
message and settings.
</Callout> </Callout>
</div> </div>
<div className="flex w-full justify-between"> <div className="flex w-full justify-between">
@ -207,18 +204,18 @@ export function ShareChatSessionModal({
// NOTE: for "insecure" non-https setup, the `navigator.clipboard.writeText` may fail // NOTE: for "insecure" non-https setup, the `navigator.clipboard.writeText` may fail
// as the browser may not allow the clipboard to be accessed. // as the browser may not allow the clipboard to be accessed.
try { try {
const cloneLink = await generateCloneLink( const seedLink = await generateSeedLink(
message, message,
assistantId, assistantId,
modelOverride modelOverride
); );
if (!cloneLink) { if (!seedLink) {
setPopup({ setPopup({
message: "Failed to generate clone link", message: "Failed to generate seed link",
type: "error", type: "error",
}); });
} else { } else {
navigator.clipboard.writeText(cloneLink); navigator.clipboard.writeText(seedLink);
setPopup({ setPopup({
message: "Link copied to clipboard!", message: "Link copied to clipboard!",
type: "success", type: "success",
@ -232,7 +229,7 @@ export function ShareChatSessionModal({
size="sm" size="sm"
variant="secondary" variant="secondary"
> >
Generate and Copy Clone Link Generate and Copy Seed Link
</Button> </Button>
</div> </div>
</> </>

View File

@ -6,6 +6,7 @@ import { BackendChatSession } from "../../interfaces";
import { import {
buildLatestMessageChain, buildLatestMessageChain,
getCitedDocumentsFromMessage, getCitedDocumentsFromMessage,
getHumanAndAIMessageFromMessageNumber,
processRawChatHistory, processRawChatHistory,
} from "../../lib"; } from "../../lib";
import { AIMessage, HumanMessage } from "../../message/Messages"; import { AIMessage, HumanMessage } from "../../message/Messages";
@ -19,8 +20,17 @@ import { Persona } from "@/app/admin/assistants/interfaces";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { OnyxDocument } from "@/lib/search/interfaces"; import { OnyxDocument } from "@/lib/search/interfaces";
import TextView from "@/components/chat_search/TextView"; import TextView from "@/components/chat_search/TextView";
import { ChatFilters } from "../../documentSidebar/ChatFilters";
import { Modal } from "@/components/Modal";
import FunctionalHeader from "@/components/chat_search/Header";
import FixedLogo from "../../shared_chat_search/FixedLogo";
import { useDocumentSelection } from "../../useDocumentSelection";
function BackToOnyxButton() { function BackToOnyxButton({
documentSidebarToggled,
}: {
documentSidebarToggled: boolean;
}) {
const router = useRouter(); const router = useRouter();
const enterpriseSettings = useContext(SettingsContext)?.enterpriseSettings; const enterpriseSettings = useContext(SettingsContext)?.enterpriseSettings;
@ -31,6 +41,17 @@ function BackToOnyxButton() {
Back to {enterpriseSettings?.application_name || "Onyx Chat"} Back to {enterpriseSettings?.application_name || "Onyx Chat"}
</Button> </Button>
</div> </div>
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
overflow-y-hidden
transition-all
duration-300
ease-in-out
${documentSidebarToggled ? "w-[400px]" : "w-[0px]"}
`}
></div>
</div> </div>
); );
} }
@ -42,10 +63,18 @@ export function SharedChatDisplay({
chatSession: BackendChatSession | null; chatSession: BackendChatSession | null;
persona: Persona; persona: Persona;
}) { }) {
const settings = useContext(SettingsContext);
const [documentSidebarToggled, setDocumentSidebarToggled] = useState(false);
const [selectedMessageForDocDisplay, setSelectedMessageForDocDisplay] =
useState<number | null>(null);
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const [presentingDocument, setPresentingDocument] = const [presentingDocument, setPresentingDocument] =
useState<OnyxDocument | null>(null); useState<OnyxDocument | null>(null);
const toggleDocumentSidebar = () => {
setDocumentSidebarToggled(!documentSidebarToggled);
};
useEffect(() => { useEffect(() => {
Prism.highlightAll(); Prism.highlightAll();
setIsReady(true); setIsReady(true);
@ -58,7 +87,7 @@ export function SharedChatDisplay({
Did not find a shared chat with the specified ID. Did not find a shared chat with the specified ID.
</Callout> </Callout>
</div> </div>
<BackToOnyxButton /> <BackToOnyxButton documentSidebarToggled={documentSidebarToggled} />
</div> </div>
); );
} }
@ -75,62 +104,215 @@ export function SharedChatDisplay({
onClose={() => setPresentingDocument(null)} onClose={() => setPresentingDocument(null)}
/> />
)} )}
<div className="w-full h-[100dvh] overflow-hidden"> {documentSidebarToggled && settings?.isMobile && (
<div className="flex max-h-full overflow-hidden pb-[72px]"> <div className="md:hidden">
<div className="flex w-full overflow-hidden overflow-y-scroll"> <Modal noPadding noScroll>
<div className="w-full h-full flex-col flex max-w-message-max mx-auto"> <ChatFilters
<div className="px-5 pt-8"> isSharedChat={true}
<h1 className="text-3xl text-strong font-bold"> selectedMessage={
{chatSession.description || selectedMessageForDocDisplay
`Chat ${chatSession.chat_session_id}`} ? messages.find(
</h1> (message) =>
<p className="text-emphasis"> message.messageId === selectedMessageForDocDisplay
{humanReadableFormat(chatSession.time_created)} ) || null
</p> : null
}
toggleDocumentSelection={() => {
setDocumentSidebarToggled(true);
}}
selectedDocuments={[]}
clearSelectedDocuments={() => {}}
selectedDocumentTokens={0}
maxTokens={0}
initialWidth={400}
isOpen={true}
setPresentingDocument={setPresentingDocument}
modal={true}
ccPairs={[]}
tags={[]}
documentSets={[]}
showFilters={false}
closeSidebar={() => {
setDocumentSidebarToggled(false);
}}
/>
</Modal>
</div>
)}
<Separator /> <div className="fixed inset-0 flex flex-col text-default">
<div className="h-[100dvh] px-2 overflow-y-hidden">
<div className="w-full h-[100dvh] flex flex-col overflow-hidden">
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
fixed
right-0
z-[1000]
bg-background
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
bg-transparent
transition-all
bg-opacity-80
duration-300
ease-in-out
h-full
${documentSidebarToggled ? "w-[400px]" : "w-[0px]"}
`}
>
<ChatFilters
modal={false}
isSharedChat={true}
selectedMessage={
selectedMessageForDocDisplay
? messages.find(
(message) =>
message.messageId === selectedMessageForDocDisplay
) || null
: null
}
toggleDocumentSelection={() => {
setDocumentSidebarToggled(true);
}}
clearSelectedDocuments={() => {}}
selectedDocumentTokens={0}
maxTokens={0}
initialWidth={400}
isOpen={true}
setPresentingDocument={setPresentingDocument}
ccPairs={[]}
tags={[]}
documentSets={[]}
showFilters={false}
closeSidebar={() => {
setDocumentSidebarToggled(false);
}}
selectedDocuments={[]}
/>
</div> </div>
{isReady ? ( )}
<div className="w-full pb-16"> <div className="flex mobile:hidden max-h-full overflow-hidden ">
{messages.map((message) => { <FunctionalHeader
if (message.type === "user") { documentSidebarToggled={documentSidebarToggled}
return ( sidebarToggled={false}
<HumanMessage toggleSidebar={() => {}}
shared page="chat"
key={message.messageId} reset={() => {}}
content={message.message} />
files={message.files} </div>
/>
); <div className="flex w-full overflow-hidden overflow-y-scroll">
} else { <div className="w-full h-full flex-col flex max-w-message-max mx-auto">
return ( <div className="fixed z-10 w-full ">
<AIMessage <div className="bg-background relative px-5 pt-4 w-full">
shared <h1 className="text-3xl text-strong font-bold">
setPresentingDocument={setPresentingDocument} {chatSession.description ||
currentPersona={persona} `Chat ${chatSession.chat_session_id}`}
key={message.messageId} </h1>
messageId={message.messageId} <p className=" text-emphasis">
content={message.message} {humanReadableFormat(chatSession.time_created)}
files={message.files || []} </p>
citedDocuments={getCitedDocumentsFromMessage(message)} <div
isComplete className={`
/> h-full absolute top-0 z-10 w-full sm:w-[90%] lg:w-[70%]
); bg-gradient-to-b via-50% z-[-1] from-background via-background to-background/10 flex
} transition-all duration-300 ease-in-out
})} ${
</div> documentSidebarToggled
) : ( ? "left-[200px] transform -translate-x-[calc(50%+100px)]"
<div className="grow flex-0 h-screen w-full flex items-center justify-center"> : "left-1/2 transform -translate-x-1/2"
<div className="mb-[33vh]"> }
<OnyxInitializingLoader /> `}
/>
</div> </div>
</div> </div>
{isReady ? (
<div className="w-full pt-24 pb-16">
{messages.map((message) => {
if (message.type === "user") {
return (
<HumanMessage
shared
key={message.messageId}
content={message.message}
files={message.files}
/>
);
} else {
return (
<AIMessage
shared
query={message.query || undefined}
hasDocs={
(message.documents &&
message.documents.length > 0) === true
}
toolCall={message.toolCall}
docs={message.documents}
setPresentingDocument={setPresentingDocument}
currentPersona={persona}
key={message.messageId}
messageId={message.messageId}
content={message.message}
files={message.files || []}
citedDocuments={getCitedDocumentsFromMessage(
message
)}
// toggleDocumentSelection={() => {
// setDocumentSidebarToggled(true);
// }}
toggleDocumentSelection={() => {
if (
!documentSidebarToggled ||
(documentSidebarToggled &&
selectedMessageForDocDisplay ===
message.messageId)
) {
toggleDocumentSidebar();
}
setSelectedMessageForDocDisplay(
message.messageId
);
}}
isComplete
/>
);
}
})}
</div>
) : (
<div className="grow flex-0 h-screen w-full flex items-center justify-center">
<div className="mb-[33vh]">
<OnyxInitializingLoader />
</div>
</div>
)}
</div>
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
overflow-y-hidden
transition-all
duration-300
ease-in-out
${documentSidebarToggled ? "w-[400px]" : "w-[0px]"}
`}
></div>
)} )}
</div> </div>
</div> </div>
</div>
<BackToOnyxButton /> <FixedLogo backgroundToggled={false} />
<BackToOnyxButton documentSidebarToggled={documentSidebarToggled} />
</div>
</div> </div>
</> </>
); );

View File

@ -13,8 +13,8 @@ import {
FetchAssistantsResponse, FetchAssistantsResponse,
fetchAssistantsSS, fetchAssistantsSS,
} from "@/lib/assistants/fetchAssistantsSS"; } from "@/lib/assistants/fetchAssistantsSS";
import FunctionalHeader from "@/components/chat_search/Header";
import { defaultPersona } from "@/app/admin/assistants/lib"; import { defaultPersona } from "@/app/admin/assistants/lib";
import { constructMiniFiedPersona } from "@/lib/assistantIconUtils";
async function getSharedChat(chatId: string) { async function getSharedChat(chatId: string) {
const response = await fetchSS( const response = await fetchSS(
@ -34,7 +34,6 @@ export default async function Page(props: {
getAuthTypeMetadataSS(), getAuthTypeMetadataSS(),
getCurrentUserSS(), getCurrentUserSS(),
getSharedChat(params.chatId), getSharedChat(params.chatId),
fetchAssistantsSS(),
]; ];
// catch cases where the backend is completely unreachable here // catch cases where the backend is completely unreachable here
@ -50,8 +49,6 @@ export default async function Page(props: {
const authTypeMetadata = results[0] as AuthTypeMetadata | null; const authTypeMetadata = results[0] as AuthTypeMetadata | null;
const user = results[1] as User | null; const user = results[1] as User | null;
const chatSession = results[2] as BackendChatSession | null; const chatSession = results[2] as BackendChatSession | null;
const assistantsResponse = results[3] as FetchAssistantsResponse | null;
const [availableAssistants, error] = assistantsResponse ?? [[], null];
const authDisabled = authTypeMetadata?.authType === "disabled"; const authDisabled = authTypeMetadata?.authType === "disabled";
if (!authDisabled && !user) { if (!authDisabled && !user) {
@ -61,22 +58,13 @@ export default async function Page(props: {
if (user && !user.is_verified && authTypeMetadata?.requiresVerification) { if (user && !user.is_verified && authTypeMetadata?.requiresVerification) {
return redirect("/auth/waiting-on-verification"); return redirect("/auth/waiting-on-verification");
} }
// prettier-ignore
const persona: Persona =
chatSession?.persona_id && availableAssistants?.length
? (availableAssistants.find((p) => p.id === chatSession.persona_id) ??
defaultPersona)
: (availableAssistants?.[0] ?? defaultPersona);
return ( const persona: Persona = constructMiniFiedPersona(
<div> chatSession?.persona_icon_color ?? null,
<div className="absolute top-0 z-40 w-full"> chatSession?.persona_icon_shape ?? null,
<FunctionalHeader page="shared" /> chatSession?.persona_name ?? "",
</div> chatSession?.persona_id ?? 0
<div className="flex relative bg-background text-default overflow-hidden pt-16 h-screen">
<SharedChatDisplay chatSession={chatSession} persona={persona} />
</div>
</div>
); );
return <SharedChatDisplay chatSession={chatSession} persona={persona} />;
} }

View File

@ -28,7 +28,7 @@ export function MetadataBadge({
className: flexNone ? "flex-none" : "mr-0.5 my-auto", className: flexNone ? "flex-none" : "mr-0.5 my-auto",
})} })}
<p className="max-w-[6rem] text-ellipsis overflow-hidden truncate whitespace-nowrap"> <p className="max-w-[6rem] text-ellipsis overflow-hidden truncate whitespace-nowrap">
{value}lllaasfasdf {value}
</p> </p>
</div> </div>
); );

View File

@ -35,7 +35,7 @@ const DropdownOption: React.FC<DropdownOptionProps> = ({
openInNewTab, openInNewTab,
}) => { }) => {
const content = ( const content = (
<div className="flex py-3 px-4 cursor-pointer rounded hover:bg-hover-light"> <div className="flex py-3 px-4 cursor-pointer rounded hover:bg-hover">
{icon} {icon}
{label} {label}
</div> </div>

View File

@ -33,7 +33,7 @@ export function AssistantIcon({
return ( return (
<CustomTooltip <CustomTooltip
disabled={disableToolip} disabled={disableToolip || !assistant.description}
showTick showTick
line line
wrap wrap

View File

@ -130,7 +130,7 @@ export default function FunctionalHeader({
: "") : "")
} }
> >
<div className="cursor-pointer mr-4 flex-none text-text-700 hover:text-text-600 transition-colors duration-300"> <div className="cursor-pointer ml-2 mr-4 flex-none text-text-700 hover:text-text-600 transition-colors duration-300">
<NewChatIcon size={20} /> <NewChatIcon size={20} />
</div> </div>
</Link> </Link>

View File

@ -1,3 +1,5 @@
import { Persona } from "@/app/admin/assistants/interfaces";
export interface GridShape { export interface GridShape {
encodedGrid: number; encodedGrid: number;
filledSquares: number; filledSquares: number;
@ -45,7 +47,9 @@ export function generateRandomIconShape(): GridShape {
if (grid[row][col]) { if (grid[row][col]) {
const x = col * 12; const x = col * 12;
const y = row * 12; const y = row * 12;
path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${y + 12} Z `; path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${
y + 12
} Z `;
} }
} }
} }
@ -94,7 +98,9 @@ export function createSVG(
if (grid[row][col]) { if (grid[row][col]) {
const x = col * 12; const x = col * 12;
const y = row * 12; const y = row * 12;
path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${y + 12} Z `; path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${
y + 12
} Z `;
} }
} }
} }
@ -132,3 +138,32 @@ function shuffleArray(array: any[]) {
[array[i], array[j]] = [array[j], array[i]]; [array[i], array[j]] = [array[j], array[i]];
} }
} }
// This is used for rendering a persona in the shared chat display
export const constructMiniFiedPersona = (
assistant_icon_color: string | null,
assistant_icon_shape: number | null,
name: string,
id: number
): Persona => {
return {
id,
name,
icon_color: assistant_icon_color ?? undefined,
icon_shape: assistant_icon_shape ?? undefined,
is_visible: true,
is_public: true,
display_priority: 0,
description: "",
document_sets: [],
prompts: [],
tools: [],
search_start_date: null,
owner: null,
starter_messages: null,
builtin_persona: false,
is_default_persona: false,
users: [],
groups: [],
};
};