Merge pull request #148 from langflow-ai/remove-sync-connectors

Remove sync from cloud connectors
This commit is contained in:
Deon Sanchez 2025-10-01 12:28:58 -06:00 committed by GitHub
commit b0ada8d01c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 162 additions and 160 deletions

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { ArrowUpRight, Loader2, PlugZap, RefreshCw } from "lucide-react"; import { ArrowUpRight, Loader2, PlugZap, Plus, RefreshCw } from "lucide-react";
import { useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { Suspense, useCallback, useEffect, useState } from "react"; import { Suspense, useCallback, useEffect, useState } from "react";
import { useUpdateFlowSettingMutation } from "@/app/api/mutations/useUpdateFlowSettingMutation"; import { useUpdateFlowSettingMutation } from "@/app/api/mutations/useUpdateFlowSettingMutation";
import { import {
@ -35,7 +35,11 @@ import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { useTask } from "@/contexts/task-context"; import { useTask } from "@/contexts/task-context";
import { useDebounce } from "@/lib/debounce"; import { useDebounce } from "@/lib/debounce";
import { DEFAULT_AGENT_SETTINGS, DEFAULT_KNOWLEDGE_SETTINGS, UI_CONSTANTS } from "@/lib/constants"; import {
DEFAULT_AGENT_SETTINGS,
DEFAULT_KNOWLEDGE_SETTINGS,
UI_CONSTANTS,
} from "@/lib/constants";
import { getFallbackModels, type ModelProvider } from "./helpers/model-helpers"; import { getFallbackModels, type ModelProvider } from "./helpers/model-helpers";
import { ModelSelectItems } from "./helpers/model-select-item"; import { ModelSelectItems } from "./helpers/model-select-item";
import { LabelWrapper } from "@/components/label-wrapper"; import { LabelWrapper } from "@/components/label-wrapper";
@ -92,6 +96,7 @@ function KnowledgeSourcesPage() {
const { isAuthenticated, isNoAuthMode } = useAuth(); const { isAuthenticated, isNoAuthMode } = useAuth();
const { addTask, tasks } = useTask(); const { addTask, tasks } = useTask();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter();
// Connectors state // Connectors state
const [connectors, setConnectors] = useState<Connector[]>([]); const [connectors, setConnectors] = useState<Connector[]>([]);
@ -159,7 +164,7 @@ function KnowledgeSourcesPage() {
onSuccess: () => { onSuccess: () => {
console.log("Setting updated successfully"); console.log("Setting updated successfully");
}, },
onError: (error) => { onError: error => {
console.error("Failed to update setting:", error.message); console.error("Failed to update setting:", error.message);
}, },
}); });
@ -298,8 +303,8 @@ function KnowledgeSourcesPage() {
// Initialize connectors list with metadata from backend // Initialize connectors list with metadata from backend
const initialConnectors = connectorTypes const initialConnectors = connectorTypes
.filter((type) => connectorsResult.connectors[type].available) // Only show available connectors .filter(type => connectorsResult.connectors[type].available) // Only show available connectors
.map((type) => ({ .map(type => ({
id: type, id: type,
name: connectorsResult.connectors[type].name, name: connectorsResult.connectors[type].name,
description: connectorsResult.connectors[type].description, description: connectorsResult.connectors[type].description,
@ -322,8 +327,8 @@ function KnowledgeSourcesPage() {
); );
const isConnected = activeConnection !== undefined; const isConnected = activeConnection !== undefined;
setConnectors((prev) => setConnectors(prev =>
prev.map((c) => prev.map(c =>
c.type === connectorType c.type === connectorType
? { ? {
...c, ...c,
@ -342,7 +347,7 @@ function KnowledgeSourcesPage() {
const handleConnect = async (connector: Connector) => { const handleConnect = async (connector: Connector) => {
setIsConnecting(connector.id); setIsConnecting(connector.id);
setSyncResults((prev) => ({ ...prev, [connector.id]: null })); setSyncResults(prev => ({ ...prev, [connector.id]: null }));
try { try {
// Use the shared auth callback URL, same as connectors page // Use the shared auth callback URL, same as connectors page
@ -392,58 +397,58 @@ function KnowledgeSourcesPage() {
} }
}; };
const handleSync = async (connector: Connector) => { // const handleSync = async (connector: Connector) => {
if (!connector.connectionId) return; // if (!connector.connectionId) return;
setIsSyncing(connector.id); // setIsSyncing(connector.id);
setSyncResults((prev) => ({ ...prev, [connector.id]: null })); // setSyncResults(prev => ({ ...prev, [connector.id]: null }));
try { // try {
const syncBody: { // const syncBody: {
connection_id: string; // connection_id: string;
max_files?: number; // max_files?: number;
selected_files?: string[]; // selected_files?: string[];
} = { // } = {
connection_id: connector.connectionId, // connection_id: connector.connectionId,
max_files: syncAllFiles ? 0 : maxFiles || undefined, // max_files: syncAllFiles ? 0 : maxFiles || undefined,
}; // };
// Note: File selection is now handled via the cloud connectors dialog // // Note: File selection is now handled via the cloud connectors dialog
const response = await fetch(`/api/connectors/${connector.type}/sync`, { // const response = await fetch(`/api/connectors/${connector.type}/sync`, {
method: "POST", // method: "POST",
headers: { // headers: {
"Content-Type": "application/json", // "Content-Type": "application/json",
}, // },
body: JSON.stringify(syncBody), // body: JSON.stringify(syncBody),
}); // });
const result = await response.json(); // const result = await response.json();
if (response.status === 201) { // if (response.status === 201) {
const taskId = result.task_id; // const taskId = result.task_id;
if (taskId) { // if (taskId) {
addTask(taskId); // addTask(taskId);
setSyncResults((prev) => ({ // setSyncResults(prev => ({
...prev, // ...prev,
[connector.id]: { // [connector.id]: {
processed: 0, // processed: 0,
total: result.total_files || 0, // total: result.total_files || 0,
}, // },
})); // }));
} // }
} else if (response.ok) { // } else if (response.ok) {
setSyncResults((prev) => ({ ...prev, [connector.id]: result })); // setSyncResults(prev => ({ ...prev, [connector.id]: result }));
// Note: Stats will auto-refresh via task completion watcher for async syncs // // Note: Stats will auto-refresh via task completion watcher for async syncs
} else { // } else {
console.error("Sync failed:", result.error); // console.error("Sync failed:", result.error);
} // }
} catch (error) { // } catch (error) {
console.error("Sync error:", error); // console.error("Sync error:", error);
} finally { // } finally {
setIsSyncing(null); // setIsSyncing(null);
} // }
}; // };
const getStatusBadge = (status: Connector["status"]) => { const getStatusBadge = (status: Connector["status"]) => {
switch (status) { switch (status) {
@ -479,6 +484,11 @@ function KnowledgeSourcesPage() {
} }
}; };
const navigateToKnowledgePage = (connector: Connector) => {
const provider = connector.type.replace(/-/g, "_");
router.push(`/upload/${provider}`);
};
// Check connector status on mount and when returning from OAuth // Check connector status on mount and when returning from OAuth
useEffect(() => { useEffect(() => {
if (isAuthenticated) { if (isAuthenticated) {
@ -498,9 +508,9 @@ function KnowledgeSourcesPage() {
// Watch for task completions and refresh stats // Watch for task completions and refresh stats
useEffect(() => { useEffect(() => {
// Find newly completed tasks by comparing with previous state // Find newly completed tasks by comparing with previous state
const newlyCompletedTasks = tasks.filter((task) => { const newlyCompletedTasks = tasks.filter(task => {
const wasCompleted = const wasCompleted =
prevTasks.find((prev) => prev.task_id === task.task_id)?.status === prevTasks.find(prev => prev.task_id === task.task_id)?.status ===
"completed"; "completed";
return task.status === "completed" && !wasCompleted; return task.status === "completed" && !wasCompleted;
}); });
@ -554,7 +564,7 @@ function KnowledgeSourcesPage() {
fetch(`/api/reset-flow/retrieval`, { fetch(`/api/reset-flow/retrieval`, {
method: "POST", method: "POST",
}) })
.then((response) => { .then(response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -567,7 +577,7 @@ function KnowledgeSourcesPage() {
handleModelChange(DEFAULT_AGENT_SETTINGS.llm_model); handleModelChange(DEFAULT_AGENT_SETTINGS.llm_model);
closeDialog(); // Close after successful completion closeDialog(); // Close after successful completion
}) })
.catch((error) => { .catch(error => {
console.error("Error restoring retrieval flow:", error); console.error("Error restoring retrieval flow:", error);
closeDialog(); // Close even on error (could show error toast instead) closeDialog(); // Close even on error (could show error toast instead)
}); });
@ -577,7 +587,7 @@ function KnowledgeSourcesPage() {
fetch(`/api/reset-flow/ingest`, { fetch(`/api/reset-flow/ingest`, {
method: "POST", method: "POST",
}) })
.then((response) => { .then(response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -592,7 +602,7 @@ function KnowledgeSourcesPage() {
setPictureDescriptions(false); setPictureDescriptions(false);
closeDialog(); // Close after successful completion closeDialog(); // Close after successful completion
}) })
.catch((error) => { .catch(error => {
console.error("Error restoring ingest flow:", error); console.error("Error restoring ingest flow:", error);
closeDialog(); // Close even on error (could show error toast instead) closeDialog(); // Close even on error (could show error toast instead)
}); });
@ -609,85 +619,88 @@ function KnowledgeSourcesPage() {
</div> </div>
{/* Conditional Sync Settings or No-Auth Message */} {/* Conditional Sync Settings or No-Auth Message */}
{isNoAuthMode ? ( {
<Card className="border-yellow-500/50 bg-yellow-500/5"> isNoAuthMode ? (
<CardHeader> <Card className="border-yellow-500/50 bg-yellow-500/5">
<CardTitle className="text-lg text-yellow-600"> <CardHeader>
Cloud connectors are only available with auth mode enabled <CardTitle className="text-lg text-yellow-600">
</CardTitle> Cloud connectors are only available with auth mode enabled
<CardDescription className="text-sm"> </CardTitle>
Please provide the following environment variables and restart: <CardDescription className="text-sm">
</CardDescription> Please provide the following environment variables and
</CardHeader> restart:
<CardContent> </CardDescription>
<div className="bg-muted rounded-md p-4 font-mono text-sm"> </CardHeader>
<div className="text-muted-foreground mb-2"> <CardContent>
# make here https://console.cloud.google.com/apis/credentials <div className="bg-muted rounded-md p-4 font-mono text-sm">
<div className="text-muted-foreground mb-2">
# make here
https://console.cloud.google.com/apis/credentials
</div>
<div>GOOGLE_OAUTH_CLIENT_ID=</div>
<div>GOOGLE_OAUTH_CLIENT_SECRET=</div>
</div> </div>
<div>GOOGLE_OAUTH_CLIENT_ID=</div> </CardContent>
<div>GOOGLE_OAUTH_CLIENT_SECRET=</div> </Card>
</div> ) : null
</CardContent> // <div className="flex items-center justify-between py-4">
</Card> // <div>
) : ( // <h3 className="text-lg font-medium">Sync Settings</h3>
<div className="flex items-center justify-between py-4"> // <p className="text-sm text-muted-foreground">
<div> // Configure how many files to sync when manually triggering a sync
<h3 className="text-lg font-medium">Sync Settings</h3> // </p>
<p className="text-sm text-muted-foreground"> // </div>
Configure how many files to sync when manually triggering a sync // <div className="flex items-center gap-4">
</p> // <div className="flex items-center space-x-2">
</div> // <Checkbox
<div className="flex items-center gap-4"> // id="syncAllFiles"
<div className="flex items-center space-x-2"> // checked={syncAllFiles}
<Checkbox // onCheckedChange={checked => {
id="syncAllFiles" // setSyncAllFiles(!!checked);
checked={syncAllFiles} // if (checked) {
onCheckedChange={(checked) => { // setMaxFiles(0);
setSyncAllFiles(!!checked); // } else {
if (checked) { // setMaxFiles(10);
setMaxFiles(0); // }
} else { // }}
setMaxFiles(10); // />
} // <Label
}} // htmlFor="syncAllFiles"
/> // className="font-medium whitespace-nowrap"
<Label // >
htmlFor="syncAllFiles" // Sync all files
className="font-medium whitespace-nowrap" // </Label>
> // </div>
Sync all files // <Label
</Label> // htmlFor="maxFiles"
</div> // className="font-medium whitespace-nowrap"
<Label // >
htmlFor="maxFiles" // Max files per sync:
className="font-medium whitespace-nowrap" // </Label>
> // <div className="relative">
Max files per sync: // <Input
</Label> // id="maxFiles"
<div className="relative"> // type="number"
<Input // value={syncAllFiles ? 0 : maxFiles}
id="maxFiles" // onChange={e => setMaxFiles(parseInt(e.target.value) || 10)}
type="number" // disabled={syncAllFiles}
value={syncAllFiles ? 0 : maxFiles} // className="w-16 min-w-16 max-w-16 flex-shrink-0 disabled:opacity-50 disabled:cursor-not-allowed"
onChange={(e) => setMaxFiles(parseInt(e.target.value) || 10)} // min="1"
disabled={syncAllFiles} // max="100"
className="w-16 min-w-16 max-w-16 flex-shrink-0 disabled:opacity-50 disabled:cursor-not-allowed" // title={
min="1" // syncAllFiles
max="100" // ? "Disabled when 'Sync all files' is checked"
title={ // : "Leave blank or set to 0 for unlimited"
syncAllFiles // }
? "Disabled when 'Sync all files' is checked" // />
: "Leave blank or set to 0 for unlimited" // </div>
} // </div>
/> // </div>
</div> }
</div>
</div>
)}
{/* Connectors Grid */} {/* Connectors Grid */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{connectors.map((connector) => ( {connectors.map(connector => (
<Card key={connector.id} className="relative flex flex-col"> <Card key={connector.id} className="relative flex flex-col">
<CardHeader> <CardHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -709,22 +722,13 @@ function KnowledgeSourcesPage() {
{connector.status === "connected" ? ( {connector.status === "connected" ? (
<div className="space-y-3"> <div className="space-y-3">
<Button <Button
onClick={() => handleSync(connector)} onClick={() => navigateToKnowledgePage(connector)}
disabled={isSyncing === connector.id} disabled={isSyncing === connector.id}
className="w-full" className="w-full"
variant="outline" variant="outline"
> >
{isSyncing === connector.id ? ( <Plus className="h-4 w-4" />
<> Add Knowledge
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Syncing...
</>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4" />
Sync Now
</>
)}
</Button> </Button>
{syncResults[connector.id] && ( {syncResults[connector.id] && (
@ -830,7 +834,7 @@ function KnowledgeSourcesPage() {
} }
confirmText="Proceed" confirmText="Proceed"
confirmIcon={<ArrowUpRight />} confirmIcon={<ArrowUpRight />}
onConfirm={(closeDialog) => onConfirm={closeDialog =>
handleEditInLangflow("chat", closeDialog) handleEditInLangflow("chat", closeDialog)
} }
variant="warning" variant="warning"
@ -850,8 +854,7 @@ function KnowledgeSourcesPage() {
<Select <Select
value={ value={
settings.agent?.llm_model || settings.agent?.llm_model ||
modelsData?.language_models?.find((m) => m.default) modelsData?.language_models?.find(m => m.default)?.value ||
?.value ||
"gpt-4" "gpt-4"
} }
onValueChange={handleModelChange} onValueChange={handleModelChange}
@ -879,7 +882,7 @@ function KnowledgeSourcesPage() {
id="system-prompt" id="system-prompt"
placeholder="Enter your agent instructions here..." placeholder="Enter your agent instructions here..."
value={systemPrompt} value={systemPrompt}
onChange={(e) => setSystemPrompt(e.target.value)} onChange={e => setSystemPrompt(e.target.value)}
rows={6} rows={6}
className={`resize-none ${ className={`resize-none ${
systemPrompt.length > MAX_SYSTEM_PROMPT_CHARS systemPrompt.length > MAX_SYSTEM_PROMPT_CHARS
@ -990,7 +993,7 @@ function KnowledgeSourcesPage() {
confirmText="Proceed" confirmText="Proceed"
confirmIcon={<ArrowUpRight />} confirmIcon={<ArrowUpRight />}
variant="warning" variant="warning"
onConfirm={(closeDialog) => onConfirm={closeDialog =>
handleEditInLangflow("ingest", closeDialog) handleEditInLangflow("ingest", closeDialog)
} }
/> />
@ -1010,8 +1013,7 @@ function KnowledgeSourcesPage() {
disabled={true} disabled={true}
value={ value={
settings.knowledge?.embedding_model || settings.knowledge?.embedding_model ||
modelsData?.embedding_models?.find((m) => m.default) modelsData?.embedding_models?.find(m => m.default)?.value ||
?.value ||
"text-embedding-ada-002" "text-embedding-ada-002"
} }
onValueChange={handleEmbeddingModelChange} onValueChange={handleEmbeddingModelChange}
@ -1049,7 +1051,7 @@ function KnowledgeSourcesPage() {
type="number" type="number"
min="1" min="1"
value={chunkSize} value={chunkSize}
onChange={(e) => handleChunkSizeChange(e.target.value)} onChange={e => handleChunkSizeChange(e.target.value)}
className="w-full pr-20" className="w-full pr-20"
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none">
@ -1072,7 +1074,7 @@ function KnowledgeSourcesPage() {
type="number" type="number"
min="0" min="0"
value={chunkOverlap} value={chunkOverlap}
onChange={(e) => handleChunkOverlapChange(e.target.value)} onChange={e => handleChunkOverlapChange(e.target.value)}
className="w-full pr-20" className="w-full pr-20"
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none">

View file

@ -73,7 +73,7 @@ class GoogleDriveConnector(BaseConnector):
# Connector metadata # Connector metadata
CONNECTOR_NAME = "Google Drive" CONNECTOR_NAME = "Google Drive"
CONNECTOR_DESCRIPTION = "Connect your Google Drive to automatically sync documents" CONNECTOR_DESCRIPTION = "Add knowledge from Google Drive"
CONNECTOR_ICON = "google-drive" CONNECTOR_ICON = "google-drive"
# Supported alias keys coming from various frontends / pickers # Supported alias keys coming from various frontends / pickers

View file

@ -19,7 +19,7 @@ class OneDriveConnector(BaseConnector):
# Connector metadata # Connector metadata
CONNECTOR_NAME = "OneDrive" CONNECTOR_NAME = "OneDrive"
CONNECTOR_DESCRIPTION = "Connect to OneDrive (personal) to sync documents and files" CONNECTOR_DESCRIPTION = "Add knowledge from OneDrive"
CONNECTOR_ICON = "onedrive" CONNECTOR_ICON = "onedrive"
def __init__(self, config: Dict[str, Any]): def __init__(self, config: Dict[str, Any]):

View file

@ -20,7 +20,7 @@ class SharePointConnector(BaseConnector):
# Connector metadata # Connector metadata
CONNECTOR_NAME = "SharePoint" CONNECTOR_NAME = "SharePoint"
CONNECTOR_DESCRIPTION = "Connect to SharePoint to sync documents and files" CONNECTOR_DESCRIPTION = "Add knowledge from SharePoint"
CONNECTOR_ICON = "sharepoint" CONNECTOR_ICON = "sharepoint"
def __init__(self, config: Dict[str, Any]): def __init__(self, config: Dict[str, Any]):