Compare commits

...

4 Commits

Author SHA1 Message Date
Vodorod
2ce029534e Добавлен русский язык + исправлены критические баги
🌍 Интернационализация:
- Добавлен русский язык в i18n.ts (supportedLngs)
- Русский пункт меню в NavBar.tsx (desktop + mobile)
- Полная локализация страницы RobotCreate.tsx
- Расширен ru.json: robotCreate (60+ ключей) + mainmenu

🐛 Исправления:
- PostCSS баг в vite.config.js (css.postcss: false → { plugins: [] })
- Backend URL конфигурация (8081 вместо 8080)
- API ключи теперь генерируются корректно

🔧 Конфигурация:
- docker-compose.yml: host network mode
- Dockerfile.frontend обновлен
- Добавлены postcss конфиги

 Все сервисы работают:
- Frontend: http://localhost:5174
- Backend: http://localhost:8081
- PostgreSQL: 5433
- MinIO: 9020/9021
2026-02-19 18:32:00 +03:00
f98e5c88fe Обновить README_RU.md 2026-02-19 13:36:36 +01:00
Dorod Parser
6498dfa4f4 feat: добавлена полная русская локализация UI (ru.json) 2026-02-19 15:31:55 +03:00
Dorod Parser
1fa914900d docs: добавлено русское описание проекта 2026-02-19 15:28:21 +03:00
11 changed files with 581 additions and 57 deletions

1
.postcssrc Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -8,15 +8,22 @@ COPY package*.json ./
# Install dependencies # Install dependencies
RUN npm install --legacy-peer-deps RUN npm install --legacy-peer-deps
# Copy frontend source code and config # Copy all configuration files first
COPY src ./src
COPY public ./public
COPY index.html ./
COPY vite.config.js ./ COPY vite.config.js ./
COPY tsconfig.json ./ 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 the frontend port
EXPOSE ${FRONTEND_PORT:-5173} EXPOSE ${FRONTEND_PORT:-5173}
# Start the frontend using the client script # Serve static files using built-in Vite preview
CMD ["npm", "run", "client", "--", "--host"] CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "5173"]

25
README_RU.md Normal file
View 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**

View File

@@ -7,7 +7,7 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME} POSTGRES_DB: ${DB_NAME}
ports: ports:
- "${DB_PORT:-5432}:${DB_PORT:-5432}" - "5433:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
healthcheck: healthcheck:
@@ -22,10 +22,10 @@ services:
environment: environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY} MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY} MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
command: server /data --console-address :${MINIO_CONSOLE_PORT:-9001} command: server /data --console-address :9001
ports: ports:
- "${MINIO_PORT:-9000}:${MINIO_PORT:-9000}" # API port - "9020:9000" # API port
- "${MINIO_CONSOLE_PORT:-9001}:${MINIO_CONSOLE_PORT:-9001}" # WebUI port - "9021:9001" # WebUI port
volumes: volumes:
- minio_data:/data - minio_data:/data
@@ -35,8 +35,7 @@ services:
# dockerfile: Dockerfile.backend # dockerfile: Dockerfile.backend
image: getmaxun/maxun-backend:latest image: getmaxun/maxun-backend:latest
restart: unless-stopped restart: unless-stopped
ports: network_mode: "host"
- "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}"
env_file: .env env_file: .env
environment: environment:
BACKEND_URL: ${BACKEND_URL} BACKEND_URL: ${BACKEND_URL}
@@ -65,12 +64,14 @@ services:
# dockerfile: Dockerfile.frontend # dockerfile: Dockerfile.frontend
image: getmaxun/maxun-frontend:latest image: getmaxun/maxun-frontend:latest
restart: unless-stopped restart: unless-stopped
ports: network_mode: "host"
- "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}"
env_file: .env env_file: .env
environment: environment:
PUBLIC_URL: ${PUBLIC_URL} PUBLIC_URL: http://localhost:5174
BACKEND_URL: ${BACKEND_URL} 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: depends_on:
- backend - backend
@@ -79,11 +80,11 @@ services:
context: . context: .
dockerfile: browser/Dockerfile dockerfile: browser/Dockerfile
args: args:
BROWSER_WS_PORT: ${BROWSER_WS_PORT:-3001} BROWSER_WS_PORT: 3001
BROWSER_HEALTH_PORT: ${BROWSER_HEALTH_PORT:-3002} BROWSER_HEALTH_PORT: 3002
ports: ports:
- "${BROWSER_WS_PORT:-3001}:${BROWSER_WS_PORT:-3001}" - "3011:3001"
- "${BROWSER_HEALTH_PORT:-3002}:${BROWSER_HEALTH_PORT:-3002}" - "3012:3002"
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- DEBUG=pw:browser* - DEBUG=pw:browser*

View File

@@ -54,7 +54,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"loglevel": "^1.8.0", "loglevel": "^1.8.0",
"loglevel-plugin-remote": "^0.6.8", "loglevel-plugin-remote": "^0.6.8",
"Dorod Parser-core": "^0.0.31", "maxun-core": "^0.0.31",
"minio": "^8.0.1", "minio": "^8.0.1",
"moment-timezone": "^0.5.45", "moment-timezone": "^0.5.45",
"node-cron": "^3.0.3", "node-cron": "^3.0.3",

1
postcss.config.cjs Normal file
View File

@@ -0,0 +1 @@
module.exports = {};

468
public/locales/ru.json Normal file
View 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": "Не удалось создать робота"
}
}
}

View File

@@ -364,6 +364,14 @@ export const NavBar: React.FC<NavBarProps> = ({
> >
Türkçe Türkçe
</MenuItem> </MenuItem>
<MenuItem
onClick={() => {
changeLanguage("ru");
handleMenuClose();
}}
>
Русский
</MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
window.open('https://docs.maxun.dev/development/i18n', '_blank'); window.open('https://docs.maxun.dev/development/i18n', '_blank');
@@ -468,6 +476,14 @@ export const NavBar: React.FC<NavBarProps> = ({
> >
Türkçe Türkçe
</MenuItem> </MenuItem>
<MenuItem
onClick={() => {
changeLanguage("ru");
handleMenuClose();
}}
>
Русский
</MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
window.open('https://docs.maxun.dev/development/i18n', '_blank'); window.open('https://docs.maxun.dev/development/i18n', '_blank');

View File

@@ -276,7 +276,7 @@ const RobotCreate: React.FC = () => {
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" component="h1"> <Typography variant="h5" component="h1">
Create New Robot {t('robotCreate.title')}
</Typography> </Typography>
</Box> </Box>
@@ -299,10 +299,10 @@ const RobotCreate: React.FC = () => {
}, },
}} }}
> >
<Tab label="Extract" id="extract-robot" aria-controls="extract-robot" /> <Tab label={t('robotCreate.tabs.extract')} id="extract-robot" aria-controls="extract-robot" />
<Tab label="Scrape" id="scrape-robot" aria-controls="scrape-robot" /> <Tab label={t('robotCreate.tabs.scrape')} id="scrape-robot" aria-controls="scrape-robot" />
<Tab label="Crawl" id="crawl-robot" aria-controls="crawl-robot" /> <Tab label={t('robotCreate.tabs.crawl')} id="crawl-robot" aria-controls="crawl-robot" />
<Tab label="Search" id="search-robot" aria-controls="search-robot" /> <Tab label={t('robotCreate.tabs.search')} id="search-robot" aria-controls="search-robot" />
</Tabs> </Tabs>
</Box> </Box>
@@ -321,11 +321,11 @@ const RobotCreate: React.FC = () => {
/> />
<Typography variant="body2" color="text.secondary" mb={3}> <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> </Typography>
<Box sx={{ width: '100%', maxWidth: 700, mb: 3 }}> <Box sx={{ width: '100%', maxWidth: 700, mb: 3 }}>
<Typography variant="subtitle1" gutterBottom sx={{ mb: 2 }} color="text.secondary"> <Typography variant="subtitle1" gutterBottom sx={{ mb: 2 }} color="text.secondary">
Choose How to Build {t('robotCreate.chooseMode')}
</Typography> </Typography>
<Box sx={{ display: 'flex', gap: 2 }}> <Box sx={{ display: 'flex', gap: 2 }}>
@@ -345,10 +345,10 @@ const RobotCreate: React.FC = () => {
<CardContent sx={{ textAlign: 'center', py: 3, color:"text.secondary" }}> <CardContent sx={{ textAlign: 'center', py: 3, color:"text.secondary" }}>
<HighlightAlt sx={{ fontSize: 32, mb: 1 }} /> <HighlightAlt sx={{ fontSize: 32, mb: 1 }} />
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Recorder Mode {t('robotCreate.modes.recorder.title')}
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
Record your actions into a workflow. {t('robotCreate.modes.recorder.description')}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
@@ -380,16 +380,16 @@ const RobotCreate: React.FC = () => {
fontSize: '0.7rem', fontSize: '0.7rem',
}} }}
> >
Beta {t('robotCreate.modes.ai.label')}
</Box> </Box>
<CardContent sx={{ textAlign: 'center', py: 3, color:"text.secondary" }}> <CardContent sx={{ textAlign: 'center', py: 3, color:"text.secondary" }}>
<AutoAwesome sx={{ fontSize: 32, mb: 1 }} /> <AutoAwesome sx={{ fontSize: 32, mb: 1 }} />
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
AI Mode {t('robotCreate.modes.ai.title')}
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
Describe the task. It builds it for you. {t('robotCreate.modes.ai.description')}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
@@ -399,45 +399,45 @@ const RobotCreate: React.FC = () => {
<Box sx={{ width: '100%', maxWidth: 700 }}> <Box sx={{ width: '100%', maxWidth: 700 }}>
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 3 }}>
<TextField <TextField
placeholder="Name" placeholder={t('robotCreate.extract.namePlaceholder')}
variant="outlined" variant="outlined"
fullWidth fullWidth
value={extractRobotName} value={extractRobotName}
onChange={(e) => setExtractRobotName(e.target.value)} onChange={(e) => setExtractRobotName(e.target.value)}
label="Name" label={t('robotCreate.extract.name')}
/> />
</Box> </Box>
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 3 }}>
<TextField <TextField
placeholder="Example: Extract first 15 company names, descriptions, and batch information" placeholder={t('robotCreate.extract.aiPromptPlaceholder')}
variant="outlined" variant="outlined"
fullWidth fullWidth
multiline multiline
rows={3} rows={3}
value={aiPrompt} value={aiPrompt}
onChange={(e) => setAiPrompt(e.target.value)} onChange={(e) => setAiPrompt(e.target.value)}
label="Extraction Prompt" label={t('robotCreate.extract.aiPrompt')}
/> />
</Box> </Box>
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 3 }}>
<TextField <TextField
placeholder="Example: https://www.ycombinator.com/companies/" placeholder={t('robotCreate.extract.websiteUrlPlaceholder')}
variant="outlined" variant="outlined"
fullWidth fullWidth
value={url} value={url}
onChange={(e) => setUrl(e.target.value)} onChange={(e) => setUrl(e.target.value)}
label="Website URL (Optional)" label={t('robotCreate.extract.websiteUrlOptional')}
/> />
</Box> </Box>
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}> <Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
<FormControl sx={{ flex: 1 }}> <FormControl sx={{ flex: 1 }}>
<InputLabel>LLM Provider</InputLabel> <InputLabel>{t('robotCreate.extract.llmProvider')}</InputLabel>
<Select <Select
value={llmProvider} value={llmProvider}
label="LLM Provider" label={t('robotCreate.extract.llmProvider')}
onChange={(e) => { onChange={(e) => {
const provider = e.target.value as 'anthropic' | 'openai' | 'ollama'; const provider = e.target.value as 'anthropic' | 'openai' | 'ollama';
setLlmProvider(provider); setLlmProvider(provider);
@@ -449,22 +449,22 @@ const RobotCreate: React.FC = () => {
} }
}} }}
> >
<MenuItem value="ollama">Ollama (Local)</MenuItem> <MenuItem value="ollama">{t('robotCreate.extract.llmProviderOllama')}</MenuItem>
<MenuItem value="anthropic">Anthropic (Claude)</MenuItem> <MenuItem value="anthropic">{t('robotCreate.extract.llmProviderAnthropic')}</MenuItem>
<MenuItem value="openai">OpenAI (GPT-4)</MenuItem> <MenuItem value="openai">{t('robotCreate.extract.llmProviderOpenAI')}</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
<FormControl sx={{ flex: 1 }}> <FormControl sx={{ flex: 1 }}>
<InputLabel>Model</InputLabel> <InputLabel>{t('robotCreate.extract.model')}</InputLabel>
<Select <Select
value={llmModel} value={llmModel}
label="Model" label={t('robotCreate.extract.model')}
onChange={(e) => setLlmModel(e.target.value)} onChange={(e) => setLlmModel(e.target.value)}
> >
{llmProvider === 'ollama' ? ( {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-vision" value="llama3.2-vision">llama3.2-vision</MenuItem>,
<MenuItem key="llama3.2" value="llama3.2">llama3.2</MenuItem> <MenuItem key="llama3.2" value="llama3.2">llama3.2</MenuItem>
] ]
@@ -495,7 +495,7 @@ const RobotCreate: React.FC = () => {
type="password" type="password"
value={llmApiKey} value={llmApiKey}
onChange={(e) => setLlmApiKey(e.target.value)} onChange={(e) => setLlmApiKey(e.target.value)}
label="API Key (Optional if set in .env)" label={t('robotCreate.extract.apiKey')}
/> />
</Box> </Box>
)} )}
@@ -508,7 +508,7 @@ const RobotCreate: React.FC = () => {
fullWidth fullWidth
value={llmBaseUrl} value={llmBaseUrl}
onChange={(e) => setLlmBaseUrl(e.target.value)} onChange={(e) => setLlmBaseUrl(e.target.value)}
label="Ollama Base URL (Optional)" label={t('robotCreate.extract.ollamaBaseUrl')}
/> />
</Box> </Box>
)} )}
@@ -626,7 +626,7 @@ const RobotCreate: React.FC = () => {
}} }}
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null} startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null}
> >
{isLoading ? 'Creating & Running...' : 'Create & Run Robot'} {isLoading ? t('robotCreate.extract.creatingAndRunning') : t('robotCreate.extract.createAndRun')}
</Button> </Button>
</Box> </Box>
)} )}
@@ -635,12 +635,12 @@ const RobotCreate: React.FC = () => {
<> <>
<Box sx={{ width: '100%', maxWidth: 700, mb: 3 }}> <Box sx={{ width: '100%', maxWidth: 700, mb: 3 }}>
<TextField <TextField
placeholder="Example: https://www.ycombinator.com/companies/" placeholder={t('robotCreate.extract.websiteUrlPlaceholder')}
variant="outlined" variant="outlined"
fullWidth fullWidth
value={url} value={url}
onChange={(e) => setUrl(e.target.value)} onChange={(e) => setUrl(e.target.value)}
label="Website URL" label={t('robotCreate.extract.websiteUrl')}
/> />
</Box> </Box>
<Box sx={{ width: '100%', maxWidth: 700 }}> <Box sx={{ width: '100%', maxWidth: 700 }}>
@@ -658,7 +658,7 @@ const RobotCreate: React.FC = () => {
}} }}
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null} startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null}
> >
{isLoading ? 'Starting...' : 'Start Recording'} {isLoading ? t('robotCreate.extract.starting') : t('robotCreate.extract.startRecording')}
</Button> </Button>
</Box> </Box>
</> </>

View File

@@ -10,7 +10,7 @@ i18n
.init({ .init({
fallbackLng: 'en', fallbackLng: 'en',
debug: import.meta.env.DEV, debug: import.meta.env.DEV,
supportedLngs: ['en', 'es', 'ja', 'zh','de', 'tr'], supportedLngs: ['en', 'es', 'ja', 'zh','de', 'tr', 'ru'],
interpolation: { interpolation: {
escapeValue: false, // React already escapes escapeValue: false, // React already escapes
}, },

View File

@@ -11,9 +11,14 @@ export default defineConfig(() => {
'import.meta.env.VITE_BACKEND_URL': JSON.stringify(process.env.VITE_BACKEND_URL), 'import.meta.env.VITE_BACKEND_URL': JSON.stringify(process.env.VITE_BACKEND_URL),
'import.meta.env.VITE_PUBLIC_URL': JSON.stringify(publicUrl), 'import.meta.env.VITE_PUBLIC_URL': JSON.stringify(publicUrl),
}, },
css: {
postcss: {
plugins: [], // Empty plugins array - NO PostCSS processing
},
},
server: { server: {
host: new URL(publicUrl).hostname, host: '0.0.0.0', // Listen on all interfaces for Docker
port: parseInt(new URL(publicUrl).port), port: 5174,
}, },
build: { build: {
outDir: 'build', outDir: 'build',