Spaces:
Sleeping
Sleeping
Download src/components/DocumentLibraryView.tsx from vooom/NewsAnalyze: direct link, hf CLI and curl.
- Browser
- Download file 6.95 kB
-
https://huggingface.co/spaces/vooom/NewsAnalyze/resolve/main/src/components/DocumentLibraryView.tsx
- Command line
-
hf download hf://spaces/vooom/NewsAnalyze/src/components/DocumentLibraryView.tsx
-
curl -L -o DocumentLibraryView.tsx https://huggingface.co/spaces/vooom/NewsAnalyze/resolve/main/src/components/DocumentLibraryView.tsx
6.95 kB
| import React, { useState, useEffect } from "react"; | |
| import { DocumentChunk } from "../contracts/tool_outputs.js"; | |
| import { FileText, Search, ExternalLink, RefreshCw } from "lucide-react"; | |
| import { useLanguage } from "../context/LanguageContext.js"; | |
| import { getBackendUrl } from "../utils/api.js"; | |
| const COUNTRIES = ["SAU", "ARE", "EGY", "USA", "DEU", "JPN", "BRA", "CHN"]; | |
| export const DocumentLibraryView: React.FC = () => { | |
| const { language, t } = useLanguage(); | |
| const isAr = language === "ar"; | |
| const [query, setQuery] = useState(isAr ? "الميزانية العامة والدين السيادي" : "sovereign debt budget"); | |
| const [debouncedQuery, setDebouncedQuery] = useState(query); | |
| const [selectedCountry, setSelectedCountry] = useState<string>("SAU"); | |
| const [alpha, setAlpha] = useState(0.6); | |
| const [documents, setDocuments] = useState<DocumentChunk[]>([]); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(""); | |
| useEffect(() => { | |
| const id = setTimeout(() => setDebouncedQuery(query), 500); | |
| return () => clearTimeout(id); | |
| }, [query]); | |
| useEffect(() => { | |
| if (!debouncedQuery.trim()) return; | |
| const controller = new AbortController(); | |
| setLoading(true); | |
| setError(""); | |
| const params = new URLSearchParams({ query: debouncedQuery, alpha: String(alpha), country: selectedCountry }); | |
| fetch(`${getBackendUrl()}/api/rag/documents?${params}`, { signal: controller.signal }) | |
| .then(async (res) => { | |
| const json = await res.json().catch(() => ({})); | |
| if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`); | |
| setDocuments(Array.isArray(json) ? json : []); | |
| }) | |
| .catch((err: Error) => { | |
| if (err.name !== "AbortError") setError(err.message); | |
| }) | |
| .finally(() => setLoading(false)); | |
| return () => controller.abort(); | |
| }, [debouncedQuery, selectedCountry, alpha]); | |
| return ( | |
| <div className="space-y-6 text-slate-100"> | |
| <div className="bg-slate-900 border border-slate-800 rounded-xl p-6 shadow-xl"> | |
| <div className="flex items-center gap-3 mb-4 border-b border-slate-800 pb-3"> | |
| <div className="w-8 h-8 rounded-lg bg-purple-500/20 text-purple-400 flex items-center justify-center shrink-0"> | |
| <FileText className="w-5 h-5" /> | |
| </div> | |
| <div> | |
| <h1 className="text-lg font-bold text-white font-display">{t.documents.title}</h1> | |
| <p className="text-xs text-slate-400">{t.documents.subtitle}</p> | |
| </div> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-12 gap-4 mb-6 p-4 bg-slate-950/80 rounded-xl border border-slate-800"> | |
| <div className="md:col-span-6"> | |
| <label className="text-[10px] font-mono text-slate-400 block mb-1">{isAr ? "عبارة البحث" : "Search query"}</label> | |
| <div className="relative"> | |
| <Search className="w-4 h-4 text-slate-500 absolute start-3 top-2.5" /> | |
| <input | |
| type="text" | |
| value={query} | |
| maxLength={1000} | |
| onChange={(e) => setQuery(e.target.value)} | |
| placeholder={t.documents.searchPlaceholder} | |
| className="w-full bg-slate-900 border border-slate-800 rounded-lg ps-9 pe-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-purple-500" | |
| /> | |
| </div> | |
| </div> | |
| <div className="md:col-span-3"> | |
| <label className="text-[10px] font-mono text-slate-400 block mb-1">{isAr ? "الدولة" : "Country"}</label> | |
| <select | |
| value={selectedCountry} | |
| onChange={(e) => setSelectedCountry(e.target.value)} | |
| className="w-full bg-slate-900 border border-slate-800 rounded-lg px-3 py-1.5 text-xs font-mono text-slate-200" | |
| > | |
| {COUNTRIES.map((c) => ( | |
| <option key={c} value={c}>{c}</option> | |
| ))} | |
| </select> | |
| </div> | |
| <div className="md:col-span-3 flex flex-col justify-center"> | |
| <div className="flex items-center justify-between text-[10px] font-mono text-slate-400 mb-1"> | |
| <span>{isAr ? "وزن العنوان مقابل النص" : "Title vs body weight"}</span> | |
| <span className="text-purple-300 font-bold">{alpha.toFixed(1)}</span> | |
| </div> | |
| <input | |
| type="range" | |
| min={0} | |
| max={1} | |
| step={0.1} | |
| value={alpha} | |
| onChange={(e) => setAlpha(parseFloat(e.target.value))} | |
| className="w-full accent-purple-400 cursor-pointer" | |
| /> | |
| </div> | |
| </div> | |
| {loading && ( | |
| <div className="flex items-center gap-2 text-xs font-mono text-slate-400 mb-3"> | |
| <RefreshCw className="w-3.5 h-3.5 animate-spin" /> {isAr ? "جارٍ البحث..." : "Searching..."} | |
| </div> | |
| )} | |
| {error && <div className="text-xs text-rose-400 mb-3">{error}</div>} | |
| {!loading && !error && documents.length === 0 && ( | |
| <div className="p-8 text-center text-xs text-slate-500 bg-slate-950/60 rounded-xl border border-slate-800">{t.documents.noDocsFound}</div> | |
| )} | |
| <div className="space-y-3"> | |
| {documents.map((doc) => ( | |
| <a | |
| key={doc.chunk_id} | |
| href={doc.url || undefined} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="block p-4 bg-slate-950/70 hover:bg-slate-800/60 border border-slate-800 hover:border-purple-500/50 rounded-xl transition-all group" | |
| > | |
| <div className="flex flex-wrap items-center justify-between gap-2 mb-2"> | |
| <div className="flex flex-wrap items-center gap-2"> | |
| <span className="px-2 py-0.5 bg-purple-950 text-purple-300 border border-purple-800/80 text-[10px] font-mono font-bold rounded"> | |
| [{doc.chunk_id}] | |
| </span> | |
| <span className="text-xs text-slate-400">{doc.source_authority}</span> | |
| <span className="text-[11px] font-mono text-slate-500"> | |
| {new Date(doc.publication_timestamp).toLocaleDateString(isAr ? "ar-SA" : "en-US")} | |
| </span> | |
| </div> | |
| <div className="text-xs font-mono text-purple-400 flex items-center gap-1"> | |
| <span>{isAr ? "الصلة" : "Relevance"}: {doc.score ?? 0}</span> | |
| {doc.url && <ExternalLink className="w-3 h-3" />} | |
| </div> | |
| </div> | |
| <h3 className="text-sm font-bold text-slate-200 group-hover:text-purple-300 transition-colors mb-1">{doc.title}</h3> | |
| <p className="text-xs text-slate-400 line-clamp-2 leading-relaxed">{doc.content}</p> | |
| </a> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |