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 = ({
<>
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 (
|