Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
9 ariadna 1
var questionString = 'Ask a question...'
2
var errorString = 'An error occurred! Please try again later.'
3
 
4
export const init = (data) => {
5
 
6
    const blockId = data['blockId']
7
    const api_type = data['api_type']
8
    const persistConvo = data['persistConvo']
9
 
10
    // Initialize local data storage if necessary
11
    // If a thread ID exists for this block, make an API request to get existing messages
12
    if (api_type === 'assistant') {
13
        chatData = localStorage.getItem("block_openai_chat_data")
14
        if (chatData) {
15
            chatData = JSON.parse(chatData)
16
            if (chatData[blockId] && chatData[blockId]['threadId'] && persistConvo === "1") {
17
                fetch(`${M.cfg.wwwroot}/blocks/openai_chat/api/thread.php?thread_id=${chatData[blockId]['threadId']}`)
18
                .then(response => response.json())
19
                .then(data => {
20
                    for (let message of data) {
21
                        addToChatLog(message.role === 'user' ? 'user' : 'bot', message.message)
22
                    }
23
                })
24
                // Some sort of error in the API call. Probably the thread no longer exists, so lets reset it
25
                .catch(error => {
26
                    chatData[blockId] = {}
27
                    localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
28
                })
29
            // The block ID doesn't exist in the chat data object, so let's create it
30
            } else {
31
                chatData[blockId] = {}
32
            }
33
        // We don't even have a chat data object, so we'll create one
34
        } else {
35
            chatData = {[blockId]: {}}
36
        }
37
        localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
38
    }
39
 
40
    // Prevent sidebar from closing when osk pops up (hack for MDL-77957)
41
    window.addEventListener('resize', event => {
42
        event.stopImmediatePropagation();
43
    }, true);
44
 
45
    document.querySelector('#openai_input').addEventListener('keyup', e => {
46
        if (e.which === 13 && e.target.value !== "") {
47
            addToChatLog('user', e.target.value)
48
            createCompletion(e.target.value, blockId, api_type)
49
            e.target.value = ''
50
        }
51
    })
52
    document.querySelector('.block_openai_chat #go').addEventListener('click', e => {
53
        const input = document.querySelector('#openai_input')
54
        if (input.value !== "") {
55
            addToChatLog('user', input.value)
56
            createCompletion(input.value, blockId, api_type)
57
            input.value = ''
58
        }
59
    })
60
 
61
    document.querySelector('.block_openai_chat #refresh').addEventListener('click', e => {
62
        clearHistory(blockId)
63
    })
64
 
65
    document.querySelector('.block_openai_chat #popout').addEventListener('click', e => {
66
        if (document.querySelector('.drawer.drawer-right')) {
67
            document.querySelector('.drawer.drawer-right').style.zIndex = '1041'
68
        }
69
        document.querySelector('.block_openai_chat').classList.toggle('expanded')
70
    })
71
 
72
    require(['core/str'], function(str) {
73
        var strings = [
74
            {
75
                key: 'askaquestion',
76
                component: 'block_openai_chat'
77
            },
78
            {
79
                key: 'erroroccurred',
80
                component: 'block_openai_chat'
81
            },
82
        ];
83
        str.get_strings(strings).then((results) => {
84
            questionString = results[0];
85
            errorString = results[1];
86
        });
87
    });
88
}
89
 
90
/**
91
 * Add a message to the chat UI
92
 * @param {string} type Which side of the UI the message should be on. Can be "user" or "bot"
93
 * @param {string} message The text of the message to add
94
 */
95
const addToChatLog = (type, message) => {
96
    let messageContainer = document.querySelector('#openai_chat_log')
97
 
98
    const messageElem = document.createElement('div')
99
    messageElem.classList.add('openai_message')
100
    for (let className of type.split(' ')) {
101
        messageElem.classList.add(className)
102
    }
103
 
104
    const messageText = document.createElement('span')
105
    messageText.innerHTML = message
106
    messageElem.append(messageText)
107
 
108
    messageContainer.append(messageElem)
109
    if (messageText.offsetWidth) {
110
        messageElem.style.width = (messageText.offsetWidth + 40) + "px"
111
    }
112
    messageContainer.scrollTop = messageContainer.scrollHeight
113
    messageContainer.closest('.block_openai_chat > div').scrollTop = messageContainer.scrollHeight
114
}
115
 
116
/**
117
 * Clears the thread ID from local storage and removes the messages from the UI in order to refresh the chat
118
 */
119
const clearHistory = (blockId) => {
120
    chatData = localStorage.getItem("block_openai_chat_data")
121
    if (chatData) {
122
        chatData = JSON.parse(chatData)
123
        if (chatData[blockId]) {
124
            chatData[blockId] = {}
125
            localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
126
        }
127
    }
128
    document.querySelector('#openai_chat_log').innerHTML = ""
129
}
130
 
131
/**
132
 * Makes an API request to get a completion from GPT-3, and adds it to the chat log
133
 * @param {string} message The text to get a completion for
134
 * @param {int} blockId The ID of the block this message is being sent from -- used to override settings if necessary
135
 * @param {string} api_type "assistant" | "chat" The type of API to use
136
 */
137
const createCompletion = (message, blockId, api_type) => {
138
    let threadId = null
139
    let chatData
140
 
141
    // If the type is assistant, attempt to fetch a thread ID
142
    if (api_type === 'assistant') {
143
        chatData = localStorage.getItem("block_openai_chat_data")
144
        if (chatData) {
145
            chatData = JSON.parse(chatData)
146
            if (chatData[blockId]) {
147
                threadId = chatData[blockId]['threadId'] || null
148
            }
149
        } else {
150
            // create the chat data item if necessary
151
            chatData = {[blockId]: {}}
152
        }
153
    }
154
 
155
    const history = buildTranscript()
156
 
157
    document.querySelector('.block_openai_chat #control_bar').classList.add('disabled')
158
    document.querySelector('#openai_input').classList.remove('error')
159
    document.querySelector('#openai_input').placeholder = questionString
160
    document.querySelector('#openai_input').blur()
161
    addToChatLog('bot loading', '...');
162
 
163
    fetch(`${M.cfg.wwwroot}/blocks/openai_chat/api/completion.php`, {
164
        method: 'POST',
165
        body: JSON.stringify({
166
            message: message,
167
            history: history,
168
            blockId: blockId,
169
            threadId: threadId
170
        })
171
    })
172
    .then(response => {
173
        let messageContainer = document.querySelector('#openai_chat_log')
174
        messageContainer.removeChild(messageContainer.lastElementChild)
175
        document.querySelector('.block_openai_chat #control_bar').classList.remove('disabled')
176
 
177
        if (!response.ok) {
178
            throw Error(response.statusText)
179
        } else {
180
            return response.json()
181
        }
182
    })
183
    .then(data => {
184
        try {
185
            addToChatLog('bot', data.message)
186
            if (data.thread_id) {
187
                chatData[blockId]['threadId'] = data.thread_id
188
                localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
189
            }
190
        } catch (error) {
191
            console.log(error)
192
            addToChatLog('bot', data.error.message)
193
        }
194
        document.querySelector('#openai_input').focus()
195
    })
196
    .catch(error => {
197
        console.log(error)
198
        document.querySelector('#openai_input').classList.add('error')
199
        document.querySelector('#openai_input').placeholder = errorString
200
    })
201
}
202
 
203
/**
204
 * Using the existing messages in the chat history, create a string that can be used to aid completion
205
 * @return {JSONObject} A transcript of the conversation up to this point
206
 */
207
const buildTranscript = () => {
208
    let transcript = []
209
    document.querySelectorAll('.openai_message').forEach((message, index) => {
210
        if (index === document.querySelectorAll('.openai_message').length - 1) {
211
            return
212
        }
213
 
214
        let user = userName
215
        if (message.classList.contains('bot')) {
216
            user = assistantName
217
        }
218
        transcript.push({"user": user, "message": message.innerText})
219
    })
220
 
221
    return transcript
222
}