@@ -107,6 +107,7 @@ export default defineEventHandler(async (event) => {
|
||||
const normalizeLoginResponse = (
|
||||
response: NewApiLoginResponse | undefined
|
||||
): { message: string; user: IUserLoginData | null } => {
|
||||
consola.info("user data", response);
|
||||
if (isUser(response)) {
|
||||
return {
|
||||
message: "登录成功",
|
||||
@@ -152,6 +153,7 @@ const pickUserBasicData = (user: INewApiLoginUserData): IUserLoginData => {
|
||||
};
|
||||
};
|
||||
|
||||
/** 判断值是否为合法的 NewAPI 登录用户对象,所有必需字段均须类型正确 */
|
||||
const isUser = (value: unknown): value is INewApiLoginUserData => {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
@@ -164,6 +166,7 @@ const isUser = (value: unknown): value is INewApiLoginUserData => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 判断值是否为非 null 的普通对象,用于后续安全访问属性 */
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> => {
|
||||
return value !== null && typeof value === "object";
|
||||
};
|
||||
|
||||
@@ -51,14 +51,14 @@ export const newApiFetchRaw = async <T>(
|
||||
path: string,
|
||||
options?: FetchOptions
|
||||
): Promise<INewApiRawResponse<T>> => {
|
||||
const response = await $fetch.raw<T>(path, {
|
||||
const res = await $fetch.raw<T>(path, {
|
||||
baseURL: BASE_URL,
|
||||
...options
|
||||
});
|
||||
|
||||
return {
|
||||
_data: response._data as T | undefined,
|
||||
headers: response.headers
|
||||
_data: res._data as T | undefined,
|
||||
headers: res.headers
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// server/utils/openai.ts
|
||||
import OpenAI from "openai";
|
||||
import type { BaseOptions } from "#shared/types/openai";
|
||||
|
||||
/** 通用 AI 调用函数(支持文本 / 图文 / 多模态) */
|
||||
export const askAI = async ({
|
||||
apiKey,
|
||||
model,
|
||||
baseURL,
|
||||
input
|
||||
}: BaseOptions & {
|
||||
input: OpenAI.Responses.ResponseCreateParams["input"];
|
||||
}) => {
|
||||
const client = new OpenAI({ apiKey, baseURL });
|
||||
|
||||
return await client.responses.create({
|
||||
model,
|
||||
input
|
||||
});
|
||||
};
|
||||
|
||||
/** 调用文本模型 */
|
||||
export const askText = async (
|
||||
options: BaseOptions & {
|
||||
text: string;
|
||||
}
|
||||
) => {
|
||||
const res = await askAI({
|
||||
...options,
|
||||
input: options.text
|
||||
});
|
||||
|
||||
return res.output_text;
|
||||
};
|
||||
|
||||
/**
|
||||
* 调用视觉模型:输入「文本 + 图片」,输出文本
|
||||
*
|
||||
* 分辨率参数说明:
|
||||
*
|
||||
* | 值 | 含义 |
|
||||
* |------|--------------------------|
|
||||
* | low | 低分辨率(更快、更省成本) |
|
||||
* | high | 高分辨率(更精准) |
|
||||
* | auto | 自动选择 |
|
||||
*
|
||||
*/
|
||||
export const askVision = async (
|
||||
options: BaseOptions & {
|
||||
text: string;
|
||||
image: string;
|
||||
detail?: "low" | "high" | "auto";
|
||||
}
|
||||
) => {
|
||||
// 如果图片不是 URL,也不是 data URL,就当成 base64 处理,自动补全 data URL 前缀
|
||||
const normalizeImage = (img: string) => {
|
||||
if (img.startsWith("http")) return img;
|
||||
if (!img.startsWith("data:")) {
|
||||
return `data:image/png;base64,${img}`;
|
||||
}
|
||||
return img;
|
||||
};
|
||||
|
||||
const res = await askAI({
|
||||
...options,
|
||||
input: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "input_text",
|
||||
text: options.text
|
||||
},
|
||||
{
|
||||
type: "input_image",
|
||||
image_url: normalizeImage(options.image),
|
||||
detail: options.detail || "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return res.output_text;
|
||||
};
|
||||
|
||||
/** 调用流式接口 */
|
||||
export const askStream = async (
|
||||
options: BaseOptions & {
|
||||
input: OpenAI.Responses.ResponseCreateParams["input"];
|
||||
}
|
||||
) => {
|
||||
const client = new OpenAI({
|
||||
apiKey: options.apiKey,
|
||||
baseURL: options.baseURL
|
||||
});
|
||||
|
||||
const stream = await client.responses.stream({
|
||||
model: options.model,
|
||||
input: options.input
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
Reference in New Issue
Block a user