В современном мире, где обучение становится все более сложным, а тесты — настоящим испытанием для студентов и учеников, а также для начинающих специалистов, которые работают в компаниях, где сильно развито грейдирование, мы постоянно ищем эффективные способы облегчить процесс получения знаний. Специально для вас я разбираю мощный плагин для браузера, который использует возможности 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.
На данный код и разбор я потратил, в общем и целом, пару-тройку часов и, надеюсь, кому-то она сэкономит немного времени:‑)