From a7de5af02127ca74525c14327aab245d6ea44c35 Mon Sep 17 00:00:00 2001 From: K Car Date: Thu, 10 Jul 2025 08:27:30 +0100 Subject: [PATCH] initial --- .env | 5 ++++ .gitignore | 1 + Dockerfile | 42 ++++++++++++++++++++++++++++ README.md | 1 + index.html | 13 +++++++++ nginx/nginx-macos-dev.conf | 46 +++++++++++++++++++++++++++++++ nginx/nginx-macos-prod.conf | 55 +++++++++++++++++++++++++++++++++++++ nginx/nginx-win-prod.conf | 55 +++++++++++++++++++++++++++++++++++++ package.json | 33 ++++++++++++++++++++++ postcss.config.js | 6 ++++ public/audioProcessor.js | 12 ++++++++ tailwind.config.js | 11 ++++++++ tsconfig.json | 25 +++++++++++++++++ tsconfig.node.json | 10 +++++++ vite.config.ts | 17 ++++++++++++ 15 files changed, 332 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 index.html create mode 100644 nginx/nginx-macos-dev.conf create mode 100644 nginx/nginx-macos-prod.conf create mode 100644 nginx/nginx-win-prod.conf create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/audioProcessor.js create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.env b/.env new file mode 100644 index 0000000..1237fb0 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +VITE_WHISPERLIVE_URL=wss://whisperlive.classroomcopilot.ai +VITE_APP_URL=whisperlive.classroomcopilot.ai +VITE_APP_PROTOCOL=https +VITE_APP_NAME=ClassroomCopilotLive +VITE_DEV=false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1d3eec6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Build stage +FROM node:20-alpine as builder + +WORKDIR /app + +# Enable corepack and set up Yarn 4.8.0 before copying files +RUN corepack enable +RUN corepack prepare yarn@4.8.0 --activate + +# Copy package files +COPY package.json yarn.lock ./ +COPY whisperlive-frontend/package.json ./whisperlive-frontend/ + +# Now run yarn install +RUN yarn install + +# Copy source files +COPY whisperlive-frontend ./whisperlive-frontend + +# Build the application +RUN yarn workspace whisperlive-frontend build + +# Production stage +FROM nginx:alpine + +# Create SSL directory +RUN mkdir -p /etc/nginx/ssl + +# Create a win/macos switcher +ARG BUILD_OS +ENV BUILD_OS=${BUILD_OS} +ARG NGINX_MODE +ENV NGINX_MODE=${NGINX_MODE} + +# Copy nginx configuration +COPY whisperlive-frontend/nginx/nginx-${BUILD_OS}-${NGINX_MODE:-dev}.conf /etc/nginx/conf.d/default.conf + +# Copy built files from builder +COPY --from=builder /app/whisperlive-frontend/dist /usr/share/nginx/html + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..18702b8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# whisperlive-frontend \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..c5fc47f --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + WhisperLive - ClassroomCopilot + + +
+ + + \ No newline at end of file diff --git a/nginx/nginx-macos-dev.conf b/nginx/nginx-macos-dev.conf new file mode 100644 index 0000000..b1cd048 --- /dev/null +++ b/nginx/nginx-macos-dev.conf @@ -0,0 +1,46 @@ +server { + listen 5054; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + + # Enable gzip compression + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self' ws: wss:;"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, no-transform"; + } + + # WebSocket proxy for WhisperLive server + location /ws { + proxy_pass https://whisperlive-macos:5050; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket specific settings + proxy_read_timeout 300s; + proxy_send_timeout 300s; + proxy_connect_timeout 75s; + } +} \ No newline at end of file diff --git a/nginx/nginx-macos-prod.conf b/nginx/nginx-macos-prod.conf new file mode 100644 index 0000000..e97326d --- /dev/null +++ b/nginx/nginx-macos-prod.conf @@ -0,0 +1,55 @@ +server { + listen 5054; + server_name localhost; + return 301 https://$server_name$request_uri; +} + +server { + listen 5055 ssl; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # SSL configuration + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Enable gzip compression + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self' ws: wss:;"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, no-transform"; + } + + # WebSocket proxy for WhisperLive server + location /ws { + proxy_pass https://whisperlive.classroomcopilot.ai; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/nginx/nginx-win-prod.conf b/nginx/nginx-win-prod.conf new file mode 100644 index 0000000..2ea98af --- /dev/null +++ b/nginx/nginx-win-prod.conf @@ -0,0 +1,55 @@ +server { + listen 80; + server_name localhost; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # SSL configuration + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Enable gzip compression + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self' ws: wss:;"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, no-transform"; + } + + # WebSocket proxy for WhisperLive server + location /ws { + proxy_pass http://whisperlive-win:5053; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..78b096b --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "whisperlive-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@headlessui/react": "^1.7.18", + "@heroicons/react": "^2.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.17", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "postcss": "^8.4.33", + "tailwindcss": "^3.4.1", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..e99ebc2 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/public/audioProcessor.js b/public/audioProcessor.js new file mode 100644 index 0000000..067d36b --- /dev/null +++ b/public/audioProcessor.js @@ -0,0 +1,12 @@ +class AudioProcessor extends AudioWorkletProcessor { + process(inputs, outputs) { + const input = inputs[0] + if (input.length > 0) { + const channelData = input[0] + this.port.postMessage(channelData) + } + return true + } +} + +registerProcessor('audio-processor', AudioProcessor) \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..da7c9ef --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f6c87b8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "src/env.d.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..862dfb2 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..0c3a809 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + '/ws': { + target: 'ws://localhost:5050', + ws: true, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/ws/, '') + } + } + } +}) \ No newline at end of file