Compare commits
4 Commits
7fa1901e5f
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ce029534e | ||
| f98e5c88fe | |||
|
|
6498dfa4f4 | ||
|
|
1fa914900d |
1
.postcssrc
Normal file
1
.postcssrc
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -8,15 +8,22 @@ COPY package*.json ./
|
||||
# Install dependencies
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# Copy frontend source code and config
|
||||
COPY src ./src
|
||||
COPY public ./public
|
||||
COPY index.html ./
|
||||
# Copy all configuration files first
|
||||
COPY vite.config.js ./
|
||||
COPY tsconfig.json ./
|
||||
COPY index.html ./
|
||||
COPY vite-env.d.ts ./
|
||||
COPY postcss.config.mjs ./
|
||||
|
||||
# Copy frontend source code
|
||||
COPY src ./src
|
||||
COPY public ./public
|
||||
|
||||
# Build production bundle
|
||||
RUN npm run build
|
||||
|
||||
# Expose the frontend port
|
||||
EXPOSE ${FRONTEND_PORT:-5173}
|
||||
|
||||
# Start the frontend using the client script
|
||||
CMD ["npm", "run", "client", "--", "--host"]
|
||||
# Serve static files using built-in Vite preview
|
||||
CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "5173"]
|
||||
25
README_RU.md
Normal file
25
README_RU.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Dorod Parser
|
||||
|
||||
**Визуальный веб-скрапер без кода с AI-интеграцией**
|
||||
|
||||
## Возможности
|
||||
|
||||
- **Recorder Mode** - записывай действия мышкой, создавай роботов
|
||||
- **AI Mode** - описывай что нужно извлечь естественным языком
|
||||
- **Динамические сайты** - адаптируется к изменениям структуры
|
||||
- **Экспорт** - Google Sheets, Airtable, JSON, CSV
|
||||
- **Self-hosted** - разворачивай на своих серверах через Docker
|
||||
- **REST API** - программное управление через SDK
|
||||
|
||||
## Технологии
|
||||
|
||||
- **Frontend:** React + Vite (порт 3030)
|
||||
- **Backend:** Node.js + Playwright
|
||||
- **Хранилище:** PostgreSQL + MinIO + Redis
|
||||
- **Лицензия:** AGPLv3
|
||||
|
||||
|
||||
docker-compose up
|
||||
\\\
|
||||
|
||||
Открой: **http://localhost:3030**
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
ports:
|
||||
- "${DB_PORT:-5432}:${DB_PORT:-5432}"
|
||||
- "5433:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -22,10 +22,10 @@ services:
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
|
||||
command: server /data --console-address :${MINIO_CONSOLE_PORT:-9001}
|
||||
command: server /data --console-address :9001
|
||||
ports:
|
||||
- "${MINIO_PORT:-9000}:${MINIO_PORT:-9000}" # API port
|
||||
- "${MINIO_CONSOLE_PORT:-9001}:${MINIO_CONSOLE_PORT:-9001}" # WebUI port
|
||||
- "9020:9000" # API port
|
||||
- "9021:9001" # WebUI port
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
|
||||
@@ -35,8 +35,7 @@ services:
|
||||
# dockerfile: Dockerfile.backend
|
||||
image: getmaxun/maxun-backend:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}"
|
||||
network_mode: "host"
|
||||
env_file: .env
|
||||
environment:
|
||||
BACKEND_URL: ${BACKEND_URL}
|
||||
@@ -65,12 +64,14 @@ services:
|
||||
# dockerfile: Dockerfile.frontend
|
||||
image: getmaxun/maxun-frontend:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}"
|
||||
network_mode: "host"
|
||||
env_file: .env
|
||||
environment:
|
||||
PUBLIC_URL: ${PUBLIC_URL}
|
||||
BACKEND_URL: ${BACKEND_URL}
|
||||
PUBLIC_URL: http://localhost:5174
|
||||
BACKEND_URL: http://localhost:8081
|
||||
VITE_BACKEND_URL: http://localhost:8081
|
||||
VITE_PUBLIC_URL: http://localhost:5174
|
||||
command: sh -c "npm run client"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
@@ -79,11 +80,11 @@ services:
|
||||
context: .
|
||||
dockerfile: browser/Dockerfile
|
||||
args:
|
||||
BROWSER_WS_PORT: ${BROWSER_WS_PORT:-3001}
|
||||
BROWSER_HEALTH_PORT: ${BROWSER_HEALTH_PORT:-3002}
|
||||
BROWSER_WS_PORT: 3001
|
||||
BROWSER_HEALTH_PORT: 3002
|
||||
ports:
|
||||
- "${BROWSER_WS_PORT:-3001}:${BROWSER_WS_PORT:-3001}"
|
||||
- "${BROWSER_HEALTH_PORT:-3002}:${BROWSER_HEALTH_PORT:-3002}"
|
||||
- "3011:3001"
|
||||
- "3012:3002"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DEBUG=pw:browser*
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"loglevel": "^1.8.0",
|
||||
"loglevel-plugin-remote": "^0.6.8",
|
||||
"Dorod Parser-core": "^0.0.31",
|
||||
"maxun-core": "^0.0.31",
|
||||
"minio": "^8.0.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
|
||||
1
postcss.config.cjs
Normal file
1
postcss.config.cjs
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
468
public/locales/ru.json
Normal file
468
public/locales/ru.json
Normal file
@@ -0,0 +1,468 @@
|
||||
{
|
||||
"login": {
|
||||
"title": "С возвращением!",
|
||||
"email": "Введите рабочий email",
|
||||
"password": "Пароль",
|
||||
"button": "Войти",
|
||||
"loading": "Загрузка",
|
||||
"register_prompt": "Нет аккаунта?",
|
||||
"register_link": "Зарегистрироваться",
|
||||
"welcome_notification": "Добро пожаловать в Dorod Parser!",
|
||||
"validation": {
|
||||
"required_fields": "Email и пароль обязательны",
|
||||
"password_length": "Пароль должен быть не менее 6 символов"
|
||||
},
|
||||
"error": {
|
||||
"user_not_found": "Пользователь не существует",
|
||||
"invalid_credentials": "Неверный email или пароль",
|
||||
"server_error": "Ошибка входа. Попробуйте позже",
|
||||
"generic": "Произошла ошибка. Попробуйте снова"
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"title": "Создать аккаунт",
|
||||
"email": "Введите рабочий email",
|
||||
"password": "Пароль",
|
||||
"button": "Зарегистрироваться",
|
||||
"loading": "Загрузка",
|
||||
"register_prompt": "Уже есть аккаунт?",
|
||||
"login_link": "Войти",
|
||||
"welcome_notification": "Добро пожаловать в Dorod Parser!",
|
||||
"validation": {
|
||||
"email_required": "Email обязателен",
|
||||
"password_requirements": "Пароль должен быть не менее 6 символов"
|
||||
},
|
||||
"error": {
|
||||
"user_exists": "Пользователь с таким email уже существует",
|
||||
"creation_failed": "Не удалось создать аккаунт",
|
||||
"server_error": "Ошибка сервера",
|
||||
"generic": "Регистрация не удалась. Попробуйте снова"
|
||||
}
|
||||
},
|
||||
"recordingtable":{
|
||||
"run": "Запустить",
|
||||
"name": "Название",
|
||||
"schedule": "Расписание",
|
||||
"integrate": "Интеграция",
|
||||
"settings": "Настройки",
|
||||
"options": "Опции",
|
||||
"heading":"Мои роботы",
|
||||
"new":"Создать робота",
|
||||
"deleteModalText": "Вы уверены, что хотите удалить этого робота?",
|
||||
"delete": "Удалить",
|
||||
"cancel": "Отмена",
|
||||
"deleteSuccess": "Робот успешно удален",
|
||||
"deleteFailed": "Не удалось удалить робота",
|
||||
"search": "Поиск роботов..."
|
||||
},
|
||||
"mainmenu": {
|
||||
"recordings": "Роботы",
|
||||
"runs": "Запуски",
|
||||
"proxy": "Прокси",
|
||||
"apikey": "API ключ",
|
||||
"feedback": "Присоединиться к Maxun Cloud",
|
||||
"apidocs": "Веб-сайт в API"
|
||||
},
|
||||
"recordingpage": {
|
||||
"stopRecording": "Остановить запись",
|
||||
"recording": "Запись...",
|
||||
"extract": "Извлечь",
|
||||
"actions": "Действия",
|
||||
"goto": "Перейти",
|
||||
"click": "Клик",
|
||||
"type": "Ввод",
|
||||
"scroll": "Прокрутка",
|
||||
"hover": "Наведение",
|
||||
"select": "Выбор",
|
||||
"clear": "Очистить",
|
||||
"press": "Нажать",
|
||||
"upload": "Загрузить",
|
||||
"download": "Скачать",
|
||||
"screenshot": "Скриншот",
|
||||
"wait": "Ожидание",
|
||||
"refresh": "Обновить",
|
||||
"back": "Назад",
|
||||
"forward": "Вперед",
|
||||
"close": "Закрыть",
|
||||
"newTab": "Новая вкладка",
|
||||
"switchTab": "Переключить вкладку",
|
||||
"pagination": "Пагинация",
|
||||
"captcha": "Капча",
|
||||
"saveRecording": "Сохранить запись",
|
||||
"discardRecording": "Отменить запись",
|
||||
"playRecording": "Воспроизвести",
|
||||
"editStep": "Редактировать шаг",
|
||||
"deleteStep": "Удалить шаг",
|
||||
"addStep": "Добавить шаг",
|
||||
"recordingName": "Название записи",
|
||||
"enterUrl": "Введите URL",
|
||||
"startRecording": "Начать запись",
|
||||
"noSteps": "Нет записанных действий",
|
||||
"error": {
|
||||
"recording_failed": "Ошибка записи",
|
||||
"save_failed": "Не удалось сохранить",
|
||||
"invalid_url": "Неверный URL"
|
||||
}
|
||||
},
|
||||
"robotSettings": {
|
||||
"title": "Настройки робота",
|
||||
"name": "Название",
|
||||
"description": "Описание",
|
||||
"url": "Начальный URL",
|
||||
"proxy": "Прокси",
|
||||
"useProxy": "Использовать прокси",
|
||||
"proxyUrl": "URL прокси",
|
||||
"userAgent": "User Agent",
|
||||
"customHeaders": "Дополнительные заголовки",
|
||||
"headerName": "Название",
|
||||
"headerValue": "Значение",
|
||||
"addHeader": "Добавить заголовок",
|
||||
"timeout": "Тайм-аут (мс)",
|
||||
"retries": "Повторные попытки",
|
||||
"screenshot": "Делать скриншоты",
|
||||
"notifications": "Уведомления",
|
||||
"emailNotifications": "Email уведомления",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"advanced": "Расширенные настройки",
|
||||
"basic": "Основные настройки"
|
||||
},
|
||||
"schedule": {
|
||||
"title": "Настройка расписания",
|
||||
"enabled": "Включить расписание",
|
||||
"frequency": "Частота",
|
||||
"hourly": "Каждый час",
|
||||
"daily": "Ежедневно",
|
||||
"weekly": "Еженедельно",
|
||||
"monthly": "Ежемесячно",
|
||||
"custom": "Настроить",
|
||||
"time": "Время",
|
||||
"timezone": "Часовой пояс",
|
||||
"days": "Дни недели",
|
||||
"monday": "Понедельник",
|
||||
"tuesday": "Вторник",
|
||||
"wednesday": "Среда",
|
||||
"thursday": "Четверг",
|
||||
"friday": "Пятница",
|
||||
"saturday": "Суббота",
|
||||
"sunday": "Воскресенье",
|
||||
"nextRun": "Следующий запуск",
|
||||
"lastRun": "Последний запуск",
|
||||
"save": "Сохранить расписание",
|
||||
"cancel": "Отмена"
|
||||
},
|
||||
"integrate": {
|
||||
"title": "Интеграции",
|
||||
"export": "Экспорт данных",
|
||||
"googleSheets": "Google Sheets",
|
||||
"airtable": "Airtable",
|
||||
"webhook": "Webhook",
|
||||
"api": "REST API",
|
||||
"connect": "Подключить",
|
||||
"disconnect": "Отключить",
|
||||
"connected": "Подключено",
|
||||
"notConnected": "Не подключено",
|
||||
"testConnection": "Проверить подключение",
|
||||
"exportFormat": "Формат экспорта",
|
||||
"json": "JSON",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"save": "Сохранить настройки"
|
||||
},
|
||||
"runs": {
|
||||
"title": "История запусков",
|
||||
"status": "Статус",
|
||||
"success": "Успешно",
|
||||
"failed": "Ошибка",
|
||||
"running": "Выполняется",
|
||||
"pending": "Ожидание",
|
||||
"cancelled": "Отменено",
|
||||
"startTime": "Время начала",
|
||||
"endTime": "Время окончания",
|
||||
"duration": "Длительность",
|
||||
"recordsExtracted": "Извлечено записей",
|
||||
"viewDetails": "Подробности",
|
||||
"viewLogs": "Логи",
|
||||
"downloadData": "Скачать данные",
|
||||
"retry": "Повторить",
|
||||
"cancel": "Отменить",
|
||||
"noRuns": "Нет запусков"
|
||||
},
|
||||
"extraction": {
|
||||
"title": "Извлечение данных",
|
||||
"addField": "Добавить поле",
|
||||
"fieldName": "Название поля",
|
||||
"selector": "Селектор",
|
||||
"type": "Тип",
|
||||
"text": "Текст",
|
||||
"attribute": "Атрибут",
|
||||
"html": "HTML",
|
||||
"link": "Ссылка",
|
||||
"image": "Изображение",
|
||||
"list": "Список",
|
||||
"table": "Таблица",
|
||||
"required": "Обязательное",
|
||||
"optional": "Опциональное",
|
||||
"removeField": "Удалить поле",
|
||||
"testExtraction": "Тестировать",
|
||||
"preview": "Предпросмотр",
|
||||
"noData": "Нет данных"
|
||||
},
|
||||
"ai": {
|
||||
"title": "AI режим",
|
||||
"prompt": "Опишите, что нужно извлечь",
|
||||
"example": "Например: извлеките все названия товаров, цены и артикулы",
|
||||
"generate": "Сгенерировать",
|
||||
"generating": "Генерация...",
|
||||
"apply": "Применить",
|
||||
"edit": "Редактировать",
|
||||
"model": "Модель AI",
|
||||
"temperature": "Температура",
|
||||
"maxTokens": "Макс. токенов",
|
||||
"error": {
|
||||
"generation_failed": "Не удалось сгенерировать",
|
||||
"api_error": "Ошибка API",
|
||||
"invalid_prompt": "Неверный промпт"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"title": "Настройка пагинации",
|
||||
"enabled": "Включить пагинацию",
|
||||
"type": "Тип",
|
||||
"button": "Кнопка",
|
||||
"scroll": "Прокрутка",
|
||||
"url": "URL паттерн",
|
||||
"nextButton": "Селектор кнопки 'Далее'",
|
||||
"maxPages": "Макс. страниц",
|
||||
"waitTime": "Время ожидания (мс)",
|
||||
"stopCondition": "Условие остановки",
|
||||
"noMorePages": "Нет больше страниц",
|
||||
"maxPagesReached": "Достигнут лимит страниц",
|
||||
"custom": "Настроить"
|
||||
},
|
||||
"captcha": {
|
||||
"title": "Обработка капчи",
|
||||
"enabled": "Включить решение капчи",
|
||||
"service": "Сервис",
|
||||
"twoCaptcha": "2Captcha",
|
||||
"antiCaptcha": "AntiCaptcha",
|
||||
"manual": "Вручную",
|
||||
"apiKey": "API ключ",
|
||||
"timeout": "Тайм-аут (сек)",
|
||||
"test": "Тестировать",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Настройки прокси",
|
||||
"enabled": "Использовать прокси",
|
||||
"type": "Тип",
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5",
|
||||
"host": "Хост",
|
||||
"port": "Порт",
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"testConnection": "Проверить подключение",
|
||||
"success": "Прокси работает",
|
||||
"failed": "Ошибка подключения"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"account": "Аккаунт",
|
||||
"profile": "Профиль",
|
||||
"security": "Безопасность",
|
||||
"notifications": "Уведомления",
|
||||
"api": "API ключи",
|
||||
"billing": "Биллинг",
|
||||
"theme": "Тема",
|
||||
"language": "Язык",
|
||||
"light": "Светлая",
|
||||
"dark": "Темная",
|
||||
"auto": "Авто",
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"logout": "Выйти"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Профиль",
|
||||
"name": "Имя",
|
||||
"email": "Email",
|
||||
"company": "Компания",
|
||||
"website": "Веб-сайт",
|
||||
"avatar": "Аватар",
|
||||
"changeAvatar": "Изменить аватар",
|
||||
"save": "Сохранить",
|
||||
"updated": "Профиль обновлен"
|
||||
},
|
||||
"security": {
|
||||
"title": "Безопасность",
|
||||
"changePassword": "Изменить пароль",
|
||||
"currentPassword": "Текущий пароль",
|
||||
"newPassword": "Новый пароль",
|
||||
"confirmPassword": "Подтвердите пароль",
|
||||
"twoFactor": "Двухфакторная аутентификация",
|
||||
"enable2FA": "Включить 2FA",
|
||||
"disable2FA": "Отключить 2FA",
|
||||
"apiKeys": "API ключи",
|
||||
"generateKey": "Сгенерировать ключ",
|
||||
"revokeKey": "Отозвать ключ",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Уведомления",
|
||||
"email": "Email уведомления",
|
||||
"runComplete": "Завершение запуска",
|
||||
"runFailed": "Ошибка запуска",
|
||||
"dailyReport": "Ежедневный отчет",
|
||||
"weeklyReport": "Еженедельный отчет",
|
||||
"webhook": "Webhook уведомления",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"testWebhook": "Тестировать",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"common": {
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить",
|
||||
"edit": "Редактировать",
|
||||
"create": "Создать",
|
||||
"update": "Обновить",
|
||||
"close": "Закрыть",
|
||||
"back": "Назад",
|
||||
"next": "Далее",
|
||||
"previous": "Назад",
|
||||
"finish": "Завершить",
|
||||
"loading": "Загрузка...",
|
||||
"saving": "Сохранение...",
|
||||
"deleting": "Удаление...",
|
||||
"success": "Успешно",
|
||||
"error": "Ошибка",
|
||||
"warning": "Предупреждение",
|
||||
"info": "Информация",
|
||||
"confirm": "Подтвердить",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"ok": "ОК",
|
||||
"search": "Поиск",
|
||||
"filter": "Фильтр",
|
||||
"sort": "Сортировка",
|
||||
"export": "Экспорт",
|
||||
"import": "Импорт",
|
||||
"download": "Скачать",
|
||||
"upload": "Загрузить",
|
||||
"copy": "Копировать",
|
||||
"paste": "Вставить",
|
||||
"cut": "Вырезать",
|
||||
"select_all": "Выбрать все",
|
||||
"clear_all": "Очистить все",
|
||||
"refresh": "Обновить",
|
||||
"settings": "Настройки",
|
||||
"help": "Помощь",
|
||||
"about": "О программе",
|
||||
"version": "Версия",
|
||||
"copyright": " 2026 Dorod Parser"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "Произошла ошибка",
|
||||
"network": "Ошибка сети",
|
||||
"timeout": "Превышен тайм-аут",
|
||||
"not_found": "Не найдено",
|
||||
"unauthorized": "Не авторизован",
|
||||
"forbidden": "Доступ запрещен",
|
||||
"server_error": "Ошибка сервера",
|
||||
"validation": "Ошибка валидации",
|
||||
"try_again": "Попробуйте снова"
|
||||
},
|
||||
"success": {
|
||||
"saved": "Сохранено",
|
||||
"deleted": "Удалено",
|
||||
"updated": "Обновлено",
|
||||
"created": "Создано",
|
||||
"sent": "Отправлено",
|
||||
"copied": "Скопировано"
|
||||
},
|
||||
"robotCreate": {
|
||||
"title": "Создать нового робота",
|
||||
"tabs": {
|
||||
"extract": "Извлечь",
|
||||
"scrape": "Скрейпинг",
|
||||
"crawl": "Обход",
|
||||
"search": "Поиск"
|
||||
},
|
||||
"chooseMode": "Выберите способ создания",
|
||||
"modes": {
|
||||
"recorder": {
|
||||
"title": "Режим записи",
|
||||
"description": "Запишите свои действия в рабочий процесс"
|
||||
},
|
||||
"ai": {
|
||||
"title": "AI режим",
|
||||
"description": "Опишите задачу. Он создаст её для вас",
|
||||
"label": "Beta"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"description": "Извлекайте структурированные данные с веб-сайтов используя AI или записывайте свой собственный процесс извлечения",
|
||||
"websiteUrl": "URL веб-сайта",
|
||||
"websiteUrlOptional": "URL веб-сайта (необязательно)",
|
||||
"websiteUrlPlaceholder": "Например: https://www.ycombinator.com/companies/",
|
||||
"startRecording": "Начать запись",
|
||||
"starting": "Запуск...",
|
||||
"name": "Название",
|
||||
"namePlaceholder": "Название",
|
||||
"aiPrompt": "Промпт для извлечения",
|
||||
"aiPromptPlaceholder": "Например: Извлечь первые 15 названий компаний, описания и информацию о партиях",
|
||||
"aiExample": "Например: 'Извлечь названия продуктов, цены и рейтинги'",
|
||||
"llmProvider": "LLM Провайдер",
|
||||
"llmProviderOllama": "Ollama (Локально)",
|
||||
"llmProviderAnthropic": "Anthropic (Claude)",
|
||||
"llmProviderOpenAI": "OpenAI (GPT-4)",
|
||||
"model": "Модель",
|
||||
"modelDefault": "По умолчанию (llama3.2-vision)",
|
||||
"ollamaBaseUrl": "Ollama Base URL (необязательно)",
|
||||
"generate": "Создать робота",
|
||||
"generating": "Создание...",
|
||||
"createAndRun": "Создать и запустить робота",
|
||||
"creatingAndRunning": "Создание и запуск...",
|
||||
"apiKey": "API ключ (необязательно, если установлен в .env)",
|
||||
"apiKeyPlaceholder": "API ключ"
|
||||
},
|
||||
"scrape": {
|
||||
"description": "Скрейпить весь контент страницы в различных форматах",
|
||||
"websiteUrl": "URL веб-сайта",
|
||||
"robotName": "Название робота (необязательно)",
|
||||
"outputFormats": "Форматы вывода",
|
||||
"createRobot": "Создать робота",
|
||||
"creating": "Создание..."
|
||||
},
|
||||
"crawl": {
|
||||
"description": "Обходить веб-сайт и извлекать данные с нескольких страниц",
|
||||
"websiteUrl": "URL веб-сайта",
|
||||
"robotName": "Название робота (необязательно)",
|
||||
"maxPages": "Максимум страниц для обхода",
|
||||
"maxDepth": "Максимальная глубина обхода",
|
||||
"includePaths": "Включить пути (через запятую, необязательно)",
|
||||
"excludePaths": "Исключить пути (через запятую, необязательно)",
|
||||
"createRobot": "Создать робота",
|
||||
"creating": "Создание...",
|
||||
"advancedOptions": "Дополнительные опции"
|
||||
},
|
||||
"search": {
|
||||
"description": "Искать информацию в интернете используя AI",
|
||||
"query": "Поисковой запрос",
|
||||
"queryPlaceholder": "О чём вы хотите узнать?",
|
||||
"robotName": "Название робота (необязательно)",
|
||||
"createRobot": "Создать поискового робота",
|
||||
"creating": "Создание..."
|
||||
},
|
||||
"errors": {
|
||||
"urlRequired": "URL обязателен",
|
||||
"queryRequired": "Поисковой запрос обязателен",
|
||||
"failedToStart": "Не удалось начать запись. Попробуйте снова",
|
||||
"failedToCreate": "Не удалось создать робота"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,6 +364,14 @@ export const NavBar: React.FC<NavBarProps> = ({
|
||||
>
|
||||
Türkçe
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
changeLanguage("ru");
|
||||
handleMenuClose();
|
||||
}}
|
||||
>
|
||||
Русский
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
window.open('https://docs.maxun.dev/development/i18n', '_blank');
|
||||
@@ -468,6 +476,14 @@ export const NavBar: React.FC<NavBarProps> = ({
|
||||
>
|
||||
Türkçe
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
changeLanguage("ru");
|
||||
handleMenuClose();
|
||||
}}
|
||||
>
|
||||
Русский
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
window.open('https://docs.maxun.dev/development/i18n', '_blank');
|
||||
|
||||
@@ -276,7 +276,7 @@ const RobotCreate: React.FC = () => {
|
||||
<ArrowBack />
|
||||
</IconButton>
|
||||
<Typography variant="h5" component="h1">
|
||||
Create New Robot
|
||||
{t('robotCreate.title')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -299,10 +299,10 @@ const RobotCreate: React.FC = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab label="Extract" id="extract-robot" aria-controls="extract-robot" />
|
||||
<Tab label="Scrape" id="scrape-robot" aria-controls="scrape-robot" />
|
||||
<Tab label="Crawl" id="crawl-robot" aria-controls="crawl-robot" />
|
||||
<Tab label="Search" id="search-robot" aria-controls="search-robot" />
|
||||
<Tab label={t('robotCreate.tabs.extract')} id="extract-robot" aria-controls="extract-robot" />
|
||||
<Tab label={t('robotCreate.tabs.scrape')} id="scrape-robot" aria-controls="scrape-robot" />
|
||||
<Tab label={t('robotCreate.tabs.crawl')} id="crawl-robot" aria-controls="crawl-robot" />
|
||||
<Tab label={t('robotCreate.tabs.search')} id="search-robot" aria-controls="search-robot" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -321,11 +321,11 @@ const RobotCreate: React.FC = () => {
|
||||
/>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" mb={3}>
|
||||
Extract structured data from websites using AI or record your own extraction workflow.
|
||||
{t('robotCreate.extract.description')}
|
||||
</Typography>
|
||||
<Box sx={{ width: '100%', maxWidth: 700, mb: 3 }}>
|
||||
<Typography variant="subtitle1" gutterBottom sx={{ mb: 2 }} color="text.secondary">
|
||||
Choose How to Build
|
||||
{t('robotCreate.chooseMode')}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
@@ -345,10 +345,10 @@ const RobotCreate: React.FC = () => {
|
||||
<CardContent sx={{ textAlign: 'center', py: 3, color:"text.secondary" }}>
|
||||
<HighlightAlt sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Recorder Mode
|
||||
{t('robotCreate.modes.recorder.title')}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Record your actions into a workflow.
|
||||
{t('robotCreate.modes.recorder.description')}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -380,16 +380,16 @@ const RobotCreate: React.FC = () => {
|
||||
fontSize: '0.7rem',
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</Box>
|
||||
{t('robotCreate.modes.ai.label')}
|
||||
</Box>
|
||||
|
||||
<CardContent sx={{ textAlign: 'center', py: 3, color:"text.secondary" }}>
|
||||
<AutoAwesome sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="h6" gutterBottom>
|
||||
AI Mode
|
||||
{t('robotCreate.modes.ai.title')}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Describe the task. It builds it for you.
|
||||
{t('robotCreate.modes.ai.description')}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -399,45 +399,45 @@ const RobotCreate: React.FC = () => {
|
||||
<Box sx={{ width: '100%', maxWidth: 700 }}>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<TextField
|
||||
placeholder="Name"
|
||||
placeholder={t('robotCreate.extract.namePlaceholder')}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={extractRobotName}
|
||||
onChange={(e) => setExtractRobotName(e.target.value)}
|
||||
label="Name"
|
||||
label={t('robotCreate.extract.name')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<TextField
|
||||
placeholder="Example: Extract first 15 company names, descriptions, and batch information"
|
||||
placeholder={t('robotCreate.extract.aiPromptPlaceholder')}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
value={aiPrompt}
|
||||
onChange={(e) => setAiPrompt(e.target.value)}
|
||||
label="Extraction Prompt"
|
||||
label={t('robotCreate.extract.aiPrompt')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<TextField
|
||||
placeholder="Example: https://www.ycombinator.com/companies/"
|
||||
placeholder={t('robotCreate.extract.websiteUrlPlaceholder')}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
label="Website URL (Optional)"
|
||||
label={t('robotCreate.extract.websiteUrlOptional')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
|
||||
<FormControl sx={{ flex: 1 }}>
|
||||
<InputLabel>LLM Provider</InputLabel>
|
||||
<InputLabel>{t('robotCreate.extract.llmProvider')}</InputLabel>
|
||||
<Select
|
||||
value={llmProvider}
|
||||
label="LLM Provider"
|
||||
label={t('robotCreate.extract.llmProvider')}
|
||||
onChange={(e) => {
|
||||
const provider = e.target.value as 'anthropic' | 'openai' | 'ollama';
|
||||
setLlmProvider(provider);
|
||||
@@ -449,22 +449,22 @@ const RobotCreate: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value="ollama">Ollama (Local)</MenuItem>
|
||||
<MenuItem value="anthropic">Anthropic (Claude)</MenuItem>
|
||||
<MenuItem value="openai">OpenAI (GPT-4)</MenuItem>
|
||||
<MenuItem value="ollama">{t('robotCreate.extract.llmProviderOllama')}</MenuItem>
|
||||
<MenuItem value="anthropic">{t('robotCreate.extract.llmProviderAnthropic')}</MenuItem>
|
||||
<MenuItem value="openai">{t('robotCreate.extract.llmProviderOpenAI')}</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl sx={{ flex: 1 }}>
|
||||
<InputLabel>Model</InputLabel>
|
||||
<InputLabel>{t('robotCreate.extract.model')}</InputLabel>
|
||||
<Select
|
||||
value={llmModel}
|
||||
label="Model"
|
||||
label={t('robotCreate.extract.model')}
|
||||
onChange={(e) => setLlmModel(e.target.value)}
|
||||
>
|
||||
{llmProvider === 'ollama' ? (
|
||||
[
|
||||
<MenuItem key="default" value="default">Default (llama3.2-vision)</MenuItem>,
|
||||
<MenuItem key="default" value="default">{t('robotCreate.extract.modelDefault')}</MenuItem>,
|
||||
<MenuItem key="llama3.2-vision" value="llama3.2-vision">llama3.2-vision</MenuItem>,
|
||||
<MenuItem key="llama3.2" value="llama3.2">llama3.2</MenuItem>
|
||||
]
|
||||
@@ -495,7 +495,7 @@ const RobotCreate: React.FC = () => {
|
||||
type="password"
|
||||
value={llmApiKey}
|
||||
onChange={(e) => setLlmApiKey(e.target.value)}
|
||||
label="API Key (Optional if set in .env)"
|
||||
label={t('robotCreate.extract.apiKey')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -508,7 +508,7 @@ const RobotCreate: React.FC = () => {
|
||||
fullWidth
|
||||
value={llmBaseUrl}
|
||||
onChange={(e) => setLlmBaseUrl(e.target.value)}
|
||||
label="Ollama Base URL (Optional)"
|
||||
label={t('robotCreate.extract.ollamaBaseUrl')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -626,7 +626,7 @@ const RobotCreate: React.FC = () => {
|
||||
}}
|
||||
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null}
|
||||
>
|
||||
{isLoading ? 'Creating & Running...' : 'Create & Run Robot'}
|
||||
{isLoading ? t('robotCreate.extract.creatingAndRunning') : t('robotCreate.extract.createAndRun')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
@@ -635,12 +635,12 @@ const RobotCreate: React.FC = () => {
|
||||
<>
|
||||
<Box sx={{ width: '100%', maxWidth: 700, mb: 3 }}>
|
||||
<TextField
|
||||
placeholder="Example: https://www.ycombinator.com/companies/"
|
||||
placeholder={t('robotCreate.extract.websiteUrlPlaceholder')}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
label="Website URL"
|
||||
label={t('robotCreate.extract.websiteUrl')}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ width: '100%', maxWidth: 700 }}>
|
||||
@@ -658,7 +658,7 @@ const RobotCreate: React.FC = () => {
|
||||
}}
|
||||
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null}
|
||||
>
|
||||
{isLoading ? 'Starting...' : 'Start Recording'}
|
||||
{isLoading ? t('robotCreate.extract.starting') : t('robotCreate.extract.startRecording')}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,7 @@ i18n
|
||||
.init({
|
||||
fallbackLng: 'en',
|
||||
debug: import.meta.env.DEV,
|
||||
supportedLngs: ['en', 'es', 'ja', 'zh','de', 'tr'],
|
||||
supportedLngs: ['en', 'es', 'ja', 'zh','de', 'tr', 'ru'],
|
||||
interpolation: {
|
||||
escapeValue: false, // React already escapes
|
||||
},
|
||||
|
||||
@@ -11,9 +11,14 @@ export default defineConfig(() => {
|
||||
'import.meta.env.VITE_BACKEND_URL': JSON.stringify(process.env.VITE_BACKEND_URL),
|
||||
'import.meta.env.VITE_PUBLIC_URL': JSON.stringify(publicUrl),
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [], // Empty plugins array - NO PostCSS processing
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: new URL(publicUrl).hostname,
|
||||
port: parseInt(new URL(publicUrl).port),
|
||||
host: '0.0.0.0', // Listen on all interfaces for Docker
|
||||
port: 5174,
|
||||
},
|
||||
build: {
|
||||
outDir: 'build',
|
||||
|
||||
Reference in New Issue
Block a user