11import { useEffect , useState } from "react" ;
22import { useParams } from "react-router-dom" ;
33import ForwardedIconComponent from "@/components/common/genericIconComponent" ;
4+ import { Badge } from "@/components/ui/badge" ;
45import { Button } from "@/components/ui/button" ;
56import { Input } from "@/components/ui/input" ;
67import Loading from "@/components/ui/loading" ;
@@ -21,51 +22,80 @@ interface ChunkCardProps {
2122
2223const ChunkCard = ( { chunk, index, onCopy } : ChunkCardProps ) => {
2324 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
25+ const [ isCopied , setIsCopied ] = useState ( false ) ;
2426 const shouldTruncate = chunk . content . length > TRUNCATE_LENGTH ;
2527 const displayContent =
2628 shouldTruncate && ! isExpanded
2729 ? chunk . content . slice ( 0 , TRUNCATE_LENGTH ) + "..."
2830 : chunk . content ;
2931
32+ const handleCopy = ( e : React . MouseEvent ) => {
33+ e . stopPropagation ( ) ;
34+ onCopy ( chunk . content ) ;
35+ setIsCopied ( true ) ;
36+ setTimeout ( ( ) => setIsCopied ( false ) , 2000 ) ;
37+ } ;
38+
3039 return (
3140 < div
3241 className = { cn (
33- "cursor-pointer rounded-lg border border-border bg-background p-4 transition-all duration-200" ,
34- isExpanded && "ring-1 ring-ring" ,
42+ "cursor-pointer rounded-lg border border-muted bg-muted p-3 transition-all duration-200" ,
3543 ) }
3644 onClick = { ( ) => shouldTruncate && setIsExpanded ( ! isExpanded ) }
3745 >
38- < div className = "mb-3 flex items-center justify-between" >
46+ < div className = "mb-2 flex items-center justify-between" >
3947 < div className = "flex items-center gap-3" >
40- < span className = "font-medium" > Chunk { index } </ span >
41- < span className = "text-sm text-muted-foreground" >
48+ < span className = "text-sm font-medium" > Chunk { index } </ span >
49+ < Badge
50+ variant = "secondary"
51+ size = "sq"
52+ className = "text-xs text-muted-foreground"
53+ >
4254 { chunk . char_count } chars
43- </ span >
55+ </ Badge >
4456 < Button
4557 variant = "ghost"
4658 size = "icon"
47- className = "h-6 w-6"
48- onClick = { ( e ) => {
49- e . stopPropagation ( ) ;
50- onCopy ( chunk . content ) ;
51- } }
59+ className = { cn (
60+ "group h-6 w-6 transition-colors" ,
61+ isCopied && "text-accent-emerald-foreground" ,
62+ ) }
63+ onClick = { handleCopy }
5264 >
5365 < ForwardedIconComponent
54- name = "Copy"
55- className = "h-3.5 w-3.5 text-muted-foreground"
66+ name = { isCopied ? "Check" : "Copy" }
67+ className = { cn (
68+ "h-3.5 w-3.5 transition-colors" ,
69+ isCopied
70+ ? "text-accent-emerald-foreground"
71+ : "text-muted-foreground group-hover:text-foreground" ,
72+ ) }
5673 />
5774 </ Button >
5875 </ div >
59- { shouldTruncate && (
60- < ForwardedIconComponent
61- name = { isExpanded ? "ChevronUp" : "ChevronDown" }
62- className = "h-4 w-4 text-muted-foreground transition-transform duration-200"
63- />
64- ) }
76+ < div className = "flex items-center gap-3" >
77+ { /* TODO: Add score when semantic search is implemented
78+ <Badge
79+ variant="secondary"
80+ size="sq"
81+ className="text-xs text-muted-foreground"
82+ >
83+ {chunk?.score ?? "N/A"} score
84+ </Badge>
85+ */ }
86+ < div className = "w-4" >
87+ { shouldTruncate && (
88+ < ForwardedIconComponent
89+ name = { isExpanded ? "ChevronUp" : "ChevronDown" }
90+ className = "h-4 w-4 text-muted-foreground transition-transform duration-200"
91+ />
92+ ) }
93+ </ div >
94+ </ div >
6595 </ div >
6696 < p
6797 className = { cn (
68- "text-sm leading-relaxed text-muted-foreground transition-all duration-200" ,
98+ "text-sm leading-relaxed text-muted-foreground transition-all duration-200 whitespace-pre-wrap break-words " ,
6999 ! isExpanded && shouldTruncate && "line-clamp-4" ,
70100 ) }
71101 >
@@ -95,9 +125,14 @@ export const SourceChunksPage = () => {
95125 navigator . clipboard . writeText ( content ) ;
96126 } ;
97127
98- const filteredChunks = ( chunks || [ ] ) . filter ( ( chunk ) =>
99- chunk . content . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) ,
100- ) ;
128+ const filteredChunks = ( chunks || [ ] )
129+ . map ( ( chunk , originalIndex ) => ( {
130+ ...chunk ,
131+ originalIndex : originalIndex + 1 ,
132+ } ) )
133+ . filter ( ( chunk ) =>
134+ chunk . content . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) ,
135+ ) ;
101136
102137 const totalPages = Math . ceil ( filteredChunks . length / CHUNKS_PER_PAGE ) ;
103138 const startIndex = ( currentPage - 1 ) * CHUNKS_PER_PAGE ;
@@ -112,12 +147,12 @@ export const SourceChunksPage = () => {
112147
113148 return (
114149 < div className = "flex h-full w-full" data-testid = "source-chunks-wrapper" >
115- < div className = "flex h-full w-full flex-col overflow-y-auto " >
116- < div className = "flex h-full w-full flex-col xl:container" >
117- < div className = "flex flex-1 flex-col justify-start px-5 pt-10" >
118- < div className = "flex h-full flex-col justify-start " >
150+ < div className = "flex h-full w-full flex-col overflow-hidden " >
151+ < div className = "flex h-full w-full flex-col overflow-hidden xl:container" >
152+ < div className = "flex h-full flex-col px-5 pt-10" >
153+ < div className = "flex h-full flex-col overflow-hidden " >
119154 < div
120- className = "flex items-center pb-8 text-xl font-semibold"
155+ className = "flex shrink-0 items-center pb-4 text-base h-[44px] font-semibold"
121156 data-testid = "mainpage_title"
122157 >
123158 < div className = "h-7 w-10 transition-all group-data-[open=true]/sidebar-wrapper:md:w-0 lg:hidden" >
@@ -141,11 +176,11 @@ export const SourceChunksPage = () => {
141176 className = "h-4 w-4"
142177 />
143178 </ Button >
144- { sourceId }
179+ < span style = { { textTransform : "none" } } > { sourceId } </ span >
145180 </ div >
146181
147- < div className = "flex h-full flex-col" >
148- < div className = "pb-6 " >
182+ < div className = "flex flex-1 flex-col overflow-hidden " >
183+ < div className = "shrink-0 pb-4 xl:w-[600px] " >
149184 < Input
150185 icon = "Search"
151186 type = "text"
@@ -169,64 +204,110 @@ export const SourceChunksPage = () => {
169204 No chunks found
170205 </ div >
171206 ) : (
172- < >
173- < div className = "flex flex-col gap-3" >
174- { paginatedChunks . map ( ( chunk , index ) => (
175- < ChunkCard
176- key = { chunk . id }
177- chunk = { chunk }
178- index = { startIndex + index + 1 }
179- onCopy = { handleCopyChunk }
180- />
181- ) ) }
207+ < div className = "flex flex-1 flex-col overflow-hidden" >
208+ < div className = "flex-1 overflow-y-auto" >
209+ < div className = "flex flex-col gap-3" >
210+ { paginatedChunks . map ( ( chunk ) => (
211+ < ChunkCard
212+ key = { chunk . id }
213+ chunk = { chunk }
214+ index = { chunk . originalIndex }
215+ onCopy = { handleCopyChunk }
216+ />
217+ ) ) }
218+ </ div >
182219 </ div >
183220
184221 { totalPages > 1 && (
185- < div className = "flex items-center justify-between border-t border-border py-4" >
186- < span className = "text-sm text-muted-foreground" >
187- Showing { startIndex + 1 } -
188- { Math . min (
189- startIndex + CHUNKS_PER_PAGE ,
190- filteredChunks . length ,
191- ) } { " " }
192- of { filteredChunks . length } chunks
193- </ span >
194- < div className = "flex items-center gap-2" >
195- < Button
196- variant = "outline"
197- size = "sm"
198- onClick = { ( ) =>
199- setCurrentPage ( ( p ) => Math . max ( 1 , p - 1 ) )
200- }
201- disabled = { currentPage === 1 }
202- >
203- < ForwardedIconComponent
204- name = "ChevronLeft"
205- className = "h-4 w-4"
206- />
207- Previous
208- </ Button >
209- < span className = "px-2 text-sm" >
210- Page { currentPage } of { totalPages }
222+ < div className = "shrink-0 pb-4 pt-3" >
223+ < div className = "flex items-center justify-between" >
224+ < span className = "text-sm text-muted-foreground" >
225+ Showing { startIndex + 1 } -
226+ { Math . min (
227+ startIndex + CHUNKS_PER_PAGE ,
228+ filteredChunks . length ,
229+ ) } { " " }
230+ of { filteredChunks . length } chunks
211231 </ span >
212- < Button
213- variant = "outline"
214- size = "sm"
215- onClick = { ( ) =>
216- setCurrentPage ( ( p ) => Math . min ( totalPages , p + 1 ) )
217- }
218- disabled = { currentPage === totalPages }
219- >
220- Next
221- < ForwardedIconComponent
222- name = "ChevronRight"
223- className = "h-4 w-4"
224- />
225- </ Button >
232+ < div className = "flex items-center gap-2" >
233+ < Button
234+ variant = "outline"
235+ size = "icon"
236+ className = "h-8 w-8"
237+ onClick = { ( ) => setCurrentPage ( 1 ) }
238+ disabled = { currentPage === 1 }
239+ >
240+ < ForwardedIconComponent
241+ name = "ChevronsLeft"
242+ className = "h-4 w-4"
243+ />
244+ </ Button >
245+ < Button
246+ variant = "outline"
247+ size = "icon"
248+ className = "h-8 w-8"
249+ onClick = { ( ) =>
250+ setCurrentPage ( ( p ) => Math . max ( 1 , p - 1 ) )
251+ }
252+ disabled = { currentPage === 1 }
253+ >
254+ < ForwardedIconComponent
255+ name = "ChevronLeft"
256+ className = "h-4 w-4"
257+ />
258+ </ Button >
259+ < div className = "flex items-center gap-1.5 px-2 text-sm" >
260+ < span > Page</ span >
261+ < input
262+ type = "number"
263+ min = { 1 }
264+ max = { totalPages }
265+ value = { currentPage }
266+ onChange = { ( e ) => {
267+ const value = parseInt ( e . target . value , 10 ) ;
268+ if ( ! isNaN ( value ) ) {
269+ setCurrentPage (
270+ Math . max ( 1 , Math . min ( totalPages , value ) ) ,
271+ ) ;
272+ }
273+ } }
274+ className = "h-7 w-16 rounded border border-input bg-background px-2 text-center text-sm focus:outline-none focus:ring-1 focus:ring-ring"
275+ />
276+ < span > of { totalPages } </ span >
277+ </ div >
278+ < Button
279+ variant = "outline"
280+ size = "icon"
281+ className = "h-8 w-8"
282+ onClick = { ( ) =>
283+ setCurrentPage ( ( p ) =>
284+ Math . min ( totalPages , p + 1 ) ,
285+ )
286+ }
287+ disabled = { currentPage === totalPages }
288+ >
289+ < ForwardedIconComponent
290+ name = "ChevronRight"
291+ className = "h-4 w-4"
292+ />
293+ </ Button >
294+ < Button
295+ variant = "outline"
296+ size = "icon"
297+ className = "h-8 w-8"
298+ onClick = { ( ) => setCurrentPage ( totalPages ) }
299+ disabled = { currentPage === totalPages }
300+ >
301+ < ForwardedIconComponent
302+ name = "ChevronsRight"
303+ className = "h-4 w-4"
304+ />
305+ </ Button >
306+ </ div >
226307 </ div >
227308 </ div >
228309 ) }
229- </ >
310+ </ div >
230311 ) }
231312 </ div >
232313 </ div >
0 commit comments