From 206527fa0dcf0500c0b021b626829e1b3a9f66e6 Mon Sep 17 00:00:00 2001 From: Mikhail Kraevskii Date: Wed, 10 Sep 2025 21:42:45 +0300 Subject: [PATCH] fixes --- .dockerignore | 16 +++++ .env.example | 4 +- Dockerfile | 37 +++++++++++ components/InterviewSession.tsx | 113 ++++++++++++++++++++++++++++---- components/VacancyReports.tsx | 2 +- lib/ky-client.ts | 2 +- next.config.js | 34 +++++++++- scripts/build-and-push.sh | 71 ++++++++++++++++++++ 8 files changed, 261 insertions(+), 18 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 scripts/build-and-push.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1c2bdeb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log* +.next +.git +.gitignore +README.md +.env +.env.local +.env.production.local +.env.development.local +coverage +.nyc_output +.DS_Store +*.tsbuildinfo \ No newline at end of file diff --git a/.env.example b/.env.example index d616a23..061fcf7 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ -NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api -NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880 +NEXT_PUBLIC_API_BASE_URL=https://hr.aiquity.xyz:8000/api +NEXT_PUBLIC_LIVEKIT_URL=wss://hackaton-eizc9zqk.livekit.cloud diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2566d36 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +FROM --platform=linux/amd64 node:18-alpine AS base + +WORKDIR /app + +FROM base AS deps +RUN apk add --no-cache libc6-compat +COPY package.json yarn.lock* ./ +RUN yarn + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +RUN yarn build + +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +RUN mkdir .next +RUN chown nextjs:nodejs .next + +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/components/InterviewSession.tsx b/components/InterviewSession.tsx index c20843b..0c446ba 100644 --- a/components/InterviewSession.tsx +++ b/components/InterviewSession.tsx @@ -34,13 +34,44 @@ interface InterviewState { export default function InterviewSession({ resumeId, onEnd }: InterviewSessionProps) { const { data: tokenData, isLoading, error } = useInterviewToken(resumeId, true) + const [connectionError, setConnectionError] = useState(null) + const [isRetrying, setIsRetrying] = useState(false) + + const getServerUrl = () => { + // Приоритет: данные от API -> fallback URLs + if (tokenData?.serverUrl) { + return tokenData.serverUrl + } + + // Fallback URLs для разных окружений + const fallbackUrls = [ + 'wss://hackaton-eizc9zqk.livekit.cloud', + ] + + return fallbackUrls[0] + } + + const handleConnectionError = (error: Error) => { + console.error('LiveKit connection error:', error) + setConnectionError(`Ошибка подключения: ${error.message}`) + } + + const retryConnection = () => { + setIsRetrying(true) + setConnectionError(null) + // Перезагрузка компонента через 1 секунду + setTimeout(() => { + setIsRetrying(false) + window.location.reload() + }, 1000) + } if (isLoading) { return (

- Подключаемся к собеседованию + Подготавливаем собеседование

Пожалуйста, подождите, мы подготавливаем для вас сессию @@ -54,17 +85,63 @@ export default function InterviewSession({ resumeId, onEnd }: InterviewSessionPr

- Ошибка подключения + Ошибка получения токена

- Не удалось подключиться к сессии собеседования + Не удалось получить токен доступа к сессии собеседования

- +
+ + +
+
+ ) + } + + if (connectionError) { + return ( +
+ +

+ Ошибка подключения к LiveKit +

+

+ {connectionError} +

+

+ Сервер: {getServerUrl()} +

+ {isRetrying ? ( +
+ + Переподключение... +
+ ) : ( +
+ + +
+ )}
) } @@ -73,13 +150,23 @@ export default function InterviewSession({ resumeId, onEnd }: InterviewSessionPr
console.log('Connected to LiveKit')} - onDisconnected={() => console.log('Disconnected from LiveKit')} + connectOptions={{ + // Дополнительные опции подключения + autoSubscribe: true, + maxRetries: 3, + }} + onConnected={() => { + console.log('Connected to LiveKit successfully') + setConnectionError(null) + }} + onDisconnected={(reason) => { + console.log('Disconnected from LiveKit:', reason) + }} onError={(error) => { - console.error('LiveKit error:', error) + handleConnectionError(error) }} > diff --git a/components/VacancyReports.tsx b/components/VacancyReports.tsx index 0a4d3cc..addc724 100644 --- a/components/VacancyReports.tsx +++ b/components/VacancyReports.tsx @@ -100,7 +100,7 @@ export default function VacancyReports({ reports }: VacancyReportsProps) { const score = report[key as keyof InterviewReport] as number; return (

- {label}: {score}/10 + {label}: {score}/100

); })} diff --git a/lib/ky-client.ts b/lib/ky-client.ts index b314ba4..06a5632 100644 --- a/lib/ky-client.ts +++ b/lib/ky-client.ts @@ -1,6 +1,6 @@ import ky from 'ky' -const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000/api' +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'https://hr.aiquity.xyz/api' // Базовый клиент без Content-Type заголовка const baseKyClient = ky.create({ diff --git a/next.config.js b/next.config.js index 0db6252..3c4efc7 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,36 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + output: 'standalone', + async headers() { + return [ + { + // Apply these headers to all routes + source: '/(.*)', + headers: [ + { + key: 'X-Frame-Options', + value: 'DENY', + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'Referrer-Policy', + value: 'origin-when-cross-origin', + }, + ], + }, + ] + }, + // Разрешить WebSocket подключения к внешним доменам + experimental: { + serverComponentsExternalPackages: [], + }, + // Настройки для работы с внешними доменами + images: { + domains: ['hr.aiquity.xyz'], + }, +} module.exports = nextConfig \ No newline at end of file diff --git a/scripts/build-and-push.sh b/scripts/build-and-push.sh new file mode 100755 index 0000000..95598cc --- /dev/null +++ b/scripts/build-and-push.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Build and push script for Yandex Cloud Container Registry +# Usage: ./scripts/build-and-push. [tag] + +set -e + +# Configuration +REGISTRY_ID="${YANDEX_REGISTRY_ID:-your-registry-id}" +IMAGE_NAME="hr-ai-frontend" +TAG="${1:-latest}" +FULL_IMAGE_NAME="cr.yandex/${REGISTRY_ID}/${IMAGE_NAME}:${TAG}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Building and pushing HR AI Frontend to Yandex Cloud Container Registry${NC}" + +# Check if required environment variables are set +if [ -z "$REGISTRY_ID" ] || [ "$REGISTRY_ID" = "your-registry-id" ]; then + echo -e "${RED}Error: YANDEX_REGISTRY_ID environment variable is not set${NC}" + echo "Please set it to your Yandex Cloud Container Registry ID" + echo "Example: export YANDEX_REGISTRY_ID=crp1234567890abcdef" + exit 1 +fi + +# Check if yc CLI is installed and authenticated +if ! command -v yc &> /dev/null; then + echo -e "${RED}Error: Yandex Cloud CLI (yc) is not installed${NC}" + echo "Please install it from: https://cloud.yandex.ru/docs/cli/quickstart" + exit 1 +fi + +# Check authentication +if ! yc config list | grep -q "token:"; then + echo -e "${RED}Error: Not authenticated with Yandex Cloud${NC}" + echo "Please run: yc init" + exit 1 +fi + +echo -e "${YELLOW}Configuring Docker for Yandex Cloud Container Registry...${NC}" +yc container registry configure-docker + +echo -e "${YELLOW}Building Docker image: ${FULL_IMAGE_NAME}${NC}" +docker build -t "${FULL_IMAGE_NAME}" . + +echo -e "${YELLOW}Pushing image to registry...${NC}" +docker push "${FULL_IMAGE_NAME}" + +echo -e "${GREEN}✓ Successfully built and pushed: ${FULL_IMAGE_NAME}${NC}" + +# Also tag as latest if a specific tag was provided +if [ "$TAG" != "latest" ]; then + LATEST_IMAGE_NAME="cr.yandex/${REGISTRY_ID}/${IMAGE_NAME}:latest" + echo -e "${YELLOW}Tagging as latest...${NC}" + docker tag "${FULL_IMAGE_NAME}" "${LATEST_IMAGE_NAME}" + docker push "${LATEST_IMAGE_NAME}" + echo -e "${GREEN}✓ Also pushed as: ${LATEST_IMAGE_NAME}${NC}" +fi + +echo -e "${GREEN}Build and push completed successfully!${NC}" +echo "" +echo "Image is available at:" +echo " ${FULL_IMAGE_NAME}" +echo "" +echo "To use in production, update your docker-compose.prod.yml:" +echo " frontend:" +echo " image: ${FULL_IMAGE_NAME}" \ No newline at end of file