Add ux improvements (#2130)

* add ux improvements

* add danswer version display

* show version properly

* improve copy + add web version to settings context

* update copy + danswer version
This commit is contained in:
pablodanswer
2024-08-14 09:43:52 -07:00
committed by GitHub
parent 54732a83c9
commit 3540aa579b
15 changed files with 270 additions and 126 deletions

View File

@@ -1,5 +1,6 @@
import json import json
from collections.abc import Generator from collections.abc import Generator
from enum import Enum
from typing import Any from typing import Any
from typing import cast from typing import cast
@@ -20,6 +21,7 @@ from danswer.tools.tool import ToolResponse
from danswer.utils.logger import setup_logger from danswer.utils.logger import setup_logger
from danswer.utils.threadpool_concurrency import run_functions_tuples_in_parallel from danswer.utils.threadpool_concurrency import run_functions_tuples_in_parallel
logger = setup_logger() logger = setup_logger()
@@ -54,6 +56,12 @@ class ImageGenerationResponse(BaseModel):
url: str url: str
class ImageShape(str, Enum):
SQUARE = "square"
PORTRAIT = "portrait"
LANDSCAPE = "landscape"
class ImageGenerationTool(Tool): class ImageGenerationTool(Tool):
_NAME = "run_image_generation" _NAME = "run_image_generation"
_DESCRIPTION = "Generate an image from a prompt." _DESCRIPTION = "Generate an image from a prompt."
@@ -102,6 +110,11 @@ class ImageGenerationTool(Tool):
"type": "string", "type": "string",
"description": "Prompt used to generate the image", "description": "Prompt used to generate the image",
}, },
"shape": {
"type": "string",
"description": "Optional. Image shape: 'square', 'portrait', or 'landscape'",
"enum": [shape.value for shape in ImageShape],
},
}, },
"required": ["prompt"], "required": ["prompt"],
}, },
@@ -161,7 +174,16 @@ class ImageGenerationTool(Tool):
# img_urls=[image_generation.url for image_generation in image_generations], # img_urls=[image_generation.url for image_generation in image_generations],
) )
def _generate_image(self, prompt: str) -> ImageGenerationResponse: def _generate_image(
self, prompt: str, shape: ImageShape
) -> ImageGenerationResponse:
if shape == ImageShape.LANDSCAPE:
size = "1792x1024"
elif shape == ImageShape.PORTRAIT:
size = "1024x1792"
else:
size = "1024x1024"
try: try:
response = image_generation( response = image_generation(
prompt=prompt, prompt=prompt,
@@ -170,6 +192,7 @@ class ImageGenerationTool(Tool):
# need to pass in None rather than empty str # need to pass in None rather than empty str
api_base=self.api_base or None, api_base=self.api_base or None,
api_version=self.api_version or None, api_version=self.api_version or None,
size=size,
n=1, n=1,
extra_headers=build_llm_extra_headers(self.additional_headers), extra_headers=build_llm_extra_headers(self.additional_headers),
) )
@@ -202,13 +225,23 @@ class ImageGenerationTool(Tool):
def run(self, **kwargs: str) -> Generator[ToolResponse, None, None]: def run(self, **kwargs: str) -> Generator[ToolResponse, None, None]:
prompt = cast(str, kwargs["prompt"]) prompt = cast(str, kwargs["prompt"])
shape = ImageShape(kwargs.get("shape", ImageShape.SQUARE))
# dalle3 only supports 1 image at a time, which is why we have to # dalle3 only supports 1 image at a time, which is why we have to
# parallelize this via threading # parallelize this via threading
results = cast( results = cast(
list[ImageGenerationResponse], list[ImageGenerationResponse],
run_functions_tuples_in_parallel( run_functions_tuples_in_parallel(
[(self._generate_image, (prompt,)) for _ in range(self.num_imgs)] [
(
self._generate_image,
(
prompt,
shape,
),
)
for _ in range(self.num_imgs)
]
), ),
) )
yield ToolResponse( yield ToolResponse(

View File

@@ -24,7 +24,7 @@ import { getDisplayNameForModel } from "@/lib/hooks";
import { Bubble } from "@/components/Bubble"; import { Bubble } from "@/components/Bubble";
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable"; import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
import { Option } from "@/components/Dropdown"; import { Option } from "@/components/Dropdown";
import { GroupsIcon, PaintingIcon, SwapIcon } from "@/components/icons/icons"; import { GroupsIcon } from "@/components/icons/icons";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled"; import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
import { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences"; import { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences";
import { useUserGroups } from "@/lib/hooks"; import { useUserGroups } from "@/lib/hooks";
@@ -48,7 +48,6 @@ import { SuccessfulPersonaUpdateRedirectType } from "./enums";
import { Persona, StarterMessage } from "./interfaces"; import { Persona, StarterMessage } from "./interfaces";
import { buildFinalPrompt, createPersona, updatePersona } from "./lib"; import { buildFinalPrompt, createPersona, updatePersona } from "./lib";
import { IconImageSelection } from "@/components/assistants/AssistantIconCreation"; import { IconImageSelection } from "@/components/assistants/AssistantIconCreation";
import { FaSwatchbook } from "react-icons/fa";
function findSearchTool(tools: ToolSnapshot[]) { function findSearchTool(tools: ToolSnapshot[]) {
return tools.find((tool) => tool.in_code_tool_id === "SearchTool"); return tools.find((tool) => tool.in_code_tool_id === "SearchTool");
@@ -604,40 +603,95 @@ export function AssistantEditor({
</div> </div>
</div> </div>
<div className="mt-2 ml-1"> <div className="mt-2 flex flex-col ml-1">
{imageGenerationTool && {imageGenerationTool && (
checkLLMSupportsImageInput( <TooltipProvider delayDuration={50}>
providerDisplayNameToProviderName.get( <Tooltip>
values.llm_model_provider_override || "" <TooltipTrigger asChild>
) || <div
defaultProviderName || className={`w-fit ${
"", !checkLLMSupportsImageInput(
values.llm_model_version_override || providerDisplayNameToProviderName.get(
defaultModelName || values.llm_model_provider_override || ""
"" ) || "",
) && ( values.llm_model_version_override || ""
<BooleanFormField )
noPadding ? "opacity-50 cursor-not-allowed"
name={`enabled_tools_map.${imageGenerationTool.id}`} : ""
label="Image Generation Tool" }`}
onChange={() => { >
toggleToolInValues(imageGenerationTool.id); <BooleanFormField
}} noPadding
/> name={`enabled_tools_map.${imageGenerationTool.id}`}
)} label="Image Generation Tool"
onChange={() => {
toggleToolInValues(imageGenerationTool.id);
}}
disabled={
!checkLLMSupportsImageInput(
providerDisplayNameToProviderName.get(
values.llm_model_provider_override || ""
) || "",
values.llm_model_version_override || ""
)
}
/>
</div>
</TooltipTrigger>
{!checkLLMSupportsImageInput(
providerDisplayNameToProviderName.get(
values.llm_model_provider_override || ""
) || "",
values.llm_model_version_override || ""
) && (
<TooltipContent side="top" align="center">
<p className="bg-background-900 max-w-[200px] mb-1 text-sm rounded-lg p-1.5 text-white">
To use Image Generation, select GPT-4o as the
default model for this Assistant.
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
)}
{searchTool && (
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-fit ${
ccPairs.length === 0
? "opacity-50 cursor-not-allowed"
: ""
}`}
>
<BooleanFormField
name={`enabled_tools_map.${searchTool.id}`}
label="Search Tool"
noPadding
onChange={() => {
setFieldValue("num_chunks", null);
toggleToolInValues(searchTool.id);
}}
disabled={ccPairs.length === 0}
/>
</div>
</TooltipTrigger>
{ccPairs.length === 0 && (
<TooltipContent side="top" align="center">
<p className="bg-background-900 max-w-[200px] mb-1 text-sm rounded-lg p-1.5 text-white">
To use the Search Tool, you need to have at
least one Connector-Credential pair configured.
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
)}
{ccPairs.length > 0 && searchTool && ( {ccPairs.length > 0 && searchTool && (
<> <>
<BooleanFormField
name={`enabled_tools_map.${searchTool.id}`}
label="Search Tool"
noPadding
onChange={() => {
setFieldValue("num_chunks", null);
toggleToolInValues(searchTool.id);
}}
/>
{searchToolEnabled() && ( {searchToolEnabled() && (
<CollapsibleSection prompt="Configure Search"> <CollapsibleSection prompt="Configure Search">
<div> <div>

View File

@@ -20,4 +20,5 @@ export interface CombinedSettings {
enterpriseSettings: EnterpriseSettings | null; enterpriseSettings: EnterpriseSettings | null;
customAnalyticsScript: string | null; customAnalyticsScript: string | null;
isMobile?: boolean; isMobile?: boolean;
webVersion: string | null;
} }

View File

@@ -5,6 +5,7 @@ import {
BackendChatSession, BackendChatSession,
BackendMessage, BackendMessage,
ChatFileType, ChatFileType,
ChatSession,
ChatSessionSharedStatus, ChatSessionSharedStatus,
DocumentsResponse, DocumentsResponse,
FileDescriptor, FileDescriptor,
@@ -26,6 +27,7 @@ import {
buildLatestMessageChain, buildLatestMessageChain,
checkAnyAssistantHasSearch, checkAnyAssistantHasSearch,
createChatSession, createChatSession,
deleteChatSession,
getCitedDocumentsFromMessage, getCitedDocumentsFromMessage,
getHumanAndAIMessageFromMessageNumber, getHumanAndAIMessageFromMessageNumber,
getLastSuccessfulMessageId, getLastSuccessfulMessageId,
@@ -79,6 +81,7 @@ import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
import FixedLogo from "./shared_chat_search/FixedLogo"; import FixedLogo from "./shared_chat_search/FixedLogo";
import { getSecondsUntilExpiration } from "@/lib/time"; import { getSecondsUntilExpiration } from "@/lib/time";
import { SetDefaultModelModal } from "./modal/SetDefaultModelModal"; import { SetDefaultModelModal } from "./modal/SetDefaultModelModal";
import { DeleteChatModal } from "./modal/DeleteChatModal";
const TEMP_USER_MESSAGE_ID = -1; const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2; const TEMP_ASSISTANT_MESSAGE_ID = -2;
@@ -1188,7 +1191,17 @@ export function ChatPage({
window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keydown", handleKeyDown);
}; };
}, [router]); }, [router]);
const [sharedChatSession, setSharedChatSession] =
useState<ChatSession | null>();
const [deletingChatSession, setDeletingChatSession] =
useState<ChatSession | null>();
const showDeleteModal = (chatSession: ChatSession) => {
setDeletingChatSession(chatSession);
};
const showShareModal = (chatSession: ChatSession) => {
setSharedChatSession(chatSession);
};
const [documentSelection, setDocumentSelection] = useState(false); const [documentSelection, setDocumentSelection] = useState(false);
const toggleDocumentSelectionAspects = () => { const toggleDocumentSelectionAspects = () => {
setDocumentSelection((documentSelection) => !documentSelection); setDocumentSelection((documentSelection) => !documentSelection);
@@ -1229,11 +1242,29 @@ export function ChatPage({
onClose={() => setSettingsToggled(false)} onClose={() => setSettingsToggled(false)}
/> />
)} )}
{sharingModalVisible && chatSessionIdRef.current !== null && (
{deletingChatSession && (
<DeleteChatModal
onClose={() => setDeletingChatSession(null)}
onSubmit={async () => {
const response = await deleteChatSession(deletingChatSession.id);
if (response.ok) {
setDeletingChatSession(null);
// go back to the main page
router.push("/chat");
} else {
alert("Failed to delete chat session");
}
}}
chatSessionName={deletingChatSession.name}
/>
)}
{sharedChatSession && (
<ShareChatSessionModal <ShareChatSessionModal
chatSessionId={chatSessionIdRef.current} chatSessionId={sharedChatSession.id}
existingSharedStatus={chatSessionSharedStatus} existingSharedStatus={sharedChatSession.shared_status}
onClose={() => setSharingModalVisible(false)} onClose={() => setSharedChatSession(null)}
onShare={(shared) => onShare={(shared) =>
setChatSessionSharedStatus( setChatSessionSharedStatus(
shared shared
@@ -1243,6 +1274,13 @@ export function ChatPage({
} }
/> />
)} )}
{sharingModalVisible && chatSessionIdRef.current !== null && (
<ShareChatSessionModal
chatSessionId={chatSessionIdRef.current}
existingSharedStatus={chatSessionSharedStatus}
onClose={() => setSharingModalVisible(false)}
/>
)}
<div className="fixed inset-0 flex flex-col text-default"> <div className="fixed inset-0 flex flex-col text-default">
<div className="h-[100dvh] overflow-y-hidden"> <div className="h-[100dvh] overflow-y-hidden">
<div className="w-full"> <div className="w-full">
@@ -1277,6 +1315,8 @@ export function ChatPage({
folders={folders} folders={folders}
openedFolders={openedFolders} openedFolders={openedFolders}
removeToggle={removeToggle} removeToggle={removeToggle}
showShareModal={showShareModal}
showDeleteModal={showDeleteModal}
/> />
</div> </div>
</div> </div>

View File

@@ -24,7 +24,7 @@ export function InMessageImage({ fileId }: { fileId: string }) {
height={1200} height={1200}
alt="Chat Message Image" alt="Chat Message Image"
onLoad={() => setImageLoaded(true)} onLoad={() => setImageLoaded(true)}
className={`object-cover object-center overflow-hidden rounded-lg w-full h-full max-w-96 max-h-96 transition-opacity duration-300 className={`object-contain object-left overflow-hidden rounded-lg w-full h-full max-w-96 max-h-96 transition-opacity duration-300
${imageLoaded ? "opacity-100" : "opacity-0"}`} ${imageLoaded ? "opacity-100" : "opacity-0"}`}
onClick={() => setFullImageShowing(true)} onClick={() => setFullImageShowing(true)}
src={buildImgUrl(fileId)} src={buildImgUrl(fileId)}

View File

@@ -82,10 +82,12 @@ const FolderItem = ({
} }
}; };
const saveFolderName = async () => { const saveFolderName = async (continueEditing?: boolean) => {
try { try {
await updateFolderName(folder.folder_id, editedFolderName); await updateFolderName(folder.folder_id, editedFolderName);
setIsEditing(false); if (!continueEditing) {
setIsEditing(false);
}
router.refresh(); // Refresh values to update the sidebar router.refresh(); // Refresh values to update the sidebar
} catch (error) { } catch (error) {
setPopup({ message: "Failed to save folder name", type: "error" }); setPopup({ message: "Failed to save folder name", type: "error" });
@@ -171,7 +173,7 @@ const FolderItem = ({
{showDeleteConfirm && ( {showDeleteConfirm && (
<div <div
ref={deleteConfirmRef} ref={deleteConfirmRef}
className="absolute max-w-xs border z-[100] border-border-medium top-0 right-0 w-[250px] -bo-0 top-2 mt-4 p-2 bg-background-100 rounded shadow-lg z-10" className="absolute max-w-xs border z-[100] border-border-medium top-0 right-0 w-[225px] -bo-0 top-2 mt-4 p-2 bg-background-100 rounded shadow-lg z-10"
> >
<p className="text-sm mb-2"> <p className="text-sm mb-2">
Are you sure you want to delete <i>{folder.folder_name}</i>? All the Are you sure you want to delete <i>{folder.folder_name}</i>? All the
@@ -216,6 +218,7 @@ const FolderItem = ({
value={editedFolderName} value={editedFolderName}
onChange={handleFolderNameChange} onChange={handleFolderNameChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onBlur={() => saveFolderName(true)}
className="text-sm px-1 flex-1 min-w-0 -my-px mr-2" className="text-sm px-1 flex-1 min-w-0 -my-px mr-2"
/> />
) : ( ) : (
@@ -239,20 +242,13 @@ const FolderItem = ({
<FiTrash size={16} /> <FiTrash size={16} />
</div> </div>
</div> </div>
{/* <div
onClick={deleteFolderHandler}
className="hover:bg-black/10 p-1 -m-1 rounded ml-2"
>
<FiTrash size={16} />
</div> */}
</div> </div>
)} )}
{isEditing && ( {isEditing && (
<div className="flex ml-auto my-auto"> <div className="flex ml-auto my-auto">
<div <div
onClick={saveFolderName} onClick={() => saveFolderName()}
className="hover:bg-black/10 p-1 -m-1 rounded" className="hover:bg-black/10 p-1 -m-1 rounded"
> >
<FiCheck size={16} /> <FiCheck size={16} />
@@ -310,6 +306,12 @@ export const FolderList = ({
} }
/> />
))} ))}
{folders.length == 1 && folders[0].chat_sessions.length == 0 && (
<p className="text-sm font-normal text-subtle mt-2">
{" "}
Drag a chat into a folder to save for later{" "}
</p>
)}
</div> </div>
); );
}; };

View File

@@ -16,12 +16,6 @@ export const DeleteChatModal = ({
<> <>
<div className="flex mb-4"> <div className="flex mb-4">
<h2 className="my-auto text-2xl font-bold">Delete chat?</h2> <h2 className="my-auto text-2xl font-bold">Delete chat?</h2>
<div
onClick={onClose}
className="my-auto ml-auto p-2 hover:bg-hover rounded cursor-pointer"
>
<FiX size={20} />
</div>
</div> </div>
<p className="mb-4"> <p className="mb-4">
Click below to confirm that you want to delete{" "} Click below to confirm that you want to delete{" "}

View File

@@ -98,6 +98,9 @@ export function SetDefaultModelModal({
}); });
} }
}; };
const defaultProvider = llmProviders.find(
(llmProvider) => llmProvider.is_default_provider
);
return ( return (
<ModalWrapper <ModalWrapper
@@ -118,46 +121,46 @@ export function SetDefaultModelModal({
{defaultModel == null && " No default model has been selected!"} {defaultModel == null && " No default model has been selected!"}
</Text> </Text>
<div className="w-full flex text-sm flex-col"> <div className="w-full flex text-sm flex-col">
<div key={-1} className="w-full border-b hover:bg-background-50"> <div
<td className="min-w-[80px]"> key={-1}
{defaultModel == null ? ( className="w-full border-b flex items-center gap-x-2 hover:bg-background-50"
<Badge>selected</Badge> >
) : ( <input
<input checked={defaultModelDestructured?.modelName == null}
type="radio" type="radio"
name="credentialSelection" name="credentialSelection"
onChange={(e) => { onChange={(e) => {
e.preventDefault(); e.preventDefault();
handleChangedefaultModel(null); handleChangedefaultModel(null);
}} }}
className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out" className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
/> />
)} {
</td> <td className="p-2">
<td className="p-2">System default</td> System default{" "}
{defaultProvider?.default_model_name &&
`(${getDisplayNameForModel(defaultProvider?.default_model_name)})`}
</td>
}
</div> </div>
{llmOptions.map(({ name, value }, index) => { {llmOptions.map(({ name, value }, index) => {
return ( return (
<div <div
key={index} key={index}
className="w-full border-b hover:bg-background-50" className="w-full flex items-center gap-x-2 border-b hover:bg-background-50"
> >
<td className="min-w-[80px]"> <input
{defaultModelDestructured?.modelName != name ? ( checked={defaultModelDestructured?.modelName == name}
<input type="radio"
type="radio" name="credentialSelection"
name="credentialSelection" onChange={(e) => {
onChange={(e) => { e.preventDefault();
e.preventDefault(); handleChangedefaultModel(value);
handleChangedefaultModel(value); }}
}} className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out" />
/>
) : (
<Badge>selected</Badge>
)}
</td>
<td className="p-2"> <td className="p-2">
{getDisplayNameForModel(name)}{" "} {getDisplayNameForModel(name)}{" "}
{defaultModelDestructured && {defaultModelDestructured &&

View File

@@ -27,6 +27,8 @@ export function ChatSessionDisplay({
isSelected, isSelected,
skipGradient, skipGradient,
closeSidebar, closeSidebar,
showShareModal,
showDeleteModal,
}: { }: {
chatSession: ChatSession; chatSession: ChatSession;
isSelected: boolean; isSelected: boolean;
@@ -35,6 +37,8 @@ export function ChatSessionDisplay({
// if not set, the gradient will still be applied and cause weirdness // if not set, the gradient will still be applied and cause weirdness
skipGradient?: boolean; skipGradient?: boolean;
closeSidebar?: () => void; closeSidebar?: () => void;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
}) { }) {
const router = useRouter(); const router = useRouter();
const [isDeletionModalVisible, setIsDeletionModalVisible] = useState(false); const [isDeletionModalVisible, setIsDeletionModalVisible] = useState(false);
@@ -77,22 +81,6 @@ export function ChatSessionDisplay({
/> />
)} )}
{isDeletionModalVisible && (
<DeleteChatModal
onClose={() => setIsDeletionModalVisible(false)}
onSubmit={async () => {
const response = await deleteChatSession(chatSession.id);
if (response.ok) {
setIsDeletionModalVisible(false);
// go back to the main page
router.push("/chat");
} else {
alert("Failed to delete chat session");
}
}}
chatSessionName={chatSession.name}
/>
)}
<Link <Link
className="flex my-1 group relative" className="flex my-1 group relative"
key={chatSession.id} key={chatSession.id}
@@ -186,11 +174,13 @@ export function ChatSessionDisplay({
} }
popover={ popover={
<div className="border border-border rounded-lg bg-background z-50 w-32"> <div className="border border-border rounded-lg bg-background z-50 w-32">
<DefaultDropdownElement {showShareModal && (
name="Share" <DefaultDropdownElement
icon={FiShare2} name="Share"
onSelect={() => setIsShareModalVisible(true)} icon={FiShare2}
/> onSelect={() => showShareModal(chatSession)}
/>
)}
<DefaultDropdownElement <DefaultDropdownElement
name="Rename" name="Rename"
icon={FiEdit2} icon={FiEdit2}
@@ -204,12 +194,14 @@ export function ChatSessionDisplay({
/> />
</div> </div>
</div> </div>
<div {showDeleteModal && (
onClick={() => setIsDeletionModalVisible(true)} <div
className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`} onClick={() => showDeleteModal(chatSession)}
> className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`}
<FiTrash size={16} /> >
</div> <FiTrash size={16} />
</div>
)}
</div> </div>
))} ))}
</div> </div>

View File

@@ -32,6 +32,8 @@ interface HistorySidebarProps {
toggled?: boolean; toggled?: boolean;
removeToggle?: () => void; removeToggle?: () => void;
reset?: () => void; reset?: () => void;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
} }
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>( export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
@@ -46,6 +48,8 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
openedFolders, openedFolders,
toggleSidebar, toggleSidebar,
removeToggle, removeToggle,
showShareModal,
showDeleteModal,
}, },
ref: ForwardedRef<HTMLDivElement> ref: ForwardedRef<HTMLDivElement>
) => { ) => {
@@ -168,6 +172,8 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
)} )}
<div className="border-b border-border pb-4 mx-3" /> <div className="border-b border-border pb-4 mx-3" />
<PagesTab <PagesTab
showDeleteModal={showDeleteModal}
showShareModal={showShareModal}
closeSidebar={removeToggle} closeSidebar={removeToggle}
page={page} page={page}
existingChats={existingChats} existingChats={existingChats}

View File

@@ -17,6 +17,8 @@ export function PagesTab({
folders, folders,
openedFolders, openedFolders,
closeSidebar, closeSidebar,
showShareModal,
showDeleteModal,
}: { }: {
page: pageType; page: pageType;
existingChats?: ChatSession[]; existingChats?: ChatSession[];
@@ -24,6 +26,8 @@ export function PagesTab({
folders?: Folder[]; folders?: Folder[];
openedFolders?: { [key: number]: boolean }; openedFolders?: { [key: number]: boolean };
closeSidebar?: () => void; closeSidebar?: () => void;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
}) { }) {
const groupedChatSessions = existingChats const groupedChatSessions = existingChats
? groupSessionsByDateRange(existingChats) ? groupSessionsByDateRange(existingChats)
@@ -117,6 +121,8 @@ export function PagesTab({
return ( return (
<div key={`${chat.id}-${chat.name}`}> <div key={`${chat.id}-${chat.name}`}>
<ChatSessionDisplay <ChatSessionDisplay
showDeleteModal={showDeleteModal}
showShareModal={showShareModal}
closeSidebar={closeSidebar} closeSidebar={closeSidebar}
search={page == "search"} search={page == "search"}
chatSession={chat} chatSession={chat}

View File

@@ -27,7 +27,6 @@ import { FiActivity, FiBarChart2 } from "react-icons/fi";
import { UserDropdown } from "../UserDropdown"; import { UserDropdown } from "../UserDropdown";
import { User } from "@/lib/types"; import { User } from "@/lib/types";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { PencilCircle } from "@phosphor-icons/react";
export function ClientLayout({ export function ClientLayout({
user, user,

View File

@@ -23,6 +23,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
if (!combinedSettings) { if (!combinedSettings) {
return null; return null;
} }
const settings = combinedSettings.settings; const settings = combinedSettings.settings;
const enterpriseSettings = combinedSettings.enterpriseSettings; const enterpriseSettings = combinedSettings.enterpriseSettings;
@@ -93,6 +94,16 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
</div> </div>
))} ))}
</nav> </nav>
{combinedSettings.webVersion && (
<div
className="flex flex-col mt-6 items-center justify-center w-full"
key={"danswerVersion"}
>
<h2 className="text-xs text-text w-52 font-medium pb-2">
Danswer version: {combinedSettings.webVersion}
</h2>
</div>
)}
</div> </div>
); );
} }

View File

@@ -344,6 +344,7 @@ interface BooleanFormFieldProps {
small?: boolean; small?: boolean;
alignTop?: boolean; alignTop?: boolean;
noLabel?: boolean; noLabel?: boolean;
disabled?: boolean;
} }
export const BooleanFormField = ({ export const BooleanFormField = ({
@@ -354,12 +355,14 @@ export const BooleanFormField = ({
noPadding, noPadding,
noLabel, noLabel,
small, small,
disabled,
alignTop, alignTop,
}: BooleanFormFieldProps) => { }: BooleanFormFieldProps) => {
return ( return (
<div className="mb-4"> <div className="mb-4">
<label className="flex text-sm"> <label className="flex text-sm">
<Field <Field
disabled={disabled}
name={name} name={name}
type="checkbox" type="checkbox"
className={`${noPadding ? "mr-3" : "mx-3"} px-5 w-3.5 h-3.5 ${ className={`${noPadding ? "mr-3" : "mx-3"} px-5 w-3.5 h-3.5 ${

View File

@@ -1,9 +1,14 @@
import { EnterpriseSettings, Settings } from "@/app/admin/settings/interfaces"; import {
CombinedSettings,
EnterpriseSettings,
Settings,
} from "@/app/admin/settings/interfaces";
import { import {
CUSTOM_ANALYTICS_ENABLED, CUSTOM_ANALYTICS_ENABLED,
SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED, SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED,
} from "@/lib/constants"; } from "@/lib/constants";
import { fetchSS } from "@/lib/utilsSS"; import { fetchSS } from "@/lib/utilsSS";
import { getWebVersion } from "@/lib/version";
export async function fetchSettingsSS() { export async function fetchSettingsSS() {
const tasks = [fetchSS("/settings")]; const tasks = [fetchSS("/settings")];
@@ -22,23 +27,18 @@ export async function fetchSettingsSS() {
const customAnalyticsScript = ( const customAnalyticsScript = (
tasks.length > 2 ? await results[2].json() : null tasks.length > 2 ? await results[2].json() : null
) as string | null; ) as string | null;
const webVersion = getWebVersion();
const combinedSettings: CombinedSettings = { const combinedSettings: CombinedSettings = {
settings, settings,
enterpriseSettings, enterpriseSettings,
customAnalyticsScript, customAnalyticsScript,
webVersion,
}; };
return combinedSettings; return combinedSettings;
} }
export interface CombinedSettings {
settings: Settings;
enterpriseSettings: EnterpriseSettings | null;
customAnalyticsScript: string | null;
isMobile?: boolean;
}
let cachedSettings: CombinedSettings; let cachedSettings: CombinedSettings;
export async function getCombinedSettings({ export async function getCombinedSettings({