import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = process.env.PORT || 7860; app.use(express.json()); // Mandatory Endpoint: /health app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', service: 'Velo' }); }); // Mandatory Endpoint: /api-docs app.get('/api-docs', (req, res) => { const html = ` Velo API Documentation

Velo API Documentation

Velo - AI-powered novel writing IDE API Endpoints

GET /health

Purpose: Readiness and liveness check for Hugging Face Spaces.

Response Example:

{
  "status": "ok",
  "service": "Velo"
}

GET /api-docs

Purpose: Returns documentation for all available API endpoints.

POST /predict

Purpose: Predict story continuation or next scene elements based on context input.

Request Example:

{
  "prompt": "The old clock struck midnight as Elena entered the library...",
  "maxLength": 100
}

Response Example:

{
  "continuation": "A faint blue light gleamed from behind the bookshelf...",
  "status": "success"
}

POST /generate

Purpose: Generate narrative suggestions or plot outlines.

Request Example:

{
  "genre": "Sci-Fi",
  "topic": "First contact with an ancient artifact"
}

Response Example:

{
  "outline": "Chapter 1: Discovery in the Kuiper Belt...",
  "status": "success"
}

GET /v1/models

Purpose: OpenAI-compatible models listing endpoint.

Response Example:

{
  "object": "list",
  "data": [
    { "id": "velo-default", "object": "model", "created": 1700000000, "owned_by": "velo" }
  ]
}

POST /v1/chat/completions

Purpose: OpenAI-compatible chat completions endpoint. Supports proxying to a custom upstream OpenAI-compatible Base URL via custom_url / base_url parameter or header.

Request Example:

{
  "model": "velo-default",
  "messages": [
    { "role": "user", "content": "Write a short scene about a wizard." }
  ],
  "custom_url": "https://api.openai.com/v1"
}

Response Example:

{
  "id": "chatcmpl-velo-1234567890",
  "object": "chat.completion",
  "created": 1700000000,
  "model": "velo-default",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The old wizard carefully opened the ancient spellbook..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 10, "completion_tokens": 15, "total_tokens": 25 }
}
`; res.setHeader('Content-Type', 'text/html'); res.send(html); }); // Functional Endpoint: /predict app.post('/predict', (req, res) => { const { prompt, maxLength = 100 } = req.body || {}; if (!prompt) { return res.status(400).json({ error: 'Missing required parameter: prompt' }); } res.json({ continuation: `[Velo Model Output for: "${prompt.slice(0, 30)}..."] A mysterious shadow glided across the floor, leaving behind glowing cyan runes.`, maxLength, status: 'success' }); }); // Functional Endpoint: /generate app.post('/generate', (req, res) => { const { genre = 'General', topic = 'Unspecified' } = req.body || {}; res.json({ genre, topic, outline: `[Generated Outline for ${genre} - ${topic}]:\n1. Introduction of characters\n2. Inciting incident\n3. Climax & Resolution`, status: 'success' }); }); // OpenAI-Compatible Endpoint: /v1/models app.get('/v1/models', (req, res) => { res.json({ object: 'list', data: [ { id: 'velo-default', object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'velo' }, { id: 'gpt-4o', object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'velo' }, { id: 'deepseek-chat', object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'velo' } ] }); }); // OpenAI-Compatible Endpoint: /v1/chat/completions with Custom URL Support app.post('/v1/chat/completions', async (req, res) => { const { model = 'velo-default', messages = [], custom_url, base_url } = req.body || {}; // Check if a custom upstream URL is specified in body, header, or query const targetUrl = custom_url || base_url || req.headers['x-custom-url'] || req.headers['x-base-url']; if (targetUrl) { try { // Normalize upstream URL to ensure /chat/completions path let cleanBase = String(targetUrl).replace(/\/+$/, ''); if (!cleanBase.endsWith('/chat/completions')) { if (!cleanBase.endsWith('/v1')) cleanBase += '/v1'; cleanBase += '/chat/completions'; } const upstreamHeaders = { 'Content-Type': 'application/json', }; if (req.headers.authorization) { upstreamHeaders['Authorization'] = req.headers.authorization; } const upstreamBody = { ...req.body }; delete upstreamBody.custom_url; delete upstreamBody.base_url; const response = await fetch(cleanBase, { method: 'POST', headers: upstreamHeaders, body: JSON.stringify(upstreamBody), }); const responseData = await response.json(); return res.status(response.status).json(responseData); } catch (err) { return res.status(502).json({ error: { message: `Failed to proxy to custom URL: ${err.message}`, type: 'proxy_error', code: 502 } }); } } // Fallback response when no custom upstream URL is proxied const lastUserMsg = messages.slice().reverse().find((m) => m.role === 'user')?.content || 'Hello'; const responseContent = `[Velo AI Response] Story continuation & creative suggestion for input: "${String(lastUserMsg).slice(0, 50)}"`; res.json({ id: `chatcmpl-velo-${Date.now()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model, choices: [ { index: 0, message: { role: 'assistant', content: responseContent }, finish_reason: 'stop' } ], usage: { prompt_tokens: lastUserMsg.length, completion_tokens: responseContent.length, total_tokens: lastUserMsg.length + responseContent.length } }); }); // Serve static frontend build from dist folder const distPath = path.join(__dirname, 'dist'); app.use(express.static(distPath)); // Fallback to index.html for SPA routing (Express 5 compatible) app.use((req, res) => { res.sendFile(path.join(distPath, 'index.html')); }); app.listen(PORT, '0.0.0.0', () => { console.log(`Velo app listening on port ${PORT}`); });