Skip to content

Commit 17374f8

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/langflow_ingestion_disable
2 parents 03e7b5b + 8a684bf commit 17374f8

31 files changed

Lines changed: 2191 additions & 558 deletions

.gitignore

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ wheels/
2424
!src/tui/_assets/flows/*.json
2525
!src/tui/_assets/flows/components/*.json
2626
!frontend/*.json
27-
!**/.claude-plugin/*.json
2827
.DS_Store
2928

3029
/config/
@@ -39,20 +38,16 @@ langflow-data/
3938
node_modules
4039

4140
# AI Tools
42-
# Per-contributor Claude Code state is ignored, but shared skills and
43-
# plugin manifests (.claude/skills, .claude-plugin) are tracked.
44-
/.claude/*
45-
!/.claude/skills/
41+
/.claude
42+
/CLAUDE.md
4643
/.gemini
4744
/GEMINI.md
4845
/.bob
46+
/AGENTS.md
4947
documents/warmup_ocr.pdf
5048
documents/openrag-documentation.pdf
5149
documents/ibm_anthropic.pdf
5250
documents/docling.pdf
5351
/opensearch-data-new-lf
5452
/opensearch-data2
55-
data/openrag.db
56-
data/openrag.db
5753
*.db
58-
/data

Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ factory-reset: ## Complete reset (stop, remove volumes, clear data, remove image
534534
echo " - Remove all volumes"; \
535535
echo " - Delete langflow-data directory"; \
536536
echo " - Delete config directory"; \
537+
echo " - Delete data directory (database and session configs)"; \
537538
echo " - Delete JWT keys (private_key.pem, public_key.pem)"; \
538539
echo " - Remove OpenRAG images"; \
539540
echo ""; \
@@ -559,6 +560,16 @@ factory-reset: ## Complete reset (stop, remove volumes, clear data, remove image
559560
rm -rf config; \
560561
echo "$(PURPLE)config removed$(NC)"; \
561562
fi; \
563+
if [ -d "data" ]; then \
564+
echo "Removing data..."; \
565+
rm -rf data; \
566+
echo "$(PURPLE)data removed$(NC)"; \
567+
fi; \
568+
if [ -n "$$OPENRAG_DATA_PATH" ] && [ -d "$$OPENRAG_DATA_PATH" ]; then \
569+
echo "Removing $$OPENRAG_DATA_PATH..."; \
570+
rm -rf "$$OPENRAG_DATA_PATH"; \
571+
echo "$(PURPLE)$$OPENRAG_DATA_PATH removed$(NC)"; \
572+
fi; \
562573
if [ -f "keys/private_key.pem" ] || [ -f "keys/public_key.pem" ]; then \
563574
echo "Removing JWT keys..."; \
564575
rm -f keys/private_key.pem keys/public_key.pem 2>/dev/null || \

frontend/app/api/mutations/useSyncConnector.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@ interface SyncResponse {
1313
errors?: Array<{ connector_type: string; error: string }> | null;
1414
}
1515

16+
export interface OrphanFile {
17+
document_id: string;
18+
filename: string;
19+
}
20+
21+
export interface SyncPreviewResponse {
22+
connector_type: string;
23+
synced_count: number;
24+
orphans: OrphanFile[];
25+
/** False when strict gating aborted orphan detection (e.g. an active
26+
* connection was unauthenticated). UI should reflect that deletions
27+
* cannot be predicted in that case. */
28+
orphans_available: boolean;
29+
}
30+
31+
export interface SyncAllPreviewResponse {
32+
orphans_by_type: Record<string, OrphanFile[]>;
33+
synced_count_by_type: Record<string, number>;
34+
orphans_available_by_type: Record<string, boolean>;
35+
}
36+
1637
// Sync all cloud connectors
1738
const syncAllConnectors = async (): Promise<SyncResponse> => {
1839
const response = await fetch("/api/connectors/sync-all", {
@@ -92,3 +113,44 @@ export const useSyncConnector = () => {
92113
},
93114
});
94115
};
116+
117+
const syncConnectorPreview = async (
118+
connectorType: string,
119+
): Promise<SyncPreviewResponse> => {
120+
const response = await fetch(
121+
`/api/connectors/${connectorType}/sync-preview`,
122+
{
123+
method: "POST",
124+
headers: { "Content-Type": "application/json" },
125+
},
126+
);
127+
128+
if (!response.ok) {
129+
const error = await response.json().catch(() => ({}));
130+
throw new Error(
131+
error.error || `Failed to preview sync for ${connectorType}`,
132+
);
133+
}
134+
135+
return response.json();
136+
};
137+
138+
const syncAllConnectorsPreview = async (): Promise<SyncAllPreviewResponse> => {
139+
const response = await fetch("/api/connectors/sync-all-preview", {
140+
method: "POST",
141+
headers: { "Content-Type": "application/json" },
142+
});
143+
144+
if (!response.ok) {
145+
const error = await response.json().catch(() => ({}));
146+
throw new Error(error.error || "Failed to preview sync");
147+
}
148+
149+
return response.json();
150+
};
151+
152+
export const useSyncConnectorPreview = () =>
153+
useMutation({ mutationFn: syncConnectorPreview });
154+
155+
export const useSyncAllConnectorsPreview = () =>
156+
useMutation({ mutationFn: syncAllConnectorsPreview });

frontend/app/api/queries/useGetSearchQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export const useGetSearchQuery = (
8686
query: string,
8787
queryData?: ParsedQueryData | null,
8888
options?: Omit<
89-
UseQueryOptions<SearchResult, Error, SearchResult>,
89+
UseQueryOptions<SearchResult, Error, SearchResult, any[]>,
9090
"queryKey" | "queryFn"
9191
>,
9292
) => {

frontend/app/knowledge/page.tsx

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,14 @@ import GoogleDriveIcon from "../../components/icons/google-drive-logo";
5555
import IBMCOSIcon from "../../components/icons/ibm-cos-icon";
5656
import OneDriveIcon from "../../components/icons/one-drive-logo";
5757
import SharePointIcon from "../../components/icons/share-point-logo";
58+
import { SyncConfirmDialog } from "../../components/sync-confirm-dialog";
5859
import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
5960
import { useRefreshOpenragDocs } from "../api/mutations/useRefreshOpenragDocs";
60-
import { useSyncAllConnectors } from "../api/mutations/useSyncConnector";
61+
import {
62+
type SyncAllPreviewResponse,
63+
useSyncAllConnectors,
64+
useSyncAllConnectorsPreview,
65+
} from "../api/mutations/useSyncConnector";
6166

6267
/** List-files uses term filters; "*" means "any" in the UI — do not send it literally. */
6368
function listFilesFilterParam(values?: string[]): string | undefined {
@@ -119,7 +124,51 @@ function SearchPage() {
119124

120125
const deleteDocumentMutation = useDeleteDocument();
121126
const syncAllConnectorsMutation = useSyncAllConnectors();
127+
const syncAllPreviewMutation = useSyncAllConnectorsPreview();
122128
const refreshOpenragDocsMutation = useRefreshOpenragDocs();
129+
const [syncDialogOpen, setSyncDialogOpen] = useState(false);
130+
const [syncPreview, setSyncPreview] = useState<SyncAllPreviewResponse | null>(
131+
null,
132+
);
133+
134+
const handleOpenSyncDialog = useCallback(async () => {
135+
setSyncPreview(null);
136+
setSyncDialogOpen(true);
137+
try {
138+
const preview = await syncAllPreviewMutation.mutateAsync();
139+
setSyncPreview(preview);
140+
} catch (error) {
141+
setSyncDialogOpen(false);
142+
toast.error(
143+
error instanceof Error ? error.message : "Failed to preview sync",
144+
);
145+
}
146+
}, [syncAllPreviewMutation]);
147+
148+
const handleConfirmSync = useCallback(async () => {
149+
try {
150+
const result = await syncAllConnectorsMutation.mutateAsync();
151+
if (result.status === "no_files") {
152+
toast.info(
153+
result.message ||
154+
"No cloud files to sync. Add files from cloud connectors first.",
155+
);
156+
} else if (
157+
result.synced_connectors &&
158+
result.synced_connectors.length > 0
159+
) {
160+
toast.success(
161+
`Sync started for ${result.synced_connectors.join(", ")}. Check task notifications for progress.`,
162+
);
163+
} else if (result.errors && result.errors.length > 0) {
164+
toast.error("Some connectors failed to sync");
165+
}
166+
} catch (error) {
167+
toast.error(
168+
error instanceof Error ? error.message : "Failed to sync connectors",
169+
);
170+
}
171+
}, [syncAllConnectorsMutation]);
123172

124173
useEffect(() => {
125174
refreshTasks();
@@ -761,36 +810,14 @@ function SearchPage() {
761810
type="button"
762811
variant="outline"
763812
className="rounded-lg flex-shrink-0"
764-
disabled={syncAllConnectorsMutation.isPending}
765-
onClick={async () => {
766-
try {
767-
toast.info("Syncing all cloud connectors...");
768-
const result = await syncAllConnectorsMutation.mutateAsync();
769-
if (result.status === "no_files") {
770-
toast.info(
771-
result.message ||
772-
"No cloud files to sync. Add files from cloud connectors first.",
773-
);
774-
} else if (
775-
result.synced_connectors &&
776-
result.synced_connectors.length > 0
777-
) {
778-
toast.success(
779-
`Sync started for ${result.synced_connectors.join(", ")}. Check task notifications for progress.`,
780-
);
781-
} else if (result.errors && result.errors.length > 0) {
782-
toast.error("Some connectors failed to sync");
783-
}
784-
} catch (error) {
785-
toast.error(
786-
error instanceof Error
787-
? error.message
788-
: "Failed to sync connectors",
789-
);
790-
}
791-
}}
813+
disabled={
814+
syncAllConnectorsMutation.isPending ||
815+
syncAllPreviewMutation.isPending
816+
}
817+
onClick={handleOpenSyncDialog}
792818
>
793-
{syncAllConnectorsMutation.isPending ? (
819+
{syncAllConnectorsMutation.isPending ||
820+
syncAllPreviewMutation.isPending ? (
794821
<>
795822
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
796823
Syncing...
@@ -961,6 +988,18 @@ function SearchPage() {
961988
<p className="my-2">Documents to be deleted:</p>
962989
{formatFilesToDelete(selectedRows)}
963990
</DeleteConfirmationDialog>
991+
992+
<SyncConfirmDialog
993+
open={syncDialogOpen}
994+
onOpenChange={setSyncDialogOpen}
995+
onConfirm={handleConfirmSync}
996+
isLoading={syncAllPreviewMutation.isPending || syncPreview === null}
997+
isSyncing={syncAllConnectorsMutation.isPending}
998+
isSyncAll
999+
orphansByType={syncPreview?.orphans_by_type}
1000+
orphansAvailableByType={syncPreview?.orphans_available_by_type}
1001+
syncedCountByType={syncPreview?.synced_count_by_type}
1002+
/>
9641003
</>
9651004
);
9661005
}

frontend/app/onboarding/_components/onboarding-card.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,10 @@ const OnboardingCard = ({
510510
>
511511
<div className="pb-6 flex items-center gap-4">
512512
<X className="w-4 h-4 text-destructive shrink-0" />
513-
<span className="text-mmd text-muted-foreground">
513+
<span
514+
data-testid="onboarding-error"
515+
className="text-mmd text-muted-foreground"
516+
>
514517
{error}
515518
</span>
516519
</div>

frontend/app/onboarding/_components/onboarding-upload.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
2424
const [uploadedTaskId, setUploadedTaskId] = useState<string | null>(null);
2525
const [shouldCreateFilter, setShouldCreateFilter] = useState(false);
2626
const [isCreatingFilter, setIsCreatingFilter] = useState(false);
27+
const [error, setError] = useState<string | null>(null);
2728

2829
const createFilterMutation = useCreateFilter();
2930
const updateOnboardingMutation = useUpdateOnboardingStateMutation();
@@ -63,6 +64,41 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
6364
matchingTask.status === "running" ||
6465
matchingTask.status === "processing";
6566

67+
// Check if matching task failed or has error status
68+
const failedTask =
69+
matchingTask.status === "failed" || matchingTask.status === "error";
70+
71+
// Check if any file inside the task failed
72+
const filesArray = matchingTask.files
73+
? (Object.values(matchingTask.files) as {
74+
status: string;
75+
error?: string;
76+
}[])
77+
: [];
78+
const hasFailedFile = filesArray.some(
79+
(file) => file.status === "failed" || file.status === "error",
80+
);
81+
82+
if (failedTask || hasFailedFile) {
83+
let errorMessage = "Document ingestion failed. Please try again.";
84+
if (matchingTask.error) {
85+
errorMessage = matchingTask.error;
86+
} else {
87+
const failedFile = filesArray.find(
88+
(file) =>
89+
(file.status === "failed" || file.status === "error") && file.error,
90+
);
91+
if (failedFile?.error) {
92+
errorMessage = failedFile.error;
93+
}
94+
}
95+
96+
setError(errorMessage);
97+
setCurrentStep(null);
98+
setUploadedTaskId(null);
99+
return;
100+
}
101+
66102
// If task is completed or has processed files, complete the onboarding step
67103
if (!isTaskActive || (matchingTask.processed_files ?? 0) > 0) {
68104
// Set to final step to show "Done"
@@ -169,6 +205,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
169205

170206
const performUpload = async (file: File) => {
171207
setIsUploading(true);
208+
setError(null);
172209
try {
173210
setCurrentStep(0);
174211
const result = await uploadFile(file, true, true); // Pass createFilter=true
@@ -193,6 +230,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
193230
const errorMessage =
194231
error instanceof Error ? error.message : "Upload failed";
195232
console.error("Upload failed", errorMessage);
233+
setError(errorMessage);
196234

197235
// Dispatch event that chat context can listen to
198236
// This avoids circular dependency issues
@@ -246,6 +284,25 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
246284
exit={{ opacity: 0, y: -24 }}
247285
transition={{ duration: 0.4, ease: "easeInOut" }}
248286
>
287+
<AnimatePresence mode="wait">
288+
{error && (
289+
<motion.div
290+
key="error"
291+
initial={{ opacity: 1, y: 0, height: "auto" }}
292+
exit={{ opacity: 0, y: -10, height: 0 }}
293+
>
294+
<div className="pb-6 flex items-center gap-4">
295+
<X className="w-4 h-4 text-destructive shrink-0" />
296+
<span
297+
data-testid="onboarding-upload-error"
298+
className="text-mmd text-muted-foreground"
299+
>
300+
{error}
301+
</span>
302+
</div>
303+
</motion.div>
304+
)}
305+
</AnimatePresence>
249306
<Button
250307
size="sm"
251308
variant="outline"

frontend/components/cloud-picker/file-item.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,24 @@ const getMimeTypeLabel = (mimeType: string) => {
3131
"application/vnd.google-apps.folder": "Folder",
3232
"application/pdf": "PDF",
3333
"text/plain": "Text",
34+
"text/csv": "CSV",
35+
"text/html": "HTML",
36+
"application/xml": "XML",
37+
"application/json": "JSON",
38+
"application/msword": "Word Doc",
39+
"application/vnd.ms-excel": "Excel",
40+
"application/vnd.ms-powerpoint": "PowerPoint",
3441
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
3542
"Word Doc",
43+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
44+
"Excel",
3645
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
3746
"PowerPoint",
47+
"application/octet-stream": "File",
48+
"image/jpeg": "JPEG",
49+
"image/png": "PNG",
50+
"image/gif": "GIF",
51+
"image/svg+xml": "SVG",
3852
};
3953

4054
return typeMap[mimeType] || mimeType?.split("/").pop() || "Document";

0 commit comments

Comments
 (0)