cencori 1.2.1 → 1.3.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/dist/index.d.mts +206 -2
- package/dist/index.d.ts +206 -2
- package/dist/index.js +210 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +207 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +52 -0
- package/dist/react/index.d.ts +52 -0
- package/dist/react/index.js +395 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +367 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/vision/index.d.mts +99 -0
- package/dist/vision/index.d.ts +99 -0
- package/dist/vision/index.js +93 -0
- package/dist/vision/index.js.map +1 -0
- package/dist/vision/index.mjs +68 -0
- package/dist/vision/index.mjs.map +1 -0
- package/package.json +20 -2
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// src/react/vision/vision-format-banner.tsx
|
|
2
|
+
import { ImageIcon, Info } from "lucide-react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var UNIVERSAL_FORMATS = ["JPEG", "PNG", "WEBP", "GIF"];
|
|
5
|
+
var PROVIDER_LIMITS = {
|
|
6
|
+
openai: { formats: ["JPEG", "PNG", "WEBP", "GIF"], maxMB: 20, notes: "Non-animated GIFs only." },
|
|
7
|
+
anthropic: { formats: ["JPEG", "PNG", "WEBP", "GIF"], maxMB: 5, notes: "Max 8000\xD78000 dimensions." },
|
|
8
|
+
google: { formats: ["JPEG", "PNG", "WEBP", "GIF", "HEIC", "HEIF"], maxMB: 20, notes: "HEIC/HEIF supported." }
|
|
9
|
+
};
|
|
10
|
+
function joinClasses(...parts) {
|
|
11
|
+
return parts.filter(Boolean).join(" ");
|
|
12
|
+
}
|
|
13
|
+
function VisionFormatBanner({
|
|
14
|
+
provider,
|
|
15
|
+
variant = "info",
|
|
16
|
+
className,
|
|
17
|
+
title
|
|
18
|
+
}) {
|
|
19
|
+
const info = provider ? PROVIDER_LIMITS[provider] : { formats: UNIVERSAL_FORMATS, maxMB: 5, notes: "These formats work across every supported provider." };
|
|
20
|
+
const heading = title ?? (provider ? `Supported for ${providerLabel(provider)}` : "Supported image formats");
|
|
21
|
+
const base = variant === "info" ? "bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-950/40 dark:border-blue-900 dark:text-blue-100" : "bg-neutral-50 border-neutral-200 text-neutral-800 dark:bg-neutral-900 dark:border-neutral-800 dark:text-neutral-100";
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
role: "note",
|
|
26
|
+
className: joinClasses(
|
|
27
|
+
"flex gap-3 rounded-lg border p-4 text-sm",
|
|
28
|
+
base,
|
|
29
|
+
className
|
|
30
|
+
),
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 pt-0.5", children: /* @__PURE__ */ jsx(Info, { className: "h-5 w-5 opacity-70", "aria-hidden": true }) }),
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: heading }),
|
|
35
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: info.formats.map((f) => /* @__PURE__ */ jsxs(
|
|
36
|
+
"span",
|
|
37
|
+
{
|
|
38
|
+
className: "inline-flex items-center gap-1 rounded-md border border-current/20 bg-white/50 px-2 py-0.5 text-xs font-medium dark:bg-black/20",
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ jsx(ImageIcon, { className: "h-3 w-3 opacity-70", "aria-hidden": true }),
|
|
41
|
+
f
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
f
|
|
45
|
+
)) }),
|
|
46
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs opacity-80", children: [
|
|
47
|
+
"Up to ",
|
|
48
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
49
|
+
info.maxMB,
|
|
50
|
+
"MB"
|
|
51
|
+
] }),
|
|
52
|
+
" per image. ",
|
|
53
|
+
info.notes
|
|
54
|
+
] })
|
|
55
|
+
] })
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function providerLabel(p) {
|
|
61
|
+
if (p === "openai") return "OpenAI";
|
|
62
|
+
if (p === "anthropic") return "Anthropic";
|
|
63
|
+
return "Google";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/react/vision/vision-uploader.tsx
|
|
67
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
68
|
+
import { AlertCircle, Loader2, Upload, X } from "lucide-react";
|
|
69
|
+
|
|
70
|
+
// src/react/vision/downscale.ts
|
|
71
|
+
var DOWNSCALABLE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
72
|
+
function canDownscale(mimeType) {
|
|
73
|
+
return DOWNSCALABLE_TYPES.has(mimeType.toLowerCase());
|
|
74
|
+
}
|
|
75
|
+
async function downscaleImage(file, maxBytes, options = {}) {
|
|
76
|
+
const originalBytes = file.size;
|
|
77
|
+
if (originalBytes <= maxBytes) {
|
|
78
|
+
return { file, originalBytes, finalBytes: originalBytes, steps: 0 };
|
|
79
|
+
}
|
|
80
|
+
if (!canDownscale(file.type)) {
|
|
81
|
+
throw new Error(`Cannot auto-compress ${file.type}. Please upload a smaller image.`);
|
|
82
|
+
}
|
|
83
|
+
const bitmap = await createImageBitmap(file);
|
|
84
|
+
let { width, height } = bitmap;
|
|
85
|
+
const minDim = options.minDimension ?? 320;
|
|
86
|
+
let quality = 0.9;
|
|
87
|
+
const qFloor = options.qualityFloor ?? 0.5;
|
|
88
|
+
const outputType = "image/jpeg";
|
|
89
|
+
let steps = 0;
|
|
90
|
+
let bytes = originalBytes;
|
|
91
|
+
let outFile = file;
|
|
92
|
+
while (bytes > maxBytes && steps < 8) {
|
|
93
|
+
steps += 1;
|
|
94
|
+
if (Math.min(width, height) > minDim && steps <= 4) {
|
|
95
|
+
width = Math.floor(width * 0.75);
|
|
96
|
+
height = Math.floor(height * 0.75);
|
|
97
|
+
} else if (quality > qFloor) {
|
|
98
|
+
quality = Math.max(qFloor, quality - 0.15);
|
|
99
|
+
} else {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(width, height) : Object.assign(document.createElement("canvas"), { width, height });
|
|
103
|
+
const ctx = canvas.getContext("2d");
|
|
104
|
+
if (!ctx) throw new Error("Canvas 2D context unavailable");
|
|
105
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
106
|
+
const blob = canvas instanceof OffscreenCanvas ? await canvas.convertToBlob({ type: outputType, quality }) : await new Promise((resolve, reject) => {
|
|
107
|
+
canvas.toBlob(
|
|
108
|
+
(b) => b ? resolve(b) : reject(new Error("toBlob returned null")),
|
|
109
|
+
outputType,
|
|
110
|
+
quality
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
bytes = blob.size;
|
|
114
|
+
outFile = new File([blob], renameToJpeg(file.name), { type: outputType, lastModified: Date.now() });
|
|
115
|
+
}
|
|
116
|
+
bitmap.close?.();
|
|
117
|
+
if (bytes > maxBytes) {
|
|
118
|
+
throw new Error(`Could not compress image below the ${(maxBytes / (1024 * 1024)).toFixed(1)}MB limit.`);
|
|
119
|
+
}
|
|
120
|
+
return { file: outFile, originalBytes, finalBytes: bytes, steps };
|
|
121
|
+
}
|
|
122
|
+
function renameToJpeg(name) {
|
|
123
|
+
const dot = name.lastIndexOf(".");
|
|
124
|
+
if (dot === -1) return `${name}.jpg`;
|
|
125
|
+
return `${name.slice(0, dot)}.jpg`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/react/vision/vision-uploader.tsx
|
|
129
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
130
|
+
var UNIVERSAL_FORMATS2 = ["image/jpeg", "image/png", "image/webp", "image/gif"];
|
|
131
|
+
var PROVIDER_RULES = {
|
|
132
|
+
openai: { formats: UNIVERSAL_FORMATS2, maxBytes: 20 * 1024 * 1024 },
|
|
133
|
+
anthropic: { formats: UNIVERSAL_FORMATS2, maxBytes: 5 * 1024 * 1024 },
|
|
134
|
+
google: { formats: [...UNIVERSAL_FORMATS2, "image/heic", "image/heif"], maxBytes: 20 * 1024 * 1024 }
|
|
135
|
+
};
|
|
136
|
+
function rulesFor(provider) {
|
|
137
|
+
if (provider) return PROVIDER_RULES[provider];
|
|
138
|
+
return { formats: UNIVERSAL_FORMATS2, maxBytes: 5 * 1024 * 1024 };
|
|
139
|
+
}
|
|
140
|
+
function formatBytes(bytes) {
|
|
141
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
142
|
+
return `${(bytes / 1024).toFixed(0)}KB`;
|
|
143
|
+
}
|
|
144
|
+
function friendlyFormats(mimes) {
|
|
145
|
+
return mimes.map((m) => m.replace("image/", "").toUpperCase()).join(", ");
|
|
146
|
+
}
|
|
147
|
+
function joinClasses2(...parts) {
|
|
148
|
+
return parts.filter(Boolean).join(" ");
|
|
149
|
+
}
|
|
150
|
+
function VisionUploader({
|
|
151
|
+
endpoint = "/api/ai/vision",
|
|
152
|
+
apiKey,
|
|
153
|
+
task = "analyze",
|
|
154
|
+
provider,
|
|
155
|
+
model,
|
|
156
|
+
prompt,
|
|
157
|
+
onResult,
|
|
158
|
+
onError,
|
|
159
|
+
hideBanner = false,
|
|
160
|
+
autoCompress = true,
|
|
161
|
+
headers,
|
|
162
|
+
className
|
|
163
|
+
}) {
|
|
164
|
+
const [file, setFile] = useState(null);
|
|
165
|
+
const [preview, setPreview] = useState(null);
|
|
166
|
+
const [error, setError] = useState(null);
|
|
167
|
+
const [loading, setLoading] = useState(false);
|
|
168
|
+
const [result, setResult] = useState(null);
|
|
169
|
+
const [dragging, setDragging] = useState(false);
|
|
170
|
+
const [notice, setNotice] = useState(null);
|
|
171
|
+
const inputRef = useRef(null);
|
|
172
|
+
const rules = useMemo(() => rulesFor(provider), [provider]);
|
|
173
|
+
const targetUrl = task === "analyze" ? endpoint : `${endpoint.replace(/\/$/, "")}/${task}`;
|
|
174
|
+
const acceptFile = useCallback(async (f) => {
|
|
175
|
+
setResult(null);
|
|
176
|
+
setNotice(null);
|
|
177
|
+
let effective = f;
|
|
178
|
+
const mime = (f.type || "").toLowerCase();
|
|
179
|
+
if (!rules.formats.includes(mime)) {
|
|
180
|
+
const err = {
|
|
181
|
+
code: "unsupported_format",
|
|
182
|
+
message: `"${f.name}" is ${mime || "an unknown type"}. Please upload one of: ${friendlyFormats(rules.formats)}.`
|
|
183
|
+
};
|
|
184
|
+
setError(err);
|
|
185
|
+
setFile(null);
|
|
186
|
+
setPreview(null);
|
|
187
|
+
onError?.(err);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (f.size > rules.maxBytes) {
|
|
191
|
+
if (autoCompress && canDownscale(mime)) {
|
|
192
|
+
try {
|
|
193
|
+
const result2 = await downscaleImage(f, rules.maxBytes);
|
|
194
|
+
effective = result2.file;
|
|
195
|
+
setNotice(
|
|
196
|
+
`Compressed from ${formatBytes(result2.originalBytes)} to ${formatBytes(result2.finalBytes)} to fit the ${provider ?? "universal"} limit.`
|
|
197
|
+
);
|
|
198
|
+
} catch (compressErr) {
|
|
199
|
+
const err = {
|
|
200
|
+
code: "image_too_large",
|
|
201
|
+
message: compressErr instanceof Error ? compressErr.message : `"${f.name}" is ${formatBytes(f.size)} \u2014 the limit is ${formatBytes(rules.maxBytes)}${provider ? ` for ${provider}` : ""}.`
|
|
202
|
+
};
|
|
203
|
+
setError(err);
|
|
204
|
+
setFile(null);
|
|
205
|
+
setPreview(null);
|
|
206
|
+
onError?.(err);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
const err = {
|
|
211
|
+
code: "image_too_large",
|
|
212
|
+
message: `"${f.name}" is ${formatBytes(f.size)} \u2014 the limit is ${formatBytes(rules.maxBytes)}${provider ? ` for ${provider}` : ""}.`
|
|
213
|
+
};
|
|
214
|
+
setError(err);
|
|
215
|
+
setFile(null);
|
|
216
|
+
setPreview(null);
|
|
217
|
+
onError?.(err);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
setError(null);
|
|
222
|
+
setFile(effective);
|
|
223
|
+
setPreview(URL.createObjectURL(effective));
|
|
224
|
+
}, [rules, provider, autoCompress, onError]);
|
|
225
|
+
const handleDrop = useCallback((e) => {
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
setDragging(false);
|
|
228
|
+
const f = e.dataTransfer.files?.[0];
|
|
229
|
+
if (f) acceptFile(f);
|
|
230
|
+
}, [acceptFile]);
|
|
231
|
+
const handleSubmit = useCallback(async () => {
|
|
232
|
+
if (!file) return;
|
|
233
|
+
setLoading(true);
|
|
234
|
+
setError(null);
|
|
235
|
+
setResult(null);
|
|
236
|
+
try {
|
|
237
|
+
const form = new FormData();
|
|
238
|
+
form.append("file", file);
|
|
239
|
+
if (prompt && task === "analyze") form.append("prompt", prompt);
|
|
240
|
+
if (model) form.append("model", model);
|
|
241
|
+
const requestHeaders = { ...headers ?? {} };
|
|
242
|
+
if (apiKey) requestHeaders.Authorization = `Bearer ${apiKey}`;
|
|
243
|
+
const res = await fetch(targetUrl, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: Object.keys(requestHeaders).length ? requestHeaders : void 0,
|
|
246
|
+
body: form
|
|
247
|
+
});
|
|
248
|
+
const data = await res.json();
|
|
249
|
+
if (!res.ok) {
|
|
250
|
+
const err = {
|
|
251
|
+
code: data && typeof data === "object" && "error" in data ? String(data.error) : "request_failed",
|
|
252
|
+
message: data && typeof data === "object" && "message" in data ? String(data.message) : `Request failed with status ${res.status}`
|
|
253
|
+
};
|
|
254
|
+
setError(err);
|
|
255
|
+
onError?.(err);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
setResult(data);
|
|
259
|
+
onResult?.(data);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
const e = { code: "network_error", message: err instanceof Error ? err.message : "Network error" };
|
|
262
|
+
setError(e);
|
|
263
|
+
onError?.(e);
|
|
264
|
+
} finally {
|
|
265
|
+
setLoading(false);
|
|
266
|
+
}
|
|
267
|
+
}, [file, prompt, model, task, targetUrl, apiKey, headers, onResult, onError]);
|
|
268
|
+
const clear = useCallback(() => {
|
|
269
|
+
setFile(null);
|
|
270
|
+
setPreview(null);
|
|
271
|
+
setResult(null);
|
|
272
|
+
setError(null);
|
|
273
|
+
setNotice(null);
|
|
274
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
275
|
+
}, []);
|
|
276
|
+
return /* @__PURE__ */ jsxs2("div", { className: joinClasses2("flex flex-col gap-3", className), children: [
|
|
277
|
+
!hideBanner && /* @__PURE__ */ jsx2(VisionFormatBanner, { provider }),
|
|
278
|
+
/* @__PURE__ */ jsxs2(
|
|
279
|
+
"div",
|
|
280
|
+
{
|
|
281
|
+
onDragOver: (e) => {
|
|
282
|
+
e.preventDefault();
|
|
283
|
+
setDragging(true);
|
|
284
|
+
},
|
|
285
|
+
onDragLeave: () => setDragging(false),
|
|
286
|
+
onDrop: handleDrop,
|
|
287
|
+
onClick: () => inputRef.current?.click(),
|
|
288
|
+
role: "button",
|
|
289
|
+
tabIndex: 0,
|
|
290
|
+
"aria-label": "Upload image",
|
|
291
|
+
className: joinClasses2(
|
|
292
|
+
"flex cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed p-8 text-center transition",
|
|
293
|
+
dragging ? "border-blue-500 bg-blue-50 dark:bg-blue-950/30" : "border-neutral-300 hover:border-neutral-400 dark:border-neutral-700 dark:hover:border-neutral-600"
|
|
294
|
+
),
|
|
295
|
+
children: [
|
|
296
|
+
preview ? /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
297
|
+
/* @__PURE__ */ jsx2("img", { src: preview, alt: "Preview", className: "max-h-64 rounded-lg object-contain" }),
|
|
298
|
+
/* @__PURE__ */ jsx2(
|
|
299
|
+
"button",
|
|
300
|
+
{
|
|
301
|
+
type: "button",
|
|
302
|
+
onClick: (e) => {
|
|
303
|
+
e.stopPropagation();
|
|
304
|
+
clear();
|
|
305
|
+
},
|
|
306
|
+
className: "absolute -right-2 -top-2 rounded-full bg-white p-1 shadow ring-1 ring-neutral-200 hover:bg-neutral-100 dark:bg-neutral-900 dark:ring-neutral-700",
|
|
307
|
+
"aria-label": "Remove image",
|
|
308
|
+
children: /* @__PURE__ */ jsx2(X, { className: "h-4 w-4" })
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
312
|
+
/* @__PURE__ */ jsx2(Upload, { className: "h-8 w-8 text-neutral-400", "aria-hidden": true }),
|
|
313
|
+
/* @__PURE__ */ jsx2("p", { className: "text-sm font-medium", children: "Drop an image or click to upload" }),
|
|
314
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-xs text-neutral-500", children: [
|
|
315
|
+
friendlyFormats(rules.formats),
|
|
316
|
+
" \xB7 up to ",
|
|
317
|
+
formatBytes(rules.maxBytes)
|
|
318
|
+
] })
|
|
319
|
+
] }),
|
|
320
|
+
/* @__PURE__ */ jsx2(
|
|
321
|
+
"input",
|
|
322
|
+
{
|
|
323
|
+
ref: inputRef,
|
|
324
|
+
type: "file",
|
|
325
|
+
accept: rules.formats.join(","),
|
|
326
|
+
className: "hidden",
|
|
327
|
+
onChange: (e) => {
|
|
328
|
+
const f = e.target.files?.[0];
|
|
329
|
+
if (f) acceptFile(f);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
),
|
|
336
|
+
error && /* @__PURE__ */ jsxs2("div", { role: "alert", className: "flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-900 dark:border-red-900 dark:bg-red-950/40 dark:text-red-100", children: [
|
|
337
|
+
/* @__PURE__ */ jsx2(AlertCircle, { className: "mt-0.5 h-4 w-4 flex-shrink-0", "aria-hidden": true }),
|
|
338
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
339
|
+
/* @__PURE__ */ jsx2("p", { className: "font-medium", children: "Can't process this image" }),
|
|
340
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xs opacity-90", children: error.message })
|
|
341
|
+
] })
|
|
342
|
+
] }),
|
|
343
|
+
notice && !error && /* @__PURE__ */ jsx2("p", { className: "text-xs text-neutral-500 dark:text-neutral-400", children: notice }),
|
|
344
|
+
file && !loading && !result && /* @__PURE__ */ jsx2(
|
|
345
|
+
"button",
|
|
346
|
+
{
|
|
347
|
+
type: "button",
|
|
348
|
+
onClick: handleSubmit,
|
|
349
|
+
className: "inline-flex items-center justify-center rounded-lg bg-neutral-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
|
|
350
|
+
children: "Analyze image"
|
|
351
|
+
}
|
|
352
|
+
),
|
|
353
|
+
loading && /* @__PURE__ */ jsxs2("div", { className: "inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400", children: [
|
|
354
|
+
/* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }),
|
|
355
|
+
"Analyzing\u2026"
|
|
356
|
+
] }),
|
|
357
|
+
result !== null && /* @__PURE__ */ jsxs2("div", { className: "rounded-lg border border-neutral-200 bg-neutral-50 p-3 text-sm dark:border-neutral-800 dark:bg-neutral-900", children: [
|
|
358
|
+
/* @__PURE__ */ jsx2("p", { className: "mb-1 text-xs font-medium uppercase text-neutral-500", children: "Result" }),
|
|
359
|
+
/* @__PURE__ */ jsx2("pre", { className: "overflow-auto whitespace-pre-wrap break-words text-xs", children: typeof result === "string" ? result : JSON.stringify(result, null, 2) })
|
|
360
|
+
] })
|
|
361
|
+
] });
|
|
362
|
+
}
|
|
363
|
+
export {
|
|
364
|
+
VisionFormatBanner,
|
|
365
|
+
VisionUploader
|
|
366
|
+
};
|
|
367
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/vision/vision-format-banner.tsx","../../src/react/vision/vision-uploader.tsx","../../src/react/vision/downscale.ts"],"sourcesContent":["/**\n * VisionFormatBanner\n *\n * Drop-in banner that documents the supported image formats and size limits\n * for the Cencori Vision API. Zero external state — safe to embed anywhere,\n * including inside a customer's own product.\n *\n * Deps: react, lucide-react, tailwindcss.\n */\n\n'use client';\n\nimport { ImageIcon, Info } from 'lucide-react';\nimport type { ReactNode } from 'react';\n\nexport type VisionProvider = 'openai' | 'anthropic' | 'google';\n\nexport interface VisionFormatBannerProps {\n /** Restrict displayed limits to a specific provider. Omit to show universal (all-provider) limits. */\n provider?: VisionProvider;\n /** Tone. */\n variant?: 'info' | 'muted';\n /** Extra classes. */\n className?: string;\n /** Optional title override. */\n title?: ReactNode;\n}\n\nconst UNIVERSAL_FORMATS = ['JPEG', 'PNG', 'WEBP', 'GIF'];\n\nconst PROVIDER_LIMITS: Record<VisionProvider, { formats: string[]; maxMB: number; notes: string }> = {\n openai: { formats: ['JPEG', 'PNG', 'WEBP', 'GIF'], maxMB: 20, notes: 'Non-animated GIFs only.' },\n anthropic: { formats: ['JPEG', 'PNG', 'WEBP', 'GIF'], maxMB: 5, notes: 'Max 8000×8000 dimensions.' },\n google: { formats: ['JPEG', 'PNG', 'WEBP', 'GIF', 'HEIC', 'HEIF'], maxMB: 20, notes: 'HEIC/HEIF supported.' },\n};\n\nfunction joinClasses(...parts: Array<string | undefined | false>): string {\n return parts.filter(Boolean).join(' ');\n}\n\nexport function VisionFormatBanner({\n provider,\n variant = 'info',\n className,\n title,\n}: VisionFormatBannerProps) {\n const info = provider ? PROVIDER_LIMITS[provider] : { formats: UNIVERSAL_FORMATS, maxMB: 5, notes: 'These formats work across every supported provider.' };\n const heading = title ?? (provider ? `Supported for ${providerLabel(provider)}` : 'Supported image formats');\n\n const base = variant === 'info'\n ? 'bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-950/40 dark:border-blue-900 dark:text-blue-100'\n : 'bg-neutral-50 border-neutral-200 text-neutral-800 dark:bg-neutral-900 dark:border-neutral-800 dark:text-neutral-100';\n\n return (\n <div\n role=\"note\"\n className={joinClasses(\n 'flex gap-3 rounded-lg border p-4 text-sm',\n base,\n className\n )}\n >\n <div className=\"flex-shrink-0 pt-0.5\">\n <Info className=\"h-5 w-5 opacity-70\" aria-hidden />\n </div>\n <div className=\"flex flex-col gap-2\">\n <p className=\"font-medium\">{heading}</p>\n <div className=\"flex flex-wrap gap-1.5\">\n {info.formats.map(f => (\n <span\n key={f}\n className=\"inline-flex items-center gap-1 rounded-md border border-current/20 bg-white/50 px-2 py-0.5 text-xs font-medium dark:bg-black/20\"\n >\n <ImageIcon className=\"h-3 w-3 opacity-70\" aria-hidden />\n {f}\n </span>\n ))}\n </div>\n <p className=\"text-xs opacity-80\">\n Up to <strong>{info.maxMB}MB</strong> per image. {info.notes}\n </p>\n </div>\n </div>\n );\n}\n\nfunction providerLabel(p: VisionProvider): string {\n if (p === 'openai') return 'OpenAI';\n if (p === 'anthropic') return 'Anthropic';\n return 'Google';\n}\n","/**\n * VisionUploader\n *\n * Drop-in image uploader for the Cencori Vision API. Handles drag-drop,\n * client-side format + size validation, and calls the endpoint via\n * multipart/form-data. Renders clear error messages and a supported-formats\n * banner. Zero external state — safe to embed in a customer's own product.\n *\n * Usage:\n * <VisionUploader\n * endpoint=\"https://api.cencori.com/api/ai/vision\"\n * apiKey={process.env.NEXT_PUBLIC_CENCORI_KEY}\n * task=\"describe\"\n * onResult={(result) => console.log(result)}\n * />\n *\n * Deps: react, lucide-react, tailwindcss.\n */\n\n'use client';\n\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport { AlertCircle, Loader2, Upload, X } from 'lucide-react';\nimport { VisionFormatBanner, type VisionProvider } from './vision-format-banner';\nimport { canDownscale, downscaleImage } from './downscale';\n\n// ── Public types ────────────────────────────────────────────────\n\nexport type VisionTask = 'analyze' | 'describe' | 'ocr' | 'classify';\n\nexport interface VisionUploaderProps {\n /** Base Vision endpoint. Defaults to same-origin `/api/ai/vision`. */\n endpoint?: string;\n /** Cencori API key. Sent as `Authorization: Bearer`. */\n apiKey?: string;\n /** Which task to run: analyze (default), describe, ocr, or classify. */\n task?: VisionTask;\n /** Provider to target — informs client-side validation. Defaults to universal (all providers). */\n provider?: VisionProvider;\n /** Optional model override. */\n model?: string;\n /** Custom prompt (only meaningful for task=\"analyze\"). */\n prompt?: string;\n /** Called on success. */\n onResult?: (result: unknown) => void;\n /** Called on error (before the built-in banner renders it). */\n onError?: (error: { code: string; message: string }) => void;\n /** Hide the supported-formats banner. Default: shown. */\n hideBanner?: boolean;\n /**\n * Auto-compress images that exceed the provider's size limit. JPEG/PNG/WEBP only.\n * Default: true.\n */\n autoCompress?: boolean;\n /** Additional request headers to forward on upload (in addition to Authorization). */\n headers?: Record<string, string>;\n /** Extra classes. */\n className?: string;\n}\n\n// ── Client-side validation rules (mirror the server) ────────────\n\nconst UNIVERSAL_FORMATS = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];\n\nconst PROVIDER_RULES: Record<VisionProvider, { formats: string[]; maxBytes: number }> = {\n openai: { formats: UNIVERSAL_FORMATS, maxBytes: 20 * 1024 * 1024 },\n anthropic: { formats: UNIVERSAL_FORMATS, maxBytes: 5 * 1024 * 1024 },\n google: { formats: [...UNIVERSAL_FORMATS, 'image/heic', 'image/heif'], maxBytes: 20 * 1024 * 1024 },\n};\n\nfunction rulesFor(provider?: VisionProvider) {\n if (provider) return PROVIDER_RULES[provider];\n return { formats: UNIVERSAL_FORMATS, maxBytes: 5 * 1024 * 1024 };\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n return `${(bytes / 1024).toFixed(0)}KB`;\n}\n\nfunction friendlyFormats(mimes: string[]): string {\n return mimes.map(m => m.replace('image/', '').toUpperCase()).join(', ');\n}\n\nfunction joinClasses(...parts: Array<string | undefined | false>): string {\n return parts.filter(Boolean).join(' ');\n}\n\n// ── Component ───────────────────────────────────────────────────\n\nexport function VisionUploader({\n endpoint = '/api/ai/vision',\n apiKey,\n task = 'analyze',\n provider,\n model,\n prompt,\n onResult,\n onError,\n hideBanner = false,\n autoCompress = true,\n headers,\n className,\n}: VisionUploaderProps) {\n const [file, setFile] = useState<File | null>(null);\n const [preview, setPreview] = useState<string | null>(null);\n const [error, setError] = useState<{ code: string; message: string } | null>(null);\n const [loading, setLoading] = useState(false);\n const [result, setResult] = useState<unknown>(null);\n const [dragging, setDragging] = useState(false);\n const [notice, setNotice] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const rules = useMemo(() => rulesFor(provider), [provider]);\n const targetUrl = task === 'analyze' ? endpoint : `${endpoint.replace(/\\/$/, '')}/${task}`;\n\n const acceptFile = useCallback(async (f: File) => {\n setResult(null);\n setNotice(null);\n let effective = f;\n\n // Wrong format: fail fast, no attempt at compression\n const mime = (f.type || '').toLowerCase();\n if (!rules.formats.includes(mime)) {\n const err = {\n code: 'unsupported_format',\n message: `\"${f.name}\" is ${mime || 'an unknown type'}. Please upload one of: ${friendlyFormats(rules.formats)}.`,\n };\n setError(err);\n setFile(null);\n setPreview(null);\n onError?.(err);\n return;\n }\n\n // Too big: try auto-compression if allowed and format is downscalable\n if (f.size > rules.maxBytes) {\n if (autoCompress && canDownscale(mime)) {\n try {\n const result = await downscaleImage(f, rules.maxBytes);\n effective = result.file;\n setNotice(\n `Compressed from ${formatBytes(result.originalBytes)} to ${formatBytes(result.finalBytes)} to fit the ${provider ?? 'universal'} limit.`\n );\n } catch (compressErr) {\n const err = {\n code: 'image_too_large',\n message: compressErr instanceof Error\n ? compressErr.message\n : `\"${f.name}\" is ${formatBytes(f.size)} — the limit is ${formatBytes(rules.maxBytes)}${provider ? ` for ${provider}` : ''}.`,\n };\n setError(err);\n setFile(null);\n setPreview(null);\n onError?.(err);\n return;\n }\n } else {\n const err = {\n code: 'image_too_large',\n message: `\"${f.name}\" is ${formatBytes(f.size)} — the limit is ${formatBytes(rules.maxBytes)}${provider ? ` for ${provider}` : ''}.`,\n };\n setError(err);\n setFile(null);\n setPreview(null);\n onError?.(err);\n return;\n }\n }\n\n setError(null);\n setFile(effective);\n setPreview(URL.createObjectURL(effective));\n }, [rules, provider, autoCompress, onError]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n setDragging(false);\n const f = e.dataTransfer.files?.[0];\n if (f) acceptFile(f);\n }, [acceptFile]);\n\n const handleSubmit = useCallback(async () => {\n if (!file) return;\n setLoading(true);\n setError(null);\n setResult(null);\n try {\n const form = new FormData();\n form.append('file', file);\n if (prompt && task === 'analyze') form.append('prompt', prompt);\n if (model) form.append('model', model);\n\n const requestHeaders: Record<string, string> = { ...(headers ?? {}) };\n if (apiKey) requestHeaders.Authorization = `Bearer ${apiKey}`;\n\n const res = await fetch(targetUrl, {\n method: 'POST',\n headers: Object.keys(requestHeaders).length ? requestHeaders : undefined,\n body: form,\n });\n const data = await res.json();\n if (!res.ok) {\n const err = {\n code: (data && typeof data === 'object' && 'error' in data ? String(data.error) : 'request_failed'),\n message: (data && typeof data === 'object' && 'message' in data ? String(data.message) : `Request failed with status ${res.status}`),\n };\n setError(err);\n onError?.(err);\n return;\n }\n setResult(data);\n onResult?.(data);\n } catch (err) {\n const e = { code: 'network_error', message: err instanceof Error ? err.message : 'Network error' };\n setError(e);\n onError?.(e);\n } finally {\n setLoading(false);\n }\n }, [file, prompt, model, task, targetUrl, apiKey, headers, onResult, onError]);\n\n const clear = useCallback(() => {\n setFile(null);\n setPreview(null);\n setResult(null);\n setError(null);\n setNotice(null);\n if (inputRef.current) inputRef.current.value = '';\n }, []);\n\n return (\n <div className={joinClasses('flex flex-col gap-3', className)}>\n {!hideBanner && <VisionFormatBanner provider={provider} />}\n\n <div\n onDragOver={e => { e.preventDefault(); setDragging(true); }}\n onDragLeave={() => setDragging(false)}\n onDrop={handleDrop}\n onClick={() => inputRef.current?.click()}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Upload image\"\n className={joinClasses(\n 'flex cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed p-8 text-center transition',\n dragging\n ? 'border-blue-500 bg-blue-50 dark:bg-blue-950/30'\n : 'border-neutral-300 hover:border-neutral-400 dark:border-neutral-700 dark:hover:border-neutral-600',\n )}\n >\n {preview ? (\n <div className=\"relative\">\n <img src={preview} alt=\"Preview\" className=\"max-h-64 rounded-lg object-contain\" />\n <button\n type=\"button\"\n onClick={e => { e.stopPropagation(); clear(); }}\n className=\"absolute -right-2 -top-2 rounded-full bg-white p-1 shadow ring-1 ring-neutral-200 hover:bg-neutral-100 dark:bg-neutral-900 dark:ring-neutral-700\"\n aria-label=\"Remove image\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ) : (\n <>\n <Upload className=\"h-8 w-8 text-neutral-400\" aria-hidden />\n <p className=\"text-sm font-medium\">Drop an image or click to upload</p>\n <p className=\"text-xs text-neutral-500\">{friendlyFormats(rules.formats)} · up to {formatBytes(rules.maxBytes)}</p>\n </>\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept={rules.formats.join(',')}\n className=\"hidden\"\n onChange={e => {\n const f = e.target.files?.[0];\n if (f) acceptFile(f);\n }}\n />\n </div>\n\n {error && (\n <div role=\"alert\" className=\"flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-900 dark:border-red-900 dark:bg-red-950/40 dark:text-red-100\">\n <AlertCircle className=\"mt-0.5 h-4 w-4 flex-shrink-0\" aria-hidden />\n <div>\n <p className=\"font-medium\">Can't process this image</p>\n <p className=\"text-xs opacity-90\">{error.message}</p>\n </div>\n </div>\n )}\n\n {notice && !error && (\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">{notice}</p>\n )}\n\n {file && !loading && !result && (\n <button\n type=\"button\"\n onClick={handleSubmit}\n className=\"inline-flex items-center justify-center rounded-lg bg-neutral-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100\"\n >\n Analyze image\n </button>\n )}\n\n {loading && (\n <div className=\"inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400\">\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden />\n Analyzing…\n </div>\n )}\n\n {result !== null && (\n <div className=\"rounded-lg border border-neutral-200 bg-neutral-50 p-3 text-sm dark:border-neutral-800 dark:bg-neutral-900\">\n <p className=\"mb-1 text-xs font-medium uppercase text-neutral-500\">Result</p>\n <pre className=\"overflow-auto whitespace-pre-wrap break-words text-xs\">\n {typeof result === 'string' ? result : JSON.stringify(result, null, 2)}\n </pre>\n </div>\n )}\n </div>\n );\n}\n","/**\n * Client-side image downscaling.\n *\n * Iteratively reduces JPEG/PNG/WEBP dimensions until the image fits under\n * `maxBytes`. Uses <canvas>, so it must run in the browser. GIF, HEIC, and\n * HEIF are not downscaled — the caller should reject those with a format error.\n */\n\nconst DOWNSCALABLE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);\n\nexport function canDownscale(mimeType: string): boolean {\n return DOWNSCALABLE_TYPES.has(mimeType.toLowerCase());\n}\n\nexport interface DownscaleResult {\n file: File;\n originalBytes: number;\n finalBytes: number;\n steps: number;\n}\n\nexport async function downscaleImage(\n file: File,\n maxBytes: number,\n options: { minDimension?: number; qualityFloor?: number } = {}\n): Promise<DownscaleResult> {\n const originalBytes = file.size;\n if (originalBytes <= maxBytes) {\n return { file, originalBytes, finalBytes: originalBytes, steps: 0 };\n }\n if (!canDownscale(file.type)) {\n throw new Error(`Cannot auto-compress ${file.type}. Please upload a smaller image.`);\n }\n\n const bitmap = await createImageBitmap(file);\n let { width, height } = bitmap;\n const minDim = options.minDimension ?? 320;\n let quality = 0.9;\n const qFloor = options.qualityFloor ?? 0.5;\n // Re-encode to JPEG unless PNG has transparency we should preserve. Simpler: always JPEG for compression.\n const outputType = 'image/jpeg';\n\n let steps = 0;\n let bytes = originalBytes;\n let outFile = file;\n\n while (bytes > maxBytes && steps < 8) {\n steps += 1;\n // Prefer reducing dimensions first (bigger win), then quality\n if (Math.min(width, height) > minDim && steps <= 4) {\n width = Math.floor(width * 0.75);\n height = Math.floor(height * 0.75);\n } else if (quality > qFloor) {\n quality = Math.max(qFloor, quality - 0.15);\n } else {\n break;\n }\n\n const canvas = typeof OffscreenCanvas !== 'undefined'\n ? new OffscreenCanvas(width, height)\n : Object.assign(document.createElement('canvas'), { width, height });\n\n const ctx = (canvas as HTMLCanvasElement | OffscreenCanvas).getContext('2d');\n if (!ctx) throw new Error('Canvas 2D context unavailable');\n (ctx as CanvasRenderingContext2D).drawImage(bitmap, 0, 0, width, height);\n\n const blob = canvas instanceof OffscreenCanvas\n ? await canvas.convertToBlob({ type: outputType, quality })\n : await new Promise<Blob>((resolve, reject) => {\n (canvas as HTMLCanvasElement).toBlob(\n (b) => b ? resolve(b) : reject(new Error('toBlob returned null')),\n outputType,\n quality,\n );\n });\n\n bytes = blob.size;\n outFile = new File([blob], renameToJpeg(file.name), { type: outputType, lastModified: Date.now() });\n }\n\n bitmap.close?.();\n\n if (bytes > maxBytes) {\n throw new Error(`Could not compress image below the ${(maxBytes / (1024 * 1024)).toFixed(1)}MB limit.`);\n }\n\n return { file: outFile, originalBytes, finalBytes: bytes, steps };\n}\n\nfunction renameToJpeg(name: string): string {\n const dot = name.lastIndexOf('.');\n if (dot === -1) return `${name}.jpg`;\n return `${name.slice(0, dot)}.jpg`;\n}\n"],"mappings":"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","names":["jsx","jsxs","UNIVERSAL_FORMATS","joinClasses","result"]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { C as CencoriConfig } from '../types-kh1whvNH.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vision API — analyze, describe, OCR, and classify images.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const result = await cencori.vision.analyze({
|
|
8
|
+
* image: { url: 'https://example.com/photo.jpg' },
|
|
9
|
+
* prompt: 'What breed of dog is this?',
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const { text } = await cencori.vision.ocr({ image: { base64, mimeType: 'image/png' } });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type VisionProvider = 'openai' | 'anthropic' | 'google';
|
|
17
|
+
type VisionTask = 'analyze' | 'describe' | 'ocr' | 'classify';
|
|
18
|
+
interface VisionImage {
|
|
19
|
+
/** https:// URL or data: URL. Exactly one of `url` or `base64` must be set. */
|
|
20
|
+
url?: string;
|
|
21
|
+
/** Raw base64 (no data: prefix). Requires `mimeType`. */
|
|
22
|
+
base64?: string;
|
|
23
|
+
/** Image mime type. Required when using `base64`. */
|
|
24
|
+
mimeType?: string;
|
|
25
|
+
}
|
|
26
|
+
interface VisionRequest {
|
|
27
|
+
image: VisionImage;
|
|
28
|
+
prompt?: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
temperature?: number;
|
|
32
|
+
responseFormat?: 'text' | 'json';
|
|
33
|
+
}
|
|
34
|
+
interface VisionUsage {
|
|
35
|
+
promptTokens: number;
|
|
36
|
+
completionTokens: number;
|
|
37
|
+
totalTokens: number;
|
|
38
|
+
}
|
|
39
|
+
interface VisionCost {
|
|
40
|
+
providerCostUsd: number;
|
|
41
|
+
cencoriChargeUsd: number;
|
|
42
|
+
markupPercentage: number;
|
|
43
|
+
}
|
|
44
|
+
interface VisionResult {
|
|
45
|
+
analysis: string;
|
|
46
|
+
model: string;
|
|
47
|
+
provider: VisionProvider;
|
|
48
|
+
usage: VisionUsage;
|
|
49
|
+
cost: VisionCost;
|
|
50
|
+
}
|
|
51
|
+
interface VisionDescribeResult {
|
|
52
|
+
description: string;
|
|
53
|
+
model: string;
|
|
54
|
+
provider: VisionProvider;
|
|
55
|
+
usage: VisionUsage;
|
|
56
|
+
cost: VisionCost;
|
|
57
|
+
}
|
|
58
|
+
interface VisionOcrResult {
|
|
59
|
+
text: string;
|
|
60
|
+
model: string;
|
|
61
|
+
provider: VisionProvider;
|
|
62
|
+
usage: VisionUsage;
|
|
63
|
+
cost: VisionCost;
|
|
64
|
+
}
|
|
65
|
+
interface VisionClassification {
|
|
66
|
+
primary_category?: string;
|
|
67
|
+
tags?: string[];
|
|
68
|
+
objects?: string[];
|
|
69
|
+
safe_for_work?: boolean;
|
|
70
|
+
confidence?: number;
|
|
71
|
+
summary?: string;
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
interface VisionClassifyResult {
|
|
75
|
+
classification: VisionClassification | string;
|
|
76
|
+
raw: string;
|
|
77
|
+
model: string;
|
|
78
|
+
provider: VisionProvider;
|
|
79
|
+
usage: VisionUsage;
|
|
80
|
+
cost: VisionCost;
|
|
81
|
+
}
|
|
82
|
+
declare class VisionNamespace {
|
|
83
|
+
private config;
|
|
84
|
+
constructor(config: Required<CencoriConfig>);
|
|
85
|
+
/**
|
|
86
|
+
* General image analysis — provide your own prompt.
|
|
87
|
+
* Default prompt: "Describe this image in detail."
|
|
88
|
+
*/
|
|
89
|
+
analyze(request: VisionRequest): Promise<VisionResult>;
|
|
90
|
+
/** Describe the image in rich detail. */
|
|
91
|
+
describe(request: VisionRequest): Promise<VisionDescribeResult>;
|
|
92
|
+
/** Extract all visible text from the image. */
|
|
93
|
+
ocr(request: VisionRequest): Promise<VisionOcrResult>;
|
|
94
|
+
/** Return structured tags, objects, and category classification. */
|
|
95
|
+
classify(request: VisionRequest): Promise<VisionClassifyResult>;
|
|
96
|
+
private post;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { type VisionClassification, type VisionClassifyResult, type VisionCost, type VisionDescribeResult, type VisionImage, VisionNamespace, type VisionOcrResult, type VisionProvider, type VisionRequest, type VisionResult, type VisionTask, type VisionUsage };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { C as CencoriConfig } from '../types-kh1whvNH.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vision API — analyze, describe, OCR, and classify images.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const result = await cencori.vision.analyze({
|
|
8
|
+
* image: { url: 'https://example.com/photo.jpg' },
|
|
9
|
+
* prompt: 'What breed of dog is this?',
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const { text } = await cencori.vision.ocr({ image: { base64, mimeType: 'image/png' } });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type VisionProvider = 'openai' | 'anthropic' | 'google';
|
|
17
|
+
type VisionTask = 'analyze' | 'describe' | 'ocr' | 'classify';
|
|
18
|
+
interface VisionImage {
|
|
19
|
+
/** https:// URL or data: URL. Exactly one of `url` or `base64` must be set. */
|
|
20
|
+
url?: string;
|
|
21
|
+
/** Raw base64 (no data: prefix). Requires `mimeType`. */
|
|
22
|
+
base64?: string;
|
|
23
|
+
/** Image mime type. Required when using `base64`. */
|
|
24
|
+
mimeType?: string;
|
|
25
|
+
}
|
|
26
|
+
interface VisionRequest {
|
|
27
|
+
image: VisionImage;
|
|
28
|
+
prompt?: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
temperature?: number;
|
|
32
|
+
responseFormat?: 'text' | 'json';
|
|
33
|
+
}
|
|
34
|
+
interface VisionUsage {
|
|
35
|
+
promptTokens: number;
|
|
36
|
+
completionTokens: number;
|
|
37
|
+
totalTokens: number;
|
|
38
|
+
}
|
|
39
|
+
interface VisionCost {
|
|
40
|
+
providerCostUsd: number;
|
|
41
|
+
cencoriChargeUsd: number;
|
|
42
|
+
markupPercentage: number;
|
|
43
|
+
}
|
|
44
|
+
interface VisionResult {
|
|
45
|
+
analysis: string;
|
|
46
|
+
model: string;
|
|
47
|
+
provider: VisionProvider;
|
|
48
|
+
usage: VisionUsage;
|
|
49
|
+
cost: VisionCost;
|
|
50
|
+
}
|
|
51
|
+
interface VisionDescribeResult {
|
|
52
|
+
description: string;
|
|
53
|
+
model: string;
|
|
54
|
+
provider: VisionProvider;
|
|
55
|
+
usage: VisionUsage;
|
|
56
|
+
cost: VisionCost;
|
|
57
|
+
}
|
|
58
|
+
interface VisionOcrResult {
|
|
59
|
+
text: string;
|
|
60
|
+
model: string;
|
|
61
|
+
provider: VisionProvider;
|
|
62
|
+
usage: VisionUsage;
|
|
63
|
+
cost: VisionCost;
|
|
64
|
+
}
|
|
65
|
+
interface VisionClassification {
|
|
66
|
+
primary_category?: string;
|
|
67
|
+
tags?: string[];
|
|
68
|
+
objects?: string[];
|
|
69
|
+
safe_for_work?: boolean;
|
|
70
|
+
confidence?: number;
|
|
71
|
+
summary?: string;
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
interface VisionClassifyResult {
|
|
75
|
+
classification: VisionClassification | string;
|
|
76
|
+
raw: string;
|
|
77
|
+
model: string;
|
|
78
|
+
provider: VisionProvider;
|
|
79
|
+
usage: VisionUsage;
|
|
80
|
+
cost: VisionCost;
|
|
81
|
+
}
|
|
82
|
+
declare class VisionNamespace {
|
|
83
|
+
private config;
|
|
84
|
+
constructor(config: Required<CencoriConfig>);
|
|
85
|
+
/**
|
|
86
|
+
* General image analysis — provide your own prompt.
|
|
87
|
+
* Default prompt: "Describe this image in detail."
|
|
88
|
+
*/
|
|
89
|
+
analyze(request: VisionRequest): Promise<VisionResult>;
|
|
90
|
+
/** Describe the image in rich detail. */
|
|
91
|
+
describe(request: VisionRequest): Promise<VisionDescribeResult>;
|
|
92
|
+
/** Extract all visible text from the image. */
|
|
93
|
+
ocr(request: VisionRequest): Promise<VisionOcrResult>;
|
|
94
|
+
/** Return structured tags, objects, and category classification. */
|
|
95
|
+
classify(request: VisionRequest): Promise<VisionClassifyResult>;
|
|
96
|
+
private post;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { type VisionClassification, type VisionClassifyResult, type VisionCost, type VisionDescribeResult, type VisionImage, VisionNamespace, type VisionOcrResult, type VisionProvider, type VisionRequest, type VisionResult, type VisionTask, type VisionUsage };
|