File size: 8,060 Bytes
e764d84
 
 
 
 
 
03000c3
 
 
 
 
 
 
 
 
 
 
 
e764d84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03000c3
e764d84
 
 
 
 
 
 
 
03000c3
 
e764d84
 
acc276b
e764d84
 
 
 
 
03000c3
 
e764d84
 
03000c3
 
e764d84
 
03000c3
e764d84
03000c3
e764d84
03000c3
 
e764d84
 
 
 
03000c3
e764d84
 
03000c3
e764d84
 
 
acc276b
03000c3
e764d84
 
 
 
 
 
 
 
 
 
 
03000c3
 
e764d84
 
 
 
 
03000c3
e764d84
03000c3
e764d84
 
03000c3
e764d84
 
 
 
 
 
 
 
 
 
 
 
03000c3
 
e764d84
 
03000c3
e764d84
 
 
 
 
03000c3
e764d84
 
03000c3
 
e764d84
 
 
 
 
03000c3
e764d84
 
03000c3
e764d84
 
acc276b
03000c3
e764d84
 
 
 
03000c3
 
e764d84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03000c3
e764d84
 
 
 
efcdc30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import logging
import gradio as gr
from transformers import pipeline
from sentence_transformers import SentenceTransformer, util
import PyPDF2

# Set up logging with a dedicated file handler
logger = logging.getLogger('SupportBot')
logger.setLevel(logging.INFO)
# Remove any existing handlers to avoid conflicts
if logger.handlers:
    logger.handlers.clear()
# Create a file handler with append mode
handler = logging.FileHandler('support_bot_log.txt', mode='a')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

# Load models
qa_model = pipeline("question-answering", model="distilbert-base-uncased-distilled-squad")
embedder = SentenceTransformer('all-MiniLM-L6-v2')

# Helper function to extract text from PDF
def extract_text_from_pdf(file_path):
    text = ""
    with open(file_path, "rb") as file:
        pdf_reader = PyPDF2.PdfReader(file)
        for page in pdf_reader.pages:
            text += page.extract_text() + "\n"
    return text

# Find the most relevant section in the document
def find_relevant_section(query, sections, section_embeddings):
    stopwords = {"and", "the", "is", "for", "to", "a", "an", "of", "in", "on", "at", "with", "by", "it", "as", "so", "what"}
    
    logger.info(f"Searching for relevant section for query: {query}")
    query_embedding = embedder.encode(query, convert_to_tensor=True)
    similarities = util.cos_sim(query_embedding, section_embeddings)[0]
    best_idx = similarities.argmax().item()
    best_section = sections[best_idx]
    similarity_score = similarities[best_idx].item()
    
    SIMILARITY_THRESHOLD = 0.4
    if similarity_score >= SIMILARITY_THRESHOLD:
        logger.info(f"Found relevant section using embeddings (score: {similarity_score})")
        handler.flush()  # Ensure log is written immediately
        return best_section
    
    logger.info(f"Low similarity ({similarity_score}). Falling back to keyword search.")
    query_words = {word for word in query.lower().split() if word not in stopwords}
    for section in sections:
        section_words = {word for word in section.lower().split() if word not in stopwords}
        common_words = query_words.intersection(section_words)
        if len(common_words) >= 2:
            logger.info(f"Keyword match found with common words: {common_words}")
            handler.flush()
            return section
    
    logger.info("No good match found. Returning default response.")
    handler.flush()
    return "I don’t have enough information to answer that."

# Process the uploaded file
def process_file(file, state):
    logger.info("Received file upload request")
    if file is None:
        logger.info("No file uploaded")
        handler.flush()
        return [("Bot", "Please upload a file.")], state
    
    file_path = file.name
    if file_path.lower().endswith(".pdf"):
        logger.info(f"Processing PDF file: {file_path}")
        text = extract_text_from_pdf(file_path)
    elif file_path.lower().endswith(".txt"):
        logger.info(f"Processing TXT file: {file_path}")
        with open(file_path, 'r', encoding='utf-8') as f:
            text = f.read()
    else:
        logger.error(f"Unsupported file format: {file_path}")
        handler.flush()
        return [("Bot", "Unsupported file format. Please upload a PDF or TXT file.")], state
    
    sections = text.split('\n\n')
    section_embeddings = embedder.encode(sections, convert_to_tensor=True)
    state['document_text'] = text
    state['sections'] = sections
    state['section_embeddings'] = section_embeddings
    state['current_query'] = None
    state['feedback_count'] = 0
    state['mode'] = 'waiting_for_query'
    state['chat_history'] = [("Bot", "File processed. You can now ask questions.")]
    logger.info(f"File processed successfully: {file_path}")
    handler.flush()
    return state['chat_history'], state

# Handle user input (queries and feedback)
def handle_input(user_input, state):
    if state['mode'] == 'waiting_for_upload':
        logger.info("User input received before file upload")
        state['chat_history'].append(("Bot", "Please upload a file first."))
        handler.flush()
    elif state['mode'] == 'waiting_for_query':
        query = user_input
        logger.info(f"User query: {query}")
        state['current_query'] = query
        state['feedback_count'] = 0
        context = find_relevant_section(query, state['sections'], state['section_embeddings'])
        if context == "I don’t have enough information to answer that.":
            answer = context
        else:
            result = qa_model(question=query, context=context)
            answer = result["answer"]
        state['last_answer'] = answer
        state['mode'] = 'waiting_for_feedback'
        state['chat_history'].append(("User", query))
        state['chat_history'].append(("Bot", f"Answer: {answer}\nPlease provide feedback: good, too vague, not helpful."))
        logger.info(f"Generated answer: {answer}")
        handler.flush()
    elif state['mode'] == 'waiting_for_feedback':
        feedback = user_input.lower()
        logger.info(f"User feedback: {feedback}")
        state['chat_history'].append(("User", feedback))
        if feedback == "good" or state['feedback_count'] >= 2:
            state['mode'] = 'waiting_for_query'
            if feedback == "good":
                state['chat_history'].append(("Bot", "Thank you for your feedback. You can ask another question."))
                logger.info("Feedback 'good' received. Ready for next query.")
            else:
                state['chat_history'].append(("Bot", "Maximum feedback iterations reached. You can ask another question."))
                logger.info("Max feedback iterations (2) reached. Ready for next query.")
            handler.flush()
        else:
            query = state['current_query']
            context = find_relevant_section(query, state['sections'], state['section_embeddings'])
            if feedback == "too vague":
                adjusted_answer = f"{state['last_answer']}\n\n(More details:\n{context[:500]}...)"
                logger.info("Feedback 'too vague'. Providing context.")
            elif feedback == "not helpful":
                adjusted_answer = qa_model(question=query + " Please provide more detailed information with examples.", context=context)['answer']
                logger.info("Feedback 'not helpful'. Re-searching with modified query.")
            else:
                state['chat_history'].append(("Bot", "Please provide valid feedback: good, too vague, not helpful."))
                logger.info(f"Invalid feedback received: {feedback}")
                handler.flush()
                return state['chat_history'], state
            state['last_answer'] = adjusted_answer
            state['feedback_count'] += 1
            state['chat_history'].append(("Bot", f"Updated answer: {adjusted_answer}\nPlease provide feedback: good, too vague, not helpful."))
            logger.info(f"Updated answer: {adjusted_answer}")
            handler.flush()
    return state['chat_history'], state

# Initial state
initial_state = {
    'document_text': None,
    'sections': None,
    'section_embeddings': None,
    'current_query': None,
    'feedback_count': 0,
    'mode': 'waiting_for_upload',
    'chat_history': [("Bot", "Please upload a PDF or TXT file to start.")],
    'last_answer': None
}

# Gradio interface
with gr.Blocks() as demo:
    state = gr.State(initial_state)
    file_upload = gr.File(label="Upload PDF or TXT file")
    chat = gr.Chatbot()
    user_input = gr.Textbox(label="Your query or feedback")
    submit_btn = gr.Button("Submit")
    log_file = gr.File(label="Download Log File", value="support_bot_log.txt")

    file_upload.upload(process_file, inputs=[file_upload, state], outputs=[chat, state])
    submit_btn.click(handle_input, inputs=[user_input, state], outputs=[chat, state]).then(lambda: "", None, user_input)

demo.launch(share=True)