From 9a44029d0b472e5493bfc2bc1f75c9cb2329b5e7 Mon Sep 17 00:00:00 2001 From: sachin1801 Date: Sun, 28 Dec 2025 00:38:17 -0500 Subject: [PATCH 1/7] new branch + file update --- .env.example | 12 - .github/workflows/deploy-aws 2.yml | 225 +++ LOCAL_TEST_ACCOUNTS.md | 70 - .../DualModeREPL.js | 473 ------ .../HybridConsole.vue | 1113 ------------- .../hybrid_repl_thread.py | 1406 ----------------- .../repl_registry.py | 274 ---- server/.env.example | 9 - server/command/simple_exec_v3 2.py | 1404 ++++++++++++++++ 9 files changed, 1629 insertions(+), 3357 deletions(-) delete mode 100644 .env.example create mode 100644 .github/workflows/deploy-aws 2.yml delete mode 100644 LOCAL_TEST_ACCOUNTS.md delete mode 100644 backup/repl_backup_20251027_003330/DualModeREPL.js delete mode 100644 backup/repl_backup_20251027_003330/HybridConsole.vue delete mode 100644 backup/repl_backup_20251027_003330/hybrid_repl_thread.py delete mode 100644 backup/repl_backup_20251027_003330/repl_registry.py delete mode 100644 server/.env.example create mode 100644 server/command/simple_exec_v3 2.py diff --git a/.env.example b/.env.example deleted file mode 100644 index 6b535214..00000000 --- a/.env.example +++ /dev/null @@ -1,12 +0,0 @@ -# Example environment variables file -# Copy this to .env.production and update with your actual values - -# For production deployment: -VUE_APP_WS_URL=wss://YOUR-FLY-APP-NAME.fly.dev -VUE_APP_WS_PORT=443 -VUE_APP_WS_PATH=/ws - -# For local development (copy to .env.development): -# VUE_APP_WS_URL=ws://localhost -# VUE_APP_WS_PORT=10086 -# VUE_APP_WS_PATH=/ws \ No newline at end of file diff --git a/.github/workflows/deploy-aws 2.yml b/.github/workflows/deploy-aws 2.yml new file mode 100644 index 00000000..ab470da3 --- /dev/null +++ b/.github/workflows/deploy-aws 2.yml @@ -0,0 +1,225 @@ +name: Deploy to AWS ECS (Main + Exam IDE) + +on: + push: + branches: [ main ] + workflow_dispatch: # Allow manual trigger + +env: + AWS_REGION: us-east-2 + ECR_REPOSITORY_MAIN: pythonide-backend + ECR_REPOSITORY_EXAM: pythonide-exam + ECS_CLUSTER: pythonide-cluster + # Main IDE service + ECS_SERVICE_MAIN: pythonide-service + # Exam IDE service + ECS_SERVICE_EXAM: pythonide-exam-task-service + ECS_TASK_DEFINITION: .github/ecs-task-definition.json + +jobs: + deploy: + name: Deploy to Main + Exam IDE + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + # Build Docker image for linux/amd64 platform + docker build --platform linux/amd64 -t $ECR_REGISTRY/$ECR_REPOSITORY_MAIN:$IMAGE_TAG . + docker build --platform linux/amd64 -t $ECR_REGISTRY/$ECR_REPOSITORY_MAIN:latest . + + # Push to Main IDE repository (pythonide-backend) + docker push $ECR_REGISTRY/$ECR_REPOSITORY_MAIN:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY_MAIN:latest + + # Tag and push to Exam IDE repository (pythonide-exam) + docker tag $ECR_REGISTRY/$ECR_REPOSITORY_MAIN:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY_EXAM:$IMAGE_TAG + docker tag $ECR_REGISTRY/$ECR_REPOSITORY_MAIN:latest $ECR_REGISTRY/$ECR_REPOSITORY_EXAM:latest + docker push $ECR_REGISTRY/$ECR_REPOSITORY_EXAM:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY_EXAM:latest + + echo "image=$ECR_REGISTRY/$ECR_REPOSITORY_MAIN:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Fill in the new image ID in the Amazon ECS task definition + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: ${{ env.ECS_TASK_DEFINITION }} + container-name: pythonide-backend + image: ${{ steps.build-image.outputs.image }} + + # ==================== MAIN IDE DEPLOYMENT ==================== + - name: Deploy to Main IDE (pythonide-service) + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + service: ${{ env.ECS_SERVICE_MAIN }} + cluster: ${{ env.ECS_CLUSTER }} + wait-for-service-stability: true + + - name: Ensure Auto-Scaling Configuration (Main IDE) + run: | + echo "šŸŽÆ Verifying auto-scaling for Main IDE..." + + # Check if auto-scaling target exists + SCALING_TARGET=$(aws application-autoscaling describe-scalable-targets \ + --service-namespace ecs \ + --resource-ids "service/${{ env.ECS_CLUSTER }}/${{ env.ECS_SERVICE_MAIN }}" \ + --region ${{ env.AWS_REGION }} \ + --query 'ScalableTargets[0].ResourceId' \ + --output text 2>/dev/null) || SCALING_TARGET="None" + + if [ "$SCALING_TARGET" = "None" ] || [ "$SCALING_TARGET" = "" ]; then + echo "āš ļø Auto-scaling not configured for Main IDE. Setting up now..." + + # Register scalable target + aws application-autoscaling register-scalable-target \ + --service-namespace ecs \ + --resource-id "service/${{ env.ECS_CLUSTER }}/${{ env.ECS_SERVICE_MAIN }}" \ + --scalable-dimension ecs:service:DesiredCount \ + --min-capacity 2 \ + --max-capacity 6 \ + --region ${{ env.AWS_REGION }} + + # Create scaling policy + aws application-autoscaling put-scaling-policy \ + --policy-name "pythonide-main-cpu-scaling-policy" \ + --service-namespace ecs \ + --resource-id "service/${{ env.ECS_CLUSTER }}/${{ env.ECS_SERVICE_MAIN }}" \ + --scalable-dimension ecs:service:DesiredCount \ + --policy-type TargetTrackingScaling \ + --target-tracking-scaling-policy-configuration '{ + "TargetValue": 45.0, + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "ScaleOutCooldown": 300, + "ScaleInCooldown": 300 + }' \ + --region ${{ env.AWS_REGION }} + + echo "āœ… Main IDE auto-scaling configured" + else + echo "āœ… Main IDE auto-scaling already configured" + fi + + # ==================== EXAM IDE DEPLOYMENT ==================== + - name: Deploy to Exam IDE (pythonide-exam-task-service) + run: | + echo "šŸŽ“ Deploying to Exam IDE..." + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ env.ECS_SERVICE_EXAM }} \ + --force-new-deployment \ + --region ${{ env.AWS_REGION }} + + echo "ā³ Waiting for Exam IDE deployment to stabilize..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ env.ECS_SERVICE_EXAM }} \ + --region ${{ env.AWS_REGION }} + + echo "āœ… Exam IDE deployment complete" + + - name: Ensure Auto-Scaling Configuration (Exam IDE) + run: | + echo "šŸŽÆ Verifying auto-scaling for Exam IDE..." + + # Check if auto-scaling target exists for exam IDE + SCALING_TARGET=$(aws application-autoscaling describe-scalable-targets \ + --service-namespace ecs \ + --resource-ids "service/${{ env.ECS_CLUSTER }}/${{ env.ECS_SERVICE_EXAM }}" \ + --region ${{ env.AWS_REGION }} \ + --query 'ScalableTargets[0].ResourceId' \ + --output text 2>/dev/null) || SCALING_TARGET="None" + + if [ "$SCALING_TARGET" = "None" ] || [ "$SCALING_TARGET" = "" ]; then + echo "āš ļø Auto-scaling not configured for Exam IDE. Setting up now..." + + # Register scalable target for exam IDE + aws application-autoscaling register-scalable-target \ + --service-namespace ecs \ + --resource-id "service/${{ env.ECS_CLUSTER }}/${{ env.ECS_SERVICE_EXAM }}" \ + --scalable-dimension ecs:service:DesiredCount \ + --min-capacity 1 \ + --max-capacity 4 \ + --region ${{ env.AWS_REGION }} + + # Create scaling policy for exam IDE + aws application-autoscaling put-scaling-policy \ + --policy-name "pythonide-exam-cpu-scaling-policy" \ + --service-namespace ecs \ + --resource-id "service/${{ env.ECS_CLUSTER }}/${{ env.ECS_SERVICE_EXAM }}" \ + --scalable-dimension ecs:service:DesiredCount \ + --policy-type TargetTrackingScaling \ + --target-tracking-scaling-policy-configuration '{ + "TargetValue": 45.0, + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "ScaleOutCooldown": 300, + "ScaleInCooldown": 300 + }' \ + --region ${{ env.AWS_REGION }} + + echo "āœ… Exam IDE auto-scaling configured" + else + echo "āœ… Exam IDE auto-scaling already configured" + fi + + # ==================== VERIFICATION ==================== + - name: Verify Both Deployments + run: | + echo "šŸ” Verifying deployments..." + echo "" + + echo "šŸ“Š MAIN IDE Status:" + aws ecs describe-services \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ env.ECS_SERVICE_MAIN }} \ + --region ${{ env.AWS_REGION }} \ + --query 'services[0].{Service:serviceName,Status:status,Running:runningCount,Desired:desiredCount}' \ + --output table + + echo "" + echo "šŸ“Š EXAM IDE Status:" + aws ecs describe-services \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ env.ECS_SERVICE_EXAM }} \ + --region ${{ env.AWS_REGION }} \ + --query 'services[0].{Service:serviceName,Status:status,Running:runningCount,Desired:desiredCount}' \ + --output table + + - name: Post-Deployment Summary + run: | + echo "" + echo "šŸŽ‰ DEPLOYMENT SUMMARY" + echo "=====================" + echo "āœ… Docker image built and pushed to ECR" + echo "āœ… Main IDE (pythonide-service) deployed" + echo "āœ… Exam IDE (pythonide-exam-task-service) deployed" + echo "āœ… Auto-scaling verified for both services" + echo "" + echo "šŸ”— Services:" + echo " • Main IDE: http://pythonide-classroom.tech/editor" + echo " • Exam IDE: http://exam.pythonide-classroom.tech/editor" + echo "" + echo "šŸ“ˆ Both services now running the same codebase (commit: ${{ github.sha }})" diff --git a/LOCAL_TEST_ACCOUNTS.md b/LOCAL_TEST_ACCOUNTS.md deleted file mode 100644 index 96dfa2f2..00000000 --- a/LOCAL_TEST_ACCOUNTS.md +++ /dev/null @@ -1,70 +0,0 @@ -# Local Docker Test Accounts - -These accounts are specifically for testing the **main IDE** on your local Docker setup (localhost:10086). - -## Quick Setup - -Run this command while your Docker containers are running: - -```bash -docker exec pythonide-app python init_local_test_accounts.py -``` - -This will create/update local test accounts with simple passwords. - -## Test Credentials - -### Student Accounts (for testing student features) - -| Username | Password | Full Name | -|----------|----------|-----------| -| **local_student1** | test123 | Local Test Student 1 | -| **local_student2** | test123 | Local Test Student 2 | -| **local_student3** | test123 | Local Test Student 3 | - -### Professor Account (for testing admin features) - -| Username | Password | Full Name | -|----------|----------|-----------| -| **local_prof** | prof123 | Local Test Professor | - -## Access - -- **URL**: http://localhost:10086 -- **Database**: Local PostgreSQL (separate from production) -- **Storage**: Docker volume `pythonide-clean_user_files` - -## Features - -Each test account gets: -- Personal directory at `Local/{username}/` -- Sample `welcome.py` file -- Full access to IDE features -- Isolated from production accounts - -## Notes - -- These accounts use `.local` email domain to distinguish them from production -- Passwords are intentionally simple for easy local testing -- Safe to delete/recreate anytime without affecting production -- The script can be run multiple times (it updates existing accounts) - -## Differences from Production - -| Aspect | Local Test | Production (AWS) | -|--------|-----------|------------------| -| Database | Local PostgreSQL | AWS RDS | -| Port | 10086 | 80/443 via ALB | -| Accounts | `local_*` test accounts | Real student accounts | -| Storage | Docker volume | AWS EFS | -| Domain | `*.test.local` | `*.college.edu` | - -## Production Accounts - -The production accounts (from CSV file) like `sa8820`, `test_1`, etc. may also exist in your local database, but their passwords are complex and stored in the CSV file at: - -``` -adminData/docker_local_credentials_20251013_170659.csv -``` - -**Use the `local_*` accounts for easy testing instead!** diff --git a/backup/repl_backup_20251027_003330/DualModeREPL.js b/backup/repl_backup_20251027_003330/DualModeREPL.js deleted file mode 100644 index 9ed1e6bb..00000000 --- a/backup/repl_backup_20251027_003330/DualModeREPL.js +++ /dev/null @@ -1,473 +0,0 @@ -// Dual-Mode REPL Implementation for VmIde.vue -// This module provides both backend WebSocket REPL and Pyodide browser-based REPL - -export const DualModeREPL = { - data() { - return { - // REPL mode configuration - replMode: 'auto', // 'backend', 'pyodide', or 'auto' - pyodideNamespace: null, - pyodideInitialized: false, - }; - }, - - methods: { - // Initialize and start REPL session with automatic mode detection - async startDualModeReplSession() { - this.replSessionId = Date.now().toString(); - console.log('šŸš€ [REPL] Starting dual-mode REPL session:', this.replSessionId); - console.log('šŸ“Š [REPL] Current state:', { - wsInfo: this.wsInfo, - wsConnected: this.wsInfo?.connected, - wsReadyState: this.wsInfo?.rws?.readyState, - replMode: this.replMode, - pyodideReady: this.pyodideReady, - pyodideInitialized: this.pyodideInitialized - }); - - // Ensure REPL console exists - this.ensureReplConsole(); - - // Determine which mode to use - const backendAvailable = this.wsInfo && this.wsInfo.connected; - const preferBackend = this.replMode === 'backend' || (this.replMode === 'auto' && backendAvailable); - - console.log('šŸ” [REPL] Mode detection:', { - backendAvailable, - preferBackend, - replMode: this.replMode - }); - - if (preferBackend && backendAvailable) { - console.log('āœ… [REPL] Using backend mode'); - await this.startBackendRepl(); - } else { - console.log('🌐 [REPL] Using Pyodide browser mode'); - await this.startPyodideRepl(); - } - }, - - // Start backend REPL via WebSocket - async startBackendRepl() { - console.log('šŸ”Œ [REPL] Starting backend REPL...'); - // Removed startup messages per user request - - try { - // Send start command via WebSocket - const startMsg = { - cmd: 'start_python_repl', - id: this.replSessionId, - data: { - projectName: this.ideInfo.currProj?.data?.name || 'repl' - } - }; - - console.log('šŸ“¤ [REPL] Sending start message:', startMsg); - console.log('šŸ”Œ [REPL] WebSocket state:', { - rws: this.wsInfo.rws, - readyState: this.wsInfo.rws?.readyState, - OPEN: WebSocket.OPEN - }); - - // Send directly via WebSocket - if (this.wsInfo.rws && this.wsInfo.rws.readyState === WebSocket.OPEN) { - this.wsInfo.rws.send(JSON.stringify(startMsg)); - console.log('āœ… [REPL] Start message sent successfully'); - // Removed startup messages per user request - } else { - throw new Error(`WebSocket not connected: readyState=${this.wsInfo.rws?.readyState}`); - } - } catch (error) { - console.error('āŒ [REPL] Backend REPL failed:', error); - this.addReplOutput('Backend connection lost. Reconnecting...', 'system'); - - // Try to reconnect instead of immediately switching to Pyodide - setTimeout(async () => { - if (this.wsInfo?.rws?.readyState === WebSocket.OPEN) { - console.log('šŸ”„ [REPL] Retrying backend REPL after reconnection'); - await this.startBackendRepl(); - } else { - console.log('🌐 [REPL] Backend still unavailable, switching to browser mode'); - this.addReplOutput('Backend unavailable, switching to browser mode...', 'error'); - await this.startPyodideRepl(); - } - }, 2000); // Wait 2 seconds for potential reconnection - } - }, - - // Start Pyodide REPL in browser - async startPyodideRepl() { - console.log('🌐 [REPL] Starting Pyodide REPL, current state:', { - pyodideReady: this.pyodideReady, - pyodideInitialized: this.pyodideInitialized - }); - - if (!this.pyodideReady) { - console.log('šŸ”„ [REPL] Pyodide not ready, initializing...'); - await this.initializePyodideForRepl(); - } - - if (this.pyodideReady) { - console.log('āœ… [REPL] Pyodide ready for use'); - // Welcome message already added by startReplSession() - } else { - console.log('āŒ [REPL] Failed to initialize Pyodide'); - this.addReplOutput('Failed to initialize browser Python', 'error'); - this.isReplMode = false; - } - }, - - // Initialize Pyodide environment - async initializePyodideForRepl() { - console.log('šŸ”„ [REPL] initializePyodideForRepl called, state:', { - pyodideLoading: this.pyodideLoading, - pyodideReady: this.pyodideReady, - loadPyodideAvailable: typeof window.loadPyodide - }); - - if (this.pyodideLoading || this.pyodideReady) { - console.log('āš ļø [REPL] Pyodide already loading or ready, skipping'); - return; - } - - this.pyodideLoading = true; - // Removed loading message for cleaner startup - - try { - // Check if Pyodide script is loaded - if (typeof window.loadPyodide !== 'function') { - throw new Error('Pyodide not available. Please check internet connection.'); - } - - console.log('šŸ“¦ [REPL] Loading Pyodide from CDN...'); - - // Load Pyodide - this.pyodide = await window.loadPyodide({ - indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/' - }); - - // Create execution namespace - this.pyodideNamespace = this.pyodide.globals.get('dict')(); - - // Setup Python environment - await this.pyodide.runPythonAsync(` - import sys - import io - import builtins - - # Store the version - python_version = sys.version - - # Setup for REPL - _ = None - `); - - this.pyodideReady = true; - this.pyodideLoading = false; - this.pyodideInitialized = true; - console.log('āœ… [REPL] Pyodide REPL ready!', { - pyodide: this.pyodide, - namespace: this.pyodideNamespace - }); - - } catch (error) { - console.error('āŒ [REPL] Pyodide initialization failed:', error); - this.pyodideLoading = false; - this.pyodideReady = false; - this.addReplOutput(`Error: ${error.message}`, 'error'); - } - }, - - // Execute command in appropriate REPL - async executeReplCommandDualMode(command) { - console.log('šŸŽÆ [REPL] executeReplCommandDualMode called with:', command); - - if (!command.trim()) { - console.log('āš ļø [REPL] Empty command, handling continuation mode'); - if (this.replContinuationMode) { - this.replContinuationMode = false; - this.replPrompt = '>>> '; - } - return; - } - - // Store in history - if (this.replHistory[this.replHistory.length - 1] !== command) { - this.replHistory.push(command); - } - this.replHistoryIndex = -1; - - // Clear input - this.replInput = ''; - this.replInputRows = 1; - - // Echo command with proper REPL input format - if (this.ideInfo.consoleSelected && this.ideInfo.consoleSelected.id) { - this.$store.commit('ide/addConsoleOutput', { - id: this.ideInfo.consoleSelected.id, - type: 'repl-input', - text: command, - prompt: this.replPrompt - }); - } - - // Determine execution mode - const useBackend = this.wsInfo?.connected && this.replSessionId && this.replMode !== 'pyodide'; - - console.log('šŸ” [REPL] Execution mode check:', { - wsConnected: this.wsInfo?.connected, - replSessionId: this.replSessionId, - replMode: this.replMode, - useBackend, - pyodideReady: this.pyodideReady - }); - - if (useBackend) { - console.log('šŸ“¤ [REPL] Executing via backend'); - await this.executeBackendCommand(command); - } else if (this.pyodideReady) { - console.log('🌐 [REPL] Executing via Pyodide'); - await this.executePyodideCommand(command); - } else { - console.log('āš ļø [REPL] No environment ready, initializing Pyodide...'); - // Try to initialize Pyodide as fallback - await this.startPyodideRepl(); - if (this.pyodideReady) { - await this.executePyodideCommand(command); - } else { - this.addReplOutput('No Python environment available', 'error'); - } - } - }, - - // Execute command on backend - async executeBackendCommand(command) { - console.log('šŸ“¤ [REPL] executeBackendCommand called with:', command); - try { - const inputMsg = { - cmd: 'send_program_input', - id: Date.now().toString(), - data: { - program_id: this.replSessionId, - input: command - } - }; - - console.log('šŸ“Ø [REPL] Sending command message:', inputMsg); - - if (this.wsInfo.rws && this.wsInfo.rws.readyState === WebSocket.OPEN) { - this.wsInfo.rws.send(JSON.stringify(inputMsg)); - console.log('āœ… [REPL] Command sent to backend'); - } else { - throw new Error(`WebSocket disconnected: readyState=${this.wsInfo.rws?.readyState}`); - } - } catch (error) { - console.error('āŒ [REPL] Backend execution failed:', error); - this.addReplOutput('Backend connection lost. Please restart REPL if needed.', 'system'); - - // Don't automatically switch to Pyodide for individual command failures - // This preserves the backend session state when connection is restored - console.log('āš ļø [REPL] Keeping backend mode, user can manually restart if needed'); - } - }, - - // Execute command in Pyodide - async executePyodideCommand(command) { - if (!this.pyodideReady || !this.pyodide) { - this.addReplOutput('Browser Python not ready', 'error'); - return; - } - - try { - // Setup output capture - await this.pyodide.runPythonAsync(` - import sys - from io import StringIO - _stdout = StringIO() - _stderr = StringIO() - _old_stdout = sys.stdout - _old_stderr = sys.stderr - sys.stdout = _stdout - sys.stderr = _stderr - `); - - let result = null; - let executionError = null; - - try { - // Execute command - result = await this.pyodide.runPythonAsync(command, { globals: this.pyodideNamespace }); - - // Store result in _ variable for REPL behavior - if (result !== undefined && result !== null) { - await this.pyodide.runPythonAsync('_ = _result', { - globals: {...this.pyodideNamespace.toJs(), _result: result} - }); - } - } catch (error) { - executionError = error; - } - - // Get output - const output = await this.pyodide.runPythonAsync(` - sys.stdout = _old_stdout - sys.stderr = _old_stderr - stdout_val = _stdout.getvalue() - stderr_val = _stderr.getvalue() - _stdout.close() - _stderr.close() - (stdout_val, stderr_val) - `); - - // Display stdout - if (output[0]) { - this.addReplOutput(output[0], 'output'); - } - - // Display stderr - if (output[1]) { - this.addReplOutput(output[1], 'error'); - } - - // Display error if any - if (executionError) { - this.addReplOutput(String(executionError), 'error'); - } else if (result !== undefined && result !== null) { - // Display result if not None - const pyNone = this.pyodide.globals.get('None'); - if (result !== pyNone) { - try { - // Get string representation - const reprStr = await this.pyodide.runPythonAsync( - `repr(_last_val)`, - {globals: {...this.pyodideNamespace.toJs(), _last_val: result}} - ); - if (reprStr && reprStr !== 'None') { - this.addReplOutput(reprStr, 'output'); - } - } catch (e) { - // Fallback to toString - const str = String(result); - if (str && str !== '[object Object]') { - this.addReplOutput(str, 'output'); - } - } - } - } - - // Update prompt for continuation (always use >>> per user preference) - this.replContinuationMode = false; - this.replPrompt = '>>> '; - - } catch (error) { - console.error('Pyodide execution error:', error); - this.addReplOutput(`Error: ${error.message}`, 'error'); - } - - // Scroll console to bottom - this.$nextTick(() => { - if (this.$refs.consoleOutputArea) { - this.$refs.consoleOutputArea.scrollTop = this.$refs.consoleOutputArea.scrollHeight; - } - }); - }, - - // Handle backend REPL responses - handleBackendReplResponse(message) { - console.log('šŸ“„ [REPL] Backend response received:', { - code: message?.code, - hasData: !!message?.data, - dataKeys: message?.data ? Object.keys(message.data) : [], - fullMessage: message - }); - - if (!message) { - console.log('āš ļø [REPL] Empty message received'); - return; - } - - // Handle response codes - if (message.code === 0 || message.code === '0') { - // Output from REPL - if (message.data) { - if (message.data.stdout) { - // Filter out prompts and continuation markers - const output = message.data.stdout; - // Don't show standalone prompts, "..." continuation lines, or lines starting with "..." - // More comprehensive filtering for all prompt variations - const isPromptOnly = ( - output.match(/^(>>>|\.\.\.)\s*$/) || // ">>> " or "... " - output.match(/^(>>>|\.\.\.)\s*\n*$/) || // with optional newlines - output.trim() === '>>>' || // just ">>>" - output.trim() === '...' || // just "..." - output.trim() === '' || // empty/whitespace only - output === '>>> ' || // exact prompt match - output === '... ' || // exact continuation match - output.match(/^\s*>>>\s*$/) || // >>> with any whitespace - output.match(/^\s*\.\.\.\s*$/) || // ... with any whitespace - output.match(/^>>>\s*\n*$/) || // >>> with trailing newlines - output.match(/^\.\.\.\s*\n*$/) // ... with trailing newlines - ); - - if (!isPromptOnly && output.trim() !== '') { - this.addReplOutput(output, 'output'); - } - } - if (message.data.stderr) { - this.addReplOutput(message.data.stderr, 'error'); - } - } - } else if (message.code === 2000) { - // REPL prompt update (always use >>> per user preference) - if (message.data?.type === 'repl_prompt') { - this.replPrompt = '>>> '; - this.replContinuationMode = false; - } else if (message.data?.stdout) { - this.addReplOutput(message.data.stdout, 'output'); - } - } else if (message.code === 1111) { - // Session ended - this.addReplOutput('Backend session ended', 'system'); - this.replSessionId = null; - // Auto-switch to Pyodide - this.addReplOutput('Switching to browser mode...', 'system'); - this.startPyodideRepl(); - } - - // Scroll to bottom - this.$nextTick(() => { - if (this.$refs.consoleOutputArea) { - this.$refs.consoleOutputArea.scrollTop = this.$refs.consoleOutputArea.scrollHeight; - } - }); - }, - - // Stop REPL session - async stopDualModeReplSession() { - // Stop backend session if active - if (this.replSessionId && this.wsInfo?.rws?.readyState === WebSocket.OPEN) { - const stopMsg = { - cmd: 'stop_python_program', - id: Date.now().toString(), - data: { - program_id: this.replSessionId - } - }; - this.wsInfo.rws.send(JSON.stringify(stopMsg)); - } - - // Clean up - this.replSessionId = null; - this.isReplMode = false; - this.replPrompt = '>>> '; - this.replContinuationMode = false; - - // Clean up Pyodide namespace but keep Pyodide loaded - if (this.pyodideNamespace) { - this.pyodideNamespace = this.pyodide?.globals.get('dict')(); - } - } - } -}; - -export default DualModeREPL; \ No newline at end of file diff --git a/backup/repl_backup_20251027_003330/HybridConsole.vue b/backup/repl_backup_20251027_003330/HybridConsole.vue deleted file mode 100644 index 9699e32e..00000000 --- a/backup/repl_backup_20251027_003330/HybridConsole.vue +++ /dev/null @@ -1,1113 +0,0 @@ -