Spaces:
Runtime error
Runtime error
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': """<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"/> | |
<meta name="viewport" content="width=device-width,initial-scale=1.0"/> | |
<title>Compassionate Community</title> | |
<link rel="stylesheet" href="style.css"/> | |
</head> | |
<body> | |
<h1>Compassionate Community</h1> | |
<p>This page served by Node.js backend. Use the Gradio interface to test sentiment analysis.</p> | |
</body> | |
</html> | |
""", | |
'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) |