Локальный ИИ-редактор текста.
Локальный ИИ-редактор текста: как я перестал отправлять черновики на чужие серверы (и что из этого вышло)
Дисклеймер: это не гайд «сделай как я», а скорее отчёт «сделал как смог, вот грабли, наступайте аккуратно». Никаких революций не обещаю.
Предыстория
Как-то раз понадобилось сократить письмо и заодно вычистить из него лишние контактные данные. Привычно вставил текст в очередной «умный» веб-редактор, нажал кнопку — и только потом подумал: а собственно куда этот текст уехал? В ToS того сервиса, скорее всего, было написано что-то в духе «мы можем использовать ваши данные для улучшения сервиса». Что в переводе с корпоративного обычно означает «ваши данные теперь немножко наши».
Не то чтобы там была гостайна, но осадок остался. Захотелось инструмент, который делает то же самое, но не шлёт ничего за пределы моего пк. Так и появился этот маленький проект.
Сразу оговорюсь, чтобы не было ложных ожиданий. В области ИИ и фронтенда я — новичок. Этот HTML-файл делали вместе с ИИ-ассистентом: я формулировал, чего хочу, проверял на своём железе и возвращал «а вот тут не работает»,«хочу вот так» а ассистент собирал и правил код. Поэтому дальше будет много терминов, которые я сам поначалу гуглил, — буду расшифровывать их прямо в тексте, как смогу, но за глубиной не гонитесь, я не эксперт.
Это отчёт пользователя, который пытался сделать приватный редактор, а не лекция.
Что хотел получить Список желаний был скромный: - сокращать, перефразировать, переводить; - анонимизировать (имена, телефоны, email — замены); - чтобы текст не улетал в облако; - чтобы запускалось без магии («скачал — открыл — работает», ну или почти).
Два пути, и почему их два
Оказалось, есть два в принципе рабочих подхода, и они довольно разные.
Путь первый: локальная модель через LM Studio (и его аналоги) LM Studio — это такой «лаунчер» для моделей, которая поднимает локальный сервер с OpenAI-совместимым API. API — интерфейс, через который программы общаются; «OpenAI-совместимый» значит «такой же формат запросов, как у ChatGPT, только у тебя на компе». Качаешь модель (у меня крутилась gemma-4-e4b-it, пробовал и qwen3.x), включаешь Server, и приложение стучится на http://127.0.0.1:1234/v1.
Важный момент: привязки именно к LM Studio нет. Приложение говорит с любым сервером, который отдаёт тот же формат API. То есть вместо LM Studio можно поднять, например: - Ollama — тоже лаунчер, попроще, с консольным управлением; сервер поднимается командой ollama serve, и модели доступны по тому же адресу (там свой порт, 11434, но его легко прописать в поле эндпоинта); - llama.cpp (через server или llama-server) — низкоуровневый вариант, самый гибкий по железу, но и возиться больше; - vLLM или LMDeploy — если вдруг захочется запустить то же самое на сервере/в контейнере для себя или маленькой команды.
Я тестировал именно на LM Studio, потому что там всё кнопками и не надо в терминал — для новичка оно понятнее. Но архитектурно приложение от этого не зависит: поменял адрес в поле «Эндпоинт» — и работает с тем, что у тебя уже поднято.
Плюсы:
- Работает быстро, если есть нормальная видеокарта.
- Модель не грузится в браузер, не занимает его память.
- Можно менять модели на лету, не переписывая код.
Минусы:
- Нужно поставить LM Studio. Для «просто открой и работай» это лишний шаг. - CORS по умолчанию выключен. CORS — это правило безопасности браузера, которое запрещает веб-странице обращаться к другому адресу, если тот явно не разрешил; LM Studio слушает на localhost, и без CORS браузер просто заблокирует запрос. Надо зайти в Server Settings и включить.
- Если модель «thinking» (типа qwen3-thinking), она начнёт выдавать простыни размышлений. Лечится enable_thinking:false в запросе, но докопаться до этого пришлось методом тыка.
Путь второй: модель прямо в браузере (transformers.js) Библиотека @huggingface/transformers умеет грузить ONNX-модели прямо в браузер через WebGPU. ONNX — просто формат, в котором веса модели упакованы для быстрого запуска где угодно, не только в Python. WebGPU — новый стандарт, дающий браузеру доступ к видеокарте для вычислений; если его нет, код падает до CPU через wasm — технологию запуска кода на процессоре внутри браузера, медленнее, но работает.
Первый запуск качает модель с Hugging Face (у меня, например, Qwen 2.5 0.5B — около гигабайта), дальше она сидит в кэше. Плюсы: - Вообще ничего не ставишь. Открыл страницу — и работаешь. - Данные реально не уходят никуда, кроме самой модели при первой загрузке. - Для нетяжёлых задач (сократить абзац, убрать имя) вполне хватает. Минусы: - WebGPU есть далеко не везде (на моей рабочей машине, увы, нет — пришлось катить на CPU через wasm; на обычных десктопах с Chrome/Edge 113+ WebGPU обычно доступен, но не гарантировано). Работает, но на маленькой модели и неспешно: секунд 10–30 на ответ. - Первый запуск — это пару минут ожидания и прогресс-бар, который непонятно, долго ли ещё.
- Не все модели с HF одинаково дружелюбны. Однажды выбрал granite-модель, которая в описании выглядела отлично, а по факту библиотека не нашла нужный файл весов (model_quantized.onnx там просто не лежал). Пришлось выкинуть её из списка и оставить те, что реально грузятся. Учимся на граблях. Чтобы не запутаться, архитектура примерно такая:
В обоих случаях текст юзера никуда не уезжает: в режиме А он остаётся на его машине (модель тоже локально), в режиме Б — в браузере (модель один раз качается с HF, дальше в кэше). Разница только в том, где крутится сама модель.
Грабли, на которые я наступил (может, сэкономите время)
1. ES-модули не грузятся с file://. Открыл HTML двойным кликом (то есть по адресу file:// — это когда файл просто лежит на диске, а не на веб-сервере) — пустая страница, потому что браузер блокирует script type="module" на локальных файлах. Module — это современный формат подключения кода, который браузер намеренно не пускает с локальных файлов из соображений безопасности. Лечится либо локальным сервером (python3 -m http.server), либо запускалкой (start.sh / start.bat), либо деплоем на GitHub Pages / HF Space. Я сделал и то, и другое.
2. WebGPU просто-так без танцев с бубном не работает. Проверка через navigator.gpu.requestAdapter() вернула пустоту — и вместо понятной ошибки получил загадочный «no available backend found».
3. Thinking-модели. Qwen3 в режиме размышлений (thinking — это когда модель сначала «думает вслух» внутри себя, выдавая длинный промежуточный текст, а потом уже ответ; задумано для сложных задач, но для «сократи абзац» только мусорит) выдавал сначала простыню think, а потом ответ. Пришлось либо отключать thinking в запросе, либо добавлять think в промпт для браузерного режима. Мелочь, но без неё вывод выглядел странно.
4. Пути к весам моделей на HF — не стандарт. У разных моделей разные имена файлов. Перед тем как добавить модель в список, стоит проверить, что она реально скачивается. Я этого не сделал с первой попытки. Угадай, чем закончилось.
Что в итоге
Получилось приложение (один HTML-файл + запускалки), которое умеет обе схемы. Запускается локально через start.sh / start.bat / start.command, либо открывается прямо на GitHub Pages или на HF Space. Для моих задач (сократить письмо, убрать телефон из черновика) хватает. На тяжёлую редактуру длинных документов маленькая браузерная модель, понятно, не претендует — тут локальная покрупнее и пошустрее.
Выкладываю не как «продукт года», а как рабочий костыль, который решил мою конкретную проблему. Если кому-то пригодится — хорошо.
Вопросы к тем, кто дальше по теме
Чтобы не плодить велосипеды впотьмах, правда интересно:
- Кто как решает проблему file:// у конечных пользователей — всё-таки паковать в Tauri/Electron, или хватает статичного хостинга?
- Реально ли CPU-режим transformers.js доводить до приемлемой скорости на моделях меньше 1B, или это борьба с ветряными мельницами?
- Какие модели размером до 1.5B по-важному держат русский для таких задач? Qwen 2.5 0.5B вроде ничего, но, может, есть что-то поинтереснее.
Буду рад комментариям — особенно если укажете, где я накосячил в коде.
Исходник: github.com/toptestsoft/privat-ai-editor