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 { user, isAdmin, isLoadingUser } = useUser();
const existingChatIdRaw = searchParams.get("chatId"); 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 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; const existingChatSessionId = existingChatIdRaw ? existingChatIdRaw : null;
@ -196,7 +222,7 @@ export function ChatPage({
}; };
const llmOverrideManager = useLlmOverride( const llmOverrideManager = useLlmOverride(
user?.preferences.default_model ?? null, modelVersionFromSearchParams || (user?.preferences.default_model ?? null),
selectedChatSession, selectedChatSession,
defaultTemperature defaultTemperature
); );
@ -1853,6 +1879,9 @@ export function ChatPage({
{sharedChatSession && ( {sharedChatSession && (
<ShareChatSessionModal <ShareChatSessionModal
assistantId={liveAssistant?.id}
message={message}
modelOverride={llmOverrideManager.llmOverride}
chatSessionId={sharedChatSession.id} chatSessionId={sharedChatSession.id}
existingSharedStatus={sharedChatSession.shared_status} existingSharedStatus={sharedChatSession.shared_status}
onClose={() => setSharedChatSession(null)} onClose={() => setSharedChatSession(null)}
@ -1867,6 +1896,9 @@ export function ChatPage({
)} )}
{sharingModalVisible && chatSessionIdRef.current !== null && ( {sharingModalVisible && chatSessionIdRef.current !== null && (
<ShareChatSessionModal <ShareChatSessionModal
message={message}
assistantId={liveAssistant?.id}
modelOverride={llmOverrideManager.llmOverride}
chatSessionId={chatSessionIdRef.current} chatSessionId={chatSessionIdRef.current}
existingSharedStatus={chatSessionSharedStatus} existingSharedStatus={chatSessionSharedStatus}
onClose={() => setSharingModalVisible(false)} onClose={() => setSharingModalVisible(false)}

View File

@ -5,6 +5,10 @@ import { Spinner } from "@/components/Spinner";
import { ChatSessionSharedStatus } from "../interfaces"; import { ChatSessionSharedStatus } from "../interfaces";
import { FiCopy } from "react-icons/fi"; import { FiCopy } from "react-icons/fi";
import { CopyButton } from "@/components/CopyButton"; 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) { function buildShareLink(chatSessionId: string) {
const baseUrl = `${window.location.protocol}//${window.location.host}`; const baseUrl = `${window.location.protocol}//${window.location.host}`;
@ -26,6 +30,34 @@ async function generateShareLink(chatSessionId: string) {
return null; 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) { async function deleteShareLink(chatSessionId: string) {
const response = await fetch(`/api/chat/chat-session/${chatSessionId}`, { const response = await fetch(`/api/chat/chat-session/${chatSessionId}`, {
method: "PATCH", method: "PATCH",
@ -43,20 +75,28 @@ export function ShareChatSessionModal({
existingSharedStatus, existingSharedStatus,
onShare, onShare,
onClose, onClose,
message,
assistantId,
modelOverride,
}: { }: {
chatSessionId: string; chatSessionId: string;
existingSharedStatus: ChatSessionSharedStatus; existingSharedStatus: ChatSessionSharedStatus;
onShare?: (shared: boolean) => void; onShare?: (shared: boolean) => void;
onClose: () => void; onClose: () => void;
message?: string;
assistantId?: number;
modelOverride?: LlmOverride;
}) { }) {
const [linkGenerating, setLinkGenerating] = useState(false);
const [shareLink, setShareLink] = useState<string>( const [shareLink, setShareLink] = useState<string>(
existingSharedStatus === ChatSessionSharedStatus.Public existingSharedStatus === ChatSessionSharedStatus.Public
? buildShareLink(chatSessionId) ? buildShareLink(chatSessionId)
: "" : ""
); );
const { popup, setPopup } = usePopup();
return ( return (
<>
{popup}
<Modal onOutsideClick={onClose} width="max-w-3xl"> <Modal onOutsideClick={onClose} width="max-w-3xl">
<> <>
<div className="flex mb-4"> <div className="flex mb-4">
@ -65,8 +105,6 @@ export function ShareChatSessionModal({
</h2> </h2>
</div> </div>
{linkGenerating && <Spinner />}
<div className="flex mt-2"> <div className="flex mt-2">
{shareLink ? ( {shareLink ? (
<div> <div>
@ -96,8 +134,6 @@ export function ShareChatSessionModal({
<Button <Button
onClick={async () => { onClick={async () => {
setLinkGenerating(true);
const success = await deleteShareLink(chatSessionId); const success = await deleteShareLink(chatSessionId);
if (success) { if (success) {
setShareLink(""); setShareLink("");
@ -105,8 +141,6 @@ export function ShareChatSessionModal({
} else { } else {
alert("Failed to delete share link"); alert("Failed to delete share link");
} }
setLinkGenerating(false);
}} }}
size="xs" size="xs"
color="red" color="red"
@ -118,20 +152,19 @@ export function ShareChatSessionModal({
<div> <div>
<Callout title="Warning" color="yellow" className="mb-4"> <Callout title="Warning" color="yellow" className="mb-4">
Ensure that all content in the chat is safe to share with the Ensure that all content in the chat is safe to share with the
whole organization. The content of the retrieved documents will whole organization. The content of the retrieved documents
not be visible, but the names of cited documents as well as the will not be visible, but the names of cited documents as well
AI and human messages will be visible. as the AI and human messages will be visible.
</Callout> </Callout>
<div className="flex w-full justify-between">
<Button <Button
icon={FiCopy} icon={FiCopy}
onClick={async () => { onClick={async () => {
setLinkGenerating(true); // NOTE: for "insecure" non-https setup, the `navigator.clipboard.writeText` may fail
// NOTE: for "inescure" 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 shareLink = await generateShareLink(chatSessionId); const shareLink =
await generateShareLink(chatSessionId);
if (!shareLink) { if (!shareLink) {
alert("Failed to generate share link"); alert("Failed to generate share link");
} else { } else {
@ -142,8 +175,6 @@ export function ShareChatSessionModal({
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
setLinkGenerating(false);
}} }}
size="xs" size="xs"
color="green" color="green"
@ -151,9 +182,55 @@ export function ShareChatSessionModal({
Generate and Copy Share Link Generate and Copy Share Link
</Button> </Button>
</div> </div>
</div>
)} )}
</div> </div>
<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> </Modal>
</>
); );
} }

View File

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