rename pages, improve knowledge and chat
This commit is contained in:
parent
46077709bf
commit
f2b407b4c8
4 changed files with 64 additions and 221 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { Library, Database, MessageSquare, Settings2 } from "lucide-react"
|
||||
import { Library, MessageSquare, Settings2 } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function Navigation() {
|
||||
|
|
@ -18,14 +18,14 @@ export function Navigation() {
|
|||
{
|
||||
label: "Knowledge",
|
||||
icon: Library,
|
||||
href: "/search",
|
||||
active: pathname === "/search",
|
||||
href: "/knowledge",
|
||||
active: pathname === "/knowledge",
|
||||
},
|
||||
{
|
||||
label: "Settings",
|
||||
icon: Settings2,
|
||||
href: "/knowledge-sources",
|
||||
active: pathname === "/knowledge-sources",
|
||||
href: "/settings",
|
||||
active: pathname === "/settings",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ export function Navigation() {
|
|||
<div className="space-y-4 py-4 flex flex-col h-full bg-background">
|
||||
<div className="px-3 py-2 flex-1">
|
||||
<div className="space-y-1">
|
||||
{routes.map((route, index) => (
|
||||
{routes.map((route) => (
|
||||
<div key={route.href}>
|
||||
<Link
|
||||
href={route.href}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { MessageCircle, Send, Loader2, User, Bot, Zap, Settings, ChevronDown, ChevronRight, Upload, AtSign, Plus } from "lucide-react"
|
||||
import { Loader2, User, Bot, Zap, Settings, ChevronDown, ChevronRight, Upload, AtSign, Plus } from "lucide-react"
|
||||
import { ProtectedRoute } from "@/components/protected-route"
|
||||
import { useTask } from "@/contexts/task-context"
|
||||
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"
|
||||
|
|
@ -85,7 +83,7 @@ function ChatPage() {
|
|||
const [isDragOver, setIsDragOver] = useState(false)
|
||||
const dragCounterRef = useRef(0)
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||
const { addTask } = useTask()
|
||||
const { selectedFilter, parsedFilterData } = useKnowledgeFilter()
|
||||
|
||||
|
|
@ -1125,44 +1123,29 @@ function ChatPage() {
|
|||
) : (
|
||||
<>
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className="space-y-2">
|
||||
<div key={index} className="space-y-6">
|
||||
{message.role === "user" && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="w-8 h-8">
|
||||
<AvatarImage src={user?.picture} alt={user?.name} />
|
||||
<AvatarFallback className="text-sm bg-primary/20 text-primary">
|
||||
{user?.name ? user.name.charAt(0).toUpperCase() : <User className="h-4 w-4" />}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="font-medium text-foreground">{user?.name || "User"}</span>
|
||||
</div>
|
||||
<div className="pl-10 max-w-full">
|
||||
<div className="flex gap-3">
|
||||
<Avatar className="w-8 h-8 flex-shrink-0">
|
||||
<AvatarImage src={user?.picture} alt={user?.name} />
|
||||
<AvatarFallback className="text-sm bg-primary/20 text-primary">
|
||||
{user?.name ? user.name.charAt(0).toUpperCase() : <User className="h-4 w-4" />}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">{message.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message.role === "assistant" && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center">
|
||||
<Bot className="h-4 w-4 text-accent-foreground" />
|
||||
</div>
|
||||
<span className="font-medium text-foreground">AI</span>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0">
|
||||
<Bot className="h-4 w-4 text-accent-foreground" />
|
||||
</div>
|
||||
<div className="pl-10 max-w-full">
|
||||
<div className="rounded-lg bg-card border border-border/40 p-4 max-w-full overflow-hidden">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-2 h-2 bg-green-400 rounded-full"></div>
|
||||
<span className="text-sm text-green-400 font-medium">Finished</span>
|
||||
<span className="text-xs text-muted-foreground ml-auto">
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
{renderFunctionCalls(message.functionCalls || [], index)}
|
||||
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">{message.content}</p>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{renderFunctionCalls(message.functionCalls || [], index)}
|
||||
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">{message.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1171,46 +1154,30 @@ function ChatPage() {
|
|||
|
||||
{/* Streaming Message Display */}
|
||||
{streamingMessage && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center">
|
||||
<Bot className="h-4 w-4 text-accent-foreground" />
|
||||
</div>
|
||||
<span className="font-medium text-foreground">AI</span>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0">
|
||||
<Bot className="h-4 w-4 text-accent-foreground" />
|
||||
</div>
|
||||
<div className="pl-10 max-w-full">
|
||||
<div className="rounded-lg bg-card border border-border/40 p-4 max-w-full overflow-hidden">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Loader2 className="w-4 h-4 animate-spin text-blue-400" />
|
||||
<span className="text-sm text-blue-400 font-medium">Streaming...</span>
|
||||
<span className="text-xs text-muted-foreground ml-auto">
|
||||
{streamingMessage.timestamp.toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
{renderFunctionCalls(streamingMessage.functionCalls, messages.length)}
|
||||
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">
|
||||
{streamingMessage.content}
|
||||
<span className="inline-block w-2 h-4 bg-blue-400 ml-1 animate-pulse"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{renderFunctionCalls(streamingMessage.functionCalls, messages.length)}
|
||||
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">
|
||||
{streamingMessage.content}
|
||||
<span className="inline-block w-2 h-4 bg-blue-400 ml-1 animate-pulse"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && !asyncMode && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center">
|
||||
<Bot className="h-4 w-4 text-accent-foreground" />
|
||||
</div>
|
||||
<span className="font-medium text-foreground">AI</span>
|
||||
{/* Loading animation - shows immediately after user submits */}
|
||||
{loading && (
|
||||
<div className="flex gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0">
|
||||
<Bot className="h-4 w-4 text-accent-foreground" />
|
||||
</div>
|
||||
<div className="pl-10 max-w-full">
|
||||
<div className="rounded-lg bg-card border border-border/40 p-4 max-w-full overflow-hidden">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Loader2 className="w-4 h-4 animate-spin text-white" />
|
||||
<span className="text-sm text-white font-medium">Thinking...</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">Thinking...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1266,7 +1233,7 @@ function ChatPage() {
|
|||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
if (input.trim() && !loading) {
|
||||
handleSubmit(e as any)
|
||||
handleSubmit(e as React.FormEvent<HTMLFormElement>)
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@
|
|||
import { useState, useEffect, useCallback, useRef } from "react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Search, Loader2, FileText, Zap, RefreshCw } from "lucide-react"
|
||||
import { Search, Loader2, FileText } from "lucide-react"
|
||||
import { ProtectedRoute } from "@/components/protected-route"
|
||||
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"
|
||||
|
||||
|
|
@ -30,7 +28,7 @@ interface SearchResponse {
|
|||
|
||||
function SearchPage() {
|
||||
|
||||
const { selectedFilter, parsedFilterData } = useKnowledgeFilter()
|
||||
const { parsedFilterData } = useKnowledgeFilter()
|
||||
const [query, setQuery] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [results, setResults] = useState<SearchResult[]>([])
|
||||
|
|
@ -161,12 +159,23 @@ function SearchPage() {
|
|||
}, [parsedFilterData, searchPerformed, query, handleSearch])
|
||||
|
||||
// Fetch stats with current knowledge filter applied
|
||||
const fetchStats = async () => {
|
||||
const fetchStats = useCallback(async () => {
|
||||
try {
|
||||
setStatsLoading(true)
|
||||
|
||||
// Build search payload with current filter data
|
||||
const searchPayload: any = {
|
||||
interface SearchPayload {
|
||||
query: string;
|
||||
limit: number;
|
||||
scoreThreshold: number;
|
||||
filters?: {
|
||||
data_sources?: string[];
|
||||
document_types?: string[];
|
||||
owners?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const searchPayload: SearchPayload = {
|
||||
query: '*',
|
||||
limit: 0,
|
||||
scoreThreshold: parsedFilterData?.scoreThreshold || 0
|
||||
|
|
@ -183,7 +192,7 @@ function SearchPage() {
|
|||
!filters.owners.includes("*")
|
||||
|
||||
if (hasSpecificFilters) {
|
||||
const processedFilters: any = {}
|
||||
const processedFilters: SearchPayload['filters'] = {}
|
||||
|
||||
// Only add filter arrays that don't contain wildcards
|
||||
if (!filters.data_sources.includes("*")) {
|
||||
|
|
@ -227,12 +236,12 @@ function SearchPage() {
|
|||
} finally {
|
||||
setStatsLoading(false)
|
||||
}
|
||||
}
|
||||
}, [parsedFilterData])
|
||||
|
||||
// Initial stats fetch and refresh when filter changes
|
||||
useEffect(() => {
|
||||
fetchStats()
|
||||
}, [parsedFilterData])
|
||||
}, [fetchStats])
|
||||
|
||||
|
||||
|
||||
|
|
@ -62,11 +62,6 @@ function KnowledgeSourcesPage() {
|
|||
const [syncResults, setSyncResults] = useState<{[key: string]: SyncResult | null}>({})
|
||||
const [maxFiles, setMaxFiles] = useState<number>(10)
|
||||
|
||||
// Stats state (from wildcard search aggregations)
|
||||
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[] } | null>(null)
|
||||
|
||||
// File upload handlers
|
||||
const handleDirectFileUpload = async (file: File) => {
|
||||
|
|
@ -87,8 +82,6 @@ function KnowledgeSourcesPage() {
|
|||
if (response.ok) {
|
||||
setUploadStatus(`File processed successfully! ID: ${result.id}`)
|
||||
|
||||
// Refresh stats after successful file upload
|
||||
fetchStats()
|
||||
} else {
|
||||
setUploadStatus(`Error: ${result.error || "Processing failed"}`)
|
||||
}
|
||||
|
|
@ -176,9 +169,6 @@ function KnowledgeSourcesPage() {
|
|||
addTask(taskId)
|
||||
setUploadStatus(`🔄 Processing started for ${totalFiles} files. Check the task notification panel for real-time progress. (Task ID: ${taskId})`)
|
||||
setBucketUrl("s3://")
|
||||
|
||||
// Refresh stats after successful bucket upload
|
||||
fetchStats()
|
||||
} else {
|
||||
setUploadStatus(`Error: ${result.error || "Bucket processing failed"}`)
|
||||
}
|
||||
|
|
@ -389,37 +379,6 @@ function KnowledgeSourcesPage() {
|
|||
}
|
||||
}, [searchParams, isAuthenticated, checkConnectorStatuses])
|
||||
|
||||
// Fetch global stats using match-all wildcard
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
setStatsLoading(true)
|
||||
const response = await fetch('/api/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query: '*', limit: 0 })
|
||||
})
|
||||
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 }))
|
||||
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)
|
||||
})
|
||||
// Frontend-only doc count: number of distinct filenames (data_sources buckets)
|
||||
setTotalDocs(dataSourceBuckets.length)
|
||||
// Chunk count from hits.total (match_all over chunks)
|
||||
setTotalChunks(Number(result.total || 0))
|
||||
}
|
||||
} catch {
|
||||
// non-fatal – keep page functional without stats
|
||||
} finally {
|
||||
setStatsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Check AWS availability
|
||||
useEffect(() => {
|
||||
|
|
@ -437,10 +396,6 @@ function KnowledgeSourcesPage() {
|
|||
checkAws()
|
||||
}, [])
|
||||
|
||||
// Initial stats fetch
|
||||
useEffect(() => {
|
||||
fetchStats()
|
||||
}, [])
|
||||
|
||||
// Track previous tasks to detect new completions
|
||||
const [prevTasks, setPrevTasks] = useState<typeof tasks>([])
|
||||
|
|
@ -454,9 +409,9 @@ function KnowledgeSourcesPage() {
|
|||
})
|
||||
|
||||
if (newlyCompletedTasks.length > 0) {
|
||||
// Refresh stats when any task newly completes
|
||||
// Task completed - could refresh data here if needed
|
||||
const timeoutId = setTimeout(() => {
|
||||
fetchStats()
|
||||
// Stats refresh removed
|
||||
}, 1000)
|
||||
|
||||
// Update previous tasks state
|
||||
|
|
@ -471,95 +426,10 @@ function KnowledgeSourcesPage() {
|
|||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Hero Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="mb-4">
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Knowledge Sources
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
Add documents to your knowledge base
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground max-w-2xl">
|
||||
Import files and folders directly, or connect external services like Google Drive to automatically sync and index your documents.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Knowledge Overview Stats */}
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">Knowledge Overview</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={fetchStats}
|
||||
disabled={statsLoading}
|
||||
className="ml-auto"
|
||||
>
|
||||
{statsLoading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</CardTitle>
|
||||
<CardDescription>Snapshot of indexed content</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Documents row */}
|
||||
<div className="grid gap-6 md:grid-cols-1">
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground mb-1">Total documents</div>
|
||||
<div className="text-2xl font-semibold">{statsLoading ? '—' : totalDocs}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="border-t border-border/50 my-6" />
|
||||
|
||||
{/* Chunks row */}
|
||||
<div className="grid gap-6 md:grid-cols-4">
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground mb-1">Total chunks</div>
|
||||
<div className="text-2xl font-semibold">{statsLoading ? '—' : totalChunks}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground mb-2">Top types</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(facetStats?.document_types || []).slice(0,5).map((b) => (
|
||||
<Badge key={`type-${b.key}`} variant="secondary">{b.key} · {b.count}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground mb-2">Top owners</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(facetStats?.owners || []).slice(0,5).map((b) => (
|
||||
<Badge key={`owner-${b.key}`} variant="secondary">{b.key || 'unknown'} · {b.count}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground mb-2">Top files</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(facetStats?.data_sources || []).slice(0,5).map((b) => (
|
||||
<Badge key={`file-${b.key}`} variant="secondary" title={b.key}>{b.key} · {b.count}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Upload Section */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight mb-2">Direct Import</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Add individual files or process entire folders from your local system
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold tracking-tight mb-2">Import</h2>
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-6 ${awsEnabled ? 'md:grid-cols-3' : 'md:grid-cols-2'}`}>
|
||||
|
|
@ -686,10 +556,7 @@ function KnowledgeSourcesPage() {
|
|||
{/* Connectors Section */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight mb-2">Connectors</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Connect external services to automatically sync and index your documents
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold tracking-tight mb-2">Cloud Connectors</h2>
|
||||
</div>
|
||||
|
||||
{/* Sync Settings */}
|
||||
Loading…
Add table
Reference in a new issue