# Guia de geração de imagem

## Quando usar este guia

Use este documento quando o seu produto precisa **gerar imagem** pelo ChatAgent, em dois
cenários:

- **Melhoria de foto de produto** — você envia uma foto e recebe uma versão tratada.
- **Social post** — você gera uma arte para story/feed, com ou sem foto de produto e logo.

Isso é diferente do contrato conversacional (`/api/messages`, `/v1/*`). Geração de imagem
é uma capability **interna**, server-to-server, com autenticação e endpoints próprios.

## Autenticação (importante)

Os endpoints de imagem aceitam **dois tipos de chave**, com comportamento diferente:

```http
Authorization: Bearer <INTERNAL_PRODUCT_IMAGE_API_KEY>
```
ou
```http
Authorization: Bearer <sua API key de tenant, chatag_...>
```

- **Chave interna** (`INTERNAL_PRODUCT_IMAGE_API_KEY`) — sem tenant, sem consumo de
  crédito, sem rastreio. Pensada pro seu próprio backend chamar direto (não pra um
  cliente/tenant específico). Configurada via variável de ambiente. Por ser uma
  credencial de servidor com acesso direto à geração, **nunca** a exponha no frontend,
  em apps mobile ou em código client-side.
- **API key de tenant** (`chatag_...`, a mesma do `/api/chat`) — consome 1 crédito do
  tenant por geração e respeita o rate limit dele, igual ao chat. Use essa quando quem
  está chamando é um tenant/cliente da sua plataforma, não seu próprio backend.

A resposta inclui `credits_remaining` quando autenticado com chave de tenant (ausente
quando autenticado com a chave interna, já que não há tenant nem crédito envolvido).

- `401` — chave ausente ou inválida (nenhuma das duas bateu).
- `402` — créditos insuficientes (só se autenticado com chave de tenant).
- `503` — `INTERNAL_PRODUCT_IMAGE_API_KEY` não configurada no servidor, ou o provider
  de imagem (`openai-oauth`) não está pronto.

## Por que existe modo assíncrono

A geração chama a API do `openai-oauth` (modelo `gpt-image-2`, sessão OAuth real —
sem automação de navegador) e costuma levar poucos segundos a pouco mais de um minuto,
variando com a fila do provedor. Ainda assim, uma requisição HTTP síncrona longa é
frágil (proxy/timeout/restart deixam a imagem órfã). Por isso:

- **Modo síncrono** (default): a resposta volta no mesmo POST quando a imagem fica pronta.
  Use só para testes ou quando você controla bem o timeout (180s+).
- **Modo assíncrono** (recomendado em produção): adicione `?async=1`. O POST responde na
  hora com `202` e um `job_id`, e você faz polling em `GET /internal/jobs/:id`.

## Melhoria de foto de produto

```http
POST /internal/product-image-enhancements
Authorization: Bearer <INTERNAL_PRODUCT_IMAGE_API_KEY>
Content-Type: application/json
```

Body:

```json
{
  "style": "ecommerce-clean",
  "prompt": "Fundo branco, luz de estúdio, realça o rótulo",
  "source_image": {
    "type": "data_url",
    "mime_type": "image/png",
    "filename": "produto.png",
    "data_url": "data:image/png;base64,iVBORw0KGgo..."
  }
}
```

Obrigatórios: `style`, `prompt`, `source_image` (com `type: "data_url"` e `data_url` em
base64 de imagem). O limite de body deste endpoint é alto (25 MB) porque a foto em base64
costuma passar de 1 MB.

## Social post

```http
POST /internal/social-posts
Authorization: Bearer <INTERNAL_PRODUCT_IMAGE_API_KEY>
Content-Type: application/json
```

Body:

```json
{
  "format": "feed_square",
  "prompt": "Post de lançamento, tom vibrante, destaque para promoção",
  "product_image": {
    "type": "data_url",
    "data_url": "data:image/jpeg;base64,/9j/4AAQ..."
  },
  "logo_image": {
    "type": "data_url",
    "data_url": "data:image/png;base64,iVBORw0KGgo..."
  }
}
```

Obrigatórios: `format` e `prompt`.

- `format` aceita: `story`, `feed_square`, `feed_portrait`.
- `product_image` é **opcional**: sem ela, o post é gerado só a partir do `prompt`
  (text-to-image). Se enviada, precisa ser uma data URL de imagem válida.
- `logo_image` é opcional.
- `shop_id`, `product_id`, `social_post_id` são opcionais (inteiros), úteis só para
  rastreio do seu lado.

## Resposta de sucesso (síncrono)

```json
{
  "success": true,
  "data": {
    "image_base64": "iVBORw0KGgo...",
    "mime_type": "image/png",
    "provider_payload": {
      "source": "chatgpt-web",
      "style": "ecommerce-clean",
      "src_kind": "remote"
    }
  }
}
```

A imagem gerada volta em `data.image_base64` (base64 puro, sem o prefixo `data:`) com o
`data.mime_type` correspondente. Para exibir ou salvar, monte
`data:<mime_type>;base64,<image_base64>` ou decodifique o base64 em arquivo.

## Fluxo assíncrono recomendado

1. `POST /internal/product-image-enhancements?async=1` (ou `/internal/social-posts?async=1`).
   Resposta imediata:

   ```json
   { "success": true, "status": "pending", "job_id": "0b3c..." }
   ```

2. Faça polling em `GET /internal/jobs/<job_id>` (mesma auth interna) a cada poucos
   segundos:

   ```json
   { "success": true, "status": "pending" }
   ```

   Quando terminar:

   ```json
   { "success": true, "status": "done", "data": { "image_base64": "...", "mime_type": "image/png" } }
   ```

   Em caso de falha:

   ```json
   {
     "success": false,
     "status": "error",
     "error": "ChatGPT recusou a geração por política de conteúdo.",
     "code": "moderation-blocked",
     "error_status": 503
   }
   ```

3. Jobs expiram da memória após ~15 minutos. Faça o polling até `done`/`error` dentro
   desse tempo; depois disso o job some e vira `404 not_found`.

## Exemplo — backend Node (assíncrono)

```javascript
const BASE = process.env.CHATAGENT_BASE_URL || "https://chatagentai.online";
const INTERNAL_KEY = process.env.INTERNAL_PRODUCT_IMAGE_API_KEY;
const headers = {
  Authorization: `Bearer ${INTERNAL_KEY}`,
  "Content-Type": "application/json",
};

async function enhanceProductImage(dataUrl, { style, prompt }) {
  const start = await fetch(`${BASE}/internal/product-image-enhancements?async=1`, {
    method: "POST",
    headers,
    body: JSON.stringify({ style, prompt, source_image: { type: "data_url", data_url: dataUrl } }),
  }).then((r) => r.json());

  if (!start.job_id) throw new Error(`falha ao enfileirar: ${JSON.stringify(start)}`);

  // polling até done/error (ou timeout do seu lado)
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 3000));
    const job = await fetch(`${BASE}/internal/jobs/${start.job_id}`, { headers }).then((r) => r.json());
    if (job.status === "done") return job.data; // { image_base64, mime_type }
    if (job.status === "error") throw new Error(`${job.code}: ${job.error}`);
  }
  throw new Error("timeout aguardando a imagem");
}
```

## Erros e códigos

Falhas de geração trazem `code` e, quando útil, `diagnostics`:

- `moderation-blocked` — o conteúdo foi recusado por política de conteúdo.
- `image-not-found` — o provider respondeu mas nenhuma imagem veio no `data[0].b64_json`.
- `chatgpt-rate-limited` (`503`) — provider sobrecarregado (HTTP 429 upstream); vale retry
  com backoff.
- `chatgpt-image-network-error` / `chatgpt-image-unavailable` (`503`) — falha de rede ou
  erro genérico falando com o `openai-oauth`; normalmente transitório, vale retry.

## Boas práticas

- Em produção, use sempre `?async=1` + polling.
- Mande imagens base64 dentro do limite (25 MB) e prefira PNG/JPEG.
- Chamadas de imagem são requisições HTTP stateless — pode disparar em paralelo, não
  há mais fila serializada por aba de navegador (isso existia na versão anterior,
  baseada em automação do ChatGPT web).
- Guarde seu próprio identificador (`social_post_id` etc.) para casar o resultado do job
  com o registro do seu sistema.
- Mantenha a `INTERNAL_PRODUCT_IMAGE_API_KEY` só no backend.
