33< head >
44 < meta charset ="UTF-8 ">
55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6- < title > PDF Chat with Gemini</ title >
6+ < title > PDF Chat with Gemini & OpenAI </ title >
77 < link href ="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css " rel ="stylesheet ">
88 < link rel ="stylesheet " href ="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css ">
99 < style >
151151 color : # 667eea ;
152152 font-weight : 600 ;
153153 }
154+
155+ .api-toggle {
156+ display : flex;
157+ background : rgba (255 , 255 , 255 , 0.1 );
158+ border-radius : 10px ;
159+ padding : 4px ;
160+ margin-bottom : 15px ;
161+ }
162+ .api-toggle-btn {
163+ flex : 1 ;
164+ border : none;
165+ background : transparent;
166+ color : white;
167+ padding : 6px ;
168+ border-radius : 8px ;
169+ font-size : 0.8rem ;
170+ font-weight : 600 ;
171+ transition : all 0.2s ;
172+ }
173+ .api-toggle-btn .active {
174+ background : white;
175+ color : # 764ba2 ;
176+ box-shadow : 0 2px 5px rgba (0 , 0 , 0 , 0.1 );
177+ }
154178 </ style >
155179</ head >
156180< body >
160184 < div class ="col-md-3 col-lg-3 sidebar d-flex flex-column ">
161185 < div class ="mb-4 text-center ">
162186 < h3 class ="mt-2 "> < i class ="fas fa-file-pdf me-2 "> </ i > DocuChat AI</ h3 >
163- < p class ="small text-light opacity-75 "> Powered by Gemini</ p >
187+ {% if is_admin %}
188+ < div class ="d-flex align-items-center justify-content-center ">
189+ < span class ="badge bg-warning text-dark "> < i class ="fas fa-user-shield me-1 "> </ i > Admin Mode</ span >
190+ < a href ="{% url 'logout' %} " class ="text-light small ms-2 "> < i class ="fas fa-sign-out-alt "> </ i > </ a >
191+ </ div >
192+ {% else %}
193+ < a href ="{% url 'login' %} " class ="text-light small text-decoration-none opacity-75 "> Admin Login</ a >
194+ {% endif %}
164195 </ div >
165196
197+ {% if not is_admin %}
198+ <!-- API Key Section for Users -->
199+ < div class ="api-section bg-white bg-opacity-10 p-3 rounded-3 mb-4 ">
200+ < h5 class ="mb-3 small text-uppercase ls-1 "> < i class ="fas fa-key me-2 "> </ i > Provider Setup</ h5 >
201+
202+ < div class ="api-toggle ">
203+ < button onclick ="toggleApi('gemini') " id ="toggle-gemini " class ="api-toggle-btn active "> Gemini</ button >
204+ < button onclick ="toggleApi('openai') " id ="toggle-openai " class ="api-toggle-btn "> OpenAI</ button >
205+ </ div >
206+
207+ < div id ="api-input-container ">
208+ < input type ="password " id ="api-key-input " class ="form-control form-control-sm mb-2 " placeholder ="Enter Gemini Key ">
209+ < button onclick ="submitKey() " id ="check-key-btn " class ="btn btn-primary btn-sm w-100 py-2 " style ="font-size: 0.75rem; ">
210+ < i class ="fas fa-plug me-1 "> </ i > Connect API
211+ </ button >
212+ </ div >
213+
214+ < div id ="active-keys " class ="mt-3 ">
215+ {% if has_gemini_key %}
216+ < div class ="d-flex justify-content-between align-items-center mb-1 ">
217+ < span class ="small "> < i class ="fas fa-check-circle text-success me-1 "> </ i > Gemini Active</ span >
218+ < button onclick ="clearKeys() " class ="btn btn-link text-light p-0 small " style ="font-size: 0.65rem; text-decoration:none; "> Clear</ button >
219+ </ div >
220+ {% endif %}
221+ {% if has_openai_key %}
222+ < div class ="d-flex justify-content-between align-items-center ">
223+ < span class ="small "> < i class ="fas fa-check-circle text-success me-1 "> </ i > OpenAI Active</ span >
224+ < button onclick ="clearKeys() " class ="btn btn-link text-light p-0 small " style ="font-size: 0.65rem; text-decoration:none; "> Clear</ button >
225+ </ div >
226+ {% endif %}
227+ </ div >
228+ </ div >
229+ {% endif %}
230+
166231 < div class ="upload-section bg-white bg-opacity-10 p-3 rounded-3 mb-4 ">
167- < h5 class ="mb-3 "> < i class ="fas fa-cloud-upload-alt me-2 "> </ i > Upload PDF</ h5 >
232+ < h5 class ="mb-3 small text-uppercase ls-1 "> < i class ="fas fa-cloud-upload-alt me-2 "> </ i > Upload PDF</ h5 >
168233 < form action ="{% url 'upload_pdf' %} " method ="post " enctype ="multipart/form-data ">
169234 {% csrf_token %}
170235 < div class ="mb-2 ">
@@ -173,11 +238,16 @@ <h5 class="mb-3"><i class="fas fa-cloud-upload-alt me-2"></i>Upload PDF</h5>
173238 < div class ="mb-3 ">
174239 < input type ="file " name ="file " class ="form-control form-control-sm " required id ="id_file " accept =".pdf ">
175240 </ div >
176- < button type ="submit " class ="btn btn-primary w-100 btn-sm "> < i class ="fas fa-cogs me-1 "> </ i > Process Document</ button >
241+ < button type ="submit " class ="btn btn-primary w-100 btn-sm " id ="upload-btn " {% if not is_admin and not has_gemini_key and not has_openai_key %}disabled{% endif %} >
242+ < i class ="fas fa-cogs me-1 "> </ i > Process Document
243+ </ button >
244+ {% if not is_admin and not has_gemini_key and not has_openai_key %}
245+ < p class ="text-center small text-warning mt-2 mb-0 " style ="font-size: 0.7rem; "> Connect an API to unlock</ p >
246+ {% endif %}
177247 </ form >
178248 </ div >
179249
180- < h5 class ="mb-3 mt-2 "> < i class ="fas fa-book me-2 "> </ i > My Library</ h5 >
250+ < h5 class ="mb-3 mt-2 small text-uppercase ls-1 "> < i class ="fas fa-book me-2 "> </ i > My Library</ h5 >
181251 < div id ="pdf-list " class ="flex-grow-1 overflow-auto pe-2 ">
182252 {% for doc in documents %}
183253 < div class ="pdf-item {% if doc.status == 'FAILED' %}border-danger{% endif %} " data-id ="{{ doc.id }} " data-status ="{{ doc.status }} ">
@@ -210,10 +280,10 @@ <h5 class="mb-3 mt-2"><i class="fas fa-book me-2"></i>My Library</h5>
210280 < div class ="chat-header d-flex justify-content-between align-items-center ">
211281 < h4 class ="m-0 text-secondary " id ="current-doc-title "> Select a document to begin</ h4 >
212282 < div class ="d-flex align-items-center ">
213- < label for ="model-selector " class ="me-2 mb-0 small text-muted "> Model:</ label >
214- < select id ="model-selector " class ="form-select form-select-sm shadow-sm ">
283+ < label for ="model-selector " class ="me-2 mb-0 small text-muted "> Active Model:</ label >
284+ < select id ="model-selector " class ="form-select form-select-sm shadow-sm " style =" min-width: 200px; " >
215285 {% for model in available_models %}
216- < option value ="{{ model.name }} " {% if model.name == "models/gemini-1.5-flash " %}selected{% endif %} >
286+ < option value ="{{ model.name }} " {% if model.name == "models/gemini-1.5-flash " or model.name == " gpt-4o " %}selected{% endif %} >
217287 {{ model.display_name }}
218288 </ option >
219289 {% empty %}
@@ -245,6 +315,49 @@ <h5>Welcome to DocuChat!</h5>
245315
246316 < script >
247317 let selectedDocId = null ;
318+ let activeApiType = 'gemini' ;
319+
320+ function toggleApi ( type ) {
321+ activeApiType = type ;
322+ document . getElementById ( 'toggle-gemini' ) . classList . toggle ( 'active' , type === 'gemini' ) ;
323+ document . getElementById ( 'toggle-openai' ) . classList . toggle ( 'active' , type === 'openai' ) ;
324+ document . getElementById ( 'api-key-input' ) . placeholder = `Enter ${ type === 'gemini' ? 'Gemini' : 'OpenAI' } Key` ;
325+ document . getElementById ( 'api-key-input' ) . value = '' ;
326+ }
327+
328+ async function submitKey ( ) {
329+ const input = document . getElementById ( 'api-key-input' ) ;
330+ const key = input . value . trim ( ) ;
331+ if ( ! key ) return ;
332+
333+ const btn = document . getElementById ( 'check-key-btn' ) ;
334+ const originalText = btn . innerHTML ;
335+ btn . innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> Connecting...' ;
336+ btn . disabled = true ;
337+
338+ const formData = new FormData ( ) ;
339+ formData . append ( 'key_type' , activeApiType ) ;
340+ formData . append ( 'key' , key ) ;
341+
342+ try {
343+ const response = await fetch ( "{% url 'validate_api_key' %}" , {
344+ method : 'POST' ,
345+ headers : { 'X-CSRFToken' : '{{ csrf_token }}' } ,
346+ body : formData
347+ } ) ;
348+ const data = await response . json ( ) ;
349+ if ( data . success ) {
350+ location . reload ( ) ;
351+ } else {
352+ alert ( 'Invalid Key: ' + data . error ) ;
353+ }
354+ } catch ( e ) {
355+ alert ( 'Connection error' ) ;
356+ } finally {
357+ btn . innerHTML = originalText ;
358+ btn . disabled = false ;
359+ }
360+ }
248361
249362 document . querySelectorAll ( '.pdf-item' ) . forEach ( item => {
250363 item . addEventListener ( 'click' , function ( ) {
@@ -282,16 +395,12 @@ <h5>Welcome to DocuChat!</h5>
282395 if ( ! query || ! selectedDocId ) return ;
283396
284397 const chatMessages = document . getElementById ( 'chat-messages' ) ;
285-
286- // Add user message
287398 const userMsgDiv = document . createElement ( 'div' ) ;
288399 userMsgDiv . className = 'message user-message shadow-sm' ;
289400 userMsgDiv . innerText = query ;
290401 chatMessages . appendChild ( userMsgDiv ) ;
291-
292402 input . value = '' ;
293403
294- // Add loading message
295404 const loadingMsgDiv = document . createElement ( 'div' ) ;
296405 loadingMsgDiv . className = 'message bot-message loading shadow-sm' ;
297406 loadingMsgDiv . innerHTML = '<i class="fas fa-robot me-2 text-primary opacity-50"></i>Analyzing document' ;
@@ -325,10 +434,9 @@ <h5>Welcome to DocuChat!</h5>
325434 chatMessages . removeChild ( loadingMsgDiv ) ;
326435 const botMsgDiv = document . createElement ( 'div' ) ;
327436 botMsgDiv . className = 'message bot-message border-danger text-danger shadow-sm' ;
328- botMsgDiv . innerHTML = `<i class="fas fa-exclamation-triangle me-2"></i>Connection error. Please try again. ` ;
437+ botMsgDiv . innerHTML = `<i class="fas fa-exclamation-triangle me-2"></i>Connection error.` ;
329438 chatMessages . appendChild ( botMsgDiv ) ;
330439 }
331-
332440 chatMessages . scrollTop = chatMessages . scrollHeight ;
333441 }
334442
@@ -338,54 +446,37 @@ <h5>Welcome to DocuChat!</h5>
338446 } ) ;
339447
340448 async function deleteDocument ( event , docId ) {
341- event . stopPropagation ( ) ; // Prevent selecting the document when clicking delete
342-
343- if ( ! confirm ( 'Are you sure you want to delete this document and its chat history? This action cannot be undone.' ) ) {
344- return ;
345- }
449+ event . stopPropagation ( ) ;
450+ if ( ! confirm ( 'Are you sure?' ) ) return ;
346451
347452 const item = document . querySelector ( `.pdf-item[data-id="${ docId } "]` ) ;
348- const originalOpacity = item . style . opacity ;
349453 item . style . opacity = '0.5' ;
350- item . style . pointerEvents = 'none' ;
351454
352455 try {
353456 const response = await fetch ( `/delete/${ docId } /` , {
354457 method : 'POST' ,
355- headers : {
356- 'X-CSRFToken' : '{{ csrf_token }}'
357- }
458+ headers : { 'X-CSRFToken' : '{{ csrf_token }}' }
358459 } ) ;
359-
360460 const data = await response . json ( ) ;
361-
362461 if ( data . success ) {
363- item . style . transform = 'translateX(-20px)' ;
364- item . style . opacity = '0' ;
365- setTimeout ( ( ) => {
366- item . remove ( ) ;
367- if ( selectedDocId == docId ) {
368- selectedDocId = null ;
369- document . getElementById ( 'query-input' ) . disabled = true ;
370- document . getElementById ( 'send-btn' ) . disabled = true ;
371- document . getElementById ( 'current-doc-title' ) . innerText = 'Select a document to begin' ;
372- document . getElementById ( 'chat-messages' ) . innerHTML = `
373- <div class="text-center mt-5 text-muted">
374- <i class="fas fa-comments fa-4x mb-3 opacity-25"></i>
375- <h5>Welcome to DocuChat!</h5>
376- <p>Select a processed document from the sidebar to ask questions about its content.</p>
377- </div>` ;
378- }
379- } , 300 ) ;
380- } else {
381- alert ( 'Error: ' + ( data . error || 'Failed to delete document' ) ) ;
382- item . style . opacity = originalOpacity ;
383- item . style . pointerEvents = 'auto' ;
462+ item . remove ( ) ;
463+ if ( selectedDocId == docId ) location . reload ( ) ;
384464 }
385465 } catch ( error ) {
386- alert ( 'Connection error. Failed to delete document.' ) ;
387- item . style . opacity = originalOpacity ;
388- item . style . pointerEvents = 'auto' ;
466+ alert ( 'Error deleting' ) ;
467+ item . style . opacity = '1' ;
468+ }
469+ }
470+
471+ async function clearKeys ( ) {
472+ try {
473+ await fetch ( "{% url 'clear_keys' %}" , {
474+ method : 'POST' ,
475+ headers : { 'X-CSRFToken' : '{{ csrf_token }}' }
476+ } ) ;
477+ location . reload ( ) ;
478+ } catch ( e ) {
479+ alert ( 'Error clearing keys' ) ;
389480 }
390481 }
391482 </ script >
0 commit comments