import { RpcMethod, JsonObject } from './commonTypes'; import { AiBuilderDocument as pushwoosh_aibuilder_v1_AiBuilderDocument } from './pushwoosh_aibuilder_v1'; import { DocumentSettings as pushwoosh_aibuilder_v1_DocumentSettings } from './pushwoosh_aibuilder_v1'; import { TextVariantStyle as pushwoosh_aibuilder_v1_TextVariantStyle } from './pushwoosh_aibuilder_v1'; import { ColorSchemeItem as pushwoosh_aibuilder_v1_ColorSchemeItem } from './pushwoosh_aibuilder_v1'; import { AiBuilderBlock as pushwoosh_aibuilder_v1_AiBuilderBlock } from './pushwoosh_aibuilder_v1'; import { EdgeInsets as pushwoosh_aibuilder_v1_EdgeInsets } from './pushwoosh_aibuilder_v1'; export type AiGenService_ExtractStyle = RpcMethod; export type AiGenService_ExtractTheme = RpcMethod; export type AiGenService_Translate = RpcMethod; export type AiGenService_ConvertUnlayer = RpcMethod; export type AiGenService_DocumentToHtml = RpcMethod; export type AiGenService_DocumentsToHtml = RpcMethod; export type AiGenService_DocumentToPageHtml = RpcMethod; export type AiGenService_DocumentToFragmentHtml = RpcMethod; export type AiGenService_DocumentToPopupHtml = RpcMethod; export type AiGenService_GenerateMediaImage = RpcMethod; export type AiGenService_SearchStockPhotos = RpcMethod; export type AiGenService_ImportStockPhoto = RpcMethod; export type AiGenService_ImportImageByUrl = RpcMethod; export type AiGenService_GenerateImageAlt = RpcMethod; /** * AiGenService unifies document create / edit / per-block regeneration and * targeted block insertion into a single streaming RPC. Dispatch rules: * - document empty (template required) → create a new document from the template * - document set → edit existing document (diff planner) * - document set + block_path → regenerate only that block; planner * skipped, structure_diff NOT emitted * - document set + add_block → insert one new block at a given * position; planner skipped, a * single-entry structure_diff is * synthesised so the client applies * it like any other edit * block_path and add_block are mutually exclusive. * * Addressing: every block reference on the wire is a path of ids. For MVP * single-page documents the path is always `[page_id, block_id]`. The path * form is in place so multi-page documents (email digests with tabs, * multi-screen landing pages) can be addressed deeper without a further * schema break. */ export interface AiGenService { Generate(request: AiGenRequest, options?: { signal?: AbortSignal; }): Promise>; /** * RenderHtml renders an existing AiBuilderDocument into a finished, * self-contained HTML document. The model writes the whole HTML — a * full page with embedded/inline CSS — from the * document's content and styling, streamed back as ordered html_delta * chunks the client concatenates. * * mode picks the flavour: EMAIL → email-safe markup (table-based * layout, inline CSS, max client compatibility); WEB_POPUP → a modern * responsive page (CSS in , fl/grid). UNSPECIFIED and FULL are * rejected like on Generate. * * An optional reference image conditions the visual style ("make it * look like this design") — its palette, typography, spacing, and * corner treatment are sampled by a vision-capable provider. * llm_provider / llm_model are the same dev-only overrides as Generate. */ RenderHtml(request: AiGenHtmlRequest, options?: { signal?: AbortSignal; }): Promise>; /** * ExtractStyle samples a visual style from a single image: a colour * palette (keyed by semantic role) plus typography hints (body / * heading font-family stacks and a free-text character note). It is a * pure extractor — no document in, no document out — meant to seed a * fresh AiBuilderDocument's color_scheme / fonts from a brand asset or * design screenshot. Unary; the result is small. */ ExtractStyle: AiGenService_ExtractStyle; /** * ExtractTheme samples a full brand theme (every palette role + both font stacks) and * the brand voice from any mix of materials. See documents/aigen-brand-utilities.md. */ ExtractTheme: AiGenService_ExtractTheme; /** * Translate renders a batch of email texts into the target languages, one call per * language. Markup and dynamic syntax survive verbatim; responses are index-aligned. */ Translate: AiGenService_Translate; /** * ConvertUnlayer deterministically converts an Unlayer email-template * design (the `editorConfig` JSON stored in EmailContent.Unlayer) into * an AiBuilderDocument, so templates built in the Unlayer block editor * can be opened in the AI builder. No LLM involved — the mapping is * pure and repeatable, and the same call can back a bulk migration. * * Unlayer widgets with no AiBuilderDocument equivalent (countdown * timers, custom tools, raw HTML) become Placeholder blocks the AI can * materialise on a follow-up edit; every such lossy step is reported * in `warnings`. The returned document always passes the EMAIL-mode * document validation. Unary; the result is document-sized. */ ConvertUnlayer: AiGenService_ConvertUnlayer; /** * DocumentToHtml renders an AiBuilderDocument to the finished email HTML * deterministically — no LLM, byte-for-byte the same output the editor * produces on save. It is the counterpart of RenderHtml: use RenderHtml * when you want the model to reimagine the layout ("style it like this * design"), use this when you want THE html of this document. * * The response carries the rendered html, the per-language localization * map, and the document that produced them, so a caller can write the * whole smartcards EmailContent without a second round-trip and without * being able to desync the three. * * EMAIL only for now. Unary: the render is fast and the result is one * document. */ DocumentToHtml: AiGenService_DocumentToHtml; /** * DocumentsToHtml renders a batch of documents, one email each, in a single * call. The template gallery paints N brand-recoloured tiles the moment the * start screen opens, and N round-trips is that whole screen; per-item errors * are reported per item so one unrenderable skeleton cannot blank the rest. */ DocumentsToHtml: AiGenService_DocumentsToHtml; /** * DocumentToPageHtml renders the same document as a standalone cloud page: * the block markup the email emitter produces, under page chrome instead of * the mail-client shell. Forms render live and a top-level content block is * allowed — both are refused in email. * * A separate RPC rather than a mode on DocumentToHtml: four of that * request's fields (the three save tags and the web-version label) and the * whole localization map are email-only, and a mode would make half the * message meaningless depending on its value. A page carries no tokens — * CloudPageBuilder hands the serialized document straight over. */ DocumentToPageHtml: AiGenService_DocumentToPageHtml; /** * DocumentToFragmentHtml renders a synced block's companion template: * body-only HTML plus its own localization tokens, which the sending * pipeline inlines into a letter with `{% email_content "" %}` BEFORE * the Liquid pass — so dynamic product and condition tags stay live inside * it. The request's document IS the fragment: its blocks are the fragment's * blocks and its settings are the style context of the letter the fragment * will appear in. */ DocumentToFragmentHtml: AiGenService_DocumentToFragmentHtml; /** * DocumentToPopupHtml renders the document as rich-media in-app HTML: the * .u-popup-container shell the display SDK and the statistics script expect, * with a click-tracking id on every clickable leaf. * * Tokens here are `|text|`, not `|html|`: rich medias are substituted by the * display SDK rather than the email payload-builder, and it has no html type. */ DocumentToPopupHtml: AiGenService_DocumentToPopupHtml; /** * GenerateMediaImage generates ONE image from a prompt and stores it in * the application's media library — the media-gallery "Generate" tab. * Same generator the streaming pipeline uses (Gemini image model, cheap * quality tier); the response carries the stored file's URL and uuid, * ready to insert as ContentImage.src / media_uuid. */ GenerateMediaImage: AiGenService_GenerateMediaImage; /** * SearchStockPhotos proxies a stock-photo search (Pexels) — the API key * stays server-side and the browser never talks to the provider. Results * are preview-only: thumbnails for the picker plus the attribution the * provider requires. Inserting a photo goes through ImportStockPhoto. */ SearchStockPhotos: AiGenService_SearchStockPhotos; /** * ImportStockPhoto copies one stock photo into the application's media * library: the server resolves the photo by id at the provider, downloads * the bytes itself (never a client-supplied URL — SSRF stays impossible) * and uploads them to media storage with a provenance description. */ ImportStockPhoto: AiGenService_ImportStockPhoto; /** * ImportImageByUrl stores a feed image in the media library, re-encoding what * email clients cannot show (WebP). Client-supplied URL: public hosts only. */ ImportImageByUrl: AiGenService_ImportImageByUrl; /** * GenerateImageAlt writes alt text for an existing image: the server * fetches the image (media-library / stock hosts only), shows it to a * vision-capable model and returns one concise accessibility-grade alt. */ GenerateImageAlt: AiGenService_GenerateImageAlt; } export type AiGenBlockTypeHint = 'AI_GEN_BLOCK_TYPE_HINT_UNSPECIFIED' | 'AI_GEN_BLOCK_TYPE_HINT_CARD' | 'AI_GEN_BLOCK_TYPE_HINT_COLUMNS' /** Top-level free-form Content block. Allowed in WEB_POPUP only. */ | 'AI_GEN_BLOCK_TYPE_HINT_CONTENT' /** * Structural stack container ("group"). The block generator is * skipped — groups carry no copy, just children referenced via * parent_id. Allowed in EMAIL and WEB_POPUP. */ | 'AI_GEN_BLOCK_TYPE_HINT_GROUP' /** * Client-created empty slot meant to be materialised into a real * block by the AI on a follow-up edit. The block generator is * skipped — the planner emits a `replace` for each placeholder it * sees in and the pipeline retargets the replace * to a real content kind before invoking the block generator. */ | 'AI_GEN_BLOCK_TYPE_HINT_PLACEHOLDER' /** * Horizontal separator line between sections. The block generator is * skipped — a divider carries no copy, only line config — so the * pipeline synthesises a default Divider for it. */ | 'AI_GEN_BLOCK_TYPE_HINT_DIVIDER'; /** * AiGenMode selects the document-domain profile the generation pipeline * runs under: which block kinds are allowed, which Meta fields are * produced, how the system prompts phrase the document type. * * Clients must send a concrete mode on every AiGenRequest. EMAIL is * the email-builder behaviour with a restricted block set and the * email-only meta fields. WEB_POPUP targets web overlay popups: full * block toolkit, popup-tuned prompts, popup-aware image generation. * FULL was a transitional placeholder; it is now deprecated — the * server rejects requests with mode=FULL and clients must move to * WEB_POPUP (or a future concrete channel like INAPP_MOBILE). * UNSPECIFIED is the proto3 zero value and is rejected by the server * with InvalidArgument. */ export type AiGenMode = 'AI_GEN_MODE_UNSPECIFIED' | 'AI_GEN_MODE_EMAIL' /** * Deprecated: kept for wire-compat. The server rejects requests * with this mode (InvalidArgument). Use AI_GEN_MODE_WEB_POPUP for * popups or a future concrete channel for other surfaces. */ | 'AI_GEN_MODE_FULL' | 'AI_GEN_MODE_WEB_POPUP' /** * Cloud pages: full-page web documents with subscription forms. MVP is * conversion + manual editing only — the generation pipeline is not * tuned for pages yet. */ | 'AI_GEN_MODE_WEB_PAGE'; /** * AiGenBlockPath wraps a block-addressing path so it can appear inside * repeated fields — proto3 disallows `repeated repeated`. Path is * `[page_id, block_id]` for MVP; additional segments may appear when * multi-page or nested containers land. */ export type AiGenBlockPath = { path: string[]; }; export type AiGenRequest = { prompt: string; /** * Edit mode: the existing document to mutate. Empty on create — use * `template` instead. Mutually exclusive with `template`. On edit the * document's styling (settings, text_variants, color_scheme) is read * from here. */ document?: pushwoosh_aibuilder_v1_AiBuilderDocument; /** * Bypass mode: path `[page_id, block_id]` of the block to regenerate. * Requires `document`. Mutually exclusive with `add_block`. Empty = * no bypass. */ blockPath: string[]; application: string; generateImages: boolean; /** * Set → add-block mode: insert a single new block into `document` at a * client-specified position, skipping the planner. Mutually exclusive * with `block_path`. Requires `document`. */ addBlock?: AiGenAddBlock; /** * Document-domain profile for this generation. Required — the server * rejects UNSPECIFIED with InvalidArgument. */ mode: AiGenMode; /** * Create mode: the prototype document the new document is generated * from. Required when `document` is empty; mutually exclusive with * `document`. The generator preserves the template's styling (settings, * text_variants, color_scheme are read from here on create) and * imitates the layout / tone of its blocks while writing fresh content * for the user's prompt. A template with no blocks degenerates to a * free create (styling only, no structural reference). */ template?: pushwoosh_aibuilder_v1_AiBuilderDocument; /** * Dev-only override of the LLM backing block/structure generation. * Empty = the server default (self-hosted Qwen/vLLM). "claude" routes * generation through Anthropic. Gated client-side behind a hidden dev * flag; the server simply honours whatever it receives. */ llmProvider?: string; /** * Dev-only override of the concrete model name for the chosen provider. * Empty = the provider's configured default (e.g. anthropic.model from * config). Ignored when it doesn't apply to the provider. */ llmModel?: string; /** * Reference images ("make it like this design") fed into the generation * LLM's context — both the structure planner and the per-block content * generator see them. Requires a vision-capable provider (Claude, or our * multimodal Qwen/vLLM). Empty = text-only generation as before. Sent * inline as base64; one image is the common case but the field is * repeated for future multi-reference prompts. */ referenceImages: AiGenReferenceImage[]; /** * Whether the image phase first tries to REUSE a suitable existing * media-library image (matched by its stored description) before generating a * new one — cuts near-duplicate generations piling up in the gallery. * OPTIONAL and default-ON: the server treats absent as true and reuses; send * an explicit false to opt out (the client dev toggle does this). No match → * generate as before. */ reuseMediaLibrary?: boolean; }; /** * AiGenReferenceImage is an inline, base64-encoded image attached to a * generation request as visual context. Not persisted as document content — * it only conditions the LLM for this one run. */ export type AiGenReferenceImage = { /** MIME type: "image/jpeg" | "image/png" | "image/gif" | "image/webp". */ mediaType: string; /** Base64-encoded image bytes (no data: URI prefix). */ data: string; }; /** * AiGenAddBlock triggers add-block mode on Generate. The server emits a * single-entry structure_diff echoing these fields so the UI can match an * optimistically-rendered placeholder, followed by the block_delta and any * image_deltas for the new block. * * Positioning: the new block lands at the end of the target parent * scope (end of the page when parent_id is empty, end of that * container's children otherwise). Fine-grained positioning is the * client's job: drop an empty `Placeholder` block on the canvas where * you want it and ask the AI to materialise it on the next edit (see * the placeholder flow in the document). */ export type AiGenAddBlock = { /** * Client-provided path for the new block: `[page_id, new_block_id]`. * `page_id` must reference an existing page in `document`; the new * block_id must not collide with any existing block id in that page. */ blockPath: string[]; /** * Optional. UNSPECIFIED = the block generator decides card vs columns * based on the prompt and surrounding blocks. */ typeHint: AiGenBlockTypeHint; /** * Id of the parent container block in the same page. Empty string = * top-level. The new block becomes a child of that container. The * server rejects parent_id that does not resolve to an existing * container block in `document`. */ parentId: string; }; export type AiGenStructureBlock = { /** [page_id, block_id] */ blockPath: string[]; typeHint: AiGenBlockTypeHint; /** per-block prompt for the block generator */ prompt: string; /** * Id of the parent container block (within the same page). Empty * string marks a top-level block. Set on container kinds' children * so the client can rebuild the tree from the stream. */ parentId: string; }; /** Emitted once for create, before any block_delta. */ export type AiGenStructure = { blocks: AiGenStructureBlock[]; }; export type AiGenStructureAdd = { /** [page_id, new_block_id] — disjoint from existing ids in that page */ blockPath: string[]; typeHint: AiGenBlockTypeHint; prompt: string; /** * Id of the parent container block. * * Wire contract: * - Empty string = top-level (the add lands at the end of the * page). * - Non-empty must resolve to a container block: either an * existing one in `document`, or an add appearing earlier in * AiGenStructureDiff.add. Back-references only — the parent * must be either already in the document or appear before this * entry in the same `add` list. Forward-references are rejected * by the server (parent_id reset to empty with a warning in the * logs). * - The add lands at the end of its parent scope; fine-grained * positioning is done by the client via placeholder slots * before the edit (see Placeholder in ai_builder_document.proto). * * Recommended client flow: process AiGenStructureDiff.add entries * left-to-right; each entry's parent_id is guaranteed resolvable * against the entries the client has already processed plus the * pre-edit document. */ parentId: string; }; export type AiGenStructureReplace = { /** existing block path, preserved */ blockPath: string[]; typeHint: AiGenBlockTypeHint; prompt: string; /** * Existing parent_id of the replaced block, echoed for completeness * so the client doesn't need a side-table lookup. Replace never moves * a block across containers — for that, the planner emits remove+add. */ parentId: string; }; /** * Emitted once for edit (when the planner ran), before any block_delta. * Not emitted on the block_path bypass path. * * Wire contract: the `add` list is ordered. Each entry's parent_id may * reference an earlier entry in the same list (back-reference); the * server rejects forward-references on sanitisation. Clients should * apply `add` entries left-to-right so a pending parent is in place * before any of its children. `remove` and `replace` are commutative * and may be applied in any order. */ export type AiGenStructureDiff = { add: AiGenStructureAdd[]; remove: AiGenBlockPath[]; replace: AiGenStructureReplace[]; updateSettings?: pushwoosh_aibuilder_v1_DocumentSettings; updateTextVariants: Record; updateColorScheme: Record; }; /** * Full block delivered after generation. Images (if any) arrive separately * via image_delta events referencing the same block_path. * * The payload reuses AiBuilderBlock from the document model directly — * the AiGen stream and storage carry the same shape, no wire/document * conversion in between. Two wire invariants on `block`: * - block.id is always empty. Block id lives in `block_path` (last * segment); clients use block_path as the source of truth. * - block.parent_id is always empty. Parent linkage is already * carried on the AiGenStructureAdd / AiGenStructureBlock entry * that introduced this block; clients use that. * block.styles carries the LLM-managed subset of BlockStyles * (background_color, border_color, border_radius). Client-managed * fields like border_width stay unset on the wire — the client owns * them end-to-end across edits. */ export type AiGenBlockDelta = { blockPath: string[]; block: pushwoosh_aibuilder_v1_AiBuilderBlock; }; export type AiGenImageDelta = { blockPath: string[]; /** * Field path inside the block ("image", "columns[0].image", ...), * matching the content-addressing scheme used by the old Create API. */ field: string; src: string; alt: string; width: number; height: number; mediaUuid: string; }; export type AiGenBlockError = { blockPath: string[]; /** * Optional field path for granular errors (e.g. a specific image inside a * block). Empty → the block itself failed. */ field: string; error: string; }; export type AiGenDoneEvent = { summary: string; }; /** * AiGenMetaDelta carries an updated value for one field of the document's * Meta so the client can render it as it arrives instead of waiting for * done. `field` is a document-level meta field name: * "template_name" | "email_subject" | "email_preheader" | "email_type" | * "footer_company" | "font_family". */ export type AiGenMetaDelta = { field: string; value: string; }; /** * AiGenColorSchemeDelta carries a document-level colour palette the model * derived for this generation — from a reference image when one was sent * ("make it like this design"), otherwise from the brand / topic in the * prompt. Emitted on create only, and only when the request carried no * color_scheme of its own (a client/template palette is never overridden). * The client applies it to AiBuilderDocument.color_scheme; the same palette * also feeds the content generator this run, so freshly generated blocks * draw their accent colours from it. Keys are role names (primary, accent, * background, …); values are the proto ColorSchemeItem (hex + description). */ export type AiGenColorSchemeDelta = { colorScheme: Record; }; export type AiGenEvent_event_progress = { type: 'progress'; data: string; }; export type AiGenEvent_event_structure = { type: 'structure'; data: AiGenStructure; }; export type AiGenEvent_event_structureDiff = { type: 'structureDiff'; data: AiGenStructureDiff; }; export type AiGenEvent_event_blockDelta = { type: 'blockDelta'; data: AiGenBlockDelta; }; export type AiGenEvent_event_imageDelta = { type: 'imageDelta'; data: AiGenImageDelta; }; export type AiGenEvent_event_blockError = { type: 'blockError'; data: AiGenBlockError; }; export type AiGenEvent_event_done = { type: 'done'; data: AiGenDoneEvent; }; export type AiGenEvent_event_fatal = { type: 'fatal'; data: string; }; export type AiGenEvent_event_metaDelta = { type: 'metaDelta'; data: AiGenMetaDelta; }; export type AiGenEvent_event_colorSchemeDelta = { type: 'colorSchemeDelta'; data: AiGenColorSchemeDelta; }; export type AiGenEvent_event = AiGenEvent_event_progress | AiGenEvent_event_structure | AiGenEvent_event_structureDiff | AiGenEvent_event_blockDelta | AiGenEvent_event_imageDelta | AiGenEvent_event_blockError | AiGenEvent_event_done | AiGenEvent_event_fatal | AiGenEvent_event_metaDelta | AiGenEvent_event_colorSchemeDelta; export type AiGenEvent = { event: AiGenEvent_event; }; export type AiGenHtmlRequest = { /** * The document to render to HTML. Required; must carry at least one * page. All of its content and styling (blocks, text, buttons, image * src/alt, color_scheme, settings) is fed to the model verbatim — the * model only formats it, it does not rewrite the copy. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; /** * Document-domain profile for the output flavour. Required — the * server rejects UNSPECIFIED and FULL like on Generate. EMAIL produces * email-safe markup (table layout, inline CSS); WEB_POPUP produces a * modern responsive page. */ mode: AiGenMode; /** * Application id, used for media-context / logging parity with * Generate. Optional for rendering. */ application: string; /** * Optional reference images: "style it like this design". Fed to a * vision-capable provider (Claude, or our multimodal Qwen/vLLM) as * visual context for palette / typography / spacing. Empty = the * style is derived from the document and instructions only. Sent * inline as base64; reuses the same shape as Generate. */ referenceImages: AiGenReferenceImage[]; /** * Dev-only override of the LLM. Empty = the server default * (self-hosted Qwen/vLLM); "claude" routes through Anthropic. Same * semantics and gating as AiGenRequest.llm_provider. */ llmProvider?: string; /** * Dev-only override of the concrete model name for the chosen * provider. Empty = the provider's configured default. */ llmModel?: string; /** * Optional free-form extra guidance from the user layered on top of * the document content ("dark theme", "more whitespace", "single * column"). Empty = render faithfully with sensible defaults. */ instructions: string; }; export type AiGenHtmlEvent_event_progress = { type: 'progress'; data: string; }; export type AiGenHtmlEvent_event_htmlDelta = { type: 'htmlDelta'; data: string; }; export type AiGenHtmlEvent_event_done = { type: 'done'; data: AiGenDoneEvent; }; export type AiGenHtmlEvent_event_fatal = { type: 'fatal'; data: string; }; export type AiGenHtmlEvent_event = AiGenHtmlEvent_event_progress | AiGenHtmlEvent_event_htmlDelta | AiGenHtmlEvent_event_done | AiGenHtmlEvent_event_fatal; /** AiGenHtmlEvent is one frame of the RenderHtml stream. */ export type AiGenHtmlEvent = { event: AiGenHtmlEvent_event; }; export type AiGenExtractStyleRequest = { /** * The image to sample the style from. Required. Sent inline as base64, * same shape as RenderHtml's reference images. Needs a vision-capable * provider (Claude, or our multimodal Qwen/vLLM). */ image: AiGenReferenceImage; /** Dev-only LLM override, same semantics as AiGenRequest.llm_provider. */ llmProvider?: string; llmModel?: string; /** * Application code. Required by the server's application-scoping * middleware (same as the other AiGen RPCs); the extractor itself does * not use it yet — it samples style from the image alone — so today it * only satisfies app/account scoping. Kept here so the wire contract is * consistent and a future media-aware extension has it ready. */ application: string; }; export type AiGenExtractStyleResponse = { /** * Palette sampled from the image, keyed by semantic role. The model * returns ONLY the roles it can confidently derive — absent roles are * the client's to fill from its own defaults. Preferred role keys * (the model may add others when clearly present in the image): * background, surface, border, text, text-secondary, text-disabled, * text-on-accent, accent, accent-hover, link, success, warning, error. * Values are the proto ColorSchemeItem (hex + human-readable label). */ colorScheme: Record; /** * CSS font-family chain for body text inferred from the image's * typographic feel (e.g. "Inter, system-ui, sans-serif"). Empty when * the image gives no clear signal. The exact face can't be identified * from an image — this is a best-effort closest stack. */ fontFamily: string; /** * CSS font-family chain for headings. Empty when no clear signal or * when it matches font_family. */ headingFontFamily: string; /** * Short free-text description of the typographic character (e.g. * "geometric sans, heavy display headings, generous tracking") for * downstream use such as RenderHtml. Empty when not determinable. */ typographyNotes: string; }; /** * AiGenSourceFormat tags a source's content type; it decides how the server feeds it to * the model. PDF needs a document-capable provider, else FailedPrecondition. */ export type AiGenSourceFormat = 'AI_GEN_SOURCE_FORMAT_UNSPECIFIED' | 'AI_GEN_SOURCE_FORMAT_TEXT' | 'AI_GEN_SOURCE_FORMAT_MARKDOWN' /** Fed as text: the model reads the content, it does not re-emit the markup. */ | 'AI_GEN_SOURCE_FORMAT_HTML' /** content is base64 PDF bytes (no data: prefix), forwarded as a native document. */ | 'AI_GEN_SOURCE_FORMAT_PDF'; /** * AiGenSource is reference material the model works off instead of inventing from the * prompt. Factual source material, never instructions. */ export type AiGenSource = { /** Required; UNSPECIFIED is InvalidArgument. */ format: AiGenSourceFormat; /** UTF-8 text, or base64 bytes for PDF. Required; capped at 200 KB (PDF ~12 MB base64). */ content: string; /** Optional label distinguishing multiple sources ("product brief"). */ name: string; }; /** * AiGenTheme is a role-keyed palette plus the two font stacks — the extractor's own * output shape, not a document's color_scheme. */ export type AiGenTheme = { /** * Keyed by semantic role, values strictly hex. ExtractTheme fills every role: * background, surface, text, heading, text-secondary, accent, text-on-accent, border, link. */ colors: Record; /** CSS font-family chain for body text, e.g. "Inter, system-ui, sans-serif". */ fontFamily: string; /** Headings; may equal font_family. */ headingFontFamily: string; }; /** At least one of image / sources / description / site_url is required; any mix is allowed. */ export type AiGenExtractThemeRequest = { /** Screenshot, logo or moodboard. Optional; needs a vision-capable model. */ image?: AiGenReferenceImage; /** Application code, for account/app scoping middleware and logging. */ application: string; /** Dev-only LLM override, same semantics as AiGenRequest.llm_provider: empty = the default. */ llmProvider?: string; llmModel?: string; /** Brand materials: brand-book PDF, HTML page, markdown or plain text. Optional. */ sources: AiGenSource[]; /** Written brand description ("dark green, minimalist, premium outdoor brand"). Optional. */ description: string; /** * Public site to read the brand off ("acme.com"). The server distils its colours, fonts * and meta into one source and adopts the logo it finds as the image. Optional. */ siteUrl: string; }; export type AiGenExtractThemeResponse = { theme: AiGenTheme; /** The logo found on site_url; empty without a site or a find. The client decides. */ logoUrl: string; /** How the brand talks, 1-3 sentences, for a copywriter to imitate. Empty without copy. */ brandVoice: string; }; export type AiGenTranslateRequest = { /** Application code, for account/app scoping middleware and logging. */ application: string; /** * Source texts: plain, inline HTML, merge tags / Liquid / %%macros%% (all kept verbatim). * Capped at 300 texts and 60 000 characters in total. */ texts: string[]; /** * Language codes as the editor names them ("es", "pt-BR"), without the source language. * Capped at 20. */ targetLanguages: string[]; /** Optional hint steering tone and terminology (campaign topic, brand voice). */ context: string; /** Dev-only LLM override, same semantics as AiGenRequest.llm_provider: empty = the default. */ llmProvider?: string; llmModel?: string; }; export type AiGenTranslatedTexts = { /** Index-aligned with AiGenTranslateRequest.texts. */ texts: string[]; }; export type AiGenTranslateResponse = { /** Exactly the requested languages: all-or-nothing, a failed language fails the call. */ translations: Record; }; export type AiGenConvertUnlayerRequest = { /** * The Unlayer design JSON (EmailContent.Unlayer.editor_config): the * object carrying body.rows[].columns[].contents[]. Required. */ editorConfig: JsonObject; /** * Unlayer localization placeholders. Accepts either the per-language * map {"": {"": ""}} stored alongside the design in * EmailContent.Unlayer.localization_data, or an already-flat * {"": ""} map. Values are substituted into the design's * {{path|type|fallback}} tokens during conversion. Optional — without * it tokens resolve to their inline fallbacks. */ localizationData: JsonObject; /** * Language key to resolve localized values with when localization_data * is per-language. Empty → "default", falling back to the first * language present. */ language: string; /** * Application code. Required by the server's application-scoping * middleware (same as the other AiGen RPCs); the converter itself does * not use it. */ application: string; /** * Optional document metadata carried into the result verbatim: the * template name (Meta.template_name) and the email subject * (EmailSettings.subject). Both live outside editor_config on the * stored EmailTemplate, so the caller supplies them. */ templateName: string; subject: string; /** * Document-domain profile to convert into. UNSPECIFIED is treated as * EMAIL for wire compatibility with earlier clients. WEB_POPUP converts * an Unlayer popup design (displayMode "popup"): body.values.popup* * map to PopupSettings and the popup container width/background, and no * EmailSettings are produced. Other modes are rejected. */ mode: AiGenMode; /** * The template's own Unlayer export (EmailContent.Unlayer.html). Only * sniffed for the p{margin:0} reset: exports without it render paragraphs * with the client-default gap, which conversion must reproduce. Optional. */ exportedHtml: string; }; export type AiGenConvertUnlayerResponse = { /** * The converted document. Complete and EMAIL-mode valid; block ids are * deterministic ("u-") so repeated conversions of the same * design yield the same document. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; /** * Human-readable notes about content that could not be mapped 1:1 — * unsupported widgets replaced with placeholders, dropped image links, * per-column backgrounds, and similar. Order follows document order. * Empty when the conversion was lossless. */ warnings: string[]; }; export type AiGenDocumentToHtmlRequest = { /** The document to render. Required; must carry at least one page. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; /** * Application id. Required: it scopes the request and it is how the * renderer resolves the products feed credential, which is deliberately * not a document field (see the note on ProductsRule). */ application: string; /** * Base URL of this service's public image endpoints (countdown GIF, barcode * PNG). Empty = the server default. Note it lands in SENT EMAIL, fetched by * recipients' mail clients, so it is never a localhost route. */ timerBaseUrl: string; /** * Save-path emission, all three ON when unset — that is what the editor * does on save, and a plain proto-false default would quietly produce * preview HTML (baked product rows, no Liquid) that looks right and sends * wrong. Set false only to render a preview. */ syncedBlockTags?: boolean; displayConditionTags?: boolean; dynamicProductTags?: boolean; /** * Anchor text of the "view in browser" strip. Empty = the built-in label * for the document's default language. */ webVersionLabel: string; /** * Bake ad slots. OFF when unset, unlike the three above: it follows the * account's AllowEmailAds, and a caller that does not know it means "no ads". */ adSlotTags: boolean; }; export type AiGenDocumentsToHtmlItem = { /** Caller's own id, echoed on the result so a batch can be matched up. */ id: string; document: pushwoosh_aibuilder_v1_AiBuilderDocument; }; export type AiGenDocumentsToHtmlRequest = { /** At least one, at most 64 — a render is CPU on a single-replica service. */ documents: AiGenDocumentsToHtmlItem[]; /** Application id. Required: it scopes the request to the caller's account. */ application: string; /** Base URL for the countdown-GIF service baked into timer items. */ timerBaseUrl: string; /** Save-path emission, all three ON when unset — same rule as DocumentToHtml. */ syncedBlockTags?: boolean; displayConditionTags?: boolean; dynamicProductTags?: boolean; webVersionLabel: string; /** Same contract as DocumentToHtml: OFF when unset. */ adSlotTags: boolean; }; export type AiGenDocumentsToHtmlResult = { id: string; html: string; localizationData: Record; /** Set instead of html when this document alone could not be rendered. */ error: string; }; export type AiGenDocumentsToHtmlResponse = { /** One result per requested document, in request order. */ results: AiGenDocumentsToHtmlResult[]; }; /** * A per-language {key: value} map. proto forbids a map whose value is a map, * so the inner one is wrapped. */ export type LocalizationLanguage = { values: Record; }; export type AiGenDocumentToPageHtmlRequest = { /** The document to render. Required; must carry at least one page. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; /** * Application id. Required: it scopes the request to the caller's account, * the same check the email renderer makes. */ application: string; /** * Base URL for the countdown-GIF service baked into timer items. Empty = * the server default. */ timerBaseUrl: string; }; export type AiGenDocumentToPageHtmlResponse = { /** The complete page document, ready to store as CloudPage.content. */ html: string; /** * The document it was rendered from, so the caller writes the same pair the * editor does — content and pushwoosh_json — without desyncing them. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; }; export type AiGenDocumentToFragmentHtmlRequest = { /** * The fragment. Required; its first page's blocks are what gets rendered, * and its settings / text variants / colour scheme are the style context. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; /** Application id. Required: it scopes the request to the caller's account. */ application: string; timerBaseUrl: string; }; export type AiGenDocumentToFragmentHtmlResponse = { html: string; localizationData: Record; document: pushwoosh_aibuilder_v1_AiBuilderDocument; }; /** Corner the close cross sits in. UNSPECIFIED is top-right, the stored default. */ export type PopupCloseButtonPosition = 'POPUP_CLOSE_BUTTON_POSITION_UNSPECIFIED' | 'POPUP_CLOSE_BUTTON_POSITION_TOP_LEFT' | 'POPUP_CLOSE_BUTTON_POSITION_TOP_RIGHT' | 'POPUP_CLOSE_BUTTON_POSITION_BOTTOM_LEFT' | 'POPUP_CLOSE_BUTTON_POSITION_BOTTOM_RIGHT'; /** * The close cross. NOT part of the document: it lives in the rich media's * style_settings beside it, the same way the products feed token is a render * option rather than a document field. An absent message means the stored * default (enabled, top-right, grey chip). */ export type PopupCloseButton = { enabled: boolean; position: PopupCloseButtonPosition; /** "transparent" drops the chip and leaves the glyph alone. */ backgroundColor: string; iconColor: string; borderRadius: number; /** Offsets from the corner inset; negative values overhang the popup edge. */ margin: pushwoosh_aibuilder_v1_EdgeInsets; }; /** * Where the card sits on screen ("Position on screen" in the In-app settings), * the other half of style_settings that renders. UNSPECIFIED is centred, which * is also what FULLSCREEN and CENTER produce. */ export type PopupPosition = 'POPUP_POSITION_UNSPECIFIED' | 'POPUP_POSITION_FULLSCREEN' | 'POPUP_POSITION_TOP' | 'POPUP_POSITION_CENTER' | 'POPUP_POSITION_BOTTOM'; export type AiGenDocumentToPopupHtmlRequest = { document: pushwoosh_aibuilder_v1_AiBuilderDocument; /** Application id. Required: it scopes the request to the caller's account. */ application: string; timerBaseUrl: string; /** * Absolute URL of the statistics script; empty renders no script tag, which * means the popup records no clicks. */ statisticsScriptUrl: string; /** Absent = the stored default. */ closeButton: PopupCloseButton; /** An edge offset authored on the document outranks this. */ position: PopupPosition; }; export type AiGenDocumentToPopupHtmlResponse = { html: string; localizationData: Record; document: pushwoosh_aibuilder_v1_AiBuilderDocument; }; export type AiGenDocumentToHtmlResponse = { /** The complete email document, ready to store as EmailContent.Smartcards.html. */ html: string; /** * Per-language values for the {{key|html|}} tokens in `html`, keyed by * language ("default" plus each translated language). Empty for a * single-language document, which carries no tokens. */ localizationData: Record; /** * The document these two were rendered from. Returned so the caller stores * the same triple the editor does — html, localization_data and content — * without being able to desync them. */ document: pushwoosh_aibuilder_v1_AiBuilderDocument; }; export type AiGenMediaImageRequest = { application: string; /** Image generation prompt; required, capped server-side. */ prompt: string; /** * Gemini ImageConfig aspect ratio ("1:1", "16:9", "21:9", "3:4", ...). * Empty = the generator default. */ aspectRatio: string; }; export type AiGenMediaImageResponse = { /** The stored media file, ready for ContentImage.src / media_uuid. */ url: string; mediaUuid: string; width: number; height: number; }; /** * StockProvider names the upstream catalog. Only Pexels for now; the enum * exists so a second provider (Pixabay) is a value, not a schema break. */ export type StockProvider = /** treated as PEXELS */ 'STOCK_PROVIDER_UNSPECIFIED' | 'STOCK_PROVIDER_PEXELS'; export type AiGenStockSearchRequest = { application: string; query: string; /** 1-based page, provider-side pagination; 0 = first page. */ page: number; provider: StockProvider; }; /** * One search hit: enough for the picker tile + the provider's required * attribution. No full-size URLs — the import round-trips by id. */ export type StockPhoto = { id: string; /** Preview for the picker grid (provider CDN, display-only). */ thumbUrl: string; width: number; height: number; photographer: string; /** Photographer / photo page at the provider — the attribution link. */ photographerUrl: string; /** Average color for grid placeholders ("#aabbcc"); may be empty. */ avgColor: string; alt: string; }; export type AiGenStockSearchResponse = { photos: StockPhoto[]; total: number; page: number; provider: StockProvider; }; export type AiGenStockImportRequest = { application: string; provider: StockProvider; /** Provider photo id from a SearchStockPhotos hit. */ photoId: string; }; export type AiGenStockImportResponse = { url: string; mediaUuid: string; width: number; height: number; /** Alt suggested by the provider's metadata; may be empty. */ alt: string; }; export type AiGenImageImportRequest = { application: string; /** http(s) URL of the image. */ url: string; /** Stored with the file; the news block passes the article title. */ description: string; }; export type AiGenImageImportResponse = { url: string; mediaUuid: string; width: number; height: number; }; export type AiGenImageAltRequest = { application: string; /** * Image to describe. Must live on an allowlisted host (the application's * media storage or a stock CDN) — the server refuses to fetch elsewhere. */ imageUrl: string; /** Dev-only LLM override, same semantics as AiGenRequest.llm_provider. */ llmProvider?: string; llmModel?: string; /** * Optional language for the alt text (ISO code); empty = the image's * dominant/default language, typically English. */ language: string; }; export type AiGenImageAltResponse = { alt: string; };