diff --git a/.gitignore b/.gitignore index 557b785..14a9383 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ __pycache__ *.db *.db-journal -build/ +audio-logs/ -audio-logs/ \ No newline at end of file +dist/ + + +*.pyd \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 01903ed..925c6e3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "python": "${workspaceFolder}/env/Scripts/python.exe", "cwd": "${workspaceFolder}", - "program": "${workspaceFolder}/run.py", + "program": "${workspaceFolder}/app/run.py", "env": { "FLASK_APP": "run.py", "FLASK_ENV": "development", diff --git a/app/api.py b/app/api.py index 72cb18f..e4123eb 100644 --- a/app/api.py +++ b/app/api.py @@ -387,7 +387,7 @@ def regenerate_message(): return jsonify({"error": str(e)}, 500) -from app.voice_service import VoiceService, VOICE_COMMANDS_RESPONSE_TO_STORE +from voice_service import VoiceService, VOICE_COMMANDS_RESPONSE_TO_STORE # 1. Создаем экземпляр (обязательно укажите путь к вашей модели Vosk) voice_inst = VoiceService() diff --git a/run.py b/app/run.py similarity index 83% rename from run.py rename to app/run.py index 4a0c234..6113a82 100644 --- a/run.py +++ b/app/run.py @@ -9,12 +9,13 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(level logger = logging.getLogger(__name__) # ----------------------------------------------- Main ------------------------------------------------------------ -if __name__ == "__main__": +# Создаем явную точку входа +def start_app(): # Добавляем папку app в Python path sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app')) - from app.api import api, voice_inst, current_obsidian_settings - from app.heartbeat_monitor import HeartbeatMonitor - from app.workflows import graph_history_manager + from api import api, voice_inst, current_obsidian_settings + from heartbeat_monitor import HeartbeatMonitor + from workflows import graph_history_manager logger.info("🚀 Запуск LLM Agent Backend сервера...") logger.info("📍 API будет доступно на: http://localhost:5000/api") @@ -33,7 +34,7 @@ if __name__ == "__main__": logger.info("HeartbeatMonitor запущен.") - from app.heartbeat_monitor import VoiceHeartbeatMonitor + from heartbeat_monitor import VoiceHeartbeatMonitor v_monitor = VoiceHeartbeatMonitor( voice_inst, @@ -48,4 +49,8 @@ if __name__ == "__main__": port=5000, host='localhost', threaded=True - ) \ No newline at end of file + ) + +# Оставляем этот блок для локальных тестов, если запускаете исходник напрямую +if __name__ == "__main__": + start_app() \ No newline at end of file diff --git a/app/title_generator.py b/app/title_generator.py index 8967d8c..ec8696f 100644 --- a/app/title_generator.py +++ b/app/title_generator.py @@ -20,7 +20,7 @@ QUEUE_POLLING_INTERVAL = 4 TITLE_TEMPERATURE = 0.1 MAX_TITLE_LENGTH = 100 -from app.voice_service import VOICE_COMMANDS_RESPONSE_TO_STORE +from voice_service import VOICE_COMMANDS_RESPONSE_TO_STORE class TitleGenerator: """Сервис для асинхронной генерации заголовков.""" diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..1b33eb9 --- /dev/null +++ b/build.bat @@ -0,0 +1,28 @@ +@echo off +chcp 65001 > nul + +:: 1. Включаем виртуальное окружение перед стартом +if exist env\Scripts\activate.bat ( + echo 🔌 Активация виртуального окружения env... + call env\Scripts\activate.bat +) else ( + echo ⚠️ Предупреждение: Папка окружения env\Scripts\activate.bat не найдена! + echo Попытка сборки в глобальном окружении... +) + +:: 2. Запуск компиляции (скрипт сам создаст папку dist) +python build/compile.py build_ext --inplace + +:: 3. Сохранение зависимостей в папку dist +pip freeze > dist/requirements_all.txt + +:: 4. Копирование деплой-скрипта из папки build в папку dist +if exist build\deploy.bat ( + copy build\deploy.bat dist\deploy.bat /y + echo ✅ Файл deploy.bat успешно скопирован в dist/ +) else ( + echo ⚠️ Предупреждение: Файл build\deploy.bat не найден! +) + +echo ===== СБОРКА ПОЛНОСТЬЮ ЗАВЕРШЕНА ===== +pause diff --git a/build/compile.py b/build/compile.py new file mode 100644 index 0000000..ee9fdb3 --- /dev/null +++ b/build/compile.py @@ -0,0 +1,93 @@ +import os +import shutil +from setuptools import setup, Extension +from Cython.Build import cythonize + +def build_project(): + # 1. Сначала определяем все абсолютные пути, пока мы в исходной директории + build_dir = os.path.dirname(os.path.abspath(__file__)) # папка build/ + project_root = os.path.abspath(os.path.join(build_dir, "..")) # корень проекта + + source_app = os.path.join(project_root, "app") + dist_dir = os.path.join(project_root, "dist") + dist_app = os.path.join(dist_dir, "app") + + build_main_file = os.path.join(build_dir, "main.py") + dist_main_file = os.path.join(dist_dir, "main.py") + + print("🚀 Старт сборки проекта...") + + # 2. Пересоздаем чистую папку dist/ + if os.path.exists(dist_dir): + shutil.rmtree(dist_dir) + os.makedirs(dist_dir) + + # 3. Копируем исходный код app/ в dist/app/ + shutil.copytree(source_app, dist_app) + + # Сохраняем текущую директорию корня, чтобы вернуться в неё в конце + original_cwd = os.getcwd() + + # СИСТЕМНЫЙ СДВИГ: Переносим рабочую директорию процесса внутрь dist/app/ + # Теперь для компилятора корнем является именно dist/app/ + os.chdir(dist_app) + + # 4. Собираем модули для компиляции (сканируем текущую папку, так как мы уже внутри dist/app) + extensions = [] + for root, _, files in os.walk("."): + for file in files: + if file.endswith('.py') and not file.startswith('__'): + full_path = os.path.join(root, file) + # Вычисляем путь относительно dist/app + clean_path = os.path.relpath(full_path, ".") + # Формируем имя модуля для Python (например, workflows.graph_manager) + module_name = clean_path.replace(os.sep, '.').rstrip('.py') + + if module_name == "run": + extensions.append(Extension("run", [clean_path])) + else: + extensions.append(Extension(module_name, [clean_path])) + + # 5. Запускаем компиляцию (теперь --inplace положит бинарники строго в dist/app/) + setup( + name="AppBinary", + script_args=["build_ext", "--inplace"], + ext_modules=cythonize( + extensions, + language_level="3", + compiler_directives={ + 'annotation_typing': False, # Отключаем строгую типизацию (фиксит max_tokens) + 'binding': True, # Сохраняем Python-совместимость методов + 'always_allow_keywords': True # Разрешаем передачу None в именованные аргументы + } + ), + ) + + # 6. Очищаем dist/app/ от исходного кода + print("\n🧹 Удаление исходного кода из папки dist/app/...") + for root, _, files in os.walk("."): + for file in files: + if (file.endswith('.py') and not file.startswith('__')) or file.endswith('.c'): + try: + os.remove(os.path.join(root, file)) + except OSError: + pass + + # Удаляем временную папку сборки компилятора C++ + if os.path.exists("build"): + shutil.rmtree("build") + + # Возвращаем рабочую директорию обратно в корень проекта + os.chdir(original_cwd) + + # 7. Копируем файл build/main.py в dist/main.py + if os.path.exists(build_main_file): + shutil.copy(build_main_file, dist_main_file) + print("📄 Файл build/main.py успешно скопирован в dist/main.py") + else: + print("⚠️ Ошибка: Файл build/main.py не найден, нечего копировать!") + + print("\n✅ Сборка успешно завершена! Папка dist/ готова к деплою.") + +if __name__ == "__main__": + build_project() diff --git a/build/deploy.bat b/build/deploy.bat new file mode 100644 index 0000000..1e34ff8 --- /dev/null +++ b/build/deploy.bat @@ -0,0 +1,56 @@ +@echo off +chcp 65001 > nul +echo =================================================== +echo 🚀 Начинаем автоматическое развертывание проекта... +echo =================================================== + +:: 1. Проверяем, установлен ли Python в системе +python --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo ❌ Ошибка: Python не найден в системе! + echo Установите Python той же мажорной версии, на которой собирался проект. + goto error +) + +:: 2. Создаем изолированное виртуальное окружение prod_env +if not exist prod_env ( + echo 📦 Создание виртуального окружения prod_env... + python -m venv prod_env + if %errorlevel% neq 0 ( + echo ❌ Не удалось создать виртуальное окружение! + goto error + ) + echo ✅ Окружение prod_env успешно создано. +) else ( + echo ℹ️ Окружение prod_env уже существует, пропускаем создание. +) + +:: 3. Обновляем pip и устанавливаем зависимости в prod_env без его активации +echo 🗂️ Установка внешних библиотек из requirements_all.txt... +prod_env\Scripts\python.exe -m pip install --upgrade pip > nul +prod_env\Scripts\python.exe -m pip install -r requirements_all.txt +if %errorlevel% neq 0 ( + echo ❌ Ошибка при установке зависимостей! Проверьте интернет-соединение. + goto error +) +echo ✅ Все внешние библиотеки успешно установлены. + +:: 4. Формируем удобный ярлык/скрипт для быстрого запуска сервера в будущем +echo @echo off > run_server.bat +echo chcp 65001 ^> nul >> run_server.bat +echo echo 🔌 Запуск защищенного бэкенд сервера... >> run_server.bat +echo prod_env\Scripts\python.exe main.py >> run_server.bat +echo pause >> run_server.bat + +echo =================================================== +echo 🎉 ДЕПЛОЙ УСПЕШНО ЗАВЕРШЕН! +echo =================================================== +echo 💡 Для запуска сервера используйте созданный файл: run_server.bat +echo =================================================== +pause +exit /b 0 + +:error +echo 🟥 Деплой завершился с ошибкой! +pause +exit /b 1 diff --git a/build/main.py b/build/main.py new file mode 100644 index 0000000..1d1a684 --- /dev/null +++ b/build/main.py @@ -0,0 +1,12 @@ +import sys +import os + +if __name__ == "__main__": + # Добавляем папку app в пути поиска, как в вашей оригинальной «голове» + sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app')) + + # Python сам найдет файл run.cp311-win_amd64.pyd и импортирует его + import run + + # Запускаем сервер + run.start_app() \ No newline at end of file diff --git a/AsyncTask.md b/docs/tech_assignment/AsyncTask.md similarity index 100% rename from AsyncTask.md rename to docs/tech_assignment/AsyncTask.md diff --git a/Target.md b/docs/tech_assignment/Target.md similarity index 100% rename from Target.md rename to docs/tech_assignment/Target.md diff --git a/Target2.md b/docs/tech_assignment/Target2.md similarity index 100% rename from Target2.md rename to docs/tech_assignment/Target2.md diff --git a/TechSpecification.md b/docs/tech_assignment/TechSpecification.md similarity index 100% rename from TechSpecification.md rename to docs/tech_assignment/TechSpecification.md diff --git a/geckodriver.exe b/geckodriver.exe deleted file mode 100644 index fbde1f7..0000000 Binary files a/geckodriver.exe and /dev/null differ diff --git a/obfuscate.txt b/obfuscate.txt deleted file mode 100644 index 0fac873..0000000 --- a/obfuscate.txt +++ /dev/null @@ -1,65 +0,0 @@ -Для создания исполняемого файла (.exe), который запускается без установки, с обфусцированным кодом Python лучше всего использовать связку PyArmor + PyInstaller: - -1. Обфускация с PyArmor: -- PyArmor шифрует и обфусцирует исходный Python-код, усложняя его анализ и обратную разработку. -- Используется командой: - ``` - pyarmor gen -O dist --recursive путь/к/вашему/проекту - ``` - или - ``` - pyarmor gen -O dist --recursive . - ``` -- Это создаст обфусцированные версии скриптов с сохранением структуры. - -2. Упаковка в exe с PyInstaller: -- После обфускации используйте PyInstaller, чтобы упаковать проект в единый exe-файл с параметром `--onefile`: - ``` - pyinstaller --onefile --noconsole ваш_главный_скрипт.py - ``` - -? python -m PyInstaller --onefile --noconsole run.py -? pyinstaller --onefile --noconsole --paths=./app run.py -? pyinstaller --onefile --hidden-import=eventlet.green --hidden-import=eventlet.green.thread --hidden-import=engineio.async_eventlet --hidden-import=engineio.async_drivers.eventlet --noconsole --paths=./app run.py - - -- Если нужно, добавляйте параметры `--add-data` для ресурсов. - -3. Итог: -- Итоговый .exe-файл будет содержать интерпретатор Python, все зависимости и обфусцированный код. -- Запуск exe не требует предварительной установки Python или зависимостей. -- Такой файл удобно выкладывать в релизы GitHub и распространять пользователям. - -4. Важные советы: -- Всегда тестируйте exe на чистой системе. -- PyArmor даёт хорошую защиту, но не абсолютную. -- Для повышения безопасности используйте активацию лицензий PyArmor (опционно). -- Настройте spec-файл PyInstaller, чтобы включить все необходимые модули и ресурсы. - -Пример типичного workflow: - -```bash -# Обфускация -pyarmor obfuscate --recursive your_project/ - -# Переход в обфусцированную папку -cd dist/your_project - -# Создание exe -pyinstaller --onefile --noconsole main.py -``` - -Тогда в папке `dist` появится самодостаточный исполняемый файл. - -Таким образом, PyArmor + PyInstaller позволяет создать exe с обфусцированным кодом, который не требует установки Python и готов к распространению через GitHub релизы.[1][7][9] - -[1](https://pressanybutton.ru/post/poleznye-instrumenty/python-proektav-exe-fajl-i-obfuskaciya-koda/) -[2](https://www.youtube.com/watch?v=15ho_Zg_Kq4) -[3](https://www.youtube.com/watch?v=HtiYdCk0_J8) -[4](https://www.reddit.com/r/learnpython/comments/144tdhy/obfuscating_python_script/) -[5](https://stackoverflow.com/questions/58334936/pack-a-pyarmor-obfuscated-script-to-exe) -[6](https://qna.habr.com/q/1055278) -[7](https://pyarmor.readthedocs.io/en/v5.7.0/pack-obfuscated-scripts.html) -[8](https://github.com/dashingsoft/pyarmor) -[9](https://www.cyberforum.ru/python-ide/thread3151287.html) -[10](https://www.reddit.com/r/learnpython/comments/1bdsnce/which_one_is_best_for_code_obfuscation/) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dd2f1d1..3907cc5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,4 +19,12 @@ sounddevice==0.5.1 numpy==2.1.0 matplotlib==3.8.4 -pandas==2.2.2 \ No newline at end of file +pandas==2.2.2 + + +# обфускация, сборка + +# stickytape # сшивка в один файл? +# python-minifier +cython +setuptools \ No newline at end of file diff --git a/run-llm-backend.bat b/run-llm-backend.bat index c0645b9..7bf1517 100644 --- a/run-llm-backend.bat +++ b/run-llm-backend.bat @@ -1,5 +1,5 @@ chcp 65001 cd /d "%~dp0" call "env\Scripts\activate.bat" -python run.py +python "app/run.py" pause \ No newline at end of file diff --git a/run.spec b/run.spec deleted file mode 100644 index a022dc8..0000000 --- a/run.spec +++ /dev/null @@ -1,38 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -a = Analysis( - ['run.py'], - pathex=['./app'], - binaries=[], - datas=[], - hiddenimports=['eventlet.green', 'eventlet.green.thread', 'engineio.async_eventlet', 'engineio.async_drivers.eventlet'], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, - optimize=0, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name='run', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=False, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, -) diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts deleted file mode 100644 index 356d257..0000000 --- a/src/types/electron.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Добавьте этот файл, если его нет -interface Window { - electron?: { - shell: { - openPath: (path: string) => Promise; - }; - }; -} \ No newline at end of file