diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx
index c901d8ad..81a3537d 100644
--- a/frontend/components/ui/button.tsx
+++ b/frontend/components/ui/button.tsx
@@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none disabled:select-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none disabled:select-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
diff --git a/frontend/components/ui/inputs/embedding-model.tsx b/frontend/components/ui/inputs/embedding-model.tsx
new file mode 100644
index 00000000..ded138ad
--- /dev/null
+++ b/frontend/components/ui/inputs/embedding-model.tsx
@@ -0,0 +1,62 @@
+import { ModelOption } from "@/app/api/queries/useGetModelsQuery";
+import {
+ getFallbackModels,
+ ModelProvider,
+} from "@/app/settings/helpers/model-helpers";
+import { ModelSelectItems } from "@/app/settings/helpers/model-select-item";
+import { LabelWrapper } from "@/components/label-wrapper";
+import {
+ Select,
+ SelectContent,
+ SelectTrigger,
+ SelectValue,
+} from "@radix-ui/react-select";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@radix-ui/react-tooltip";
+
+interface EmbeddingModelInputProps {
+ disabled?: boolean;
+ value: string;
+ onChange: (value: string) => void;
+ modelsData?: {
+ embedding_models: ModelOption[];
+ };
+ currentProvider?: ModelProvider;
+}
+
+export const EmbeddingModelInput = ({
+ disabled,
+ value,
+ onChange,
+ modelsData,
+ currentProvider = "openai",
+}: EmbeddingModelInputProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/frontend/components/ui/inputs/number-input.tsx b/frontend/components/ui/inputs/number-input.tsx
new file mode 100644
index 00000000..b312292f
--- /dev/null
+++ b/frontend/components/ui/inputs/number-input.tsx
@@ -0,0 +1,74 @@
+import { LabelWrapper } from "@/components/label-wrapper";
+import { Button } from "../button";
+import { Input } from "../input";
+import { Minus, Plus } from "lucide-react";
+
+interface NumberInputProps {
+ id: string;
+ label: string;
+ value: number;
+ onChange: (value: number) => void;
+ unit: string;
+ min?: number;
+ max?: number;
+ disabled?: boolean;
+}
+
+export const NumberInput = ({
+ id,
+ label,
+ value,
+ onChange,
+ min = 1,
+ max,
+ disabled,
+ unit,
+}: NumberInputProps) => {
+ return (
+
+
+
onChange(parseInt(e.target.value) || 0)}
+ className="w-full pr-20 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
+ />
+
+
+ {unit}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index 56dc8dc8..5b786b7b 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -351,4 +351,17 @@
.discord-error {
@apply text-xs opacity-70;
}
+
+ .box-shadow-inner::after {
+ content: " ";
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ pointer-events: none;
+ background: linear-gradient(to top, hsl(var(--background)), transparent);
+ display: block;
+ width: 100%;
+ height: 30px;
+ }
}
diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx
index f8b6a141..6fe74c4c 100644
--- a/frontend/src/app/settings/page.tsx
+++ b/frontend/src/app/settings/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { ArrowUpRight, Loader2, Minus, Plus } from "lucide-react";
+import { ArrowUpRight, Loader2, Minus, PlugZap, Plus } from "lucide-react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { Suspense, useCallback, useEffect, useState } from "react";
@@ -101,34 +101,6 @@ interface Connection {
created_at: string;
last_sync?: string;
}
-
-const DEFAULT_CONNECTORS: Connector[] = [
- {
- id: "google_drive",
- name: "Google Drive",
- description: "Google Drive is not configured.",
- icon: ,
- status: "not_connected",
- type: "google_drive",
- },
- {
- id: "one_drive",
- name: "OneDrive",
- description: "OneDrive is not configured.",
- icon: ,
- status: "not_connected",
- type: "one_drive",
- },
- {
- id: "amazon_s3",
- name: "SharePoint",
- description: "SharePoint is not configured.",
- icon: ,
- status: "not_connected",
- type: "sharepoint",
- },
-];
-
function KnowledgeSourcesPage() {
const { isAuthenticated, isNoAuthMode } = useAuth();
const { addTask, tasks } = useTask();
@@ -203,7 +175,7 @@ function KnowledgeSourcesPage() {
onSuccess: () => {
console.log("Setting updated successfully");
},
- onError: error => {
+ onError: (error) => {
console.error("Failed to update setting:", error.message);
},
});
@@ -304,16 +276,8 @@ function KnowledgeSourcesPage() {
const getConnectorIcon = useCallback((iconName: string) => {
const iconMap: { [key: string]: React.ReactElement } = {
"google-drive": ,
- sharepoint: (
-
- SP
-
- ),
- onedrive: (
-
-
-
- ),
+ sharepoint: ,
+ onedrive: ,
};
return (
iconMap[iconName] || (
@@ -338,14 +302,15 @@ function KnowledgeSourcesPage() {
// Initialize connectors list with metadata from backend
const initialConnectors = connectorTypes
- .filter(type => connectorsResult.connectors[type].available) // Only show available connectors
- .map(type => ({
+ .filter((type) => connectorsResult.connectors[type].available) // Only show available connectors
+ .map((type) => ({
id: type,
name: connectorsResult.connectors[type].name,
description: connectorsResult.connectors[type].description,
icon: getConnectorIcon(connectorsResult.connectors[type].icon),
status: "not_connected" as const,
type: type,
+ available: connectorsResult.connectors[type].available,
}));
setConnectors(initialConnectors);
@@ -362,8 +327,8 @@ function KnowledgeSourcesPage() {
);
const isConnected = activeConnection !== undefined;
- setConnectors(prev =>
- prev.map(c =>
+ setConnectors((prev) =>
+ prev.map((c) =>
c.type === connectorType
? {
...c,
@@ -382,7 +347,7 @@ function KnowledgeSourcesPage() {
const handleConnect = async (connector: Connector) => {
setIsConnecting(connector.id);
- setSyncResults(prev => ({ ...prev, [connector.id]: null }));
+ setSyncResults((prev) => ({ ...prev, [connector.id]: null }));
try {
// Use the shared auth callback URL, same as connectors page
@@ -522,9 +487,9 @@ function KnowledgeSourcesPage() {
// Watch for task completions and refresh stats
useEffect(() => {
// Find newly completed tasks by comparing with previous state
- const newlyCompletedTasks = tasks.filter(task => {
+ const newlyCompletedTasks = tasks.filter((task) => {
const wasCompleted =
- prevTasks.find(prev => prev.task_id === task.task_id)?.status ===
+ prevTasks.find((prev) => prev.task_id === task.task_id)?.status ===
"completed";
return task.status === "completed" && !wasCompleted;
});
@@ -578,7 +543,7 @@ function KnowledgeSourcesPage() {
fetch(`/api/reset-flow/retrieval`, {
method: "POST",
})
- .then(response => {
+ .then((response) => {
if (response.ok) {
return response.json();
}
@@ -591,7 +556,7 @@ function KnowledgeSourcesPage() {
handleModelChange(DEFAULT_AGENT_SETTINGS.llm_model);
closeDialog(); // Close after successful completion
})
- .catch(error => {
+ .catch((error) => {
console.error("Error restoring retrieval flow:", error);
closeDialog(); // Close even on error (could show error toast instead)
});
@@ -601,7 +566,7 @@ function KnowledgeSourcesPage() {
fetch(`/api/reset-flow/ingest`, {
method: "POST",
})
- .then(response => {
+ .then((response) => {
if (response.ok) {
return response.json();
}
@@ -616,7 +581,7 @@ function KnowledgeSourcesPage() {
setPictureDescriptions(false);
closeDialog(); // Close after successful completion
})
- .catch(error => {
+ .catch((error) => {
console.error("Error restoring ingest flow:", error);
closeDialog(); // Close even on error (could show error toast instead)
});
@@ -735,11 +700,9 @@ function KnowledgeSourcesPage() {
//
//
}
-
{/* Connectors Grid */}
- {DEFAULT_CONNECTORS.map(connector => {
- const actualConnector = connectors.find(c => c.id === connector.id);
+ {connectors.map((connector) => {
return (
@@ -748,7 +711,7 @@ function KnowledgeSourcesPage() {
{connector.icon}
@@ -756,45 +719,66 @@ function KnowledgeSourcesPage() {
{connector.name}
- {actualConnector &&
- getStatusBadge(actualConnector.status)}
+ {connector && getStatusBadge(connector.status)}
- {actualConnector?.description
- ? `${actualConnector.name} is configured.`
+ {connector?.description
+ ? `${connector.name} is configured.`
: connector.description}
- {actualConnector?.status === "connected" ? (
+ {connector?.available ? (
-
-
- {syncResults[connector.id] && (
-
-
- Processed:{" "}
- {syncResults[connector.id]?.processed || 0}
-
-
- Added: {syncResults[connector.id]?.added || 0}
-
- {syncResults[connector.id]?.errors && (
-
- Errors: {syncResults[connector.id]?.errors}
+ {connector?.status === "connected" ? (
+ <>
+
+ {syncResults[connector.id] && (
+
+
+ Processed:{" "}
+ {syncResults[connector.id]?.processed || 0}
+
+
+ Added: {syncResults[connector.id]?.added || 0}
+
+ {syncResults[connector.id]?.errors && (
+
+ Errors: {syncResults[connector.id]?.errors}
+
+ )}
)}
-
+ >
+ ) : (
+
)}
) : (
@@ -877,7 +861,7 @@ function KnowledgeSourcesPage() {
}
confirmText="Proceed"
confirmIcon={
}
- onConfirm={closeDialog =>
+ onConfirm={(closeDialog) =>
handleEditInLangflow("chat", closeDialog)
}
variant="warning"
@@ -916,7 +900,7 @@ function KnowledgeSourcesPage() {
id="system-prompt"
placeholder="Enter your agent instructions here..."
value={systemPrompt}
- onChange={e => setSystemPrompt(e.target.value)}
+ onChange={(e) => setSystemPrompt(e.target.value)}
rows={6}
className={`resize-none ${
systemPrompt.length > MAX_SYSTEM_PROMPT_CHARS
@@ -1025,7 +1009,7 @@ function KnowledgeSourcesPage() {
confirmText="Proceed"
confirmIcon={
}
variant="warning"
- onConfirm={closeDialog =>
+ onConfirm={(closeDialog) =>
handleEditInLangflow("ingest", closeDialog)
}
/>
@@ -1048,7 +1032,8 @@ function KnowledgeSourcesPage() {
disabled={true}
value={
settings.knowledge?.embedding_model ||
- modelsData?.embedding_models?.find(m => m.default)?.value ||
+ modelsData?.embedding_models?.find((m) => m.default)
+ ?.value ||
"text-embedding-ada-002"
}
onValueChange={handleEmbeddingModelChange}
@@ -1084,7 +1069,7 @@ function KnowledgeSourcesPage() {
type="number"
min="1"
value={chunkSize}
- onChange={e => handleChunkSizeChange(e.target.value)}
+ onChange={(e) => handleChunkSizeChange(e.target.value)}
className="w-full pr-20 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
@@ -1127,7 +1112,7 @@ function KnowledgeSourcesPage() {
type="number"
min="0"
value={chunkOverlap}
- onChange={e => handleChunkOverlapChange(e.target.value)}
+ onChange={(e) => handleChunkOverlapChange(e.target.value)}
className="w-full pr-20 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index 7c72ec3d..10b9b0e5 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -6,373 +6,394 @@ import { useEffect, useState } from "react";
import { type CloudFile, UnifiedCloudPicker } from "@/components/cloud-picker";
import type { IngestSettings } from "@/components/cloud-picker/types";
import { Button } from "@/components/ui/button";
-import { Toast } from "@/components/ui/toast";
import { useTask } from "@/contexts/task-context";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
// CloudFile interface is now imported from the unified cloud picker
interface CloudConnector {
- id: string;
- name: string;
- description: string;
- status: "not_connected" | "connecting" | "connected" | "error";
- type: string;
- connectionId?: string;
- clientId: string;
- hasAccessToken: boolean;
- accessTokenError?: string;
+ id: string;
+ name: string;
+ description: string;
+ status: "not_connected" | "connecting" | "connected" | "error";
+ type: string;
+ connectionId?: string;
+ clientId: string;
+ hasAccessToken: boolean;
+ accessTokenError?: string;
}
export default function UploadProviderPage() {
- const params = useParams();
- const router = useRouter();
- const provider = params.provider as string;
- const { addTask, tasks } = useTask();
+ const params = useParams();
+ const router = useRouter();
+ const provider = params.provider as string;
+ const { addTask, tasks } = useTask();
- const [connector, setConnector] = useState
(null);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
- const [accessToken, setAccessToken] = useState(null);
- const [selectedFiles, setSelectedFiles] = useState([]);
- const [isIngesting, setIsIngesting] = useState(false);
- const [currentSyncTaskId, setCurrentSyncTaskId] = useState(
- null,
- );
- const [ingestSettings, setIngestSettings] = useState({
- chunkSize: 1000,
- chunkOverlap: 200,
- ocr: false,
- pictureDescriptions: false,
- embeddingModel: "text-embedding-3-small",
- });
+ const [connector, setConnector] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [accessToken, setAccessToken] = useState(null);
+ const [selectedFiles, setSelectedFiles] = useState([]);
+ const [isIngesting, setIsIngesting] = useState(false);
+ const [currentSyncTaskId, setCurrentSyncTaskId] = useState(
+ null
+ );
+ const [ingestSettings, setIngestSettings] = useState({
+ chunkSize: 1000,
+ chunkOverlap: 200,
+ ocr: false,
+ pictureDescriptions: false,
+ embeddingModel: "text-embedding-3-small",
+ });
- useEffect(() => {
- const fetchConnectorInfo = async () => {
- setIsLoading(true);
- setError(null);
+ useEffect(() => {
+ const fetchConnectorInfo = async () => {
+ setIsLoading(true);
+ setError(null);
- try {
- // Fetch available connectors to validate the provider
- const connectorsResponse = await fetch("/api/connectors");
- if (!connectorsResponse.ok) {
- throw new Error("Failed to load connectors");
- }
+ try {
+ // Fetch available connectors to validate the provider
+ const connectorsResponse = await fetch("/api/connectors");
+ if (!connectorsResponse.ok) {
+ throw new Error("Failed to load connectors");
+ }
- const connectorsResult = await connectorsResponse.json();
- const providerInfo = connectorsResult.connectors[provider];
+ const connectorsResult = await connectorsResponse.json();
+ const providerInfo = connectorsResult.connectors[provider];
- if (!providerInfo || !providerInfo.available) {
- setError(
- `Cloud provider "${provider}" is not available or configured.`,
- );
- return;
- }
+ if (!providerInfo || !providerInfo.available) {
+ setError(
+ `Cloud provider "${provider}" is not available or configured.`
+ );
+ return;
+ }
- // Check connector status
- const statusResponse = await fetch(
- `/api/connectors/${provider}/status`,
- );
- if (!statusResponse.ok) {
- throw new Error(`Failed to check ${provider} status`);
- }
+ // Check connector status
+ const statusResponse = await fetch(
+ `/api/connectors/${provider}/status`
+ );
+ if (!statusResponse.ok) {
+ throw new Error(`Failed to check ${provider} status`);
+ }
- const statusData = await statusResponse.json();
- const connections = statusData.connections || [];
- const activeConnection = connections.find(
- (conn: { is_active: boolean; connection_id: string }) =>
- conn.is_active,
- );
- const isConnected = activeConnection !== undefined;
+ const statusData = await statusResponse.json();
+ const connections = statusData.connections || [];
+ const activeConnection = connections.find(
+ (conn: { is_active: boolean; connection_id: string }) =>
+ conn.is_active
+ );
+ const isConnected = activeConnection !== undefined;
- let hasAccessToken = false;
- let accessTokenError: string | undefined;
+ let hasAccessToken = false;
+ let accessTokenError: string | undefined;
- // Try to get access token for connected connectors
- if (isConnected && activeConnection) {
- try {
- const tokenResponse = await fetch(
- `/api/connectors/${provider}/token?connection_id=${activeConnection.connection_id}`,
- );
- if (tokenResponse.ok) {
- const tokenData = await tokenResponse.json();
- if (tokenData.access_token) {
- hasAccessToken = true;
- setAccessToken(tokenData.access_token);
- }
- } else {
- const errorData = await tokenResponse
- .json()
- .catch(() => ({ error: "Token unavailable" }));
- accessTokenError = errorData.error || "Access token unavailable";
- }
- } catch {
- accessTokenError = "Failed to fetch access token";
- }
- }
+ // Try to get access token for connected connectors
+ if (isConnected && activeConnection) {
+ try {
+ const tokenResponse = await fetch(
+ `/api/connectors/${provider}/token?connection_id=${activeConnection.connection_id}`
+ );
+ if (tokenResponse.ok) {
+ const tokenData = await tokenResponse.json();
+ if (tokenData.access_token) {
+ hasAccessToken = true;
+ setAccessToken(tokenData.access_token);
+ }
+ } else {
+ const errorData = await tokenResponse
+ .json()
+ .catch(() => ({ error: "Token unavailable" }));
+ accessTokenError = errorData.error || "Access token unavailable";
+ }
+ } catch {
+ accessTokenError = "Failed to fetch access token";
+ }
+ }
- setConnector({
- id: provider,
- name: providerInfo.name,
- description: providerInfo.description,
- status: isConnected ? "connected" : "not_connected",
- type: provider,
- connectionId: activeConnection?.connection_id,
- clientId: activeConnection?.client_id,
- hasAccessToken,
- accessTokenError,
- });
- } catch (error) {
- console.error("Failed to load connector info:", error);
- setError(
- error instanceof Error
- ? error.message
- : "Failed to load connector information",
- );
- } finally {
- setIsLoading(false);
- }
- };
+ setConnector({
+ id: provider,
+ name: providerInfo.name,
+ description: providerInfo.description,
+ status: isConnected ? "connected" : "not_connected",
+ type: provider,
+ connectionId: activeConnection?.connection_id,
+ clientId: activeConnection?.client_id,
+ hasAccessToken,
+ accessTokenError,
+ });
+ } catch (error) {
+ console.error("Failed to load connector info:", error);
+ setError(
+ error instanceof Error
+ ? error.message
+ : "Failed to load connector information"
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
- if (provider) {
- fetchConnectorInfo();
- }
- }, [provider]);
+ if (provider) {
+ fetchConnectorInfo();
+ }
+ }, [provider]);
- // Watch for sync task completion and redirect
- useEffect(() => {
- if (!currentSyncTaskId) return;
+ // Watch for sync task completion and redirect
+ useEffect(() => {
+ if (!currentSyncTaskId) return;
- const currentTask = tasks.find(
- (task) => task.task_id === currentSyncTaskId,
- );
+ const currentTask = tasks.find(
+ (task) => task.task_id === currentSyncTaskId
+ );
- if (currentTask && currentTask.status === "completed") {
- // Task completed successfully, show toast and redirect
- setIsIngesting(false);
- setTimeout(() => {
- router.push("/knowledge");
- }, 2000); // 2 second delay to let user see toast
- } else if (currentTask && currentTask.status === "failed") {
- // Task failed, clear the tracking but don't redirect
- setIsIngesting(false);
- setCurrentSyncTaskId(null);
- }
- }, [tasks, currentSyncTaskId, router]);
+ if (currentTask && currentTask.status === "completed") {
+ // Task completed successfully, show toast and redirect
+ setIsIngesting(false);
+ setTimeout(() => {
+ router.push("/knowledge");
+ }, 2000); // 2 second delay to let user see toast
+ } else if (currentTask && currentTask.status === "failed") {
+ // Task failed, clear the tracking but don't redirect
+ setIsIngesting(false);
+ setCurrentSyncTaskId(null);
+ }
+ }, [tasks, currentSyncTaskId, router]);
- const handleFileSelected = (files: CloudFile[]) => {
- setSelectedFiles(files);
- console.log(`Selected ${files.length} files from ${provider}:`, files);
- // You can add additional handling here like triggering sync, etc.
- };
+ const handleFileSelected = (files: CloudFile[]) => {
+ setSelectedFiles(files);
+ console.log(`Selected ${files.length} files from ${provider}:`, files);
+ // You can add additional handling here like triggering sync, etc.
+ };
- const handleSync = async (connector: CloudConnector) => {
- if (!connector.connectionId || selectedFiles.length === 0) return;
+ const handleSync = async (connector: CloudConnector) => {
+ if (!connector.connectionId || selectedFiles.length === 0) return;
- setIsIngesting(true);
+ setIsIngesting(true);
- try {
- const syncBody: {
- connection_id: string;
- max_files?: number;
- selected_files?: string[];
- settings?: IngestSettings;
- } = {
- connection_id: connector.connectionId,
- selected_files: selectedFiles.map((file) => file.id),
- settings: ingestSettings,
- };
+ try {
+ const syncBody: {
+ connection_id: string;
+ max_files?: number;
+ selected_files?: string[];
+ settings?: IngestSettings;
+ } = {
+ connection_id: connector.connectionId,
+ selected_files: selectedFiles.map((file) => file.id),
+ settings: ingestSettings,
+ };
- const response = await fetch(`/api/connectors/${connector.type}/sync`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(syncBody),
- });
+ const response = await fetch(`/api/connectors/${connector.type}/sync`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(syncBody),
+ });
- const result = await response.json();
+ const result = await response.json();
- if (response.status === 201) {
- const taskIds = result.task_ids;
- if (taskIds && taskIds.length > 0) {
- const taskId = taskIds[0]; // Use the first task ID
- addTask(taskId);
- setCurrentSyncTaskId(taskId);
- }
- } else {
- console.error("Sync failed:", result.error);
- }
- } catch (error) {
- console.error("Sync error:", error);
- setIsIngesting(false);
- }
- };
+ if (response.status === 201) {
+ const taskIds = result.task_ids;
+ if (taskIds && taskIds.length > 0) {
+ const taskId = taskIds[0]; // Use the first task ID
+ addTask(taskId);
+ setCurrentSyncTaskId(taskId);
+ }
+ } else {
+ console.error("Sync failed:", result.error);
+ }
+ } catch (error) {
+ console.error("Sync error:", error);
+ setIsIngesting(false);
+ }
+ };
- const getProviderDisplayName = () => {
- const nameMap: { [key: string]: string } = {
- google_drive: "Google Drive",
- onedrive: "OneDrive",
- sharepoint: "SharePoint",
- };
- return nameMap[provider] || provider;
- };
+ const getProviderDisplayName = () => {
+ const nameMap: { [key: string]: string } = {
+ google_drive: "Google Drive",
+ onedrive: "OneDrive",
+ sharepoint: "SharePoint",
+ };
+ return nameMap[provider] || provider;
+ };
- if (isLoading) {
- return (
-
-
-
-
-
Loading {getProviderDisplayName()} connector...
-
-
-
- );
- }
+ if (isLoading) {
+ return (
+
+
+
+
+
Loading {getProviderDisplayName()} connector...
+
+
+
+ );
+ }
- if (error || !connector) {
- return (
-
-
-
-
+ if (error || !connector) {
+ return (
+
+
+
+
-
-
-
-
- Provider Not Available
-
-
{error}
-
-
-
-
- );
- }
+
+
+
+
+ Provider Not Available
+
+
{error}
+
+
+
+
+ );
+ }
- if (connector.status !== "connected") {
- return (
-
-
-
-
+ if (connector.status !== "connected") {
+ return (
+
+
+
+
-
-
-
-
- {connector.name} Not Connected
-
-
- You need to connect your {connector.name} account before you can
- select files.
-
-
-
-
-
- );
- }
+
+
+
+
+ {connector.name} Not Connected
+
+
+ You need to connect your {connector.name} account before you can
+ select files.
+
+
+
+
+
+ );
+ }
- if (!connector.hasAccessToken) {
- return (
-
-
-
-
+ if (!connector.hasAccessToken) {
+ return (
+
+
+
+
-
-
-
-
- Access Token Required
-
-
- {connector.accessTokenError ||
- `Unable to get access token for ${connector.name}. Try reconnecting your account.`}
-
-
-
-
-
- );
- }
+
+
+
+
+ Access Token Required
+
+
+ {connector.accessTokenError ||
+ `Unable to get access token for ${connector.name}. Try reconnecting your account.`}
+
+
+
+
+
+ );
+ }
- return (
-
-
-
-
- Add from {getProviderDisplayName()}
-
-
+ const hasSelectedFiles = selectedFiles.length > 0;
-
-
-
+ return (
+
+
+
+
+ Add from {getProviderDisplayName()}
+
+
-
-
-
-
-
-
-
- );
+
+
+
+
+
+
+
+
+
+
+
+ {!hasSelectedFiles ? (
+
+ Select at least one file before ingesting
+
+ ) : null}
+
+
+
+
+ );
}
diff --git a/frontend/src/components/cloud-picker/file-item.tsx b/frontend/src/components/cloud-picker/file-item.tsx
index 3f6b5ab5..f06aa92e 100644
--- a/frontend/src/components/cloud-picker/file-item.tsx
+++ b/frontend/src/components/cloud-picker/file-item.tsx
@@ -1,11 +1,16 @@
"use client";
-import { Badge } from "@/components/ui/badge";
-import { FileText, Folder, Trash } from "lucide-react";
+import { FileText, Folder, Trash2 } from "lucide-react";
import { CloudFile } from "./types";
+import GoogleDriveIcon from "@/app/settings/icons/google-drive-icon";
+import SharePointIcon from "@/app/settings/icons/share-point-icon";
+import OneDriveIcon from "@/app/settings/icons/one-drive-icon";
+import { Button } from "@/components/ui/button";
interface FileItemProps {
+ provider: string;
file: CloudFile;
+ shouldDisableActions: boolean;
onRemove: (fileId: string) => void;
}
@@ -41,27 +46,43 @@ const formatFileSize = (bytes?: number) => {
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
};
-export const FileItem = ({ file, onRemove }: FileItemProps) => (
+const getProviderIcon = (provider: string) => {
+ switch (provider) {
+ case "google_drive":
+ return ;
+ case "onedrive":
+ return ;
+ case "sharepoint":
+ return ;
+ default:
+ return ;
+ }
+};
+
+export const FileItem = ({ file, onRemove, provider }: FileItemProps) => (
- {getFileIcon(file.mimeType)}
+ {provider ? getProviderIcon(provider) : getFileIcon(file.mimeType)}
{file.name}
-
+
{getMimeTypeLabel(file.mimeType)}
-
+
-
+
{formatFileSize(file.size) || "—"}
-
- onRemove(file.id)}
- />
+ >
+
+
);
diff --git a/frontend/src/components/cloud-picker/file-list.tsx b/frontend/src/components/cloud-picker/file-list.tsx
index 775d78c4..7033fcf8 100644
--- a/frontend/src/components/cloud-picker/file-list.tsx
+++ b/frontend/src/components/cloud-picker/file-list.tsx
@@ -5,37 +5,50 @@ import { CloudFile } from "./types";
import { FileItem } from "./file-item";
interface FileListProps {
+ provider: string;
files: CloudFile[];
onClearAll: () => void;
onRemoveFile: (fileId: string) => void;
+ shouldDisableActions: boolean;
}
export const FileList = ({
+ provider,
files,
onClearAll,
onRemoveFile,
+ shouldDisableActions,
}: FileListProps) => {
if (files.length === 0) {
return null;
}
return (
-
+
-
Added files
+
Added files ({files.length})
-
- {files.map(file => (
-
- ))}
+
+
+ {files.map((file) => (
+
+ ))}
+
);
diff --git a/frontend/src/components/cloud-picker/ingest-settings.tsx b/frontend/src/components/cloud-picker/ingest-settings.tsx
index d5843a2a..a9ed0c8f 100644
--- a/frontend/src/components/cloud-picker/ingest-settings.tsx
+++ b/frontend/src/components/cloud-picker/ingest-settings.tsx
@@ -1,14 +1,28 @@
"use client";
-import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
-import { ChevronRight, Info } from "lucide-react";
+import { ChevronRight } from "lucide-react";
import { IngestSettings as IngestSettingsType } from "./types";
+import { LabelWrapper } from "@/components/label-wrapper";
+import {
+ Select,
+ SelectContent,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { ModelSelectItems } from "@/app/settings/helpers/model-select-item";
+import { getFallbackModels } from "@/app/settings/helpers/model-helpers";
+import { NumberInput } from "@/components/ui/inputs/number-input";
interface IngestSettingsProps {
isOpen: boolean;
@@ -44,7 +58,7 @@ export const IngestSettings = ({
@@ -58,35 +72,85 @@ export const IngestSettings = ({
-
-
+
+ {/* Embedding model selection - currently disabled */}
+
+
+
+
+
+
-
Chunk size
-
- handleSettingsChange({
- chunkSize: parseInt(e.target.value) || 0,
- })
- }
+ onChange={(value) => handleSettingsChange({ chunkSize: value })}
+ unit="characters"
/>
-
Chunk overlap
-
- handleSettingsChange({
- chunkOverlap: parseInt(e.target.value) || 0,
- })
+ onChange={(value) =>
+ handleSettingsChange({ chunkOverlap: value })
}
+ unit="characters"
/>
-
+ {/*
+
+
Table Structure
+
+ Capture table structure during ingest.
+
+
+
+ handleSettingsChange({ tableStructure: checked })
+ }
+ />
+ */}
+
+
OCR
@@ -95,13 +159,13 @@ export const IngestSettings = ({
+ onCheckedChange={(checked) =>
handleSettingsChange({ ocr: checked })
}
/>
-
+
Picture descriptions
@@ -112,26 +176,11 @@ export const IngestSettings = ({
+ onCheckedChange={(checked) =>
handleSettingsChange({ pictureDescriptions: checked })
}
/>
-
-
-
- Embedding model
-
-
-
- handleSettingsChange({ embeddingModel: e.target.value })
- }
- placeholder="text-embedding-3-small"
- />
-
diff --git a/frontend/src/components/cloud-picker/picker-header.tsx b/frontend/src/components/cloud-picker/picker-header.tsx
index 54407aa7..e0d9cfa4 100644
--- a/frontend/src/components/cloud-picker/picker-header.tsx
+++ b/frontend/src/components/cloud-picker/picker-header.tsx
@@ -51,19 +51,13 @@ export const PickerHeader = ({
Select files from {getProviderName(provider)} to ingest.
-
- csv, json, pdf,{" "}
-
+16 more{" "}
-
150 MB max
-
);
diff --git a/frontend/src/components/cloud-picker/types.ts b/frontend/src/components/cloud-picker/types.ts
index ca346bf0..20b1eae0 100644
--- a/frontend/src/components/cloud-picker/types.ts
+++ b/frontend/src/components/cloud-picker/types.ts
@@ -25,6 +25,7 @@ export interface UnifiedCloudPickerProps {
baseUrl?: string;
// Ingest settings
onSettingsChange?: (settings: IngestSettings) => void;
+ isIngesting: boolean;
}
export interface GoogleAPI {
diff --git a/frontend/src/components/cloud-picker/unified-cloud-picker.tsx b/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
index fd77698f..ff0c3903 100644
--- a/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
+++ b/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
@@ -16,6 +16,7 @@ export const UnifiedCloudPicker = ({
onFileSelected,
selectedFiles = [],
isAuthenticated,
+ isIngesting,
accessToken,
onPickerStateChange,
clientId,
@@ -116,7 +117,7 @@ export const UnifiedCloudPicker = ({
const handler = createProviderHandler(
provider,
accessToken,
- isOpen => {
+ (isOpen) => {
setIsPickerOpen(isOpen);
onPickerStateChange?.(isOpen);
},
@@ -126,8 +127,8 @@ export const UnifiedCloudPicker = ({
handler.openPicker((files: CloudFile[]) => {
// Merge new files with existing ones, avoiding duplicates
- const existingIds = new Set(selectedFiles.map(f => f.id));
- const newFiles = files.filter(f => !existingIds.has(f.id));
+ const existingIds = new Set(selectedFiles.map((f) => f.id));
+ const newFiles = files.filter((f) => !existingIds.has(f.id));
onFileSelected([...selectedFiles, ...newFiles]);
});
} catch (error) {
@@ -138,7 +139,7 @@ export const UnifiedCloudPicker = ({
};
const handleRemoveFile = (fileId: string) => {
- const updatedFiles = selectedFiles.filter(file => file.id !== fileId);
+ const updatedFiles = selectedFiles.filter((file) => file.id !== fileId);
onFileSelected(updatedFiles);
};
@@ -168,20 +169,24 @@ export const UnifiedCloudPicker = ({
}
return (
-