Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

var questionString = 'Ask a question...'
var errorString = 'An error occurred! Please try again later.'

export const init = (data) => {

    const blockId = data['blockId']
    const api_type = data['api_type']
    const persistConvo = data['persistConvo']

    // Initialize local data storage if necessary
    // If a thread ID exists for this block, make an API request to get existing messages
    if (api_type === 'assistant') {
        chatData = localStorage.getItem("block_openai_chat_data")
        if (chatData) {
            chatData = JSON.parse(chatData)
            if (chatData[blockId] && chatData[blockId]['threadId'] && persistConvo === "1") {
                fetch(`${M.cfg.wwwroot}/blocks/openai_chat/api/thread.php?thread_id=${chatData[blockId]['threadId']}`)
                .then(response => response.json())
                .then(data => {
                    for (let message of data) {
                        addToChatLog(message.role === 'user' ? 'user' : 'bot', message.message)
                    }
                })
                // Some sort of error in the API call. Probably the thread no longer exists, so lets reset it
                .catch(error => {
                    chatData[blockId] = {}
                    localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
                })
            // The block ID doesn't exist in the chat data object, so let's create it
            } else {
                chatData[blockId] = {}
            }
        // We don't even have a chat data object, so we'll create one
        } else {
            chatData = {[blockId]: {}}
        }
        localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
    }

    // Prevent sidebar from closing when osk pops up (hack for MDL-77957)
    window.addEventListener('resize', event => {
        event.stopImmediatePropagation();
    }, true);

    document.querySelector('#openai_input').addEventListener('keyup', e => {
        if (e.which === 13 && e.target.value !== "") {
            addToChatLog('user', e.target.value)
            createCompletion(e.target.value, blockId, api_type)
            e.target.value = ''
        }
    })
    document.querySelector('.block_openai_chat #go').addEventListener('click', e => {
        const input = document.querySelector('#openai_input')
        if (input.value !== "") {
            addToChatLog('user', input.value)
            createCompletion(input.value, blockId, api_type)
            input.value = ''
        }
    })

    document.querySelector('.block_openai_chat #refresh').addEventListener('click', e => {
        clearHistory(blockId)
    })

    document.querySelector('.block_openai_chat #popout').addEventListener('click', e => {
        if (document.querySelector('.drawer.drawer-right')) {
            document.querySelector('.drawer.drawer-right').style.zIndex = '1041'
        }
        document.querySelector('.block_openai_chat').classList.toggle('expanded')
    })

    require(['core/str'], function(str) {
        var strings = [
            {
                key: 'askaquestion',
                component: 'block_openai_chat'
            },
            {
                key: 'erroroccurred',
                component: 'block_openai_chat'
            },
        ];
        str.get_strings(strings).then((results) => {
            questionString = results[0];
            errorString = results[1];
        });
    });
}

/**
 * Add a message to the chat UI
 * @param {string} type Which side of the UI the message should be on. Can be "user" or "bot"
 * @param {string} message The text of the message to add
 */
const addToChatLog = (type, message) => {
    let messageContainer = document.querySelector('#openai_chat_log')
    
    const messageElem = document.createElement('div')
    messageElem.classList.add('openai_message')
    for (let className of type.split(' ')) {
        messageElem.classList.add(className)
    }

    const messageText = document.createElement('span')
    messageText.innerHTML = message
    messageElem.append(messageText)

    messageContainer.append(messageElem)
    if (messageText.offsetWidth) {
        messageElem.style.width = (messageText.offsetWidth + 40) + "px"
    }
    messageContainer.scrollTop = messageContainer.scrollHeight
    messageContainer.closest('.block_openai_chat > div').scrollTop = messageContainer.scrollHeight
}

/**
 * Clears the thread ID from local storage and removes the messages from the UI in order to refresh the chat
 */
const clearHistory = (blockId) => {
    chatData = localStorage.getItem("block_openai_chat_data")
    if (chatData) {
        chatData = JSON.parse(chatData)
        if (chatData[blockId]) {
            chatData[blockId] = {}
            localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
        }
    }
    document.querySelector('#openai_chat_log').innerHTML = ""
}

/**
 * Makes an API request to get a completion from GPT-3, and adds it to the chat log
 * @param {string} message The text to get a completion for
 * @param {int} blockId The ID of the block this message is being sent from -- used to override settings if necessary
 * @param {string} api_type "assistant" | "chat" The type of API to use
 */
const createCompletion = (message, blockId, api_type) => {
    let threadId = null
    let chatData

    // If the type is assistant, attempt to fetch a thread ID
    if (api_type === 'assistant') {
        chatData = localStorage.getItem("block_openai_chat_data")
        if (chatData) {
            chatData = JSON.parse(chatData)
            if (chatData[blockId]) {
                threadId = chatData[blockId]['threadId'] || null
            }
        } else {
            // create the chat data item if necessary
            chatData = {[blockId]: {}}
        }
    }  

    const history = buildTranscript()

    document.querySelector('.block_openai_chat #control_bar').classList.add('disabled')
    document.querySelector('#openai_input').classList.remove('error')
    document.querySelector('#openai_input').placeholder = questionString
    document.querySelector('#openai_input').blur()
    addToChatLog('bot loading', '...');

    fetch(`${M.cfg.wwwroot}/blocks/openai_chat/api/completion.php`, {
        method: 'POST',
        body: JSON.stringify({
            message: message,
            history: history,
            blockId: blockId,
            threadId: threadId
        })
    })
    .then(response => {
        let messageContainer = document.querySelector('#openai_chat_log')
        messageContainer.removeChild(messageContainer.lastElementChild)
        document.querySelector('.block_openai_chat #control_bar').classList.remove('disabled')

        if (!response.ok) {
            throw Error(response.statusText)
        } else {
            return response.json()
        }
    })
    .then(data => {
        try {
            addToChatLog('bot', data.message)
            if (data.thread_id) {
                chatData[blockId]['threadId'] = data.thread_id
                localStorage.setItem("block_openai_chat_data", JSON.stringify(chatData));
            }
        } catch (error) {
            console.log(error)
            addToChatLog('bot', data.error.message)
        }
        document.querySelector('#openai_input').focus()
    })
    .catch(error => {
        console.log(error)
        document.querySelector('#openai_input').classList.add('error')
        document.querySelector('#openai_input').placeholder = errorString
    })
}

/**
 * Using the existing messages in the chat history, create a string that can be used to aid completion
 * @return {JSONObject} A transcript of the conversation up to this point
 */
const buildTranscript = () => {
    let transcript = []
    document.querySelectorAll('.openai_message').forEach((message, index) => {
        if (index === document.querySelectorAll('.openai_message').length - 1) {
            return
        }

        let user = userName
        if (message.classList.contains('bot')) {
            user = assistantName
        }
        transcript.push({"user": user, "message": message.innerText})
    })

    return transcript
}