/** * TapToAIService — JavaScript клас за TapTo AI API (Node.js 18+ или браузър). Ползва Fetch API. * Документация: https://tapto.bg/app/ai-api-integration * * ВНИМАНИЕ: в браузъра токенът се вижда от всеки посетител — викайте API-то от вашия сървър. * * ПРИМЕР * const ai = new TapToAIService('ВАШИЯТ_API_ТОКЕН') * console.log(await ai.getCredits()) * * // AI студио — 2 снимки на легло за куче в хол, с куче (0,10 кр.) * const r = await ai.studioGenerate({ type: 'pets', scene: 'living', subject: 'dog' }, fileInput.files[0]) * r.images?.forEach(img => console.log(img.url)) */ class TapToAIService { constructor(apiToken, baseUrl = 'https://tapto.bg/api/ai') { this.apiToken = apiToken this.baseUrl = baseUrl.replace(/\/+$/, '') } async _send(endpoint, method = 'POST', data = {}, files = {}) { let url = `${this.baseUrl}/${endpoint.replace(/^\/+/, '')}` const headers = { Authorization: `Bearer ${this.apiToken}`, Accept: 'application/json' } const clean = Object.fromEntries(Object.entries(data || {}).filter(([, v]) => v !== null && v !== undefined && v !== '')) let body if (method === 'GET') { const qs = new URLSearchParams(clean).toString() if (qs) url += `?${qs}` } else if (Object.values(files).some(Boolean)) { body = new FormData() Object.entries(clean).forEach(([k, v]) => body.append(k, v)) Object.entries(files).forEach(([k, f]) => f && body.append(k, f)) } else { headers['Content-Type'] = 'application/json' body = JSON.stringify(clean) } const response = await fetch(url, { method, headers, body }) return response.json() } /** Наличните кредити. */ getCredits() { return this._send('credits', 'GET') } /** Транзакциите: search, status, type, sort_by, sort_order, per_page, page. */ getTransactions(params = {}) { return this._send('transactions', 'GET', params) } /** Генерираните снимки: search, per_page, page. */ getHistory(params = {}) { return this._send('history', 'GET', params) } /** AI студио: видове продукти, сцени, хора/животни, формати и цена. */ studioPresets(locale = 'bg') { return this._send('studio/presets', 'GET', { locale }) } /** * AI студио: 2 продуктови снимки наведнъж (0,10 кр.). * @param {Object} options type, scene, subject, format, color, lighting, mood, angle, season, fidelity, product, extra, avoid, seed, steps, cfg * @param {File|Blob|null} image снимка на продукта; без нея е задължително options.product */ studioGenerate(options, image = null) { return this._send('studio/generate', 'POST', options, { input_image: image }) } /** * Блог статия (0,20 кр. + 0,10 кр. корица): пуска писането и връща { id }. * options: topic (задължително), keywords, language, length (short|medium|long), tone (friendly|expert|selling|simple), * audience, notes, cover (true/false), cover_style (photo|illustration|3d|minimal) */ blogArticleStart(options) { return this._send('blog-article', 'POST', options) } /** Блог статия: състоянието (pending|done|error) и статията, когато е готова. */ blogArticle(id) { return this._send(`blog-article/${Number(id)}`, 'GET') } /** Блог статия: пуска и чака (до maxSeconds), докато стане готова. */ async writeBlogArticle(options, maxSeconds = 300) { const start = await this.blogArticleStart(options) if (!start.id) return start const until = Date.now() + maxSeconds * 1000 let r do { await new Promise(resolve => setTimeout(resolve, 5000)) r = await this.blogArticle(start.id) } while (r.state === 'pending' && Date.now() < until) return r } /** Вашите блог статии: per_page, page. */ blogArticles(params = {}) { return this._send('blog-articles', 'GET', params) } /** Снимка по свободно описание (prompt) — 0,10 кр. Опции: model, width, height, steps, cfg_scale, negative_prompt, output_format. */ generateImage(prompt, options = {}, inputImage = null) { return this._send('generate', 'POST', { prompt, ...options }, { input_image: inputImage }) } /** Премахване на фон — File/Blob или адрес (0,01 кр.). */ removeBackground(imageFileOrUrl, model = null, outputFormat = 'PNG') { const data = { model, output_format: outputFormat } if (typeof imageFileOrUrl === 'string') return this._send('remove-background', 'POST', { ...data, image_url: imageFileOrUrl }) return this._send('remove-background', 'POST', data, { image: imageFileOrUrl }) } /** SEO Agent — SEO текстове за продукт на няколко езика (0,01 кр.). */ generateSeoAgent(siteName, siteUrl, languages, productName, productPrice, currency, description, imageFiles = [], additionalReqs = null, includeIcons = false) { const files = Object.fromEntries(imageFiles.map((f, i) => [`images[${i}]`, f])) return this._send('seo-agent', 'POST', { site_name: siteName, site_url: siteUrl, languages: Array.isArray(languages) ? languages.join(', ') : languages, product_name: productName, product_price: productPrice, currency, description, additional_requirements: additionalReqs, include_icons: includeIcons ? 1 : 0, }, files) } } if (typeof module !== 'undefined') module.exports = TapToAIService