opencode-pollinations-plugin 6.1.0-beta.9 → 6.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +130 -0
- package/README.es.md +130 -0
- package/README.fr.md +130 -0
- package/README.it.md +130 -0
- package/README.md +87 -73
- package/dist/index.js +52 -161
- package/dist/locales/de.json +374 -0
- package/dist/locales/en.json +373 -0
- package/dist/locales/es.json +374 -0
- package/dist/locales/fr.json +373 -0
- package/dist/locales/index.d.ts +1 -0
- package/dist/locales/index.js +37 -0
- package/dist/locales/it.json +374 -0
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +394 -125
- package/dist/server/config.d.ts +34 -23
- package/dist/server/config.js +200 -108
- package/dist/server/connect-response.d.ts +2 -0
- package/dist/server/connect-response.js +59 -0
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +164 -106
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/logger.d.ts +8 -0
- package/dist/server/logger.js +38 -0
- package/dist/server/models/cache.d.ts +35 -0
- package/dist/server/models/cache.js +160 -0
- package/dist/server/models/fetcher.d.ts +18 -0
- package/dist/server/models/fetcher.js +194 -0
- package/dist/server/models/index.d.ts +6 -0
- package/dist/server/models/index.js +5 -0
- package/dist/server/models/manual.d.ts +15 -0
- package/dist/server/models/manual.js +92 -0
- package/dist/server/models/types.d.ts +55 -0
- package/dist/server/models/types.js +7 -0
- package/dist/server/models/worker.d.ts +22 -0
- package/dist/server/models/worker.js +174 -0
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +21 -8
- package/dist/server/proxy.js +222 -307
- package/dist/server/quota.d.ts +2 -0
- package/dist/server/quota.js +89 -86
- package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
- package/dist/server/scripts/pollinations_pricing.js +246 -0
- package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
- package/dist/server/scripts/test_cost_endpoints.js +61 -0
- package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
- package/dist/server/scripts/test_dynamic_pricing.js +39 -0
- package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
- package/dist/server/scripts/test_freetier_audit.js +215 -0
- package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
- package/dist/server/scripts/test_parallel_cost.js +104 -0
- package/dist/server/toast.d.ts +7 -1
- package/dist/server/toast.js +43 -10
- package/dist/tools/design/gen_diagram.d.ts +2 -0
- package/dist/tools/design/gen_diagram.js +94 -0
- package/dist/tools/design/gen_palette.d.ts +2 -0
- package/dist/tools/design/gen_palette.js +182 -0
- package/dist/tools/design/gen_qrcode.d.ts +2 -0
- package/dist/tools/design/gen_qrcode.js +50 -0
- package/dist/tools/ffmpeg.d.ts +24 -0
- package/dist/tools/ffmpeg.js +54 -0
- package/dist/tools/index.d.ts +25 -0
- package/dist/tools/index.js +86 -0
- package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
- package/dist/tools/pollinations/beta_discovery.js +201 -0
- package/dist/tools/pollinations/cost-guard.d.ts +38 -0
- package/dist/tools/pollinations/cost-guard.js +136 -0
- package/dist/tools/pollinations/deepsearch.d.ts +7 -0
- package/dist/tools/pollinations/deepsearch.js +80 -0
- package/dist/tools/pollinations/gen_audio.d.ts +18 -0
- package/dist/tools/pollinations/gen_audio.js +220 -0
- package/dist/tools/pollinations/gen_image.d.ts +11 -0
- package/dist/tools/pollinations/gen_image.js +211 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +157 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +249 -0
- package/dist/tools/pollinations/polli_config.d.ts +2 -0
- package/dist/tools/pollinations/polli_config.js +95 -0
- package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
- package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
- package/dist/tools/pollinations/polli_status.d.ts +2 -0
- package/dist/tools/pollinations/polli_status.js +31 -0
- package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
- package/dist/tools/pollinations/polli_web_search.js +126 -0
- package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
- package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
- package/dist/tools/pollinations/shared.d.ts +181 -0
- package/dist/tools/pollinations/shared.js +758 -0
- package/dist/tools/pollinations/test_estimators.d.ts +1 -0
- package/dist/tools/pollinations/test_estimators.js +22 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
- package/dist/tools/pollinations/transcribe_audio.js +171 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +179 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +237 -0
- package/dist/tools/power/file_to_url.d.ts +2 -0
- package/dist/tools/power/file_to_url.js +217 -0
- package/dist/tools/power/remove_background.d.ts +2 -0
- package/dist/tools/power/remove_background.js +404 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +79 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +80 -0
- package/package.json +9 -3
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_image Tool - Pollinations Image Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-19 - Dynamic ModelRegistry + Cost Guard + Toasts
|
|
5
|
+
*
|
|
6
|
+
* All models are dynamic from the Pollinations API.
|
|
7
|
+
* Unknown models are accepted as (beta) and passed through to the API.
|
|
8
|
+
* Cost Guard reads enablePaidTools, costConfirmationRequired, costThreshold.
|
|
9
|
+
*/
|
|
10
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { getApiKey, hasApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateImageCost, extractCostFromHeaders, isCostEstimatorEnabled, supportsI2I, getPaidImageModels, fetchEnterBalance, sanitizeFilename, validateHttpUrl, } from './shared.js';
|
|
14
|
+
import { loadConfig } from '../../server/config.js';
|
|
15
|
+
import { checkCostControl, isTokenBased } from './cost-guard.js';
|
|
16
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
17
|
+
import { t } from '../../locales/index.js';
|
|
18
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
19
|
+
const DEFAULT_MODEL = 'flux';
|
|
20
|
+
// ─── Tool Definition ──────────────────────────────────────────────────────
|
|
21
|
+
export const polliGenImageTool = tool({
|
|
22
|
+
description: t('tools.image.desc'),
|
|
23
|
+
args: {
|
|
24
|
+
prompt: tool.schema.string().describe(t('tools.image.arg_prompt')),
|
|
25
|
+
model: tool.schema.string().describe(t('tools.image.arg_model')),
|
|
26
|
+
width: tool.schema.number().min(256).max(4096).optional().describe(t('tools.image.arg_width')),
|
|
27
|
+
height: tool.schema.number().min(256).max(4096).optional().describe(t('tools.image.arg_height')),
|
|
28
|
+
reference_image: tool.schema.string().optional().describe(t('tools.image.arg_ref')),
|
|
29
|
+
seed: tool.schema.number().optional().describe(t('tools.image.arg_seed')),
|
|
30
|
+
quality: tool.schema.enum(['low', 'med', 'high']).optional().describe(t('tools.image.arg_quality')),
|
|
31
|
+
transparent: tool.schema.boolean().optional().describe(t('tools.image.arg_trans')),
|
|
32
|
+
save_to: tool.schema.string().optional().describe(t('tools.image.arg_save_to')),
|
|
33
|
+
filename: tool.schema.string().optional().describe(t('tools.image.arg_filename')),
|
|
34
|
+
},
|
|
35
|
+
async execute(args, context) {
|
|
36
|
+
const apiKey = getApiKey();
|
|
37
|
+
const hasKey = hasApiKey();
|
|
38
|
+
// Determine model based on key presence
|
|
39
|
+
let model = args.model;
|
|
40
|
+
const width = args.width || 1024;
|
|
41
|
+
const height = args.height || 1024;
|
|
42
|
+
// Fetch known models from registry
|
|
43
|
+
const imageModels = getPaidImageModels();
|
|
44
|
+
const knownModel = !!imageModels[model];
|
|
45
|
+
const isBetaModel = !knownModel;
|
|
46
|
+
// Force Auth Check for ALL Image Generations
|
|
47
|
+
if (!hasKey) {
|
|
48
|
+
return t('tools.image.req_key', { models: Object.keys(imageModels).slice(0, 5).join(', ') });
|
|
49
|
+
}
|
|
50
|
+
// Unknown model → beta passthrough (don't reject)
|
|
51
|
+
if (isBetaModel) {
|
|
52
|
+
emitStatusToast('warning', t('tools.image.unreferenced_model', { model }), '🎨 gen_image');
|
|
53
|
+
}
|
|
54
|
+
// Validate I2I support (for known models only; beta models get default behavior)
|
|
55
|
+
if (args.reference_image) {
|
|
56
|
+
if (!validateHttpUrl(args.reference_image)) {
|
|
57
|
+
return t('tools.image.req_url_i2i') || '❌ URL invalide. Utilisez http:// ou https://';
|
|
58
|
+
}
|
|
59
|
+
if (knownModel && !supportsI2I(model)) {
|
|
60
|
+
const models = Object.entries(imageModels)
|
|
61
|
+
.filter(([, info]) => info.i2i)
|
|
62
|
+
.map(([name]) => name)
|
|
63
|
+
.join(', ');
|
|
64
|
+
return t('tools.image.no_i2i', { model, models });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Estimate cost
|
|
68
|
+
const estimatedCost = estimateImageCost(model);
|
|
69
|
+
// Cost Guard check V2
|
|
70
|
+
const costCheck = checkCostControl('polli_gen_image', args, model, estimatedCost, 'image');
|
|
71
|
+
if (!costCheck.allowed) {
|
|
72
|
+
return costCheck.message || t('tools.image.blocked');
|
|
73
|
+
}
|
|
74
|
+
// Emit start toast
|
|
75
|
+
const config = loadConfig();
|
|
76
|
+
const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
|
|
77
|
+
emitStatusToast('info', t('tools.image.generating', { model, width, height }) + argsStr, '🎨 polli_gen_image');
|
|
78
|
+
// Set metadata
|
|
79
|
+
context.metadata({ title: `🎨 Image: ${model}${isBetaModel ? ' (beta)' : ''}` });
|
|
80
|
+
try {
|
|
81
|
+
let imageData;
|
|
82
|
+
let responseHeaders = {};
|
|
83
|
+
let usedModel = model;
|
|
84
|
+
// === ENTER endpoint ONLY (gen.pollinations.ai) ===
|
|
85
|
+
const params = new URLSearchParams({
|
|
86
|
+
nologo: 'true',
|
|
87
|
+
private: 'true',
|
|
88
|
+
width: String(width),
|
|
89
|
+
height: String(height),
|
|
90
|
+
});
|
|
91
|
+
// Model parameter
|
|
92
|
+
params.set('model', model);
|
|
93
|
+
// Seed
|
|
94
|
+
if (args.seed !== undefined) {
|
|
95
|
+
params.set('seed', String(args.seed));
|
|
96
|
+
}
|
|
97
|
+
// I2I: reference image(s)
|
|
98
|
+
if (args.reference_image) {
|
|
99
|
+
params.set('image', args.reference_image);
|
|
100
|
+
}
|
|
101
|
+
// Quality (gptimage only)
|
|
102
|
+
if (args.quality && model.startsWith('gptimage')) {
|
|
103
|
+
params.set('quality', args.quality);
|
|
104
|
+
}
|
|
105
|
+
// Transparent (gptimage only)
|
|
106
|
+
if (args.transparent !== undefined && model.startsWith('gptimage')) {
|
|
107
|
+
params.set('transparent', String(args.transparent));
|
|
108
|
+
}
|
|
109
|
+
const promptEncoded = encodeURIComponent(args.prompt);
|
|
110
|
+
const url = `https://gen.pollinations.ai/image/${promptEncoded}?${params}`;
|
|
111
|
+
const headers = {};
|
|
112
|
+
if (apiKey)
|
|
113
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
114
|
+
// 1. Fetch balance avant génération
|
|
115
|
+
const balBefore = await fetchEnterBalance();
|
|
116
|
+
const result = await httpsGet(url, headers);
|
|
117
|
+
imageData = result.data;
|
|
118
|
+
responseHeaders = result.headers;
|
|
119
|
+
// Update used model from response if available
|
|
120
|
+
if (responseHeaders['x-model-used']) {
|
|
121
|
+
usedModel = responseHeaders['x-model-used'];
|
|
122
|
+
}
|
|
123
|
+
// Save the image
|
|
124
|
+
let outputDir = getDefaultOutputDir('images');
|
|
125
|
+
let filename = args.filename ? sanitizeFilename(args.filename) : undefined;
|
|
126
|
+
if (args.save_to) {
|
|
127
|
+
if (args.save_to.match(/\.(png|jpe?g|webp|gif)$/i)) {
|
|
128
|
+
outputDir = path.dirname(args.save_to);
|
|
129
|
+
filename = path.basename(args.save_to);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
outputDir = args.save_to;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
ensureDir(outputDir);
|
|
136
|
+
filename = filename || generateFilename('image', usedModel, 'png');
|
|
137
|
+
const filePath = path.join(outputDir, filename.includes('.') ? filename : `${filename}.png`);
|
|
138
|
+
fs.writeFileSync(filePath, imageData);
|
|
139
|
+
// 2. Fetch balance après génération (delay for API sync)
|
|
140
|
+
let balAfter = null;
|
|
141
|
+
let realCost;
|
|
142
|
+
if (balBefore !== null) {
|
|
143
|
+
await new Promise(r => setTimeout(r, 1000)); // Laisse le temps au ledger
|
|
144
|
+
balAfter = await fetchEnterBalance();
|
|
145
|
+
if (balAfter !== null) {
|
|
146
|
+
realCost = Math.round((balBefore - balAfter) * 10000) / 10000;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Extract cost from headers as fallback/info
|
|
150
|
+
const costTracking = extractCostFromHeaders(responseHeaders);
|
|
151
|
+
// Build result
|
|
152
|
+
const fileSize = fs.statSync(filePath).size;
|
|
153
|
+
const lines = [];
|
|
154
|
+
// Inject costWarning at top if present
|
|
155
|
+
if (costCheck.message && !costCheck.allowed) { // Assuming costWarning should come from costCheck if not allowed
|
|
156
|
+
lines.push(costCheck.message);
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
lines.push(t('tools.image.res_title'));
|
|
160
|
+
lines.push(`━━━━━━━━━━━━━━━━━━`);
|
|
161
|
+
lines.push(t('tools.image.res_prompt', { prompt: args.prompt.substring(0, 100) + (args.prompt.length > 100 ? '...' : '') }));
|
|
162
|
+
lines.push(t('tools.image.res_model', { model: `${usedModel}${isBetaModel ? ' (beta)' : ''}` }));
|
|
163
|
+
lines.push(t('tools.image.res_res', { width, height }));
|
|
164
|
+
// Add I2I info if used
|
|
165
|
+
if (args.reference_image) {
|
|
166
|
+
lines.push(t('tools.image.res_i2i_src', { src: args.reference_image.substring(0, 50) + '...' }));
|
|
167
|
+
}
|
|
168
|
+
lines.push(t('tools.image.res_file', { path: filePath }));
|
|
169
|
+
lines.push(t('tools.image.res_size', { size: formatFileSize(fileSize) }));
|
|
170
|
+
// Pricing details (Estimé vs Réel)
|
|
171
|
+
if (isCostEstimatorEnabled()) {
|
|
172
|
+
const maxCost = estimatedCost * 3;
|
|
173
|
+
lines.push(t('tools.image.cost_title'));
|
|
174
|
+
if (isTokenBased('image', usedModel)) {
|
|
175
|
+
lines.push(`- Cost : ${formatCost(estimatedCost)} (Max théorique: ${formatCost(maxCost)})`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
lines.push(t('tools.image.cost_estimated', { cost: formatCost(estimatedCost) }));
|
|
179
|
+
}
|
|
180
|
+
if (realCost !== undefined) {
|
|
181
|
+
lines.push(t('tools.image.cost_real_wallet', { cost: formatCost(realCost) }));
|
|
182
|
+
}
|
|
183
|
+
else if (costTracking.costUsd !== undefined) {
|
|
184
|
+
lines.push(t('tools.image.cost_real_api', { cost: formatCost(costTracking.costUsd) }));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
lines.push(t('tools.image.cost_unknown'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (responseHeaders['x-request-id']) {
|
|
191
|
+
lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
|
|
192
|
+
}
|
|
193
|
+
// Emit success toast
|
|
194
|
+
emitStatusToast('success', t('tools.image.success', { model: usedModel }), '🎨 gen_image', { filePath: filePath });
|
|
195
|
+
return lines.join('\n');
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
emitStatusToast('error', t('tools.image.error', { error: err.message?.substring(0, 60) }), '🎨 gen_image');
|
|
199
|
+
if (err.message?.includes('402') || err.message?.includes('Payment')) {
|
|
200
|
+
return t('tools.image.insufficient_funds', { model });
|
|
201
|
+
}
|
|
202
|
+
if (err.message?.includes('401') || err.message?.includes('403')) {
|
|
203
|
+
return t('tools.image.invalid_key');
|
|
204
|
+
}
|
|
205
|
+
if (err.message?.includes('400')) {
|
|
206
|
+
return t('tools.image.invalid_params', { error: err.message });
|
|
207
|
+
}
|
|
208
|
+
return t('tools.image.gen_error_msg', { error: err.message });
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_music Tool - Pollinations Music Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Model: elevenmusic (ElevenLabs Music)
|
|
7
|
+
* Endpoint: gen.pollinations.ai/audio/{text}
|
|
8
|
+
*
|
|
9
|
+
* Parameters:
|
|
10
|
+
* - duration: 3-300 seconds
|
|
11
|
+
* - instrumental: boolean (vocals or instrumental only)
|
|
12
|
+
*/
|
|
13
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
14
|
+
export declare const polliGenMusicTool: ToolDefinition;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_music Tool - Pollinations Music Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Model: elevenmusic (ElevenLabs Music)
|
|
7
|
+
* Endpoint: gen.pollinations.ai/audio/{text}
|
|
8
|
+
*
|
|
9
|
+
* Parameters:
|
|
10
|
+
* - duration: 3-300 seconds
|
|
11
|
+
* - instrumental: boolean (vocals or instrumental only)
|
|
12
|
+
*/
|
|
13
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { getApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateMusicCost, extractCostFromHeaders, isCostEstimatorEnabled, sanitizeFilename, } from './shared.js';
|
|
17
|
+
import { loadConfig } from '../../server/config.js';
|
|
18
|
+
import { checkCostControl, isTokenBased } from './cost-guard.js';
|
|
19
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
20
|
+
import { t } from '../../locales/index.js';
|
|
21
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
22
|
+
const DEFAULT_DURATION = 10;
|
|
23
|
+
const MODEL_NAME = 'elevenmusic';
|
|
24
|
+
// ─── Tool Definition ──────────────────────────────────────────────────────
|
|
25
|
+
export const polliGenMusicTool = tool({
|
|
26
|
+
description: t('tools.polli_gen_music.desc'),
|
|
27
|
+
args: {
|
|
28
|
+
prompt: tool.schema.string().describe(t('tools.polli_gen_music.arg_prompt')),
|
|
29
|
+
duration: tool.schema.number().optional()
|
|
30
|
+
.describe(t('tools.polli_gen_music.arg_duration', { default: DEFAULT_DURATION, max: 300 })),
|
|
31
|
+
instrumental: tool.schema.boolean().optional().describe(t('tools.polli_gen_music.arg_instrumental')),
|
|
32
|
+
seed: tool.schema.number().optional().describe(t('tools.polli_gen_music.arg_seed')),
|
|
33
|
+
save_to: tool.schema.string().optional().describe(t('tools.polli_gen_music.arg_save_to')),
|
|
34
|
+
filename: tool.schema.string().optional().describe(t('tools.polli_gen_music.arg_filename')),
|
|
35
|
+
},
|
|
36
|
+
async execute(args, context) {
|
|
37
|
+
const apiKey = getApiKey();
|
|
38
|
+
if (!apiKey) {
|
|
39
|
+
return t('tools.polli_gen_music.req_key');
|
|
40
|
+
}
|
|
41
|
+
// Get dynamic range from ModelRegistry (populated via OpenAPI)
|
|
42
|
+
const { getMusicModel } = await import('./shared.js');
|
|
43
|
+
const modelConfig = getMusicModel()[MODEL_NAME];
|
|
44
|
+
const [minDuration, maxDuration] = modelConfig?.duration || [3, 300];
|
|
45
|
+
const duration = Math.min(Math.max(args.duration || DEFAULT_DURATION, minDuration), maxDuration);
|
|
46
|
+
const instrumental = args.instrumental || false;
|
|
47
|
+
// Estimate cost
|
|
48
|
+
const estimatedCost = estimateMusicCost(duration);
|
|
49
|
+
// Cost Guard check V2
|
|
50
|
+
const costCheck = checkCostControl('polli_gen_music', args, MODEL_NAME, estimatedCost, 'audio');
|
|
51
|
+
if (!costCheck.allowed) {
|
|
52
|
+
return costCheck.message || t('tools.polli_gen_music.blocked');
|
|
53
|
+
}
|
|
54
|
+
// Estimate generation time
|
|
55
|
+
const genTimeSeconds = Math.ceil(duration * 1.2);
|
|
56
|
+
// Emit start toast
|
|
57
|
+
const config = loadConfig();
|
|
58
|
+
const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
|
|
59
|
+
emitStatusToast('info', t('tools.polli_gen_music.toast_start', { duration, time: genTimeSeconds }) + argsStr, '🎵 polli_gen_music');
|
|
60
|
+
// Metadata
|
|
61
|
+
context.metadata({ title: `🎵 Music: ${duration}s (~${genTimeSeconds}s gen time)` });
|
|
62
|
+
try {
|
|
63
|
+
// Build URL
|
|
64
|
+
const params = new URLSearchParams({
|
|
65
|
+
model: MODEL_NAME,
|
|
66
|
+
nologo: 'true',
|
|
67
|
+
private: 'true',
|
|
68
|
+
duration: String(duration),
|
|
69
|
+
});
|
|
70
|
+
if (instrumental) {
|
|
71
|
+
params.set('instrumental', 'true');
|
|
72
|
+
}
|
|
73
|
+
// Seed for reproducibility
|
|
74
|
+
if (args.seed !== undefined) {
|
|
75
|
+
params.set('seed', String(args.seed));
|
|
76
|
+
}
|
|
77
|
+
const promptEncoded = encodeURIComponent(args.prompt);
|
|
78
|
+
const url = `https://gen.pollinations.ai/audio/${promptEncoded}?${params}`;
|
|
79
|
+
const headers = {
|
|
80
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
81
|
+
};
|
|
82
|
+
// Music generation takes time
|
|
83
|
+
const result = await httpsGet(url, headers);
|
|
84
|
+
const audioData = result.data;
|
|
85
|
+
const responseHeaders = result.headers;
|
|
86
|
+
// Save audio
|
|
87
|
+
let outputDir = getDefaultOutputDir('music');
|
|
88
|
+
let filename = args.filename ? sanitizeFilename(args.filename) : undefined;
|
|
89
|
+
if (args.save_to) {
|
|
90
|
+
if (args.save_to.match(/\.(mp3|wav|ogg|m4a)$/i)) {
|
|
91
|
+
outputDir = path.dirname(args.save_to);
|
|
92
|
+
filename = path.basename(args.save_to);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
outputDir = args.save_to;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
ensureDir(outputDir);
|
|
99
|
+
filename = filename || generateFilename('music', MODEL_NAME, 'mp3');
|
|
100
|
+
const filePath = path.join(outputDir, filename.endsWith('.mp3') ? filename : `${filename}.mp3`);
|
|
101
|
+
fs.writeFileSync(filePath, audioData);
|
|
102
|
+
const fileSize = fs.statSync(filePath).size;
|
|
103
|
+
let actualCost = estimatedCost;
|
|
104
|
+
if (responseHeaders) {
|
|
105
|
+
const costTracking = extractCostFromHeaders(responseHeaders);
|
|
106
|
+
if (costTracking.costUsd !== undefined)
|
|
107
|
+
actualCost = costTracking.costUsd;
|
|
108
|
+
}
|
|
109
|
+
// Build result
|
|
110
|
+
const lines = [];
|
|
111
|
+
// Inject costWarning at top if present
|
|
112
|
+
if (costCheck.message && !costCheck.allowed) {
|
|
113
|
+
lines.push(costCheck.message);
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
lines.push(t('tools.polli_gen_music.res_title'));
|
|
117
|
+
lines.push(`━━━━━━━━━━━━━━━━━━`);
|
|
118
|
+
lines.push(t('tools.polli_gen_music.res_prompt', { prompt: args.prompt }));
|
|
119
|
+
lines.push(t('tools.polli_gen_music.res_duration', { duration }));
|
|
120
|
+
lines.push(t('tools.polli_gen_music.res_mode', { mode: instrumental ? t('tools.polli_gen_music.res_mode_inst') : t('tools.polli_gen_music.res_mode_vocal') }));
|
|
121
|
+
lines.push(t('tools.polli_gen_music.res_file', { path: filePath }));
|
|
122
|
+
lines.push(t('tools.polli_gen_music.res_size', { size: formatFileSize(fileSize) }));
|
|
123
|
+
// Cost info
|
|
124
|
+
if (isCostEstimatorEnabled()) {
|
|
125
|
+
if (isTokenBased('audio', MODEL_NAME)) {
|
|
126
|
+
const maxCost = estimatedCost * 3;
|
|
127
|
+
lines.push(t('tools.polli_gen_music.res_cost_tok', { cost: formatCost(actualCost), maxCost: formatCost(maxCost) }));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
lines.push(t('tools.polli_gen_music.res_cost', { cost: formatCost(actualCost) }));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (responseHeaders['x-model-used']) {
|
|
134
|
+
lines.push(t('tools.polli_gen_music.res_model_used', { model: responseHeaders['x-model-used'] }));
|
|
135
|
+
}
|
|
136
|
+
if (responseHeaders['x-request-id']) {
|
|
137
|
+
lines.push(t('tools.polli_gen_music.res_request_id', { id: responseHeaders['x-request-id'] }));
|
|
138
|
+
}
|
|
139
|
+
// Emit success toast
|
|
140
|
+
emitStatusToast('success', t('tools.polli_gen_music.toast_success', { duration }), '🎵 gen_music');
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
emitStatusToast('error', t('tools.polli_gen_music.toast_err', { error: err.message?.substring(0, 60) }), '🎵 gen_music');
|
|
145
|
+
if (err.message?.includes('402') || err.message?.includes('Payment')) {
|
|
146
|
+
return t('tools.polli_gen_music.err_pollen');
|
|
147
|
+
}
|
|
148
|
+
if (err.message?.includes('401') || err.message?.includes('403')) {
|
|
149
|
+
return t('tools.polli_gen_music.err_auth');
|
|
150
|
+
}
|
|
151
|
+
if (err.message?.includes('Timeout')) {
|
|
152
|
+
return t('tools.polli_gen_music.err_timeout');
|
|
153
|
+
}
|
|
154
|
+
return t('tools.polli_gen_music.err_gen', { error: err.message });
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_video Tool - Pollinations Video Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Video models with different capabilities:
|
|
7
|
+
* - T2V (Text-to-Video): grok-video, ltx-2, veo, seedance, seedance-pro
|
|
8
|
+
* - I2V (Image-to-Video): wan (I2V ONLY!), veo, seedance, seedance-pro
|
|
9
|
+
* - Veo Interpolation: Uses image=url1,url2 for transitions
|
|
10
|
+
*
|
|
11
|
+
* Response headers for cost tracking:
|
|
12
|
+
* - x-usage-completion-video-seconds (grok, ltx-2, veo, wan)
|
|
13
|
+
* - x-usage-completion-video-tokens (seedance, seedance-pro)
|
|
14
|
+
*/
|
|
15
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
16
|
+
export declare const polliGenVideoTool: ToolDefinition;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_video Tool - Pollinations Video Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Video models with different capabilities:
|
|
7
|
+
* - T2V (Text-to-Video): grok-video, ltx-2, veo, seedance, seedance-pro
|
|
8
|
+
* - I2V (Image-to-Video): wan (I2V ONLY!), veo, seedance, seedance-pro
|
|
9
|
+
* - Veo Interpolation: Uses image=url1,url2 for transitions
|
|
10
|
+
*
|
|
11
|
+
* Response headers for cost tracking:
|
|
12
|
+
* - x-usage-completion-video-seconds (grok, ltx-2, veo, wan)
|
|
13
|
+
* - x-usage-completion-video-tokens (seedance, seedance-pro)
|
|
14
|
+
*/
|
|
15
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import { getApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateVideoCost, extractCostFromHeaders, isCostEstimatorEnabled, supportsI2V, requiresI2V, validateAspectRatio, getDurationRange, getVideoModels, fetchEnterBalance, sanitizeFilename, validateHttpUrl, } from './shared.js';
|
|
19
|
+
import { loadConfig } from '../../server/config.js';
|
|
20
|
+
import { checkCostControl, isTokenBased } from './cost-guard.js';
|
|
21
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
22
|
+
import { t } from '../../locales/index.js';
|
|
23
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
24
|
+
const CHEAPEST_MODEL = 'grok-video';
|
|
25
|
+
const DEFAULT_DURATION = 3;
|
|
26
|
+
const DEFAULT_ASPECT_RATIO = '16:9';
|
|
27
|
+
// ─── Tool Definition ──────────────────────────────────────────────────────
|
|
28
|
+
export const polliGenVideoTool = tool({
|
|
29
|
+
description: t('tools.polli_gen_video.desc'),
|
|
30
|
+
args: {
|
|
31
|
+
prompt: tool.schema.string().describe(t('tools.polli_gen_video.arg_prompt')),
|
|
32
|
+
model: tool.schema.string().describe(t('tools.polli_gen_video.arg_model', { model: CHEAPEST_MODEL })),
|
|
33
|
+
duration: tool.schema.number().optional().describe(t('tools.polli_gen_video.arg_duration')),
|
|
34
|
+
aspect_ratio: tool.schema.enum(['16:9', '9:16', '1:1', '4:3']).optional().describe(t('tools.polli_gen_video.arg_aspect')),
|
|
35
|
+
reference_image: tool.schema.string().optional().describe(t('tools.polli_gen_video.arg_ref')),
|
|
36
|
+
seed: tool.schema.number().optional().describe(t('tools.polli_gen_video.arg_seed')),
|
|
37
|
+
save_to: tool.schema.string().optional().describe(t('tools.polli_gen_video.arg_save_to')),
|
|
38
|
+
filename: tool.schema.string().optional().describe(t('tools.polli_gen_video.arg_filename')),
|
|
39
|
+
},
|
|
40
|
+
async execute(args, context) {
|
|
41
|
+
const apiKey = getApiKey();
|
|
42
|
+
if (!apiKey) {
|
|
43
|
+
return t('tools.polli_gen_video.req_key');
|
|
44
|
+
}
|
|
45
|
+
const model = args.model;
|
|
46
|
+
const aspectRatio = args.aspect_ratio || DEFAULT_ASPECT_RATIO;
|
|
47
|
+
// Get model config from dynamic registry
|
|
48
|
+
const videoModels = getVideoModels();
|
|
49
|
+
const modelConfig = videoModels[model];
|
|
50
|
+
const isBetaModel = !modelConfig;
|
|
51
|
+
if (isBetaModel) {
|
|
52
|
+
emitStatusToast('warning', t('tools.polli_gen_video.warn_beta', { model }), '🎬 gen_video');
|
|
53
|
+
}
|
|
54
|
+
// Validate duration (for known models; beta models use defaults)
|
|
55
|
+
const [minDuration, maxDuration] = isBetaModel ? [1, 20] : getDurationRange(model);
|
|
56
|
+
const duration = args.duration || Math.min(DEFAULT_DURATION, maxDuration);
|
|
57
|
+
if (duration < minDuration || duration > maxDuration) {
|
|
58
|
+
return t('tools.polli_gen_video.invalid_duration', { model, duration, min: minDuration, max: maxDuration });
|
|
59
|
+
}
|
|
60
|
+
// Validate aspect ratio (for known models; beta models accept any)
|
|
61
|
+
if (!isBetaModel && !validateAspectRatio(model, aspectRatio)) {
|
|
62
|
+
return t('tools.polli_gen_video.invalid_aspect', { model, aspect: aspectRatio, supported: modelConfig.aspectRatios.join(', ') });
|
|
63
|
+
}
|
|
64
|
+
// Check I2V requirements & validation
|
|
65
|
+
if (args.reference_image) {
|
|
66
|
+
const urls = args.reference_image.split(',').map(u => u.trim());
|
|
67
|
+
for (const u of urls) {
|
|
68
|
+
if (!validateHttpUrl(u)) {
|
|
69
|
+
return '❌ URL invalide. Utilisez http:// ou https://';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const requiresReferenceImage = !isBetaModel && requiresI2V(model);
|
|
74
|
+
const supportsReferenceImage = isBetaModel || supportsI2V(model);
|
|
75
|
+
if (requiresReferenceImage && !args.reference_image) {
|
|
76
|
+
return t('tools.polli_gen_video.req_i2v', { model });
|
|
77
|
+
}
|
|
78
|
+
if (args.reference_image && !supportsReferenceImage) {
|
|
79
|
+
const models = Object.entries(videoModels)
|
|
80
|
+
.filter(([, info]) => info.i2v)
|
|
81
|
+
.map(([name]) => name)
|
|
82
|
+
.join(', ');
|
|
83
|
+
return t('tools.polli_gen_video.no_i2v', { model, models });
|
|
84
|
+
}
|
|
85
|
+
// Estimate cost
|
|
86
|
+
const estimatedCost = estimateVideoCost(model, duration);
|
|
87
|
+
// Cost Guard check V2
|
|
88
|
+
const costCheck = checkCostControl('polli_gen_video', args, model, estimatedCost, 'video');
|
|
89
|
+
if (!costCheck.allowed) {
|
|
90
|
+
return costCheck.message || t('tools.polli_gen_video.blocked');
|
|
91
|
+
}
|
|
92
|
+
// Emit start toast
|
|
93
|
+
const config = loadConfig();
|
|
94
|
+
const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
|
|
95
|
+
emitStatusToast('info', t('tools.polli_gen_video.toast_start', { model, duration }) + argsStr, '🎬 polli_gen_video');
|
|
96
|
+
// Metadata
|
|
97
|
+
context.metadata({ title: `🎬 Video: ${model}${isBetaModel ? ' (beta)' : ''} (${duration}s)` });
|
|
98
|
+
try {
|
|
99
|
+
// Build URL
|
|
100
|
+
const params = new URLSearchParams({
|
|
101
|
+
model: model,
|
|
102
|
+
nologo: 'true',
|
|
103
|
+
private: 'true',
|
|
104
|
+
});
|
|
105
|
+
// Duration parameter
|
|
106
|
+
params.set('duration', String(duration));
|
|
107
|
+
// Aspect ratio - convert to width/height for API
|
|
108
|
+
const aspectToSize = {
|
|
109
|
+
'16:9': { w: 1920, h: 1080 },
|
|
110
|
+
'9:16': { w: 1080, h: 1920 },
|
|
111
|
+
'1:1': { w: 1024, h: 1024 },
|
|
112
|
+
'4:3': { w: 1440, h: 1080 },
|
|
113
|
+
};
|
|
114
|
+
const size = aspectToSize[aspectRatio] || aspectToSize['16:9'];
|
|
115
|
+
params.set('width', String(size.w));
|
|
116
|
+
params.set('height', String(size.h));
|
|
117
|
+
// I2V: reference image(s)
|
|
118
|
+
if (args.reference_image) {
|
|
119
|
+
// Veo interpolation: comma-separated URLs
|
|
120
|
+
// Other I2V models: single URL
|
|
121
|
+
params.set('image', args.reference_image);
|
|
122
|
+
}
|
|
123
|
+
// Seed for reproducibility
|
|
124
|
+
if (args.seed !== undefined) {
|
|
125
|
+
params.set('seed', String(args.seed));
|
|
126
|
+
}
|
|
127
|
+
const promptEncoded = encodeURIComponent(args.prompt);
|
|
128
|
+
const url = `https://gen.pollinations.ai/image/${promptEncoded}?${params}`;
|
|
129
|
+
const headers = {
|
|
130
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
131
|
+
};
|
|
132
|
+
// 1. Fetch balance avant génération
|
|
133
|
+
const balBefore = await fetchEnterBalance();
|
|
134
|
+
// Video generation takes time (30-70 seconds depending on model)
|
|
135
|
+
const result = await httpsGet(url, headers);
|
|
136
|
+
const videoData = result.data;
|
|
137
|
+
const responseHeaders = result.headers;
|
|
138
|
+
// Save video
|
|
139
|
+
let outputDir = getDefaultOutputDir('videos');
|
|
140
|
+
let filename = args.filename ? sanitizeFilename(args.filename) : undefined;
|
|
141
|
+
if (args.save_to) {
|
|
142
|
+
if (args.save_to.match(/\.(mp4|webm|mov|avi)$/i)) {
|
|
143
|
+
outputDir = path.dirname(args.save_to);
|
|
144
|
+
filename = path.basename(args.save_to);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
outputDir = args.save_to;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
ensureDir(outputDir);
|
|
151
|
+
filename = filename || generateFilename('video', model, 'mp4');
|
|
152
|
+
const filePath = path.join(outputDir, filename.endsWith('.mp4') ? filename : `${filename}.mp4`);
|
|
153
|
+
fs.writeFileSync(filePath, videoData);
|
|
154
|
+
const fileSize = fs.statSync(filePath).size;
|
|
155
|
+
// Extract actual cost from headers as fallback
|
|
156
|
+
const costTracking = extractCostFromHeaders(responseHeaders);
|
|
157
|
+
// 2. Fetch balance après génération (delay for API sync)
|
|
158
|
+
let balAfter = null;
|
|
159
|
+
let realCost;
|
|
160
|
+
if (balBefore !== null) {
|
|
161
|
+
await new Promise(r => setTimeout(r, 1000)); // Laisse le temps au ledger
|
|
162
|
+
balAfter = await fetchEnterBalance();
|
|
163
|
+
if (balAfter !== null) {
|
|
164
|
+
realCost = Math.round((balBefore - balAfter) * 10000) / 10000;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Build result
|
|
168
|
+
const lines = [];
|
|
169
|
+
// Inject costWarning at top if present
|
|
170
|
+
if (costCheck.message && !costCheck.allowed) { // Assuming costWarning should come from costCheck if not allowed
|
|
171
|
+
lines.push(costCheck.message);
|
|
172
|
+
lines.push('');
|
|
173
|
+
}
|
|
174
|
+
lines.push(t('tools.polli_gen_video.res_title'));
|
|
175
|
+
lines.push(`━━━━━━━━━━━━━━━━━━`);
|
|
176
|
+
lines.push(t('tools.polli_gen_video.res_prompt', { prompt: args.prompt.substring(0, 80) + (args.prompt.length > 80 ? '...' : '') }));
|
|
177
|
+
lines.push(t('tools.polli_gen_video.res_model', { model: `${model}${isBetaModel ? ' (beta)' : ''}${modelConfig?.cost?.includes('💎') ? ' 💎' : ''}` }));
|
|
178
|
+
lines.push(t('tools.polli_gen_video.res_duration', { duration }));
|
|
179
|
+
lines.push(t('tools.polli_gen_video.res_aspect', { aspect: aspectRatio }));
|
|
180
|
+
// Add I2V info if used
|
|
181
|
+
if (args.reference_image) {
|
|
182
|
+
const isInterpolation = model === 'veo' && args.reference_image.includes(',');
|
|
183
|
+
lines.push(t('tools.polli_gen_video.res_i2v_mode', { mode: isInterpolation ? t('tools.polli_gen_video.res_i2v_interp') : t('tools.polli_gen_video.res_i2v_single') }));
|
|
184
|
+
lines.push(t('tools.polli_gen_video.res_source', { url: args.reference_image.substring(0, 50) + '...' }));
|
|
185
|
+
}
|
|
186
|
+
// Audio info (known models only)
|
|
187
|
+
if (modelConfig?.audio) {
|
|
188
|
+
lines.push(t('tools.polli_gen_video.res_audio_ok'));
|
|
189
|
+
}
|
|
190
|
+
else if (!isBetaModel) {
|
|
191
|
+
lines.push(t('tools.polli_gen_video.res_audio_no'));
|
|
192
|
+
}
|
|
193
|
+
lines.push(t('tools.polli_gen_video.res_file', { path: filePath }));
|
|
194
|
+
lines.push(t('tools.polli_gen_video.res_size', { size: formatFileSize(fileSize) }));
|
|
195
|
+
// Pricing details (Estimé vs Réel)
|
|
196
|
+
if (isCostEstimatorEnabled()) {
|
|
197
|
+
const maxCost = estimatedCost * 3;
|
|
198
|
+
lines.push(t('tools.polli_gen_video.res_cost_title'));
|
|
199
|
+
if (isTokenBased('video', model)) {
|
|
200
|
+
lines.push(t('tools.polli_gen_video.res_cost_est_tok', { cost: formatCost(estimatedCost), maxCost: formatCost(maxCost) }));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
lines.push(t('tools.polli_gen_video.res_cost_est', { cost: formatCost(estimatedCost) }));
|
|
204
|
+
}
|
|
205
|
+
if (realCost !== undefined) {
|
|
206
|
+
lines.push(t('tools.polli_gen_video.res_cost_real_wallet', { cost: formatCost(realCost) }));
|
|
207
|
+
}
|
|
208
|
+
else if (costTracking.costUsd !== undefined) {
|
|
209
|
+
lines.push(t('tools.polli_gen_video.res_cost_real_headers', { cost: formatCost(costTracking.costUsd) }));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
lines.push(t('tools.polli_gen_video.res_cost_real_unknown'));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (responseHeaders['x-model-used']) {
|
|
216
|
+
lines.push(t('tools.polli_gen_video.res_model_used', { model: responseHeaders['x-model-used'] }));
|
|
217
|
+
}
|
|
218
|
+
if (responseHeaders['x-request-id']) {
|
|
219
|
+
lines.push(t('tools.polli_gen_video.res_request_id', { id: responseHeaders['x-request-id'] }));
|
|
220
|
+
}
|
|
221
|
+
// Gen time estimate (known models only)
|
|
222
|
+
if (modelConfig?.genTime) {
|
|
223
|
+
lines.push(t('tools.polli_gen_video.res_time', { time: modelConfig.genTime }));
|
|
224
|
+
}
|
|
225
|
+
// Emit success toast
|
|
226
|
+
emitStatusToast('success', t('tools.polli_gen_video.toast_success', { model, duration }), '🎬 gen_video');
|
|
227
|
+
return lines.join('\n');
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
emitStatusToast('error', t('tools.polli_gen_video.toast_err', { error: err.message?.substring(0, 60) }), '🎬 gen_video');
|
|
231
|
+
if (err.message?.includes('402') || err.message?.includes('Payment')) {
|
|
232
|
+
return t('tools.polli_gen_video.err_pollen');
|
|
233
|
+
}
|
|
234
|
+
if (err.message?.includes('400')) {
|
|
235
|
+
if (requiresI2V(model) && !args.reference_image) {
|
|
236
|
+
return t('tools.polli_gen_video.err_i2v_req', { model });
|
|
237
|
+
}
|
|
238
|
+
return t('tools.polli_gen_video.err_invalid', { msg: err.message });
|
|
239
|
+
}
|
|
240
|
+
if (err.message?.includes('520') && model === 'ltx-2') {
|
|
241
|
+
return t('tools.polli_gen_video.err_520');
|
|
242
|
+
}
|
|
243
|
+
if (err.message?.includes('Timeout')) {
|
|
244
|
+
return t('tools.polli_gen_video.err_timeout');
|
|
245
|
+
}
|
|
246
|
+
return t('tools.polli_gen_video.err_gen', { error: err.message });
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|