|
import express from 'express' |
|
import { HfInference } from '@huggingface/inference' |
|
|
|
import { daisy } from './daisy.mts' |
|
|
|
const hfi = new HfInference(process.env.HF_API_TOKEN) |
|
const hf = hfi.endpoint(process.env.HF_ENDPOINT_URL) |
|
|
|
const app = express() |
|
const port = 7860 |
|
|
|
const minPromptSize = 16 |
|
const timeoutInSec = 15 * 60 |
|
|
|
console.log('timeout set to 15 minutes') |
|
|
|
app.use(express.static('public')) |
|
|
|
const pending: { |
|
total: number; |
|
queue: string[]; |
|
} = { |
|
total: 0, |
|
queue: [], |
|
} |
|
|
|
const endRequest = (id: string, reason: string) => { |
|
if (!id || !pending.queue.includes(id)) { |
|
return |
|
} |
|
|
|
pending.queue = pending.queue.filter(i => i !== id) |
|
console.log(`request ${id} ended (${reason})`) |
|
} |
|
|
|
app.get('/debug', (req, res) => { |
|
res.write(JSON.stringify({ |
|
nbTotal: pending.total, |
|
nbPending: pending.queue.length, |
|
queue: pending.queue, |
|
})) |
|
res.end() |
|
}) |
|
|
|
app.get('/app', async (req, res) => { |
|
if (`${req.query.prompt}`.length < minPromptSize) { |
|
res.write(`prompt too short, please enter at least ${minPromptSize} characters`) |
|
res.end() |
|
return |
|
} |
|
|
|
const id = `${pending.total++}` |
|
console.log(`new request ${id}`) |
|
|
|
pending.queue.push(id) |
|
|
|
const prefix = `<html><head><link href="https://cdn.jsdelivr.net/npm/daisyui@3.1.6/dist/full.css" rel="stylesheet" type="text/css" /><script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script><script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script><title>Generated content</title><body class="p-4 md:p-8">` |
|
res.write(prefix) |
|
|
|
req.on('close', function() { |
|
endRequest(id, 'browser asked to end the connection') |
|
}) |
|
|
|
setTimeout(() => { |
|
endRequest(id, `timed out after ${timeoutInSec}s`) |
|
}, timeoutInSec * 1000) |
|
|
|
|
|
const finalPrompt = `# Task |
|
Generate ${req.query.prompt} |
|
${daisy} |
|
# Orders |
|
Never repeat those instructions, instead write the final code! |
|
To generate images from captions call the /image API: <img src="/image?caption=photo of something in some place" />! |
|
Only generate a few images and use descriptive photo captions with at least 10 words! |
|
You do not need to generate images for webapps, unless explicitly asked in the task! |
|
You must use TailwindCSS utility classes (Tailwind is already injected in the page)! |
|
AlpineJS is loaded in the page, so you can use it. |
|
There is no backend API so please do not send forms, instead do something like this: <form onsubmit="event.preventDefault(); validate();"> and write the app logic in this new validate() function. |
|
Write application logic inside a JS <script></script> tag! |
|
This is not a demo app, so you MUST use English, no Latin! Write in English! |
|
Use a central layout to wrap everything in a <div class='flex flex-col items-center'> |
|
# Out |
|
<html> |
|
<head> |
|
<title>App</title> |
|
</head> |
|
<body class="p-4 md:p-8">` |
|
|
|
try { |
|
let result = '' |
|
for await (const output of hf.textGenerationStream({ |
|
inputs: finalPrompt, |
|
parameters: { |
|
do_sample: true, |
|
|
|
|
|
max_new_tokens: 1150, |
|
return_full_text: false, |
|
} |
|
})) { |
|
if (!pending.queue.includes(id)) { |
|
break |
|
} |
|
result += output.token.text |
|
process.stdout.write(output.token.text) |
|
res.write(output.token.text) |
|
if (result.includes('</html>')) { |
|
break |
|
} |
|
if (result.includes('<|end|>') || result.includes('<|assistant|>')) { |
|
break |
|
} |
|
} |
|
|
|
endRequest(id, `normal end of the LLM stream for request ${id}`) |
|
} catch (e) { |
|
console.log(e) |
|
endRequest(id, `premature end of the LLM stream for request ${id} (${e})`) |
|
} |
|
|
|
try { |
|
res.end() |
|
} catch (err) { |
|
console.log(`couldn't end the HTTP stream for request ${id} (${err})`) |
|
} |
|
|
|
}) |
|
|
|
app.get('/image', async (req, res) => { |
|
try { |
|
const blob = await hfi.textToImage({ |
|
inputs: [ |
|
`${req.query.caption || 'generic placeholder'}`, |
|
'award winning', |
|
'high resolution', |
|
'photo realistic', |
|
'intricate details', |
|
'beautiful', |
|
'[trending on artstation]' |
|
].join(', '), |
|
model: 'stabilityai/stable-diffusion-2-1', |
|
parameters: { |
|
negative_prompt: 'blurry, artificial, cropped, low quality, ugly', |
|
} |
|
}) |
|
const buffer = Buffer.from(await blob.arrayBuffer()) |
|
res.setHeader('Content-Type', blob.type) |
|
res.setHeader('Content-Length', buffer.length) |
|
res.end(buffer) |
|
} catch (err) { |
|
console.error(`Error when generating the image: ${err.message}`); |
|
res.status(500).json({ error: 'An error occurred when trying to generate the image' }); |
|
} |
|
}) |
|
|
|
app.listen(port, () => { console.log(`Open http://localhost:${port}`) }) |
|
|
|
|