diff --git a/backend/danswer/chat/prompts.yaml b/backend/danswer/chat/prompts.yaml
index 89fffcfa8..d83d4ede4 100644
--- a/backend/danswer/chat/prompts.yaml
+++ b/backend/danswer/chat/prompts.yaml
@@ -35,14 +35,12 @@ prompts:
description: "Generates images based on user prompts!"
system: >
You are an advanced image generation system capable of creating diverse and detailed images.
- The current date is DANSWER_DATETIME_REPLACEMENT.
You can interpret user prompts and generate high-quality, creative images that match their descriptions.
You always strive to create safe and appropriate content, avoiding any harmful or offensive imagery.
task: >
Generate an image based on the user's description.
- If the user's request is unclear or too vague, ask for clarification to ensure the best possible result.
Provide a detailed description of the generated image, including key elements, colors, and composition.
diff --git a/web/src/app/chat/files/images/InMessageImage.tsx b/web/src/app/chat/files/images/InMessageImage.tsx
index 929914eb3..501a04447 100644
--- a/web/src/app/chat/files/images/InMessageImage.tsx
+++ b/web/src/app/chat/files/images/InMessageImage.tsx
@@ -1,11 +1,11 @@
-"use client";
-
import { useState } from "react";
import { FullImageModal } from "./FullImageModal";
import { buildImgUrl } from "./utils";
+import Image from "next/image";
export function InMessageImage({ fileId }: { fileId: string }) {
const [fullImageShowing, setFullImageShowing] = useState(false);
+ const [imageLoaded, setImageLoaded] = useState(false);
return (
<>
@@ -15,12 +15,22 @@ export function InMessageImage({ fileId }: { fileId: string }) {
onOpenChange={(open) => setFullImageShowing(open)}
/>
- setFullImageShowing(true)}
- src={buildImgUrl(fileId)}
- loading="lazy"
- />
+
+ {!imageLoaded && (
+
+ )}
+
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
+ ${imageLoaded ? "opacity-100" : "opacity-0"}`}
+ onClick={() => setFullImageShowing(true)}
+ src={buildImgUrl(fileId)}
+ loading="lazy"
+ />
+
>
);
}
diff --git a/web/src/app/chat/message/Messages.tsx b/web/src/app/chat/message/Messages.tsx
index 9ee7f47a6..e8ded751f 100644
--- a/web/src/app/chat/message/Messages.tsx
+++ b/web/src/app/chat/message/Messages.tsx
@@ -59,6 +59,7 @@ import { Tooltip } from "@/components/tooltip/Tooltip";
import { useMouseTracking } from "./hooks";
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
import { SettingsContext } from "@/components/settings/SettingsProvider";
+import GeneratingImageDisplay from "../tools/GeneratingImageDisplay";
const TOOLS_WITH_CUSTOM_HANDLING = [
SEARCH_TOOL_NAME,
@@ -153,6 +154,7 @@ export const AIMessage = ({
handleForceSearch?: () => void;
retrievalDisabled?: boolean;
}) => {
+ const toolCallGenerating = toolCall && !toolCall.tool_result;
const processContent = (content: string | JSX.Element) => {
if (typeof content !== "string") {
return content;
@@ -168,7 +170,7 @@ export const AIMessage = ({
}
}
- return content + (!isComplete ? " [*]() " : "");
+ return content + (!isComplete && !toolCallGenerating ? " [*]() " : "");
};
const finalContent = processContent(content as string);
@@ -344,15 +346,7 @@ export const AIMessage = ({
{toolCall &&
toolCall.tool_name === IMAGE_GENERATION_TOOL_NAME &&
- !toolCall.tool_result && (
-
- }
- isRunning={!toolCall.tool_result}
- />
- )}
+ !toolCall.tool_result && }
{toolCall &&
toolCall.tool_name === INTERNET_SEARCH_TOOL_NAME && (
@@ -369,7 +363,7 @@ export const AIMessage = ({
/>
)}
- {content ? (
+ {content || files ? (
<>
diff --git a/web/src/app/chat/tools/GeneratingImageDisplay.tsx b/web/src/app/chat/tools/GeneratingImageDisplay.tsx
new file mode 100644
index 000000000..46df35d79
--- /dev/null
+++ b/web/src/app/chat/tools/GeneratingImageDisplay.tsx
@@ -0,0 +1,102 @@
+import React, { useState, useEffect, useRef } from "react";
+
+export default function GeneratingImageDisplay({ isCompleted = false }) {
+ const [progress, setProgress] = useState(0);
+ const progressRef = useRef(0);
+ const animationRef = useRef();
+ const startTimeRef = useRef(Date.now());
+
+ useEffect(() => {
+ // Animation setup
+ let lastUpdateTime = 0;
+ const updateInterval = 500;
+ const animationDuration = 30000;
+
+ const animate = (timestamp: number) => {
+ const elapsedTime = timestamp - startTimeRef.current;
+
+ // Calculate progress using logarithmic curve
+ const maxProgress = 99.9;
+ const progress =
+ maxProgress * (1 - Math.exp(-elapsedTime / animationDuration));
+
+ // Update progress if enough time has passed
+ if (timestamp - lastUpdateTime > updateInterval) {
+ progressRef.current = progress;
+ setProgress(Math.round(progress * 10) / 10);
+ lastUpdateTime = timestamp;
+ }
+
+ // Continue animation if not completed
+ if (!isCompleted && elapsedTime < animationDuration) {
+ animationRef.current = requestAnimationFrame(animate);
+ }
+ };
+
+ // Start animation
+ startTimeRef.current = performance.now();
+ animationRef.current = requestAnimationFrame(animate);
+
+ // Cleanup function
+ return () => {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ }
+ };
+ }, [isCompleted]);
+
+ // Handle completion
+ useEffect(() => {
+ if (isCompleted) {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ }
+ setProgress(100);
+ }
+ }, [isCompleted]);
+
+ return (
+
+ );
+}
diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx
index f94cc4135..992d30f15 100644
--- a/web/src/components/icons/icons.tsx
+++ b/web/src/components/icons/icons.tsx
@@ -2278,6 +2278,28 @@ export const NewChatIcon = ({
);
};
+export const ImageIcon = ({
+ size = 16,
+ className = defaultTailwindCSS,
+}: IconProps) => {
+ return (
+
+
+
+ );
+};
+
export const PaintingIcon = ({
size = 16,
className = defaultTailwindCSS,
diff --git a/web/src/components/search/SearchBar.tsx b/web/src/components/search/SearchBar.tsx
index 86dc71fdb..6892e89da 100644
--- a/web/src/components/search/SearchBar.tsx
+++ b/web/src/components/search/SearchBar.tsx
@@ -210,7 +210,6 @@ export const FullSearchBar = ({
- {/*
*/}
{(ccPairs.length > 0 || documentSets.length > 0) && (
)}
- {/* ccPairs, documentSets, filterManager, finalAvailableDocumentSets, finalAvailableSources, tags */}
- {/*
/ */}
{toggleAgentic && (
diff --git a/web/src/components/search/filtering/Filters.tsx b/web/src/components/search/filtering/Filters.tsx
index 620976bd9..1d2f4a2da 100644
--- a/web/src/components/search/filtering/Filters.tsx
+++ b/web/src/components/search/filtering/Filters.tsx
@@ -81,7 +81,7 @@ export function SourceSelector({
showDocSidebar ? "4xl:block" : "!block"
} duration-1000 flex ease-out transition-all transform origin-top-right`}
>
-
+
Filters
@@ -469,46 +469,6 @@ export function HorizontalSourceSelector({
/>
)}
-
- {/*
- {timeRange && timeRange.selectValue && (
-
setTimeRange(null)}>
- {timeRange.selectValue}
-
- )}
- {selectedSources.map((source) => (
-
handleSourceSelect(source)}
- >
- <>
-
- {source.displayName}
- >
-
- ))}
- {selectedDocumentSets.map((documentSetName) => (
-
handleDocumentSetSelect(documentSetName)}
- >
- <>
-
- {documentSetName}
- >
-
- ))}
- {selectedTags.map((tag) => (
-
handleTagSelect(tag)}
- >
-
- {tag.tag_key}= {tag.tag_value}
-
-
- ))}
-
*/}
);
}