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

Особенно если на том конце британец с каким-то специфическим акцентом или индиец, который говорит очень быстро и очень уверенно. Оба варианта — моя реальность в первые недели работы брокером в Дубае.

Я дизайнер. Умею проектировать сложные системы. Понимаю продукт. Но телефонный разговор с незнакомым акцентом — это отдельный навык, и он не появляется от одного желания.

Можно было просто тренироваться и ждать. Я решил построить инструмент.

Что такое CallCoach

Нативное iOS-приложение на Swift с Apple Intelligence и Live Translation.

Во время звонка оно в реальном времени распознаёт речь собеседника и показывает текст прямо на экране. Анализирует что он сказал. Предлагает подсказки — что ответить, как продолжить, какой вопрос задать.

Приложение CallCoach внутри XCode для запуска эмулятора и тестов

Это не транскрипция ради транскрипции. Это скрипт-ассистент, который всплывает именно тогда, когда нужен — в живом разговоре, без задержки.

Почему Swift и Apple Intelligence

Мог взять любой веб-стек. Но веб не даёт нужной скорости и нативной интеграции с микрофоном и системными API.

Apple Intelligence — локальный языковой движок прямо на устройстве. Никаких запросов в облако, никакой задержки, никакой утечки данных разговора. Всё на телефоне.

Live Translation справляется с акцентированным английским с нужной точностью. Именно то, что нужно для мультинационального Дубая.

Работа с приложением внутри Codex – 5-ти часовое окно токенов закончилось, теперь ждем следующего

Как это изменило работу

Когда видишь текст того, что говорит собеседник — тревога уходит. Перестаёшь тратить энергию на «что он сказал» и начинаешь думать про «что ответить».

Из режима выживания — в режим диалога. Принципиальный сдвиг.

Продолжаю развивать. Следующий шаг — база скриптов под разные сценарии: первый звонок, возражение, уточнение бюджета, договорённость о встрече. Подсказки, которые появляются не просто как транскрипция, а как умный контекстный ответ на конкретную фразу.

Почему я это рассказываю

CallCoach — пример того, как я думаю о продуктах.

Не «какую технологию попробовать», а «какую проблему решить». Проблема была конкретная и личная. Решение — нативное, быстрое, без лишних зависимостей.

Дизайнер, который умеет писать код — не разработчик. Но иногда именно это позволяет идее не умереть в очереди на реализацию.

Техническая реализация

С технической точки зрения CallCoach я собирал как реальный рабочий инструмент, а не как концепт.

Сначала я проработал саму идею и архитектуру вместе с ChatGPT: сформулировал продуктовую задачу, пользовательский сценарий и подготовил большой стартовый промт для Codex, чтобы он создал базовый Swift-проект, поднял зависимости и собрал первый рабочий каркас приложения.

Когда окно токенов у Codex закончилось, я передал проект в Claude Code. Дальше основная «мясная» часть разработки шла уже там — с моделью Opus 4.7.

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

После этого я уже регулярно переходил в Xcode: собирал билд, запускал тесты, ловил падения, возвращал ошибки обратно в Claude Code и вместе с ним фиксировал проблемные места. Часть правок вносил уже прямо внутри Xcode, где тоже подключал Codex и Claude как помощников по месту.

Как устроена логика приложения

Само приложение построено вокруг локальной обработки речи на устройстве.

В обычном режиме CallCoach может слушать поток через микрофон и показывать живую транскрипцию. Но ключевым техническим решением стало разделение голосов.

Главная проблема была в том, что при звонке через динамики микрофон слышит и тебя, и собеседника одновременно. В результате транскрипция загрязняется обеими сторонами диалога и перестаёт быть полезной для коучинга.

Чтобы это решить, я сделал два режима работы.

Режим 1 — System Audio

Это основной и рекомендуемый режим.

Через ScreenCaptureKit приложение захватывает системный аудиовыход, то есть только то, что уходит в динамики. Это позволяет транскрибировать именно голос собеседника, не захватывая мой собственный голос из микрофона.

Дополнительно используется флаг excludesCurrentProcessAudio = true, чтобы исключить звуки самого приложения.

При первом запуске macOS запрашивает разрешение на запись экрана — без этого системный захват аудио невозможен.

Режим 2 — Microphone + Push-to-Mute

Это запасной сценарий.

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

То есть логика очень простая: говорю сам — удерживаю кнопку, закончил — отпустил, приложение снова слушает собеседника.

Что было написано в коде

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

AudioCaptureManager

Отвечает за захват звука через AVAudioEngine.

  • использует installTap на inputNode
  • startRecording() возвращает AsyncStream<AVAudioPCMBuffer>
  • поток буферов передаётся дальше без проблем с actor crossing
  • параллельно считается уровень сигнала через RMS и dB metering
  • обновления UI отправляются на @MainActor через Task

SystemAudioCaptureManager

Это отдельный класс, который я добавил для захвата системного аудио через SCStream.

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

TranscriptionManager

Этот слой принимает поток аудиобуферов и передаёт его в SpeechAnalyzer из Speech framework.

Дальше он итерирует AsyncThrowingStream<SpeechTranscriptionResult>, разделяет partial и final transcription и явно управляет переходами на @MainActor.

То есть это уже не просто «распознать звук», а аккуратно встроить потоковую транскрипцию в живой интерфейс.

CoachingEngine

Это отдельный слой логики, который превращает обычную транскрипцию в полезный разговорный инструмент.

  • processNewTranscript(_:) использует debounce на 1.5 секунды
  • предыдущий Task отменяется, если приходит новый кусок речи
  • LanguageModelSession создаётся один раз на сессию и потом переиспользуется
  • история контекста сохраняется
  • парсер разбирает ответы в формате [ТИП]: текст с fallback на .phrase
  • определяется этап разговора по ключевым словам
  • в модель отправляются последние примерно 300 слов, а не весь транскрипт целиком

Отдельно я подмешивал контекст под рынок Дубая: Emaar, DAMAC, Binghatti, ROI, бронь, встреча, Zoom и другие характерные для разговора сигналы.

ContentView

Здесь всё связывается в пользовательский интерфейс.

  • .task запрашивает разрешение на микрофон при запуске
  • .onChange(segments.count) триггерит coach.processNewTranscript
  • toggleSession() передаёт поток и формат в startTranscription
  • ошибки менеджеров выводятся через отдельные .onChange

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

Как я обучал подсказки

Отдельно я подкармливал систему прикладным контекстом.

Для сценария холодных звонков использовал реальные sales scripts: приветствие, удержание разговора, базовая квалификация клиента, прояснение бюджета, работа с сомнениями и закрытие либо на встречу, либо на подробный Zoom.

Причём это были не абстрактные шаблоны из интернета, а логика, которой нас учили в агентстве SPI Dubai.

За счёт этого подсказки стали более прикладными. Не просто «ответь что-нибудь вежливое», а именно продолжение разговора в нужной структуре и на нужном этапе.

Второй сценарий — собеседования на продуктового дизайнера

Параллельно я сделал второе направление использования этого же подхода: прохождение собеседований на продуктового дизайнера.

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

То есть по сути CallCoach стал не просто speech-to-text приложением, а универсальным conversational assistant, который можно адаптировать под конкретный сценарий общения.

Как выглядел реальный процесс разработки

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

Идея рождается из собственной боли.

Архитектура быстро собирается с помощью языковых моделей.

Тяжёлый код пишется в связке с агентами.

Финальная доводка происходит уже руками в Xcode — через сборки, падения, отладку и повторные итерации.

Не «магия нейросети», а нормальный инженерный процесс, просто ускоренный новыми инструментами.


Changelog / как собирался CallCoach

1. Формулировка идеи

  • Зафиксировал конкретную проблему: в холодных звонках с британскими и индийскими акцентами часть речи просто не успевает распознаваться на слух.
  • Определил задачу продукта: не просто транскрибировать разговор, а помогать вести его дальше.
  • Вместе с ChatGPT проработал концепцию, user flow и структуру будущего приложения.
  • Сгенерировал большой стартовый промт для Codex, чтобы быстро поднять каркас проекта на Swift.

2. Первичная сборка проекта

  • Codex создал базовый Swift-проект.
  • Были подняты основные зависимости.
  • Подготовлена базовая архитектура.
  • Собран первый runnable prototype.

3. Основная инженерная реализация

  • После исчерпания окна токенов Codex проект был передан в Claude Code.
  • Основная разработка продолжилась с использованием Opus 4.7.
  • Через Claude Code была собрана «мясная» часть приложения: захват аудио, транскрипция, коучинговая логика, связка с интерфейсом.

4. Решение главной продуктовой проблемы — разделение голосов

  • Выявлена ключевая проблема: микрофон слышит и пользователя, и собеседника.
  • Реализованы два режима:
    • System Audio — захват только системного аудиовыхода через ScreenCaptureKit
    • Microphone + Push-to-Mute — fallback-режим с ручным отключением передачи буферов в транскрибер
  • Добавлен excludesCurrentProcessAudio = true, чтобы исключить звуки самого приложения.

5. Реализованные компоненты

  • SystemAudioCaptureManager.swift — новый класс для захвата системного аудио через SCStream
  • AudioCaptureManager.swift — захват звука через AVAudioEngine, добавлен isMuted
  • TranscriptionManager — потоковая транскрипция через SpeechAnalyzer
  • CoachingEngine — debounce, переиспользование сессии модели, разбор фраз, логика этапов звонка
  • ContentView.swift — управление сессией, выбор источника аудио, ошибки, кнопки и интерфейс
  • Info.plist — разрешения для системного захвата
  • project.pbxproj — подключение ScreenCaptureKit.framework

6. Отладка и тесты

  • Сборка и запуск шли через Xcode.
  • При каждом падении приложения ошибка возвращалась в Claude Code.
  • Исправления вносились итеративно: Xcode → crash/error → Claude Code → фикс → новая сборка.
  • Часть быстрых правок делалась уже прямо внутри Xcode с подключёнными Codex и Claude.

7. Обучение под реальные сценарии

  • Для cold calling в систему были добавлены реальные sales scripts:
    • приветствие
    • обычный small talk
    • квалификация клиента
    • выявление интереса
    • закрытие на встречу или Zoom
  • Скрипты опирались на реальную практику и обучение в SPI Dubai.
  • Параллельно создан второй сценарий использования: помощь при собеседованиях на роль продуктового дизайнера.
  • Для этого модель обучалась на моих проектах, кейсах и портфолио.

8. Что получилось в итоге

  • локальная транскрипция без отправки данных в облако
  • realtime-подсказки по ходу разговора
  • отдельная логика для продаж и интервью
  • нативный стек без веб-костылей
  • рабочий продукт, собранный в связке ChatGPT + Codex + Claude Code + Xcode
Ссылка скопирована!