fix: added empty message on file upload handling, fixed time calculation on onboarding (#316)

* fixed miscalculation of provider steps

* Handled empty message with file

* fixed same key
This commit is contained in:
Lucas Oliveira 2025-10-27 18:08:27 -03:00 committed by GitHub
parent 31681bb0d9
commit a7af519d01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 90 additions and 53 deletions

View file

@ -594,8 +594,8 @@ export function Navigation({
No documents yet No documents yet
</div> </div>
) : ( ) : (
newConversationFiles?.map((file) => ( newConversationFiles?.map((file, index) => (
<div key={`${file}`} className="flex-1 min-w-0 px-3"> <div key={`${file}-${index}`} className="flex-1 min-w-0 px-3">
<div className="text-mmd font-medium text-foreground truncate"> <div className="text-mmd font-medium text-foreground truncate">
{file} {file}
</div> </div>

View file

@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
import { Message } from "./message"; import { Message } from "./message";
interface UserMessageProps { interface UserMessageProps {
content: string; content: string | undefined;
isCompleted?: boolean; isCompleted?: boolean;
animate?: boolean; animate?: boolean;
files?: string; files?: string;

View file

@ -9,7 +9,7 @@ import { type EndpointType, useChat } from "@/contexts/chat-context";
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
import { useTask } from "@/contexts/task-context"; import { useTask } from "@/contexts/task-context";
import { useChatStreaming } from "@/hooks/useChatStreaming"; import { useChatStreaming } from "@/hooks/useChatStreaming";
import { FILES_REGEX } from "@/lib/constants"; import { FILE_CONFIRMATION, FILES_REGEX } from "@/lib/constants";
import { useLoadingStore } from "@/stores/loadingStore"; import { useLoadingStore } from "@/stores/loadingStore";
import { useGetNudgesQuery } from "../api/queries/useGetNudgesQuery"; import { useGetNudgesQuery } from "../api/queries/useGetNudgesQuery";
import { AssistantMessage } from "./components/assistant-message"; import { AssistantMessage } from "./components/assistant-message";
@ -911,9 +911,9 @@ function ChatPage() {
} }
// Only send message if there's input text // Only send message if there's input text
if (input.trim()) { if (input.trim() || uploadedFile) {
// Pass the responseId from upload (if any) to handleSendMessage // Pass the responseId from upload (if any) to handleSendMessage
handleSendMessage(input, uploadedResponseId || undefined); handleSendMessage(!input.trim() ? FILE_CONFIRMATION : input, uploadedResponseId || undefined);
} }
}; };
@ -1154,6 +1154,8 @@ function ChatPage() {
} }
}; };
console.log(messages)
return ( return (
<> <>
{/* Debug header - only show in debug mode */} {/* Debug header - only show in debug mode */}
@ -1236,7 +1238,10 @@ function ChatPage() {
? message.source !== "langflow" ? message.source !== "langflow"
: false : false
} }
content={message.content} content={index >= 2
&& (messages[index - 2]?.content.match(
FILES_REGEX,
)?.[0] ?? undefined) && message.content === FILE_CONFIRMATION ? undefined : message.content}
files={ files={
index >= 2 index >= 2
? messages[index - 2]?.content.match( ? messages[index - 2]?.content.match(

View file

@ -19,34 +19,30 @@ export function AnimatedProviderSteps({
setCurrentStep, setCurrentStep,
steps, steps,
storageKey = "provider-steps", storageKey = "provider-steps",
processingStartTime,
}: { }: {
currentStep: number; currentStep: number;
isCompleted: boolean; isCompleted: boolean;
setCurrentStep: (step: number) => void; setCurrentStep: (step: number) => void;
steps: string[]; steps: string[];
storageKey?: string; storageKey?: string;
processingStartTime?: number | null;
}) { }) {
const [startTime, setStartTime] = useState<number | null>(null); const [startTime, setStartTime] = useState<number | null>(null);
const [elapsedTime, setElapsedTime] = useState<number>(0); const [elapsedTime, setElapsedTime] = useState<number>(0);
// Initialize start time from local storage or set new one // Initialize start time from prop or local storage
useEffect(() => { useEffect(() => {
const storedStartTime = localStorage.getItem(`${storageKey}-start`);
const storedElapsedTime = localStorage.getItem(`${storageKey}-elapsed`); const storedElapsedTime = localStorage.getItem(`${storageKey}-elapsed`);
if (isCompleted && storedElapsedTime) { if (isCompleted && storedElapsedTime) {
// If completed, use stored elapsed time // If completed, use stored elapsed time
setElapsedTime(parseFloat(storedElapsedTime)); setElapsedTime(parseFloat(storedElapsedTime));
} else if (storedStartTime) { } else if (processingStartTime) {
// If in progress, use stored start time // Use the start time passed from parent (when user clicked Complete)
setStartTime(parseInt(storedStartTime)); setStartTime(processingStartTime);
} else {
// First time, set new start time
const now = Date.now();
setStartTime(now);
localStorage.setItem(`${storageKey}-start`, now.toString());
} }
}, [storageKey, isCompleted]); }, [storageKey, isCompleted, processingStartTime]);
// Progress through steps // Progress through steps
useEffect(() => { useEffect(() => {
@ -64,7 +60,6 @@ export function AnimatedProviderSteps({
const elapsed = Date.now() - startTime; const elapsed = Date.now() - startTime;
setElapsedTime(elapsed); setElapsedTime(elapsed);
localStorage.setItem(`${storageKey}-elapsed`, elapsed.toString()); localStorage.setItem(`${storageKey}-elapsed`, elapsed.toString());
localStorage.removeItem(`${storageKey}-start`);
} }
}, [isCompleted, startTime, storageKey]); }, [isCompleted, startTime, storageKey]);

View file

@ -32,12 +32,11 @@ interface OnboardingCardProps {
setLoadingStatus?: (status: string[]) => void; setLoadingStatus?: (status: string[]) => void;
} }
const STEP_LIST = [ const STEP_LIST = [
"Setting up your model provider", "Setting up your model provider",
"Defining schema", "Defining schema",
"Configuring Langflow", "Configuring Langflow",
"Ingesting sample data", "Ingesting sample data",
]; ];
const TOTAL_PROVIDER_STEPS = STEP_LIST.length; const TOTAL_PROVIDER_STEPS = STEP_LIST.length;
@ -106,7 +105,13 @@ const OnboardingCard = ({
llm_model: "", llm_model: "",
}); });
const [currentStep, setCurrentStep] = useState<number | null>(isCompleted ? TOTAL_PROVIDER_STEPS : null); const [currentStep, setCurrentStep] = useState<number | null>(
isCompleted ? TOTAL_PROVIDER_STEPS : null,
);
const [processingStartTime, setProcessingStartTime] = useState<number | null>(
null,
);
// Query tasks to track completion // Query tasks to track completion
const { data: tasks } = useGetTasksQuery({ const { data: tasks } = useGetTasksQuery({
@ -131,8 +136,8 @@ const OnboardingCard = ({
// If no active tasks and we've started onboarding, complete it // If no active tasks and we've started onboarding, complete it
if ( if (
(!activeTasks || (activeTasks.processed_files ?? 0) > 0) && (!activeTasks || (activeTasks.processed_files ?? 0) > 0) &&
tasks.length > 0 tasks.length > 0 &&
&& !isCompleted !isCompleted
) { ) {
// Set to final step to show "Done" // Set to final step to show "Done"
setCurrentStep(TOTAL_PROVIDER_STEPS); setCurrentStep(TOTAL_PROVIDER_STEPS);
@ -189,6 +194,8 @@ const OnboardingCard = ({
onboardingData.project_id = settings.project_id; onboardingData.project_id = settings.project_id;
} }
// Record the start time when user clicks Complete
setProcessingStartTime(Date.now());
onboardingMutation.mutate(onboardingData); onboardingMutation.mutate(onboardingData);
setCurrentStep(0); setCurrentStep(0);
}; };
@ -211,30 +218,55 @@ const OnboardingCard = ({
onValueChange={handleSetModelProvider} onValueChange={handleSetModelProvider}
> >
<TabsList className="mb-4"> <TabsList className="mb-4">
<TabsTrigger <TabsTrigger value="openai">
value="openai" <div
> className={cn(
<div className={cn("flex items-center justify-center gap-2 w-8 h-8 rounded-md", modelProvider === "openai" ? "bg-white" : "bg-muted")}> "flex items-center justify-center gap-2 w-8 h-8 rounded-md",
<OpenAILogo className={cn("w-4 h-4 shrink-0", modelProvider === "openai" ? "text-black" : "text-muted-foreground")} /> modelProvider === "openai" ? "bg-white" : "bg-muted",
)}
>
<OpenAILogo
className={cn(
"w-4 h-4 shrink-0",
modelProvider === "openai"
? "text-black"
: "text-muted-foreground",
)}
/>
</div> </div>
OpenAI OpenAI
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger value="watsonx">
value="watsonx" <div
> className={cn(
<div className={cn("flex items-center justify-center gap-2 w-8 h-8 rounded-md", modelProvider === "watsonx" ? "bg-[#1063FE]" : "bg-muted")}> "flex items-center justify-center gap-2 w-8 h-8 rounded-md",
<IBMLogo className={cn("w-4 h-4 shrink-0", modelProvider === "watsonx" ? "text-white" : "text-muted-foreground")} /> modelProvider === "watsonx" ? "bg-[#1063FE]" : "bg-muted",
)}
>
<IBMLogo
className={cn(
"w-4 h-4 shrink-0",
modelProvider === "watsonx"
? "text-white"
: "text-muted-foreground",
)}
/>
</div> </div>
IBM watsonx.ai IBM watsonx.ai
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger value="ollama">
value="ollama" <div
> className={cn(
<div className={cn("flex items-center justify-center gap-2 w-8 h-8 rounded-md", modelProvider === "ollama" ? "bg-white" : "bg-muted")}> "flex items-center justify-center gap-2 w-8 h-8 rounded-md",
modelProvider === "ollama" ? "bg-white" : "bg-muted",
)}
>
<OllamaLogo <OllamaLogo
className={cn( className={cn(
"w-4 h-4 shrink-0", "w-4 h-4 shrink-0",
modelProvider === "ollama" ? "text-black" : "text-muted-foreground", modelProvider === "ollama"
? "text-black"
: "text-muted-foreground",
)} )}
/> />
</div> </div>
@ -285,11 +317,13 @@ const OnboardingCard = ({
</TooltipTrigger> </TooltipTrigger>
{!isComplete && ( {!isComplete && (
<TooltipContent> <TooltipContent>
{isLoadingModels ? "Loading models..." : (!!settings.llm_model && {isLoadingModels
!!settings.embedding_model && ? "Loading models..."
!isDoclingHealthy : !!settings.llm_model &&
? "docling-serve must be running to continue" !!settings.embedding_model &&
: "Please fill in all required fields")} !isDoclingHealthy
? "docling-serve must be running to continue"
: "Please fill in all required fields"}
</TooltipContent> </TooltipContent>
)} )}
</Tooltip> </Tooltip>
@ -303,11 +337,12 @@ const OnboardingCard = ({
transition={{ duration: 0.4, ease: "easeInOut" }} transition={{ duration: 0.4, ease: "easeInOut" }}
> >
<AnimatedProviderSteps <AnimatedProviderSteps
currentStep={currentStep} currentStep={currentStep}
isCompleted={isCompleted} isCompleted={isCompleted}
setCurrentStep={setCurrentStep} setCurrentStep={setCurrentStep}
steps={STEP_LIST} steps={STEP_LIST}
/> processingStartTime={processingStartTime}
/>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>

View file

@ -35,4 +35,6 @@ export const TOTAL_ONBOARDING_STEPS = 3;
export const ONBOARDING_STEP_KEY = "onboarding_current_step"; export const ONBOARDING_STEP_KEY = "onboarding_current_step";
export const FILES_REGEX = export const FILES_REGEX =
/(?<=I'm uploading a document called ['"])[^'"]+\.[^.]+(?=['"]\. Here is its content:)/; /(?<=I'm uploading a document called ['"])[^'"]+\.[^.]+(?=['"]\. Here is its content:)/;
export const FILE_CONFIRMATION = "Confirm that you received this file.";