mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-10-09 20:55:06 +02: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:
@@ -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(
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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)}
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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{" "}
|
||||||
|
@@ -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 &&
|
||||||
|
@@ -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>
|
||||||
|
@@ -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}
|
||||||
|
@@ -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}
|
||||||
|
@@ -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,
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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 ${
|
||||||
|
@@ -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({
|
||||||
|
Reference in New Issue
Block a user