Merge branch 'feat/new-onboarding' of github.com:langflow-ai/openrag into fix/provider-design
This commit is contained in:
commit
545a5f40fb
14 changed files with 364 additions and 187 deletions
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
|
|
@ -23,3 +23,4 @@ yarn-error.log*
|
|||
!package.json
|
||||
!package-lock.json
|
||||
!yarn.lock
|
||||
!scraper.config.json
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ const config = {
|
|||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: process.env.BASE_URL ? process.env.BASE_URL : '/',
|
||||
|
||||
// Control search engine indexing - set to true to prevent indexing
|
||||
noIndex: true,
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'langflow-ai', // Usually your GitHub org/user name.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"build:pdf": "npm run build && npm run serve & sleep 10 && npx docusaurus-to-pdf && pkill -f 'docusaurus serve'",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
|
|
|
|||
BIN
docs/pdf/openrag-documentation.pdf
Normal file
BIN
docs/pdf/openrag-documentation.pdf
Normal file
Binary file not shown.
7
docs/scraper.config.json
Normal file
7
docs/scraper.config.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"entryPoint": "http://localhost:3000",
|
||||
"outputDir": "./pdf/openrag-documentation.pdf",
|
||||
"customStyles": "table { max-width: 3500px !important; } .navbar, .footer, .breadcrumbs { display: none !important; }",
|
||||
"forceImages": true
|
||||
}
|
||||
12
docs/static/robots.txt
vendored
Normal file
12
docs/static/robots.txt
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Robots.txt for OpenRAG Documentation
|
||||
|
||||
# Block all crawlers by default
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
|
||||
# Allow specific crawlers if needed (uncomment when ready for launch)
|
||||
# User-agent: Googlebot
|
||||
# Allow: /
|
||||
|
||||
# Sitemap location (uncomment when ready for launch)
|
||||
# Sitemap: https://docs.openr.ag/sitemap.xml
|
||||
|
|
@ -26,6 +26,10 @@ import { Input } from "@/components/ui/input";
|
|||
import { Label } from "@/components/ui/label";
|
||||
import { useTask } from "@/contexts/task-context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
duplicateCheck,
|
||||
uploadFile as uploadFileUtil,
|
||||
} from "@/lib/upload-utils";
|
||||
import type { File as SearchFile } from "@/src/app/api/queries/useGetSearchQuery";
|
||||
|
||||
export function KnowledgeDropdown() {
|
||||
|
|
@ -163,8 +167,17 @@ export function KnowledgeDropdown() {
|
|||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
const resetFileInput = () => {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const files = event.target.files;
|
||||
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
|
||||
|
|
@ -172,37 +185,16 @@ export function KnowledgeDropdown() {
|
|||
setIsOpen(false);
|
||||
|
||||
try {
|
||||
// Check if filename already exists (using ORIGINAL filename)
|
||||
console.log("[Duplicate Check] Checking file:", file.name);
|
||||
const checkResponse = await fetch(
|
||||
`/api/documents/check-filename?filename=${encodeURIComponent(
|
||||
file.name
|
||||
)}`
|
||||
);
|
||||
|
||||
console.log("[Duplicate Check] Response status:", checkResponse.status);
|
||||
|
||||
if (!checkResponse.ok) {
|
||||
const errorText = await checkResponse.text();
|
||||
console.error("[Duplicate Check] Error response:", errorText);
|
||||
throw new Error(
|
||||
`Failed to check duplicates: ${checkResponse.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const checkData = await checkResponse.json();
|
||||
const checkData = await duplicateCheck(file);
|
||||
console.log("[Duplicate Check] Result:", checkData);
|
||||
|
||||
if (checkData.exists) {
|
||||
// Show duplicate handling dialog
|
||||
console.log("[Duplicate Check] Duplicate detected, showing dialog");
|
||||
setPendingFile(file);
|
||||
setDuplicateFilename(file.name);
|
||||
setShowDuplicateDialog(true);
|
||||
// Reset file input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
resetFileInput();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -217,105 +209,20 @@ export function KnowledgeDropdown() {
|
|||
}
|
||||
}
|
||||
|
||||
// Reset file input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
resetFileInput();
|
||||
};
|
||||
|
||||
const uploadFile = async (file: File, replace: boolean) => {
|
||||
setFileUploading(true);
|
||||
|
||||
// Trigger the same file upload event as the chat page
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fileUploadStart", {
|
||||
detail: { filename: file.name },
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("replace_duplicates", replace.toString());
|
||||
|
||||
// Use router upload and ingest endpoint (automatically routes based on configuration)
|
||||
const uploadIngestRes = await fetch("/api/router/upload_ingest", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const uploadIngestJson = await uploadIngestRes.json();
|
||||
|
||||
if (!uploadIngestRes.ok) {
|
||||
throw new Error(uploadIngestJson?.error || "Upload and ingest failed");
|
||||
}
|
||||
|
||||
// Extract results from the response - handle both unified and simple formats
|
||||
const fileId =
|
||||
uploadIngestJson?.upload?.id ||
|
||||
uploadIngestJson?.id ||
|
||||
uploadIngestJson?.task_id;
|
||||
const filePath =
|
||||
uploadIngestJson?.upload?.path || uploadIngestJson?.path || "uploaded";
|
||||
const runJson = uploadIngestJson?.ingestion;
|
||||
const deleteResult = uploadIngestJson?.deletion;
|
||||
console.log("c", uploadIngestJson);
|
||||
if (!fileId) {
|
||||
throw new Error("Upload successful but no file id returned");
|
||||
}
|
||||
// Check if ingestion actually succeeded
|
||||
if (
|
||||
runJson &&
|
||||
runJson.status !== "COMPLETED" &&
|
||||
runJson.status !== "SUCCESS"
|
||||
) {
|
||||
const errorMsg = runJson.error || "Ingestion pipeline failed";
|
||||
throw new Error(
|
||||
`Ingestion failed: ${errorMsg}. Try setting DISABLE_INGEST_WITH_LANGFLOW=true if you're experiencing Langflow component issues.`
|
||||
);
|
||||
}
|
||||
// Log deletion status if provided
|
||||
if (deleteResult) {
|
||||
if (deleteResult.status === "deleted") {
|
||||
console.log(
|
||||
"File successfully cleaned up from Langflow:",
|
||||
deleteResult.file_id
|
||||
);
|
||||
} else if (deleteResult.status === "delete_failed") {
|
||||
console.warn(
|
||||
"Failed to cleanup file from Langflow:",
|
||||
deleteResult.error
|
||||
);
|
||||
}
|
||||
}
|
||||
// Notify UI
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fileUploaded", {
|
||||
detail: {
|
||||
file: file,
|
||||
result: {
|
||||
file_id: fileId,
|
||||
file_path: filePath,
|
||||
run: runJson,
|
||||
deletion: deleteResult,
|
||||
unified: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await uploadFileUtil(file, replace);
|
||||
refetchTasks();
|
||||
} catch (error) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fileUploadError", {
|
||||
detail: {
|
||||
filename: file.name,
|
||||
error: error instanceof Error ? error.message : "Upload failed",
|
||||
},
|
||||
})
|
||||
);
|
||||
toast.error("Upload failed", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
window.dispatchEvent(new CustomEvent("fileUploadComplete"));
|
||||
setFileUploading(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -332,6 +239,7 @@ export function KnowledgeDropdown() {
|
|||
});
|
||||
|
||||
await uploadFile(pendingFile, true);
|
||||
|
||||
setPendingFile(null);
|
||||
setDuplicateFilename("");
|
||||
}
|
||||
|
|
|
|||
138
frontend/lib/upload-utils.ts
Normal file
138
frontend/lib/upload-utils.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
export interface DuplicateCheckResponse {
|
||||
exists: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface UploadFileResult {
|
||||
fileId: string;
|
||||
filePath: string;
|
||||
run: unknown;
|
||||
deletion: unknown;
|
||||
unified: boolean;
|
||||
raw: unknown;
|
||||
}
|
||||
|
||||
export async function duplicateCheck(
|
||||
file: File
|
||||
): Promise<DuplicateCheckResponse> {
|
||||
const response = await fetch(
|
||||
`/api/documents/check-filename?filename=${encodeURIComponent(file.name)}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
errorText || `Failed to check duplicates: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
replace = false
|
||||
): Promise<UploadFileResult> {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fileUploadStart", {
|
||||
detail: { filename: file.name },
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("replace_duplicates", replace.toString());
|
||||
|
||||
const uploadResponse = await fetch("/api/router/upload_ingest", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = await uploadResponse.json();
|
||||
} catch (error) {
|
||||
throw new Error("Upload failed: unable to parse server response");
|
||||
}
|
||||
|
||||
const uploadIngestJson =
|
||||
typeof payload === "object" && payload !== null ? payload : {};
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
const errorMessage =
|
||||
(uploadIngestJson as { error?: string }).error ||
|
||||
"Upload and ingest failed";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const fileId =
|
||||
(uploadIngestJson as { upload?: { id?: string } }).upload?.id ||
|
||||
(uploadIngestJson as { id?: string }).id ||
|
||||
(uploadIngestJson as { task_id?: string }).task_id;
|
||||
const filePath =
|
||||
(uploadIngestJson as { upload?: { path?: string } }).upload?.path ||
|
||||
(uploadIngestJson as { path?: string }).path ||
|
||||
"uploaded";
|
||||
const runJson = (uploadIngestJson as { ingestion?: unknown }).ingestion;
|
||||
const deletionJson = (uploadIngestJson as { deletion?: unknown }).deletion;
|
||||
|
||||
if (!fileId) {
|
||||
throw new Error("Upload successful but no file id returned");
|
||||
}
|
||||
|
||||
if (
|
||||
runJson &&
|
||||
typeof runJson === "object" &&
|
||||
"status" in (runJson as Record<string, unknown>) &&
|
||||
(runJson as { status?: string }).status !== "COMPLETED" &&
|
||||
(runJson as { status?: string }).status !== "SUCCESS"
|
||||
) {
|
||||
const errorMsg =
|
||||
(runJson as { error?: string }).error ||
|
||||
"Ingestion pipeline failed";
|
||||
throw new Error(
|
||||
`Ingestion failed: ${errorMsg}. Try setting DISABLE_INGEST_WITH_LANGFLOW=true if you're experiencing Langflow component issues.`
|
||||
);
|
||||
}
|
||||
|
||||
const result: UploadFileResult = {
|
||||
fileId,
|
||||
filePath,
|
||||
run: runJson,
|
||||
deletion: deletionJson,
|
||||
unified: true,
|
||||
raw: uploadIngestJson,
|
||||
};
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fileUploaded", {
|
||||
detail: {
|
||||
file,
|
||||
result: {
|
||||
file_id: fileId,
|
||||
file_path: filePath,
|
||||
run: runJson,
|
||||
deletion: deletionJson,
|
||||
unified: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fileUploadError", {
|
||||
detail: {
|
||||
filename: file.name,
|
||||
error:
|
||||
error instanceof Error ? error.message : "Upload failed",
|
||||
},
|
||||
})
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
window.dispatchEvent(new CustomEvent("fileUploadComplete"));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { StickToBottom } from "use-stick-to-bottom";
|
||||
import { AssistantMessage } from "@/app/chat/components/assistant-message";
|
||||
import { UserMessage } from "@/app/chat/components/user-message";
|
||||
|
|
@ -8,7 +8,9 @@ import Nudges from "@/app/chat/nudges";
|
|||
import type { Message } from "@/app/chat/types";
|
||||
import OnboardingCard from "@/app/onboarding/components/onboarding-card";
|
||||
import { useChatStreaming } from "@/hooks/useChatStreaming";
|
||||
|
||||
import { OnboardingStep } from "./onboarding-step";
|
||||
import OnboardingUpload from "./onboarding-upload";
|
||||
|
||||
export function OnboardingContent({
|
||||
handleStepComplete,
|
||||
|
|
@ -57,6 +59,12 @@ export function OnboardingContent({
|
|||
// Determine which message to show (streaming takes precedence)
|
||||
const displayMessage = streamingMessage || assistantMessage;
|
||||
|
||||
useEffect(() => {
|
||||
if (currentStep === 1 && !isLoading && !!displayMessage) {
|
||||
handleStepComplete();
|
||||
}
|
||||
}, [isLoading, displayMessage, handleStepComplete, currentStep]);
|
||||
|
||||
return (
|
||||
<StickToBottom
|
||||
className="flex h-full flex-1 flex-col"
|
||||
|
|
@ -66,6 +74,7 @@ export function OnboardingContent({
|
|||
>
|
||||
<StickToBottom.Content className="flex flex-col min-h-full overflow-x-hidden px-8 py-6">
|
||||
<div className="flex flex-col place-self-center w-full space-y-6">
|
||||
{/* Step 1 */}
|
||||
<OnboardingStep
|
||||
isVisible={currentStep >= 0}
|
||||
isCompleted={currentStep > 0}
|
||||
|
|
@ -74,6 +83,7 @@ export function OnboardingContent({
|
|||
<OnboardingCard onComplete={handleStepComplete} />
|
||||
</OnboardingStep>
|
||||
|
||||
{/* Step 2 */}
|
||||
<OnboardingStep
|
||||
isVisible={currentStep >= 1}
|
||||
isCompleted={currentStep > 1 || !!selectedNudge}
|
||||
|
|
@ -92,7 +102,7 @@ export function OnboardingContent({
|
|||
{currentStep >= 1 && !!selectedNudge && (
|
||||
<UserMessage
|
||||
content={selectedNudge}
|
||||
isCompleted={currentStep > 1}
|
||||
isCompleted={currentStep > 2}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -100,65 +110,41 @@ export function OnboardingContent({
|
|||
{currentStep >= 1 &&
|
||||
!!selectedNudge &&
|
||||
(displayMessage || isLoading) && (
|
||||
<>
|
||||
<AssistantMessage
|
||||
content={displayMessage?.content || ""}
|
||||
functionCalls={displayMessage?.functionCalls}
|
||||
messageIndex={0}
|
||||
expandedFunctionCalls={new Set()}
|
||||
onToggle={() => {}}
|
||||
isStreaming={!!streamingMessage}
|
||||
isCompleted={currentStep > 1}
|
||||
/>
|
||||
{!isLoading && displayMessage && currentStep === 1 && (
|
||||
<div className="mt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStepComplete}
|
||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<AssistantMessage
|
||||
content={displayMessage?.content || ""}
|
||||
functionCalls={displayMessage?.functionCalls}
|
||||
messageIndex={0}
|
||||
expandedFunctionCalls={new Set()}
|
||||
onToggle={() => {}}
|
||||
isStreaming={!!streamingMessage}
|
||||
isCompleted={currentStep > 2}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
{/* Step 3 */}
|
||||
<OnboardingStep
|
||||
isVisible={currentStep >= 2}
|
||||
isVisible={currentStep >= 2 && !isLoading && !!displayMessage}
|
||||
isCompleted={currentStep > 2}
|
||||
text="Step 2: Connect your model"
|
||||
text="Now, let's add your data."
|
||||
hideIcon={true}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-muted-foreground">
|
||||
Choose and connect your preferred AI model provider.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStepComplete}
|
||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
<OnboardingUpload onComplete={handleStepComplete} />
|
||||
</OnboardingStep>
|
||||
|
||||
{/* Step 4 */}
|
||||
<OnboardingStep
|
||||
isVisible={currentStep >= 3}
|
||||
isCompleted={currentStep > 3}
|
||||
text="Step 3: You're all set!"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-muted-foreground">
|
||||
Your account is ready to use. Let's start chatting!
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStepComplete}
|
||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||
>
|
||||
Go to Chat
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStepComplete}
|
||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||
>
|
||||
Go to Chat
|
||||
</button>
|
||||
</div>
|
||||
</OnboardingStep>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ interface OnboardingStepProps {
|
|||
isCompleted?: boolean;
|
||||
icon?: ReactNode;
|
||||
isMarkdown?: boolean;
|
||||
hideIcon?: boolean;
|
||||
}
|
||||
|
||||
export function OnboardingStep({
|
||||
|
|
@ -21,6 +22,7 @@ export function OnboardingStep({
|
|||
isCompleted = false,
|
||||
icon,
|
||||
isMarkdown = false,
|
||||
hideIcon = false,
|
||||
}: OnboardingStepProps) {
|
||||
const [displayedText, setDisplayedText] = useState("");
|
||||
const [showChildren, setShowChildren] = useState(false);
|
||||
|
|
@ -66,13 +68,17 @@ export function OnboardingStep({
|
|||
>
|
||||
<Message
|
||||
icon={
|
||||
icon || (
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
||||
<DogIcon
|
||||
className="h-6 w-6 text-accent-foreground transition-colors duration-300"
|
||||
disabled={isCompleted}
|
||||
/>
|
||||
</div>
|
||||
hideIcon ? (
|
||||
<div className="w-8 h-8 rounded-lg flex-shrink-0" />
|
||||
) : (
|
||||
icon || (
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
||||
<DogIcon
|
||||
className="h-6 w-6 text-accent-foreground transition-colors duration-300"
|
||||
disabled={isCompleted}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
|
|
|
|||
114
frontend/src/app/new-onboarding/components/onboarding-upload.tsx
Normal file
114
frontend/src/app/new-onboarding/components/onboarding-upload.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { ChangeEvent, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { duplicateCheck, uploadFile } from "@/lib/upload-utils";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { AnimatedProviderSteps } from "@/app/onboarding/components/animated-provider-steps";
|
||||
|
||||
interface OnboardingUploadProps {
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState<number | null>(null);
|
||||
|
||||
const STEP_LIST = [
|
||||
"Analyzing your document",
|
||||
"Ingesting your document",
|
||||
];
|
||||
|
||||
const resetFileInput = () => {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
|
||||
const performUpload = async (file: File, replace = false) => {
|
||||
setIsUploading(true);
|
||||
try {
|
||||
setCurrentStep(1);
|
||||
await uploadFile(file, replace);
|
||||
console.log("Document uploaded successfully");
|
||||
} catch (error) {
|
||||
console.error("Upload failed", (error as Error).message);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
setCurrentStep(STEP_LIST.length);
|
||||
onComplete();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const selectedFile = event.target.files?.[0];
|
||||
if (!selectedFile) {
|
||||
resetFileInput();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setCurrentStep(0);
|
||||
const duplicateInfo = await duplicateCheck(selectedFile);
|
||||
if (duplicateInfo.exists) {
|
||||
console.log("Duplicate file detected");
|
||||
return;
|
||||
}
|
||||
|
||||
await performUpload(selectedFile, false);
|
||||
} catch (error) {
|
||||
console.error("Unable to prepare file for upload", (error as Error).message);
|
||||
} finally {
|
||||
resetFileInput();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
{currentStep === null ? (
|
||||
<motion.div
|
||||
key="user-ingest"
|
||||
initial={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -24 }}
|
||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleUploadClick}
|
||||
disabled={isUploading}
|
||||
>
|
||||
{isUploading ? "Uploading..." : "Add a Document"}
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="ingest-steps"
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
>
|
||||
<AnimatedProviderSteps
|
||||
currentStep={currentStep}
|
||||
setCurrentStep={setCurrentStep}
|
||||
steps={STEP_LIST}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
export default OnboardingUpload;
|
||||
|
|
@ -3,11 +3,7 @@
|
|||
import { Suspense, useState } from "react";
|
||||
import { DoclingHealthBanner } from "@/components/docling-health-banner";
|
||||
import { ProtectedRoute } from "@/components/protected-route";
|
||||
import { DotPattern } from "@/components/ui/dot-pattern";
|
||||
import { cn } from "@/lib/utils";
|
||||
import OnboardingCard from "../onboarding/components/onboarding-card";
|
||||
import { OnboardingContent } from "./components/onboarding-content";
|
||||
import { OnboardingStep } from "./components/onboarding-step";
|
||||
import { ProgressBar } from "./components/progress-bar";
|
||||
|
||||
const TOTAL_STEPS = 4;
|
||||
|
|
@ -28,7 +24,7 @@ function NewOnboardingPage() {
|
|||
{/* Chat-like content area */}
|
||||
<div className="flex flex-col items-center gap-5 w-full max-w-3xl z-10">
|
||||
<div className="w-full h-[872px] bg-background border rounded-lg p-4 shadow-sm overflow-y-auto">
|
||||
<OnboardingContent />
|
||||
<OnboardingContent handleStepComplete={handleStepComplete} currentStep={currentStep} />
|
||||
</div>
|
||||
|
||||
<ProgressBar currentStep={currentStep} totalSteps={TOTAL_STEPS} />
|
||||
|
|
|
|||
|
|
@ -9,16 +9,12 @@ import { cn } from "@/lib/utils";
|
|||
export function AnimatedProviderSteps({
|
||||
currentStep,
|
||||
setCurrentStep,
|
||||
steps,
|
||||
}: {
|
||||
currentStep: number;
|
||||
setCurrentStep: (step: number) => void;
|
||||
steps: string[];
|
||||
}) {
|
||||
const steps = [
|
||||
"Setting up your model provider",
|
||||
"Defining schema",
|
||||
"Configuring Langflow",
|
||||
"Ingesting sample data",
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (currentStep < steps.length - 1) {
|
||||
|
|
@ -27,7 +23,7 @@ export function AnimatedProviderSteps({
|
|||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [currentStep, setCurrentStep]);
|
||||
}, [currentStep, setCurrentStep, steps]);
|
||||
|
||||
const isDone = currentStep >= steps.length;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,15 @@ interface OnboardingCardProps {
|
|||
onComplete: () => void;
|
||||
}
|
||||
|
||||
const TOTAL_PROVIDER_STEPS = 4;
|
||||
|
||||
const STEP_LIST = [
|
||||
"Setting up your model provider",
|
||||
"Defining schema",
|
||||
"Configuring Langflow",
|
||||
"Ingesting sample data",
|
||||
];
|
||||
|
||||
const TOTAL_PROVIDER_STEPS = STEP_LIST.length;
|
||||
|
||||
const OnboardingCard = ({ onComplete }: OnboardingCardProps) => {
|
||||
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
||||
|
|
@ -250,9 +258,10 @@ const OnboardingCard = ({ onComplete }: OnboardingCardProps) => {
|
|||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
>
|
||||
<AnimatedProviderSteps
|
||||
currentStep={currentStep}
|
||||
setCurrentStep={setCurrentStep}
|
||||
/>
|
||||
currentStep={currentStep}
|
||||
setCurrentStep={setCurrentStep}
|
||||
steps={STEP_LIST}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue