mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-17 08:37:59 +00:00
feat: integrate ts-rest (#5741)
* feat: integrate ts-rest * chore: classify core contract and pro contract * chore: update lockfile * chore: tweak dir structure * chore: tweak dir structure
This commit is contained in:
172
packages/global/common/tsRest/fastgpt/client.ts
Normal file
172
packages/global/common/tsRest/fastgpt/client.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { contract } from './contracts';
|
||||||
|
import { initClient, tsRestFetchApi } from '@ts-rest/core';
|
||||||
|
import { TOKEN_ERROR_CODE } from '../../error/errorCode';
|
||||||
|
import { getNanoid } from '../../string/tools';
|
||||||
|
import { type ApiFetcherArgs } from '@ts-rest/core';
|
||||||
|
import { AnyResponseSchema } from '../../type';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
|
import { getWebReqUrl } from '../../../../web/common/system/utils';
|
||||||
|
|
||||||
|
export const client = initClient(contract, {
|
||||||
|
baseUrl: getWebReqUrl('/api'),
|
||||||
|
throwOnUnknownStatus: true,
|
||||||
|
validateResponse: false,
|
||||||
|
credentials: 'include',
|
||||||
|
baseHeaders: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8'
|
||||||
|
},
|
||||||
|
api: async (args: BeforeFetchOptions) => {
|
||||||
|
const prepare = beforeFetch(args);
|
||||||
|
const response = await tsRestFetchApi(args);
|
||||||
|
return afterFetch(response, prepare);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const WHITE_LIST = ['/chat/share', '/chat', '/login'];
|
||||||
|
async function isTokenExpired() {
|
||||||
|
if (WHITE_LIST.includes(window.location.pathname)) return;
|
||||||
|
|
||||||
|
await client.support.user.account.logout();
|
||||||
|
const lastRoute = encodeURIComponent(location.pathname + location.search);
|
||||||
|
window.location.replace(getWebReqUrl(`/login?lastRoute=${lastRoute}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkBusinessCode(code: number) {
|
||||||
|
if (code in TOKEN_ERROR_CODE) {
|
||||||
|
isTokenExpired();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item = { id: string; controller: AbortController };
|
||||||
|
const queue = new Map<string, Item[]>();
|
||||||
|
function checkMaxRequestLimitation(options: { url: string; max: number }): {
|
||||||
|
id: string;
|
||||||
|
signal: AbortSignal;
|
||||||
|
release: () => void;
|
||||||
|
} {
|
||||||
|
const { url, max } = options;
|
||||||
|
const id = getNanoid();
|
||||||
|
const controller = new AbortController();
|
||||||
|
const item = queue.get(url);
|
||||||
|
|
||||||
|
const current = item ?? [];
|
||||||
|
if (current.length >= max) {
|
||||||
|
const first = current.shift()!;
|
||||||
|
first.controller.abort();
|
||||||
|
}
|
||||||
|
current.push({ id, controller });
|
||||||
|
if (!item) queue.set(url, current);
|
||||||
|
|
||||||
|
const release = () => {
|
||||||
|
const item = queue.get(url);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const index = item.findIndex((item) => item.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
item.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.length <= 0) {
|
||||||
|
queue.delete(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { id, signal: controller.signal, release };
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHttpStatus(status: number): status is 200 {
|
||||||
|
if (status !== 200) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
type BeforeFetchOptions = ApiFetcherArgs & { max?: number };
|
||||||
|
function beforeFetch(options: BeforeFetchOptions):
|
||||||
|
| {
|
||||||
|
limit: { id: string; url: string; release: () => void };
|
||||||
|
}
|
||||||
|
| undefined {
|
||||||
|
const { max, ...args } = options;
|
||||||
|
if (!max || max <= 0) return;
|
||||||
|
|
||||||
|
const { id, signal, release } = checkMaxRequestLimitation({ url: args.path, max });
|
||||||
|
args.fetchOptions ??= {};
|
||||||
|
args.fetchOptions.signal = signal;
|
||||||
|
|
||||||
|
return {
|
||||||
|
limit: { id, url: args.path, release }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterFetch(
|
||||||
|
response: Awaited<ReturnType<typeof tsRestFetchApi>>,
|
||||||
|
prepare?: ReturnType<typeof beforeFetch>
|
||||||
|
) {
|
||||||
|
if (checkHttpStatus(response.status)) {
|
||||||
|
try {
|
||||||
|
const body = AnyResponseSchema.parse(response.body);
|
||||||
|
|
||||||
|
response.body = body.data;
|
||||||
|
|
||||||
|
if (prepare?.limit) {
|
||||||
|
prepare.limit.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unknown error while intercept response');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`HTTP error, status: ${response.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client = typeof client;
|
||||||
|
type U<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : U<T[K]> }[keyof T];
|
||||||
|
export type Endpoints = U<Client>;
|
||||||
|
type _Options<T extends Endpoints> = NonNullable<Parameters<T>[0]>;
|
||||||
|
type ExtractBodySchema<T extends Endpoints> = 'body' extends keyof _Options<T>
|
||||||
|
? _Options<T>['body']
|
||||||
|
: never;
|
||||||
|
type ExtractQuerySchema<T extends Endpoints> = 'query' extends keyof _Options<T>
|
||||||
|
? _Options<T>['query']
|
||||||
|
: never;
|
||||||
|
export type Params<T extends Endpoints> = (ExtractBodySchema<T> extends never
|
||||||
|
? {}
|
||||||
|
: ExtractBodySchema<T>) &
|
||||||
|
(ExtractQuerySchema<T> extends never ? {} : ExtractQuerySchema<T>);
|
||||||
|
export type Options<T extends Endpoints> = Omit<_Options<T>, 'body' | 'query'>;
|
||||||
|
type Body<T extends Endpoints> = Extract<Awaited<ReturnType<T>>, { status: 200 }>['body'];
|
||||||
|
type RestAPIResult<T extends Endpoints> = Body<T>;
|
||||||
|
|
||||||
|
const call = async <T extends Endpoints>(
|
||||||
|
api: T,
|
||||||
|
options: _Options<T>
|
||||||
|
): Promise<RestAPIResult<T>> => {
|
||||||
|
const res = await api(options as any);
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(`Unexpected status: ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.body as RestAPIResult<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RestAPI = <T extends Endpoints>(
|
||||||
|
endpoint: T,
|
||||||
|
transform?: (params: Params<T>) => {
|
||||||
|
body?: ExtractBodySchema<T> extends never ? any : ExtractBodySchema<T>;
|
||||||
|
query?: ExtractQuerySchema<T> extends never ? any : ExtractQuerySchema<T>;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
return (params?: Params<T>, options?: Options<T>) => {
|
||||||
|
const transformedData = params && transform ? transform(params) : {};
|
||||||
|
const finalOptions = { ...options, ...transformedData } as _Options<T>;
|
||||||
|
|
||||||
|
return call(endpoint, finalOptions);
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,6 @@
|
|||||||
|
import { settingContract } from './setting';
|
||||||
|
import { c } from '../../../init';
|
||||||
|
|
||||||
|
export const chatContract = c.router({
|
||||||
|
setting: settingContract
|
||||||
|
});
|
@@ -0,0 +1,94 @@
|
|||||||
|
import { ObjectIdSchema } from '../../../../../../type';
|
||||||
|
import {
|
||||||
|
ChatFavouriteAppResponseItemSchema,
|
||||||
|
ChatFavouriteAppUpdateSchema
|
||||||
|
} from '../../../../../../../core/chat/favouriteApp/type';
|
||||||
|
import { c } from '../../../../../init';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const favouriteContract = c.router({
|
||||||
|
list: {
|
||||||
|
path: '/proApi/core/chat/setting/favourite/list',
|
||||||
|
method: 'GET',
|
||||||
|
query: z.object({
|
||||||
|
name: z.string().optional().openapi({ example: 'FastGPT' }),
|
||||||
|
tag: z.string().optional().openapi({ example: 'i7Ege2W2' })
|
||||||
|
}),
|
||||||
|
responses: {
|
||||||
|
200: z.array(ChatFavouriteAppResponseItemSchema)
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '获取精选应用列表',
|
||||||
|
summary: '获取精选应用列表'
|
||||||
|
},
|
||||||
|
|
||||||
|
update: {
|
||||||
|
path: '/proApi/core/chat/setting/favourite/update',
|
||||||
|
method: 'PUT',
|
||||||
|
body: ChatFavouriteAppUpdateSchema,
|
||||||
|
responses: {
|
||||||
|
200: c.type<void>()
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '更新精选应用',
|
||||||
|
summary: '更新精选应用'
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: {
|
||||||
|
path: '/proApi/core/chat/setting/favourite/delete',
|
||||||
|
method: 'DELETE',
|
||||||
|
query: z.object({
|
||||||
|
id: ObjectIdSchema
|
||||||
|
}),
|
||||||
|
responses: {
|
||||||
|
200: c.type<void>()
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '删除精选应用',
|
||||||
|
summary: '删除精选应用'
|
||||||
|
},
|
||||||
|
|
||||||
|
order: {
|
||||||
|
path: '/proApi/core/chat/setting/favourite/order',
|
||||||
|
method: 'PUT',
|
||||||
|
body: z.array(
|
||||||
|
z.object({
|
||||||
|
id: ObjectIdSchema,
|
||||||
|
order: z.number()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
responses: {
|
||||||
|
200: c.type<void>()
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '更新精选应用顺序',
|
||||||
|
summary: '更新精选应用顺序'
|
||||||
|
},
|
||||||
|
|
||||||
|
tags: {
|
||||||
|
path: '/proApi/core/chat/setting/favourite/tags',
|
||||||
|
method: 'PUT',
|
||||||
|
body: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
tags: z.array(z.string())
|
||||||
|
})
|
||||||
|
),
|
||||||
|
responses: {
|
||||||
|
200: c.type<void>()
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '更新精选应用标签',
|
||||||
|
summary: '更新精选应用标签'
|
||||||
|
}
|
||||||
|
});
|
@@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
ChatSettingResponseSchema,
|
||||||
|
ChatSettingSchema
|
||||||
|
} from '../../../../../../core/chat/setting/type';
|
||||||
|
import { c } from '../../../../init';
|
||||||
|
import { favouriteContract } from './favourite';
|
||||||
|
|
||||||
|
export const settingContract = c.router({
|
||||||
|
favourite: favouriteContract,
|
||||||
|
|
||||||
|
detail: {
|
||||||
|
path: '/proApi/core/chat/setting/detail',
|
||||||
|
method: 'GET',
|
||||||
|
responses: {
|
||||||
|
200: ChatSettingResponseSchema
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '获取聊天设置',
|
||||||
|
summary: '获取聊天设置'
|
||||||
|
},
|
||||||
|
|
||||||
|
update: {
|
||||||
|
path: '/proApi/core/chat/setting/update',
|
||||||
|
method: 'PUT',
|
||||||
|
body: ChatSettingSchema.partial(),
|
||||||
|
responses: {
|
||||||
|
200: c.type<void>()
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['chat']
|
||||||
|
},
|
||||||
|
description: '更新聊天设置',
|
||||||
|
summary: '更新聊天设置'
|
||||||
|
}
|
||||||
|
});
|
14
packages/global/common/tsRest/fastgpt/contracts/index.ts
Normal file
14
packages/global/common/tsRest/fastgpt/contracts/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { supportContract } from './support';
|
||||||
|
import { chatContract } from './chat';
|
||||||
|
import { c } from '../../init';
|
||||||
|
|
||||||
|
// 前端使用的完整合约(开源 + Pro)
|
||||||
|
// FastGPT 后端使用的合约
|
||||||
|
export const contract = c.router({
|
||||||
|
chat: {
|
||||||
|
...chatContract
|
||||||
|
},
|
||||||
|
support: {
|
||||||
|
...supportContract
|
||||||
|
}
|
||||||
|
});
|
@@ -0,0 +1,6 @@
|
|||||||
|
import { userContract } from './user';
|
||||||
|
import { c } from '../../../init';
|
||||||
|
|
||||||
|
export const supportContract = c.router({
|
||||||
|
user: userContract
|
||||||
|
});
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { c } from '../../../../../init';
|
||||||
|
|
||||||
|
export const accountContract = c.router({
|
||||||
|
logout: {
|
||||||
|
path: '/support/user/account/login',
|
||||||
|
method: 'POST',
|
||||||
|
body: c.type<undefined>(),
|
||||||
|
responses: {
|
||||||
|
200: c.type<void>()
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
tags: ['support']
|
||||||
|
},
|
||||||
|
description: '退出登录',
|
||||||
|
summary: '退出登录'
|
||||||
|
}
|
||||||
|
});
|
@@ -0,0 +1,6 @@
|
|||||||
|
import { accountContract } from '../../../../fastgpt/contracts/support/user/account';
|
||||||
|
import { c } from '../../../../init';
|
||||||
|
|
||||||
|
export const userContract = c.router({
|
||||||
|
account: accountContract
|
||||||
|
});
|
21
packages/global/common/tsRest/fastgpt/server.ts
Normal file
21
packages/global/common/tsRest/fastgpt/server.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createNextRouter } from '@ts-rest/next';
|
||||||
|
import { createNextRoute } from '@ts-rest/next';
|
||||||
|
import { contract } from './contracts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 FastGPT 单个路由
|
||||||
|
*/
|
||||||
|
export function createServerRoute(
|
||||||
|
implementation: Parameters<typeof createNextRoute<typeof contract>>[1]
|
||||||
|
) {
|
||||||
|
return createNextRoute(contract, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 FastGPT 路由器
|
||||||
|
*/
|
||||||
|
export function createServerRouter(
|
||||||
|
router: Parameters<typeof createNextRouter<typeof contract>>[1]
|
||||||
|
) {
|
||||||
|
return createNextRouter(contract, router);
|
||||||
|
}
|
55
packages/global/common/tsRest/fastgptpro/contracts/index.ts
Normal file
55
packages/global/common/tsRest/fastgptpro/contracts/index.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { chatContract } from '../../fastgpt/contracts/chat';
|
||||||
|
import { c } from '../../init';
|
||||||
|
|
||||||
|
// 通过 FastGPT 后端转发到 Pro 后端使用的合约
|
||||||
|
const transformedProContract = c.router({
|
||||||
|
chat: transformPaths(chatContract)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pro 后端独有的接口
|
||||||
|
const proOnlyContract = c.router({
|
||||||
|
// TODO
|
||||||
|
// admin: adminContract,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 最终的 Pro 合约 = 转换后的 Pro 接口 + Pro 后端独有的接口
|
||||||
|
// Pro 后端使用的合约
|
||||||
|
export const proContract = c.router({
|
||||||
|
...transformedProContract,
|
||||||
|
...proOnlyContract
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换路径前缀
|
||||||
|
* 将 /proApi 替换为空字符串,用于 Pro 后端
|
||||||
|
*/
|
||||||
|
function transformPaths<T extends Record<string, any>>(
|
||||||
|
router: T,
|
||||||
|
removePrefix: string = '/proApi',
|
||||||
|
replaceWith: string = ''
|
||||||
|
): T {
|
||||||
|
const transform = (obj: any): any => {
|
||||||
|
if (typeof obj !== 'object' || obj === null) return obj;
|
||||||
|
|
||||||
|
// 如果是路由定义(有 path 属性)
|
||||||
|
if ('path' in obj && typeof obj.path === 'string') {
|
||||||
|
return {
|
||||||
|
...obj,
|
||||||
|
path: obj.path.replace(removePrefix, replaceWith),
|
||||||
|
metadata: {
|
||||||
|
...obj.metadata,
|
||||||
|
originalPath: obj.path
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理嵌套的路由
|
||||||
|
const result: any = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
result[key] = transform(obj[key]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return transform(router) as T;
|
||||||
|
}
|
21
packages/global/common/tsRest/fastgptpro/server.ts
Normal file
21
packages/global/common/tsRest/fastgptpro/server.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { proContract } from './contracts';
|
||||||
|
import { createNextRoute, createNextRouter } from '@ts-rest/next';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Pro 单个路由
|
||||||
|
*/
|
||||||
|
export function createProServerRoute(
|
||||||
|
implementation: Parameters<typeof createNextRoute<typeof proContract>>[1]
|
||||||
|
) {
|
||||||
|
return createNextRoute(proContract, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Pro 路由器
|
||||||
|
* 只需实现 Pro 接口(路径已自动转换 /proApi → 空)
|
||||||
|
*/
|
||||||
|
export function createProServerRouter(
|
||||||
|
router: Parameters<typeof createNextRouter<typeof proContract>>[1]
|
||||||
|
) {
|
||||||
|
return createNextRouter(proContract, router);
|
||||||
|
}
|
3
packages/global/common/tsRest/init.ts
Normal file
3
packages/global/common/tsRest/init.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { initContract } from '@ts-rest/core';
|
||||||
|
|
||||||
|
export const c = initContract();
|
34
packages/global/common/tsRest/openapi.ts
Normal file
34
packages/global/common/tsRest/openapi.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { contract } from './fastgpt/contracts';
|
||||||
|
import { generateOpenApi } from '@ts-rest/open-api';
|
||||||
|
|
||||||
|
const hasCustomTags = (metadata: unknown): metadata is { tags: string[] } => {
|
||||||
|
return !!metadata && typeof metadata === 'object' && 'tags' in metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OpenAPIObject = ReturnType<typeof generateOpenApi>;
|
||||||
|
export function generateOpenApiDocument(c: typeof contract): OpenAPIObject {
|
||||||
|
return generateOpenApi(
|
||||||
|
c,
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
title: 'FastGPT OpenAPI',
|
||||||
|
version: '4.12.4',
|
||||||
|
description: 'FastGPT OpenAPI'
|
||||||
|
},
|
||||||
|
servers: [{ url: '/api' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationMapper(operation, appRoute) {
|
||||||
|
return {
|
||||||
|
...operation,
|
||||||
|
...(hasCustomTags(appRoute.metadata)
|
||||||
|
? {
|
||||||
|
tags: appRoute.metadata.tags
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setOperationId: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
14
packages/global/common/tsRest/type.ts
Normal file
14
packages/global/common/tsRest/type.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { AppRoute } from '@ts-rest/core';
|
||||||
|
import type { createSingleRouteHandler } from '@ts-rest/next';
|
||||||
|
|
||||||
|
export type { AppRoute } from '@ts-rest/core';
|
||||||
|
|
||||||
|
export type Endpoint<T extends AppRoute> = Parameters<typeof createSingleRouteHandler<T>>[1];
|
||||||
|
export type Args<T extends AppRoute> = Parameters<Endpoint<T>>[0];
|
||||||
|
type Result<T extends AppRoute> = Awaited<ReturnType<Endpoint<T>>>;
|
||||||
|
|
||||||
|
type Ok<T extends AppRoute> = Extract<Result<T>, { status: 200 }>;
|
||||||
|
type Response<T extends AppRoute> =
|
||||||
|
Ok<T> extends { body: infer B } ? (B extends { data: infer D } ? D : B) : never;
|
||||||
|
|
||||||
|
export type Handler<T extends AppRoute> = (args: Args<T>) => Promise<Response<T>>;
|
45
packages/global/common/type/index.ts
Normal file
45
packages/global/common/type/index.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
extendZodWithOpenApi(z);
|
||||||
|
|
||||||
|
export const createCommonResponseSchema = <T extends z.ZodTypeAny>(schema: T) =>
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
code: z.number().describe('状态码'),
|
||||||
|
message: z.string().describe('消息'),
|
||||||
|
data: schema.describe('数据'),
|
||||||
|
statusText: z.string().describe('状态文本')
|
||||||
|
})
|
||||||
|
.describe('通用响应')
|
||||||
|
.openapi({
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
data: null,
|
||||||
|
message: 'Success',
|
||||||
|
statusText: 'Success'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ObjectIdSchema = z
|
||||||
|
.string()
|
||||||
|
.length(24)
|
||||||
|
.describe('MongoDB ObjectId 格式的字符串')
|
||||||
|
.openapi({ example: '6894728240dc458ece573294' });
|
||||||
|
export type ObjectIdType = z.infer<typeof ObjectIdSchema>;
|
||||||
|
|
||||||
|
export const ParentIdSchema = ObjectIdSchema.nullish()
|
||||||
|
.openapi({ example: null })
|
||||||
|
.describe('父级ID, 可以是 ObjectId、null 或 undefined(表示根级)');
|
||||||
|
export type ParentIdType = z.infer<typeof ParentIdSchema>;
|
||||||
|
|
||||||
|
export const DateTimeSchema = z.date().describe('ISO 8601 格式的时间字符串');
|
||||||
|
export type DateTimeType = z.infer<typeof DateTimeSchema>;
|
||||||
|
|
||||||
|
export const OptionalDateTimeSchema = DateTimeSchema.nullish()
|
||||||
|
.openapi({ example: null })
|
||||||
|
.describe('ISO 8601 格式的时间字符串, 可以是 null 或 undefined');
|
||||||
|
export type OptionalDateTimeType = z.infer<typeof OptionalDateTimeSchema>;
|
||||||
|
|
||||||
|
// 任意类型数据的响应
|
||||||
|
export const AnyResponseSchema = createCommonResponseSchema(z.any());
|
18
packages/global/core/chat/favouriteApp/type.d.ts
vendored
18
packages/global/core/chat/favouriteApp/type.d.ts
vendored
@@ -1,18 +0,0 @@
|
|||||||
export type ChatFavouriteAppSchema = {
|
|
||||||
_id: string;
|
|
||||||
teamId: string;
|
|
||||||
appId: string;
|
|
||||||
favouriteTags: string[]; // tag id list
|
|
||||||
order: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatFavouriteAppUpdateParams = {
|
|
||||||
appId: string;
|
|
||||||
order: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatFavouriteApp = ChatFavouriteAppSchema & {
|
|
||||||
name: string;
|
|
||||||
avatar: string;
|
|
||||||
intro: string;
|
|
||||||
};
|
|
34
packages/global/core/chat/favouriteApp/type.ts
Normal file
34
packages/global/core/chat/favouriteApp/type.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
||||||
|
import { ObjectIdSchema } from '../../../common/type';
|
||||||
|
|
||||||
|
extendZodWithOpenApi(z);
|
||||||
|
|
||||||
|
export const ChatFavouriteTagSchema = z.object({
|
||||||
|
id: z.string().openapi({ example: 'i7Ege2W2' }),
|
||||||
|
name: z.string().openapi({ example: '游戏' })
|
||||||
|
});
|
||||||
|
export type ChatFavouriteTagType = z.infer<typeof ChatFavouriteTagSchema>;
|
||||||
|
|
||||||
|
export const ChatFavouriteAppSchema = z.object({
|
||||||
|
_id: ObjectIdSchema,
|
||||||
|
teamId: ObjectIdSchema,
|
||||||
|
appId: ObjectIdSchema,
|
||||||
|
favouriteTags: z.array(z.string()).openapi({ example: ['i7Ege2W2', 'i7Ege2W3'] }),
|
||||||
|
order: z.number().openapi({ example: 1 })
|
||||||
|
});
|
||||||
|
export type ChatFavouriteAppType = z.infer<typeof ChatFavouriteAppSchema>;
|
||||||
|
|
||||||
|
export const ChatFavouriteAppResponseItemSchema = z.object({
|
||||||
|
...ChatFavouriteAppSchema.shape,
|
||||||
|
name: z.string().openapi({ example: 'FastGPT' }),
|
||||||
|
intro: z.string().openapi({ example: 'FastGPT' }),
|
||||||
|
avatar: z.string().openapi({ example: 'https://fastgpt.com/avatar.png' })
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ChatFavouriteAppResponseItemType = z.infer<typeof ChatFavouriteAppResponseItemSchema>;
|
||||||
|
|
||||||
|
export const ChatFavouriteAppUpdateSchema = z.array(
|
||||||
|
ChatFavouriteAppSchema.pick({ appId: true, order: true })
|
||||||
|
);
|
||||||
|
export type ChatFavouriteAppUpdateType = z.infer<typeof ChatFavouriteAppUpdateSchema>;
|
37
packages/global/core/chat/setting/type.d.ts
vendored
37
packages/global/core/chat/setting/type.d.ts
vendored
@@ -1,37 +0,0 @@
|
|||||||
export type ChatSettingSchema = {
|
|
||||||
_id: string;
|
|
||||||
appId: string;
|
|
||||||
teamId: string;
|
|
||||||
slogan: string;
|
|
||||||
dialogTips: string;
|
|
||||||
enableHome: boolean;
|
|
||||||
homeTabTitle: string;
|
|
||||||
wideLogoUrl?: string;
|
|
||||||
squareLogoUrl?: string;
|
|
||||||
|
|
||||||
selectedTools: {
|
|
||||||
pluginId: string;
|
|
||||||
inputs?: Record<`${NodeInputKeyEnum}` | string, any>;
|
|
||||||
}[];
|
|
||||||
quickAppIds: string[];
|
|
||||||
favouriteTags: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatSettingUpdateParams = Partial<Omit<ChatSettingSchema, '_id' | 'appId' | 'teamId'>>;
|
|
||||||
|
|
||||||
export type QuickAppType = { _id: string; name: string; avatar: string };
|
|
||||||
export type ChatFavouriteTagType = ChatSettingSchema['favouriteTags'][number];
|
|
||||||
export type SelectedToolType = ChatSettingSchema['selectedTools'][number] & {
|
|
||||||
name: string;
|
|
||||||
avatar: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatSettingReturnType =
|
|
||||||
| (Omit<ChatSettingSchema, 'quickAppIds' | 'selectedTools'> & {
|
|
||||||
quickAppList: QuickAppType[];
|
|
||||||
selectedTools: SelectedToolType[];
|
|
||||||
})
|
|
||||||
| undefined;
|
|
58
packages/global/core/chat/setting/type.ts
Normal file
58
packages/global/core/chat/setting/type.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
||||||
|
import { ObjectIdSchema } from '../../../common/type';
|
||||||
|
import { ChatFavouriteTagSchema } from '../favouriteApp/type';
|
||||||
|
|
||||||
|
extendZodWithOpenApi(z);
|
||||||
|
|
||||||
|
export const ChatQuickAppSchema = z.object({
|
||||||
|
_id: ObjectIdSchema,
|
||||||
|
name: z.string().openapi({ example: 'FastGPT' }),
|
||||||
|
avatar: z.string().openapi({ example: 'https://fastgpt.com/avatar.png' })
|
||||||
|
});
|
||||||
|
export type ChatQuickAppType = z.infer<typeof ChatQuickAppSchema>;
|
||||||
|
|
||||||
|
export const ChatSelectedToolSchema = z.object({
|
||||||
|
pluginId: z.string().openapi({ example: 'systemTool-getTime' }),
|
||||||
|
inputs: z.record(z.string(), z.any()).optional(),
|
||||||
|
name: z.string().openapi({ example: 'FastGPT' }),
|
||||||
|
avatar: z.string().openapi({ example: 'https://fastgpt.com/avatar.png' })
|
||||||
|
});
|
||||||
|
export type ChatSelectedToolType = z.infer<typeof ChatSelectedToolSchema>;
|
||||||
|
|
||||||
|
export const ChatSettingSchema = z.object({
|
||||||
|
_id: ObjectIdSchema,
|
||||||
|
appId: ObjectIdSchema,
|
||||||
|
teamId: ObjectIdSchema,
|
||||||
|
slogan: z.string().openapi({ example: '你好👋,我是 FastGPT ! 请问有什么可以帮你?' }),
|
||||||
|
dialogTips: z.string().openapi({ example: '你可以问我任何问题' }),
|
||||||
|
enableHome: z.boolean().openapi({ example: true }),
|
||||||
|
homeTabTitle: z.string().openapi({ example: '首页' }),
|
||||||
|
wideLogoUrl: z.string().optional().openapi({
|
||||||
|
example:
|
||||||
|
'/api/system/img/avatar/686f319cbb6eea8840884338/2025_09_30/9f77bd74be89c71cd3b97bd2fb8b2c05_DeviconMysql.png'
|
||||||
|
}),
|
||||||
|
squareLogoUrl: z.string().optional().openapi({
|
||||||
|
example:
|
||||||
|
'/api/system/img/avatar/686f319cbb6eea8840884338/2025_09_30/9f77bd74be89c71cd3b97bd2fb8b2c05_DeviconMysql.png'
|
||||||
|
}),
|
||||||
|
selectedTools: z
|
||||||
|
.array(ChatSelectedToolSchema.pick({ pluginId: true, inputs: true }))
|
||||||
|
.openapi({ example: [{ pluginId: 'systemTool-getTime', inputs: {} }] }),
|
||||||
|
quickAppIds: z
|
||||||
|
.array(ObjectIdSchema)
|
||||||
|
.openapi({ example: ['68ac3dc6a717b3bcacc749bb', '68ac2da6a717b3bcacc73032'] }),
|
||||||
|
favouriteTags: z
|
||||||
|
.array(ChatFavouriteTagSchema)
|
||||||
|
.openapi({ example: [{ id: 'i7Ege2W2', name: '游戏' }] })
|
||||||
|
});
|
||||||
|
export type ChatSettingType = z.infer<typeof ChatSettingSchema>;
|
||||||
|
|
||||||
|
export const ChatSettingResponseSchema = z.object({
|
||||||
|
...ChatSettingSchema.omit({ quickAppIds: true, selectedTools: true }).shape,
|
||||||
|
quickAppList: z.array(ChatQuickAppSchema),
|
||||||
|
selectedTools: z.array(ChatSelectedToolSchema)
|
||||||
|
});
|
||||||
|
export type ChatSettingResponseType = z.infer<typeof ChatSettingResponseSchema>;
|
||||||
|
|
||||||
|
export type ChatSettingUpdateType = Partial<z.infer<typeof ChatSettingSchema>>;
|
@@ -3,9 +3,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastgpt-sdk/plugin": "^0.1.19",
|
"@fastgpt-sdk/plugin": "^0.1.19",
|
||||||
|
"@anatine/zod-openapi": "^2.2.8",
|
||||||
"@apidevtools/swagger-parser": "^10.1.0",
|
"@apidevtools/swagger-parser": "^10.1.0",
|
||||||
"@bany/curl-to-json": "^1.2.8",
|
"@bany/curl-to-json": "^1.2.8",
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
|
"@ts-rest/core": "^3.52.1",
|
||||||
|
"@ts-rest/next": "^3.52.1",
|
||||||
|
"@ts-rest/open-api": "^3.52.1",
|
||||||
"cron-parser": "^4.9.0",
|
"cron-parser": "^4.9.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
@@ -17,7 +21,8 @@
|
|||||||
"openai": "4.61.0",
|
"openai": "4.61.0",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"timezones-list": "^3.0.2",
|
"timezones-list": "^3.0.2",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21",
|
||||||
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
|
@@ -12,6 +12,72 @@ export interface ResponseType<T = any> {
|
|||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProcessedError {
|
||||||
|
code: number;
|
||||||
|
statusText: string;
|
||||||
|
message: string;
|
||||||
|
data?: any;
|
||||||
|
shouldClearCookie: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用错误处理函数,提取错误信息并分类记录日志
|
||||||
|
* @param params - 包含错误对象、URL和默认状态码的参数
|
||||||
|
* @returns 处理后的错误对象
|
||||||
|
*/
|
||||||
|
export function processError(params: {
|
||||||
|
error: any;
|
||||||
|
url?: string;
|
||||||
|
defaultCode?: number;
|
||||||
|
}): ProcessedError {
|
||||||
|
const { error, url, defaultCode = 500 } = params;
|
||||||
|
|
||||||
|
const errResponseKey = typeof error === 'string' ? error : error?.message;
|
||||||
|
|
||||||
|
// 1. 处理特定的业务错误(ERROR_RESPONSE)
|
||||||
|
if (ERROR_RESPONSE[errResponseKey]) {
|
||||||
|
const shouldClearCookie = errResponseKey === ERROR_ENUM.unAuthorization;
|
||||||
|
|
||||||
|
// 记录业务侧错误日志
|
||||||
|
addLog.info(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: ERROR_RESPONSE[errResponseKey].code || defaultCode,
|
||||||
|
statusText: ERROR_RESPONSE[errResponseKey].statusText || 'error',
|
||||||
|
message: ERROR_RESPONSE[errResponseKey].message,
|
||||||
|
data: ERROR_RESPONSE[errResponseKey].data,
|
||||||
|
shouldClearCookie
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 提取通用错误消息
|
||||||
|
let msg = error?.response?.statusText || error?.message || '请求错误';
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
msg = error;
|
||||||
|
} else if (proxyError[error?.code]) {
|
||||||
|
msg = '网络连接异常';
|
||||||
|
} else if (error?.response?.data?.error?.message) {
|
||||||
|
msg = error?.response?.data?.error?.message;
|
||||||
|
} else if (error?.error?.message) {
|
||||||
|
msg = error?.error?.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 根据错误类型记录不同级别的日志
|
||||||
|
if (error instanceof UserError) {
|
||||||
|
addLog.info(`Request error: ${url}, ${msg}`);
|
||||||
|
} else {
|
||||||
|
addLog.error(`System unexpected error: ${url}, ${msg}`, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 返回处理后的错误信息
|
||||||
|
return {
|
||||||
|
code: defaultCode,
|
||||||
|
statusText: 'error',
|
||||||
|
message: replaceSensitiveText(msg),
|
||||||
|
shouldClearCookie: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const jsonRes = <T = any>(
|
export const jsonRes = <T = any>(
|
||||||
res: NextApiResponse,
|
res: NextApiResponse,
|
||||||
props?: {
|
props?: {
|
||||||
@@ -24,53 +90,37 @@ export const jsonRes = <T = any>(
|
|||||||
) => {
|
) => {
|
||||||
const { code = 200, message = '', data = null, error, url } = props || {};
|
const { code = 200, message = '', data = null, error, url } = props || {};
|
||||||
|
|
||||||
const errResponseKey = typeof error === 'string' ? error : error?.message;
|
// 如果有错误,使用统一的错误处理逻辑
|
||||||
// Specified error
|
if (error) {
|
||||||
if (ERROR_RESPONSE[errResponseKey]) {
|
const processedError = processError({ error, url, defaultCode: code });
|
||||||
// login is expired
|
|
||||||
if (errResponseKey === ERROR_ENUM.unAuthorization) {
|
// 如果需要清除 cookie
|
||||||
|
if (processedError.shouldClearCookie) {
|
||||||
clearCookie(res);
|
clearCookie(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bussiness Side Error
|
res.status(processedError.code);
|
||||||
addLog.info(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]);
|
|
||||||
|
|
||||||
res.status(code);
|
|
||||||
|
|
||||||
|
// 如果有自定义消息,直接发送
|
||||||
if (message) {
|
if (message) {
|
||||||
res.send(message);
|
res.send(message);
|
||||||
} else {
|
} else {
|
||||||
res.json(ERROR_RESPONSE[errResponseKey]);
|
res.json({
|
||||||
|
code: processedError.code,
|
||||||
|
statusText: processedError.statusText,
|
||||||
|
message: processedError.message,
|
||||||
|
data: processedError.data !== undefined ? processedError.data : null
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// another error
|
// 成功响应
|
||||||
let msg = '';
|
|
||||||
if ((code < 200 || code >= 400) && !message) {
|
|
||||||
msg = error?.response?.statusText || error?.message || '请求错误';
|
|
||||||
if (typeof error === 'string') {
|
|
||||||
msg = error;
|
|
||||||
} else if (proxyError[error?.code]) {
|
|
||||||
msg = '网络连接异常';
|
|
||||||
} else if (error?.response?.data?.error?.message) {
|
|
||||||
msg = error?.response?.data?.error?.message;
|
|
||||||
} else if (error?.error?.message) {
|
|
||||||
msg = error?.error?.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof UserError) {
|
|
||||||
addLog.info(`Request error: ${url}, ${msg}`);
|
|
||||||
} else {
|
|
||||||
addLog.error(`System unexpected error: ${url}, ${msg}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(code).json({
|
res.status(code).json({
|
||||||
code,
|
code,
|
||||||
statusText: '',
|
statusText: '',
|
||||||
message: replaceSensitiveText(message || msg),
|
message: replaceSensitiveText(message),
|
||||||
data: data !== undefined ? data : null
|
data: data !== undefined ? data : null
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||||
import { type ChatFavouriteAppSchema as ChatFavouriteAppType } from '@fastgpt/global/core/chat/favouriteApp/type';
|
import { type ChatFavouriteAppType } from '@fastgpt/global/core/chat/favouriteApp/type';
|
||||||
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
||||||
import { AppCollectionName } from '../../app/schema';
|
import { AppCollectionName } from '../../app/schema';
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||||
import { type ChatSettingSchema as ChatSettingType } from '@fastgpt/global/core/chat/setting/type';
|
import { type ChatSettingType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
||||||
import { AppCollectionName } from '../../app/schema';
|
import { AppCollectionName } from '../../app/schema';
|
||||||
|
|
||||||
|
1745
pnpm-lock.yaml
generated
1745
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,11 @@
|
|||||||
"@fortaine/fetch-event-source": "^3.0.6",
|
"@fortaine/fetch-event-source": "^3.0.6",
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@node-rs/jieba": "2.0.1",
|
"@node-rs/jieba": "2.0.1",
|
||||||
|
"@scalar/api-reference-react": "0.7.48",
|
||||||
"@tanstack/react-query": "^4.24.10",
|
"@tanstack/react-query": "^4.24.10",
|
||||||
|
"@ts-rest/core": "^3.52.1",
|
||||||
|
"@ts-rest/next": "^3.52.1",
|
||||||
|
"@ts-rest/open-api": "^3.52.1",
|
||||||
"ahooks": "^3.7.11",
|
"ahooks": "^3.7.11",
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
|
@@ -24,7 +24,7 @@ import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
|||||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
import type { ChatTypeEnum } from './constants';
|
import type { ChatTypeEnum } from './constants';
|
||||||
import type { QuickAppType } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatQuickAppType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
|
|
||||||
export type ChatProviderProps = {
|
export type ChatProviderProps = {
|
||||||
appId: string;
|
appId: string;
|
||||||
@@ -39,7 +39,7 @@ export type ChatProviderProps = {
|
|||||||
slogan?: string;
|
slogan?: string;
|
||||||
|
|
||||||
currentQuickAppId?: string;
|
currentQuickAppId?: string;
|
||||||
quickAppList?: QuickAppType[];
|
quickAppList?: ChatQuickAppType[];
|
||||||
onSwitchQuickApp?: (appId: string) => Promise<void>;
|
onSwitchQuickApp?: (appId: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -31,7 +31,8 @@ import {
|
|||||||
} from '@/pageComponents/chat/constants';
|
} from '@/pageComponents/chat/constants';
|
||||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import type { ChatSettingReturnType } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatSettingResponseType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
|
|
||||||
import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants';
|
import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants';
|
||||||
|
|
||||||
const ChatHeader = ({
|
const ChatHeader = ({
|
||||||
@@ -44,7 +45,7 @@ const ChatHeader = ({
|
|||||||
chatSettings
|
chatSettings
|
||||||
}: {
|
}: {
|
||||||
pane: ChatSidebarPaneEnum;
|
pane: ChatSidebarPaneEnum;
|
||||||
chatSettings?: ChatSettingReturnType;
|
chatSettings?: ChatSettingResponseType;
|
||||||
|
|
||||||
history: ChatItemType[];
|
history: ChatItemType[];
|
||||||
showHistory?: boolean;
|
showHistory?: boolean;
|
||||||
|
@@ -18,12 +18,12 @@ import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import type { ChatFavouriteTagType } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatFavouriteTagType } from '@fastgpt/global/core/chat/favouriteApp/type';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
import { getFavouriteApps, updateChatSetting, updateFavouriteAppTags } from '@/web/core/chat/api';
|
import { getFavouriteApps, updateChatSetting, updateFavouriteAppTags } from '@/web/core/chat/api';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import type { ChatFavouriteApp } from '@fastgpt/global/core/chat/favouriteApp/type';
|
import type { ChatFavouriteAppResponseItemType } from '@fastgpt/global/core/chat/favouriteApp/type';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||||
@@ -211,7 +211,9 @@ const SaveTagForAppSubPanel = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const [localAllFavourites, setLocalAllFavourites] = useState<ChatFavouriteApp[]>([]);
|
const [localAllFavourites, setLocalAllFavourites] = useState<ChatFavouriteAppResponseItemType[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalAllFavourites(favouriteApps);
|
setLocalAllFavourites(favouriteApps);
|
||||||
|
@@ -29,10 +29,10 @@ import { deleteFavouriteApp, getFavouriteApps, updateFavouriteAppOrder } from '@
|
|||||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import { Box, Wrap } from '@chakra-ui/react';
|
import { Box, Wrap } from '@chakra-ui/react';
|
||||||
import type { ChatFavouriteApp } from '@fastgpt/global/core/chat/favouriteApp/type';
|
import type { ChatFavouriteAppResponseItemType } from '@fastgpt/global/core/chat/favouriteApp/type';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||||
import type { ChatFavouriteTagType } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatFavouriteTagType } from '@fastgpt/global/core/chat/favouriteApp/type';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
||||||
@@ -86,7 +86,7 @@ const FavouriteAppSetting = ({ Header }: Props) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [localFavourites, setLocalFavourites] = useState<ChatFavouriteApp[]>([]);
|
const [localFavourites, setLocalFavourites] = useState<ChatFavouriteAppResponseItemType[]>([]);
|
||||||
|
|
||||||
// search favourite apps by apps' name and tag
|
// search favourite apps by apps' name and tag
|
||||||
const { loading: isSearching, runAsync: getApps } = useRequest2(
|
const { loading: isSearching, runAsync: getApps } = useRequest2(
|
||||||
@@ -107,7 +107,7 @@ const FavouriteAppSetting = ({ Header }: Props) => {
|
|||||||
|
|
||||||
// update app order
|
// update app order
|
||||||
const { runAsync: orderApp } = useRequest2(
|
const { runAsync: orderApp } = useRequest2(
|
||||||
async (list: ChatFavouriteApp[]) => {
|
async (list: ChatFavouriteAppResponseItemType[]) => {
|
||||||
await updateFavouriteAppOrder(
|
await updateFavouriteAppOrder(
|
||||||
list.map((item, idx) => ({
|
list.map((item, idx) => ({
|
||||||
id: item._id,
|
id: item._id,
|
||||||
@@ -122,7 +122,7 @@ const FavouriteAppSetting = ({ Header }: Props) => {
|
|||||||
// delete app
|
// delete app
|
||||||
const { runAsync: deleteApp } = useRequest2(
|
const { runAsync: deleteApp } = useRequest2(
|
||||||
async (id: string) => {
|
async (id: string) => {
|
||||||
await deleteFavouriteApp(id);
|
await deleteFavouriteApp({ id });
|
||||||
getApps();
|
getApps();
|
||||||
},
|
},
|
||||||
{ manual: true }
|
{ manual: true }
|
||||||
@@ -252,7 +252,7 @@ const FavouriteAppSetting = ({ Header }: Props) => {
|
|||||||
{/* 表格内容 */}
|
{/* 表格内容 */}
|
||||||
<Box overflow={'auto'} flex="1 0 0" h={0} px={[2, 0]}>
|
<Box overflow={'auto'} flex="1 0 0" h={0} px={[2, 0]}>
|
||||||
{localFavourites.length > 0 ? (
|
{localFavourites.length > 0 ? (
|
||||||
<DndDrag<ChatFavouriteApp>
|
<DndDrag<ChatFavouriteAppResponseItemType>
|
||||||
dataList={localFavourites}
|
dataList={localFavourites}
|
||||||
renderInnerPlaceholder={false}
|
renderInnerPlaceholder={false}
|
||||||
onDragEndCb={(list) => {
|
onDragEndCb={(list) => {
|
||||||
|
@@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { App } from '@/pageComponents/chat/ChatSetting/AppTree';
|
import type { App } from '@/pageComponents/chat/ChatSetting/AppTree';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import type { QuickAppType } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatQuickAppType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
@@ -20,7 +20,7 @@ import { ChevronRightIcon } from '@chakra-ui/icons';
|
|||||||
type Props = {
|
type Props = {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm: (list: QuickAppType[]) => void;
|
onConfirm: (list: ChatQuickAppType[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
||||||
@@ -28,7 +28,7 @@ const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
|||||||
|
|
||||||
const [localSelectedIds, setLocalSelectedIds] = useState<string[]>(selectedIds);
|
const [localSelectedIds, setLocalSelectedIds] = useState<string[]>(selectedIds);
|
||||||
|
|
||||||
const [selectedInfo, setSelectedInfo] = useState<Record<string, QuickAppType>>({});
|
const [selectedInfo, setSelectedInfo] = useState<Record<string, ChatQuickAppType>>({});
|
||||||
|
|
||||||
const { watch, setValue } = useForm<{ name: string }>({
|
const { watch, setValue } = useForm<{ name: string }>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -78,7 +78,7 @@ const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
|||||||
if (exists) {
|
if (exists) {
|
||||||
// remove id and its cached info
|
// remove id and its cached info
|
||||||
setSelectedInfo((old) => {
|
setSelectedInfo((old) => {
|
||||||
const next: Record<string, QuickAppType> = { ...old };
|
const next: Record<string, ChatQuickAppType> = { ...old };
|
||||||
delete next[id];
|
delete next[id];
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
@@ -99,7 +99,7 @@ const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
|||||||
[availableAppsMap]
|
[availableAppsMap]
|
||||||
);
|
);
|
||||||
|
|
||||||
const checkedQuickApps = useMemo<QuickAppType[]>(() => {
|
const checkedQuickApps = useMemo<ChatQuickAppType[]>(() => {
|
||||||
return localSelectedIds
|
return localSelectedIds
|
||||||
.map((id) => {
|
.map((id) => {
|
||||||
const cached = selectedInfo[id];
|
const cached = selectedInfo[id];
|
||||||
@@ -108,7 +108,7 @@ const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
|||||||
const app = availableAppsMap.get(id);
|
const app = availableAppsMap.get(id);
|
||||||
if (app) return { _id: app._id, name: app.name, avatar: app.avatar };
|
if (app) return { _id: app._id, name: app.name, avatar: app.avatar };
|
||||||
})
|
})
|
||||||
.filter(Boolean) as QuickAppType[];
|
.filter(Boolean) as ChatQuickAppType[];
|
||||||
}, [localSelectedIds, selectedInfo, availableAppsMap]);
|
}, [localSelectedIds, selectedInfo, availableAppsMap]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -117,7 +117,7 @@ const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
|||||||
getAppBasicInfoByIds(missing)
|
getAppBasicInfoByIds(missing)
|
||||||
.then((list) => {
|
.then((list) => {
|
||||||
setSelectedInfo((old) => {
|
setSelectedInfo((old) => {
|
||||||
const next: Record<string, QuickAppType> = { ...old };
|
const next: Record<string, ChatQuickAppType> = { ...old };
|
||||||
list.forEach((item) => {
|
list.forEach((item) => {
|
||||||
next[item.id] = { _id: item.id, name: item.name, avatar: item.avatar };
|
next[item.id] = { _id: item.id, name: item.name, avatar: item.avatar };
|
||||||
});
|
});
|
||||||
@@ -306,7 +306,7 @@ const AddQuickAppModal = ({ selectedIds, onClose, onConfirm }: Props) => {
|
|||||||
{checkedQuickApps.length === 0 && !isFetching && (
|
{checkedQuickApps.length === 0 && !isFetching && (
|
||||||
<EmptyTip text={t('chat:setting.home.no_selected_app')} />
|
<EmptyTip text={t('chat:setting.home.no_selected_app')} />
|
||||||
)}
|
)}
|
||||||
<DndDrag<QuickAppType>
|
<DndDrag<ChatQuickAppType>
|
||||||
dataList={checkedQuickApps}
|
dataList={checkedQuickApps}
|
||||||
renderInnerPlaceholder={false}
|
renderInnerPlaceholder={false}
|
||||||
onDragEndCb={(list) => {
|
onDragEndCb={(list) => {
|
||||||
|
@@ -17,10 +17,10 @@ import { updateChatSetting } from '@/web/core/chat/api';
|
|||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import ImageUpload from '@/pageComponents/chat/ChatSetting/ImageUpload';
|
import ImageUpload from '@/pageComponents/chat/ChatSetting/ImageUpload';
|
||||||
import type {
|
import type {
|
||||||
ChatSettingUpdateParams,
|
ChatQuickAppType,
|
||||||
QuickAppType,
|
ChatSelectedToolType
|
||||||
SelectedToolType
|
|
||||||
} from '@fastgpt/global/core/chat/setting/type';
|
} from '@fastgpt/global/core/chat/setting/type';
|
||||||
|
import type { ChatSettingUpdateType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import ToolSelectModal from '@/pageComponents/chat/ChatSetting/ToolSelectModal';
|
import ToolSelectModal from '@/pageComponents/chat/ChatSetting/ToolSelectModal';
|
||||||
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
|
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
@@ -35,8 +35,7 @@ import {
|
|||||||
} from '@/pageComponents/chat/constants';
|
} from '@/pageComponents/chat/constants';
|
||||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import type { ChatSettingReturnType } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatSettingResponseType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
|
||||||
|
|
||||||
const AddQuickAppModal = dynamic(
|
const AddQuickAppModal = dynamic(
|
||||||
() => import('@/pageComponents/chat/ChatSetting/HomepageSetting/AddQuickAppModal')
|
() => import('@/pageComponents/chat/ChatSetting/HomepageSetting/AddQuickAppModal')
|
||||||
@@ -47,9 +46,9 @@ type Props = {
|
|||||||
onDiagramShow: (show: boolean) => void;
|
onDiagramShow: (show: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormValues = Omit<ChatSettingUpdateParams, 'selectedTools' | 'quickAppIds'> & {
|
type FormValues = Omit<ChatSettingUpdateType, 'selectedTools' | 'quickAppIds'> & {
|
||||||
selectedTools: SelectedToolType[];
|
selectedTools: ChatSelectedToolType[];
|
||||||
quickAppList: QuickAppType[];
|
quickAppList: ChatQuickAppType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const HomepageSetting = ({ Header, onDiagramShow }: Props) => {
|
const HomepageSetting = ({ Header, onDiagramShow }: Props) => {
|
||||||
@@ -61,7 +60,7 @@ const HomepageSetting = ({ Header, onDiagramShow }: Props) => {
|
|||||||
const refreshChatSetting = useContextSelector(ChatSettingContext, (v) => v.refreshChatSetting);
|
const refreshChatSetting = useContextSelector(ChatSettingContext, (v) => v.refreshChatSetting);
|
||||||
|
|
||||||
const chatSettings2Form = useCallback(
|
const chatSettings2Form = useCallback(
|
||||||
(data?: ChatSettingReturnType) => {
|
(data?: ChatSettingResponseType) => {
|
||||||
return {
|
return {
|
||||||
enableHome: data?.enableHome,
|
enableHome: data?.enableHome,
|
||||||
slogan: data?.slogan || t('chat:setting.home.slogan.default'),
|
slogan: data?.slogan || t('chat:setting.home.slogan.default'),
|
||||||
|
@@ -44,12 +44,12 @@ import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
|||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { workflowStartNodeId } from '@/web/core/app/constants';
|
import { workflowStartNodeId } from '@/web/core/app/constants';
|
||||||
import ConfigToolModal from '@/pageComponents/app/detail/SimpleApp/components/ConfigToolModal';
|
import ConfigToolModal from '@/pageComponents/app/detail/SimpleApp/components/ConfigToolModal';
|
||||||
import type { ChatSettingSchema } from '@fastgpt/global/core/chat/setting/type';
|
import type { ChatSettingResponseType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
import CostTooltip from '@/components/core/app/plugin/CostTooltip';
|
import CostTooltip from '@/components/core/app/plugin/CostTooltip';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedTools: ChatSettingSchema['selectedTools'];
|
selectedTools: ChatSettingResponseType['selectedTools'];
|
||||||
chatConfig?: AppSimpleEditFormType['chatConfig'];
|
chatConfig?: AppSimpleEditFormType['chatConfig'];
|
||||||
onAddTool: (tool: FlowNodeTemplateType) => void;
|
onAddTool: (tool: FlowNodeTemplateType) => void;
|
||||||
onRemoveTool: (tool: NodeTemplateListItemType) => void;
|
onRemoveTool: (tool: NodeTemplateListItemType) => void;
|
||||||
|
@@ -47,7 +47,6 @@ import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
|||||||
import { ChatSidebarPaneEnum } from '../constants';
|
import { ChatSidebarPaneEnum } from '../constants';
|
||||||
import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar';
|
import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar';
|
||||||
import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer';
|
import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer';
|
||||||
import type { QuickAppType } from '@fastgpt/global/core/chat/setting/type';
|
|
||||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import '@scalar/api-reference-react/style.css';
|
||||||
|
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
|
|
||||||
@@ -48,6 +50,21 @@ function App({ Component, pageProps }: AppPropsWithLayout) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const showHead = !router?.pathname || !routesWithCustomHead.includes(router.pathname);
|
const showHead = !router?.pathname || !routesWithCustomHead.includes(router.pathname);
|
||||||
|
|
||||||
|
if (router.pathname === '/openapi') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showHead && (
|
||||||
|
<NextHead
|
||||||
|
title={title}
|
||||||
|
desc={process.env.SYSTEM_DESCRIPTION || t('common:system_intro', { title })}
|
||||||
|
icon={getWebReqUrl(feConfigs?.favicon || process.env.SYSTEM_FAVICON)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{setLayout(<Component {...pageProps} />)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showHead && (
|
{showHead && (
|
||||||
|
17
projects/app/src/pages/openapi/index.tsx
Normal file
17
projects/app/src/pages/openapi/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import NextHead from '@/components/common/NextHead';
|
||||||
|
import { contract } from '@fastgpt/global/common/tsRest/fastgpt/contracts';
|
||||||
|
import { generateOpenApiDocument } from '@fastgpt/global/common/tsRest/openapi';
|
||||||
|
import { ApiReferenceReact } from '@scalar/api-reference-react';
|
||||||
|
|
||||||
|
export default function OpenAPI() {
|
||||||
|
const config = {
|
||||||
|
content: generateOpenApiDocument(contract)
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NextHead title="OpenAPI" />
|
||||||
|
<ApiReferenceReact configuration={config} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,5 +1,64 @@
|
|||||||
import { NextEntry } from '@fastgpt/service/common/middle/entry';
|
import { NextEntry } from '@fastgpt/service/common/middle/entry';
|
||||||
|
import type { AppRoute, Args, Handler, Endpoint } from '@fastgpt/global/common/tsRest/type';
|
||||||
|
import { addLog } from '@fastgpt/service/common/system/log';
|
||||||
|
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||||
|
import { processError } from '@fastgpt/service/common/response';
|
||||||
|
import { clearCookie } from '@fastgpt/service/support/permission/auth/common';
|
||||||
|
|
||||||
export const NextAPI = NextEntry({
|
export const NextAPI = NextEntry({ beforeCallback: [] });
|
||||||
beforeCallback: []
|
|
||||||
});
|
export function RestAPI<T extends AppRoute>(handler: Handler<T>): Endpoint<T> {
|
||||||
|
return (async (args: Args<T>) => {
|
||||||
|
const start = Date.now();
|
||||||
|
const url = `${args.req?.method || 'Unknown'} ${args.req?.url || 'Unknown'}`;
|
||||||
|
addLog.debug(`RestAPI Request start ${url}`);
|
||||||
|
|
||||||
|
await Promise.all([withNextCors(args.req, args.res)]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = await handler(args);
|
||||||
|
|
||||||
|
// Get request duration
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
if (duration < 2000) {
|
||||||
|
addLog.debug(`RestAPI Request finish ${url}, time: ${duration}ms`);
|
||||||
|
} else {
|
||||||
|
addLog.warn(`RestAPI Request finish ${url}, time: ${duration}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200 as const,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: body,
|
||||||
|
statusText: 'success'
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
} catch (error) {
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
|
||||||
|
// 使用统一的错误处理逻辑
|
||||||
|
const processedError = processError({
|
||||||
|
error,
|
||||||
|
url: `${url} (${duration}ms)`,
|
||||||
|
defaultCode: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果需要清除 cookie
|
||||||
|
if (processedError.shouldClearCookie) {
|
||||||
|
clearCookie(args.res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200 as const,
|
||||||
|
body: {
|
||||||
|
code: processedError.code,
|
||||||
|
statusText: processedError.statusText,
|
||||||
|
message: processedError.message,
|
||||||
|
data: processedError.data
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
}) as unknown as Endpoint<T>;
|
||||||
|
}
|
||||||
|
@@ -1,13 +1,5 @@
|
|||||||
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
|
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
|
||||||
import type { ChatHistoryItemType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
import type { ChatHistoryItemType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||||
import type {
|
|
||||||
ChatFavouriteTagType,
|
|
||||||
ChatSettingSchema,
|
|
||||||
ChatSettingUpdateParams,
|
|
||||||
QuickAppType,
|
|
||||||
SelectedToolType,
|
|
||||||
ChatSettingReturnType
|
|
||||||
} from '@fastgpt/global/core/chat/setting/type';
|
|
||||||
import type { getResDataQuery } from '@/pages/api/core/chat/getResData';
|
import type { getResDataQuery } from '@/pages/api/core/chat/getResData';
|
||||||
import type {
|
import type {
|
||||||
CloseCustomFeedbackParams,
|
CloseCustomFeedbackParams,
|
||||||
@@ -38,11 +30,7 @@ import type {
|
|||||||
GetCollectionQuoteProps,
|
GetCollectionQuoteProps,
|
||||||
GetCollectionQuoteRes
|
GetCollectionQuoteRes
|
||||||
} from '@/pages/api/core/chat/quote/getCollectionQuote';
|
} from '@/pages/api/core/chat/quote/getCollectionQuote';
|
||||||
import type {
|
import { RestAPI, client } from '@fastgpt/global/common/tsRest/fastgpt/client';
|
||||||
ChatFavouriteAppUpdateParams,
|
|
||||||
ChatFavouriteAppSchema,
|
|
||||||
ChatFavouriteApp
|
|
||||||
} from '@fastgpt/global/core/chat/favouriteApp/type';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取初始化聊天内容
|
* 获取初始化聊天内容
|
||||||
@@ -121,30 +109,26 @@ export const getCollectionQuote = (data: GetCollectionQuoteProps) =>
|
|||||||
POST<GetCollectionQuoteRes>(`/core/chat/quote/getCollectionQuote`, data);
|
POST<GetCollectionQuoteRes>(`/core/chat/quote/getCollectionQuote`, data);
|
||||||
|
|
||||||
/*---------- chat setting ------------*/
|
/*---------- chat setting ------------*/
|
||||||
export const getChatSetting = () => {
|
export const getChatSetting = RestAPI(client.chat.setting.detail);
|
||||||
return GET<ChatSettingReturnType>('/proApi/core/chat/setting/detail');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateChatSetting = (data: ChatSettingUpdateParams) => {
|
export const updateChatSetting = RestAPI(client.chat.setting.update, (params) => ({
|
||||||
return POST<ChatSettingSchema>('/proApi/core/chat/setting/update', data);
|
body: params
|
||||||
};
|
}));
|
||||||
|
|
||||||
export const getFavouriteApps = (data?: { name?: string; tag?: string }) => {
|
export const getFavouriteApps = RestAPI(client.chat.setting.favourite.list);
|
||||||
return GET<ChatFavouriteApp[]>('/proApi/core/chat/setting/favourite/list', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateFavouriteApps = (data: ChatFavouriteAppUpdateParams[]) => {
|
export const updateFavouriteApps = RestAPI(client.chat.setting.favourite.update, (params) => ({
|
||||||
return POST<ChatFavouriteAppSchema[]>('/proApi/core/chat/setting/favourite/update', data);
|
body: params
|
||||||
};
|
}));
|
||||||
|
|
||||||
export const updateFavouriteAppOrder = (data: { id: string; order: number }[]) => {
|
export const updateFavouriteAppOrder = RestAPI(client.chat.setting.favourite.order, (params) => ({
|
||||||
return PUT<void>('/proApi/core/chat/setting/favourite/order', data);
|
body: params
|
||||||
};
|
}));
|
||||||
|
|
||||||
export const updateFavouriteAppTags = (data: { id: string; tags: string[] }[]) => {
|
export const updateFavouriteAppTags = RestAPI(client.chat.setting.favourite.tags, (params) => ({
|
||||||
return PUT<void>('/proApi/core/chat/setting/favourite/tags', data);
|
body: params
|
||||||
};
|
}));
|
||||||
|
|
||||||
export const deleteFavouriteApp = (id: string) => {
|
export const deleteFavouriteApp = RestAPI(client.chat.setting.favourite.delete, (params) => ({
|
||||||
return DELETE<void>(`/proApi/core/chat/setting/favourite/delete?id=${id}`);
|
query: params
|
||||||
};
|
}));
|
||||||
|
@@ -7,10 +7,7 @@ import {
|
|||||||
} from '@/pageComponents/chat/constants';
|
} from '@/pageComponents/chat/constants';
|
||||||
import { getChatSetting } from '@/web/core/chat/api';
|
import { getChatSetting } from '@/web/core/chat/api';
|
||||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||||
import type {
|
import type { ChatSettingResponseType } from '@fastgpt/global/core/chat/setting/type';
|
||||||
ChatSettingReturnType,
|
|
||||||
ChatSettingSchema
|
|
||||||
} from '@fastgpt/global/core/chat/setting/type';
|
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
@@ -25,9 +22,9 @@ export type ChatSettingContextValue = {
|
|||||||
) => void;
|
) => void;
|
||||||
collapse: CollapseStatusType;
|
collapse: CollapseStatusType;
|
||||||
onTriggerCollapse: () => void;
|
onTriggerCollapse: () => void;
|
||||||
chatSettings?: ChatSettingReturnType;
|
chatSettings: ChatSettingResponseType | undefined;
|
||||||
refreshChatSetting: () => Promise<ChatSettingReturnType>;
|
refreshChatSetting: () => Promise<ChatSettingResponseType | undefined>;
|
||||||
logos: Pick<ChatSettingSchema, 'wideLogoUrl' | 'squareLogoUrl'>;
|
logos: { wideLogoUrl?: string; squareLogoUrl?: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChatSettingContext = createContext<ChatSettingContextValue>({
|
export const ChatSettingContext = createContext<ChatSettingContextValue>({
|
||||||
@@ -36,12 +33,9 @@ export const ChatSettingContext = createContext<ChatSettingContextValue>({
|
|||||||
collapse: defaultCollapseStatus,
|
collapse: defaultCollapseStatus,
|
||||||
onTriggerCollapse: () => {},
|
onTriggerCollapse: () => {},
|
||||||
chatSettings: undefined,
|
chatSettings: undefined,
|
||||||
refreshChatSetting: function (): Promise<ChatSettingReturnType> {
|
logos: { wideLogoUrl: '', squareLogoUrl: '' },
|
||||||
|
refreshChatSetting: function (): Promise<ChatSettingResponseType | undefined> {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
|
||||||
logos: {
|
|
||||||
wideLogoUrl: '',
|
|
||||||
squareLogoUrl: ''
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,7 +113,7 @@ export const ChatSettingContextProvider = ({ children }: { children: React.React
|
|||||||
}
|
}
|
||||||
}, [pane, handlePaneChange]);
|
}, [pane, handlePaneChange]);
|
||||||
|
|
||||||
const logos: Pick<ChatSettingSchema, 'wideLogoUrl' | 'squareLogoUrl'> = useMemo(
|
const logos: Pick<ChatSettingResponseType, 'wideLogoUrl' | 'squareLogoUrl'> = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
wideLogoUrl: chatSettings?.wideLogoUrl,
|
wideLogoUrl: chatSettings?.wideLogoUrl,
|
||||||
squareLogoUrl: chatSettings?.squareLogoUrl
|
squareLogoUrl: chatSettings?.squareLogoUrl
|
||||||
|
Reference in New Issue
Block a user