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
1 change: 1 addition & 0 deletions Libs/pollilib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './src/mcp.js';
export * from './src/binary.js';
export * from './src/errors.js';
export * from './src/sse.js';
export * from './src/defaults.js';
7 changes: 6 additions & 1 deletion Libs/pollilib/src/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,13 @@ function buildTtsParams(options) {
assignIfPresent(params, 'language', extras.language);
delete extras.language;

if ('referer' in extras && extras.referer) {
params.referer = extras.referer;
delete extras.referer;
}

if ('referrer' in extras && extras.referrer) {
params.referrer = extras.referrer;
params.referer = params.referer ?? extras.referrer;
delete extras.referrer;
}

Expand Down
40 changes: 30 additions & 10 deletions Libs/pollilib/src/client.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DEFAULT_REFERRER, DEFAULT_TOKEN } from './defaults.js';

const DEFAULT_ENDPOINTS = {
image: 'https://image.pollinations.ai',
text: 'https://text.pollinations.ai',
Expand All @@ -18,6 +20,20 @@ export class PolliClient {
defaultHeaders = {},
} = options ?? {};

let authOptions = auth;
let referrerOption = referrer;
let tokenOption = token;
let tokenProviderOption = tokenProvider;

if (!authOptions && !tokenOption && !tokenProviderOption) {
authOptions = {
mode: 'token',
placement: 'query',
token: DEFAULT_TOKEN,
referrer: referrerOption ?? undefined,
};
}

const impl = fetchImpl ?? globalThis.fetch;
if (typeof impl !== 'function') {
throw new Error('PolliClient requires a fetch implementation');
Expand All @@ -34,7 +50,12 @@ export class PolliClient {
this.textBase = resolvedBases.text;
this.timeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 60_000;
this.defaultHeaders = normalizeHeaderBag(defaultHeaders);
this._auth = createAuthManager({ auth, referrer, token, tokenProvider });
this._auth = createAuthManager({
auth: authOptions,
referrer: referrerOption,
token: tokenOption,
tokenProvider: tokenProviderOption,
});
}

get authMode() {
Expand Down Expand Up @@ -307,7 +328,7 @@ function isBodyObject(value) {
}

function createAuthManager({ auth, referrer, token, tokenProvider } = {}) {
const fallbackReferrer = referrer ?? inferReferrer();
const fallbackReferrer = referrer ?? inferReferrer() ?? DEFAULT_REFERRER;
if (auth) {
if (auth.mode === 'none') {
return new AuthManager({ mode: 'none', referrer: null, placement: 'header', getToken: async () => null });
Expand Down Expand Up @@ -374,12 +395,11 @@ class AuthManager {

async apply({ method, url, headers, body, includeReferrer, includeToken, tokenPlacement }) {
if (includeReferrer !== false && this.referrer) {
if ((method === 'GET' || method === 'HEAD') && !url.searchParams.has('referrer')) {
url.searchParams.set('referrer', this.referrer);
} else if (isBodyObject(body) && body.referrer == null) {
body.referrer = this.referrer;
} else if (!url.searchParams.has('referrer')) {
url.searchParams.set('referrer', this.referrer);
if (!url.searchParams.has('referer')) {
url.searchParams.set('referer', this.referrer);
}
if (isBodyObject(body) && body.referer == null) {
body.referer = this.referrer;
}
}

Expand Down Expand Up @@ -407,8 +427,8 @@ class AuthManager {
}

async decorateUrl(url, { includeReferrer = true, includeToken = false, tokenPlacement } = {}) {
if (includeReferrer && this.referrer && !url.searchParams.has('referrer')) {
url.searchParams.set('referrer', this.referrer);
if (includeReferrer && this.referrer && !url.searchParams.has('referer')) {
url.searchParams.set('referer', this.referrer);
}
if (includeToken && this.mode === 'token') {
const token = await this.getToken();
Expand Down
4 changes: 4 additions & 0 deletions Libs/pollilib/src/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const DEFAULT_MODEL = 'unity';
export const DEFAULT_SEED = '12345678';
export const DEFAULT_REFERRER = 'https://www.unityailab.com';
export const DEFAULT_TOKEN = 'POLLI_TOKEN';
18 changes: 15 additions & 3 deletions Libs/pollilib/src/image.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getDefaultClient } from './client.js';
import { DEFAULT_MODEL, DEFAULT_SEED } from './defaults.js';
import { BinaryData } from './binary.js';
import { raiseForStatus } from './errors.js';

Expand Down Expand Up @@ -41,10 +42,16 @@ function buildImageParams(options) {
const params = {};
const extras = { ...options };

assignIfPresent(params, 'model', extras.model);
const model = extras.model ?? DEFAULT_MODEL;
if (model) {
params.model = model;
}
delete extras.model;

assignIfPresent(params, 'seed', extras.seed);
const seed = extras.seed ?? DEFAULT_SEED;
if (seed != null) {
params.seed = seed;
}
delete extras.seed;

assignIfPresent(params, 'width', extras.width);
Expand Down Expand Up @@ -91,8 +98,13 @@ function buildImageParams(options) {
delete extras.high_contrast;
delete extras.highContrast;

if ('referer' in extras && extras.referer) {
params.referer = extras.referer;
delete extras.referer;
}

if ('referrer' in extras && extras.referrer) {
params.referrer = extras.referrer;
params.referer = params.referer ?? extras.referrer;
delete extras.referrer;
}

Expand Down
53 changes: 42 additions & 11 deletions Libs/pollilib/src/text.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getDefaultClient } from './client.js';
import { DEFAULT_MODEL, DEFAULT_SEED } from './defaults.js';
import { sseEvents } from './sse.js';
import { raiseForStatus } from './errors.js';

Expand All @@ -7,12 +8,14 @@ export async function text(prompt, options = {}, client = getDefaultClient()) {
const { stream = false, timeoutMs, ...rest } = options ?? {};

const params = buildTextParams(rest);
const url = `${client.textBase}/${encodeURIComponent(normalizedPrompt)}`;
if (!('input' in params)) {
params.input = normalizedPrompt;
}
const url = `${client.textBase}/openai`;

if (stream) {
params.stream = 'true';
const response = await client.get(url, {
params,
params: { ...params, stream: 'true' },
headers: { Accept: 'text/event-stream' },
timeoutMs: timeoutMs ?? 0,
});
Expand All @@ -35,14 +38,15 @@ export async function text(prompt, options = {}, client = getDefaultClient()) {
}

export async function chat(options = {}, client = getDefaultClient()) {
const { body, stream, timeoutMs } = buildChatPayload(options);
const { body, stream, timeoutMs, query } = buildChatPayload(options);
const url = `${client.textBase}/openai`;

if (stream) {
body.stream = true;
const response = await client.postJson(url, body, {
headers: { Accept: 'text/event-stream' },
timeoutMs: timeoutMs ?? 0,
params: query,
});
if (!response.ok) {
await raiseForStatus(response, 'chat (stream)', { consumeBody: false });
Expand All @@ -57,7 +61,7 @@ export async function chat(options = {}, client = getDefaultClient()) {
})();
}

const response = await client.postJson(url, body, { timeoutMs });
const response = await client.postJson(url, body, { timeoutMs, params: query });
await raiseForStatus(response, 'chat');
return await response.json();
}
Expand Down Expand Up @@ -140,10 +144,16 @@ function buildTextParams(options) {
const params = {};
const extras = { ...options };

assignIfPresent(params, 'model', extras.model);
const model = extras.model ?? DEFAULT_MODEL;
if (model) {
params.model = model;
}
delete extras.model;

assignIfPresent(params, 'seed', extras.seed);
const seed = extras.seed ?? DEFAULT_SEED;
if (seed != null) {
params.seed = seed;
}
delete extras.seed;

assignIfPresent(params, 'temperature', pickFirst(extras, ['temperature']));
Expand Down Expand Up @@ -181,8 +191,13 @@ function buildTextParams(options) {
delete extras.private;
}

if ('referer' in extras && extras.referer) {
params.referer = extras.referer;
delete extras.referer;
}

if ('referrer' in extras && extras.referrer) {
params.referrer = extras.referrer;
params.referer = params.referer ?? extras.referrer;
delete extras.referrer;
}

Expand All @@ -196,11 +211,15 @@ function buildTextParams(options) {

function buildChatPayload(options = {}) {
const extras = { ...options };
const model = extras.model;
const model = extras.model ?? DEFAULT_MODEL;
delete extras.model;

if (!model) {
throw new Error('chat() requires a model');
}
delete extras.model;

const seed = extras.seed ?? DEFAULT_SEED;
delete extras.seed;

const messages = normalizeMessages(extras.messages ?? [], extras.system ?? extras.systemPrompt);
if (!messages.length) {
Expand All @@ -213,6 +232,10 @@ function buildChatPayload(options = {}) {

const body = { model, messages };

if (seed != null) {
body.seed = seed;
}

if ('private' in extras) {
body.private = !!extras.private;
delete extras.private;
Expand Down Expand Up @@ -256,7 +279,15 @@ function buildChatPayload(options = {}) {
body[key] = value;
}

return { body, stream, timeoutMs };
const query = {};
if (model) {
query.model = model;
}
if (seed != null) {
query.seed = seed;
}

return { body, stream, timeoutMs, query };
}

function normalizeMessages(messages, systemPrompt) {
Expand Down
9 changes: 2 additions & 7 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import './style.css';
import { chat, image, textModels, tts } from '../Libs/pollilib/index.js';
import { chat, image, textModels, tts, DEFAULT_SEED } from '../Libs/pollilib/index.js';
import { createPollinationsClient } from './pollinations-client.js';
import {
createFallbackModel,
matchesModelIdentifier,
normalizeTextCatalog,
} from './model-catalog.js';
import { doesResponseMatchModel, isMatchingModelName } from './model-matching.js';
import { generateSeed } from './seed.js';

const FALLBACK_MODELS = [
createFallbackModel('openai', 'OpenAI GPT-5 Nano (fallback)'),
Expand Down Expand Up @@ -433,7 +432,6 @@ async function handleChatResponse(initialResponse, model, endpoint) {
messages: state.conversation,
tools: [IMAGE_TOOL],
tool_choice: 'auto',
seed: generateSeed(),
},
client,
);
Expand Down Expand Up @@ -540,14 +538,13 @@ async function generateImageAsset(prompt, { width, height, model: imageModel } =
if (!client) {
throw new Error('Pollinations client is not ready.');
}
const seed = generateSeed();
const seed = DEFAULT_SEED;
const binary = await image(
prompt,
{
width,
height,
model: imageModel,
seed,
nologo: true,
private: true,
enhance: true,
Expand Down Expand Up @@ -755,15 +752,13 @@ 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
20 changes: 15 additions & 5 deletions src/pollinations-client.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import { PolliClient } from '../Libs/pollilib/index.js';
import { PolliClient, DEFAULT_REFERRER, DEFAULT_TOKEN } from '../Libs/pollilib/index.js';

let tokenPromise = null;
let cachedResult = null;

export async function createPollinationsClient({ referrer } = {}) {
const tokenResult = await ensureToken();
const { token, source, messages = [], errors = [] } = tokenResult;
const inferredReferrer = referrer ?? inferReferrer();
const inferredReferrer = referrer ?? inferReferrer() ?? DEFAULT_REFERRER;

const clientOptions = {};
if (inferredReferrer) {
clientOptions.referrer = inferredReferrer;
}

if (token) {
clientOptions.auth = {
mode: 'token',
placement: 'query',
getToken: async () => token,
referrer: inferredReferrer ?? undefined,
};
} else if (inferredReferrer) {
clientOptions.referrer = inferredReferrer;
}

const client = new PolliClient(clientOptions);
return {
client,
tokenSource: token ? source : null,
tokenSource: token ? source ?? null : null,
tokenMessages: messages,
tokenErrors: errors,
};
Expand Down Expand Up @@ -81,6 +83,14 @@ async function resolveToken() {
const messages = errors
.map(entry => formatError(entry.source, entry.error))
.filter(Boolean);
if (DEFAULT_TOKEN) {
return {
token: DEFAULT_TOKEN,
source: 'default',
errors,
messages,
};
}
return {
token: null,
source: null,
Expand Down
Loading