122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import { Result } from '@kevisual/query'
|
||
export interface JimengOptions {
|
||
/** API密钥,用于认证请求 */
|
||
apiKey: string;
|
||
/** API基础URL */
|
||
baseUrl: string;
|
||
/** 请求超时时间(毫秒) */
|
||
timeout: number;
|
||
}
|
||
|
||
export interface JimengGenerateOptions {
|
||
/** 图片生成提示词 */
|
||
prompt: string;
|
||
/** 使用的模型版本,默认 jimeng-4.0 */
|
||
model?: string;
|
||
/** 图片比例,默认 1:1 */
|
||
ratio?: string;
|
||
/** 图片分辨率,默认 2k */
|
||
resolution?: string;
|
||
}
|
||
|
||
interface JimengResponse {
|
||
/** 请求创建时间戳 */
|
||
created: number;
|
||
/** 生成的图片列表 */
|
||
data: Array<{
|
||
/** 图片URL */
|
||
url: string;
|
||
}>;
|
||
}
|
||
|
||
export class JimengService {
|
||
private apiKey: string;
|
||
private baseUrl: string;
|
||
private timeout: number;
|
||
|
||
constructor(options: JimengOptions) {
|
||
this.apiKey = options.apiKey;
|
||
this.baseUrl = options.baseUrl || 'https://jimeng-api.kevisual.cn/v1';
|
||
this.timeout = options.timeout;
|
||
}
|
||
|
||
async generateImage(options: JimengGenerateOptions): Promise<Result<JimengResponse>> {
|
||
const {
|
||
prompt,
|
||
model = 'jimeng-4.6',
|
||
ratio = '1:1',
|
||
resolution = '2k'
|
||
} = options;
|
||
|
||
try {
|
||
const controller = new AbortController();
|
||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||
|
||
const response = await fetch(`${this.baseUrl}/images/generations`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${this.apiKey}`,
|
||
},
|
||
body: JSON.stringify({
|
||
model,
|
||
prompt,
|
||
ratio,
|
||
resolution,
|
||
}),
|
||
signal: controller.signal,
|
||
});
|
||
|
||
clearTimeout(timeoutId);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`jimeng API error: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json() as JimengResponse;
|
||
return { code: 200, data: result };
|
||
} catch (error: any) {
|
||
return { code: 500, message: error.message || 'Unknown error' };
|
||
}
|
||
}
|
||
|
||
async downloadImage(url: string): Promise<Uint8Array> {
|
||
const controller = new AbortController();
|
||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||
|
||
try {
|
||
const response = await fetch(url, {
|
||
signal: controller.signal,
|
||
});
|
||
|
||
clearTimeout(timeoutId);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Failed to download image: ${response.statusText}`);
|
||
}
|
||
|
||
const arrayBuffer = await response.arrayBuffer();
|
||
return new Uint8Array(arrayBuffer);
|
||
} catch (error: any) {
|
||
clearTimeout(timeoutId);
|
||
if (error.name === 'AbortError') {
|
||
throw new Error('Image download timeout');
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
/** 获取图片过期时间 */
|
||
async getExpiredTime(url: string): Promise<{ expiredAt: number, expired: boolean }> {
|
||
// https://p3-dreamina-sign.byteimg.com/tos-cn-i-tb4s082cfz/c018e06ee6654dd78ccacb29eff4744e~tplv-tb4s082cfz-aigc_resize:0:0.png?lk3s=43402efa&x-expires=1767852000&x-signature=34yf37N955BP37eLaYEzKeLQn0Q%3D&format=.png
|
||
const urlObj = new URL(url);
|
||
let expires = urlObj.searchParams.get('x-expires');
|
||
if (!expires) {
|
||
expires = '0';
|
||
}
|
||
const expiredAt = parseInt(expires) * 1000;
|
||
const expired = Date.now() > expiredAt;
|
||
return { expiredAt, expired };
|
||
}
|
||
}
|
||
|