Здесь представлен список моих принятых пулл реквестов в опенсорс проекты.
Wouter (2+ млн скачиваний в неделю)
Во время работы над дипломом я использовал лёгковесную библиотеку wouter для роутинга в Electron-приложении на React. Поскольку в проекте не было веб-сервера и всё работало через файловую систему, навигация строилась через hash в URL — обычный path-based роутинг в таких условиях не работает. Из-за этого я столкнулся с тем, что wouter не поддерживает search params для hash-роутера.
Сначала решил проблему локально, переопределив хук useHashLocation, а потом решил довести это до PR в основной репозиторий. Реализация на тот момент была не самой элегантной, но с помощью советов мейнтейнера wouter, Алексея Тактарова, я доработал решение — и в итоге поддержка search params при hash-матчинге путей была добавлена в библиотеку.
PR: https://github.com/molefrog/wouter/pull/465
Плагин vite-react-ssg
Во время работы в BotHub передо мной стояла задача внедрить SSG (static site generation) для SPA-приложения на vite+react. После анализа доступных инструментов я выбрал плагин vite-react-ssg как наиболее подходящий, но в процессе интеграции столкнулся с багом при совместной работе с vite-plugin-pwa: сборка падала с ошибкой “The URL must be of scheme file”.
Чтобы рендерить страницы во время сборки, vite-react-ssg по опции mock эмулирует браузерное окружение через jsdom с помощью утилиты jsdomGlobal — она подставляет объекты вроде document в глобальное окружение Node.js и возвращает функцию для очистки этих моков. Эта функция очистки нигде в коде плагина не вызывалась.
После того как все страницы отрендерены, vite-react-ssg сам вызывает regenerateSW у vite-plugin-pwa, чтобы пересобрать service worker. vite-plugin-pwa использует rollup-plugin-terser для минификации service worker’а, а terser определяет окружение по наличию объекта document: если document есть, он считает, что работает в браузере, и пытается резолвить путь к воркеру через document.baseURI. Из-за оставшихся моков terser ошибочно думал, что находится в браузере, и падал с ошибкой при резолвинге URL.
Я добавил вызов функции очистки моков перед вызовом regenerateSW, что исправило ошибку.
PR: https://github.com/Daydreamer-riri/vite-react-ssg/pull/59
gpt4free (66к звёзд)
gpt4free — open-source библиотека, агрегирующая доступ к разным LLM-провайдерам под единым API, в том числе к веб-версии ChatGPT через провайдер OpenaiChat.
Стриминговый API chatgpt.com устроен нестандартно: он отдаёт не текст, а последовательность операций, которые нужно применить на фронте, чтобы собрать сообщение — это позволяет модели не только дописывать ответ, но и переписывать уже показанное, вплоть до замены отдельного символа.
Каждая строка потока — это операция над накапливаемым ответом, с тремя полями: p — путь внутри структуры сообщения, куда она применяется (/message/content/parts/0 для текста, /message/metadata/refresh_key_info для служебных метаданных), o — тип операции (append — дописать значение к тому, что уже есть по этому пути; replace — заменить значение по пути целиком; patch — применить сразу несколько таких операций списком), и v — само значение. Из-за replace и patch модель может переписывать уже показанный текст, а не только дописывать новый. gpt4free разбирает поток построчно, собирая из него ответ в формате стрима Chat Completions API.
Исправил сериализацию None в строку “None” в поле content при стриминге — сериализатор стал возвращать пустую строку.
PR: https://github.com/xtekky/gpt4free/pull/2942
Если в диалоге использовалась генерация изображений, в потоке появлялась строка с p == “/message/metadata/refresh_key_info” и непустым v. Разбор в iter_messages_line проверял только, что p отсутствует или равен /message/content/parts/0, и в обоих случаях отдавал v как текст ответа — из-за чего значение refresh_key_info попадало в вывод пользователю.
Я добавил проверку на этот конкретный путь перед основной веткой: при совпадении в поток уходит пустая строка вместо содержимого этой строки.
PR: https://github.com/xtekky/gpt4free/pull/3026
Помимо путей вида p, в сам текстовый контент (v для /message/content/parts/0) модель встраивает escape-последовательности вида \ue200…\ue201 — ссылки на источники, картинки, видео, товары и погодные виджеты (turn0image0, turn0search3, turn0news5 и т.д.). Число после turn0search/turn0news в старой реализации использовалось прямо как индекс в списке источников, хотя реальное соответствие нужно искать по полям ref_index и ref_type, которые приходят в самих источниках отдельными строками потока. Из-за этого ссылки указывали не на те источники.
Отдельная проблема была в том, что одна строка потока могла нести сразу две последовательности подряд — одну завершённую и одну начатую, — а старый regex вырезал \ue200/\ue201 без разбора и ломал ещё не полученную до конца последовательность.
Я добавил класс ContentReferences, который сопоставляет ссылки на источники по ref_index/ref_type по мере прихода строк потока, и переработал regex так, чтобы заменялись только полностью завершённые последовательности, а начатая и ещё не закрытая оставалась в буфере до следующей строки. Отдельным коммитом поправил format_link: он не проверял, что заголовок ссылки — пустая строка, и в таком случае оставлял битую ссылку без текста.
PR: https://github.com/xtekky/gpt4free/pull/3071