mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-07-13 14:43:05 +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:
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -2105,6 +2105,7 @@ export function ChatPage({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sharingModalVisible && chatSessionIdRef.current !== null && (
|
{sharingModalVisible && chatSessionIdRef.current !== null && (
|
||||||
<ShareChatSessionModal
|
<ShareChatSessionModal
|
||||||
message={message}
|
message={message}
|
||||||
|
@ -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)}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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 &&
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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} />;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -33,7 +33,7 @@ export function AssistantIcon({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomTooltip
|
<CustomTooltip
|
||||||
disabled={disableToolip}
|
disabled={disableToolip || !assistant.description}
|
||||||
showTick
|
showTick
|
||||||
line
|
line
|
||||||
wrap
|
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} />
|
<NewChatIcon size={20} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -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: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user