diff --git a/cspell.config.yaml b/cspell.config.yaml
index 1b8c3ad..2bdab4e 100644
--- a/cspell.config.yaml
+++ b/cspell.config.yaml
@@ -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
diff --git a/docs/content/docs/packages/generate/image.mdx b/docs/content/docs/packages/generate/image.mdx
new file mode 100644
index 0000000..a33beb2
--- /dev/null
+++ b/docs/content/docs/packages/generate/image.mdx
@@ -0,0 +1,34 @@
+---
+title: Image
+description: Creates an image given a prompt.
+---
+
+import { PkgSizeBadge } from '@/components/pkg-size-badge'
+
+
+
+```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'
+})
+```
diff --git a/docs/content/docs/packages/generate/meta.json b/docs/content/docs/packages/generate/meta.json
index 2315ef0..815deb6 100644
--- a/docs/content/docs/packages/generate/meta.json
+++ b/docs/content/docs/packages/generate/meta.json
@@ -5,6 +5,7 @@
"text",
"object",
"speech",
- "transcription"
+ "transcription",
+ "image"
]
}
diff --git a/docs/content/docs/references/meta.json b/docs/content/docs/references/meta.json
index 22a91bf..6f50f93 100644
--- a/docs/content/docs/references/meta.json
+++ b/docs/content/docs/references/meta.json
@@ -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)",
diff --git a/docs/package.json b/docs/package.json
index 247a561..23c35a5 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -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:^",
diff --git a/eslint.config.ts b/eslint.config.ts
index 8b6dda3..a855986 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -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',
+ ],
+}])
diff --git a/packages-top/xsai/package.json b/packages-top/xsai/package.json
index 81764a7..98c9b93 100644
--- a/packages-top/xsai/package.json
+++ b/packages-top/xsai/package.json
@@ -42,6 +42,7 @@
},
"dependencies": {
"@xsai/embed": "workspace:",
+ "@xsai/generate-image": "workspace:",
"@xsai/generate-object": "workspace:",
"@xsai/generate-speech": "workspace:",
"@xsai/generate-text": "workspace:",
diff --git a/packages-top/xsai/src/index.ts b/packages-top/xsai/src/index.ts
index 069e5fd..004ba7b 100644
--- a/packages-top/xsai/src/index.ts
+++ b/packages-top/xsai/src/index.ts
@@ -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'
diff --git a/packages/generate-image/README.md b/packages/generate-image/README.md
new file mode 100644
index 0000000..39ef677
--- /dev/null
+++ b/packages/generate-image/README.md
@@ -0,0 +1 @@
+https://xsai.js.org/docs/packages/generate/image
diff --git a/packages/generate-image/package.json b/packages/generate-image/package.json
new file mode 100644
index 0000000..e00d805
--- /dev/null
+++ b/packages/generate-image/package.json
@@ -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:"
+ }
+}
diff --git a/packages/generate-image/src/index.ts b/packages/generate-image/src/index.ts
new file mode 100644
index 0000000..1d1ae6e
--- /dev/null
+++ b/packages/generate-image/src/index.ts
@@ -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 =>
+ (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)
+ .then(({ data }) => {
+ const images: GenerateImageResultImage[] = data.map(({ b64_json }) => convertImage(b64_json))
+
+ return { image: images[0], images }
+ })
diff --git a/packages/generate-object/src/index.ts b/packages/generate-object/src/index.ts
index 05cc312..eea96a9 100644
--- a/packages/generate-object/src/index.ts
+++ b/packages/generate-object/src/index.ts
@@ -19,6 +19,7 @@ type GenerateObjectOutputOption = 'array' | 'object'
export async function generateObject(options: GenerateObjectOptions & { output: 'array' }): Promise>>>
export async function generateObject(options: GenerateObjectOptions & { output: 'object' }): Promise>>
export async function generateObject(options: GenerateObjectOptions): Promise>>
+// eslint-disable-next-line func-style
export async function generateObject(options: GenerateObjectOptions & { output?: GenerateObjectOutputOption }) {
const { schema: schemaValidator } = options
diff --git a/packages/stream-object/src/index.ts b/packages/stream-object/src/index.ts
index c490a81..9981e53 100644
--- a/packages/stream-object/src/index.ts
+++ b/packages/stream-object/src/index.ts
@@ -43,18 +43,16 @@ export async function streamObject(
& StreamObjectOptions
& { output: 'array' }
): Promise & { elementStream: ReadableStream>, partialObjectStream: undefined }>
-
export async function streamObject(
options: StreamObjectExtraOptions
& StreamObjectOptions
& { output: 'object' }
): Promise & { elementStream: undefined, partialObjectStream: ReadableStream>> }>
-
export async function streamObject(
options: StreamObjectExtraOptions
& StreamObjectOptions
): Promise & { elementStream: undefined, partialObjectStream: ReadableStream>> }>
-
+// eslint-disable-next-line func-style
export async function streamObject(
options: StreamObjectExtraOptions
& StreamObjectOptions
diff --git a/packages/utils-stream/src/to-async-iterator.ts b/packages/utils-stream/src/to-async-iterator.ts
index ef22322..532f425 100644
--- a/packages/utils-stream/src/to-async-iterator.ts
+++ b/packages/utils-stream/src/to-async-iterator.ts
@@ -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(
stream: ReadableStream,
): AsyncGenerator {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 76472f3..c76f465 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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':
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 406ab16..4f9836a 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -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