From 1eb0fb3593458f2a064aea14af237d8a99f4a5f9 Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Fri, 3 Oct 2025 15:00:53 -0600
Subject: [PATCH 01/12] design sweep for ingest for connector page
---
frontend/src/app/upload/[provider]/page.tsx | 659 +++++++++---------
.../components/cloud-picker/picker-header.tsx | 8 +-
2 files changed, 332 insertions(+), 335 deletions(-)
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index 7c72ec3d..8e0a306c 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -12,367 +12,370 @@ import { useTask } from "@/contexts/task-context";
// 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()}
-
-
+ return (
+
+
+
+
+ Add from {getProviderDisplayName()}
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
- );
+
+
+
+
+
+
+
+ );
}
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
-
);
From 01084d2a8957e18c8bdededba51e6777550bb81b Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Fri, 3 Oct 2025 15:30:45 -0600
Subject: [PATCH 02/12] wip commit
---
frontend/src/app/upload/[provider]/page.tsx | 9 ++---
.../src/components/cloud-picker/file-item.tsx | 37 +++++++++++++++----
.../src/components/cloud-picker/file-list.tsx | 16 ++++++--
.../cloud-picker/ingest-settings.tsx | 12 +++---
.../cloud-picker/unified-cloud-picker.tsx | 9 +++--
5 files changed, 55 insertions(+), 28 deletions(-)
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index 8e0a306c..e3f3f57e 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -328,7 +328,7 @@ export default function UploadProviderPage() {
return (
-
+
@@ -366,12 +366,9 @@ export default function UploadProviderPage() {
disabled={selectedFiles.length === 0 || isIngesting}
>
{isIngesting ? (
- <>
- Ingesting {selectedFiles.length} file
- {selectedFiles.length > 1 ? "s" : ""}...
- >
+ <>Ingesting {selectedFiles.length} Files...>
) : (
- <>Ingest files>
+ <>Start ingest>
)}
diff --git a/frontend/src/components/cloud-picker/file-item.tsx b/frontend/src/components/cloud-picker/file-item.tsx
index 3f6b5ab5..e9bdec38 100644
--- a/frontend/src/components/cloud-picker/file-item.tsx
+++ b/frontend/src/components/cloud-picker/file-item.tsx
@@ -1,10 +1,15 @@
"use client";
import { Badge } from "@/components/ui/badge";
-import { FileText, Folder, Trash } from "lucide-react";
+import { FileText, Folder, Trash, 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;
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..b584249a 100644
--- a/frontend/src/components/cloud-picker/file-list.tsx
+++ b/frontend/src/components/cloud-picker/file-list.tsx
@@ -5,12 +5,14 @@ import { CloudFile } from "./types";
import { FileItem } from "./file-item";
interface FileListProps {
+ provider: string;
files: CloudFile[];
onClearAll: () => void;
onRemoveFile: (fileId: string) => void;
}
export const FileList = ({
+ provider,
files,
onClearAll,
onRemoveFile,
@@ -22,19 +24,25 @@ export const FileList = ({
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..8c72ce6b 100644
--- a/frontend/src/components/cloud-picker/ingest-settings.tsx
+++ b/frontend/src/components/cloud-picker/ingest-settings.tsx
@@ -44,7 +44,7 @@ export const IngestSettings = ({
@@ -65,7 +65,7 @@ export const IngestSettings = ({
+ onChange={(e) =>
handleSettingsChange({
chunkSize: parseInt(e.target.value) || 0,
})
@@ -77,7 +77,7 @@ export const IngestSettings = ({
+ onChange={(e) =>
handleSettingsChange({
chunkOverlap: parseInt(e.target.value) || 0,
})
@@ -95,7 +95,7 @@ export const IngestSettings = ({
+ onCheckedChange={(checked) =>
handleSettingsChange({ ocr: checked })
}
/>
@@ -112,7 +112,7 @@ export const IngestSettings = ({
+ onCheckedChange={(checked) =>
handleSettingsChange({ pictureDescriptions: checked })
}
/>
@@ -126,7 +126,7 @@ export const IngestSettings = ({
+ onChange={(e) =>
handleSettingsChange({ embeddingModel: e.target.value })
}
placeholder="text-embedding-3-small"
diff --git a/frontend/src/components/cloud-picker/unified-cloud-picker.tsx b/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
index fd77698f..0a4699af 100644
--- a/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
+++ b/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
@@ -116,7 +116,7 @@ export const UnifiedCloudPicker = ({
const handler = createProviderHandler(
provider,
accessToken,
- isOpen => {
+ (isOpen) => {
setIsPickerOpen(isOpen);
onPickerStateChange?.(isOpen);
},
@@ -126,8 +126,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 +138,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);
};
@@ -179,6 +179,7 @@ export const UnifiedCloudPicker = ({
/>
Date: Fri, 3 Oct 2025 15:41:45 -0600
Subject: [PATCH 03/12] fix file item spacing and typography
---
frontend/src/components/cloud-picker/file-item.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frontend/src/components/cloud-picker/file-item.tsx b/frontend/src/components/cloud-picker/file-item.tsx
index e9bdec38..5370e4cc 100644
--- a/frontend/src/components/cloud-picker/file-item.tsx
+++ b/frontend/src/components/cloud-picker/file-item.tsx
@@ -62,14 +62,14 @@ const getProviderIcon = (provider: string) => {
export const FileItem = ({ file, onRemove, provider }: FileItemProps) => (
{provider ? getProviderIcon(provider) : getFileIcon(file.mimeType)}
{file.name}
-
+
{getMimeTypeLabel(file.mimeType)}
-
+
From a2e1210a4dcf12543bb13d406240c620e3d17dfb Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Mon, 6 Oct 2025 08:50:19 -0600
Subject: [PATCH 04/12] wip commit
---
frontend/src/app/upload/[provider]/page.tsx | 11 ++++++-----
frontend/src/components/cloud-picker/file-item.tsx | 4 ++--
frontend/src/components/cloud-picker/file-list.tsx | 5 +++++
frontend/src/components/cloud-picker/types.ts | 1 +
.../components/cloud-picker/unified-cloud-picker.tsx | 2 ++
5 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index e3f3f57e..3254bc73 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -6,7 +6,6 @@ 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";
// CloudFile interface is now imported from the unified cloud picker
@@ -345,6 +344,7 @@ export default function UploadProviderPage() {
onFileSelected={handleFileSelected}
selectedFiles={selectedFiles}
isAuthenticated={true}
+ isIngesting={isIngesting}
accessToken={accessToken || undefined}
clientId={connector.clientId}
onSettingsChange={setIngestSettings}
@@ -361,14 +361,15 @@ export default function UploadProviderPage() {
Back
diff --git a/frontend/src/components/cloud-picker/file-item.tsx b/frontend/src/components/cloud-picker/file-item.tsx
index 5370e4cc..f06aa92e 100644
--- a/frontend/src/components/cloud-picker/file-item.tsx
+++ b/frontend/src/components/cloud-picker/file-item.tsx
@@ -1,7 +1,6 @@
"use client";
-import { Badge } from "@/components/ui/badge";
-import { FileText, Folder, Trash, Trash2 } 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";
@@ -11,6 +10,7 @@ import { Button } from "@/components/ui/button";
interface FileItemProps {
provider: string;
file: CloudFile;
+ shouldDisableActions: boolean;
onRemove: (fileId: string) => void;
}
diff --git a/frontend/src/components/cloud-picker/file-list.tsx b/frontend/src/components/cloud-picker/file-list.tsx
index b584249a..db7daf64 100644
--- a/frontend/src/components/cloud-picker/file-list.tsx
+++ b/frontend/src/components/cloud-picker/file-list.tsx
@@ -9,6 +9,7 @@ interface FileListProps {
files: CloudFile[];
onClearAll: () => void;
onRemoveFile: (fileId: string) => void;
+ shouldDisableActions: boolean;
}
export const FileList = ({
@@ -16,11 +17,14 @@ export const FileList = ({
files,
onClearAll,
onRemoveFile,
+ shouldDisableActions,
}: FileListProps) => {
if (files.length === 0) {
return null;
}
+ console.log({ shouldDisableActions });
+
return (
@@ -42,6 +46,7 @@ export const FileList = ({
file={file}
onRemove={onRemoveFile}
provider={provider}
+ shouldDisableActions={shouldDisableActions}
/>
))}
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 0a4699af..e92cef13 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,
@@ -183,6 +184,7 @@ export const UnifiedCloudPicker = ({
files={selectedFiles}
onClearAll={handleClearAll}
onRemoveFile={handleRemoveFile}
+ shouldDisableActions={isIngesting}
/>
Date: Mon, 6 Oct 2025 10:40:57 -0600
Subject: [PATCH 05/12] updated inputs in Ingest Settings for cloud-picker
component
---
.../components/ui/inputs/embedding-model.tsx | 62 ++++++++++++
.../components/ui/inputs/number-input.tsx | 68 +++++++++++++
.../cloud-picker/ingest-settings.tsx | 98 +++++++++++++------
3 files changed, 197 insertions(+), 31 deletions(-)
create mode 100644 frontend/components/ui/inputs/embedding-model.tsx
create mode 100644 frontend/components/ui/inputs/number-input.tsx
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..44e04fcb
--- /dev/null
+++ b/frontend/components/ui/inputs/number-input.tsx
@@ -0,0 +1,68 @@
+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/components/cloud-picker/ingest-settings.tsx b/frontend/src/components/cloud-picker/ingest-settings.tsx
index 8c72ce6b..422e1a70 100644
--- a/frontend/src/components/cloud-picker/ingest-settings.tsx
+++ b/frontend/src/components/cloud-picker/ingest-settings.tsx
@@ -7,8 +7,23 @@ import {
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;
@@ -40,6 +55,8 @@ export const IngestSettings = ({
onSettingsChange?.(updatedSettings);
};
+ console.log({ currentSettings });
+
return (
+
+ {/* 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"
/>
@@ -117,21 +168,6 @@ export const IngestSettings = ({
}
/>
-
-
-
- Embedding model
-
-
-
- handleSettingsChange({ embeddingModel: e.target.value })
- }
- placeholder="text-embedding-3-small"
- />
-
From 5de3a821c484eb2d519b02d6eee362aea24417cb Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Mon, 6 Oct 2025 10:48:39 -0600
Subject: [PATCH 06/12] remove console.log
---
frontend/src/components/cloud-picker/ingest-settings.tsx | 3 ---
1 file changed, 3 deletions(-)
diff --git a/frontend/src/components/cloud-picker/ingest-settings.tsx b/frontend/src/components/cloud-picker/ingest-settings.tsx
index 422e1a70..883ebf13 100644
--- a/frontend/src/components/cloud-picker/ingest-settings.tsx
+++ b/frontend/src/components/cloud-picker/ingest-settings.tsx
@@ -1,6 +1,5 @@
"use client";
-import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import {
Collapsible,
@@ -55,8 +54,6 @@ export const IngestSettings = ({
onSettingsChange?.(updatedSettings);
};
- console.log({ currentSettings });
-
return (
Date: Mon, 6 Oct 2025 10:53:42 -0600
Subject: [PATCH 07/12] heading margin
---
frontend/src/app/upload/[provider]/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index 3254bc73..ab169063 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -327,7 +327,7 @@ export default function UploadProviderPage() {
return (
-
+
From 9f16f9f25a1b72715617c2110dff6ff5d24d8a22 Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Mon, 6 Oct 2025 11:38:33 -0600
Subject: [PATCH 08/12] Added inner shadow and capped height
---
frontend/components/ui/button.tsx | 2 +-
frontend/src/app/globals.css | 13 ++++++++++
.../src/components/cloud-picker/file-list.tsx | 26 +++++++++----------
.../cloud-picker/unified-cloud-picker.tsx | 20 +++++++-------
4 files changed, 38 insertions(+), 23 deletions(-)
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/src/app/globals.css b/frontend/src/app/globals.css
index 150d1c56..f3f04679 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -312,4 +312,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/components/cloud-picker/file-list.tsx b/frontend/src/components/cloud-picker/file-list.tsx
index db7daf64..7033fcf8 100644
--- a/frontend/src/components/cloud-picker/file-list.tsx
+++ b/frontend/src/components/cloud-picker/file-list.tsx
@@ -23,10 +23,8 @@ export const FileList = ({
return null;
}
- console.log({ shouldDisableActions });
-
return (
-
+
Added files ({files.length})
-
- {files.map((file) => (
-
- ))}
+
+
+ {files.map((file) => (
+
+ ))}
+
);
diff --git a/frontend/src/components/cloud-picker/unified-cloud-picker.tsx b/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
index e92cef13..ff0c3903 100644
--- a/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
+++ b/frontend/src/components/cloud-picker/unified-cloud-picker.tsx
@@ -169,15 +169,17 @@ export const UnifiedCloudPicker = ({
}
return (
-
-
+
+
Date: Mon, 6 Oct 2025 11:46:41 -0600
Subject: [PATCH 09/12] remove max head room
---
frontend/src/app/upload/[provider]/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index ab169063..0a015c82 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -326,7 +326,7 @@ export default function UploadProviderPage() {
}
return (
-
+
-
+
From b3a11fed39aaff0e313f0062c52a97023d9d7bdd Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Mon, 6 Oct 2025 14:36:21 -0600
Subject: [PATCH 11/12] fix number input border colors
---
.../components/ui/inputs/number-input.tsx | 18 +++++++++-----
.../cloud-picker/ingest-settings.tsx | 24 +++++++++++++++----
2 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/frontend/components/ui/inputs/number-input.tsx b/frontend/components/ui/inputs/number-input.tsx
index 44e04fcb..b312292f 100644
--- a/frontend/components/ui/inputs/number-input.tsx
+++ b/frontend/components/ui/inputs/number-input.tsx
@@ -37,28 +37,34 @@ export const NumberInput = ({
onChange={(e) => 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/components/cloud-picker/ingest-settings.tsx b/frontend/src/components/cloud-picker/ingest-settings.tsx
index 883ebf13..a9ed0c8f 100644
--- a/frontend/src/components/cloud-picker/ingest-settings.tsx
+++ b/frontend/src/components/cloud-picker/ingest-settings.tsx
@@ -110,8 +110,8 @@ export const IngestSettings = ({
-
-
+
+
-
+ {/*
+
+
Table Structure
+
+ Capture table structure during ingest.
+
+
+
+ handleSettingsChange({ tableStructure: checked })
+ }
+ />
+ */}
+
+
OCR
@@ -149,7 +165,7 @@ export const IngestSettings = ({
/>
-
+
Picture descriptions
From a54ea69869334157a5e9ce75006ab52b4d109802 Mon Sep 17 00:00:00 2001
From: Brent O'Neill
Date: Mon, 6 Oct 2025 15:14:39 -0600
Subject: [PATCH 12/12] hide tooltip when it has files
---
frontend/src/app/upload/[provider]/page.tsx | 49 ++++++++++++++-------
1 file changed, 33 insertions(+), 16 deletions(-)
diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx
index 608dde98..10b9b0e5 100644
--- a/frontend/src/app/upload/[provider]/page.tsx
+++ b/frontend/src/app/upload/[provider]/page.tsx
@@ -7,6 +7,11 @@ import { type CloudFile, UnifiedCloudPicker } from "@/components/cloud-picker";
import type { IngestSettings } from "@/components/cloud-picker/types";
import { Button } from "@/components/ui/button";
import { useTask } from "@/contexts/task-context";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
// CloudFile interface is now imported from the unified cloud picker
@@ -325,6 +330,8 @@ export default function UploadProviderPage() {
);
}
+ const hasSelectedFiles = selectedFiles.length > 0;
+
return (
@@ -355,26 +362,36 @@ export default function UploadProviderPage() {
-
+
+
+
+
+ {!hasSelectedFiles ? (
+
+ Select at least one file before ingesting
+
+ ) : null}
+