1+ const API_BASE = 'http://localhost:8080/api/v1' ;
2+ let uploadedFiles = [ ] ;
3+ let selectedTopic = null ;
4+
5+ // Initialize
6+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
7+ loadTopics ( ) ;
8+ loadStats ( ) ;
9+ setupUploadListeners ( ) ;
10+ } ) ;
11+
12+ // Load topics
13+ async function loadTopics ( ) {
14+ try {
15+ const response = await fetch ( `${ API_BASE } /topics` ) ;
16+ const topics = await response . json ( ) ;
17+
18+ const select = document . getElementById ( 'topicSelect' ) ;
19+ Object . keys ( topics ) . forEach ( topic => {
20+ const option = document . createElement ( 'option' ) ;
21+ option . value = topic ;
22+ option . textContent = topic . charAt ( 0 ) . toUpperCase ( ) + topic . slice ( 1 ) ;
23+ select . appendChild ( option ) ;
24+ } ) ;
25+
26+ select . addEventListener ( 'change' , ( e ) => {
27+ selectedTopic = e . target . value ;
28+ if ( selectedTopic ) {
29+ const info = topics [ selectedTopic ] ;
30+ const infoDiv = document . getElementById ( 'topicInfo' ) ;
31+ infoDiv . innerHTML = `<strong>${ info . description } </strong>` ;
32+ infoDiv . classList . add ( 'show' ) ;
33+ }
34+ } ) ;
35+ } catch ( error ) {
36+ console . error ( 'Error loading topics:' , error ) ;
37+ }
38+ }
39+
40+ // Setup upload listeners
41+ function setupUploadListeners ( ) {
42+ setupUploadArea ( 'pdf' , '#pdfUploadArea' , '#pdfInput' , '#pdfStatus' , uploadPdf ) ;
43+ setupUploadArea ( 'md' , '#mdUploadArea' , '#mdInput' , '#mdStatus' , uploadMarkdown ) ;
44+ }
45+
46+ function setupUploadArea ( type , areaSelector , inputSelector , statusSelector , uploadHandler ) {
47+ const area = document . querySelector ( areaSelector ) ;
48+ const input = document . querySelector ( inputSelector ) ;
49+
50+ area . addEventListener ( 'click' , ( ) => input . click ( ) ) ;
51+ area . addEventListener ( 'dragover' , ( e ) => e . preventDefault ( ) ) ;
52+ area . addEventListener ( 'drop' , ( e ) => {
53+ e . preventDefault ( ) ;
54+ uploadHandler ( e . dataTransfer . files ) ;
55+ } ) ;
56+ input . addEventListener ( 'change' , ( e ) => uploadHandler ( e . target . files ) ) ;
57+ }
58+
59+ async function uploadPdf ( files ) {
60+ if ( ! selectedTopic ) {
61+ alert ( 'Please select a topic first' ) ;
62+ return ;
63+ }
64+
65+ for ( let file of files ) {
66+ const formData = new FormData ( ) ;
67+ formData . append ( 'file' , file ) ;
68+
69+ const statusDiv = document . getElementById ( 'pdfStatus' ) ;
70+ statusDiv . innerHTML = `<div class="status info">⏳ Uploading ${ file . name } ...</div>` ;
71+
72+ try {
73+ const response = await fetch (
74+ `${ API_BASE } /topics/${ selectedTopic } /documents/upload/pdf` ,
75+ { method : 'POST' , body : formData }
76+ ) ;
77+
78+ if ( response . ok ) {
79+ const data = await response . json ( ) ;
80+ uploadedFiles . push ( data ) ;
81+ statusDiv . innerHTML = `<div class="status success">✅ ${ file . name } - ${ data . chunksCount } chunks indexed</div>` ;
82+ updateFileList ( ) ;
83+ } else {
84+ statusDiv . innerHTML = `<div class="status error">❌ Failed to upload ${ file . name } </div>` ;
85+ }
86+ } catch ( error ) {
87+ statusDiv . innerHTML = `<div class="status error">❌ Error: ${ error . message } </div>` ;
88+ }
89+ }
90+ }
91+
92+ async function uploadMarkdown ( files ) {
93+ if ( ! selectedTopic ) {
94+ alert ( 'Please select a topic first' ) ;
95+ return ;
96+ }
97+
98+ for ( let file of files ) {
99+ const formData = new FormData ( ) ;
100+ formData . append ( 'file' , file ) ;
101+
102+ const statusDiv = document . getElementById ( 'mdStatus' ) ;
103+ statusDiv . innerHTML = `<div class="status info">⏳ Uploading ${ file . name } ... </div>` ;
104+
105+ try {
106+ const response = await fetch (
107+ `${ API_BASE } /topics/${ selectedTopic } /documents/upload/markdown` ,
108+ { method : 'POST' , body : formData }
109+ ) ;
110+
111+ if ( response . ok ) {
112+ const data = await response . json ( ) ;
113+ uploadedFiles . push ( data ) ;
114+ statusDiv . innerHTML = `<div class="status success">✅ ${ file . name } - ${ data . chunksCount } chunks indexed</div>` ;
115+ updateFileList ( ) ;
116+ } else {
117+ statusDiv . innerHTML = `<div class="status error">❌ Failed to upload ${ file . name } </div>` ;
118+ }
119+ } catch ( error ) {
120+ statusDiv . innerHTML = `<div class="status error">❌ Error: ${ error . message } </div>` ;
121+ }
122+ }
123+ }
124+
125+ function updateFileList ( ) {
126+ if ( uploadedFiles . length > 0 ) {
127+ document . getElementById ( 'fileList' ) . style . display = 'block' ;
128+ document . getElementById ( 'fileItems' ) . innerHTML = uploadedFiles
129+ . map ( f => `
130+ <div class="file-item">
131+ <div class="info">
132+ <strong>${ f . filename } </strong>
133+ <p>${ f . chunksCount } chunks • ${ f . type . toUpperCase ( ) } </p>
134+ </div>
135+ <span class="badge">${ f . status } </span>
136+ </div>
137+ ` )
138+ . join ( '' ) ;
139+ }
140+ }
141+
142+ async function submitQuery ( ) {
143+ if ( ! selectedTopic ) {
144+ alert ( 'Please select a topic first' ) ;
145+ return ;
146+ }
147+
148+ const query = document . getElementById ( 'queryInput' ) . value . trim ( ) ;
149+ if ( ! query ) {
150+ alert ( 'Please enter a question' ) ;
151+ return ;
152+ }
153+
154+ const loader = document . getElementById ( 'loader' ) ;
155+ const resultDiv = document . getElementById ( 'result' ) ;
156+
157+ loader . classList . add ( 'active' ) ;
158+ resultDiv . innerHTML = '' ;
159+
160+ try {
161+ const response = await fetch (
162+ `${ API_BASE } /topics/${ selectedTopic } /query` ,
163+ {
164+ method : 'POST' ,
165+ headers : { 'Content-Type' : 'application/json' } ,
166+ body : JSON . stringify ( { query, topK : 5 } )
167+ }
168+ ) ;
169+
170+ const data = await response . json ( ) ;
171+
172+ let sourcesHtml = `<strong>Sources (${ data . sourceCount } ):</strong>` ;
173+ if ( data . sources && data . sources . length > 0 ) {
174+ sourcesHtml += data . sources . map ( s => `
175+ <div class="source-item">
176+ <div class="title">${ s . filename } </div>
177+ <div class="meta">${ s . title } ${ s . author ? ' • ' + s . author : '' } ${ s . publishingYear ? ' (' + s . publishingYear + ')' : '' } </div>
178+ </div>
179+ ` ) . join ( '' ) ;
180+ }
181+
182+ resultDiv . innerHTML = `
183+ <div class="result">
184+ <div class="answer">
185+ <strong>Answer:</strong><br>
186+ ${ data . answer }
187+ </div>
188+ <div class="sources">
189+ ${ sourcesHtml }
190+ </div>
191+ </div>
192+ ` ;
193+ } catch ( error ) {
194+ resultDiv . innerHTML = `<div class="status error">Error: ${ error . message } </div>` ;
195+ } finally {
196+ loader . classList . remove ( 'active' ) ;
197+ }
198+ }
199+
200+ async function loadStats ( ) {
201+ try {
202+ const response = await fetch ( `${ API_BASE } /topics/stats` ) ;
203+ const stats = await response . json ( ) ;
204+
205+ const statsDiv = document . getElementById ( 'stats' ) ;
206+ statsDiv . innerHTML = Object . entries ( stats )
207+ . map ( ( [ topic , info ] ) => `
208+ <div class="stat-card">
209+ <div class="stat-label">${ topic . toUpperCase ( ) } </div>
210+ <div class="stat-value">${ info . vectors_count || 0 } </div>
211+ <div class="stat-label">Vectors</div>
212+ </div>
213+ ` )
214+ . join ( '' ) ;
215+ } catch ( error ) {
216+ console . error ( 'Error loading stats:' , error ) ;
217+ }
218+ }
219+
220+ document . getElementById ( 'queryInput' ) ?. addEventListener ( 'keypress' , ( e ) => {
221+ if ( e . key === 'Enter' ) submitQuery ( ) ;
222+ } ) ;
0 commit comments