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 && ( +
+ )} + Chat Message Image 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} - - - ))} -
*/}
); }