diff --git a/backend/danswer/tools/images/image_generation_tool.py b/backend/danswer/tools/images/image_generation_tool.py index 8b100df03..1a525f6a2 100644 --- a/backend/danswer/tools/images/image_generation_tool.py +++ b/backend/danswer/tools/images/image_generation_tool.py @@ -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( diff --git a/web/src/app/admin/assistants/AssistantEditor.tsx b/web/src/app/admin/assistants/AssistantEditor.tsx index ec323818f..65368f2a8 100644 --- a/web/src/app/admin/assistants/AssistantEditor.tsx +++ b/web/src/app/admin/assistants/AssistantEditor.tsx @@ -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({ -
- {imageGenerationTool && - checkLLMSupportsImageInput( - providerDisplayNameToProviderName.get( - values.llm_model_provider_override || "" - ) || - defaultProviderName || - "", - values.llm_model_version_override || - defaultModelName || - "" - ) && ( - { - toggleToolInValues(imageGenerationTool.id); - }} - /> - )} +
+ {imageGenerationTool && ( + + + +
+ { + toggleToolInValues(imageGenerationTool.id); + }} + disabled={ + !checkLLMSupportsImageInput( + providerDisplayNameToProviderName.get( + values.llm_model_provider_override || "" + ) || "", + values.llm_model_version_override || "" + ) + } + /> +
+
+ {!checkLLMSupportsImageInput( + providerDisplayNameToProviderName.get( + values.llm_model_provider_override || "" + ) || "", + values.llm_model_version_override || "" + ) && ( + +

+ To use Image Generation, select GPT-4o as the + default model for this Assistant. +

+
+ )} +
+
+ )} + + {searchTool && ( + + + +
+ { + setFieldValue("num_chunks", null); + toggleToolInValues(searchTool.id); + }} + disabled={ccPairs.length === 0} + /> +
+
+ {ccPairs.length === 0 && ( + +

+ To use the Search Tool, you need to have at + least one Connector-Credential pair configured. +

+
+ )} +
+
+ )} {ccPairs.length > 0 && searchTool && ( <> - { - setFieldValue("num_chunks", null); - toggleToolInValues(searchTool.id); - }} - /> - {searchToolEnabled() && (
diff --git a/web/src/app/admin/settings/interfaces.ts b/web/src/app/admin/settings/interfaces.ts index 571cd193b..b6e2b2bd7 100644 --- a/web/src/app/admin/settings/interfaces.ts +++ b/web/src/app/admin/settings/interfaces.ts @@ -20,4 +20,5 @@ export interface CombinedSettings { enterpriseSettings: EnterpriseSettings | null; customAnalyticsScript: string | null; isMobile?: boolean; + webVersion: string | null; } diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index dc1228647..8f76c4a59 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -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(); + const [deletingChatSession, setDeletingChatSession] = + useState(); + 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 && ( + 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 && ( 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 && ( + setSharingModalVisible(false)} + /> + )}
@@ -1277,6 +1315,8 @@ export function ChatPage({ folders={folders} openedFolders={openedFolders} removeToggle={removeToggle} + showShareModal={showShareModal} + showDeleteModal={showDeleteModal} />
diff --git a/web/src/app/chat/files/images/InMessageImage.tsx b/web/src/app/chat/files/images/InMessageImage.tsx index 464fc1b09..6af28350b 100644 --- a/web/src/app/chat/files/images/InMessageImage.tsx +++ b/web/src/app/chat/files/images/InMessageImage.tsx @@ -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)} diff --git a/web/src/app/chat/folders/FolderList.tsx b/web/src/app/chat/folders/FolderList.tsx index c57f97bc1..fb6bb17cd 100644 --- a/web/src/app/chat/folders/FolderList.tsx +++ b/web/src/app/chat/folders/FolderList.tsx @@ -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 && (

Are you sure you want to delete {folder.folder_name}? 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 = ({

- - {/*
- -
*/}
)} {isEditing && (
saveFolderName()} className="hover:bg-black/10 p-1 -m-1 rounded" > @@ -310,6 +306,12 @@ export const FolderList = ({ } /> ))} + {folders.length == 1 && folders[0].chat_sessions.length == 0 && ( +

+ {" "} + Drag a chat into a folder to save for later{" "} +

+ )}
); }; diff --git a/web/src/app/chat/modal/DeleteChatModal.tsx b/web/src/app/chat/modal/DeleteChatModal.tsx index ed89c9613..f5268d8ed 100644 --- a/web/src/app/chat/modal/DeleteChatModal.tsx +++ b/web/src/app/chat/modal/DeleteChatModal.tsx @@ -16,12 +16,6 @@ export const DeleteChatModal = ({ <>

Delete chat?

-
- -

Click below to confirm that you want to delete{" "} diff --git a/web/src/app/chat/modal/SetDefaultModelModal.tsx b/web/src/app/chat/modal/SetDefaultModelModal.tsx index bf440dee5..b930870b5 100644 --- a/web/src/app/chat/modal/SetDefaultModelModal.tsx +++ b/web/src/app/chat/modal/SetDefaultModelModal.tsx @@ -98,6 +98,9 @@ export function SetDefaultModelModal({ }); } }; + const defaultProvider = llmProviders.find( + (llmProvider) => llmProvider.is_default_provider + ); return (

-
- - {defaultModel == null ? ( - selected - ) : ( - { - e.preventDefault(); - handleChangedefaultModel(null); - }} - className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out" - /> - )} - - System default +
+ { + e.preventDefault(); + handleChangedefaultModel(null); + }} + className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out" + /> + { + + System default{" "} + {defaultProvider?.default_model_name && + `(${getDisplayNameForModel(defaultProvider?.default_model_name)})`} + + }
{llmOptions.map(({ name, value }, index) => { return (
- - {defaultModelDestructured?.modelName != name ? ( - { - e.preventDefault(); - handleChangedefaultModel(value); - }} - className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out" - /> - ) : ( - selected - )} - + { + e.preventDefault(); + handleChangedefaultModel(value); + }} + className="form-radio ml-4 h-4 w-4 text-blue-600 transition duration-150 ease-in-out" + /> + {getDisplayNameForModel(name)}{" "} {defaultModelDestructured && diff --git a/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx b/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx index 69d8f7011..77376dc22 100644 --- a/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx +++ b/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx @@ -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 && ( - 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} - /> - )} - setIsShareModalVisible(true)} - /> + {showShareModal && ( + showShareModal(chatSession)} + /> + )}
-
setIsDeletionModalVisible(true)} - className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`} - > - -
+ {showDeleteModal && ( +
showDeleteModal(chatSession)} + className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`} + > + +
+ )}
))}
diff --git a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx index fc8596182..10a4fc052 100644 --- a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx +++ b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx @@ -32,6 +32,8 @@ interface HistorySidebarProps { toggled?: boolean; removeToggle?: () => void; reset?: () => void; + showShareModal?: (chatSession: ChatSession) => void; + showDeleteModal?: (chatSession: ChatSession) => void; } export const HistorySidebar = forwardRef( @@ -46,6 +48,8 @@ export const HistorySidebar = forwardRef( openedFolders, toggleSidebar, removeToggle, + showShareModal, + showDeleteModal, }, ref: ForwardedRef ) => { @@ -168,6 +172,8 @@ export const HistorySidebar = forwardRef( )}
void; + showShareModal?: (chatSession: ChatSession) => void; + showDeleteModal?: (chatSession: ChatSession) => void; }) { const groupedChatSessions = existingChats ? groupSessionsByDateRange(existingChats) @@ -117,6 +121,8 @@ export function PagesTab({ return (
))} + {combinedSettings.webVersion && ( +
+

+ Danswer version: {combinedSettings.webVersion} +

+
+ )}
); } diff --git a/web/src/components/admin/connectors/Field.tsx b/web/src/components/admin/connectors/Field.tsx index 86ed69fce..84345a75f 100644 --- a/web/src/components/admin/connectors/Field.tsx +++ b/web/src/components/admin/connectors/Field.tsx @@ -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 (