lint
This commit is contained in:
parent
674fed412d
commit
60fa42f0a2
5 changed files with 10 additions and 291 deletions
|
|
@ -125,7 +125,6 @@ export function KnowledgeDropdown({ active, variant = 'navigation' }: KnowledgeD
|
|||
|
||||
if (response.status === 201) {
|
||||
const taskId = result.task_id || result.id
|
||||
const totalFiles = result.total_files || 0
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error("No task ID received from server")
|
||||
|
|
@ -166,7 +165,6 @@ export function KnowledgeDropdown({ active, variant = 'navigation' }: KnowledgeD
|
|||
|
||||
if (response.status === 201) {
|
||||
const taskId = result.task_id || result.id
|
||||
const totalFiles = result.total_files || 0
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error("No task ID received from server")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { Library, MessageSquare, Settings2, Plus, FileText } from "lucide-react"
|
|||
import { cn } from "@/lib/utils"
|
||||
import { useState, useEffect, useRef, useCallback } from "react"
|
||||
import { useChat } from "@/contexts/chat-context"
|
||||
import { KnowledgeDropdown } from "@/components/knowledge-dropdown"
|
||||
|
||||
import { EndpointType } from "@/contexts/chat-context"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Search, Loader2, FileText, HardDrive, Building2, Cloud, Plus } from "lucide-react"
|
||||
import { Search, Loader2, FileText, HardDrive, Building2, Cloud } from "lucide-react"
|
||||
import { TbBrandOnedrive } from "react-icons/tb"
|
||||
import { SiGoogledrive } from "react-icons/si"
|
||||
import { ProtectedRoute } from "@/components/protected-route"
|
||||
|
|
@ -14,7 +12,6 @@ import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"
|
|||
import { useTask } from "@/contexts/task-context"
|
||||
import { KnowledgeDropdown } from "@/components/knowledge-dropdown"
|
||||
|
||||
type FacetBucket = { key: string; count: number }
|
||||
|
||||
interface ChunkResult {
|
||||
filename: string
|
||||
|
|
@ -75,24 +72,16 @@ function getSourceIcon(connectorType?: string) {
|
|||
}
|
||||
|
||||
function SearchPage() {
|
||||
const router = useRouter()
|
||||
const { isMenuOpen } = useTask()
|
||||
const { parsedFilterData, isPanelOpen } = useKnowledgeFilter()
|
||||
const [query, setQuery] = useState("*")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [chunkResults, setChunkResults] = useState<ChunkResult[]>([])
|
||||
const [fileResults, setFileResults] = useState<FileResult[]>([])
|
||||
const [viewMode, setViewMode] = useState<'files' | 'chunks'>('files')
|
||||
const [selectedFile, setSelectedFile] = useState<string | null>(null)
|
||||
const [searchPerformed, setSearchPerformed] = useState(false)
|
||||
const prevFilterDataRef = useRef<string>("")
|
||||
|
||||
// Stats state for knowledge overview
|
||||
const [statsLoading, setStatsLoading] = useState<boolean>(false)
|
||||
const [totalDocs, setTotalDocs] = useState<number>(0)
|
||||
const [totalChunks, setTotalChunks] = useState<number>(0)
|
||||
const [facetStats, setFacetStats] = useState<{ data_sources: FacetBucket[]; document_types: FacetBucket[]; owners: FacetBucket[]; connector_types: FacetBucket[] } | null>(null)
|
||||
|
||||
const handleSearch = useCallback(async (e?: React.FormEvent) => {
|
||||
if (e) e.preventDefault()
|
||||
if (!query.trim()) return
|
||||
|
|
@ -116,7 +105,7 @@ function SearchPage() {
|
|||
|
||||
const searchPayload: SearchPayload = {
|
||||
query,
|
||||
limit: parsedFilterData?.limit || (query.trim() === "*" ? 50 : 10), // Higher limit for wildcard searches
|
||||
limit: parsedFilterData?.limit || (query.trim() === "*" ? 10000 : 10), // Maximum allowed limit for wildcard searches
|
||||
scoreThreshold: parsedFilterData?.scoreThreshold || 0
|
||||
}
|
||||
|
||||
|
|
@ -284,106 +273,12 @@ function SearchPage() {
|
|||
prevFilterDataRef.current = currentFilterString
|
||||
}, [parsedFilterData, searchPerformed, query, handleSearch])
|
||||
|
||||
// Fetch stats with current knowledge filter applied
|
||||
const fetchStats = useCallback(async () => {
|
||||
try {
|
||||
setStatsLoading(true)
|
||||
|
||||
// Build search payload with current filter data
|
||||
interface SearchPayload {
|
||||
query: string;
|
||||
limit: number;
|
||||
scoreThreshold: number;
|
||||
filters?: {
|
||||
data_sources?: string[];
|
||||
document_types?: string[];
|
||||
owners?: string[];
|
||||
connector_types?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const searchPayload: SearchPayload = {
|
||||
query: '*',
|
||||
limit: 50, // Get more results to ensure we have owner mapping data
|
||||
scoreThreshold: parsedFilterData?.scoreThreshold || 0
|
||||
}
|
||||
|
||||
// Add filters from global context if available and not wildcards
|
||||
if (parsedFilterData?.filters) {
|
||||
const filters = parsedFilterData.filters
|
||||
|
||||
// Only include filters if they're not wildcards (not "*")
|
||||
const hasSpecificFilters =
|
||||
!filters.data_sources.includes("*") ||
|
||||
!filters.document_types.includes("*") ||
|
||||
!filters.owners.includes("*") ||
|
||||
(filters.connector_types && !filters.connector_types.includes("*"))
|
||||
|
||||
if (hasSpecificFilters) {
|
||||
const processedFilters: SearchPayload['filters'] = {}
|
||||
|
||||
// Only add filter arrays that don't contain wildcards
|
||||
if (!filters.data_sources.includes("*")) {
|
||||
processedFilters.data_sources = filters.data_sources
|
||||
}
|
||||
if (!filters.document_types.includes("*")) {
|
||||
processedFilters.document_types = filters.document_types
|
||||
}
|
||||
if (!filters.owners.includes("*")) {
|
||||
processedFilters.owners = filters.owners
|
||||
}
|
||||
if (filters.connector_types && !filters.connector_types.includes("*")) {
|
||||
processedFilters.connector_types = filters.connector_types
|
||||
}
|
||||
|
||||
// Only add filters object if it has any actual filters
|
||||
if (Object.keys(processedFilters).length > 0) {
|
||||
searchPayload.filters = processedFilters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch('/api/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(searchPayload)
|
||||
})
|
||||
const result = await response.json()
|
||||
if (response.ok) {
|
||||
const aggs = result.aggregations || {}
|
||||
const toBuckets = (agg: { buckets?: Array<{ key: string | number; doc_count: number }> }): FacetBucket[] =>
|
||||
(agg?.buckets || []).map(b => ({ key: String(b.key), count: b.doc_count }))
|
||||
|
||||
// Now we can aggregate directly on owner names since they're keyword fields
|
||||
|
||||
const dataSourceBuckets = toBuckets(aggs.data_sources)
|
||||
setFacetStats({
|
||||
data_sources: dataSourceBuckets.slice(0, 10),
|
||||
document_types: toBuckets(aggs.document_types).slice(0, 10),
|
||||
owners: toBuckets(aggs.owners).slice(0, 10),
|
||||
connector_types: toBuckets(aggs.connector_types || {}).slice(0, 10)
|
||||
})
|
||||
setTotalDocs(dataSourceBuckets.length)
|
||||
setTotalChunks(Number(result.total || 0))
|
||||
}
|
||||
} catch {
|
||||
// non-fatal – keep page functional without stats
|
||||
} finally {
|
||||
setStatsLoading(false)
|
||||
}
|
||||
}, [parsedFilterData])
|
||||
|
||||
// Auto-search on mount with "*"
|
||||
useEffect(() => {
|
||||
if (query === "*") {
|
||||
handleSearch()
|
||||
}
|
||||
}, []) // Only run once on mount
|
||||
|
||||
// Initial stats fetch and refresh when filter changes
|
||||
useEffect(() => {
|
||||
fetchStats()
|
||||
}, [fetchStats])
|
||||
// Only trigger initial search on mount when query is "*"
|
||||
handleSearch()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []) // Only run once on mount - ignore handleSearch dependency
|
||||
|
||||
|
||||
|
||||
|
|
@ -447,8 +342,7 @@ function SearchPage() {
|
|||
|
||||
{/* Results Display */}
|
||||
<div className="space-y-4">
|
||||
{viewMode === 'files' ? (
|
||||
selectedFile ? (
|
||||
{selectedFile ? (
|
||||
// Show chunks for selected file
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
|
|
@ -536,29 +430,7 @@ function SearchPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
// Show chunks view
|
||||
chunkResults.map((result, index) => (
|
||||
<div key={index} className="bg-muted/20 rounded-lg p-4 border border-border/50">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4 text-blue-400" />
|
||||
<span className="font-medium truncate">{result.filename}</span>
|
||||
</div>
|
||||
<span className="text-xs text-green-400 bg-green-400/20 px-2 py-1 rounded">
|
||||
{result.score.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
{result.mimetype} • Page {result.page}
|
||||
</div>
|
||||
<p className="text-sm text-foreground/90 leading-relaxed">
|
||||
{result.text}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||
import { Badge } from "@/components/ui/badge"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Upload, FolderOpen, Loader2, PlugZap, RefreshCw, Download, Cloud } from "lucide-react"
|
||||
import { Loader2, PlugZap, RefreshCw } from "lucide-react"
|
||||
import { ProtectedRoute } from "@/components/protected-route"
|
||||
import { useTask } from "@/contexts/task-context"
|
||||
import { useAuth } from "@/contexts/auth-context"
|
||||
import { FileUploadArea } from "@/components/file-upload-area"
|
||||
|
||||
|
||||
interface Connector {
|
||||
|
|
@ -45,14 +44,6 @@ function KnowledgeSourcesPage() {
|
|||
const { addTask, tasks } = useTask()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
// File upload state
|
||||
const [fileUploadLoading, setFileUploadLoading] = useState(false)
|
||||
const [pathUploadLoading, setPathUploadLoading] = useState(false)
|
||||
const [folderPath, setFolderPath] = useState("/app/documents/")
|
||||
const [bucketUploadLoading, setBucketUploadLoading] = useState(false)
|
||||
const [bucketUrl, setBucketUrl] = useState("s3://")
|
||||
const [uploadStatus, setUploadStatus] = useState<string>("")
|
||||
const [awsEnabled, setAwsEnabled] = useState(false)
|
||||
|
||||
// Connectors state
|
||||
const [connectors, setConnectors] = useState<Connector[]>([])
|
||||
|
|
@ -62,123 +53,6 @@ function KnowledgeSourcesPage() {
|
|||
const [maxFiles, setMaxFiles] = useState<number>(10)
|
||||
|
||||
|
||||
// File upload handlers
|
||||
const handleDirectFileUpload = async (file: File) => {
|
||||
setFileUploadLoading(true)
|
||||
setUploadStatus("")
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append("file", file)
|
||||
|
||||
const response = await fetch("/api/upload", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
setUploadStatus(`File processed successfully! ID: ${result.id}`)
|
||||
|
||||
} else {
|
||||
setUploadStatus(`Error: ${result.error || "Processing failed"}`)
|
||||
}
|
||||
} catch (error) {
|
||||
setUploadStatus(`Error: ${error instanceof Error ? error.message : "Processing failed"}`)
|
||||
} finally {
|
||||
setFileUploadLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePathUpload = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!folderPath.trim()) return
|
||||
|
||||
setPathUploadLoading(true)
|
||||
setUploadStatus("")
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/upload_path", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ path: folderPath }),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (response.status === 201) {
|
||||
const taskId = result.task_id || result.id
|
||||
const totalFiles = result.total_files || 0
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error("No task ID received from server")
|
||||
}
|
||||
|
||||
addTask(taskId)
|
||||
|
||||
setUploadStatus(`🔄 Processing started for ${totalFiles} files. Check the task notification panel for real-time progress. (Task ID: ${taskId})`)
|
||||
setFolderPath("")
|
||||
setPathUploadLoading(false)
|
||||
|
||||
} else if (response.ok) {
|
||||
const successful = result.results?.filter((r: {status: string}) => r.status === "indexed").length || 0
|
||||
const total = result.results?.length || 0
|
||||
setUploadStatus(`Path processed successfully! ${successful}/${total} files indexed.`)
|
||||
setFolderPath("")
|
||||
setPathUploadLoading(false)
|
||||
} else {
|
||||
setUploadStatus(`Error: ${result.error || "Path upload failed"}`)
|
||||
setPathUploadLoading(false)
|
||||
}
|
||||
} catch (error) {
|
||||
setUploadStatus(`Error: ${error instanceof Error ? error.message : "Path upload failed"}`)
|
||||
setPathUploadLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBucketUpload = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!bucketUrl.trim()) return
|
||||
|
||||
setBucketUploadLoading(true)
|
||||
setUploadStatus("")
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/upload_bucket", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ s3_url: bucketUrl }),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (response.status === 201) {
|
||||
const taskId = result.task_id || result.id
|
||||
const totalFiles = result.total_files || 0
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error("No task ID received from server")
|
||||
}
|
||||
|
||||
addTask(taskId)
|
||||
setUploadStatus(`🔄 Processing started for ${totalFiles} files. Check the task notification panel for real-time progress. (Task ID: ${taskId})`)
|
||||
setBucketUrl("s3://")
|
||||
} else {
|
||||
setUploadStatus(`Error: ${result.error || "Bucket processing failed"}`)
|
||||
}
|
||||
} catch (error) {
|
||||
setUploadStatus(
|
||||
`Error: ${error instanceof Error ? error.message : "Bucket processing failed"}`,
|
||||
)
|
||||
} finally {
|
||||
setBucketUploadLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get connector icon
|
||||
const getConnectorIcon = (iconName: string) => {
|
||||
|
|
@ -379,21 +253,6 @@ function KnowledgeSourcesPage() {
|
|||
}, [searchParams, isAuthenticated, checkConnectorStatuses])
|
||||
|
||||
|
||||
// Check AWS availability
|
||||
useEffect(() => {
|
||||
const checkAws = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/upload_options")
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setAwsEnabled(Boolean(data.aws))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check AWS availability", err)
|
||||
}
|
||||
}
|
||||
checkAws()
|
||||
}, [])
|
||||
|
||||
|
||||
// Track previous tasks to detect new completions
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ export function TaskNotificationMenu() {
|
|||
const processed = task.processed_files || 0
|
||||
const successful = task.successful_files || 0
|
||||
const failed = task.failed_files || 0
|
||||
const skipped = Math.max(0, processed - successful - failed) // Calculate skipped
|
||||
|
||||
if (total > 0) {
|
||||
return {
|
||||
|
|
@ -70,7 +69,6 @@ export function TaskNotificationMenu() {
|
|||
processed,
|
||||
successful,
|
||||
failed,
|
||||
skipped,
|
||||
remaining: total - processed
|
||||
}
|
||||
}
|
||||
|
|
@ -180,12 +178,6 @@ export function TaskNotificationMenu() {
|
|||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-yellow-500 rounded-full"></div>
|
||||
<span className="text-yellow-600">
|
||||
{formatTaskProgress(task)?.detailed.skipped} skipped
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
|
||||
<span className="text-muted-foreground">
|
||||
{formatTaskProgress(task)?.detailed.remaining} pending
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -269,8 +261,7 @@ export function TaskNotificationMenu() {
|
|||
{task.status === 'completed' && formatTaskProgress(task)?.detailed && (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{formatTaskProgress(task)?.detailed.successful} success, {' '}
|
||||
{formatTaskProgress(task)?.detailed.failed} failed, {' '}
|
||||
{formatTaskProgress(task)?.detailed.skipped} skipped
|
||||
{formatTaskProgress(task)?.detailed.failed} failed
|
||||
</div>
|
||||
)}
|
||||
{task.status === 'failed' && task.error && (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue