mirror of
https://gh.wpcy.net/https://github.com/Godfrey2712/freescout_ai_faq.git
synced 2026-04-18 06:12:28 +08:00
307 lines
13 KiB
Python
307 lines
13 KiB
Python
from flask import Flask, render_template, request, jsonify
|
|
from bs4 import BeautifulSoup
|
|
import openai
|
|
import requests
|
|
import re
|
|
import tempfile
|
|
import os
|
|
|
|
# Initialize Flask app
|
|
app = Flask(__name__)
|
|
|
|
# Define the api key for open-ai here
|
|
# Get the API key from the env file
|
|
api_key = None
|
|
api_key = os.getenv('api_key')
|
|
|
|
#Define the api key for freescout here
|
|
free_scout_api_key = None
|
|
free_scout_api_key = os.getenv('freeScout_api_key')
|
|
|
|
# Global variable to store the temp file_path
|
|
file_path = None
|
|
|
|
# This is the part to update the model
|
|
# The default parameters for the open-ai api
|
|
openai_parameters = {
|
|
"model": "gpt-4",
|
|
"temperature": 0.3,
|
|
"max_tokens": 2000
|
|
}
|
|
|
|
# Initialize the AI response, customer messages, and support responses
|
|
gpt_support_faq = []
|
|
support_messages_raw = 0
|
|
customer_messages_raw = 0
|
|
|
|
# Define a global variable to track the index of the next message to return
|
|
global support_messages_index
|
|
support_messages_index = 0
|
|
global customer_messages_index
|
|
customer_messages_index = 0
|
|
|
|
# Route to the html
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
# Route to the main function which calls other functions
|
|
@app.route('/run_function', methods=['POST'])
|
|
def run_function():
|
|
global file_path
|
|
global free_scout_api_key
|
|
|
|
# Get the FreeScout API key from the form input
|
|
if free_scout_api_key is None:
|
|
free_scout_api_key = request.form.get('freeScoutApiKey')
|
|
|
|
# Get values from frontend for mailbox id and page size
|
|
mailbox_id = request.form.get('mailboxId')
|
|
page_size = int(request.form.get('pageSize'))
|
|
|
|
# Extract the flag value from the request
|
|
continue_despite_mismatch = request.form.get('continueDespiteMismatch')
|
|
# Convert to boolean
|
|
continue_despite_mismatch = continue_despite_mismatch == 'true'
|
|
|
|
# Freescout udp url with variables
|
|
url = f"https://helpdesk.team.com/api/conversations?embed=threads&mailboxId={mailbox_id}&pageSize={page_size}"
|
|
headers = {
|
|
# Freescout API (To generate a new one, speak to system administrator)
|
|
"X-FreeScout-API-Key": free_scout_api_key,
|
|
}
|
|
|
|
params = {
|
|
"embed": "threads",
|
|
}
|
|
response = requests.get(url, headers=headers, params=params)
|
|
|
|
# Data extraction fix here
|
|
# Loop for data extractions from freescout
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
if "_embedded" in data and "conversations" in data["_embedded"]:
|
|
# Initialize counts for message types
|
|
message_counts = {"customer": 0, "message": 0}
|
|
with tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding="utf-8") as knowledge_file:
|
|
for conversation in data["_embedded"]["conversations"]:
|
|
if "threadsCount" in conversation and conversation["threadsCount"] > 0:
|
|
threads = conversation.get("_embedded", {}).get("threads", [])
|
|
if threads:
|
|
for thread in threads:
|
|
customer_messages = [] # To store body values with type customer
|
|
support_responses = [] # To store body values with type message
|
|
|
|
if thread["type"] == "customer":
|
|
customer_messages.append(strip_name_and_email_from_body(thread['body']))
|
|
elif thread["type"] == "message":
|
|
message_counts["message"] += 1
|
|
support_responses.append(strip_name_and_email_from_body(thread['body']))
|
|
|
|
# Skip the thread if both customer_messages and support_responses are empty
|
|
if not customer_messages and not support_responses:
|
|
continue
|
|
|
|
# Write customer messages to the file if not empty
|
|
if customer_messages:
|
|
knowledge_file.write("Customer Message: ")
|
|
for body in customer_messages:
|
|
knowledge_file.write(body + "\n")
|
|
|
|
# Write support responses to the file if not empty
|
|
if support_responses:
|
|
knowledge_file.write("Support Response: ")
|
|
for body in support_responses:
|
|
knowledge_file.write(body + "\n")
|
|
|
|
knowledge_file.write("=" * 30 + "\n")
|
|
# Check if the requested number of FAQs matches the actual number of support responses
|
|
requested_faqs = page_size
|
|
actual_responses = message_counts["message"]
|
|
if requested_faqs != actual_responses and not continue_despite_mismatch:
|
|
return jsonify({'status': 'error', 'message': f'The requested number of FAQs ({requested_faqs}) does not match the actual number of support responses of ({actual_responses}) in the threads'})
|
|
# Assign file_path to the global variable
|
|
file_path = knowledge_file.name
|
|
|
|
summarize_from_file(file_path)
|
|
|
|
else:
|
|
print("No conversations found.")
|
|
else:
|
|
print(f"Failed to retrieve conversations. Status code: {response.status_code}")
|
|
print(response.text)
|
|
|
|
return "Function executed successfully!"
|
|
|
|
# Route to get the results
|
|
@app.route('/results')
|
|
def fetch_results():
|
|
global gpt_support_faq
|
|
global support_messages_raw
|
|
global support_messages_index
|
|
global customer_messages_raw
|
|
global customer_messages_index
|
|
|
|
if gpt_support_faq and (support_messages_raw or customer_messages_raw):
|
|
# Check if there are messages remaining to be returned
|
|
if support_messages_index < len(support_messages_raw):
|
|
# Get the next message from support_messages_raw
|
|
next_support_message = support_messages_raw[support_messages_index]
|
|
next_customer_message = customer_messages_raw[customer_messages_index]
|
|
# Increment the index for the next call
|
|
support_messages_index += 1
|
|
customer_messages_index += 1
|
|
|
|
# Check if there are messages remaining in gpt_support_faq
|
|
if gpt_support_faq:
|
|
next_gpt_message = gpt_support_faq[0] # Get the next message
|
|
gpt_support_faq = gpt_support_faq[1:] # Remove the first message from the list
|
|
else:
|
|
next_gpt_message = "No more AI-generated messages available."
|
|
|
|
# Manage the response from the request
|
|
response_data = {
|
|
"status": "success",
|
|
"messages": next_gpt_message + "<br><br>",
|
|
"original_messages": "<b>ORIGINAL MESSAGE:</b><br><br>" + next_customer_message + "<br> <br>" + "<b>ORIGINAL RESPONSE:</b><br><br>" + next_support_message + "\n" "<br> <br>" + "=" * 60 + "<br> <br>"
|
|
}
|
|
return jsonify(response_data)
|
|
else:
|
|
return jsonify({"status": "success", "message": "No more support messages available."})
|
|
else:
|
|
return jsonify({"status": "error", "message": "No support messages found."})
|
|
|
|
# Clean freescout data
|
|
def extract_text_from_html(html):
|
|
if html is not None:
|
|
soup = BeautifulSoup(html, 'html.parser')
|
|
return soup.get_text()
|
|
return ""
|
|
|
|
# Strip personal details off
|
|
def strip_name_and_email_from_body(body):
|
|
if body is not None:
|
|
# Define regular expressions to match name, email, URL, and address patterns
|
|
name_pattern = re.compile(r"Your Name:(.*?)(\n|$)")
|
|
email_pattern = re.compile(r"Email:(.*?)(\n|$)")
|
|
email_pattern_2 = re.compile(r"email address:(.*?)(\n|$)")
|
|
email_pattern_3 = re.compile(r"E-mail:(.*?)(\n|$)")
|
|
url_pattern = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
|
|
address_pattern = re.compile(r"\b\d{1,5}\s+[\w\s]+(?:\s+\w+){1,2}\b")
|
|
|
|
# Replace matched patterns with an empty string
|
|
body_without_name = name_pattern.sub('', body)
|
|
body_without_email = email_pattern.sub('', body_without_name)
|
|
body_without_email_2 = email_pattern_2.sub('', body_without_email)
|
|
body_without_email_3 = email_pattern_3.sub('', body_without_email_2)
|
|
body_without_url = url_pattern.sub('', body_without_email_3)
|
|
body_without_address = address_pattern.sub('', body_without_url)
|
|
|
|
# Extract text from HTML
|
|
cleaned_body = extract_text_from_html(body_without_address)
|
|
# Remove words containing "@" (emails)
|
|
cleaned_body = ' '.join([word for word in cleaned_body.split() if '@' not in word])
|
|
# Remove words containing "+" (emails)
|
|
cleaned_body = ' '.join([word for word in cleaned_body.split() if '+' not in word])
|
|
|
|
return cleaned_body
|
|
|
|
return ""
|
|
|
|
# AI prompt for generating the FAQ from freescout
|
|
def summarize(text, is_customer_message=True):
|
|
global api_key
|
|
# If API key is not provided from the environment variable
|
|
if api_key is None:
|
|
# Get the API key from environment variables
|
|
api_key = request.form.get('apiKey')
|
|
openai.api_key = api_key
|
|
|
|
if is_customer_message:
|
|
instruction = f"If it is a customer message, summarize it into an FAQ topic while retaining context: {text}"
|
|
role = "user"
|
|
else:
|
|
instruction = f"""
|
|
From {text}, generate one meaningful FAQ topic and one answer to the FAQ.
|
|
Seperate them with one line.
|
|
"""
|
|
role = "assistant"
|
|
|
|
# Paramaterizing the open-ai variables
|
|
prompt = {"role": role, "content": instruction}
|
|
response = openai.ChatCompletion.create(
|
|
model=openai_parameters["model"],
|
|
messages=[prompt],
|
|
temperature=openai_parameters["temperature"],
|
|
max_tokens=openai_parameters["max_tokens"]
|
|
)
|
|
return response.choices[0].message['content'].strip().replace('\"', '')
|
|
|
|
# Parse the freescout data to the model for inference
|
|
def summarize_from_file(file_path):
|
|
with open(file_path, 'r') as file:
|
|
# Split the content by threads, excluding the first and last split parts which are not actual threads
|
|
content = file.read()
|
|
threads = content.split("=" * 30 + "\n")[1:-1]
|
|
|
|
# Initialize lists to hold the concatenated customer requests and support messages
|
|
customer_requests = []
|
|
support_messages = []
|
|
|
|
for thread in threads:
|
|
# Initialize variables to hold concatenated messages for the current thread
|
|
customer_message = ""
|
|
support_response = ""
|
|
|
|
# Process each line in the thread
|
|
thread_lines = thread.strip().split('\n')
|
|
for line in thread_lines:
|
|
if ": " not in line:
|
|
continue # Skip lines that don't contain the key-value separator
|
|
|
|
key, value = line.split(": ", 1)
|
|
value = value.strip()
|
|
|
|
# Concatenate messages based on their type
|
|
if key == "Customer Message":
|
|
# Concatenate with a newline if customer_message is not empty
|
|
customer_message += ("\n" "<br>" if customer_message else "") + value
|
|
elif key == "Support Response":
|
|
# Similarly, concatenate with a newline for support_response
|
|
support_response += ("\n" "<br>" if support_response else "") + value
|
|
|
|
# After processing each thread, append the concatenated messages to their respective lists
|
|
if customer_message:
|
|
customer_requests.append(customer_message)
|
|
if support_response:
|
|
support_messages.append(support_response)
|
|
|
|
# The data file will be used as the chatbot knowledge file
|
|
with tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding="utf-8") as temp_categorized_file:
|
|
global gpt_support_faq
|
|
for i, (customer_request, support_message) in enumerate(zip(customer_requests, support_messages)):
|
|
# Skip summarizing the customer request
|
|
# Summarize support message only
|
|
summarized_support_message = summarize(support_message, is_customer_message=False)
|
|
|
|
# Write to temp file
|
|
temp_categorized_file.write(f"{i+1}. {summarized_support_message}\n")
|
|
temp_categorized_file.write("=" * 100 + "\n")
|
|
|
|
# Storing the last summarized message globally if needed
|
|
# Append new summary to the global list
|
|
gpt_support_faq.append(f"{i+1}. {summarized_support_message}")
|
|
print (gpt_support_faq)
|
|
|
|
global support_messages_raw
|
|
support_messages_raw = [] # Initialize as an empty list
|
|
for i, message in enumerate(support_messages, start=1):
|
|
support_messages_raw.append(f"{i}. {message}") # Append each message along with its index
|
|
|
|
global customer_messages_raw
|
|
customer_messages_raw = [] # Initialize as an empty list
|
|
for i, message in enumerate(customer_requests, start=1):
|
|
customer_messages_raw.append(f"{i}. {message}") # Append each message along with its index
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=5000)
|