File size: 8,525 Bytes
e4c798e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import io
import os
import json
import logging
import secrets

import gradio as gr
import numpy as np
import openai
import pandas as pd
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload, MediaFileUpload
from openai.embeddings_utils import distances_from_embeddings

from .gpt_processor import QuestionAnswerer
from .work_flow_controller import WorkFlowController

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
openai.api_key = OPENAI_API_KEY


class Chatbot:
    def __init__(self):
        self.history = []
        self.upload_state = "waiting"
        self.uid = self.__generate_uid()

        self.g_drive_service = self.__init_drive_service()
        self.knowledge_base = None
        self.context = None
        self.context_page_num = None
        self.context_file_name = None

    def build_knowledge_base(self, files, upload_mode="once"):
        work_flow_controller = WorkFlowController(files, self.uid)
        self.csv_result_path = work_flow_controller.csv_result_path
        self.json_result_path = work_flow_controller.json_result_path

        if upload_mode == "Upload to Database":
            self.__get_db_knowledge_base()
        else:
            self.__get_local_knowledge_base()

    def __get_db_knowledge_base(self):
        filename = "knowledge_base.csv"
        db = self.__read_db(self.g_drive_service)
        cur_content = pd.read_csv(self.csv_result_path)
        for _ in range(10):
            try:
                self.__write_into_db(self.g_drive_service, db, cur_content)
                break
            except Exception as e:
                logging.error(e)
                logging.error("Failed to upload to database, retrying...")
                continue
        self.knowledge_base = db
        self.upload_state = "done"

    def __get_local_knowledge_base(self):
        with open(self.csv_result_path, "r", encoding="UTF-8") as fp:
            knowledge_base = pd.read_csv(fp)
        knowledge_base["page_embedding"] = (
            knowledge_base["page_embedding"].apply(eval).apply(np.array)
        )

        self.knowledge_base = knowledge_base
        self.upload_state = "done"

    def __write_into_db(self, service, db: pd.DataFrame, cur_content: pd.DataFrame):
        db = pd.concat([db, cur_content], ignore_index=True)
        db.to_csv(f"{self.uid}_knowledge_base.csv", index=False)
        media = MediaFileUpload(f"{self.uid}_knowledge_base.csv", resumable=True)
        request = (
            service.files()
            .update(fileId="1m3ozrphHP221hhdCFMFX9-10nzSDfNyW", media_body=media)
            .execute()
        )

    def __init_drive_service(self):
        SCOPES = ["https://www.googleapis.com/auth/drive"]
        SERVICE_ACCOUNT_INFO = os.getenv("CREDENTIALS")
        service_account_info_dict = json.loads(SERVICE_ACCOUNT_INFO)

        creds = Credentials.from_service_account_info(
            service_account_info_dict, scopes=SCOPES
        )

        return build("drive", "v3", credentials=creds)

    def __read_db(self, service):
        request = service.files().get_media(fileId="1m3ozrphHP221hhdCFMFX9-10nzSDfNyW")
        fh = io.BytesIO()
        downloader = MediaIoBaseDownload(fh, request)

        done = False
        while done is False:
            status, done = downloader.next_chunk()
            print(f"Download {int(status.progress() * 100)}%.")

        fh.seek(0)

        return pd.read_csv(fh)

    def __read_file(self, service, filename) -> pd.DataFrame:
        query = f"name='{filename}'"
        results = service.files().list(q=query).execute()
        files = results.get("files", [])

        file_id = files[0]["id"]

        request = service.files().get_media(fileId=file_id)
        fh = io.BytesIO()
        downloader = MediaIoBaseDownload(fh, request)

        done = False
        while done is False:
            status, done = downloader.next_chunk()
            print(f"Download {int(status.progress() * 100)}%.")

        fh.seek(0)

        return pd.read_csv(fh)

    def __upload_file(self, service):
        results = service.files().list(pageSize=10).execute()
        items = results.get("files", [])
        if not items:
            print("No files found.")
        else:
            print("Files:")
            for item in items:
                print(f"{item['name']} ({item['id']})")

        media = MediaFileUpload(self.csv_result_path, resumable=True)
        filename_prefix = "ex_bot_database_"
        filename = filename_prefix + self.uid + ".csv"
        request = (
            service.files()
            .create(
                media_body=media,
                body={
                    "name": filename,
                    "parents": [
                        "1Lp21EZlVlqL-c27VQBC6wTbUC1YpKMsG"
                    ],
                },
            )
            .execute()
        )

    def clear_state(self):
        self.context = None
        self.context_page_num = None
        self.context_file_name = None
        self.knowledge_base = None
        self.upload_state = "waiting"
        self.history = []

    def send_system_notification(self):
        if self.upload_state == "waiting":
            conversation = [["已上傳文件", "文件處理中(摘要、翻譯等),結束後將自動回覆"]]
            return conversation
        elif self.upload_state == "done":
            conversation = [["已上傳文件", "文件處理完成,請開始提問"]]
            return conversation

    def change_md(self):
        content = self.__construct_summary()
        return gr.Markdown.update(content, visible=True)

    def __construct_summary(self):
        with open(self.json_result_path, "r", encoding="UTF-8") as fp:
            knowledge_base = json.load(fp)

        context = ""
        for key in knowledge_base.keys():
            file_name = knowledge_base[key]["file_name"]
            total_page = knowledge_base[key]["total_pages"]
            summary = knowledge_base[key]["summarized_content"]
            file_context = f"""
                ### 文件摘要
                {file_name}  (共 {total_page} 頁)<br><br>
                {summary}<br><br>
            """
            context += file_context
        return context

    def user(self, message):
        self.history += [[message, None]]
        return "", self.history

    def bot(self):
        user_message = self.history[-1][0]
        print(f"user_message: {user_message}")

        if self.knowledge_base is None:
            response = [
                [user_message, "請先上傳文件"],
            ]
            self.history = response
            return self.history

        else:
            self.__get_index_file(user_message)
            if self.context is None:
                response = [
                    [user_message, "無法找到相關文件,請重新提問"],
                ]
                self.history = response
                return self.history
            else:
                qa_processor = QuestionAnswerer()
                bot_message = qa_processor.answer_question(
                    self.context,
                    self.context_page_num,
                    self.context_file_name,
                    self.history,
                )
                print(f"bot_message: {bot_message}")
                response = [
                    [user_message, bot_message],
                ]
                self.history[-1] = response[0]
                return self.history

    def __get_index_file(self, user_message):
        user_message_embedding = openai.Embedding.create(
            input=user_message, engine="text-embedding-ada-002"
        )["data"][0]["embedding"]

        self.knowledge_base["distance"] = distances_from_embeddings(
            user_message_embedding,
            self.knowledge_base["page_embedding"].values,
            distance_metric="cosine",
        )
        self.knowledge_base = self.knowledge_base.sort_values(
            by="distance", ascending=True
        )

        if self.knowledge_base["distance"].values[0] > 0.2:
            self.context = None
        else:
            self.context = self.knowledge_base["page_content"].values[0]
            self.context_page_num = self.knowledge_base["page_num"].values[0]
            self.context_file_name = self.knowledge_base["file_name"].values[0]

    def __generate_uid(self):
        return secrets.token_hex(8)