fixed linting

This commit is contained in:
Saifeddine ALOUI
2025-01-24 21:01:34 +01:00
parent 91422b0eb6
commit 1776c1afcd
2 changed files with 90 additions and 89 deletions

View File

@@ -468,13 +468,12 @@ def parse_args() -> argparse.Namespace:
help="Path to SSL private key file (required if --ssl is enabled)", help="Path to SSL private key file (required if --ssl is enabled)",
) )
parser.add_argument( parser.add_argument(
'--auto-scan-at-startup', "--auto-scan-at-startup",
action='store_true', action="store_true",
default=False, default=False,
help='Enable automatic scanning when the program starts' help="Enable automatic scanning when the program starts",
) )
args = parser.parse_args() args = parser.parse_args()
return args return args
@@ -912,7 +911,7 @@ def create_app(args):
"""Lifespan context manager for startup and shutdown events""" """Lifespan context manager for startup and shutdown events"""
# Startup logic # Startup logic
# Now only if this option is active, we can scan. This is better for big databases where there are hundreds of # Now only if this option is active, we can scan. This is better for big databases where there are hundreds of
# files. Makes the startup faster # files. Makes the startup faster
if args.auto_scan_at_startup: if args.auto_scan_at_startup:
ASCIIColors.info("Auto scan is active, rescanning the input directory.") ASCIIColors.info("Auto scan is active, rescanning the input directory.")
try: try:
@@ -923,8 +922,10 @@ def create_app(args):
except Exception as e: except Exception as e:
trace_exception(e) trace_exception(e)
logging.error(f"Error indexing file {file_path}: {str(e)}") logging.error(f"Error indexing file {file_path}: {str(e)}")
logging.info(f"Indexed {len(new_files)} documents from {args.input_dir}") logging.info(
f"Indexed {len(new_files)} documents from {args.input_dir}"
)
except Exception as e: except Exception as e:
logging.error(f"Error during startup indexing: {str(e)}") logging.error(f"Error during startup indexing: {str(e)}")
@@ -932,17 +933,17 @@ def create_app(args):
async def scan_for_new_documents(): async def scan_for_new_documents():
""" """
Manually trigger scanning for new documents in the directory managed by `doc_manager`. Manually trigger scanning for new documents in the directory managed by `doc_manager`.
This endpoint facilitates manual initiation of a document scan to identify and index new files. This endpoint facilitates manual initiation of a document scan to identify and index new files.
It processes all newly detected files, attempts indexing each file, logs any errors that occur, It processes all newly detected files, attempts indexing each file, logs any errors that occur,
and returns a summary of the operation. and returns a summary of the operation.
Returns: Returns:
dict: A dictionary containing: dict: A dictionary containing:
- "status" (str): Indicates success or failure of the scanning process. - "status" (str): Indicates success or failure of the scanning process.
- "indexed_count" (int): The number of successfully indexed documents. - "indexed_count" (int): The number of successfully indexed documents.
- "total_documents" (int): Total number of documents that have been indexed so far. - "total_documents" (int): Total number of documents that have been indexed so far.
Raises: Raises:
HTTPException: If an error occurs during the document scanning process, a 500 status HTTPException: If an error occurs during the document scanning process, a 500 status
code is returned with details about the exception. code is returned with details about the exception.
@@ -970,25 +971,25 @@ def create_app(args):
async def upload_to_input_dir(file: UploadFile = File(...)): async def upload_to_input_dir(file: UploadFile = File(...)):
""" """
Endpoint for uploading a file to the input directory and indexing it. Endpoint for uploading a file to the input directory and indexing it.
This API endpoint accepts a file through an HTTP POST request, checks if the This API endpoint accepts a file through an HTTP POST request, checks if the
uploaded file is of a supported type, saves it in the specified input directory, uploaded file is of a supported type, saves it in the specified input directory,
indexes it for retrieval, and returns a success status with relevant details. indexes it for retrieval, and returns a success status with relevant details.
Parameters: Parameters:
file (UploadFile): The file to be uploaded. It must have an allowed extension as per file (UploadFile): The file to be uploaded. It must have an allowed extension as per
`doc_manager.supported_extensions`. `doc_manager.supported_extensions`.
Returns: Returns:
dict: A dictionary containing the upload status ("success"), dict: A dictionary containing the upload status ("success"),
a message detailing the operation result, and a message detailing the operation result, and
the total number of indexed documents. the total number of indexed documents.
Raises: Raises:
HTTPException: If the file type is not supported, it raises a 400 Bad Request error. HTTPException: If the file type is not supported, it raises a 400 Bad Request error.
If any other exception occurs during the file handling or indexing, If any other exception occurs during the file handling or indexing,
it raises a 500 Internal Server Error with details about the exception. it raises a 500 Internal Server Error with details about the exception.
""" """
try: try:
if not doc_manager.is_supported_file(file.filename): if not doc_manager.is_supported_file(file.filename):
raise HTTPException( raise HTTPException(
@@ -1017,23 +1018,23 @@ def create_app(args):
async def query_text(request: QueryRequest): async def query_text(request: QueryRequest):
""" """
Handle a POST request at the /query endpoint to process user queries using RAG capabilities. Handle a POST request at the /query endpoint to process user queries using RAG capabilities.
Parameters: Parameters:
request (QueryRequest): A Pydantic model containing the following fields: request (QueryRequest): A Pydantic model containing the following fields:
- query (str): The text of the user's query. - query (str): The text of the user's query.
- mode (ModeEnum): Optional. Specifies the mode of retrieval augmentation. - mode (ModeEnum): Optional. Specifies the mode of retrieval augmentation.
- stream (bool): Optional. Determines if the response should be streamed. - stream (bool): Optional. Determines if the response should be streamed.
- only_need_context (bool): Optional. If true, returns only the context without further processing. - only_need_context (bool): Optional. If true, returns only the context without further processing.
Returns: Returns:
QueryResponse: A Pydantic model containing the result of the query processing. QueryResponse: A Pydantic model containing the result of the query processing.
If a string is returned (e.g., cache hit), it's directly returned. If a string is returned (e.g., cache hit), it's directly returned.
Otherwise, an async generator may be used to build the response. Otherwise, an async generator may be used to build the response.
Raises: Raises:
HTTPException: Raised when an error occurs during the request handling process, HTTPException: Raised when an error occurs during the request handling process,
with status code 500 and detail containing the exception message. with status code 500 and detail containing the exception message.
""" """
try: try:
response = await rag.aquery( response = await rag.aquery(
request.query, request.query,
@@ -1074,7 +1075,7 @@ def create_app(args):
Returns: Returns:
StreamingResponse: A streaming response containing the RAG query results. StreamingResponse: A streaming response containing the RAG query results.
""" """
try: try:
response = await rag.aquery( # Use aquery instead of query, and add await response = await rag.aquery( # Use aquery instead of query, and add await
request.query, request.query,
@@ -1134,7 +1135,7 @@ def create_app(args):
Returns: Returns:
InsertResponse: A response object containing the status of the operation, a message, and the number of documents inserted. InsertResponse: A response object containing the status of the operation, a message, and the number of documents inserted.
""" """
try: try:
await rag.ainsert(request.text) await rag.ainsert(request.text)
return InsertResponse( return InsertResponse(
@@ -1772,7 +1773,7 @@ def create_app(args):
"max_tokens": args.max_tokens, "max_tokens": args.max_tokens,
}, },
} }
# Serve the static files # Serve the static files
static_dir = Path(__file__).parent / "static" static_dir = Path(__file__).parent / "static"
static_dir.mkdir(exist_ok=True) static_dir.mkdir(exist_ok=True)
@@ -1780,13 +1781,13 @@ def create_app(args):
return app return app
def main(): def main():
args = parse_args() args = parse_args()
import uvicorn import uvicorn
app = create_app(args) app = create_app(args)
display_splash_screen(args) display_splash_screen(args)
uvicorn_config = { uvicorn_config = {
"app": app, "app": app,
"host": args.host, "host": args.host,

View File

@@ -11,7 +11,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-python.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-javascript.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-css.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-css.min.js"></script>
<style> <style>
body { body {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
@@ -37,12 +37,12 @@
overflow-x: auto; overflow-x: auto;
margin: 1rem 0; margin: 1rem 0;
} }
code { code {
font-family: 'Fira Code', monospace; font-family: 'Fira Code', monospace;
font-size: 0.9em; font-size: 0.9em;
} }
/* Inline code styling */ /* Inline code styling */
:not(pre) > code { :not(pre) > code {
background: #f4f4f4; background: #f4f4f4;
@@ -50,7 +50,7 @@
border-radius: 0.3em; border-radius: 0.3em;
font-size: 0.9em; font-size: 0.9em;
} }
/* Prose modifications for better markdown rendering */ /* Prose modifications for better markdown rendering */
.prose pre { .prose pre {
background: #f4f4f4; background: #f4f4f4;
@@ -58,7 +58,7 @@
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 1rem 0; margin: 1rem 0;
} }
.prose code { .prose code {
color: #374151; color: #374151;
background: #f4f4f4; background: #f4f4f4;
@@ -66,7 +66,7 @@
border-radius: 0.3em; border-radius: 0.3em;
font-size: 0.9em; font-size: 0.9em;
} }
.prose { .prose {
max-width: none; max-width: none;
} }
@@ -82,14 +82,14 @@
<!-- Health Check Button --> <!-- Health Check Button -->
<button id="healthCheckBtn" class="p-2 text-slate-600 hover:text-slate-800 transition-colors rounded-lg hover:bg-slate-100"> <button id="healthCheckBtn" class="p-2 text-slate-600 hover:text-slate-800 transition-colors rounded-lg hover:bg-slate-100">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
</button> </button>
<!-- Settings Button --> <!-- Settings Button -->
<button id="settingsBtn" class="p-2 text-slate-600 hover:text-slate-800 transition-colors rounded-lg hover:bg-slate-100"> <button id="settingsBtn" class="p-2 text-slate-600 hover:text-slate-800 transition-colors rounded-lg hover:bg-slate-100">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/> d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 15a3 3 0 100-6 3 3 0 000 6z" /> d="M12 15a3 3 0 100-6 3 3 0 000 6z" />
@@ -107,7 +107,7 @@
<section class="mb-8"> <section class="mb-8">
<div class="bg-slate-50 p-6 rounded-lg"> <div class="bg-slate-50 p-6 rounded-lg">
<h2 class="text-xl font-semibold text-slate-800 mb-4">Upload Documents</h2> <h2 class="text-xl font-semibold text-slate-800 mb-4">Upload Documents</h2>
<!-- Upload Form --> <!-- Upload Form -->
<form id="uploadForm" class="space-y-4"> <form id="uploadForm" class="space-y-4">
<!-- Drop Zone --> <!-- Drop Zone -->
@@ -135,9 +135,9 @@
</div> </div>
<!-- Upload Button --> <!-- Upload Button -->
<button type="submit" <button type="submit"
class="w-full bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 class="w-full bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700
transition-colors font-medium focus:outline-none focus:ring-2 transition-colors font-medium focus:outline-none focus:ring-2
focus:ring-blue-500 focus:ring-offset-2"> focus:ring-blue-500 focus:ring-offset-2">
Upload Documents Upload Documents
</button> </button>
@@ -149,19 +149,19 @@
<section> <section>
<div class="bg-slate-50 p-6 rounded-lg"> <div class="bg-slate-50 p-6 rounded-lg">
<h2 class="text-xl font-semibold text-slate-800 mb-4">Query Documents</h2> <h2 class="text-xl font-semibold text-slate-800 mb-4">Query Documents</h2>
<form id="queryForm" class="space-y-4"> <form id="queryForm" class="space-y-4">
<textarea id="queryInput" <textarea id="queryInput"
class="w-full p-4 border border-slate-300 rounded-lg focus:ring-2 class="w-full p-4 border border-slate-300 rounded-lg focus:ring-2
focus:ring-blue-500 focus:border-blue-500 transition-all focus:ring-blue-500 focus:border-blue-500 transition-all
min-h-[120px] resize-y" min-h-[120px] resize-y"
placeholder="Enter your query here..." placeholder="Enter your query here..."
></textarea> ></textarea>
<button type="submit" <button type="submit"
class="w-full bg-green-600 text-white px-6 py-3 rounded-lg class="w-full bg-green-600 text-white px-6 py-3 rounded-lg
hover:bg-green-700 transition-colors font-medium hover:bg-green-700 transition-colors font-medium
focus:outline-none focus:ring-2 focus:ring-green-500 focus:outline-none focus:ring-2 focus:ring-green-500
focus:ring-offset-2"> focus:ring-offset-2">
Submit Query Submit Query
</button> </button>
@@ -178,11 +178,11 @@
<div id="settingsModal" class="hidden fixed inset-0 bg-slate-900/50 backdrop-blur-sm flex items-center justify-center"> <div id="settingsModal" class="hidden fixed inset-0 bg-slate-900/50 backdrop-blur-sm flex items-center justify-center">
<div class="bg-white rounded-xl shadow-lg p-6 w-full max-w-md m-4"> <div class="bg-white rounded-xl shadow-lg p-6 w-full max-w-md m-4">
<h3 class="text-lg font-semibold text-slate-900 mb-4">Settings</h3> <h3 class="text-lg font-semibold text-slate-900 mb-4">Settings</h3>
<input type="text" id="apiKeyInput" <input type="text" id="apiKeyInput"
class="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500" class="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500"
placeholder="Enter API Key"> placeholder="Enter API Key">
<div class="mt-6 flex justify-end space-x-4"> <div class="mt-6 flex justify-end space-x-4">
<button id="closeSettingsBtn" <button id="closeSettingsBtn"
class="px-4 py-2 text-slate-700 hover:text-slate-900"> class="px-4 py-2 text-slate-700 hover:text-slate-900">
Cancel Cancel
</button> </button>
@@ -239,7 +239,7 @@
<div class="flex items-center justify-between p-3 bg-white rounded-lg border"> <div class="flex items-center justify-between p-3 bg-white rounded-lg border">
<div class="flex items-center"> <div class="flex items-center">
<svg class="w-5 h-5 text-slate-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-slate-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" /> d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg> </svg>
<span class="text-sm text-slate-600">${file.name}</span> <span class="text-sm text-slate-600">${file.name}</span>
@@ -247,8 +247,8 @@
</div> </div>
<button type="button" data-index="${index}" class="text-red-500 hover:text-red-700 p-1"> <button type="button" data-index="${index}" class="text-red-500 hover:text-red-700 p-1">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd" /> clip-rule="evenodd" />
</svg> </svg>
</button> </button>
@@ -290,7 +290,7 @@
dropZone.addEventListener('click', () => { dropZone.addEventListener('click', () => {
fileInput.click(); fileInput.click();
}); });
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => { dropZone.addEventListener(eventName, (e) => {
e.preventDefault(); e.preventDefault();
@@ -319,25 +319,25 @@
uploadForm.addEventListener('submit', async (e) => { uploadForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const files = fileInput.files; const files = fileInput.files;
if (files.length === 0) { if (files.length === 0) {
uploadStatus.innerHTML = '<span class="text-red-500">Please select files to upload</span>'; uploadStatus.innerHTML = '<span class="text-red-500">Please select files to upload</span>';
return; return;
} }
uploadProgress.classList.remove('hidden'); uploadProgress.classList.remove('hidden');
const progressBar = uploadProgress.querySelector('.bg-blue-600'); const progressBar = uploadProgress.querySelector('.bg-blue-600');
uploadStatus.textContent = 'Starting upload...'; uploadStatus.textContent = 'Starting upload...';
try { try {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
uploadStatus.textContent = `Uploading ${file.name} (${i + 1}/${files.length})...`; uploadStatus.textContent = `Uploading ${file.name} (${i + 1}/${files.length})...`;
console.log(`Uploading file: ${file.name}`); console.log(`Uploading file: ${file.name}`);
try { try {
const response = await fetch('/documents/upload', { const response = await fetch('/documents/upload', {
method: 'POST', method: 'POST',
@@ -346,30 +346,30 @@
}, },
body: formData body: formData
}); });
console.log('Response status:', response.status); console.log('Response status:', response.status);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
throw new Error(`Upload failed: ${errorData.detail || response.statusText}`); throw new Error(`Upload failed: ${errorData.detail || response.statusText}`);
} }
// Update progress // Update progress
const progress = ((i + 1) / files.length) * 100; const progress = ((i + 1) / files.length) * 100;
progressBar.style.width = `${progress}%`; progressBar.style.width = `${progress}%`;
console.log(`Progress: ${progress}%`); console.log(`Progress: ${progress}%`);
} catch (error) { } catch (error) {
console.error('Upload error:', error); console.error('Upload error:', error);
uploadStatus.innerHTML = `<span class="text-red-500">Error uploading ${file.name}: ${error.message}</span>`; uploadStatus.innerHTML = `<span class="text-red-500">Error uploading ${file.name}: ${error.message}</span>`;
return; return;
} }
} }
// All files uploaded successfully // All files uploaded successfully
uploadStatus.innerHTML = '<span class="text-green-500">All files uploaded successfully!</span>'; uploadStatus.innerHTML = '<span class="text-green-500">All files uploaded successfully!</span>';
progressBar.style.width = '100%'; progressBar.style.width = '100%';
// Clear the file input and selection display // Clear the file input and selection display
setTimeout(() => { setTimeout(() => {
fileInput.value = ''; fileInput.value = '';
@@ -377,7 +377,7 @@
uploadProgress.classList.add('hidden'); uploadProgress.classList.add('hidden');
progressBar.style.width = '0%'; progressBar.style.width = '0%';
}, 3000); }, 3000);
} catch (error) { } catch (error) {
console.error('General upload error:', error); console.error('General upload error:', error);
uploadStatus.innerHTML = `<span class="text-red-500">Upload failed: ${error.message}</span>`; uploadStatus.innerHTML = `<span class="text-red-500">Upload failed: ${error.message}</span>`;
@@ -391,20 +391,20 @@
button.className = 'copy-button absolute top-1 right-1 p-1 bg-slate-700/80 hover:bg-slate-700 text-white rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity'; button.className = 'copy-button absolute top-1 right-1 p-1 bg-slate-700/80 hover:bg-slate-700 text-white rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity';
button.innerHTML = ` button.innerHTML = `
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /> d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg> </svg>
`; `;
pre.style.position = 'relative'; pre.style.position = 'relative';
pre.classList.add('group'); pre.classList.add('group');
button.addEventListener('click', async () => { button.addEventListener('click', async () => {
const codeElement = pre.querySelector('code'); const codeElement = pre.querySelector('code');
if (!codeElement) return; if (!codeElement) return;
const text = codeElement.textContent; const text = codeElement.textContent;
try { try {
// First try using the Clipboard API // First try using the Clipboard API
if (navigator.clipboard && window.isSecureContext) { if (navigator.clipboard && window.isSecureContext) {
@@ -428,29 +428,29 @@
return; return;
} }
} }
// Show success feedback // Show success feedback
button.innerHTML = ` button.innerHTML = `
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg> </svg>
`; `;
// Reset button after 2 seconds // Reset button after 2 seconds
setTimeout(() => { setTimeout(() => {
button.innerHTML = ` button.innerHTML = `
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /> d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg> </svg>
`; `;
}, 2000); }, 2000);
} catch (err) { } catch (err) {
console.error('Copy failed:', err); console.error('Copy failed:', err);
} }
}); });
pre.appendChild(button); pre.appendChild(button);
} }
}); });
@@ -460,12 +460,12 @@
queryForm.addEventListener('submit', async (e) => { queryForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const query = queryInput.value.trim(); const query = queryInput.value.trim();
if (!query) { if (!query) {
queryResponse.innerHTML = '<p class="text-red-500">Please enter a query</p>'; queryResponse.innerHTML = '<p class="text-red-500">Please enter a query</p>';
return; return;
} }
// Show loading state // Show loading state
queryResponse.innerHTML = ` queryResponse.innerHTML = `
<div class="animate-pulse"> <div class="animate-pulse">
@@ -478,10 +478,10 @@
</div> </div>
</div> </div>
`; `;
try { try {
console.log('Sending query:', query); console.log('Sending query:', query);
const response = await fetch('/query', { const response = await fetch('/query', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -490,17 +490,17 @@
}, },
body: JSON.stringify({ query }) body: JSON.stringify({ query })
}); });
console.log('Response status:', response.status); console.log('Response status:', response.status);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
throw new Error(`Query failed: ${errorData.detail || response.statusText}`); throw new Error(`Query failed: ${errorData.detail || response.statusText}`);
} }
const data = await response.json(); const data = await response.json();
console.log('Query response:', data); console.log('Query response:', data);
// Format and display the response // Format and display the response
if (data.response) { if (data.response) {
const formattedResponse = marked.parse(data.response, { const formattedResponse = marked.parse(data.response, {
@@ -510,19 +510,19 @@
} }
return code; return code;
} }
}); });
queryResponse.innerHTML = ` queryResponse.innerHTML = `
<div class="prose prose-slate max-w-none"> <div class="prose prose-slate max-w-none">
${formattedResponse} ${formattedResponse}
</div> </div>
`; `;
// Re-trigger Prism highlighting // Re-trigger Prism highlighting
Prism.highlightAllUnder(queryResponse); Prism.highlightAllUnder(queryResponse);
} else { } else {
queryResponse.innerHTML = '<p class="text-slate-600">No response data received</p>'; queryResponse.innerHTML = '<p class="text-slate-600">No response data received</p>';
} }
// Call this after loading markdown content // Call this after loading markdown content
addCopyButtons(); addCopyButtons();
// Optional: Add sources if available // Optional: Add sources if available
@@ -534,7 +534,7 @@
${data.sources.map(source => ` ${data.sources.map(source => `
<li class="flex items-center space-x-2"> <li class="flex items-center space-x-2">
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
<span>${source}</span> <span>${source}</span>
@@ -545,7 +545,7 @@
`; `;
queryResponse.insertAdjacentHTML('beforeend', sourcesHtml); queryResponse.insertAdjacentHTML('beforeend', sourcesHtml);
} }
} catch (error) { } catch (error) {
console.error('Query error:', error); console.error('Query error:', error);
queryResponse.innerHTML = ` queryResponse.innerHTML = `
@@ -555,14 +555,14 @@
</div> </div>
`; `;
} }
// Optional: Add a copy button for the response // Optional: Add a copy button for the response
const copyButton = document.createElement('button'); const copyButton = document.createElement('button');
copyButton.className = 'mt-4 px-3 py-1 text-sm text-slate-600 hover:text-slate-800 border border-slate-300 rounded hover:bg-slate-50 transition-colors'; copyButton.className = 'mt-4 px-3 py-1 text-sm text-slate-600 hover:text-slate-800 border border-slate-300 rounded hover:bg-slate-50 transition-colors';
copyButton.innerHTML = ` copyButton.innerHTML = `
<span class="flex items-center space-x-1"> <span class="flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /> d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg> </svg>
<span>Copy Response</span> <span>Copy Response</span>
@@ -583,7 +583,7 @@
copyButton.innerHTML = ` copyButton.innerHTML = `
<span class="flex items-center space-x-1"> <span class="flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /> d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg> </svg>
<span>Copy Response</span> <span>Copy Response</span>