@qafka/react-native 2.4.2 → 2.5.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.5.0] — 2026-07-19
11
+
12
+ ### Added
13
+
14
+ - Image generation: a new `generateImage()` method creates an image from a text description, or reframes one or more reference images to a target aspect ratio or exact pixel size without stretching or distortion. Results can be returned as base64, a signed time-limited URL, or both, and multiple variants can be requested in a single call. Higher-quality `pro` output is available when it is enabled on your plan. See the "Image Generation" section of the README for the full API, response shape, and error handling.
15
+
10
16
  ## [2.4.2] — 2026-06-22
11
17
 
12
18
  ### Fixed
package/README.md CHANGED
@@ -87,6 +87,78 @@ Pass `projectId` when your app has more than one registered project:
87
87
  <Qafka projectId="proj_abc123" />
88
88
  ```
89
89
 
90
+ ## Image Generation
91
+
92
+ Generate images from text descriptions or reframe reference images to target aspect ratios and sizes without distortion. The SDK must be initialized before calling this method.
93
+
94
+ ```tsx
95
+ import { getSDK } from '@qafka/react-native';
96
+
97
+ const result = await getSDK().generateImage({
98
+ description: 'product photo reframed to 16:9 landscape',
99
+ referenceImage: 'https://example.com/photo.jpg',
100
+ aspectRatio: '16:9',
101
+ quality: 'standard',
102
+ n: 1,
103
+ });
104
+ ```
105
+
106
+ ### Parameters
107
+
108
+ | Param | Type | Required | Notes |
109
+ |-------|------|----------|-------|
110
+ | `description` | string | Yes | Text description of the desired image or transformation. |
111
+ | `referenceImage` | string \| string[] | No | Base64 data-URI, URL, or array of either. Used to guide or reframe the output. |
112
+ | `aspectRatio` | string | No | Free-form ratio, e.g. `"16:9"`, `"7:3"`, `"1:1"`. Ignored if `size` is set. |
113
+ | `size` | `{ width: number; height: number }` | No | Exact pixel dimensions. Overrides `aspectRatio`. |
114
+ | `quality` | `'standard' \| 'pro'` | No | Default: `'standard'`. `'pro'` requires image generation pro to be enabled on your plan. |
115
+ | `responseType` | `'base64' \| 'url' \| 'both'` | No | Default: `'base64'`. `'url'` returns a signed, time-limited link; `'both'` returns both. |
116
+ | `n` | number | No | Number of variants to generate. Default: `1`. When `n > 1`, result is an array. |
117
+
118
+ ### Result
119
+
120
+ Each generated image is an object with:
121
+
122
+ ```tsx
123
+ {
124
+ base64?: string; // Returned when responseType includes 'base64'
125
+ url?: string; // Signed link, time-limited. Returned when responseType includes 'url'
126
+ expiresAt?: string; // ISO 8601 timestamp. Present when url is returned
127
+ mimeType: string; // e.g. 'image/png'
128
+ }
129
+ ```
130
+
131
+ When `n > 1`, the result is an array of such objects.
132
+
133
+ ### Example
134
+
135
+ Reframe an uploaded product photo to a banner size and fetch the result as a base64 image:
136
+
137
+ ```tsx
138
+ try {
139
+ const result = await getSDK().generateImage({
140
+ description: 'High-quality product banner, maintaining all product details',
141
+ referenceImage: productPhotoBase64,
142
+ size: { width: 1200, height: 300 },
143
+ responseType: 'base64',
144
+ });
145
+
146
+ setGeneratedImage(result.base64);
147
+ } catch (err) {
148
+ if (err.code === 'not_entitled') {
149
+ showAlert('Image generation is not enabled on your plan.');
150
+ } else if (err.code === 'rate_limited') {
151
+ showAlert('Too many requests. Please try again later.');
152
+ } else if (err.code === 'content_filtered') {
153
+ showAlert('The description or reference image was filtered for policy reasons.');
154
+ } else if (err.code === 'invalid_reference') {
155
+ showAlert('The reference image could not be processed.');
156
+ } else {
157
+ showAlert('Image generation failed. Please try again.');
158
+ }
159
+ }
160
+ ```
161
+
90
162
  ## Documentation
91
163
 
92
164
  Full guides, API reference, theming, voice mode, tools, and advanced configuration:
@@ -1,4 +1,4 @@
1
- import { SDKConfig, SDKStatusType, ChatMessage, ChatResponse } from './types';
1
+ import { SDKConfig, SDKStatusType, ChatMessage, ChatResponse, GenerateImageParams, GenerateImageResult } from './types';
2
2
  /**
3
3
  * QafkaSDK - Main SDK class
4
4
  * Provides AI-powered conversational interface for React Native apps
@@ -131,6 +131,10 @@ export declare class QafkaSDK {
131
131
  type: string;
132
132
  }, conversationId?: string): Promise<any>;
133
133
  /**
134
+ * Generate an image based on a description
135
+ */
136
+ generateImage(params: GenerateImageParams): Promise<GenerateImageResult>;
137
+ /**
134
138
  * Get project theme configuration
135
139
  */
136
140
  getProjectTheme(): Promise<any>;
package/dist/QafkaSDK.js CHANGED
@@ -383,6 +383,21 @@ class QafkaSDK {
383
383
  return this.backendService.uploadFile(toolId, file, conversationId);
384
384
  }
385
385
  /**
386
+ * Generate an image based on a description
387
+ */
388
+ async generateImage(params) {
389
+ if (this.status !== 'ready') {
390
+ throw new Error('SDK not initialized. Call initialize() first.');
391
+ }
392
+ if (!params?.description?.trim()) {
393
+ throw new Error('description cannot be empty');
394
+ }
395
+ if (!this.backendService) {
396
+ throw new Error('Backend service not initialized');
397
+ }
398
+ return this.backendService.generateImage(params);
399
+ }
400
+ /**
386
401
  * Get project theme configuration
387
402
  */
388
403
  async getProjectTheme() {
@@ -1,4 +1,4 @@
1
- import { ChatResponse } from '../types';
1
+ import { ChatResponse, GenerateImageParams, GenerateImageResult } from '../types';
2
2
  /**
3
3
  * Backend API Service using native fetch
4
4
  */
@@ -19,6 +19,7 @@ export declare class BackendService {
19
19
  private onToolDataRequested;
20
20
  private readonly toolDataResolvedFor;
21
21
  private static readonly RESOLVER_TIMEOUT_MS;
22
+ private static readonly IMAGE_TIMEOUT_MS;
22
23
  constructor(apiKey: string | null, apiUrl?: string, subProjectId?: string);
23
24
  setSessionTokenGetter(getter: () => Promise<string | null>): void;
24
25
  /**
@@ -80,7 +81,9 @@ export declare class BackendService {
80
81
  */
81
82
  private postToolData;
82
83
  /**
83
- * Make HTTP request with timeout
84
+ * Make HTTP request with timeout. `timeoutMs` optionally overrides the
85
+ * instance default (`this.timeout`) for calls that are known to run long,
86
+ * e.g. image generation.
84
87
  */
85
88
  private fetchWithTimeout;
86
89
  /**
@@ -104,6 +107,22 @@ export declare class BackendService {
104
107
  */
105
108
  getTheme(): Promise<any>;
106
109
  /**
110
+ * Generate one or more images via the backend `/images` endpoint.
111
+ * Uses a much longer timeout than the default (image inference is slow).
112
+ * When `params.n` is absent or <= 1, the single result is unwrapped from
113
+ * the `images` array; otherwise the full array is returned.
114
+ */
115
+ generateImage(params: GenerateImageParams): Promise<GenerateImageResult>;
116
+ /**
117
+ * Response handler for `/images`. Mirrors `handleResponse`'s ok/!ok split
118
+ * but maps the server's `errorData.error` onto `Error.code` (same
119
+ * augmentation pattern as the `DEV_KEY_INVALID` error in
120
+ * `sendMessageStream`) so callers can branch on stable error codes
121
+ * (`not_entitled`, `rate_limited`, `content_filtered`, `invalid_reference`,
122
+ * `generation_failed`, ...) instead of parsing message strings.
123
+ */
124
+ private handleImageResponse;
125
+ /**
107
126
  * Send message with streaming response
108
127
  * React Native compatible version using XMLHttpRequest
109
128
  */
@@ -32,6 +32,9 @@ class BackendService {
32
32
  onToolDataRequested = null;
33
33
  toolDataResolvedFor = new Map();
34
34
  static RESOLVER_TIMEOUT_MS = 3000;
35
+ // Image generation is slow (model inference); give it a much longer
36
+ // budget than the default 60s used for chat.
37
+ static IMAGE_TIMEOUT_MS = 120_000;
35
38
  constructor(apiKey, apiUrl, subProjectId) {
36
39
  // If custom apiUrl provided, ensure it has /api/v1 prefix
37
40
  if (apiUrl) {
@@ -233,11 +236,13 @@ class BackendService {
233
236
  });
234
237
  }
235
238
  /**
236
- * Make HTTP request with timeout
239
+ * Make HTTP request with timeout. `timeoutMs` optionally overrides the
240
+ * instance default (`this.timeout`) for calls that are known to run long,
241
+ * e.g. image generation.
237
242
  */
238
- async fetchWithTimeout(url, options) {
243
+ async fetchWithTimeout(url, options, timeoutMs = this.timeout) {
239
244
  const controller = new AbortController();
240
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
245
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
241
246
  try {
242
247
  const response = await this.fetchWithRefresh(url, {
243
248
  ...options,
@@ -410,6 +415,45 @@ class BackendService {
410
415
  }
411
416
  }
412
417
  /**
418
+ * Generate one or more images via the backend `/images` endpoint.
419
+ * Uses a much longer timeout than the default (image inference is slow).
420
+ * When `params.n` is absent or <= 1, the single result is unwrapped from
421
+ * the `images` array; otherwise the full array is returned.
422
+ */
423
+ async generateImage(params) {
424
+ const response = await this.fetchWithTimeout(`${this.baseURL}/images`, {
425
+ method: 'POST',
426
+ body: JSON.stringify(params),
427
+ }, BackendService.IMAGE_TIMEOUT_MS);
428
+ const data = await this.handleImageResponse(response);
429
+ const images = data.images ?? [];
430
+ return (params.n ?? 1) > 1 ? images : images[0];
431
+ }
432
+ /**
433
+ * Response handler for `/images`. Mirrors `handleResponse`'s ok/!ok split
434
+ * but maps the server's `errorData.error` onto `Error.code` (same
435
+ * augmentation pattern as the `DEV_KEY_INVALID` error in
436
+ * `sendMessageStream`) so callers can branch on stable error codes
437
+ * (`not_entitled`, `rate_limited`, `content_filtered`, `invalid_reference`,
438
+ * `generation_failed`, ...) instead of parsing message strings.
439
+ */
440
+ async handleImageResponse(response) {
441
+ if (response.ok) {
442
+ return response.json();
443
+ }
444
+ const errorData = await response.json().catch(() => ({}));
445
+ const err = new Error(errorData.message || `❌ Image generation failed (HTTP ${response.status})`);
446
+ err.code = errorData.error;
447
+ err.status = response.status;
448
+ // RateLimitGuard returns 429 with body { error: 'RATE_LIMIT_EXCEEDED' },
449
+ // but the public contract promises 'rate_limited' — normalize so callers
450
+ // can reliably branch on it.
451
+ if (response.status === 429) {
452
+ err.code = 'rate_limited';
453
+ }
454
+ throw err;
455
+ }
456
+ /**
413
457
  * Send message with streaming response
414
458
  * React Native compatible version using XMLHttpRequest
415
459
  */
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Image generation types for QafkaSDK
3
+ * Parameters and results for generateImage() method
4
+ */
5
+ export interface GenerateImageParams {
6
+ description: string;
7
+ referenceImage?: string | string[];
8
+ aspectRatio?: string;
9
+ size?: {
10
+ width: number;
11
+ height: number;
12
+ };
13
+ quality?: 'standard' | 'pro';
14
+ responseType?: 'base64' | 'url' | 'both';
15
+ n?: number;
16
+ }
17
+ export interface GeneratedImage {
18
+ base64?: string;
19
+ url?: string;
20
+ expiresAt?: string;
21
+ mimeType: string;
22
+ }
23
+ export type GenerateImageResult = GeneratedImage | GeneratedImage[];
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Image generation types for QafkaSDK
4
+ * Parameters and results for generateImage() method
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -7,3 +7,4 @@ export * from './chat';
7
7
  export * from './sdk';
8
8
  export * from './components';
9
9
  export * from './external-navigation';
10
+ export * from './image';
@@ -23,3 +23,4 @@ __exportStar(require("./chat"), exports);
23
23
  __exportStar(require("./sdk"), exports);
24
24
  __exportStar(require("./components"), exports);
25
25
  __exportStar(require("./external-navigation"), exports);
26
+ __exportStar(require("./image"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qafka/react-native",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "Drop-in AI assistant for React Native: chat, voice, and tool execution with screen-aware navigation.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",