import gradio as gr
import time
import sys
from llama_index.llms.google_genai import GoogleGenAI
from llama_index.core import Settings
from config import *
from document_processor import *
from llama_index.core.chat_engine import CondensePlusContextChatEngine
query_engine = None
chunks_df = None
chat_engine = None
chat_history = []
def answer_question(question, history):
global query_engine, chat_engine
if query_engine is None:
return "
❌ System not initialized or document database is empty
", "", history
try:
start_time = time.time()
# Initialize chat engine if not exists
if chat_engine is None:
chat_engine = CondensePlusContextChatEngine.from_defaults(
retriever=query_engine.retriever,
response_synthesizer=query_engine.response_synthesizer
)
response = chat_engine.chat(question)
retrieved_nodes = query_engine.retriever.retrieve(question)
end_time = time.time()
processing_time = end_time - start_time
sources_html = generate_sources_html(retrieved_nodes)
answer_with_time = f"""
📋 Answer:
{response.response}
⏱️ Processing time: {processing_time:.2f} sec
"""
# Update chat history (keep last 6 messages - 3 exchanges)
new_history = history + [[question, response.response]]
if len(new_history) > 3:
new_history = new_history[-3:]
return answer_with_time, sources_html, new_history
except Exception as e:
error_msg = f"❌ Error processing question: {str(e)}
"
return error_msg, "", history
def generate_sources_html(nodes):
html = ""
html += "
📚 Sources:
"
unique_docs = {}
for node in nodes:
metadata = node.metadata if hasattr(node, 'metadata') else {}
doc_name = metadata.get('document_name', 'unknown')
doc_link = metadata.get('document_link', '')
doc_key = f"{doc_name}||{doc_link}"
if doc_key not in unique_docs:
unique_docs[doc_key] = []
unique_docs[doc_key].append(node)
for doc_key, doc_nodes in unique_docs.items():
doc_name, doc_link = doc_key.split('||', 1)
html += f"
"
if doc_link:
html += f"
"
else:
html += f"
📄 {doc_name}
"
html += f"
Found relevant fragments: {len(doc_nodes)}
"
html += "
"
html += "
"
return html
def get_documents_display():
documents = get_existing_documents()
if not documents:
return "No documents in system yet
"
html = f""
html += f"
📚 {len(documents)} documents in the system:
"
html += "
"
for i, doc_name in enumerate(documents, 1):
html += f"
"
html += f"{i}. {doc_name}"
html += "
"
html += "
"
return html
def upload_and_process_file(files, doc_names, doc_links):
global query_engine, chunks_df, chat_engine
if not files:
return "No files selected", get_documents_display()
if len(files) != len(doc_names) or len(files) != len(doc_links):
return "Error: Number of files must match number of document names and links", get_documents_display()
existing_docs = get_existing_documents()
results = []
for i, file in enumerate(files):
doc_name = doc_names[i].strip() if i < len(doc_names) else ""
doc_link = doc_links[i].strip() if i < len(doc_links) else ""
if not doc_name:
doc_name = file.name.split('/')[-1].replace('.txt', '').replace('.pdf', '')
# Check if document already exists
if doc_name in existing_docs:
results.append(f"⚠️ {doc_name}: Document already exists in the system")
continue
log_message(f"🔄 Starting processing of file {i+1}/{len(files)}: {file.name}")
file_info, error = process_uploaded_file(file.name, file.name.split('/')[-1], doc_name, doc_link)
if error:
results.append(f"❌ {file.name.split('/')[-1]}: {error}")
continue
query_engine, chunks_df, error = add_to_vector_index(file_info['chunks'], file_info, chunks_df)
if error:
results.append(f"❌ {file_info['file_name']}: Error adding to database - {error}")
else:
results.append(f"✅ {file_info['document']}: Successfully processed and added to database")
log_message(f"✅ Completed processing: {file_info['document']}")
# Reset chat engine to include new documents
chat_engine = None
return "\n".join(results), get_documents_display()
def create_interface():
with gr.Blocks(title="AIEXP - AI Expert for Regulatory Documentation", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# AIEXP - Artificial Intelligence Expert
## Tool for working with regulatory documentation
""")
with gr.Tab("🔍 Document Search"):
gr.Markdown("### Ask a question about the uploaded documentation")
with gr.Row():
with gr.Column(scale=3):
chatbot = gr.Chatbot(
label="Chat History",
height=400,
show_label=True
)
question_input = gr.Textbox(
label="Your question to the knowledge base",
placeholder="Enter your question about the documents...",
lines=3
)
ask_btn = gr.Button("🔍 Find Answer", variant="primary", size="lg")
clear_btn = gr.Button("🗑️ Clear History", variant="secondary")
gr.Examples(
examples=[
"Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
"Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
"В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?",
],
inputs=question_input
)
with gr.Column(scale=1):
answer_output = gr.HTML(
label="",
value="The answer to your question will appear here...
",
)
sources_output = gr.HTML(
label="",
value="Sources will appear here...
",
)
ask_btn.click(
fn=answer_question,
inputs=[question_input, chatbot],
outputs=[answer_output, sources_output, chatbot]
).then(
lambda: "", inputs=None, outputs=question_input
)
question_input.submit(
fn=answer_question,
inputs=[question_input, chatbot],
outputs=[answer_output, sources_output, chatbot]
).then(
lambda: "", inputs=None, outputs=question_input
)
clear_btn.click(
lambda: [], inputs=None, outputs=chatbot
)
with gr.Tab("📚 Document Management"):
gr.Markdown("### Document database and adding new files")
with gr.Row():
with gr.Column(scale=2):
documents_display = gr.HTML(
label="Document list",
value=get_documents_display()
)
refresh_btn = gr.Button("🔄 Refresh List", variant="secondary")
with gr.Column(scale=1, elem_id="upload-column"):
gr.Markdown("#### Upload new documents", elem_classes=["upload-header"])
gr.Markdown("Supported formats: PDF, TXT", elem_classes=["upload-info"])
file_upload = gr.File(
file_count="multiple",
file_types=[".pdf", ".txt"],
label="Select files to upload",
elem_classes=["upload-file"]
)
doc_names_input = gr.Textbox(
label="Document names (one per line)",
placeholder="Enter document names, one per line...",
lines=5,
elem_classes=["upload-input"]
)
doc_links_input = gr.Textbox(
label="Document links (one per line)",
placeholder="Enter document links, one per line...",
lines=5,
elem_classes=["upload-input"]
)
upload_btn = gr.Button("📤 Upload and Process", variant="primary", elem_classes=["upload-btn"])
upload_status = gr.Textbox(
label="Upload status",
lines=8,
max_lines=10,
interactive=False,
elem_classes=["upload-status"]
)
def process_names_and_links(names_text, links_text):
names = [name.strip() for name in names_text.split('\n') if name.strip()]
links = [link.strip() for link in links_text.split('\n') if link.strip()]
return names, links
upload_btn.click(
fn=lambda files, names, links: upload_and_process_file(
files,
*process_names_and_links(names, links)
),
inputs=[file_upload, doc_names_input, doc_links_input],
outputs=[upload_status, documents_display]
)
refresh_btn.click(
fn=lambda: get_documents_display(),
outputs=[documents_display]
)
# Add CSS to fix white background in upload tab
demo.css = """
#upload-column {
background-color: #f8f9fa !important;
padding: 20px !important;
border-radius: 10px !important;
border: 1px solid #e9ecef !important;
}
.upload-header h4 {
color: #2d3748 !important;
margin-bottom: 10px !important;
}
.upload-info {
color: #666 !important;
margin-bottom: 15px !important;
}
.upload-file, .upload-input, .upload-status {
background-color: white !important;
border: 1px solid #ced4da !important;
border-radius: 5px !important;
}
.upload-btn {
margin-top: 10px !important;
}
"""
return demo