mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-04-09 20:39:29 +02:00
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:
parent
59c774353a
commit
9011b8a139
@ -182,12 +182,15 @@ def get_chat_session(
|
||||
description=chat_session.description,
|
||||
persona_id=chat_session.persona_id,
|
||||
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,
|
||||
messages=[
|
||||
translate_db_message_to_chat_message_detail(
|
||||
msg, remove_doc_content=is_shared # if shared, don't leak doc content
|
||||
)
|
||||
for msg in session_messages
|
||||
translate_db_message_to_chat_message_detail(msg) for msg in session_messages
|
||||
],
|
||||
time_created=chat_session.time_created,
|
||||
shared_status=chat_session.shared_status,
|
||||
|
@ -225,6 +225,8 @@ class ChatSessionDetailResponse(BaseModel):
|
||||
description: str | None
|
||||
persona_id: int | None = None
|
||||
persona_name: str | None
|
||||
persona_icon_color: str | None
|
||||
persona_icon_shape: int | None
|
||||
messages: list[ChatMessageDetail]
|
||||
time_created: datetime
|
||||
shared_status: ChatSessionSharedStatus
|
||||
|
@ -2105,6 +2105,7 @@ export function ChatPage({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sharingModalVisible && chatSessionIdRef.current !== null && (
|
||||
<ShareChatSessionModal
|
||||
message={message}
|
||||
|
@ -16,6 +16,7 @@ interface DocumentDisplayProps {
|
||||
isSelected: boolean;
|
||||
handleSelect: (documentId: string) => void;
|
||||
tokenLimitReached: boolean;
|
||||
hideSelection?: boolean;
|
||||
setPresentingDocument: Dispatch<SetStateAction<OnyxDocument | null>>;
|
||||
}
|
||||
|
||||
@ -62,6 +63,7 @@ export function ChatDocumentDisplay({
|
||||
closeSidebar,
|
||||
document,
|
||||
modal,
|
||||
hideSelection,
|
||||
isSelected,
|
||||
handleSelect,
|
||||
tokenLimitReached,
|
||||
@ -76,7 +78,9 @@ export function ChatDocumentDisplay({
|
||||
const hasMetadata =
|
||||
document.updated_at || Object.keys(document.metadata).length > 0;
|
||||
return (
|
||||
<div className={`opacity-100 ${modal ? "w-[90vw]" : "w-full"}`}>
|
||||
<div
|
||||
className={`max-w-[400px] opacity-100 ${modal ? "w-[90vw]" : "w-full"}`}
|
||||
>
|
||||
<div
|
||||
className={`flex relative flex-col gap-0.5 rounded-xl mx-2 my-1 ${
|
||||
isSelected ? "bg-gray-200" : "hover:bg-background-125"
|
||||
@ -115,7 +119,7 @@ export function ChatDocumentDisplay({
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute top-2 right-2">
|
||||
{!isInternet && (
|
||||
{!isInternet && !hideSelection && (
|
||||
<DocumentSelector
|
||||
isSelected={isSelected}
|
||||
handleSelect={() => handleSelect(document.document_id)}
|
||||
|
@ -17,7 +17,7 @@ import { SourceSelector } from "../shared_chat_search/SearchFilters";
|
||||
import { XIcon } from "@/components/icons/icons";
|
||||
|
||||
interface ChatFiltersProps {
|
||||
filterManager: FilterManager;
|
||||
filterManager?: FilterManager;
|
||||
closeSidebar: () => void;
|
||||
selectedMessage: Message | null;
|
||||
selectedDocuments: OnyxDocument[] | null;
|
||||
@ -27,6 +27,7 @@ interface ChatFiltersProps {
|
||||
maxTokens: number;
|
||||
initialWidth: number;
|
||||
isOpen: boolean;
|
||||
isSharedChat?: boolean;
|
||||
modal: boolean;
|
||||
ccPairs: CCPairBasicInfo[];
|
||||
tags: Tag[];
|
||||
@ -48,6 +49,7 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
|
||||
selectedDocumentTokens,
|
||||
maxTokens,
|
||||
initialWidth,
|
||||
isSharedChat,
|
||||
isOpen,
|
||||
ccPairs,
|
||||
tags,
|
||||
@ -79,13 +81,14 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
|
||||
const dedupedDocuments = removeDuplicateDocs(currentDocuments || []);
|
||||
|
||||
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
|
||||
console.log("SELECTED MESSAGE is", selectedMessage);
|
||||
|
||||
const hasSelectedDocuments = selectedDocumentIds.length > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
id="onyx-chat-sidebar"
|
||||
className={`relative max-w-full ${
|
||||
className={`relative bg-background max-w-full ${
|
||||
!modal ? "border-l h-full border-sidebar-border" : ""
|
||||
}`}
|
||||
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">
|
||||
{showFilters ? (
|
||||
<SourceSelector
|
||||
{...filterManager!}
|
||||
modal={modal}
|
||||
tagsOnLeft={true}
|
||||
filtersUntoggled={false}
|
||||
{...filterManager}
|
||||
availableDocumentSets={documentSets}
|
||||
existingSources={ccPairs.map((ccPair) => ccPair.source)}
|
||||
availableTags={tags}
|
||||
@ -157,6 +160,7 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
|
||||
)!
|
||||
);
|
||||
}}
|
||||
hideSelection={isSharedChat}
|
||||
tokenLimitReached={tokenLimitReached}
|
||||
/>
|
||||
</div>
|
||||
|
@ -102,6 +102,8 @@ export interface BackendChatSession {
|
||||
description: string;
|
||||
persona_id: number;
|
||||
persona_name: string;
|
||||
persona_icon_color: string | null;
|
||||
persona_icon_shape: number | null;
|
||||
messages: BackendMessage[];
|
||||
time_created: string;
|
||||
shared_status: ChatSessionSharedStatus;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Citation } from "@/components/search/results/Citation";
|
||||
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 { ValidSources } from "@/lib/types";
|
||||
import React, { memo } from "react";
|
||||
@ -9,7 +9,15 @@ import { SlackIcon } from "@/components/icons/icons";
|
||||
import { SourceIcon } from "@/components/SourceIcon";
|
||||
|
||||
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();
|
||||
if (value?.startsWith("[") && value?.endsWith("]")) {
|
||||
const match = value.match(/\[(\d+)\]/);
|
||||
@ -21,9 +29,11 @@ export const MemoizedAnchor = memo(
|
||||
? new URL(associatedDoc.link).origin + "/favicon.ico"
|
||||
: "";
|
||||
|
||||
const icon = (
|
||||
<SourceIcon sourceType={associatedDoc?.source_type} iconSize={18} />
|
||||
);
|
||||
const icon =
|
||||
(associatedDoc && (
|
||||
<SourceIcon sourceType={associatedDoc?.source_type} iconSize={18} />
|
||||
)) ||
|
||||
null;
|
||||
|
||||
return (
|
||||
<MemoizedLink
|
||||
|
@ -322,7 +322,7 @@ export const AIMessage = ({
|
||||
const anchorCallback = useCallback(
|
||||
(props: any) => (
|
||||
<MemoizedAnchor
|
||||
updatePresentingDocument={setPresentingDocument}
|
||||
updatePresentingDocument={setPresentingDocument!}
|
||||
docs={docs}
|
||||
>
|
||||
{props.children}
|
||||
@ -378,6 +378,7 @@ export const AIMessage = ({
|
||||
onMessageSelection &&
|
||||
otherMessagesCanSwitchTo &&
|
||||
otherMessagesCanSwitchTo.length > 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
id="onyx-ai-message"
|
||||
@ -402,21 +403,16 @@ export const AIMessage = ({
|
||||
<div className="max-w-message-max break-words">
|
||||
{!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME ? (
|
||||
<>
|
||||
{query !== undefined &&
|
||||
handleShowRetrieved !== undefined &&
|
||||
!retrievalDisabled && (
|
||||
<div className="mb-1">
|
||||
<SearchSummary
|
||||
index={index || 0}
|
||||
query={query}
|
||||
finished={toolCall?.tool_result != undefined}
|
||||
hasDocs={hasDocs || false}
|
||||
messageId={messageId}
|
||||
handleShowRetrieved={handleShowRetrieved}
|
||||
handleSearchQueryEdit={handleSearchQueryEdit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{query !== undefined && !retrievalDisabled && (
|
||||
<div className="mb-1">
|
||||
<SearchSummary
|
||||
index={index || 0}
|
||||
query={query}
|
||||
finished={toolCall?.tool_result != undefined}
|
||||
handleSearchQueryEdit={handleSearchQueryEdit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{handleForceSearch &&
|
||||
content &&
|
||||
query === undefined &&
|
||||
|
@ -43,18 +43,12 @@ export function ShowHideDocsButton({
|
||||
export function SearchSummary({
|
||||
index,
|
||||
query,
|
||||
hasDocs,
|
||||
finished,
|
||||
messageId,
|
||||
handleShowRetrieved,
|
||||
handleSearchQueryEdit,
|
||||
}: {
|
||||
index: number;
|
||||
finished: boolean;
|
||||
query: string;
|
||||
hasDocs: boolean;
|
||||
messageId: number | null;
|
||||
handleShowRetrieved: (messageId: number | null) => void;
|
||||
handleSearchQueryEdit?: (query: string) => void;
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
@ -34,7 +34,7 @@ async function generateShareLink(chatSessionId: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function generateCloneLink(
|
||||
async function generateSeedLink(
|
||||
message?: string,
|
||||
assistantId?: number,
|
||||
modelOverride?: LlmOverride
|
||||
@ -115,7 +115,7 @@ export function ShareChatSessionModal({
|
||||
{shareLink ? (
|
||||
<div>
|
||||
<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
|
||||
link:
|
||||
</Text>
|
||||
@ -157,10 +157,8 @@ export function ShareChatSessionModal({
|
||||
) : (
|
||||
<div>
|
||||
<Callout type="warning" title="Warning" className="mb-4">
|
||||
Ensure that all content in the chat is safe to share with the
|
||||
whole organization. The content of the retrieved documents
|
||||
will not be visible, but the names of cited documents as well
|
||||
as the AI and human messages will be visible.
|
||||
Please make sure that all content in this chat is safe to
|
||||
share with the whole organization.
|
||||
</Callout>
|
||||
<div className="flex w-full justify-between">
|
||||
<Button
|
||||
@ -194,10 +192,9 @@ export function ShareChatSessionModal({
|
||||
|
||||
<Separator className="my-4" />
|
||||
<div className="mb-4">
|
||||
<Callout type="notice" title="Clone Chat">
|
||||
Generate a link to clone this chat session with the current query.
|
||||
This allows others to start a new chat with the same initial
|
||||
message and settings.
|
||||
<Callout type="notice" title="Seed New Chat">
|
||||
Generate a link to a new chat session with the same settings as
|
||||
this chat (including the assistant and model).
|
||||
</Callout>
|
||||
</div>
|
||||
<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
|
||||
// as the browser may not allow the clipboard to be accessed.
|
||||
try {
|
||||
const cloneLink = await generateCloneLink(
|
||||
const seedLink = await generateSeedLink(
|
||||
message,
|
||||
assistantId,
|
||||
modelOverride
|
||||
);
|
||||
if (!cloneLink) {
|
||||
if (!seedLink) {
|
||||
setPopup({
|
||||
message: "Failed to generate clone link",
|
||||
message: "Failed to generate seed link",
|
||||
type: "error",
|
||||
});
|
||||
} else {
|
||||
navigator.clipboard.writeText(cloneLink);
|
||||
navigator.clipboard.writeText(seedLink);
|
||||
setPopup({
|
||||
message: "Link copied to clipboard!",
|
||||
type: "success",
|
||||
@ -232,7 +229,7 @@ export function ShareChatSessionModal({
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
Generate and Copy Clone Link
|
||||
Generate and Copy Seed Link
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
@ -6,6 +6,7 @@ import { BackendChatSession } from "../../interfaces";
|
||||
import {
|
||||
buildLatestMessageChain,
|
||||
getCitedDocumentsFromMessage,
|
||||
getHumanAndAIMessageFromMessageNumber,
|
||||
processRawChatHistory,
|
||||
} from "../../lib";
|
||||
import { AIMessage, HumanMessage } from "../../message/Messages";
|
||||
@ -19,8 +20,17 @@ import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { OnyxDocument } from "@/lib/search/interfaces";
|
||||
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 enterpriseSettings = useContext(SettingsContext)?.enterpriseSettings;
|
||||
|
||||
@ -31,6 +41,17 @@ function BackToOnyxButton() {
|
||||
Back to {enterpriseSettings?.application_name || "Onyx Chat"}
|
||||
</Button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@ -42,10 +63,18 @@ export function SharedChatDisplay({
|
||||
chatSession: BackendChatSession | null;
|
||||
persona: Persona;
|
||||
}) {
|
||||
const settings = useContext(SettingsContext);
|
||||
const [documentSidebarToggled, setDocumentSidebarToggled] = useState(false);
|
||||
const [selectedMessageForDocDisplay, setSelectedMessageForDocDisplay] =
|
||||
useState<number | null>(null);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [presentingDocument, setPresentingDocument] =
|
||||
useState<OnyxDocument | null>(null);
|
||||
|
||||
const toggleDocumentSidebar = () => {
|
||||
setDocumentSidebarToggled(!documentSidebarToggled);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Prism.highlightAll();
|
||||
setIsReady(true);
|
||||
@ -58,7 +87,7 @@ export function SharedChatDisplay({
|
||||
Did not find a shared chat with the specified ID.
|
||||
</Callout>
|
||||
</div>
|
||||
<BackToOnyxButton />
|
||||
<BackToOnyxButton documentSidebarToggled={documentSidebarToggled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -75,62 +104,215 @@ export function SharedChatDisplay({
|
||||
onClose={() => setPresentingDocument(null)}
|
||||
/>
|
||||
)}
|
||||
<div className="w-full h-[100dvh] overflow-hidden">
|
||||
<div className="flex max-h-full overflow-hidden pb-[72px]">
|
||||
<div className="flex w-full overflow-hidden overflow-y-scroll">
|
||||
<div className="w-full h-full flex-col flex max-w-message-max mx-auto">
|
||||
<div className="px-5 pt-8">
|
||||
<h1 className="text-3xl text-strong font-bold">
|
||||
{chatSession.description ||
|
||||
`Chat ${chatSession.chat_session_id}`}
|
||||
</h1>
|
||||
<p className="text-emphasis">
|
||||
{humanReadableFormat(chatSession.time_created)}
|
||||
</p>
|
||||
{documentSidebarToggled && settings?.isMobile && (
|
||||
<div className="md:hidden">
|
||||
<Modal noPadding noScroll>
|
||||
<ChatFilters
|
||||
isSharedChat={true}
|
||||
selectedMessage={
|
||||
selectedMessageForDocDisplay
|
||||
? messages.find(
|
||||
(message) =>
|
||||
message.messageId === selectedMessageForDocDisplay
|
||||
) || null
|
||||
: 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>
|
||||
{isReady ? (
|
||||
<div className="w-full 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
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
currentPersona={persona}
|
||||
key={message.messageId}
|
||||
messageId={message.messageId}
|
||||
content={message.message}
|
||||
files={message.files || []}
|
||||
citedDocuments={getCitedDocumentsFromMessage(message)}
|
||||
isComplete
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grow flex-0 h-screen w-full flex items-center justify-center">
|
||||
<div className="mb-[33vh]">
|
||||
<OnyxInitializingLoader />
|
||||
)}
|
||||
<div className="flex mobile:hidden max-h-full overflow-hidden ">
|
||||
<FunctionalHeader
|
||||
documentSidebarToggled={documentSidebarToggled}
|
||||
sidebarToggled={false}
|
||||
toggleSidebar={() => {}}
|
||||
page="chat"
|
||||
reset={() => {}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full overflow-hidden overflow-y-scroll">
|
||||
<div className="w-full h-full flex-col flex max-w-message-max mx-auto">
|
||||
<div className="fixed z-10 w-full ">
|
||||
<div className="bg-background relative px-5 pt-4 w-full">
|
||||
<h1 className="text-3xl text-strong font-bold">
|
||||
{chatSession.description ||
|
||||
`Chat ${chatSession.chat_session_id}`}
|
||||
</h1>
|
||||
<p className=" text-emphasis">
|
||||
{humanReadableFormat(chatSession.time_created)}
|
||||
</p>
|
||||
<div
|
||||
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
|
||||
${
|
||||
documentSidebarToggled
|
||||
? "left-[200px] transform -translate-x-[calc(50%+100px)]"
|
||||
: "left-1/2 transform -translate-x-1/2"
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</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>
|
||||
|
||||
<BackToOnyxButton />
|
||||
<FixedLogo backgroundToggled={false} />
|
||||
<BackToOnyxButton documentSidebarToggled={documentSidebarToggled} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -13,8 +13,8 @@ import {
|
||||
FetchAssistantsResponse,
|
||||
fetchAssistantsSS,
|
||||
} from "@/lib/assistants/fetchAssistantsSS";
|
||||
import FunctionalHeader from "@/components/chat_search/Header";
|
||||
import { defaultPersona } from "@/app/admin/assistants/lib";
|
||||
import { constructMiniFiedPersona } from "@/lib/assistantIconUtils";
|
||||
|
||||
async function getSharedChat(chatId: string) {
|
||||
const response = await fetchSS(
|
||||
@ -34,7 +34,6 @@ export default async function Page(props: {
|
||||
getAuthTypeMetadataSS(),
|
||||
getCurrentUserSS(),
|
||||
getSharedChat(params.chatId),
|
||||
fetchAssistantsSS(),
|
||||
];
|
||||
|
||||
// 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 user = results[1] as User | 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";
|
||||
if (!authDisabled && !user) {
|
||||
@ -61,22 +58,13 @@ export default async function Page(props: {
|
||||
if (user && !user.is_verified && authTypeMetadata?.requiresVerification) {
|
||||
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 (
|
||||
<div>
|
||||
<div className="absolute top-0 z-40 w-full">
|
||||
<FunctionalHeader page="shared" />
|
||||
</div>
|
||||
|
||||
<div className="flex relative bg-background text-default overflow-hidden pt-16 h-screen">
|
||||
<SharedChatDisplay chatSession={chatSession} persona={persona} />
|
||||
</div>
|
||||
</div>
|
||||
const persona: Persona = constructMiniFiedPersona(
|
||||
chatSession?.persona_icon_color ?? null,
|
||||
chatSession?.persona_icon_shape ?? null,
|
||||
chatSession?.persona_name ?? "",
|
||||
chatSession?.persona_id ?? 0
|
||||
);
|
||||
|
||||
return <SharedChatDisplay chatSession={chatSession} persona={persona} />;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export function MetadataBadge({
|
||||
className: flexNone ? "flex-none" : "mr-0.5 my-auto",
|
||||
})}
|
||||
<p className="max-w-[6rem] text-ellipsis overflow-hidden truncate whitespace-nowrap">
|
||||
{value}lllaasfasdf
|
||||
{value}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -35,7 +35,7 @@ const DropdownOption: React.FC<DropdownOptionProps> = ({
|
||||
openInNewTab,
|
||||
}) => {
|
||||
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}
|
||||
{label}
|
||||
</div>
|
||||
|
@ -33,7 +33,7 @@ export function AssistantIcon({
|
||||
|
||||
return (
|
||||
<CustomTooltip
|
||||
disabled={disableToolip}
|
||||
disabled={disableToolip || !assistant.description}
|
||||
showTick
|
||||
line
|
||||
wrap
|
||||
|
@ -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} />
|
||||
</div>
|
||||
</Link>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
|
||||
export interface GridShape {
|
||||
encodedGrid: number;
|
||||
filledSquares: number;
|
||||
@ -45,7 +47,9 @@ export function generateRandomIconShape(): GridShape {
|
||||
if (grid[row][col]) {
|
||||
const x = col * 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]) {
|
||||
const x = col * 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]];
|
||||
}
|
||||
}
|
||||
|
||||
// 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: [],
|
||||
};
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user