import os import time import subprocess import requests import traceback import gradio as gr # Node-related files stored directly in Python dictionaries MAIN_FILES = { '.env': """HF_API_TOKEN=YOUR_HF_API_TOKEN_HERE NODE_ENV=production PORT=3000 RATE_LIMIT_WINDOW_MS=60000 RATE_LIMIT_MAX=100 HF_API_URL=https://api-inference.huggingface.co/models/distilbert-base-uncased-finetuned-sst-2-english """, 'package.json': """{ "name": "compassionate-community", "version": "1.0.0", "description": "A sentiment-based story ordering web app", "main": "server.js", "scripts": { "start": "node server.js" }, "dependencies": { "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", "helmet": "^6.0.1", "node-fetch": "^3.3.1", "pino": "^8.5.0", "pino-pretty": "^10.0.0", "express-rate-limit": "^6.7.0" }, "engines": { "node": ">=16.0.0" } } """, 'server.js': """const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const rateLimit = require('express-rate-limit'); const { port, nodeEnv, rateLimitWindowMs, rateLimitMax } = require('./config'); const sentimentRoutes = require('./routes/sentiment'); const logger = require('./logger'); const app = express(); app.use(helmet()); app.use(cors()); app.use(express.json()); const limiter = rateLimit({ windowMs: rateLimitWindowMs, max: rateLimitMax, message: { error: 'Too many requests, please try again later' } }); app.use(limiter); app.use('/sentiment', sentimentRoutes); app.use(express.static('public')); app.use((req,res)=>{ res.status(404).json({error:'Not Found'}); }); app.use((err,req,res,next)=>{ logger.error({ err }, 'Unhandled error'); res.status(500).json({error:'Internal Server Error'}); }); app.listen(port, () => { logger.info(`Server running on http://localhost:${port} in ${nodeEnv} mode`); }); """, 'config.js': """require('dotenv').config(); const requiredVars = ['HF_API_TOKEN', 'HF_API_URL', 'PORT', 'RATE_LIMIT_WINDOW_MS', 'RATE_LIMIT_MAX']; requiredVars.forEach(v => { if (!process.env[v]) { console.error(`ERROR: Missing required environment variable ${v}`); process.exit(1); } }); module.exports = { hfApiToken: process.env.HF_API_TOKEN, hfApiUrl: process.env.HF_API_URL, port: process.env.PORT, rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10), rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX, 10), nodeEnv: process.env.NODE_ENV || 'development' }; """, 'logger.js': """const pino = require('pino'); module.exports = pino({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty', options: { colorize: true } } : undefined }); """, 'routes/sentiment.js': """const express = require('express'); const router = express.Router(); const { getSentiment } = require('../utils/huggingface'); const logger = require('../logger'); router.post('/', async (req, res) => { const { text } = req.body; if (!text) { return res.status(400).json({ error: 'No text provided' }); } try { const sentiment = await getSentiment(text); return res.json({ sentiment }); } catch (err) { logger.error({ err }, 'Error fetching sentiment'); return res.status(500).json({ error: 'Internal server error' }); } }); module.exports = router; """, 'utils/huggingface.js': """const fetch = require('node-fetch'); const { hfApiToken, hfApiUrl } = require('../config'); const logger = require('../logger'); async function getSentiment(text) { const response = await fetch(hfApiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${hfApiToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ inputs: text }) }); if (!response.ok) { logger.error(`Hugging Face API error: ${response.status} - ${response.statusText}`); throw new Error(`HF API request failed with status ${response.status}`); } const data = await response.json(); if (!Array.isArray(data) || !data[0]) { throw new Error('Unexpected HF API response format'); } const { label, score } = data[0]; return label === 'NEGATIVE' ? 1 - score : score; } module.exports = { getSentiment }; """, 'Dockerfile': """FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . EXPOSE 3000 CMD ["npm", "start"] """, 'README.md': """# Compassionate Community A sentiment-based web service using Hugging Face. Add your HF token to .env or set it as a Space secret. """ } PUBLIC_FILES = { 'index.html': """
This page served by Node.js backend. Use the Gradio interface to test sentiment analysis.
""", 'style.css': """body { font-family: Arial, sans-serif; margin: 20px; background: #f7f7f7; } h1 { font-size: 24px; margin-bottom: 10px; } """ } def create_project_files(): logs = [] base_dir = "compassionate-community" try: # Check for HF_API_TOKEN from secret (Space) hf_api_token = os.getenv("HF_API_TOKEN", None) if not os.path.exists(base_dir): os.makedirs(base_dir) logs.append(f"Created directory: {base_dir}") for fname, content in MAIN_FILES.items(): fpath = os.path.join(base_dir, fname) if not os.path.exists(fpath): if fname == '.env' and hf_api_token: content = content.replace("YOUR_HF_API_TOKEN_HERE", hf_api_token) with open(fpath, 'w', encoding='utf-8') as f: f.write(content) logs.append(f"Created file: {fpath}") public_dir = os.path.join(base_dir, "public") if not os.path.exists(public_dir): os.makedirs(public_dir) logs.append(f"Created directory: {public_dir}") for fname, content in PUBLIC_FILES.items(): fpath = os.path.join(public_dir, fname) if not os.path.exists(fpath): with open(fpath, 'w', encoding='utf-8') as f: f.write(content) logs.append(f"Created file: {fpath}") logs.append("Project structure set up successfully!") except Exception as e: logs.append("ERROR during setup:") logs.append(str(e)) logs.append(traceback.format_exc()) return "\n".join(logs) def start_node_server(): base_dir = "compassionate-community" # Check if token is set with open(os.path.join(base_dir, '.env'), 'r', encoding='utf-8') as envf: env_content = envf.read() if "YOUR_HF_API_TOKEN_HERE" in env_content: return "No valid HF_API_TOKEN provided. Please set it as a secret or edit .env." try: subprocess.check_call(["npm", "install"], cwd=base_dir) except Exception as e: return f"Failed npm install: {e}" subprocess.Popen(["npm", "start"], cwd=base_dir) # Wait up to 30s for Node server to start for i in range(30): try: r = requests.get("http://localhost:3000") if r.status_code in (200, 404): return "Node server running at http://localhost:3000" except: pass time.sleep(1) return "Node server did not start within 30 seconds." def get_sentiment(text): # Check token again with open("compassionate-community/.env", 'r', encoding='utf-8') as envf: env_content = envf.read() if "YOUR_HF_API_TOKEN_HERE" in env_content: return "Warning: No HF_API_TOKEN set. Cannot perform sentiment analysis." url = "http://localhost:3000/sentiment" try: r = requests.post(url, json={"text": text}, timeout=10) if r.status_code == 200: data = r.json() return f"Sentiment score: {data['sentiment']:.2f}" else: return f"Error: {r.status_code} {r.text}" except Exception as e: return f"Request failed: {e}" setup_logs = create_project_files() server_logs = start_node_server() def query_interface(input_text): return get_sentiment(input_text) with gr.Blocks() as demo: gr.Markdown("# Compassionate Community Full Service\n") gr.Markdown("**Setup Logs:**") gr.Textbox(value=setup_logs, label="Setup Logs", interactive=False) gr.Markdown("**Server Status:**") gr.Textbox(value=server_logs, label="Server Status", interactive=False) gr.Markdown("**Test the Sentiment Service:**") input_text = gr.Textbox(placeholder="Enter text describing a struggle...") output = gr.Textbox(label="Output") run_button = gr.Button("Analyze Sentiment") run_button.click(query_interface, inputs=input_text, outputs=output) demo.launch(server_name="0.0.0.0", server_port=7860)