feat: new package generate-image (#123)

* feat: new package `generate-image`

* fix(generate-image): url path

* fix: lint errors

* docs: add generate-image

* chore(xsai): update exports

---------

Co-authored-by: Neko <neko@ayaka.moe>
This commit is contained in:
藍+85CDandNeko authored and GitHub committed 2025-03-27 20:22:13 +08:00
1 parent b2d4ba5fea
commit f51578f9b0
16 files changed
+208 -44

No files matched your search

+3
View File
@@ -3,17 +3,20 @@ ignorePaths: []
dictionaryDefinitions: []
dictionaries: []
words:
- AAAAIGZ
- agentic
- all-minilm
- arktype
- automd
- BAAI
- Bhdmlm
- bumpp
- Cerebras
- changelogithub
- codestral
- cognitivecomputations
- composio
- dall
- deepinfra
- deepseek
- elevenlabs
@@ -0,0 +1,34 @@
---
title: Image
description: Creates an image given a prompt.
---
import { PkgSizeBadge } from '@/components/pkg-size-badge'
<PkgSizeBadge pkg="@xsai/generate-image" />
```package-install
npm i @xsai/generate-image
```
## Examples
```ts twoslash
import { generateImage } from '@xsai/generate-image'
import { env } from 'node:process'
const { image } = await generateImage({
apiKey: env.OPENAI_API_KEY!,
baseURL: 'http://api.openai.com/v1/',
model: 'dall-e-3',
prompt: 'A cute baby sea otter'
})
const { images } = await generateImage({
apiKey: env.OPENAI_API_KEY!,
baseURL: 'http://api.openai.com/v1/',
n: 4, // [!code highlight]
model: 'dall-e-3',
prompt: 'A cute baby sea otter'
})
```
@@ -5,6 +5,7 @@
"text",
"object",
"speech",
"transcription"
"transcription",
"image"
]
}
+1
View File
@@ -2,6 +2,7 @@
"defaultOpen": false,
"pages": [
"[@xsai/embed](https://tsdocs.dev/search/docs/@xsai/embed)",
"[@xsai/generate-image](https://tsdocs.dev/search/docs/@xsai/generate-image)",
"[@xsai/generate-object](https://tsdocs.dev/search/docs/@xsai/generate-object)",
"[@xsai/generate-speech](https://tsdocs.dev/search/docs/@xsai/generate-speech)",
"[@xsai/generate-text](https://tsdocs.dev/search/docs/@xsai/generate-text)",
+1
View File
@@ -35,6 +35,7 @@
"@xsai-ext/providers-cloud": "workspace:^",
"@xsai-ext/providers-local": "workspace:^",
"@xsai/embed": "workspace:^",
"@xsai/generate-image": "workspace:^",
"@xsai/generate-object": "workspace:^",
"@xsai/generate-speech": "workspace:^",
"@xsai/generate-text": "workspace:^",
+26 -35
View File
@@ -1,36 +1,27 @@
// import { defineConfig } from '@importantimport/eslint-config'
import { GLOB_MARKDOWN_CODE, GLOB_TESTS } from '@antfu/eslint-config'
import { defineConfig } from '@importantimport/eslint-config'
// export default defineConfig({
// typescript: { tsconfigPath: './tsconfig.json' },
// })
import antfu, { GLOB_MARKDOWN_CODE, GLOB_TESTS } from '@antfu/eslint-config'
import { ii } from '@importantimport/eslint-config'
export default antfu({ typescript: { tsconfigPath: './tsconfig.json' } })
.append(ii({ typescript: { tsconfigPath: './tsconfig.json' } }))
.append({
ignores: [
'docs/components/ui/**/*.tsx',
],
})
.append({
rules: {
'@masknet/no-default-error': 'off',
'@masknet/no-then': 'off',
'sonarjs/todo-tag': 'warn',
},
})
.append({
files: [...GLOB_TESTS, GLOB_MARKDOWN_CODE],
rules: {
'@masknet/no-top-level': 'off',
'@masknet/unicode-specific-set': 'off',
},
})
.append({
ignores: [
'cspell.config.yaml',
'cspell.config.yml',
],
})
export default defineConfig({
typescript: { tsconfigPath: './tsconfig.json' },
}, [{
ignores: [
'docs/components/ui/**/*.tsx',
],
}, {
rules: {
'@masknet/no-default-error': 'off',
'@masknet/no-then': 'off',
'sonarjs/todo-tag': 'warn',
},
}, {
files: [...GLOB_TESTS, GLOB_MARKDOWN_CODE],
rules: {
'@masknet/no-top-level': 'off',
'@masknet/unicode-specific-set': 'off',
},
}, {
ignores: [
'cspell.config.yaml',
'cspell.config.yml',
],
}])
+1
View File
@@ -42,6 +42,7 @@
},
"dependencies": {
"@xsai/embed": "workspace:",
"@xsai/generate-image": "workspace:",
"@xsai/generate-object": "workspace:",
"@xsai/generate-speech": "workspace:",
"@xsai/generate-text": "workspace:",
+1
View File
@@ -1,4 +1,5 @@
export * from '@xsai/embed'
export * from '@xsai/generate-image'
export * from '@xsai/generate-object'
export * from '@xsai/generate-speech'
export * from '@xsai/generate-text'
+1
View File
@@ -0,0 +1 @@
https://xsai.js.org/docs/packages/generate/image
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@xsai/generate-image",
"type": "module",
"version": "0.2.0-beta.1",
"description": "extra-small AI SDK for Browser, Node.js, Deno, Bun or Edge Runtime.",
"author": "Moeru AI",
"license": "MIT",
"homepage": "https://xsai.js.org",
"repository": {
"type": "git",
"url": "git+https://github.com/moeru-ai/xsai.git",
"directory": "packages/generate-image"
},
"bugs": "https://github.com/moeru-ai/xsai/issues",
"keywords": [
"xsai",
"openai",
"ai"
],
"sideEffects": false,
"exports": "./src/index.ts",
"files": [
"dist"
],
"publishConfig": {
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"scripts": {
"build": "tsup"
},
"dependencies": {
"@xsai/shared": "workspace:"
}
}
+79
View File
@@ -0,0 +1,79 @@
import type { CommonRequestOptions } from '@xsai/shared'
import { requestBody, requestHeaders, requestURL, responseJSON } from '@xsai/shared'
export interface GenerateImageOptions extends CommonRequestOptions {
[key: string]: unknown
/**
* The number of images to generate.
* @default `1`
*/
n?: number
/** A text description of the desired image(s). */
prompt: string
/**
* The size of the generated images.
* @default `1024x1024`
*/
size?: `${number}x${number}`
}
export interface GenerateImageResponse {
created: number
data: {
b64_json: string
revised_prompt?: string
}[]
}
export interface GenerateImageResult {
image: GenerateImageResultImage
images: GenerateImageResultImage[]
}
export interface GenerateImageResultImage {
base64: string
mimeType: string
}
/** @internal */
const mimeTypes = {
'/9j/': 'image/jpg',
'AAAAIGZ0eXBhdmlm': 'image/avif',
'iVBORw0KGgo': 'image/png',
'R0lGOD': 'image/gif',
'UklGRg==': 'image/webp',
}
/** @internal */
const convertImage = (b64_json: string) => {
const key = Object.keys(mimeTypes).find(prefix => b64_json.startsWith(prefix)) as keyof typeof mimeTypes | undefined
const mimeType = mimeTypes[key ?? 'iVBORw0KGgo']
return {
// eslint-disable-next-line @masknet/string-no-data-url
base64: `data:${mimeType};base64,${b64_json}`,
mimeType,
}
}
/** @experimental */
export const generateImage = async (options: GenerateImageOptions): Promise<GenerateImageResult> =>
(options.fetch ?? globalThis.fetch)(requestURL('images/generations', options.baseURL), {
body: requestBody({
...options,
responseFormat: 'b64_json',
}),
headers: requestHeaders({
'Content-Type': 'application/json',
...options.headers,
}, options.apiKey),
method: 'POST',
signal: options.abortSignal,
})
.then(responseJSON<GenerateImageResponse>)
.then(({ data }) => {
const images: GenerateImageResultImage[] = data.map(({ b64_json }) => convertImage(b64_json))
return { image: images[0], images }
})
+1
View File
@@ -19,6 +19,7 @@ type GenerateObjectOutputOption = 'array' | 'object'
export async function generateObject<T extends Schema>(options: GenerateObjectOptions<T> & { output: 'array' }): Promise<GenerateObjectResult<Array<Infer<T>>>>
export async function generateObject<T extends Schema>(options: GenerateObjectOptions<T> & { output: 'object' }): Promise<GenerateObjectResult<Infer<T>>>
export async function generateObject<T extends Schema>(options: GenerateObjectOptions<T>): Promise<GenerateObjectResult<Infer<T>>>
// eslint-disable-next-line func-style
export async function generateObject<T extends Schema>(options: GenerateObjectOptions<T> & { output?: GenerateObjectOutputOption }) {
const { schema: schemaValidator } = options
+1 -3
View File
@@ -43,18 +43,16 @@ export async function streamObject<T extends Schema>(
& StreamObjectOptions<T>
& { output: 'array' }
): Promise<StreamObjectResult<T> & { elementStream: ReadableStream<Infer<T>>, partialObjectStream: undefined }>
export async function streamObject<T extends Schema>(
options: StreamObjectExtraOptions<T>
& StreamObjectOptions<T>
& { output: 'object' }
): Promise<StreamObjectResult<T> & { elementStream: undefined, partialObjectStream: ReadableStream<PartialDeep<Infer<T>>> }>
export async function streamObject<T extends Schema>(
options: StreamObjectExtraOptions<T>
& StreamObjectOptions<T>
): Promise<StreamObjectResult<T> & { elementStream: undefined, partialObjectStream: ReadableStream<PartialDeep<Infer<T>>> }>
// eslint-disable-next-line func-style
export async function streamObject<T extends Schema>(
options: StreamObjectExtraOptions<T>
& StreamObjectOptions<T>
@@ -1,4 +1,5 @@
/** @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#browser_compatibility} */
// eslint-disable-next-line func-style
export async function* toAsyncIterator<T>(
stream: ReadableStream<T>,
): AsyncGenerator<T, void, unknown> {
+12
View File
@@ -145,6 +145,9 @@ importers:
'@xsai/embed':
specifier: workspace:^
version: link:../packages/embed
'@xsai/generate-image':
specifier: workspace:^
version: link:../packages/generate-image
'@xsai/generate-object':
specifier: workspace:^
version: link:../packages/generate-object
@@ -258,6 +261,9 @@ importers:
'@xsai/embed':
specifier: 'workspace:'
version: link:../../packages/embed
'@xsai/generate-image':
specifier: 'workspace:'
version: link:../../packages/generate-image
'@xsai/generate-object':
specifier: 'workspace:'
version: link:../../packages/generate-object
@@ -330,6 +336,12 @@ importers:
specifier: 'workspace:'
version: link:../shared
packages/generate-image:
dependencies:
'@xsai/shared':
specifier: 'workspace:'
version: link:../shared
packages/generate-object:
dependencies:
'@xsai/generate-text':
+2 -5
View File
@@ -10,12 +10,9 @@ catalog:
catalogs:
schema:
'@valibot/to-json-schema': ^1.0.0
valibot: ^1.0.0
'@valibot/to-json-schema': &valibot ^1.0.0
arktype: ^2.1.13
effect: ^3.14.2
valibot: *valibot
zod: ^3.24.2
zod-to-json-schema: ^3.24.5