mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-03-26 17:51:54 +01:00
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:
parent
54732a83c9
commit
3540aa579b
@ -1,5 +1,6 @@
|
||||
import json
|
||||
from collections.abc import Generator
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
|
||||
@ -20,6 +21,7 @@ from danswer.tools.tool import ToolResponse
|
||||
from danswer.utils.logger import setup_logger
|
||||
from danswer.utils.threadpool_concurrency import run_functions_tuples_in_parallel
|
||||
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
|
||||
@ -54,6 +56,12 @@ class ImageGenerationResponse(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
class ImageShape(str, Enum):
|
||||
SQUARE = "square"
|
||||
PORTRAIT = "portrait"
|
||||
LANDSCAPE = "landscape"
|
||||
|
||||
|
||||
class ImageGenerationTool(Tool):
|
||||
_NAME = "run_image_generation"
|
||||
_DESCRIPTION = "Generate an image from a prompt."
|
||||
@ -102,6 +110,11 @@ class ImageGenerationTool(Tool):
|
||||
"type": "string",
|
||||
"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"],
|
||||
},
|
||||
@ -161,7 +174,16 @@ class ImageGenerationTool(Tool):
|
||||
# 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:
|
||||
response = image_generation(
|
||||
prompt=prompt,
|
||||
@ -170,6 +192,7 @@ class ImageGenerationTool(Tool):
|
||||
# need to pass in None rather than empty str
|
||||
api_base=self.api_base or None,
|
||||
api_version=self.api_version or None,
|
||||
size=size,
|
||||
n=1,
|
||||
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]:
|
||||
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
|
||||
# parallelize this via threading
|
||||
results = cast(
|
||||
list[ImageGenerationResponse],
|
||||
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(
|
||||
|
@ -24,7 +24,7 @@ import { getDisplayNameForModel } from "@/lib/hooks";
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
|
||||
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 { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences";
|
||||
import { useUserGroups } from "@/lib/hooks";
|
||||
@ -48,7 +48,6 @@ import { SuccessfulPersonaUpdateRedirectType } from "./enums";
|
||||
import { Persona, StarterMessage } from "./interfaces";
|
||||
import { buildFinalPrompt, createPersona, updatePersona } from "./lib";
|
||||
import { IconImageSelection } from "@/components/assistants/AssistantIconCreation";
|
||||
import { FaSwatchbook } from "react-icons/fa";
|
||||
|
||||
function findSearchTool(tools: ToolSnapshot[]) {
|
||||
return tools.find((tool) => tool.in_code_tool_id === "SearchTool");
|
||||
@ -604,40 +603,95 @@ export function AssistantEditor({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 ml-1">
|
||||
{imageGenerationTool &&
|
||||
checkLLMSupportsImageInput(
|
||||
providerDisplayNameToProviderName.get(
|
||||
values.llm_model_provider_override || ""
|
||||
) ||
|
||||
defaultProviderName ||
|
||||
"",
|
||||
values.llm_model_version_override ||
|
||||
defaultModelName ||
|
||||
""
|
||||
) && (
|
||||
<BooleanFormField
|
||||
noPadding
|
||||
name={`enabled_tools_map.${imageGenerationTool.id}`}
|
||||
label="Image Generation Tool"
|
||||
onChange={() => {
|
||||
toggleToolInValues(imageGenerationTool.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-2 flex flex-col ml-1">
|
||||
{imageGenerationTool && (
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-fit ${
|
||||
!checkLLMSupportsImageInput(
|
||||
providerDisplayNameToProviderName.get(
|
||||
values.llm_model_provider_override || ""
|
||||
) || "",
|
||||
values.llm_model_version_override || ""
|
||||
)
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<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 && (
|
||||
<>
|
||||
<BooleanFormField
|
||||
name={`enabled_tools_map.${searchTool.id}`}
|
||||
label="Search Tool"
|
||||
noPadding
|
||||
onChange={() => {
|
||||
setFieldValue("num_chunks", null);
|
||||
toggleToolInValues(searchTool.id);
|
||||
}}
|
||||
/>
|
||||
|
||||
{searchToolEnabled() && (
|
||||
<CollapsibleSection prompt="Configure Search">
|
||||
<div>
|
||||
|
@ -20,4 +20,5 @@ export interface CombinedSettings {
|
||||
enterpriseSettings: EnterpriseSettings | null;
|
||||
customAnalyticsScript: string | null;
|
||||
isMobile?: boolean;
|
||||
webVersion: string | null;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
BackendChatSession,
|
||||
BackendMessage,
|
||||
ChatFileType,
|
||||
ChatSession,
|
||||
ChatSessionSharedStatus,
|
||||
DocumentsResponse,
|
||||
FileDescriptor,
|
||||
@ -26,6 +27,7 @@ import {
|
||||
buildLatestMessageChain,
|
||||
checkAnyAssistantHasSearch,
|
||||
createChatSession,
|
||||
deleteChatSession,
|
||||
getCitedDocumentsFromMessage,
|
||||
getHumanAndAIMessageFromMessageNumber,
|
||||
getLastSuccessfulMessageId,
|
||||
@ -79,6 +81,7 @@ import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
|
||||
import FixedLogo from "./shared_chat_search/FixedLogo";
|
||||
import { getSecondsUntilExpiration } from "@/lib/time";
|
||||
import { SetDefaultModelModal } from "./modal/SetDefaultModelModal";
|
||||
import { DeleteChatModal } from "./modal/DeleteChatModal";
|
||||
|
||||
const TEMP_USER_MESSAGE_ID = -1;
|
||||
const TEMP_ASSISTANT_MESSAGE_ID = -2;
|
||||
@ -1188,7 +1191,17 @@ export function ChatPage({
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [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 toggleDocumentSelectionAspects = () => {
|
||||
setDocumentSelection((documentSelection) => !documentSelection);
|
||||
@ -1229,11 +1242,29 @@ export function ChatPage({
|
||||
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
|
||||
chatSessionId={chatSessionIdRef.current}
|
||||
existingSharedStatus={chatSessionSharedStatus}
|
||||
onClose={() => setSharingModalVisible(false)}
|
||||
chatSessionId={sharedChatSession.id}
|
||||
existingSharedStatus={sharedChatSession.shared_status}
|
||||
onClose={() => setSharedChatSession(null)}
|
||||
onShare={(shared) =>
|
||||
setChatSessionSharedStatus(
|
||||
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="h-[100dvh] overflow-y-hidden">
|
||||
<div className="w-full">
|
||||
@ -1277,6 +1315,8 @@ export function ChatPage({
|
||||
folders={folders}
|
||||
openedFolders={openedFolders}
|
||||
removeToggle={removeToggle}
|
||||
showShareModal={showShareModal}
|
||||
showDeleteModal={showDeleteModal}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@ export function InMessageImage({ fileId }: { fileId: string }) {
|
||||
height={1200}
|
||||
alt="Chat Message Image"
|
||||
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"}`}
|
||||
onClick={() => setFullImageShowing(true)}
|
||||
src={buildImgUrl(fileId)}
|
||||
|
@ -82,10 +82,12 @@ const FolderItem = ({
|
||||
}
|
||||
};
|
||||
|
||||
const saveFolderName = async () => {
|
||||
const saveFolderName = async (continueEditing?: boolean) => {
|
||||
try {
|
||||
await updateFolderName(folder.folder_id, editedFolderName);
|
||||
setIsEditing(false);
|
||||
if (!continueEditing) {
|
||||
setIsEditing(false);
|
||||
}
|
||||
router.refresh(); // Refresh values to update the sidebar
|
||||
} catch (error) {
|
||||
setPopup({ message: "Failed to save folder name", type: "error" });
|
||||
@ -171,7 +173,7 @@ const FolderItem = ({
|
||||
{showDeleteConfirm && (
|
||||
<div
|
||||
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">
|
||||
Are you sure you want to delete <i>{folder.folder_name}</i>? All the
|
||||
@ -216,6 +218,7 @@ const FolderItem = ({
|
||||
value={editedFolderName}
|
||||
onChange={handleFolderNameChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={() => saveFolderName(true)}
|
||||
className="text-sm px-1 flex-1 min-w-0 -my-px mr-2"
|
||||
/>
|
||||
) : (
|
||||
@ -239,20 +242,13 @@ const FolderItem = ({
|
||||
<FiTrash size={16} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div
|
||||
onClick={deleteFolderHandler}
|
||||
className="hover:bg-black/10 p-1 -m-1 rounded ml-2"
|
||||
>
|
||||
<FiTrash size={16} />
|
||||
</div> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isEditing && (
|
||||
<div className="flex ml-auto my-auto">
|
||||
<div
|
||||
onClick={saveFolderName}
|
||||
onClick={() => saveFolderName()}
|
||||
className="hover:bg-black/10 p-1 -m-1 rounded"
|
||||
>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
@ -16,12 +16,6 @@ export const DeleteChatModal = ({
|
||||
<>
|
||||
<div className="flex mb-4">
|
||||
<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>
|
||||
<p className="mb-4">
|
||||
Click below to confirm that you want to delete{" "}
|
||||
|
@ -98,6 +98,9 @@ export function SetDefaultModelModal({
|
||||
});
|
||||
}
|
||||
};
|
||||
const defaultProvider = llmProviders.find(
|
||||
(llmProvider) => llmProvider.is_default_provider
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
@ -118,46 +121,46 @@ export function SetDefaultModelModal({
|
||||
{defaultModel == null && " No default model has been selected!"}
|
||||
</Text>
|
||||
<div className="w-full flex text-sm flex-col">
|
||||
<div key={-1} className="w-full border-b hover:bg-background-50">
|
||||
<td className="min-w-[80px]">
|
||||
{defaultModel == null ? (
|
||||
<Badge>selected</Badge>
|
||||
) : (
|
||||
<input
|
||||
type="radio"
|
||||
name="credentialSelection"
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
handleChangedefaultModel(null);
|
||||
}}
|
||||
className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="p-2">System default</td>
|
||||
<div
|
||||
key={-1}
|
||||
className="w-full border-b flex items-center gap-x-2 hover:bg-background-50"
|
||||
>
|
||||
<input
|
||||
checked={defaultModelDestructured?.modelName == null}
|
||||
type="radio"
|
||||
name="credentialSelection"
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
handleChangedefaultModel(null);
|
||||
}}
|
||||
className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
{
|
||||
<td className="p-2">
|
||||
System default{" "}
|
||||
{defaultProvider?.default_model_name &&
|
||||
`(${getDisplayNameForModel(defaultProvider?.default_model_name)})`}
|
||||
</td>
|
||||
}
|
||||
</div>
|
||||
|
||||
{llmOptions.map(({ name, value }, index) => {
|
||||
return (
|
||||
<div
|
||||
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]">
|
||||
{defaultModelDestructured?.modelName != name ? (
|
||||
<input
|
||||
type="radio"
|
||||
name="credentialSelection"
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
handleChangedefaultModel(value);
|
||||
}}
|
||||
className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
) : (
|
||||
<Badge>selected</Badge>
|
||||
)}
|
||||
</td>
|
||||
<input
|
||||
checked={defaultModelDestructured?.modelName == name}
|
||||
type="radio"
|
||||
name="credentialSelection"
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
handleChangedefaultModel(value);
|
||||
}}
|
||||
className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
|
||||
<td className="p-2">
|
||||
{getDisplayNameForModel(name)}{" "}
|
||||
{defaultModelDestructured &&
|
||||
|
@ -27,6 +27,8 @@ export function ChatSessionDisplay({
|
||||
isSelected,
|
||||
skipGradient,
|
||||
closeSidebar,
|
||||
showShareModal,
|
||||
showDeleteModal,
|
||||
}: {
|
||||
chatSession: ChatSession;
|
||||
isSelected: boolean;
|
||||
@ -35,6 +37,8 @@ export function ChatSessionDisplay({
|
||||
// if not set, the gradient will still be applied and cause weirdness
|
||||
skipGradient?: boolean;
|
||||
closeSidebar?: () => void;
|
||||
showShareModal?: (chatSession: ChatSession) => void;
|
||||
showDeleteModal?: (chatSession: ChatSession) => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
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
|
||||
className="flex my-1 group relative"
|
||||
key={chatSession.id}
|
||||
@ -186,11 +174,13 @@ export function ChatSessionDisplay({
|
||||
}
|
||||
popover={
|
||||
<div className="border border-border rounded-lg bg-background z-50 w-32">
|
||||
<DefaultDropdownElement
|
||||
name="Share"
|
||||
icon={FiShare2}
|
||||
onSelect={() => setIsShareModalVisible(true)}
|
||||
/>
|
||||
{showShareModal && (
|
||||
<DefaultDropdownElement
|
||||
name="Share"
|
||||
icon={FiShare2}
|
||||
onSelect={() => showShareModal(chatSession)}
|
||||
/>
|
||||
)}
|
||||
<DefaultDropdownElement
|
||||
name="Rename"
|
||||
icon={FiEdit2}
|
||||
@ -204,12 +194,14 @@ export function ChatSessionDisplay({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setIsDeletionModalVisible(true)}
|
||||
className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`}
|
||||
>
|
||||
<FiTrash size={16} />
|
||||
</div>
|
||||
{showDeleteModal && (
|
||||
<div
|
||||
onClick={() => showDeleteModal(chatSession)}
|
||||
className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`}
|
||||
>
|
||||
<FiTrash size={16} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -32,6 +32,8 @@ interface HistorySidebarProps {
|
||||
toggled?: boolean;
|
||||
removeToggle?: () => void;
|
||||
reset?: () => void;
|
||||
showShareModal?: (chatSession: ChatSession) => void;
|
||||
showDeleteModal?: (chatSession: ChatSession) => void;
|
||||
}
|
||||
|
||||
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
@ -46,6 +48,8 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
openedFolders,
|
||||
toggleSidebar,
|
||||
removeToggle,
|
||||
showShareModal,
|
||||
showDeleteModal,
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) => {
|
||||
@ -168,6 +172,8 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
)}
|
||||
<div className="border-b border-border pb-4 mx-3" />
|
||||
<PagesTab
|
||||
showDeleteModal={showDeleteModal}
|
||||
showShareModal={showShareModal}
|
||||
closeSidebar={removeToggle}
|
||||
page={page}
|
||||
existingChats={existingChats}
|
||||
|
@ -17,6 +17,8 @@ export function PagesTab({
|
||||
folders,
|
||||
openedFolders,
|
||||
closeSidebar,
|
||||
showShareModal,
|
||||
showDeleteModal,
|
||||
}: {
|
||||
page: pageType;
|
||||
existingChats?: ChatSession[];
|
||||
@ -24,6 +26,8 @@ export function PagesTab({
|
||||
folders?: Folder[];
|
||||
openedFolders?: { [key: number]: boolean };
|
||||
closeSidebar?: () => void;
|
||||
showShareModal?: (chatSession: ChatSession) => void;
|
||||
showDeleteModal?: (chatSession: ChatSession) => void;
|
||||
}) {
|
||||
const groupedChatSessions = existingChats
|
||||
? groupSessionsByDateRange(existingChats)
|
||||
@ -117,6 +121,8 @@ export function PagesTab({
|
||||
return (
|
||||
<div key={`${chat.id}-${chat.name}`}>
|
||||
<ChatSessionDisplay
|
||||
showDeleteModal={showDeleteModal}
|
||||
showShareModal={showShareModal}
|
||||
closeSidebar={closeSidebar}
|
||||
search={page == "search"}
|
||||
chatSession={chat}
|
||||
|
@ -27,7 +27,6 @@ import { FiActivity, FiBarChart2 } from "react-icons/fi";
|
||||
import { UserDropdown } from "../UserDropdown";
|
||||
import { User } from "@/lib/types";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { PencilCircle } from "@phosphor-icons/react";
|
||||
|
||||
export function ClientLayout({
|
||||
user,
|
||||
|
@ -23,6 +23,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
||||
if (!combinedSettings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const settings = combinedSettings.settings;
|
||||
const enterpriseSettings = combinedSettings.enterpriseSettings;
|
||||
|
||||
@ -93,6 +94,16 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
||||
</div>
|
||||
))}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
@ -344,6 +344,7 @@ interface BooleanFormFieldProps {
|
||||
small?: boolean;
|
||||
alignTop?: boolean;
|
||||
noLabel?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const BooleanFormField = ({
|
||||
@ -354,12 +355,14 @@ export const BooleanFormField = ({
|
||||
noPadding,
|
||||
noLabel,
|
||||
small,
|
||||
disabled,
|
||||
alignTop,
|
||||
}: BooleanFormFieldProps) => {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<label className="flex text-sm">
|
||||
<Field
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
type="checkbox"
|
||||
className={`${noPadding ? "mr-3" : "mx-3"} px-5 w-3.5 h-3.5 ${
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { EnterpriseSettings, Settings } from "@/app/admin/settings/interfaces";
|
||||
import {
|
||||
CombinedSettings,
|
||||
EnterpriseSettings,
|
||||
Settings,
|
||||
} from "@/app/admin/settings/interfaces";
|
||||
import {
|
||||
CUSTOM_ANALYTICS_ENABLED,
|
||||
SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED,
|
||||
} from "@/lib/constants";
|
||||
import { fetchSS } from "@/lib/utilsSS";
|
||||
import { getWebVersion } from "@/lib/version";
|
||||
|
||||
export async function fetchSettingsSS() {
|
||||
const tasks = [fetchSS("/settings")];
|
||||
@ -22,23 +27,18 @@ export async function fetchSettingsSS() {
|
||||
const customAnalyticsScript = (
|
||||
tasks.length > 2 ? await results[2].json() : null
|
||||
) as string | null;
|
||||
const webVersion = getWebVersion();
|
||||
|
||||
const combinedSettings: CombinedSettings = {
|
||||
settings,
|
||||
enterpriseSettings,
|
||||
customAnalyticsScript,
|
||||
webVersion,
|
||||
};
|
||||
|
||||
return combinedSettings;
|
||||
}
|
||||
|
||||
export interface CombinedSettings {
|
||||
settings: Settings;
|
||||
enterpriseSettings: EnterpriseSettings | null;
|
||||
customAnalyticsScript: string | null;
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
let cachedSettings: CombinedSettings;
|
||||
|
||||
export async function getCombinedSettings({
|
||||
|
Loading…
x
Reference in New Issue
Block a user