В современном мире, где обучение становится все более сложным, а тесты — настоящим испытанием для студентов и учеников, а также для начинающих специалистов, которые работают в компаниях, где сильно развито грейдирование, мы постоянно ищем эффективные способы облегчить процесс получения знаний. Специально для вас я разбираю мощный плагин для браузера, который использует возможности GPT для решения тестов на любые темы. Этот не инновационный инструмент, но таких примеров разбора я в интернете не нашёл. В этой статье мы расскажем, как работает этот плагин, какие преимущества он предлагает и как вы можете использовать его, чтобы достигать результатов на 100%. Давайте разберемся, как сделать вашу учебу проще и эффективнее с помощью этой уникальной технологии!
В своём примере я использовал браузер Chrome. Ну что же, приступим.
Сам плагин состоит из 2 файлов end.js, в которых прописывается основной функционал плагина и manifest.json, в котором даны инструкции для сайтов и в котором задействуется наш основной файл end.js.
Файл manifest.json содержит:
{ "name": "Тест", "version": "1.0", "manifest_version": 3, "content_scripts": [ { "matches": ["https://Здесь ссылка на сайт/*"], "js": [ "end.js" ] } ] }
В общем и целом, здесь от нас требуется сущий пустяк: указать своё название плагина в name и адрес сайта. С manifest.json я решил не заморачиваться, поскольку это не какой-то грандиозный проект, а обычный помощник в тестах.
Теперь перейдём к самому вкусненькому — end.js. Специально для этой статьи я оставил максимум комментариев к строкам. Первой функцией я решил добавить на сайт кнопку, чтобы потом на неё повесить обращение к gpt.
// Добавление кнопки для отображения вопроса и отправки в GPT function addButton() { const button = document.createElement("button"); button.innerText = "Правильный ответ"; button.style.position = "fixed"; // Фиксированное положение button.style.top = "10px"; // Позиция от верхнего края button.style.right = "60px"; // Позиция от правого края button.style.zIndex = 1000; // Чтобы кнопка была сверху // Создаем индикатор загрузки const loadingIndicator = document.createElement("span"); loadingIndicator.style.position = "fixed"; // Фиксированное положение loadingIndicator.style.top = "10px"; // Позиция от верхнего края loadingIndicator.style.right = "60px"; // Позиция от правого края loadingIndicator.style.zIndex = 1000; // Чтобы кнопка была сверху loadingIndicator.innerText = "Загрузка..."; loadingIndicator.style.display = "none"; // Скрываем индикатор по умолчанию loadingIndicator.style.marginLeft = "10px"; // Отступ от кнопки loadingIndicator.style.color = "red"; // Цвет текста индикатора let intervalId; // Переменная для хранения идентификатора интервала // Функция для обновления текста индикатора const updateLoadingText = () => { if (loadingIndicator.innerText === "Загрузка...") { loadingIndicator.innerText = "Загрузка."; } else if (loadingIndicator.innerText === "Загрузка.") { loadingIndicator.innerText = "Загрузка.."; } else { loadingIndicator.innerText = "Загрузка..."; } }; // Добавляем обработчик событий для нажатия кнопки button.onclick = async () => { loadingIndicator.style.display = "inline"; // Показываем индикатор загрузки button.style.display = "none"; // Скрываем кнопку intervalId = setInterval(updateLoadingText, 500); // Запускаем интервал для обновления текста await showMessageFromIframe(); // Ждем завершения обработки loadingIndicator.style.display = "none"; // Скрываем индикатор загрузки button.style.display = "inline"; // Показываем кнопку clearInterval(intervalId); // Очищаем интервал loadingIndicator.innerText = "Загрузка..."; // Сбрасываем текст }; // Добавляем индикатор загрузки и кнопку в тело документа document.body.appendChild(button); document.body.appendChild(loadingIndicator); console.log("Кнопка добавлена на страницу."); }
Собственно говоря, ничего такого грандиозного. Обычная кнопка и сменяющий её индикатор загрузки, видимый, пока не закончит выполняться сама функция обращения к gpt showMessageFromIframe(). Теперь перейдём непосредственно к ней.
Хочу предупредить сразу. В моём случае при переходе к тесту открывалась ещё одна страница. Сложно сказать, зачем так сделали, ну да бог с ним. Кстати, это мне принесло немало головной боли в момент, когда я пытался считать текст с сайта. Так что данную область вам точно придётся переписывать под себя.
// Функция для отображения сообщения из iframe async function showMessageFromIframe() { const iframe = document.querySelector("iframe.content_frame"); // Получаем iframe if (iframe) { const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; // Получаем доступ к документу iframe const questionElement = iframeDocument.querySelector("#q_6879ubhfbka7-myuq0b3e1owp > div.quiz-player-skin__main-container > div.quiz-slide-container > div.quiz-session-view > div > div.slide-layout > div > div:nth-child(1) > div > p > span"); // Укажите путь к вопросу const variandElement = iframeDocument.querySelector("#q_6879ubhfbka7-myuq0b3e1owp > div.quiz-player-skin__main-container > div.quiz-slide-container > div.quiz-session-view > div > div.slide-layout > div > div:nth-child(2) > div > div"); // Указываем путь к вариантом ответа if (questionElement && variandElement) { const question = questionElement.innerText; // Получаем текст вопроса const variants = Array.from(variandElement.querySelectorAll("div")).map(variant => variant.innerText); // Получаем текст вариантов ответов const questionWithVariants = question + " " + variants.join(" "); // Объединяем вопрос и варианты ответов await main(questionWithVariants); // Отправляем вопрос в GPT } else { alert("Вопрос не найден в iframe."); } } else { alert("Iframe не найден."); } }
Вот тут как раз мы получаем сам вопрос и варианты ответов, которые находятся в одном блоке в разных <div>, и собираем их все. После объединяем и получаем единое сообщение для отправки к GPT.
// Основной процесс async function main(question) { const threadId = await createThread(); // Шаг 1: Создаем тему if (threadId) { await addMessageToThread(threadId, question); // Шаг 2: Добавляем сообщение const runId = await createRun(threadId, "asst_код_асисстента", "Тебе даются вопросы с вариантоми ответов и ты на основе базы данных выдаёшь правильный ответ один. Ты выдаёшь только один правильный вариант ответа без каких либо пояснений."); // Шаг 3: Создаем запуск // Проверяем статус выполнения запуска await checkRunStatus(threadId, runId); // Шаг 4: Проверка статуса выполнения // Получение сообщений после создания запуска await getMessagesFromThread(threadId); // Шаг 4: Получаем сообщения } }
Поскольку мы не пользуемся в этом примере никакими крутыми плюшками, а обращение к GPT будет обычным fetch‑запросом, то пункт с checkRunStatus очень важен. В технической документации о ней не особо говорится, но на форумах нашёл, что при данном подходе она прямо-таки необходима.
Ну что ж, теперь погрузимся в недра GPT.
Начнём всё как по тех. документации от open.ai: создаём тему для нашего ранее уже созданного ассистента в функции createThread().
async function createThread() { console.log("Создание темы..."); try { const response = await fetch("https://api.openai.com/v1/threads", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer уникальный_ключ_выданный_openai", "OpenAI-Beta": "assistants=v2" }, body: JSON.stringify({}) }); if (!response.ok) { throw new Error(`Ошибка HTTP: ${response.status}`); } const data = await response.json(); console.log("Тема создана, ID:", data.id); return data.id; // Вернуть ID темы для дальнейшего использования } catch (error) { console.error("Ошибка при создании темы:", error); } }
Специально чтобы всё отслеживать и мониторить на случай ошибок, я также оставил вывод в консоль console.log(«…»); практически во всех функциях. В каждом запросе мы будем использовать «Authorization»: «Bearer уникальный_ключ_выданный_openai» и Bearer не нужно отсюда удалять, как советуют на многих форумах))). Функция возвращает нам уникальный ID, который мы будем использовать в дальнейшем.
Теперь идём к следующей функции addMessageToThread. Наконец-то отправим наше сообщение в GPT.
async function addMessageToThread(threadId, message) { console.log("Добавление сообщения в тему..."); try { const response = await fetch(`https://api.openai.com/v1/threads/${threadId}/messages`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer уникальный_ключ_выданный_openai", "OpenAI-Beta": "assistants=v2" }, body: JSON.stringify({ role: "user", content: message }) }); if (!response.ok) { throw new Error(`Ошибка HTTP: ${response.status}`); } const data = await response.json(); console.log("Сообщение добавлено в тему:", data); } catch (error) { console.error("Ошибка при добавлении сообщения в тему:", error); } }
Да-да. От строк:
const data = await response.json(); console.log("Сообщение добавлено в тему:", data);
можно избавиться, но всё же мне было интересно, что выдаст open.ai в ответ. Сама функция нам ничего не возвращает, лишь отправляет сообщение в GPT. Здесь интересного мало, так что идём дальше к функции createRun.
async function createRun(threadId, assistantId, instructions) { console.log("Создание запуска..."); try { const response = await fetch(`https://api.openai.com/v1/threads/${threadId}/runs`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer уникальный_ключ_выданный_openai", "OpenAI-Beta": "assistants=v2" }, body: JSON.stringify({ assistant_id: assistantId, instructions: instructions }) }); if (!response.ok) { throw new Error(`Ошибка HTTP: ${response.status}`); } const data = await response.json(); console.log("Запуск создан:", data); return data.id; // Вернуть ID созданного запуска для последующего использования } catch (error) { console.error("Ошибка при создании запуска:", error); } }
Она уже, в свою очередь, как я понял, отправляет нашему ассистенту команду на генерацию долгожданного ответа и ещё выдаёт нам уникальный id, к которому мы будем в дальнейшем обращаться за статусом готовности ответа.
Теперь перейдём к ней… Той самой функции, которая опрашивает периодически, готов ли наш ответ или нет. checkRunStatus
async function checkRunStatus(threadId, runId) { let status = ''; try { while (status !== 'completed') { const response = await fetch(`https://api.openai.com/v1/threads/${threadId}/runs/${runId}`, { method: "GET", headers: { "Authorization": "Bearer уникальный_ключ_выданный_openai", "OpenAI-Beta": "assistants=v1" } }); if (!response.ok) { throw new Error(`Ошибка HTTP: ${response.status}`); } const data = await response.json(); status = data.status; console.log("Статус запуска:", status); // Подождать перед следующей проверкой await new Promise(res => setTimeout(res, 2000)); } } catch (error) { console.error("Ошибка при проверке статуса запуска:", error); } }
Как только наш ассистент составит ответ, у него изменится идентификатор на completed, и наш ответ будет готов к тому, чтобы мы его забрали.
Я, честно говоря, долго возился и пытался понять, почему мне не приходит ответ после отправки к GPT. Ведь я пытаюсь забрать его, а мне приходит какая-то дичь.
Ну и наконец получаем ответ, который я решил вывести в alert:
// Получение сообщений после завершения выполнения async function getMessagesFromThread(threadId) { console.log("Получение сообщений из темы..."); try { const response = await fetch(`https://api.openai.com/v1/threads/${threadId}/messages`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": "Bearer уникальный_ключ_выданный_openai", "OpenAI-Beta": "assistants=v2" } }); if (!response.ok) { throw new Error(`Ошибка HTTP: ${response.status}`); } const data = await response.json(); console.log("Полученный ответ:", data); // Проверяем, если данные содержат сообщения внутри объекта data if (Array.isArray(data.data) && data.data.length > 0) { const messages = data.data[0].content; // Получаем содержимое сообщения const answer = messages[0].text.value; // Получаем текст ответа const cleanedAnswer = answer.replace(/【\d+:\d+†source】/g, '').trim(); // Удаляем все вхождения формата 【x:y†source】 alert(`Правильный вариант: ${cleanedAnswer}`); } else { alert("Ассистент не вернул ответ."); } } catch (error) { console.error("Ошибка при получении сообщений из темы:", error);
При выводе заметил, что у нас после ответа выдаётся идентификатор файлов, к которым обращался ассистент для поиска ответа. Да, я знаю, что это мелочь, но глаза всё равно режет. Поэтому строкой:const cleanedAnswer = answer.replace(/【\d+:\d+†source】/g, '').trim(); // Удаляем все вхождения формата 【x:y†source】
удаляем всё лишнее после ответа.
У любителей чистого кода прошу прощения за все левые выводы в консоль. Мне нравится лицезреть, что именно у меня работает, и в случае ошибки намного проще выявить проблемный сегмент.
Да, и если решили просто скопировать данные запросы, не забываем их расставить хотя бы в правильной последовательности, чтобы сначала была функция, а только потом её вызывали.
Конечная строчка кода — это:addButton();
собственно, сам вызов начальной функции.
Теперь к самой сути ассистента
Этот этап я начинал с создания сначала векторной базы данных, а потом уже писал своего ассистента.
Итак, нам нужна будет база, по которой ассистент ищет ответы на вопросы. Мне с этим повезло: у меня были методички и техническая документация в цифровом варианте. Главное, чтобы всё было в текстовом формате!
Берём свою базу. Заранее я её не подготавливал, поскольку в ней ассистент и так хорошо ориентируется, и кладём в отдельный текстовый файл у себя на ПК.
Далее переходим в лк openai и выбираем Хранилище, там выбираем именно Vector stores (Векторные магазины), надеюсь, правильно перевёл)
Нажимаем на создание нового и загружаем ему наш файл с базой.
После чего уже в созданной базе нажимаем на создание ассистента. Напоминаю: мы не переходим в ассистенты, а создаём его прямо из базы. Так к ассистенту при создании привязывается база.
Да, возможно, её получится подключить отдельно, но я так и не нашёл, где именно это можно сделать.
И уже после этого можно брать наш идентификатор ассистента и писать под него код.
Хочу заметить, что версию GPT в плагине я выбирал gpt4o‑mini. И работает плагин из России у меня без VPN.
На данный код и разбор я потратил, в общем и целом, пару-тройку часов и, надеюсь, кому-то она сэкономит немного времени:‑)
ссылка на оригинал статьи https://habr.com/ru/articles/831892/
Добавить комментарий