Merge branch 'add-mcp-agent-flows' of https://github.com/langflow-ai/openrag into add-mcp-agent-flows
This commit is contained in:
commit
f464a71500
11 changed files with 1932 additions and 1912 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -67,7 +67,7 @@ export function KnowledgeFilterList({
|
|||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
<div className="px-3 flex-1 min-h-0 flex flex-col">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex items-center justify-between mb-3 ml-3 mr-2">
|
||||
<div className="flex items-center justify-between mb-3 mr-2 ml-4">
|
||||
<h3 className="text-xs font-medium text-muted-foreground">
|
||||
Knowledge Filters
|
||||
</h3>
|
||||
|
|
@ -82,11 +82,11 @@ export function KnowledgeFilterList({
|
|||
</div>
|
||||
<div className="overflow-y-auto scrollbar-hide space-y-1">
|
||||
{loading ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-2">
|
||||
Loading...
|
||||
</div>
|
||||
) : filters.length === 0 ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||
<div className="text-[13px] text-muted-foreground pb-2 pt-3 ml-4">
|
||||
{searchQuery ? "No filters found" : "No saved filters"}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export function KnowledgeFilterPanel() {
|
|||
// Load available facets using search aggregations hook
|
||||
const { data: aggregations } = useGetSearchAggregations("*", 1, 0, {
|
||||
enabled: isPanelOpen,
|
||||
placeholderData: (prev) => prev,
|
||||
placeholderData: prev => prev,
|
||||
staleTime: 60_000,
|
||||
gcTime: 5 * 60_000,
|
||||
});
|
||||
|
|
@ -214,7 +214,7 @@ export function KnowledgeFilterPanel() {
|
|||
facetType: keyof typeof selectedFilters,
|
||||
newValues: string[]
|
||||
) => {
|
||||
setSelectedFilters((prev) => ({
|
||||
setSelectedFilters(prev => ({
|
||||
...prev,
|
||||
[facetType]: newValues,
|
||||
}));
|
||||
|
|
@ -234,7 +234,7 @@ export function KnowledgeFilterPanel() {
|
|||
return (
|
||||
<div className="h-full bg-background border-l">
|
||||
<Card className="h-full rounded-none border-0 flex flex-col">
|
||||
<CardHeader className="pb-3">
|
||||
<CardHeader className="pb-3 pt-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
Knowledge Filter
|
||||
|
|
@ -271,7 +271,7 @@ export function KnowledgeFilterPanel() {
|
|||
<Input
|
||||
id="filter-name"
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
onChange={e => {
|
||||
const v = e.target.value;
|
||||
setName(v);
|
||||
if (nameError && v.trim()) {
|
||||
|
|
@ -302,7 +302,7 @@ export function KnowledgeFilterPanel() {
|
|||
<Textarea
|
||||
id="filter-description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
placeholder="Provide a brief description of your knowledge filter..."
|
||||
rows={3}
|
||||
/>
|
||||
|
|
@ -319,7 +319,7 @@ export function KnowledgeFilterPanel() {
|
|||
placeholder="Enter your search query..."
|
||||
value={query}
|
||||
className="font-mono placeholder:font-mono"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
rows={2}
|
||||
disabled={!!queryOverride && !createMode}
|
||||
/>
|
||||
|
|
@ -329,13 +329,13 @@ export function KnowledgeFilterPanel() {
|
|||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<MultiSelect
|
||||
options={(availableFacets.data_sources || []).map((bucket) => ({
|
||||
options={(availableFacets.data_sources || []).map(bucket => ({
|
||||
value: bucket.key,
|
||||
label: bucket.key,
|
||||
count: bucket.count,
|
||||
}))}
|
||||
value={selectedFilters.data_sources}
|
||||
onValueChange={(values) =>
|
||||
onValueChange={values =>
|
||||
handleFilterChange("data_sources", values)
|
||||
}
|
||||
placeholder="Select sources..."
|
||||
|
|
@ -345,15 +345,13 @@ export function KnowledgeFilterPanel() {
|
|||
|
||||
<div className="space-y-2">
|
||||
<MultiSelect
|
||||
options={(availableFacets.document_types || []).map(
|
||||
(bucket) => ({
|
||||
value: bucket.key,
|
||||
label: bucket.key,
|
||||
count: bucket.count,
|
||||
})
|
||||
)}
|
||||
options={(availableFacets.document_types || []).map(bucket => ({
|
||||
value: bucket.key,
|
||||
label: bucket.key,
|
||||
count: bucket.count,
|
||||
}))}
|
||||
value={selectedFilters.document_types}
|
||||
onValueChange={(values) =>
|
||||
onValueChange={values =>
|
||||
handleFilterChange("document_types", values)
|
||||
}
|
||||
placeholder="Select types..."
|
||||
|
|
@ -363,13 +361,13 @@ export function KnowledgeFilterPanel() {
|
|||
|
||||
<div className="space-y-2">
|
||||
<MultiSelect
|
||||
options={(availableFacets.owners || []).map((bucket) => ({
|
||||
options={(availableFacets.owners || []).map(bucket => ({
|
||||
value: bucket.key,
|
||||
label: bucket.key,
|
||||
count: bucket.count,
|
||||
}))}
|
||||
value={selectedFilters.owners}
|
||||
onValueChange={(values) => handleFilterChange("owners", values)}
|
||||
onValueChange={values => handleFilterChange("owners", values)}
|
||||
placeholder="Select owners..."
|
||||
allOptionLabel="All Owners"
|
||||
/>
|
||||
|
|
@ -378,14 +376,14 @@ export function KnowledgeFilterPanel() {
|
|||
<div className="space-y-2">
|
||||
<MultiSelect
|
||||
options={(availableFacets.connector_types || []).map(
|
||||
(bucket) => ({
|
||||
bucket => ({
|
||||
value: bucket.key,
|
||||
label: bucket.key,
|
||||
count: bucket.count,
|
||||
})
|
||||
)}
|
||||
value={selectedFilters.connector_types}
|
||||
onValueChange={(values) =>
|
||||
onValueChange={values =>
|
||||
handleFilterChange("connector_types", values)
|
||||
}
|
||||
placeholder="Select connectors..."
|
||||
|
|
@ -405,7 +403,7 @@ export function KnowledgeFilterPanel() {
|
|||
min="1"
|
||||
max="1000"
|
||||
value={resultLimit}
|
||||
onChange={(e) => {
|
||||
onChange={e => {
|
||||
const newLimit = Math.max(
|
||||
1,
|
||||
Math.min(1000, parseInt(e.target.value) || 1)
|
||||
|
|
@ -418,7 +416,7 @@ export function KnowledgeFilterPanel() {
|
|||
</div>
|
||||
<Slider
|
||||
value={[resultLimit]}
|
||||
onValueChange={(values) => setResultLimit(values[0])}
|
||||
onValueChange={values => setResultLimit(values[0])}
|
||||
max={1000}
|
||||
min={1}
|
||||
step={1}
|
||||
|
|
@ -438,7 +436,7 @@ export function KnowledgeFilterPanel() {
|
|||
max="5"
|
||||
step="0.1"
|
||||
value={scoreThreshold}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
setScoreThreshold(parseFloat(e.target.value) || 0)
|
||||
}
|
||||
className="h-6 text-xs text-right px-2 bg-muted/30 !border-0 rounded ml-auto focus:ring-0 focus:outline-none"
|
||||
|
|
@ -447,7 +445,7 @@ export function KnowledgeFilterPanel() {
|
|||
</div>
|
||||
<Slider
|
||||
value={[scoreThreshold]}
|
||||
onValueChange={(values) => setScoreThreshold(values[0])}
|
||||
onValueChange={values => setScoreThreshold(values[0])}
|
||||
max={5}
|
||||
min={0}
|
||||
step={0.1}
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ function ChatPage() {
|
|||
content: `🔄 Starting upload of **${file.name}**...`,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, uploadStartMessage]);
|
||||
setMessages(prev => [...prev, uploadStartMessage]);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
|
@ -282,7 +282,7 @@ function ChatPage() {
|
|||
content: `⏳ Upload initiated for **${file.name}**. Processing in background... (Task ID: ${taskId})`,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev.slice(0, -1), pollingMessage]);
|
||||
setMessages(prev => [...prev.slice(0, -1), pollingMessage]);
|
||||
} else if (response.ok) {
|
||||
// Original flow: Direct response
|
||||
|
||||
|
|
@ -296,7 +296,7 @@ function ChatPage() {
|
|||
timestamp: new Date(),
|
||||
};
|
||||
|
||||
setMessages((prev) => [...prev.slice(0, -1), uploadMessage]);
|
||||
setMessages(prev => [...prev.slice(0, -1), uploadMessage]);
|
||||
|
||||
// Add file to conversation docs
|
||||
if (result.filename) {
|
||||
|
|
@ -305,7 +305,7 @@ function ChatPage() {
|
|||
|
||||
// Update the response ID for this endpoint
|
||||
if (result.response_id) {
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[endpoint]: result.response_id,
|
||||
}));
|
||||
|
|
@ -329,7 +329,7 @@ function ChatPage() {
|
|||
content: `❌ Failed to process document. Please try again.`,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev.slice(0, -1), errorMessage]);
|
||||
setMessages(prev => [...prev.slice(0, -1), errorMessage]);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
setLoading(false);
|
||||
|
|
@ -620,7 +620,7 @@ function ChatPage() {
|
|||
lastLoadedConversationRef.current = conversationData.response_id;
|
||||
|
||||
// Set the previous response ID for this conversation
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[conversationData.endpoint]: conversationData.response_id,
|
||||
}));
|
||||
|
|
@ -662,7 +662,7 @@ function ChatPage() {
|
|||
content: `🔄 Starting upload of **${filename}**...`,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, uploadStartMessage]);
|
||||
setMessages(prev => [...prev, uploadStartMessage]);
|
||||
};
|
||||
|
||||
const handleFileUploaded = (event: CustomEvent) => {
|
||||
|
|
@ -680,11 +680,11 @@ function ChatPage() {
|
|||
timestamp: new Date(),
|
||||
};
|
||||
|
||||
setMessages((prev) => [...prev.slice(0, -1), uploadMessage]);
|
||||
setMessages(prev => [...prev.slice(0, -1), uploadMessage]);
|
||||
|
||||
// Update the response ID for this endpoint
|
||||
if (result.response_id) {
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[endpoint]: result.response_id,
|
||||
}));
|
||||
|
|
@ -711,7 +711,7 @@ function ChatPage() {
|
|||
content: `❌ Upload failed for **${filename}**: ${error}`,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev.slice(0, -1), errorMessage]);
|
||||
setMessages(prev => [...prev.slice(0, -1), errorMessage]);
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
|
|
@ -1007,7 +1007,7 @@ function ChatPage() {
|
|||
if (chunk.delta.finish_reason) {
|
||||
console.log("Finish reason:", chunk.delta.finish_reason);
|
||||
// Mark any pending function calls as completed
|
||||
currentFunctionCalls.forEach((fc) => {
|
||||
currentFunctionCalls.forEach(fc => {
|
||||
if (fc.status === "pending" && fc.argumentsString) {
|
||||
try {
|
||||
fc.arguments = JSON.parse(fc.argumentsString);
|
||||
|
|
@ -1040,13 +1040,13 @@ function ChatPage() {
|
|||
|
||||
// Try to find an existing pending call to update (created by earlier deltas)
|
||||
let existing = currentFunctionCalls.find(
|
||||
(fc) => fc.id === chunk.item.id
|
||||
fc => fc.id === chunk.item.id
|
||||
);
|
||||
if (!existing) {
|
||||
existing = [...currentFunctionCalls]
|
||||
.reverse()
|
||||
.find(
|
||||
(fc) =>
|
||||
fc =>
|
||||
fc.status === "pending" &&
|
||||
!fc.id &&
|
||||
fc.name === (chunk.item.tool_name || chunk.item.name)
|
||||
|
|
@ -1077,7 +1077,7 @@ function ChatPage() {
|
|||
currentFunctionCalls.push(functionCall);
|
||||
console.log(
|
||||
"🟢 Function calls now:",
|
||||
currentFunctionCalls.map((fc) => ({
|
||||
currentFunctionCalls.map(fc => ({
|
||||
id: fc.id,
|
||||
name: fc.name,
|
||||
}))
|
||||
|
|
@ -1150,7 +1150,7 @@ function ChatPage() {
|
|||
);
|
||||
console.log(
|
||||
"🔵 Looking for existing function calls:",
|
||||
currentFunctionCalls.map((fc) => ({
|
||||
currentFunctionCalls.map(fc => ({
|
||||
id: fc.id,
|
||||
name: fc.name,
|
||||
}))
|
||||
|
|
@ -1158,7 +1158,7 @@ function ChatPage() {
|
|||
|
||||
// Find existing function call by ID or name
|
||||
const functionCall = currentFunctionCalls.find(
|
||||
(fc) =>
|
||||
fc =>
|
||||
fc.id === chunk.item.id ||
|
||||
fc.name === chunk.item.tool_name ||
|
||||
fc.name === chunk.item.name
|
||||
|
|
@ -1206,7 +1206,7 @@ function ChatPage() {
|
|||
|
||||
// Find existing function call by ID, or by name/type if ID not available
|
||||
const functionCall = currentFunctionCalls.find(
|
||||
(fc) =>
|
||||
fc =>
|
||||
fc.id === chunk.item.id ||
|
||||
fc.name === chunk.item.tool_name ||
|
||||
fc.name === chunk.item.name ||
|
||||
|
|
@ -1261,13 +1261,13 @@ function ChatPage() {
|
|||
|
||||
// Dedupe by id or pending with same name
|
||||
let existing = currentFunctionCalls.find(
|
||||
(fc) => fc.id === chunk.item.id
|
||||
fc => fc.id === chunk.item.id
|
||||
);
|
||||
if (!existing) {
|
||||
existing = [...currentFunctionCalls]
|
||||
.reverse()
|
||||
.find(
|
||||
(fc) =>
|
||||
fc =>
|
||||
fc.status === "pending" &&
|
||||
!fc.id &&
|
||||
fc.name ===
|
||||
|
|
@ -1306,7 +1306,7 @@ function ChatPage() {
|
|||
currentFunctionCalls.push(functionCall);
|
||||
console.log(
|
||||
"🟡 Function calls now:",
|
||||
currentFunctionCalls.map((fc) => ({
|
||||
currentFunctionCalls.map(fc => ({
|
||||
id: fc.id,
|
||||
name: fc.name,
|
||||
type: fc.type,
|
||||
|
|
@ -1404,7 +1404,7 @@ function ChatPage() {
|
|||
};
|
||||
|
||||
if (!controller.signal.aborted && thisStreamId === streamIdRef.current) {
|
||||
setMessages((prev) => [...prev, finalMessage]);
|
||||
setMessages(prev => [...prev, finalMessage]);
|
||||
setStreamingMessage(null);
|
||||
if (previousResponseIds[endpoint]) {
|
||||
cancelNudges();
|
||||
|
|
@ -1417,7 +1417,7 @@ function ChatPage() {
|
|||
!controller.signal.aborted &&
|
||||
thisStreamId === streamIdRef.current
|
||||
) {
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[endpoint]: newResponseId,
|
||||
}));
|
||||
|
|
@ -1445,7 +1445,7 @@ function ChatPage() {
|
|||
"Sorry, I couldn't connect to the chat service. Please try again.",
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, errorMessage]);
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1458,7 +1458,7 @@ function ChatPage() {
|
|||
timestamp: new Date(),
|
||||
};
|
||||
|
||||
setMessages((prev) => [...prev, userMessage]);
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setInput("");
|
||||
setLoading(true);
|
||||
setIsFilterHighlighted(false);
|
||||
|
|
@ -1524,14 +1524,14 @@ function ChatPage() {
|
|||
content: result.response,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, assistantMessage]);
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
if (result.response_id) {
|
||||
cancelNudges();
|
||||
}
|
||||
|
||||
// Store the response ID if present for this endpoint
|
||||
if (result.response_id) {
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[endpoint]: result.response_id,
|
||||
}));
|
||||
|
|
@ -1552,7 +1552,7 @@ function ChatPage() {
|
|||
content: "Sorry, I encountered an error. Please try again.",
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, errorMessage]);
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Chat error:", error);
|
||||
|
|
@ -1562,7 +1562,7 @@ function ChatPage() {
|
|||
"Sorry, I couldn't connect to the chat service. Please try again.",
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, errorMessage]);
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1575,7 +1575,7 @@ function ChatPage() {
|
|||
};
|
||||
|
||||
const toggleFunctionCall = (functionCallId: string) => {
|
||||
setExpandedFunctionCalls((prev) => {
|
||||
setExpandedFunctionCalls(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(functionCallId)) {
|
||||
newSet.delete(functionCallId);
|
||||
|
|
@ -1632,7 +1632,7 @@ function ChatPage() {
|
|||
|
||||
// Set the response_id we want to continue from as the previous response ID
|
||||
// This tells the backend to continue the conversation from this point
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[endpoint]: responseIdToForkFrom,
|
||||
}));
|
||||
|
|
@ -1903,7 +1903,7 @@ function ChatPage() {
|
|||
}
|
||||
|
||||
if (isFilterDropdownOpen) {
|
||||
const filteredFilters = availableFilters.filter((filter) =>
|
||||
const filteredFilters = availableFilters.filter(filter =>
|
||||
filter.name.toLowerCase().includes(filterSearchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
|
|
@ -1921,7 +1921,7 @@ function ChatPage() {
|
|||
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setSelectedFilterIndex((prev) =>
|
||||
setSelectedFilterIndex(prev =>
|
||||
prev < filteredFilters.length - 1 ? prev + 1 : 0
|
||||
);
|
||||
return;
|
||||
|
|
@ -1929,7 +1929,7 @@ function ChatPage() {
|
|||
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setSelectedFilterIndex((prev) =>
|
||||
setSelectedFilterIndex(prev =>
|
||||
prev > 0 ? prev - 1 : filteredFilters.length - 1
|
||||
);
|
||||
return;
|
||||
|
|
@ -2159,7 +2159,7 @@ function ChatPage() {
|
|||
{endpoint === "chat" && (
|
||||
<div className="flex-shrink-0 ml-2">
|
||||
<button
|
||||
onClick={(e) => handleForkConversation(index, e)}
|
||||
onClick={e => handleForkConversation(index, e)}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-accent rounded text-muted-foreground hover:text-foreground"
|
||||
title="Fork conversation from here"
|
||||
>
|
||||
|
|
@ -2223,8 +2223,8 @@ function ChatPage() {
|
|||
)}
|
||||
|
||||
{/* Input Area - Fixed at bottom */}
|
||||
<div className="flex-shrink-0 p-6 pb-8 pt-4 flex justify-center">
|
||||
<div className="w-full max-w-[75%]">
|
||||
<div className="pb-8 pt-4 flex px-6">
|
||||
<div className="w-full">
|
||||
<form onSubmit={handleSubmit} className="relative">
|
||||
<div className="relative w-full bg-muted/20 rounded-lg border border-border/50 focus-within:ring-1 focus-within:ring-ring">
|
||||
{selectedFilter && (
|
||||
|
|
@ -2257,7 +2257,7 @@ function ChatPage() {
|
|||
value={input}
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onHeightChange={(height) => setTextareaHeight(height)}
|
||||
onHeightChange={height => setTextareaHeight(height)}
|
||||
maxRows={7}
|
||||
minRows={2}
|
||||
placeholder="Type to ask a question..."
|
||||
|
|
@ -2286,7 +2286,7 @@ function ChatPage() {
|
|||
variant="outline"
|
||||
size="iconSm"
|
||||
className="absolute bottom-3 left-3 h-8 w-8 p-0 rounded-full hover:bg-muted/50"
|
||||
onMouseDown={(e) => {
|
||||
onMouseDown={e => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={onAtClick}
|
||||
|
|
@ -2296,7 +2296,7 @@ function ChatPage() {
|
|||
</Button>
|
||||
<Popover
|
||||
open={isFilterDropdownOpen}
|
||||
onOpenChange={(open) => {
|
||||
onOpenChange={open => {
|
||||
setIsFilterDropdownOpen(open);
|
||||
}}
|
||||
>
|
||||
|
|
@ -2321,7 +2321,7 @@ function ChatPage() {
|
|||
align="start"
|
||||
sideOffset={6}
|
||||
alignOffset={-18}
|
||||
onOpenAutoFocus={(e) => {
|
||||
onOpenAutoFocus={e => {
|
||||
// Prevent auto focus on the popover content
|
||||
e.preventDefault();
|
||||
// Keep focus on the input
|
||||
|
|
@ -2354,7 +2354,7 @@ function ChatPage() {
|
|||
</button>
|
||||
)}
|
||||
{availableFilters
|
||||
.filter((filter) =>
|
||||
.filter(filter =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase())
|
||||
|
|
@ -2383,7 +2383,7 @@ function ChatPage() {
|
|||
)}
|
||||
</button>
|
||||
))}
|
||||
{availableFilters.filter((filter) =>
|
||||
{availableFilters.filter(filter =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase())
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@ import { Label } from "@/components/ui/label";
|
|||
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
|
||||
import { useTask } from "@/contexts/task-context";
|
||||
import {
|
||||
type ChunkResult,
|
||||
type File,
|
||||
useGetSearchQuery,
|
||||
type ChunkResult,
|
||||
type File,
|
||||
useGetSearchQuery,
|
||||
} from "../../api/queries/useGetSearchQuery";
|
||||
// import { Label } from "@/components/ui/label";
|
||||
// import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { KnowledgeSearchInput } from "@/components/knowledge-search-input";
|
||||
|
||||
const getFileTypeLabel = (mimetype: string) => {
|
||||
if (mimetype === "application/pdf") return "PDF";
|
||||
if (mimetype === "text/plain") return "Text";
|
||||
if (mimetype === "application/msword") return "Word Document";
|
||||
return "Unknown";
|
||||
if (mimetype === "application/pdf") return "PDF";
|
||||
if (mimetype === "text/plain") return "Text";
|
||||
if (mimetype === "application/msword") return "Word Document";
|
||||
return "Unknown";
|
||||
};
|
||||
|
||||
function ChunksPageContent() {
|
||||
|
|
@ -43,13 +43,13 @@ function ChunksPageContent() {
|
|||
number | null
|
||||
>(null);
|
||||
|
||||
// Calculate average chunk length
|
||||
const averageChunkLength = useMemo(
|
||||
() =>
|
||||
chunks.reduce((acc, chunk) => acc + chunk.text.length, 0) /
|
||||
chunks.length || 0,
|
||||
[chunks],
|
||||
);
|
||||
// Calculate average chunk length
|
||||
const averageChunkLength = useMemo(
|
||||
() =>
|
||||
chunks.reduce((acc, chunk) => acc + chunk.text.length, 0) /
|
||||
chunks.length || 0,
|
||||
[chunks]
|
||||
);
|
||||
|
||||
// const [selectAll, setSelectAll] = useState(false);
|
||||
|
||||
|
|
@ -59,70 +59,70 @@ function ChunksPageContent() {
|
|||
parsedFilterData
|
||||
);
|
||||
|
||||
const handleCopy = useCallback((text: string, index: number) => {
|
||||
// Trim whitespace and remove new lines/tabs for cleaner copy
|
||||
navigator.clipboard.writeText(text.trim().replace(/[\n\r\t]/gm, ""));
|
||||
setActiveCopiedChunkIndex(index);
|
||||
setTimeout(() => setActiveCopiedChunkIndex(null), 10 * 1000); // 10 seconds
|
||||
}, []);
|
||||
const handleCopy = useCallback((text: string, index: number) => {
|
||||
// Trim whitespace and remove new lines/tabs for cleaner copy
|
||||
navigator.clipboard.writeText(text.trim().replace(/[\n\r\t]/gm, ""));
|
||||
setActiveCopiedChunkIndex(index);
|
||||
setTimeout(() => setActiveCopiedChunkIndex(null), 10 * 1000); // 10 seconds
|
||||
}, []);
|
||||
|
||||
const fileData = (data as File[]).find(
|
||||
(file: File) => file.filename === filename,
|
||||
);
|
||||
const fileData = (data as File[]).find(
|
||||
(file: File) => file.filename === filename
|
||||
);
|
||||
|
||||
// Extract chunks for the specific file
|
||||
useEffect(() => {
|
||||
if (!filename || !(data as File[]).length) {
|
||||
setChunks([]);
|
||||
return;
|
||||
}
|
||||
// Extract chunks for the specific file
|
||||
useEffect(() => {
|
||||
if (!filename || !(data as File[]).length) {
|
||||
setChunks([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setChunks(
|
||||
fileData?.chunks?.map((chunk, i) => ({ ...chunk, index: i + 1 })) || [],
|
||||
);
|
||||
}, [data, filename]);
|
||||
setChunks(
|
||||
fileData?.chunks?.map((chunk, i) => ({ ...chunk, index: i + 1 })) || []
|
||||
);
|
||||
}, [data, filename]);
|
||||
|
||||
// Set selected state for all checkboxes when selectAll changes
|
||||
useEffect(() => {
|
||||
if (selectAll) {
|
||||
setSelectedChunks(new Set(chunks.map((_, index) => index)));
|
||||
} else {
|
||||
setSelectedChunks(new Set());
|
||||
}
|
||||
}, [selectAll, setSelectedChunks, chunks]);
|
||||
// Set selected state for all checkboxes when selectAll changes
|
||||
useEffect(() => {
|
||||
if (selectAll) {
|
||||
setSelectedChunks(new Set(chunks.map((_, index) => index)));
|
||||
} else {
|
||||
setSelectedChunks(new Set());
|
||||
}
|
||||
}, [selectAll, setSelectedChunks, chunks]);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
router.push("/knowledge");
|
||||
}, [router]);
|
||||
const handleBack = useCallback(() => {
|
||||
router.push("/knowledge");
|
||||
}, [router]);
|
||||
|
||||
// const handleChunkCardCheckboxChange = useCallback(
|
||||
// (index: number) => {
|
||||
// setSelectedChunks((prevSelected) => {
|
||||
// const newSelected = new Set(prevSelected);
|
||||
// if (newSelected.has(index)) {
|
||||
// newSelected.delete(index);
|
||||
// } else {
|
||||
// newSelected.add(index);
|
||||
// }
|
||||
// return newSelected;
|
||||
// });
|
||||
// },
|
||||
// [setSelectedChunks]
|
||||
// );
|
||||
// const handleChunkCardCheckboxChange = useCallback(
|
||||
// (index: number) => {
|
||||
// setSelectedChunks((prevSelected) => {
|
||||
// const newSelected = new Set(prevSelected);
|
||||
// if (newSelected.has(index)) {
|
||||
// newSelected.delete(index);
|
||||
// } else {
|
||||
// newSelected.add(index);
|
||||
// }
|
||||
// return newSelected;
|
||||
// });
|
||||
// },
|
||||
// [setSelectedChunks]
|
||||
// );
|
||||
|
||||
if (!filename) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
||||
<p className="text-lg text-muted-foreground">No file specified</p>
|
||||
<p className="text-sm text-muted-foreground/70 mt-2">
|
||||
Please select a file from the knowledge page
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!filename) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
||||
<p className="text-lg text-muted-foreground">No file specified</p>
|
||||
<p className="text-sm text-muted-foreground/70 mt-2">
|
||||
Please select a file from the knowledge page
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
|
|
@ -149,7 +149,7 @@ function ChunksPageContent() {
|
|||
<Checkbox
|
||||
id="selectAllChunks"
|
||||
checked={selectAll}
|
||||
onCheckedChange={(handleSelectAll) =>
|
||||
onCheckedChange={handleSelectAll =>
|
||||
setSelectAll(!!handleSelectAll)
|
||||
}
|
||||
/>
|
||||
|
|
@ -160,8 +160,8 @@ function ChunksPageContent() {
|
|||
Select all
|
||||
</Label>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Area - matches knowledge page structure */}
|
||||
<div className="flex-1 overflow-auto pr-6">
|
||||
|
|
@ -200,73 +200,73 @@ function ChunksPageContent() {
|
|||
}
|
||||
/>
|
||||
</div> */}
|
||||
<span className="text-sm font-bold">
|
||||
Chunk {chunk.index}
|
||||
</span>
|
||||
<span className="bg-background p-1 rounded text-xs text-muted-foreground/70">
|
||||
{chunk.text.length} chars
|
||||
</span>
|
||||
<div className="py-1">
|
||||
<Button
|
||||
onClick={() => handleCopy(chunk.text, index)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
>
|
||||
{activeCopiedChunkIndex === index ? (
|
||||
<Check className="text-muted-foreground" />
|
||||
) : (
|
||||
<Copy className="text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm font-bold">
|
||||
Chunk {chunk.index}
|
||||
</span>
|
||||
<span className="bg-background p-1 rounded text-xs text-muted-foreground/70">
|
||||
{chunk.text.length} chars
|
||||
</span>
|
||||
<div className="py-1">
|
||||
<Button
|
||||
onClick={() => handleCopy(chunk.text, index)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
>
|
||||
{activeCopiedChunkIndex === index ? (
|
||||
<Check className="text-muted-foreground" />
|
||||
) : (
|
||||
<Copy className="text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="bg-background p-1 rounded text-xs text-muted-foreground/70">
|
||||
{chunk.score.toFixed(2)} score
|
||||
</span>
|
||||
<span className="bg-background p-1 rounded text-xs text-muted-foreground/70">
|
||||
{chunk.score.toFixed(2)} score
|
||||
</span>
|
||||
|
||||
{/* TODO: Update to use active toggle */}
|
||||
{/* <span className="px-2 py-1 text-green-500">
|
||||
{/* TODO: Update to use active toggle */}
|
||||
{/* <span className="px-2 py-1 text-green-500">
|
||||
<Switch
|
||||
className="ml-2 bg-green-500"
|
||||
checked={true}
|
||||
/>
|
||||
Active
|
||||
</span> */}
|
||||
</div>
|
||||
<blockquote className="text-sm text-muted-foreground leading-relaxed ml-1.5">
|
||||
{chunk.text}
|
||||
</blockquote>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Right panel - Summary (TODO), Technical details, */}
|
||||
{chunks.length > 0 && (
|
||||
<div className="w-[320px] py-20 px-2">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold mt-3 mb-4">
|
||||
Technical details
|
||||
</h2>
|
||||
<dl>
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">
|
||||
Total chunks
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{chunks.length}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Avg length</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{averageChunkLength.toFixed(0)} chars
|
||||
</dd>
|
||||
</div>
|
||||
{/* TODO: Uncomment after data is available */}
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
</div>
|
||||
<blockquote className="text-sm text-muted-foreground leading-relaxed ml-1.5">
|
||||
{chunk.text}
|
||||
</blockquote>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Right panel - Summary (TODO), Technical details, */}
|
||||
{chunks.length > 0 && (
|
||||
<div className="w-[320px] py-20 px-2">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold mt-3 mb-4">
|
||||
Technical details
|
||||
</h2>
|
||||
<dl>
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">
|
||||
Total chunks
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{chunks.length}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Avg length</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{averageChunkLength.toFixed(0)} chars
|
||||
</dd>
|
||||
</div>
|
||||
{/* TODO: Uncomment after data is available */}
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Process time</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
</dd>
|
||||
|
|
@ -276,79 +276,79 @@ function ChunksPageContent() {
|
|||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
</dd>
|
||||
</div> */}
|
||||
</dl>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold mt-2 mb-3">
|
||||
Original document
|
||||
</h2>
|
||||
<dl>
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
</dl>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold mt-2 mb-3">
|
||||
Original document
|
||||
</h2>
|
||||
<dl>
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Name</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{fileData?.filename}
|
||||
</dd>
|
||||
</div> */}
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Type</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{fileData ? getFileTypeLabel(fileData.mimetype) : "Unknown"}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Size</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{fileData?.size
|
||||
? `${Math.round(fileData.size / 1024)} KB`
|
||||
: "Unknown"}
|
||||
</dd>
|
||||
</div>
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Type</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{fileData ? getFileTypeLabel(fileData.mimetype) : "Unknown"}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Size</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
{fileData?.size
|
||||
? `${Math.round(fileData.size / 1024)} KB`
|
||||
: "Unknown"}
|
||||
</dd>
|
||||
</div>
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Uploaded</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
N/A
|
||||
</dd>
|
||||
</div> */}
|
||||
{/* TODO: Uncomment after data is available */}
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
{/* TODO: Uncomment after data is available */}
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Source</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0"></dd>
|
||||
</div> */}
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
{/* <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 mb-2.5">
|
||||
<dt className="text-sm/6 text-muted-foreground">Updated</dt>
|
||||
<dd className="mt-1 text-sm/6 text-gray-100 sm:col-span-2 sm:mt-0">
|
||||
N/A
|
||||
</dd>
|
||||
</div> */}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChunksPage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50 animate-spin" />
|
||||
<p className="text-lg text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ChunksPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50 animate-spin" />
|
||||
<p className="text-lg text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ChunksPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProtectedChunksPage() {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<ChunksPage />
|
||||
</ProtectedRoute>
|
||||
);
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<ChunksPage />
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ function SearchPage() {
|
|||
parsedFilterData
|
||||
);
|
||||
// Convert TaskFiles to File format and merge with backend results
|
||||
const taskFilesAsFiles: File[] = taskFiles.map((taskFile) => {
|
||||
const taskFilesAsFiles: File[] = taskFiles.map(taskFile => {
|
||||
return {
|
||||
filename: taskFile.filename,
|
||||
mimetype: taskFile.mimetype,
|
||||
|
|
@ -82,12 +82,12 @@ function SearchPage() {
|
|||
|
||||
// Create a map of task files by filename for quick lookup
|
||||
const taskFileMap = new Map(
|
||||
taskFilesAsFiles.map((file) => [file.filename, file])
|
||||
taskFilesAsFiles.map(file => [file.filename, file])
|
||||
);
|
||||
|
||||
// Override backend files with task file status if they exist
|
||||
const backendFiles = (searchData as File[])
|
||||
.map((file) => {
|
||||
.map(file => {
|
||||
const taskFile = taskFileMap.get(file.filename);
|
||||
if (taskFile) {
|
||||
// Override backend file with task file data (includes status)
|
||||
|
|
@ -95,17 +95,17 @@ function SearchPage() {
|
|||
}
|
||||
return file;
|
||||
})
|
||||
.filter((file) => {
|
||||
.filter(file => {
|
||||
// Only filter out files that are currently processing AND in taskFiles
|
||||
const taskFile = taskFileMap.get(file.filename);
|
||||
return !taskFile || taskFile.status !== "processing";
|
||||
});
|
||||
|
||||
const filteredTaskFiles = taskFilesAsFiles.filter((taskFile) => {
|
||||
const filteredTaskFiles = taskFilesAsFiles.filter(taskFile => {
|
||||
return (
|
||||
taskFile.status !== "active" &&
|
||||
!backendFiles.some(
|
||||
(backendFile) => backendFile.filename === taskFile.filename
|
||||
backendFile => backendFile.filename === taskFile.filename
|
||||
)
|
||||
);
|
||||
});
|
||||
|
|
@ -184,7 +184,6 @@ function SearchPage() {
|
|||
{
|
||||
field: "avgScore",
|
||||
headerName: "Avg score",
|
||||
initialFlex: 0.5,
|
||||
cellRenderer: ({ value }: CustomCellRendererProps<File>) => {
|
||||
return (
|
||||
<span className="text-xs text-accent-emerald-foreground bg-accent-emerald px-2 py-1 rounded">
|
||||
|
|
@ -246,7 +245,7 @@ function SearchPage() {
|
|||
|
||||
try {
|
||||
// Delete each file individually since the API expects one filename at a time
|
||||
const deletePromises = selectedRows.map((row) =>
|
||||
const deletePromises = selectedRows.map(row =>
|
||||
deleteDocumentMutation.mutateAsync({ filename: row.filename })
|
||||
);
|
||||
|
||||
|
|
@ -345,7 +344,7 @@ function SearchPage() {
|
|||
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
||||
|
||||
Documents to be deleted:
|
||||
${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`}
|
||||
${selectedRows.map(row => `• ${row.filename}`).join("\n")}`}
|
||||
confirmText="Delete All"
|
||||
onConfirm={handleBulkDelete}
|
||||
isLoading={deleteDocumentMutation.isPending}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,12 +60,12 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||
const isOnKnowledgePage = pathname.startsWith("/knowledge");
|
||||
|
||||
// List of paths with smaller max-width
|
||||
const smallWidthPaths = ["/settings", "/settings/connector/new"];
|
||||
const smallWidthPaths = ["/settings/connector/new"];
|
||||
const isSmallWidthPath = smallWidthPaths.includes(pathname);
|
||||
|
||||
// Calculate active tasks for the bell icon
|
||||
const activeTasks = tasks.filter(
|
||||
(task) =>
|
||||
task =>
|
||||
task.status === "pending" ||
|
||||
task.status === "running" ||
|
||||
task.status === "processing"
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ async def run_ingestion(
|
|||
payload = await request.json()
|
||||
file_ids = payload.get("file_ids")
|
||||
file_paths = payload.get("file_paths") or []
|
||||
file_metadata = payload.get("file_metadata") or [] # List of {filename, mimetype, size}
|
||||
session_id = payload.get("session_id")
|
||||
tweaks = payload.get("tweaks") or {}
|
||||
settings = payload.get("settings", {})
|
||||
|
|
@ -66,6 +67,21 @@ async def run_ingestion(
|
|||
{"error": "Provide file_paths or file_ids"}, status_code=400
|
||||
)
|
||||
|
||||
# Build file_tuples from file_metadata if provided, otherwise use empty strings
|
||||
file_tuples = []
|
||||
for i, file_path in enumerate(file_paths):
|
||||
if i < len(file_metadata):
|
||||
meta = file_metadata[i]
|
||||
filename = meta.get("filename", "")
|
||||
mimetype = meta.get("mimetype", "application/octet-stream")
|
||||
# For files already uploaded, we don't have content, so use empty bytes
|
||||
file_tuples.append((filename, b"", mimetype))
|
||||
else:
|
||||
# Extract filename from path if no metadata provided
|
||||
import os
|
||||
filename = os.path.basename(file_path)
|
||||
file_tuples.append((filename, b"", "application/octet-stream"))
|
||||
|
||||
# Convert UI settings to component tweaks using exact component IDs
|
||||
if settings:
|
||||
logger.debug("Applying ingestion settings", settings=settings)
|
||||
|
|
@ -114,6 +130,7 @@ async def run_ingestion(
|
|||
|
||||
result = await langflow_file_service.run_ingestion_flow(
|
||||
file_paths=file_paths or [],
|
||||
file_tuples=file_tuples,
|
||||
jwt_token=jwt_token,
|
||||
session_id=session_id,
|
||||
tweaks=tweaks,
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class LangflowConnectorService:
|
|||
|
||||
ingestion_result = await self.langflow_service.run_ingestion_flow(
|
||||
file_paths=[langflow_file_path],
|
||||
file_tuples=[file_tuple],
|
||||
jwt_token=jwt_token,
|
||||
tweaks=tweaks,
|
||||
owner=owner_user_id,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class LangflowFileService:
|
|||
async def run_ingestion_flow(
|
||||
self,
|
||||
file_paths: List[str],
|
||||
file_tuples: list[tuple[str, str, str]],
|
||||
jwt_token: str,
|
||||
session_id: Optional[str] = None,
|
||||
tweaks: Optional[Dict[str, Any]] = None,
|
||||
|
|
@ -67,7 +68,6 @@ class LangflowFileService:
|
|||
owner_name: Optional[str] = None,
|
||||
owner_email: Optional[str] = None,
|
||||
connector_type: Optional[str] = None,
|
||||
file_tuples: Optional[list[tuple[str, str, str]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Trigger the ingestion flow with provided file paths.
|
||||
|
|
@ -135,14 +135,19 @@ class LangflowFileService:
|
|||
# To compute the file size in bytes, use len() on the file content (which should be bytes)
|
||||
file_size_bytes = len(file_tuples[0][1]) if file_tuples and len(file_tuples[0]) > 1 else 0
|
||||
# Avoid logging full payload to prevent leaking sensitive data (e.g., JWT)
|
||||
|
||||
# Extract file metadata if file_tuples is provided
|
||||
filename = str(file_tuples[0][0]) if file_tuples and len(file_tuples) > 0 else ""
|
||||
mimetype = str(file_tuples[0][2]) if file_tuples and len(file_tuples) > 0 and len(file_tuples[0]) > 2 else ""
|
||||
|
||||
headers={
|
||||
"X-Langflow-Global-Var-JWT": str(jwt_token),
|
||||
"X-Langflow-Global-Var-OWNER": str(owner),
|
||||
"X-Langflow-Global-Var-OWNER_NAME": str(owner_name),
|
||||
"X-Langflow-Global-Var-OWNER_EMAIL": str(owner_email),
|
||||
"X-Langflow-Global-Var-CONNECTOR_TYPE": str(connector_type),
|
||||
"X-Langflow-Global-Var-FILENAME": str(file_tuples[0][0]),
|
||||
"X-Langflow-Global-Var-MIMETYPE": str(file_tuples[0][2]),
|
||||
"X-Langflow-Global-Var-FILENAME": filename,
|
||||
"X-Langflow-Global-Var-MIMETYPE": mimetype,
|
||||
"X-Langflow-Global-Var-FILESIZE": str(file_size_bytes),
|
||||
}
|
||||
logger.info(f"[LF] Headers {headers}")
|
||||
|
|
@ -271,14 +276,14 @@ class LangflowFileService:
|
|||
try:
|
||||
ingest_result = await self.run_ingestion_flow(
|
||||
file_paths=[file_path],
|
||||
file_tuples=[file_tuple],
|
||||
jwt_token=jwt_token,
|
||||
session_id=session_id,
|
||||
tweaks=final_tweaks,
|
||||
jwt_token=jwt_token,
|
||||
owner=owner,
|
||||
owner_name=owner_name,
|
||||
owner_email=owner_email,
|
||||
connector_type=connector_type,
|
||||
file_tuples=[file_tuple],
|
||||
)
|
||||
logger.debug("[LF] Ingestion completed successfully")
|
||||
except Exception as e:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue