update
This commit is contained in:
@@ -1,51 +1,121 @@
|
||||
import { adapter } from '@kevisual/query/query'
|
||||
export type CoreOpts = {
|
||||
baseURL?: string;
|
||||
token?: string;
|
||||
import { Result } from '@kevisual/query'
|
||||
export interface JimengOptions {
|
||||
/** API密钥,用于认证请求 */
|
||||
apiKey: string;
|
||||
/** API基础URL */
|
||||
baseUrl: string;
|
||||
/** 请求超时时间(毫秒) */
|
||||
timeout: number;
|
||||
}
|
||||
export class Core {
|
||||
baseURL: string = 'https://jimeng-api.kevisual.cn/v1';
|
||||
token?: string;
|
||||
constructor(opts: CoreOpts = {}) {
|
||||
console.log("Core initialized");
|
||||
if (opts.baseURL) {
|
||||
this.baseURL = opts.baseURL;
|
||||
}
|
||||
if (opts.token) {
|
||||
this.token = opts.token;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
makeHeader() {
|
||||
return {
|
||||
Authorization: this.token ? `Bearer ${this.token}` : undefined,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
generateImage({ model = 'jimeng-4.0', prompt, resolution = '2k' }: ImageOptions) {
|
||||
const url = `${this.baseURL}/images/generations`;
|
||||
return adapter({
|
||||
url,
|
||||
headers: this.makeHeader(),
|
||||
body: {
|
||||
model,
|
||||
prompt,
|
||||
resolution
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
export type ImageOptions = {
|
||||
model?: string;
|
||||
prompt: string;
|
||||
/**
|
||||
* 宽高比,如 "16:9", "4:3", "1:1" 等
|
||||
*/
|
||||
ratio?: string;
|
||||
/**
|
||||
*
|
||||
* 图片分辨率,如 "1024x768", "512x512" 等
|
||||
* 4k 2k
|
||||
*/
|
||||
resolution?: string;
|
||||
}
|
||||
10
src/provider/chat-adapter/mimo.ts
Normal file
10
src/provider/chat-adapter/mimo.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { BaseChat, type BaseChatOptions } from "../chat.ts";
|
||||
|
||||
type MimoOptions = Partial<BaseChatOptions>;
|
||||
export class MimoChat extends BaseChat {
|
||||
static BASE_URL = 'https://api.xiaomimimo.com/v1';
|
||||
constructor(options: MimoOptions) {
|
||||
const baseURL = options.baseURL || MimoChat.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,6 @@ export class Ollama extends BaseChat {
|
||||
const baseURL = options.baseURL || Ollama.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
async chat(messages: ChatMessage[], options?: ChatMessageOptions) {
|
||||
const res = await super.chat(messages, options);
|
||||
console.log('thunk', this.getChatUsage());
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* 获取模型列表
|
||||
* @returns
|
||||
|
||||
@@ -32,8 +32,4 @@ export class SiliconFlow extends BaseChat {
|
||||
async getUsageInfo(): Promise<SiliconFlowUsageResponse> {
|
||||
return this.get('/user/info');
|
||||
}
|
||||
async chat(messages: ChatMessage[], options?: ChatMessageOptions) {
|
||||
const res = await super.chat(messages, options);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,17 @@ export class BaseChat implements BaseChatInterface, Usage {
|
||||
/**
|
||||
* 聊天
|
||||
*/
|
||||
async chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete> {
|
||||
chat(options: ChatMessageOptions): Promise<ChatMessageComplete>;
|
||||
chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
|
||||
async chat(messagesOrOptions: ChatMessage[] | ChatMessageOptions, options?: ChatMessageOptions): Promise<ChatMessageComplete> {
|
||||
const isFirstParamOptions = !Array.isArray(messagesOrOptions);
|
||||
const messages: ChatMessage[] = isFirstParamOptions ? messagesOrOptions.messages! : messagesOrOptions;
|
||||
const opts: ChatMessageOptions = isFirstParamOptions ? messagesOrOptions : options || {};
|
||||
|
||||
const requestBody = {
|
||||
model: this.model,
|
||||
messages,
|
||||
...options,
|
||||
...opts,
|
||||
stream: false,
|
||||
};
|
||||
|
||||
@@ -110,15 +116,21 @@ export class BaseChat implements BaseChatInterface, Usage {
|
||||
this.responseText = res.choices[0]?.message?.content || '';
|
||||
return res;
|
||||
}
|
||||
async chatStream(messages: ChatMessage[], options?: ChatMessageOptions) {
|
||||
if (options?.response_format) {
|
||||
chatStream(options: ChatMessageOptions): AsyncGenerator<ChatMessageComplete>;
|
||||
chatStream(messages: ChatMessage[], options?: ChatMessageOptions): AsyncGenerator<ChatMessageComplete>;
|
||||
async *chatStream(messagesOrOptions: ChatMessage[] | ChatMessageOptions, options?: ChatMessageOptions) {
|
||||
const isFirstParamOptions = !Array.isArray(messagesOrOptions);
|
||||
const messages: ChatMessage[] = isFirstParamOptions ? messagesOrOptions.messages! : messagesOrOptions;
|
||||
const opts: ChatMessageOptions = isFirstParamOptions ? messagesOrOptions : options || {};
|
||||
|
||||
if (opts.response_format) {
|
||||
throw new Error('response_format is not supported in stream mode');
|
||||
}
|
||||
|
||||
const requestBody = {
|
||||
model: this.model,
|
||||
messages,
|
||||
...options,
|
||||
...opts,
|
||||
stream: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ export type ChatMessage = {
|
||||
role?: 'user' | 'assistant' | 'system' | 'tool';
|
||||
content: string;
|
||||
}
|
||||
export type ChatMessageOptions = {
|
||||
messages: ChatMessage[];
|
||||
export type ChatMessageOptions<T = {}> = {
|
||||
messages?: ChatMessage[];
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
@@ -43,7 +43,7 @@ export type ChatMessageOptions = {
|
||||
stream?: boolean;
|
||||
/**
|
||||
* 是否能够思考
|
||||
* 如果会话是千文,服务器的接口,默认为 true
|
||||
* 如果会话是千文,服务器的接口,默认为 false
|
||||
*/
|
||||
enable_thinking?: boolean;
|
||||
response_format?: 'text' | 'json' | 'xml' | 'html';
|
||||
@@ -53,7 +53,9 @@ export type ChatMessageOptions = {
|
||||
*/
|
||||
tool_calls?: any;
|
||||
|
||||
};
|
||||
[key: string]: any;
|
||||
|
||||
} & T;
|
||||
|
||||
type Choice = {
|
||||
finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call';
|
||||
@@ -167,6 +169,7 @@ export type EmbeddingMessageComplete = {
|
||||
|
||||
|
||||
export interface BaseChatInterface {
|
||||
chat(options: ChatMessageOptions): Promise<ChatMessageComplete>;
|
||||
chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user