@coreviz/sdk 1.0.14 → 1.0.17

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.md CHANGED
@@ -51,6 +51,17 @@ Check out [coreviz.io/tools](https://coreviz.io/tools) to explore these features
51
51
  npm install @coreviz/sdk
52
52
  ```
53
53
 
54
+ ### React Native / Expo
55
+
56
+ When using this SDK in Expo / React Native, install the Expo image utilities (used for `resize`):
57
+
58
+ ```bash
59
+ npx expo install expo-image-manipulator expo-file-system
60
+ ```
61
+
62
+ Notes:
63
+ - **Local mode** (`mode: 'local'`) for `tag()` / `embed()` is **not supported** on React Native / Expo.
64
+
54
65
  ## Configuration
55
66
 
56
67
  To use the AI features, you need to instantiate the `CoreViz` class with your API key.
@@ -0,0 +1,49 @@
1
+ export interface CoreVizConfig {
2
+ apiKey?: string;
3
+ token?: string;
4
+ }
5
+ export interface DescribeOptions {
6
+ }
7
+ export interface EditOptions {
8
+ prompt: string;
9
+ aspectRatio?: 'match_input_image' | '1:1' | '16:9' | '9:16' | '4:3' | '3:4';
10
+ outputFormat?: 'jpg' | 'png';
11
+ model?: 'flux-kontext-max' | 'google/nano-banana' | 'seedream-4';
12
+ }
13
+ export interface TagOptions {
14
+ prompt: string;
15
+ options?: string[];
16
+ multiple?: boolean;
17
+ mode?: 'api' | 'local';
18
+ }
19
+ export interface TagResponse {
20
+ tags: string[];
21
+ raw?: unknown;
22
+ }
23
+ export interface EmbedOptions {
24
+ type?: 'image' | 'text';
25
+ mode?: 'api' | 'local';
26
+ }
27
+ export interface EmbedResponse {
28
+ embedding: number[];
29
+ }
30
+ export interface BatchGenerateOptions {
31
+ referenceImages?: string[];
32
+ count?: number;
33
+ aspectRatio?: '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '4:5' | '5:4' | '9:16' | '16:9' | '21:9';
34
+ model?: 'google/nano-banana' | 'google/nano-banana-pro';
35
+ }
36
+ export declare class CoreViz {
37
+ private apiKey?;
38
+ private token?;
39
+ constructor(config?: CoreVizConfig);
40
+ private getHeaders;
41
+ private handleResponse;
42
+ describe(image: string, _options?: DescribeOptions): Promise<string>;
43
+ edit(image: string, options: EditOptions): Promise<string>;
44
+ batchGenerate(prompt: string, options?: BatchGenerateOptions): Promise<string[]>;
45
+ tag(image: string, options: TagOptions): Promise<TagResponse>;
46
+ embed(input: string, options?: EmbedOptions): Promise<EmbedResponse>;
47
+ resize(input: string | File, maxWidth?: number, maxHeight?: number): Promise<string>;
48
+ similarity(vecA: number[], vecB: number[]): number;
49
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CoreViz = void 0;
4
+ const resize_native_1 = require("./resize.native");
5
+ class CoreViz {
6
+ constructor(config = {}) {
7
+ // React Native / Expo doesn't provide `process.env` in the same way; keep config explicit.
8
+ this.apiKey = config.apiKey;
9
+ this.token = config.token;
10
+ }
11
+ getHeaders() {
12
+ const headers = {
13
+ 'Content-Type': 'application/json',
14
+ };
15
+ if (this.token) {
16
+ headers['Authorization'] = `Bearer ${this.token}`;
17
+ }
18
+ else {
19
+ headers['x-api-key'] = this.apiKey || '';
20
+ }
21
+ return headers;
22
+ }
23
+ async handleResponse(response) {
24
+ if (response.status === 402) {
25
+ throw new Error('Insufficient credits');
26
+ }
27
+ if (!response.ok) {
28
+ throw new Error(`Request failed (${response.status})`);
29
+ }
30
+ const data = (await response.json());
31
+ if (data.error) {
32
+ throw new Error(data.error);
33
+ }
34
+ return data;
35
+ }
36
+ async describe(image, _options) {
37
+ const resizedImage = await (0, resize_native_1.resize)(image, 512, 512);
38
+ const headers = this.getHeaders();
39
+ const response = await fetch(`https://lab.coreviz.io/api/ai/describe`, {
40
+ method: 'POST',
41
+ headers,
42
+ body: JSON.stringify({ image: resizedImage }),
43
+ });
44
+ const data = await this.handleResponse(response);
45
+ return data.description;
46
+ }
47
+ async edit(image, options) {
48
+ const resizedImage = await (0, resize_native_1.resize)(image, 1024, 1024);
49
+ const headers = this.getHeaders();
50
+ const response = await fetch(`https://lab.coreviz.io/api/ai/edit`, {
51
+ method: 'POST',
52
+ headers,
53
+ body: JSON.stringify({
54
+ image: resizedImage,
55
+ prompt: options.prompt,
56
+ aspectRatio: options.aspectRatio || 'match_input_image',
57
+ outputFormat: options.outputFormat || 'jpg',
58
+ model: options.model || 'flux-kontext-max',
59
+ }),
60
+ });
61
+ const data = await this.handleResponse(response);
62
+ return data.result;
63
+ }
64
+ async batchGenerate(prompt, options = {}) {
65
+ const headers = this.getHeaders();
66
+ let resizedImages = [];
67
+ if (options.referenceImages && options.referenceImages.length > 0) {
68
+ resizedImages = await Promise.all(options.referenceImages.map((img) => (0, resize_native_1.resize)(img, 1024, 1024)));
69
+ }
70
+ const response = await fetch(`https://lab.coreviz.io/api/ai/batch-generate`, {
71
+ method: 'POST',
72
+ headers,
73
+ body: JSON.stringify({
74
+ prompt,
75
+ image: resizedImages.length > 0 ? resizedImages : undefined,
76
+ count: options.count || 1,
77
+ aspectRatio: options.aspectRatio,
78
+ model: options.model || 'google/nano-banana-pro',
79
+ }),
80
+ });
81
+ const data = await this.handleResponse(response);
82
+ return data.results || [];
83
+ }
84
+ async tag(image, options) {
85
+ const mode = options?.mode || 'api';
86
+ if (mode === 'local') {
87
+ throw new Error("Local tagging is not supported on React Native/Expo. Use `mode: 'api'`.");
88
+ }
89
+ const resizedImage = await (0, resize_native_1.resize)(image, 512, 512);
90
+ const headers = this.getHeaders();
91
+ const response = await fetch('https://lab.coreviz.io/api/ai/tag', {
92
+ method: 'POST',
93
+ headers,
94
+ body: JSON.stringify({
95
+ image: resizedImage,
96
+ prompt: options.prompt,
97
+ options: options.options && options.options.length > 0 ? options.options : undefined,
98
+ multiple: options.multiple ?? true,
99
+ }),
100
+ });
101
+ const data = await this.handleResponse(response);
102
+ const tags = Array.isArray(data.tags)
103
+ ? data.tags
104
+ : Array.isArray(data.result)
105
+ ? data.result
106
+ : typeof data.tag === 'string'
107
+ ? [data.tag]
108
+ : [];
109
+ return {
110
+ tags,
111
+ raw: data,
112
+ };
113
+ }
114
+ async embed(input, options) {
115
+ const mode = options?.mode || 'api';
116
+ if (mode === 'local') {
117
+ throw new Error("Local embedding is not supported on React Native/Expo. Use `mode: 'api'`.");
118
+ }
119
+ const headers = this.getHeaders();
120
+ let body = {};
121
+ // Determine type
122
+ let isImage = false;
123
+ if (options?.type) {
124
+ isImage = options.type === 'image';
125
+ }
126
+ else {
127
+ // Heuristic: assume URL/data:image => image
128
+ isImage = input.startsWith('data:image') || input.startsWith('http://') || input.startsWith('https://') || input.startsWith('file://');
129
+ }
130
+ if (isImage) {
131
+ const resizedImage = await (0, resize_native_1.resize)(input, 512, 512);
132
+ body.image = resizedImage;
133
+ }
134
+ else {
135
+ body.text = input;
136
+ }
137
+ const response = await fetch('https://lab.coreviz.io/api/ai/embed', {
138
+ method: 'POST',
139
+ headers,
140
+ body: JSON.stringify(body),
141
+ });
142
+ const data = await this.handleResponse(response);
143
+ return data;
144
+ }
145
+ async resize(input, maxWidth, maxHeight) {
146
+ return (0, resize_native_1.resize)(input, maxWidth, maxHeight);
147
+ }
148
+ similarity(vecA, vecB) {
149
+ if (vecA.length !== vecB.length)
150
+ return 0;
151
+ let dotProduct = 0;
152
+ let normA = 0;
153
+ let normB = 0;
154
+ for (let i = 0; i < vecA.length; i++) {
155
+ dotProduct += vecA[i] * vecB[i];
156
+ normA += vecA[i] * vecA[i];
157
+ normB += vecB[i] * vecB[i];
158
+ }
159
+ if (normA === 0 || normB === 0)
160
+ return 0;
161
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
162
+ }
163
+ }
164
+ exports.CoreViz = CoreViz;
@@ -0,0 +1,4 @@
1
+ import { CoreViz, CoreVizConfig, DescribeOptions, EditOptions, TagOptions, TagResponse, EmbedOptions, EmbedResponse } from './coreviz.native';
2
+ import { resize } from './resize.native';
3
+ export { CoreViz, resize };
4
+ export type { CoreVizConfig, DescribeOptions, EditOptions, TagOptions, TagResponse, EmbedOptions, EmbedResponse };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resize = exports.CoreViz = void 0;
4
+ const coreviz_native_1 = require("./coreviz.native");
5
+ Object.defineProperty(exports, "CoreViz", { enumerable: true, get: function () { return coreviz_native_1.CoreViz; } });
6
+ const resize_native_1 = require("./resize.native");
7
+ Object.defineProperty(exports, "resize", { enumerable: true, get: function () { return resize_native_1.resize; } });
package/dist/resize.js CHANGED
@@ -112,6 +112,7 @@ async function serverResize(inputStr, maxWidth, maxHeight) {
112
112
  }
113
113
  try {
114
114
  // Dynamic import to prevent bundling sharp on the client
115
+ // Note: `sharp` is an optional dependency (for RN/Expo compatibility). If missing, we gracefully fall back.
115
116
  const sharpModule = await Promise.resolve().then(() => __importStar(require('sharp')));
116
117
  const sharp = sharpModule.default;
117
118
  let buffer;
@@ -155,7 +156,7 @@ async function serverResize(inputStr, maxWidth, maxHeight) {
155
156
  return inputStr;
156
157
  }
157
158
  catch (error) {
158
- console.warn("Failed to resize image on server:", error);
159
+ console.warn("Failed to resize image on server. If you need server-side resizing, ensure `sharp` is installed. Falling back to original image.", error);
159
160
  return inputStr; // Fallback to original
160
161
  }
161
162
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * React Native / Expo image resize implementation.
3
+ *
4
+ * - Uses `expo-image-manipulator` to resize while preserving aspect ratio (fit: inside).
5
+ * - Supports inputs:
6
+ * - `file://...` URIs (typical from Expo ImagePicker/Camera)
7
+ * - `data:image/...;base64,...` data URLs
8
+ * - `http(s)://...` URLs (downloaded to cache first)
9
+ *
10
+ * Dependencies (expected to be installed by Expo apps):
11
+ * - expo-image-manipulator
12
+ * - expo-file-system
13
+ */
14
+ type ResizeInput = string | File;
15
+ export declare function resize(input: ResizeInput, maxWidth?: number, maxHeight?: number): Promise<string>;
16
+ export {};
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /**
3
+ * React Native / Expo image resize implementation.
4
+ *
5
+ * - Uses `expo-image-manipulator` to resize while preserving aspect ratio (fit: inside).
6
+ * - Supports inputs:
7
+ * - `file://...` URIs (typical from Expo ImagePicker/Camera)
8
+ * - `data:image/...;base64,...` data URLs
9
+ * - `http(s)://...` URLs (downloaded to cache first)
10
+ *
11
+ * Dependencies (expected to be installed by Expo apps):
12
+ * - expo-image-manipulator
13
+ * - expo-file-system
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.resize = resize;
50
+ function isDataUrl(s) {
51
+ return /^data:image\/\w+;base64,/.test(s);
52
+ }
53
+ function isHttpUrl(s) {
54
+ return /^https?:\/\//.test(s);
55
+ }
56
+ function getExtFromDataUrl(dataUrl) {
57
+ const m = dataUrl.match(/^data:(image\/\w+);base64,/);
58
+ const mime = m?.[1] || 'image/jpeg';
59
+ return mime.includes('png') ? 'png' : 'jpg';
60
+ }
61
+ function fitInside(width, height, maxWidth, maxHeight) {
62
+ if (width <= 0 || height <= 0)
63
+ return { width, height };
64
+ const wRatio = maxWidth / width;
65
+ const hRatio = maxHeight / height;
66
+ const ratio = Math.min(wRatio, hRatio, 1); // never enlarge
67
+ return { width: Math.round(width * ratio), height: Math.round(height * ratio) };
68
+ }
69
+ async function getImageSize(uri) {
70
+ // Importing from 'react-native' is safe for RN builds; this file is never used in web/node builds.
71
+ const { Image } = await Promise.resolve().then(() => __importStar(require('react-native')));
72
+ return await new Promise((resolve, reject) => {
73
+ Image.getSize(uri, (width, height) => resolve({ width, height }), (error) => reject(error));
74
+ });
75
+ }
76
+ async function ensureLocalFileUri(input) {
77
+ const FileSystem = await Promise.resolve().then(() => __importStar(require('expo-file-system/legacy')));
78
+ const cacheDir = FileSystem.cacheDirectory;
79
+ // data URL -> write to cache
80
+ if (isDataUrl(input)) {
81
+ const format = getExtFromDataUrl(input);
82
+ const base64Data = input.split(',')[1] || '';
83
+ const fileUri = `${cacheDir}coreviz_${Date.now()}.${format}`;
84
+ await FileSystem.writeAsStringAsync(fileUri, base64Data, { encoding: FileSystem.EncodingType.Base64 });
85
+ return { uri: fileUri, format };
86
+ }
87
+ // http(s) URL -> download to cache
88
+ if (isHttpUrl(input)) {
89
+ const format = input.toLowerCase().includes('.png') ? 'png' : 'jpg';
90
+ const fileUri = `${cacheDir}coreviz_${Date.now()}.${format}`;
91
+ const res = await FileSystem.downloadAsync(input, fileUri);
92
+ return { uri: res.uri, format };
93
+ }
94
+ // assume it's already a local file URI (file://...) or a content URI
95
+ return { uri: input, format: 'jpg' };
96
+ }
97
+ async function resize(input, maxWidth = 1920, maxHeight = 1080) {
98
+ if (typeof input !== 'string') {
99
+ throw new Error('React Native resizing only supports string inputs (file URI, data URL, or http(s) URL).');
100
+ }
101
+ // Lazy import to keep this module Expo-safe and avoid forcing deps in web/node builds.
102
+ let ImageManipulator;
103
+ try {
104
+ ImageManipulator = await Promise.resolve().then(() => __importStar(require('expo-image-manipulator')));
105
+ }
106
+ catch (e) {
107
+ throw new Error("Missing optional dependency 'expo-image-manipulator'. Install it in your Expo app: `npx expo install expo-image-manipulator`");
108
+ }
109
+ const { uri, format } = await ensureLocalFileUri(input);
110
+ const { width, height } = await getImageSize(uri);
111
+ const target = fitInside(width, height, maxWidth, maxHeight);
112
+ // If already within constraints, still normalize to a data URL (so downstream API calls always have base64 available).
113
+ const actions = target.width === width && target.height === height ? [] : [{ resize: { width: target.width, height: target.height } }];
114
+ const saveFormat = format === 'png' ? ImageManipulator.SaveFormat.PNG : ImageManipulator.SaveFormat.JPEG;
115
+ const result = await ImageManipulator.manipulateAsync(uri, actions, {
116
+ base64: true,
117
+ compress: saveFormat === ImageManipulator.SaveFormat.JPEG ? 0.92 : 1,
118
+ format: saveFormat,
119
+ });
120
+ if (!result.base64) {
121
+ throw new Error('Failed to produce base64 output during resize.');
122
+ }
123
+ const mime = saveFormat === ImageManipulator.SaveFormat.PNG ? 'image/png' : 'image/jpeg';
124
+ return `data:${mime};base64,${result.base64}`;
125
+ }
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@coreviz/sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.17",
4
4
  "description": "CoreViz SDK",
5
5
  "main": "dist/index.js",
6
+ "react-native": "dist/index.native.js",
6
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "react-native": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.native.js"
13
+ },
14
+ "default": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
7
20
  "files": [
8
21
  "dist",
9
22
  "README.md"
@@ -31,8 +44,21 @@
31
44
  "@types/node": "^24.10.1",
32
45
  "typescript": "^5.9.3"
33
46
  },
34
- "dependencies": {
47
+ "dependencies": {},
48
+ "optionalDependencies": {
35
49
  "sharp": "^0.34.5",
36
50
  "@huggingface/transformers": "^3.8.0"
51
+ },
52
+ "peerDependencies": {
53
+ "expo-file-system": "*",
54
+ "expo-image-manipulator": "*"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "expo-file-system": {
58
+ "optional": true
59
+ },
60
+ "expo-image-manipulator": {
61
+ "optional": true
62
+ }
37
63
  }
38
64
  }