Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,27 @@ make sure the contents of `dist/` are deployed.

## Configuring the Pollinations token

Pollinations models that require tiered access need a token on every request. The application now
expects the token to be provided at runtime so it is never bundled into the static assets.
Pollinations models that require tiered access expect the token to be supplied as a request
parameter. The demo can resolve the token at runtime (via URL parameters, meta tags, or injected
globals) and also honours build-time environment variables when you want to bake the token into the
bundle.

- **GitHub Pages / production** – Provide the `POLLI_TOKEN` secret in the repository (or Pages
environment). The included Pages Function at `.github/functions/polli-token.js` exposes the token
at runtime via `/api/polli-token`, and responses are marked as non-cacheable.
- **Local development** – Either define `POLLI_TOKEN`/`VITE_POLLI_TOKEN` in your shell when running
`npm run dev`, add a `<meta name="pollinations-token" ...>` tag to `index.html`, or inject
`window.__POLLINATIONS_TOKEN__` before the application bootstraps.
- **Static overrides** – When a dynamic endpoint is unavailable, append a `token` query parameter
to the page URL (e.g. `https://example.github.io/chatdemo/?token=your-secret`). The application
will capture the token, remove it from the visible URL, and apply it to subsequent Pollinations
requests.

If the token cannot be resolved the UI remains disabled and an error is shown in the status banner.
environment). You can surface the token to the client by setting `window.__POLLINATIONS_TOKEN__`,
defining a `<meta name="pollinations-token" content="...">` tag, adding a `token=...` query
parameter to the published URL (e.g. `https://example.github.io/chatdemo/?token=your-secret`), or
injecting `POLLI_TOKEN`/`VITE_POLLI_TOKEN` during the build so the token ships with the bundle.
The token is removed from the visible URL after it is captured.
- **Local development** – Define `POLLI_TOKEN`/`VITE_POLLI_TOKEN` in your shell when running
`npm run dev`, add a meta tag as above, or inject `window.__POLLINATIONS_TOKEN__` before the
application bootstraps. Build-time environment variables also work in development.
- **Optional runtime endpoint** – If you expose the token via a custom endpoint, configure its URL
with `POLLI_TOKEN_ENDPOINT`/`VITE_POLLI_TOKEN_ENDPOINT` (environment variables),
`window.__POLLINATIONS_TOKEN_ENDPOINT__`, or a `<meta name="pollinations-token-endpoint" ...>` tag.
When present, the client will fetch the token from that endpoint.

If the token cannot be resolved the application continues without one, allowing you to browse public
models while gated Pollinations models remain unavailable until a token is supplied.

All chat and image requests automatically include a random eight-digit `seed` parameter so they
match Pollinations' expected request format.
36 changes: 32 additions & 4 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
matchesModelIdentifier,
normalizeTextCatalog,
} from './model-catalog.js';
import { generateSeed } from './seed.js';

const FALLBACK_MODELS = [
createFallbackModel('openai', 'OpenAI GPT-5 Nano (fallback)'),
Expand Down Expand Up @@ -443,6 +444,7 @@ async function handleChatResponse(initialResponse, model, endpoint) {
messages: state.conversation,
tools: [IMAGE_TOOL],
tool_choice: 'auto',
seed: generateSeed(),
},
client,
);
Expand Down Expand Up @@ -499,7 +501,7 @@ async function handleToolCalls(toolCalls) {
const caption = String(args.caption ?? prompt).trim() || prompt;

try {
const { dataUrl } = await generateImageAsset(prompt, {
const { dataUrl, seed } = await generateImageAsset(prompt, {
width,
height,
model: args.model,
Expand All @@ -520,6 +522,7 @@ async function handleToolCalls(toolCalls) {
prompt,
width,
height,
seed,
}),
});
} catch (error) {
Expand All @@ -545,12 +548,14 @@ async function generateImageAsset(prompt, { width, height, model: imageModel } =
if (!client) {
throw new Error('Pollinations client is not ready.');
}
const seed = generateSeed();
const binary = await image(
prompt,
{
width,
height,
model: imageModel,
seed,
nologo: true,
private: true,
enhance: true,
Expand All @@ -559,7 +564,7 @@ async function generateImageAsset(prompt, { width, height, model: imageModel } =
);
const dataUrl = binary.toDataUrl();
resetStatusIfIdle();
return { dataUrl };
return { dataUrl, seed };
} catch (error) {
console.error('Image generation failed', error);
throw error;
Expand Down Expand Up @@ -770,13 +775,15 @@ async function requestChatCompletion(model, endpoints) {
const attemptErrors = [];
for (const endpoint of endpoints) {
try {
const requestSeed = generateSeed();
const response = await chat(
{
model: model.id,
endpoint,
messages: state.conversation,
tools: [IMAGE_TOOL],
tool_choice: 'auto',
seed: requestSeed,
},
client,
);
Expand Down Expand Up @@ -907,11 +914,27 @@ async function initializeApp() {
els.voicePlayback.checked = false;
}

let tokenSource = null;
let tokenMessages = [];

try {
const { client: polliClient, tokenSource } = await createPollinationsClient();
const {
client: polliClient,
tokenSource: resolvedTokenSource,
tokenMessages: resolvedTokenMessages,
} = await createPollinationsClient();
client = polliClient;
tokenSource = resolvedTokenSource;
tokenMessages = Array.isArray(resolvedTokenMessages) ? resolvedTokenMessages : [];
if (tokenSource) {
console.info('Pollinations token loaded via %s.', tokenSource);
} else if (tokenMessages.length) {
console.warn(
'Proceeding without a Pollinations token. Attempts: %s',
tokenMessages.join('; '),
);
} else {
console.info('Proceeding without a Pollinations token.');
}
} catch (error) {
console.error('Failed to configure Pollinations client', error);
Expand All @@ -928,6 +951,10 @@ async function initializeApp() {
setLoading(false);
}

if (!tokenSource && !state.statusError) {
setStatus('Ready. Pollinations token not configured; only public models are available.');
}

try {
setupRecognition();
} catch (error) {
Expand Down Expand Up @@ -955,14 +982,15 @@ els.form.addEventListener('submit', async event => {
if (!prompt) {
throw new Error('Provide a prompt after /image');
}
const { dataUrl } = await generateImageAsset(prompt);
const { dataUrl, seed } = await generateImageAsset(prompt);
addMessage({
role: 'assistant',
type: 'image',
url: dataUrl,
alt: prompt,
caption: prompt,
});
console.info('Generated Pollinations image with seed %s.', seed);
resetStatusIfIdle();
} else {
await sendPrompt(raw);
Expand Down
Loading