✨ Добавлен русский язык + исправлены критические баги
🌍 Интернационализация: - Добавлен русский язык в 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
This commit is contained in:
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"]
|
||||
@@ -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 = {};
|
||||
@@ -55,6 +55,14 @@
|
||||
"deleteFailed": "Не удалось удалить робота",
|
||||
"search": "Поиск роботов..."
|
||||
},
|
||||
"mainmenu": {
|
||||
"recordings": "Роботы",
|
||||
"runs": "Запуски",
|
||||
"proxy": "Прокси",
|
||||
"apikey": "API ключ",
|
||||
"feedback": "Присоединиться к Maxun Cloud",
|
||||
"apidocs": "Веб-сайт в API"
|
||||
},
|
||||
"recordingpage": {
|
||||
"stopRecording": "Остановить запись",
|
||||
"recording": "Запись...",
|
||||
@@ -375,5 +383,86 @@
|
||||
"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