Последние два года я почти не пишу код. Наверное, только 10% кода в моих личных и коммерческих проектах написано мной, все остальное генерируют нейронки. За это время у меня выработался определенный подход к созданию проектов и появились инструменты, которые я для этого использую. Этим я и хочу поделиться с вами под катом.

С 2022 года многое изменилось. Сначала оригинальный Github Copilot появился в открытой бете, затем пошли ранние модели OpenAI – неряшливые, теряющие контекст, добиться от них внятного результата на длинной дистанции было невозможно. Но в течение этих лет нейронка перестала быть просто продвинутым автокомплитом, теперь это полноценный инструмент для решения задач. Главное – правильно эти задачи ставить. И вот тут, как я заметил, у многих, даже опытных программистов, возникают сложности и появляются вопросы в целеобразности использования нейронок для написания кода в принципе. Это и подтолкнуло меня написать пост.

Особенно полезным он будет для начинающих, которым, как и мне когда-то, интереснее "делать штуки", чем непосредственно писать код. В этом посте я покажу, как с помощью своих инструментов создать self-hosted решение, похожее на Telegra.ph, но с редактором на Markdown.

Подход, который я описываю дальше, можно использовать с разными моделями: ChatGPT, Claude, DeepSeek, даже с бесплатным GitHub Copilot в современном виде (хотя с ним это наиболее неудобно). Недавно я описывал свой опыт работы с Copilot на dev.to — это скорее мучение, чем работа.

Я рекомендую использовать Cursor, сейчас его можно попробовать в Pro версии в течение двух недель. Пока он предоставляет самый приятный и бесшовный опыт программирования с нейронками, что я встречал. Именно работу с ним, я и буду описывать дальше.

Системные требования

Для работы над проектом вам понадобятся:

  • Python версии выше 3.10

  • Pip для установки зависимостей

  • Git для работы с репозиторием

  • Docker Desktop (бесплатный план)

  • Cursor, или доступ к любому AI-чату

  • Утилита snap2txt, которую можно установить через pip install snap2txt

  • Prototype (база проекта)

Работа над проектом

Я бы хотел, чтобы эта часть была максимально понятна начинающим, поэтому некоторые вещи буду расписывать подробно. Если для вас это слишком, просто прокрутите ниже или посмотрите гифки.

1. Создаем проект из репозитория Prototype

Сначала копирую git clone с адресом репозитория базового проекта Prototype и создаю в рабочей папке проект с новым названием tapnote.

Коротко о Prototype

Prototype – это quick-start набор для проектов на Django в Docker-контейнере. Я использую Docker, а не venv или virtualenv, потому что Docker дает полную изоляцию на уровне ОС и гарантирует одинаковое окружение на всех машинах. Это удобно для быстрого развертывания на платформах вроде Railway.app или DO. Внутри Prototype – это Dockerfile и docker-compose с основными настройками, хелпер для работы с API OpenAI и shell-скрипт для запуска всего этого одной командой.

Для запуска setup.sh перехожу в новый каталог через cd tapnote и запускаю ./setup.sh. Через несколько секунд все готово.

2. Проверяем работу контейнера

В Docker Desktop появится новый контейнер с названием проекта. Если он успешно запущен, можно открыть вкладку 'web' и убедиться, что в логе нет ошибок. Также по адресу localhost:9009 в браузере откроется стандартная стартовая страница Django.

3. Открываем проект в Cursor и делаем предварительную настройку

Под предварительной настройкой я имею в виду инициализацию Git и создание рабочего контекста проекта. Для этого выполняю git init и делаю первый коммит. В этом проекте будет всего два коммита – до и после промпта. Для создания контекста использую инструмент snap2txt с параметром --il.

Коротко о snap2txt

snap2txt – это Python-утилита, которая сворачивает весь проект в единый текстовый файл. Я сделал ее около полутора лет назад и пользуюсь почти каждый день. Она нужна, чтобы создать файл с контекстом проекта и подать его на вход нейронке вместе с задачей. Так ей сразу будет доступен полный контекст проекта, структура каталогов и файлов. Параметры --il и --wl означают вызов с ignore list или white list — это отдельные файлы в корне утилиты, где прописано, какие файлы добавлять в контекст, а какие нет. --il работает схоже с .gitignore, --wl — наоборот.

Теперь, когда файл с контекстом создан, открываю панель Composer внутри редактора и двойным кликом по project_contents.txt добавляю контекст проекта в Composer.

4. Детально описываем задачу, принимаем файлы и запускаем необходимые команды

Здесь я немного схитрил и вставил заранее подготовленный промпт в чат Composer. Моей задачей было показать создание проекта за один промпт, поэтому я немного отшлифовал его в процессе подготовки статьи. Но более простые проекты делаются простым описанием без предварительной подготовки. Для проекта вроде этого понадобится 2-3 итерации, чтобы поправить моменты, о которых вы не подумали сразу. Однако при грамотном проектировании можно создать сложный проект за один присест.

Для работы в Cursor и во внешнем чате понадобятся разные конфигурации запросов. Cursor напрямую взаимодействует с файлами проекта, поэтому ему нужно меньше пояснений касательно структуры файлов и формата ответов. Внешний же чат, только генерирует инструкции по созданию файлов в нужных каталогах и их содержимое. Кроме того, у каждой модели есть свои особенности: например reasoning-модели, вроде o1, часто лучше справляются с объемными задачами, но любят "размазывать" код и комментарии по ответу, что снижает читаемость. Это поведение можно обрабатывать дополнительными инсрукциями, но здесь я не буду подробно останавливаться на этих нюансах.

Идеальный промпт для этого проекта получился не сразу, и большую роль сыграла структура описания. Я выделил 3 большие группы с детальным описанием необходимых изменений в базовом проекте:

**FUNCTIONAL**

- Общее описание функционала проекта и название приложений (для Django).
- Описание функционала главной страницы
- Описание воркфлоу публикации
- Описниние деталей рендеринга маркдауна
- Описание механизма редактирования созданных постов
- Описание дополнительных страниц, в данном случае 404

---

**STYLING**

- Общее описание жалаемого внешнего вида
  - Описание цветов, отступов, внешнего вида кнопок
  - Отдельные пожелание по специфичеким CSS-параметрам (no outlines, например)
- Описание жалаемого вида элементов Markdown после рендеринга: размер заголовоков, внешний вид кода и так далее
- Желаемый шрифт, для простоты подключаем напрямую
- Отдельно упомянул необходимость использования Tailwind; его так же для простоты подключаем прямо в базовый темплейт

---

**NOTES**

- Дополнительные инструкции по редактированию файлов
- Повторное описание мелочей, которые важно не упустить (для перестраховки, опционально)
- Отдельные пожелания по рендерингу некоторых элементов (например, добавлять target="_blank" в ссылки)
Задача целиком

FUNCTIONAL

  • Create a simple, minimalistic, Telegraph-like blog app named 'TapNote'.

  • The homepage will function as a simplistic text editor:

    • Contains only a textarea with the placeholder text: 'write in markdown'.

    • Includes a 'Publish' button located on the right side of the textarea.

  • Publishing workflow:

    • When the author fills the textarea and presses the 'Publish' button, the note becomes available via a unique link: domain/hashcode.

    • The hashcode is a randomly generated string (long enough to handle billions of notes) and must be unique for each note; visually resembles an API key.

    • When a user opens the link, they see the rendered version of the Markdown note saved in the database.

  • Markdown rendering details:

    • Render Markdown elements into proper HTML with visually distinguishable styling:

      • Headers (#, ##, etc.) should be styled with different font sizes and weights.

      • Code snippets should appear in a distinct, styled block; code inside the snippet must be properly rendered, all indents are saved correctly.

      • Ensure all Markdown elements are properly rendered with clear and intuitive styling.

    • Links and image rendering:

      • Links in Markdown (e.g., [text](https://example.com)) should be rendered as clickable HTML <a> elements with target="_blank" – Important!. Confirm during implementation that this functionality is applied consistently to all links in the rendered Markdown.

      • Image links in Markdown (e.g., ![alt text](https://example.com/image.png)) should display the referenced image.

      • Ensure that both links and images are sanitized during rendering to prevent XSS vulnerabilities.

  • Editing functionality:

    • When a note is published, generate a unique edit token and store it in a browser cookie.

    • Allow the original author to see an Edit button when accessing their post if the cookie token matches the database token.

    • Clicking the Edit button opens the Markdown editor, pre-filled with the note's existing content.

    • Include a backup edit link (domain/hashcode/edit?token=unique_token) for users who clear cookies or switch devices.

    • Ensure that tokens are securely handled to prevent unauthorized access.

    • 404 error handling:

    • Create a custom 404 page with a minimalistic template displaying only "404" as the title of the page.

    • Explicitly override Django's default 404 behavior by using a custom error view in urls.py that renders this template.

    • Ensure the custom 404 page is applied globally across the app.


STYLING

  • The overall design should be light and minimalistic:

    • Use only black and white— strictly avoid outlines, no grey color, no borders in text area , except for buttons and code snippet.

    • Rendered elements should have visible padding

    • Rendered code snippet should have a light gray background

    • Choose the font Space Mono for the entire project.

    • Buttons should:

      • Be rounded.

      • Have a light border, no background color.

  • Markdown rendering specifics:

    • Titles and headers must have distinct font sizes and weights.

    • Code blocks should appear styled as readable snippets.

    • Ensure all Markdown content looks clean and consistent when rendered.

  • Frontend framework: TailwindCSS:

    • Integrate Tailwind by linking it via a <script> tag in the header template.


NOTES

  • Docker restrictions:

    • Do not modify Docker-related files or scripts.

    • Work only within the Django codebase.

  • Create all necessary Django apps to support the required functionality.

  • Provide all updated and newly created files.

  • Follow standard Django project layout

  • New apps should be created at the project root level

  • Include commands needed for maintaining the app (e.g., migrations) in a format suitable for execution inside the Docker container (not on the host machine). Commands must be provided in this chat, not in a separate file.

  • MARKDOWN RENDERING REQUIREMENTS

    • All links must open in new tabs using target="_blank"

    • Add rel="noopener noreferrer" to all links for security

    • Process both regular links and image tags appropriately

    • Use string replacement after markdown rendering to ensure consistent link behavior

    • Avoid using complex markdown extensions that might cause compatibility issues – 404 PAGE REQUIREMENTS

    • Must work in production (DEBUG=False in settings.py) environment

    • Should take over the entire viewport with fixed positioning

    • Must override Django's default 404 page

После отправки задачи в чат Composer нужно подождать несколько секунд, пока он создаст и наполнит нужные файлы. После этого их нужно сохранить, нажав Accept в окошке со списком файлов внизу чата, и выполнить необходимые команды.

На команды хочу обратить отдельное внимание. Почему-то именно здесь Composer периодически ошибается. Иногда он дает команды, готовые к выполнению в контейнере через терминал – в этом случае нужно просто нажать Run в окошке с командами, и они выполнятся. В других случаях в этом окошке есть комментарии, и при запуске через кнопку команды не выполняются из-за того, что комментарии попадают на вход в терминал. Тогда их нужно копировать по одной и выполнять вручную. Иногда команды бывают без docker-compose, написанные для выполнения в виртуальной среде. В таком случае нужно открыть контейнер в Docker Desktop, скопировать и выполнить эти команды в таб Exec по одной.

5. Обновляем страницу в браузере, создаем тестовую заметку

После выполнения команд, если все миграции были успешно созданы, возвращаемся к веб-интерфейсу. Можно обновить стандартную страницу Django, если она открыта, или открыть сайт заново по тому же localhost:9009. Теперь видим <textarea> для маркдауна и кнопку 'Publish'. Заполняем тестовую заметку и публикуем ее. Все описанные в промпте элементы отрендерились как ожидается, код и картинки отображаются правильно. В этом проекте изображения не хранятся на диске, а подтягиваются из указанного источника – в данном случае с демо-страницы WordPress темплейта.

Проверяем функционал редактирования и заканчиваем работу над проектом.

6. Последний коммит

Теперь, когда все готово, можно сделать второй, заключительный, коммит. От инициализации проекта до готового прототипа прошло около трех минут в реальном времени. Описанное в посте состояние проекта можно посмотреть здесь.


Markdown-редактор, кстати, за время подготовки статьи претерпел некоторые изменения и теперь умеет работать с расширенной разметкой и даже рендерить YouTube видео внутри поста. Этот проект хранится в отдельном репозитории с подробными инструкциями по использованию и развертыванию у себя.

Проект, который я показал в статье – всего лишь простой пример, который показывает как быстро можно пройти путь от идеи до рабочего решения, если правильно использовать современные инструменты. Если вы давно хотели сделать свой проект, но откладывали из-за необходимости погружаться в детали реализации – возможно, сейчас самое время попробовать.

Когда я думаю о том, что всего два с небольшим года назад представить себе такой подход к разработке было просто невозможно, когда все, что могла нейронка – это сгенерировать boilerplate-функцию, дух захватывает. Ведь мы только в начале пути. И когда Цукерберг говорит о том, что в 2025 году в фейсбуке mid-level код будут готовить AI-ассистенты, я абсолютно в это верю. Потому что это уже почти здесь.

Комментарии (7)


  1. Wiggin2014
    03.02.2025 08:26

    ссылочку на Prototype не подбросите?


    1. eaterman99 Автор
      03.02.2025 08:26

      Спасибо что заметили, добавил в пост.


  1. positroid
    03.02.2025 08:26

    Для простых задач / приложений или прототипов - cursor отлично справляется. А вот дальше начинаются нюансы с переписыванием кода, когда часть уже работающего кода теряется, часть переписывается иначе с потерей совместимости с другими блоками кода в этом же приложении.

    Также чат композера не бесконечный и в какой-то момент начинает отказываться работать из-за слишком большого контекста, а новый чат, соответственно, не знает что происходило в предыдущем и начинает изучать проект заново.

    Тем не менее, я в виде челленджа с cursor написал бота учета расходов на гугл таблицах без единой правки кода вручную, по ссылке можно посмотреть на историю промптов и сделать выводы о его потенциальной применимости.

    Есть большое количество сложностей, по хорошему нужно проектировать приложение начиная с архитектуры с расчетом на ограниченный контекст ИИ с максимально независимыми блоками / модулями и покрывать все тестами (ну т.е. проблемы те же, что у людей).


    1. eaterman99 Автор
      03.02.2025 08:26

      Согласен на счет проблем с отказом работать, но это часто случается когда контекст уже переполнен. Еще важный момент который я не упомянул в посте – это то что Курсор хорошо работает почему-то только с моделью claude-3.5-sonnet. При переключении на любую модель OpenAI, он отказывается работать с файлами на прямую и работает в режиме чата.


      1. positroid
        03.02.2025 08:26

        отказывается работать с файлами на прямую

        4o работает в качестве агента и умеет работать с файлами/командами, но получается это не всегда, вы правы, sonnet 3.5 стабильнее. Недавно завезли o3-mini - она тоже работает с агентами, другие думающие модели такой возможности лишены (o1, недавний R1).

        Sonnet у меня часто отказывается работать с файлами, если они лежат вне текущего проекта, когда даю задачу сходить в другую папку и что-то посмотреть. Иногда пытается сказать, что нет доступа, иногда генерирует команды для консоли (cd / ls / cat), но если настоять - то идет и смотрит безо всяких проблем)


    1. peterjohnsons
      03.02.2025 08:26

      А вот дальше начинаются нюансы с переписыванием кода, когда часть уже работающего кода теряется, часть переписывается иначе с потерей совместимости с другими блоками кода в этом же приложении.

      Если у вас нейронка начинает "забывать" старый код и начинает "выдумывать заново" то что уже работало и так, то с большой долей вероятности вы используете ChatGPT. Именно по этой причине я перешел с ChatGPT на Claude Sonnet 3.5. Она намного более стабильно "держит" код. И не искажает его "выдумыванием заново", а только может выбросить что-то что посчитает не нужным. Это решается уточнением этих моментов в промте, наверное это часть обучения, чтобы "быть полезной" и иногда эту сторону приходится обуздывать.

      У ChatGPT эта проблема начинает усиливаться с увеличением контекста, чего лишен Claude.

      Просто попробуйте и сравните. За это многие разработчики и отдают ему предпочтение.


      1. positroid
        03.02.2025 08:26

        Да нет, все обозначенные примеры писал sonnet 3.5, 4o себя изначально показывала хуже, поэтому быстро с неё ушел. Пока размер приложения небольшой - все более или менее хорошо, но как только нужные части перестают помещаться в контекст - начинаются сложности.

        Сейчас тестирую o3-mini, но значимого объема данных пока нет, чтобы сделать выводы. Deepseek R1, который подвезли на днях, увы, не умеет работать в качестве агента