This commit is contained in:
pablodanswer 2024-10-27 10:52:55 -07:00 committed by GitHub
parent da3c5e3711
commit a1bfa7847a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 203 additions and 92 deletions

View File

@ -140,7 +140,33 @@ export function ChatPage({
const { user, isAdmin, isLoadingUser } = useUser();
const existingChatIdRaw = searchParams.get("chatId");
const [sendOnLoad, setSendOnLoad] = useState<string | null>(
searchParams.get(SEARCH_PARAM_NAMES.SEND_ON_LOAD)
);
const currentPersonaId = searchParams.get(SEARCH_PARAM_NAMES.PERSONA_ID);
const modelVersionFromSearchParams = searchParams.get(
SEARCH_PARAM_NAMES.STRUCTURED_MODEL
);
// Effect to handle sendOnLoad
useEffect(() => {
if (sendOnLoad) {
const newSearchParams = new URLSearchParams(searchParams.toString());
newSearchParams.delete(SEARCH_PARAM_NAMES.SEND_ON_LOAD);
// Update the URL without the send-on-load parameter
router.replace(`?${newSearchParams.toString()}`, { scroll: false });
// Update our local state to reflect the change
setSendOnLoad(null);
// If there's a message, submit it
if (message) {
onSubmit({ messageOverride: message });
}
}
}, [sendOnLoad, searchParams, router]);
const existingChatSessionId = existingChatIdRaw ? existingChatIdRaw : null;
@ -196,7 +222,7 @@ export function ChatPage({
};
const llmOverrideManager = useLlmOverride(
user?.preferences.default_model ?? null,
modelVersionFromSearchParams || (user?.preferences.default_model ?? null),
selectedChatSession,
defaultTemperature
);
@ -1853,6 +1879,9 @@ export function ChatPage({
{sharedChatSession && (
<ShareChatSessionModal
assistantId={liveAssistant?.id}
message={message}
modelOverride={llmOverrideManager.llmOverride}
chatSessionId={sharedChatSession.id}
existingSharedStatus={sharedChatSession.shared_status}
onClose={() => setSharedChatSession(null)}
@ -1867,6 +1896,9 @@ export function ChatPage({
)}
{sharingModalVisible && chatSessionIdRef.current !== null && (
<ShareChatSessionModal
message={message}
assistantId={liveAssistant?.id}
modelOverride={llmOverrideManager.llmOverride}
chatSessionId={chatSessionIdRef.current}
existingSharedStatus={chatSessionSharedStatus}
onClose={() => setSharingModalVisible(false)}

View File

@ -5,6 +5,10 @@ import { Spinner } from "@/components/Spinner";
import { ChatSessionSharedStatus } from "../interfaces";
import { FiCopy } from "react-icons/fi";
import { CopyButton } from "@/components/CopyButton";
import { SEARCH_PARAM_NAMES } from "../searchParams";
import { usePopup } from "@/components/admin/connectors/Popup";
import { destructureValue, structureValue } from "@/lib/llm/utils";
import { LlmOverride } from "@/lib/hooks";
function buildShareLink(chatSessionId: string) {
const baseUrl = `${window.location.protocol}//${window.location.host}`;
@ -26,6 +30,34 @@ async function generateShareLink(chatSessionId: string) {
return null;
}
async function generateCloneLink(
message?: string,
assistantId?: number,
modelOverride?: LlmOverride
) {
const baseUrl = `${window.location.protocol}//${window.location.host}`;
const model = modelOverride
? structureValue(
modelOverride.name,
modelOverride.provider,
modelOverride.modelName
)
: null;
return `${baseUrl}/chat${
message
? `?${SEARCH_PARAM_NAMES.USER_PROMPT}=${encodeURIComponent(message)}`
: ""
}${
assistantId
? `${message ? "&" : "?"}${SEARCH_PARAM_NAMES.PERSONA_ID}=${assistantId}`
: ""
}${
model
? `${message || assistantId ? "&" : "?"}${SEARCH_PARAM_NAMES.STRUCTURED_MODEL}=${encodeURIComponent(model)}`
: ""
}${message ? `&${SEARCH_PARAM_NAMES.SEND_ON_LOAD}=true` : ""}`;
}
async function deleteShareLink(chatSessionId: string) {
const response = await fetch(`/api/chat/chat-session/${chatSessionId}`, {
method: "PATCH",
@ -43,117 +75,162 @@ export function ShareChatSessionModal({
existingSharedStatus,
onShare,
onClose,
message,
assistantId,
modelOverride,
}: {
chatSessionId: string;
existingSharedStatus: ChatSessionSharedStatus;
onShare?: (shared: boolean) => void;
onClose: () => void;
message?: string;
assistantId?: number;
modelOverride?: LlmOverride;
}) {
const [linkGenerating, setLinkGenerating] = useState(false);
const [shareLink, setShareLink] = useState<string>(
existingSharedStatus === ChatSessionSharedStatus.Public
? buildShareLink(chatSessionId)
: ""
);
const { popup, setPopup } = usePopup();
return (
<Modal onOutsideClick={onClose} width="max-w-3xl">
<>
<div className="flex mb-4">
<h2 className="text-2xl text-emphasis font-bold flex my-auto">
Share link to Chat
</h2>
</div>
<>
{popup}
<Modal onOutsideClick={onClose} width="max-w-3xl">
<>
<div className="flex mb-4">
<h2 className="text-2xl text-emphasis font-bold flex my-auto">
Share link to Chat
</h2>
</div>
{linkGenerating && <Spinner />}
<div className="flex mt-2">
{shareLink ? (
<div>
<Text>
This chat session is currently shared. Anyone at your
organization can view the message history using the following
link:
</Text>
<div className="flex mt-2">
{shareLink ? (
<div>
<Text>
This chat session is currently shared. Anyone at your
organization can view the message history using the following
link:
</Text>
<div className="flex mt-2">
<CopyButton content={shareLink} />
<a
href={shareLink}
target="_blank"
className="underline text-link mt-1 ml-1 text-sm my-auto"
rel="noreferrer"
>
{shareLink}
</a>
</div>
<div className="flex mt-2">
<CopyButton content={shareLink} />
<a
href={shareLink}
target="_blank"
className="underline text-link mt-1 ml-1 text-sm my-auto"
rel="noreferrer"
>
{shareLink}
</a>
</div>
<Divider />
<Divider />
<Text className="mb-4">
Click the button below to make the chat private again.
</Text>
<Text className="mb-4">
Click the button below to make the chat private again.
</Text>
<Button
onClick={async () => {
setLinkGenerating(true);
const success = await deleteShareLink(chatSessionId);
if (success) {
setShareLink("");
onShare && onShare(false);
} else {
alert("Failed to delete share link");
}
setLinkGenerating(false);
}}
size="xs"
color="red"
>
Delete Share Link
</Button>
</div>
) : (
<div>
<Callout title="Warning" color="yellow" 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.
</Callout>
<Button
icon={FiCopy}
onClick={async () => {
setLinkGenerating(true);
// NOTE: for "inescure" non-https setup, the `navigator.clipboard.writeText` may fail
// as the browser may not allow the clipboard to be accessed.
try {
const shareLink = await generateShareLink(chatSessionId);
if (!shareLink) {
alert("Failed to generate share link");
<Button
onClick={async () => {
const success = await deleteShareLink(chatSessionId);
if (success) {
setShareLink("");
onShare && onShare(false);
} else {
setShareLink(shareLink);
onShare && onShare(true);
navigator.clipboard.writeText(shareLink);
alert("Failed to delete share link");
}
} catch (e) {
console.error(e);
}
}}
size="xs"
color="red"
>
Delete Share Link
</Button>
</div>
) : (
<div>
<Callout title="Warning" color="yellow" 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.
</Callout>
<div className="flex w-full justify-between">
<Button
icon={FiCopy}
onClick={async () => {
// 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 shareLink =
await generateShareLink(chatSessionId);
if (!shareLink) {
alert("Failed to generate share link");
} else {
setShareLink(shareLink);
onShare && onShare(true);
navigator.clipboard.writeText(shareLink);
}
} catch (e) {
console.error(e);
}
}}
size="xs"
color="green"
>
Generate and Copy Share Link
</Button>
</div>
</div>
)}
</div>
setLinkGenerating(false);
}}
size="xs"
color="green"
>
Generate and Copy Share Link
</Button>
</div>
)}
</div>
</>
</Modal>
<Divider className="my-4" />
<div className="mb-4">
<Callout title="Clone Chat" color="blue">
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>
</div>
<div className="flex w-full justify-between">
<Button
icon={FiCopy}
onClick={async () => {
// 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(
message,
assistantId,
modelOverride
);
if (!cloneLink) {
setPopup({
message: "Failed to generate clone link",
type: "error",
});
} else {
navigator.clipboard.writeText(cloneLink);
setPopup({
message: "Link copied to clipboard!",
type: "success",
});
}
} catch (e) {
console.error(e);
alert("Failed to generate or copy link.");
}
}}
size="xs"
color="blue"
>
Generate and Copy Clone Link
</Button>
</div>
</>
</Modal>
</>
);
}

View File

@ -9,6 +9,7 @@ export const SEARCH_PARAM_NAMES = {
TEMPERATURE: "temperature",
MODEL_VERSION: "model-version",
SYSTEM_PROMPT: "system-prompt",
STRUCTURED_MODEL: "structured-model",
// user message
USER_PROMPT: "user-prompt",
SUBMIT_ON_LOAD: "submit-on-load",
@ -16,6 +17,7 @@ export const SEARCH_PARAM_NAMES = {
TITLE: "title",
// for seeding chats
SEEDED: "seeded",
SEND_ON_LOAD: "send-on-load",
};
export function shouldSubmitOnLoad(searchParams: ReadonlyURLSearchParams) {