@purpleschool/multisite 0.0.35 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/controller/http/catalog.ts +6 -0
- package/api/controller/http/file.ts +1 -0
- package/api/controller/http/index.ts +2 -0
- package/api/controller/http/text-chat.ts +18 -0
- package/build/api/controller/http/catalog.d.ts +7 -0
- package/build/api/controller/http/catalog.d.ts.map +1 -0
- package/build/api/controller/http/catalog.js +9 -0
- package/build/api/controller/http/file.d.ts +1 -0
- package/build/api/controller/http/file.d.ts.map +1 -1
- package/build/api/controller/http/file.js +1 -0
- package/build/api/controller/http/index.d.ts +2 -0
- package/build/api/controller/http/index.d.ts.map +1 -1
- package/build/api/controller/http/index.js +2 -0
- package/build/api/controller/http/text-chat.d.ts +18 -0
- package/build/api/controller/http/text-chat.d.ts.map +1 -0
- package/build/api/controller/http/text-chat.js +20 -0
- package/build/commands/auth/verify-email.command.d.ts.map +1 -1
- package/build/commands/auth/verify-email.command.js +2 -1
- package/build/commands/catalog/index.d.ts +156 -0
- package/build/commands/catalog/index.d.ts.map +1 -0
- package/build/commands/catalog/index.js +23 -0
- package/build/commands/index.d.ts +2 -0
- package/build/commands/index.d.ts.map +1 -1
- package/build/commands/index.js +2 -0
- package/build/commands/page/create-page.command.d.ts +8 -0
- package/build/commands/page/create-page.command.d.ts.map +1 -1
- package/build/commands/page/find-page-by-alias.command.d.ts +16 -0
- package/build/commands/page/find-page-by-alias.command.d.ts.map +1 -1
- package/build/commands/page/find-page-by-criteria.command.d.ts +5 -0
- package/build/commands/page/find-page-by-criteria.command.d.ts.map +1 -1
- package/build/commands/page/find-page.command.d.ts +11 -0
- package/build/commands/page/find-page.command.d.ts.map +1 -1
- package/build/commands/page/update-page.command.d.ts +8 -0
- package/build/commands/page/update-page.command.d.ts.map +1 -1
- package/build/commands/product/fast-buy-product.command.d.ts.map +1 -1
- package/build/commands/product/fast-buy-product.command.js +2 -1
- package/build/commands/subscription/fast-buy-subscription.command.d.ts.map +1 -1
- package/build/commands/subscription/fast-buy-subscription.command.js +2 -1
- package/build/commands/text-chat/category.d.ts +321 -0
- package/build/commands/text-chat/category.d.ts.map +1 -0
- package/build/commands/text-chat/category.js +29 -0
- package/build/commands/text-chat/index.d.ts +292 -0
- package/build/commands/text-chat/index.d.ts.map +1 -0
- package/build/commands/text-chat/index.js +73 -0
- package/build/constants/catalog.d.ts +5 -0
- package/build/constants/catalog.d.ts.map +1 -0
- package/build/constants/catalog.js +8 -0
- package/build/constants/errors/errors.d.ts +72 -0
- package/build/constants/errors/errors.d.ts.map +1 -1
- package/build/constants/errors/errors.js +72 -0
- package/build/constants/files/enums/file-type.enum.d.ts +3 -1
- package/build/constants/files/enums/file-type.enum.d.ts.map +1 -1
- package/build/constants/files/enums/file-type.enum.js +2 -0
- package/build/constants/index.d.ts +3 -0
- package/build/constants/index.d.ts.map +1 -1
- package/build/constants/index.js +3 -0
- package/build/constants/text-chat/attachments.d.ts +12 -0
- package/build/constants/text-chat/attachments.d.ts.map +1 -0
- package/build/constants/text-chat/attachments.js +52 -0
- package/build/constants/text-chat/index.d.ts +38 -0
- package/build/constants/text-chat/index.d.ts.map +1 -0
- package/build/constants/text-chat/index.js +59 -0
- package/build/constants/text-provider/index.d.ts +37 -0
- package/build/constants/text-provider/index.d.ts.map +1 -0
- package/build/constants/text-provider/index.js +46 -0
- package/build/constants/tools/enums/tool-type.enum.d.ts +1 -0
- package/build/constants/tools/enums/tool-type.enum.d.ts.map +1 -1
- package/build/constants/tools/enums/tool-type.enum.js +1 -0
- package/build/helpers/index.d.ts +1 -0
- package/build/helpers/index.d.ts.map +1 -1
- package/build/helpers/index.js +1 -0
- package/build/helpers/text-chat/index.d.ts +23 -0
- package/build/helpers/text-chat/index.d.ts.map +1 -0
- package/build/helpers/text-chat/index.js +91 -0
- package/build/models/catalog/index.d.ts +181 -0
- package/build/models/catalog/index.d.ts.map +1 -0
- package/build/models/catalog/index.js +36 -0
- package/build/models/index.d.ts +2 -0
- package/build/models/index.d.ts.map +1 -1
- package/build/models/index.js +2 -0
- package/build/models/page.schema.d.ts +6 -0
- package/build/models/page.schema.d.ts.map +1 -1
- package/build/models/page.schema.js +1 -0
- package/build/models/text-chat/category.schema.d.ts +79 -0
- package/build/models/text-chat/category.schema.d.ts.map +1 -0
- package/build/models/text-chat/category.schema.js +38 -0
- package/build/models/text-chat/index.d.ts +319 -0
- package/build/models/text-chat/index.d.ts.map +1 -0
- package/build/models/text-chat/index.js +79 -0
- package/build/models/text-chat/provider-config.schema.d.ts +92 -0
- package/build/models/text-chat/provider-config.schema.d.ts.map +1 -0
- package/build/models/text-chat/provider-config.schema.js +42 -0
- package/build/models/text-chat/stream.schema.d.ts +83 -0
- package/build/models/text-chat/stream.schema.d.ts.map +1 -0
- package/build/models/text-chat/stream.schema.js +24 -0
- package/build/models/user.schema.d.ts +1 -0
- package/build/models/user.schema.d.ts.map +1 -1
- package/build/models/user.schema.js +3 -2
- package/build/queries/catalog/index.d.ts +259 -0
- package/build/queries/catalog/index.d.ts.map +1 -0
- package/build/queries/catalog/index.js +31 -0
- package/build/queries/index.d.ts +2 -0
- package/build/queries/index.d.ts.map +1 -1
- package/build/queries/index.js +2 -0
- package/build/queries/text-chat/category.d.ts +297 -0
- package/build/queries/text-chat/category.d.ts.map +1 -0
- package/build/queries/text-chat/category.js +45 -0
- package/build/queries/text-chat/index.d.ts +472 -0
- package/build/queries/text-chat/index.d.ts.map +1 -0
- package/build/queries/text-chat/index.js +61 -0
- package/commands/auth/verify-email.command.ts +2 -1
- package/commands/catalog/index.ts +26 -0
- package/commands/index.ts +2 -0
- package/commands/product/fast-buy-product.command.ts +2 -1
- package/commands/subscription/fast-buy-subscription.command.ts +2 -1
- package/commands/text-chat/category.ts +42 -0
- package/commands/text-chat/index.ts +71 -0
- package/constants/catalog.ts +4 -0
- package/constants/errors/errors.ts +73 -0
- package/constants/files/enums/file-type.enum.ts +2 -0
- package/constants/index.ts +3 -0
- package/constants/text-chat/attachments.ts +55 -0
- package/constants/text-chat/index.ts +43 -0
- package/constants/text-provider/index.ts +42 -0
- package/constants/tools/enums/tool-type.enum.ts +1 -0
- package/helpers/index.ts +1 -0
- package/helpers/text-chat/index.ts +91 -0
- package/models/catalog/index.ts +35 -0
- package/models/index.ts +2 -0
- package/models/page.schema.ts +1 -0
- package/models/text-chat/category.schema.ts +40 -0
- package/models/text-chat/index.ts +82 -0
- package/models/text-chat/provider-config.schema.ts +47 -0
- package/models/text-chat/stream.schema.ts +43 -0
- package/models/user.schema.ts +3 -1
- package/package.json +1 -1
- package/queries/catalog/index.ts +29 -0
- package/queries/index.ts +2 -0
- package/queries/text-chat/category.ts +51 -0
- package/queries/text-chat/index.ts +56 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TextChatSchema, TextChatPriceSchema, TextChatFileSchema } from '../../models/text-chat';
|
|
3
|
+
export * from './category';
|
|
4
|
+
|
|
5
|
+
export namespace UploadTextChatFileCommand {
|
|
6
|
+
// Multipart field `file`. No owner, site, content or extraction metadata from the client.
|
|
7
|
+
export const ResponseSchema = z.object({ data: TextChatFileSchema });
|
|
8
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export namespace CreateTextChatCommand {
|
|
12
|
+
export const RequestSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
// Page.toolOptionId, supplied only when starting a specialized chat.
|
|
15
|
+
categoryId: z.string().uuid().nullable().optional(),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
19
|
+
export const ResponseSchema = z.object({ data: TextChatSchema });
|
|
20
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export namespace RenameTextChatCommand {
|
|
24
|
+
export const RequestParamsSchema = z.object({ uuid: z.string().uuid() });
|
|
25
|
+
export type RequestParams = z.infer<typeof RequestParamsSchema>;
|
|
26
|
+
export const RequestSchema = z.object({ title: z.string().trim().min(1).max(200) }).strict();
|
|
27
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
28
|
+
export const ResponseSchema = z.object({ data: TextChatSchema });
|
|
29
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export namespace DeleteTextChatCommand {
|
|
33
|
+
export const RequestParamsSchema = z.object({ uuid: z.string().uuid() });
|
|
34
|
+
export type RequestParams = z.infer<typeof RequestParamsSchema>;
|
|
35
|
+
export const ResponseSchema = z.object({ data: z.object({ deleted: z.literal(true) }) });
|
|
36
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export namespace SendTextChatMessageCommand {
|
|
40
|
+
export const RequestParamsSchema = z.object({ uuid: z.string().uuid() });
|
|
41
|
+
export type RequestParams = z.infer<typeof RequestParamsSchema>;
|
|
42
|
+
export const RequestSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
modelId: z.string().uuid(),
|
|
45
|
+
text: z.string().refine((text) => text.trim().length > 0, 'Message must not be blank'),
|
|
46
|
+
fileIds: z.array(z.string().uuid()).default([]),
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
|
49
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
50
|
+
// Response is a TextChatStreamFrame sequence, not a JSON response envelope.
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export namespace CalculateTextChatPriceCommand {
|
|
54
|
+
export const RequestSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
chatId: z.string().uuid().optional(),
|
|
57
|
+
// Only new chats accept a category; existing chats use their saved category.
|
|
58
|
+
categoryId: z.string().uuid().nullable().optional(),
|
|
59
|
+
modelId: z.string().uuid(),
|
|
60
|
+
inputLength: z.number().int().safe().nonnegative(),
|
|
61
|
+
fileIds: z.array(z.string().uuid()).default([]),
|
|
62
|
+
})
|
|
63
|
+
.strict()
|
|
64
|
+
.refine(
|
|
65
|
+
(request) => !request.chatId || request.categoryId === undefined,
|
|
66
|
+
'Existing chats use their saved category',
|
|
67
|
+
);
|
|
68
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
69
|
+
export const ResponseSchema = z.object({ data: TextChatPriceSchema });
|
|
70
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
71
|
+
}
|
|
@@ -1,4 +1,77 @@
|
|
|
1
|
+
import { TEXT_CHAT_ERROR_CODE } from '../text-chat';
|
|
2
|
+
import { CATALOG_ERROR_CODE } from '../catalog';
|
|
3
|
+
|
|
1
4
|
export const ERRORS = {
|
|
5
|
+
TEXT_CHAT_GENERATION_FAILED: {
|
|
6
|
+
message: 'Не удалось получить ответ нейросети. Попробуйте позже.',
|
|
7
|
+
code: TEXT_CHAT_ERROR_CODE.GENERATION_FAILED,
|
|
8
|
+
httpCode: 502,
|
|
9
|
+
},
|
|
10
|
+
TEXT_CHAT_STREAM_INTERRUPTED: {
|
|
11
|
+
message: 'Соединение прервано. Часть ответа могла сохраниться в истории.',
|
|
12
|
+
code: TEXT_CHAT_ERROR_CODE.STREAM_INTERRUPTED,
|
|
13
|
+
httpCode: 502,
|
|
14
|
+
},
|
|
15
|
+
TEXT_CHAT_SETTLEMENT_FAILED: {
|
|
16
|
+
message: 'Не удалось завершить обработку ответа. Проверьте сохранённую историю.',
|
|
17
|
+
code: TEXT_CHAT_ERROR_CODE.SETTLEMENT_FAILED,
|
|
18
|
+
httpCode: 500,
|
|
19
|
+
},
|
|
20
|
+
CATALOG_GROUP_NOT_FOUND: {
|
|
21
|
+
message: 'Группа каталога не найдена',
|
|
22
|
+
code: CATALOG_ERROR_CODE.GROUP_NOT_FOUND,
|
|
23
|
+
httpCode: 404,
|
|
24
|
+
},
|
|
25
|
+
CATALOG_GROUP_IN_USE: {
|
|
26
|
+
message: 'Перенесите или отвяжите категории перед удалением группы',
|
|
27
|
+
code: CATALOG_ERROR_CODE.GROUP_IN_USE,
|
|
28
|
+
httpCode: 409,
|
|
29
|
+
},
|
|
30
|
+
TEXT_CHAT_CONTEXT_LIMIT: {
|
|
31
|
+
message: 'Превышен допустимый размер запроса или контекста',
|
|
32
|
+
code: TEXT_CHAT_ERROR_CODE.CONTEXT_LIMIT,
|
|
33
|
+
httpCode: 400,
|
|
34
|
+
},
|
|
35
|
+
TEXT_CHAT_INVALID_ATTACHMENT: {
|
|
36
|
+
message: 'Недопустимое вложение текстового чата',
|
|
37
|
+
code: TEXT_CHAT_ERROR_CODE.INVALID_ATTACHMENT,
|
|
38
|
+
httpCode: 400,
|
|
39
|
+
},
|
|
40
|
+
TEXT_CHAT_NOT_FOUND: {
|
|
41
|
+
message: 'Текстовый чат не найден',
|
|
42
|
+
code: TEXT_CHAT_ERROR_CODE.NOT_FOUND,
|
|
43
|
+
httpCode: 404,
|
|
44
|
+
},
|
|
45
|
+
TEXT_CHAT_BUSY: {
|
|
46
|
+
message: 'Дождитесь завершения ответа в этом чате',
|
|
47
|
+
code: TEXT_CHAT_ERROR_CODE.BUSY,
|
|
48
|
+
httpCode: 409,
|
|
49
|
+
},
|
|
50
|
+
TEXT_CHAT_CATEGORY_TOOL_MISMATCH: {
|
|
51
|
+
message: 'Инструмент категории не соответствует инструменту страницы',
|
|
52
|
+
code: TEXT_CHAT_ERROR_CODE.CATEGORY_TOOL_MISMATCH,
|
|
53
|
+
httpCode: 409,
|
|
54
|
+
},
|
|
55
|
+
TEXT_CHAT_CATEGORY_NOT_FOUND: {
|
|
56
|
+
message: 'Категория текстового чата не найдена',
|
|
57
|
+
code: TEXT_CHAT_ERROR_CODE.CATEGORY_NOT_FOUND,
|
|
58
|
+
httpCode: 404,
|
|
59
|
+
},
|
|
60
|
+
TEXT_CHAT_CATEGORY_UNAVAILABLE: {
|
|
61
|
+
message: 'Категория недоступна для нового чата',
|
|
62
|
+
code: TEXT_CHAT_ERROR_CODE.CATEGORY_UNAVAILABLE,
|
|
63
|
+
httpCode: 409,
|
|
64
|
+
},
|
|
65
|
+
TEXT_CHAT_CATEGORY_IN_USE: {
|
|
66
|
+
message: 'Категория используется чатами или SEO-страницами; её можно архивировать',
|
|
67
|
+
code: TEXT_CHAT_ERROR_CODE.CATEGORY_IN_USE,
|
|
68
|
+
httpCode: 409,
|
|
69
|
+
},
|
|
70
|
+
TEXT_CHAT_MODEL_UNAVAILABLE: {
|
|
71
|
+
message: 'Текстовая модель недоступна',
|
|
72
|
+
code: TEXT_CHAT_ERROR_CODE.MODEL_UNAVAILABLE,
|
|
73
|
+
httpCode: 403,
|
|
74
|
+
},
|
|
2
75
|
UNKNOWN_ERROR: {
|
|
3
76
|
message: 'Неизвестная ошибка',
|
|
4
77
|
code: 'UNKNOWN_001',
|
package/constants/index.ts
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FILE_TYPE } from '../files/enums/file-type.enum';
|
|
2
|
+
|
|
3
|
+
export const TEXT_CHAT_UPLOAD_LIMITS = {
|
|
4
|
+
[FILE_TYPE.IMAGE]: 5 * 1024 ** 2,
|
|
5
|
+
[FILE_TYPE.DOCUMENT]: 20 * 1024 ** 2,
|
|
6
|
+
[FILE_TYPE.TEXT]: 10 * 1024 ** 2,
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export const TEXT_CHAT_FILE_FORMATS: ReadonlyArray<{
|
|
10
|
+
extensions: readonly string[];
|
|
11
|
+
mimeType: string;
|
|
12
|
+
type: FILE_TYPE.IMAGE | FILE_TYPE.DOCUMENT | FILE_TYPE.TEXT;
|
|
13
|
+
}> = [
|
|
14
|
+
{ extensions: ['jpg', 'jpeg'], mimeType: 'image/jpeg', type: FILE_TYPE.IMAGE },
|
|
15
|
+
{ extensions: ['png'], mimeType: 'image/png', type: FILE_TYPE.IMAGE },
|
|
16
|
+
{ extensions: ['webp'], mimeType: 'image/webp', type: FILE_TYPE.IMAGE },
|
|
17
|
+
{ extensions: ['pdf'], mimeType: 'application/pdf', type: FILE_TYPE.DOCUMENT },
|
|
18
|
+
{
|
|
19
|
+
extensions: ['docx'],
|
|
20
|
+
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
21
|
+
type: FILE_TYPE.DOCUMENT,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
extensions: ['xlsx'],
|
|
25
|
+
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
26
|
+
type: FILE_TYPE.DOCUMENT,
|
|
27
|
+
},
|
|
28
|
+
{ extensions: ['txt'], mimeType: 'text/plain', type: FILE_TYPE.TEXT },
|
|
29
|
+
{ extensions: ['md', 'markdown'], mimeType: 'text/markdown', type: FILE_TYPE.TEXT },
|
|
30
|
+
{ extensions: ['csv'], mimeType: 'text/csv', type: FILE_TYPE.TEXT },
|
|
31
|
+
{ extensions: ['tsv'], mimeType: 'text/tab-separated-values', type: FILE_TYPE.TEXT },
|
|
32
|
+
{ extensions: ['json'], mimeType: 'application/json', type: FILE_TYPE.TEXT },
|
|
33
|
+
{ extensions: ['xml'], mimeType: 'application/xml', type: FILE_TYPE.TEXT },
|
|
34
|
+
{ extensions: ['yaml', 'yml'], mimeType: 'application/x-yaml', type: FILE_TYPE.TEXT },
|
|
35
|
+
{ extensions: ['html', 'htm'], mimeType: 'text/html', type: FILE_TYPE.TEXT },
|
|
36
|
+
{ extensions: ['css'], mimeType: 'text/css', type: FILE_TYPE.TEXT },
|
|
37
|
+
{ extensions: ['scss'], mimeType: 'text/x-scss', type: FILE_TYPE.TEXT },
|
|
38
|
+
{ extensions: ['js', 'mjs', 'cjs'], mimeType: 'application/javascript', type: FILE_TYPE.TEXT },
|
|
39
|
+
{ extensions: ['ts', 'tsx'], mimeType: 'application/typescript', type: FILE_TYPE.TEXT },
|
|
40
|
+
{ extensions: ['sh'], mimeType: 'application/x-sh', type: FILE_TYPE.TEXT },
|
|
41
|
+
{ extensions: ['py'], mimeType: 'text/x-python', type: FILE_TYPE.TEXT },
|
|
42
|
+
{ extensions: ['c', 'h'], mimeType: 'text/x-c', type: FILE_TYPE.TEXT },
|
|
43
|
+
{
|
|
44
|
+
extensions: ['cpp', 'hpp', 'cc', 'cxx', 'hh', 'hxx'],
|
|
45
|
+
mimeType: 'text/x-c++',
|
|
46
|
+
type: FILE_TYPE.TEXT,
|
|
47
|
+
},
|
|
48
|
+
{ extensions: ['java'], mimeType: 'text/x-java-source', type: FILE_TYPE.TEXT },
|
|
49
|
+
{ extensions: ['php'], mimeType: 'text/x-php', type: FILE_TYPE.TEXT },
|
|
50
|
+
{ extensions: ['rb'], mimeType: 'text/x-ruby', type: FILE_TYPE.TEXT },
|
|
51
|
+
{ extensions: ['go'], mimeType: 'text/x-go', type: FILE_TYPE.TEXT },
|
|
52
|
+
{ extensions: ['ini'], mimeType: 'text/x-ini', type: FILE_TYPE.TEXT },
|
|
53
|
+
{ extensions: ['properties'], mimeType: 'text/x-properties', type: FILE_TYPE.TEXT },
|
|
54
|
+
{ extensions: ['conf', 'config', 'env'], mimeType: 'text/x-config', type: FILE_TYPE.TEXT },
|
|
55
|
+
];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export * from './attachments';
|
|
2
|
+
|
|
3
|
+
export enum TEXT_CHAT_MESSAGE_ROLE {
|
|
4
|
+
USER = 'user',
|
|
5
|
+
ASSISTANT = 'assistant',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export enum TEXT_CHAT_MESSAGE_STATUS {
|
|
9
|
+
PENDING = 'pending',
|
|
10
|
+
SUCCESS = 'success',
|
|
11
|
+
FAILED = 'failed',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export enum TEXT_CHAT_ERROR_CODE {
|
|
15
|
+
CATEGORY_TOOL_MISMATCH = 'CATEGORY_TOOL_MISMATCH',
|
|
16
|
+
NOT_FOUND = 'text_chat_not_found',
|
|
17
|
+
MODEL_UNAVAILABLE = 'text_chat_model_unavailable',
|
|
18
|
+
INSUFFICIENT_BALANCE = 'text_chat_insufficient_balance',
|
|
19
|
+
INVALID_ATTACHMENT = 'text_chat_invalid_attachment',
|
|
20
|
+
CONTEXT_LIMIT = 'text_chat_context_limit',
|
|
21
|
+
BUSY = 'text_chat_busy',
|
|
22
|
+
GENERATION_FAILED = 'text_chat_generation_failed',
|
|
23
|
+
STREAM_INTERRUPTED = 'text_chat_stream_interrupted',
|
|
24
|
+
SETTLEMENT_FAILED = 'text_chat_settlement_failed',
|
|
25
|
+
CATEGORY_NOT_FOUND = 'text_chat_category_not_found',
|
|
26
|
+
CATEGORY_UNAVAILABLE = 'text_chat_category_unavailable',
|
|
27
|
+
CATEGORY_IN_USE = 'text_chat_category_in_use',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export enum TEXT_CHAT_CATEGORY_SCOPE {
|
|
31
|
+
ACTIVE = 'active',
|
|
32
|
+
ARCHIVED = 'archived',
|
|
33
|
+
ALL = 'all',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export enum TEXT_CHAT_STREAM_EVENT {
|
|
37
|
+
START = 'start',
|
|
38
|
+
DELTA = 'delta',
|
|
39
|
+
FINISH = 'finish',
|
|
40
|
+
ERROR = 'error',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const TEXT_CHAT_STREAM_CONTENT_TYPE = 'application/x-ndjson' as const;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export enum TEXT_PROVIDER_CODE {
|
|
2
|
+
WELLFLOW = 'wellflow',
|
|
3
|
+
API_ROUTE = 'api_route',
|
|
4
|
+
DEROUTER = 'derouter',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum TEXT_PROVIDER_MESSAGE_ROLE {
|
|
8
|
+
SYSTEM = 'system',
|
|
9
|
+
USER = 'user',
|
|
10
|
+
ASSISTANT = 'assistant',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum TEXT_PROVIDER_TRANSPORT {
|
|
14
|
+
AI_PROXY = 'ai_proxy',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export enum TEXT_PROVIDER_ATTEMPT_EVENT {
|
|
18
|
+
START = 'start',
|
|
19
|
+
FIRST_DELTA = 'first_delta',
|
|
20
|
+
SUCCESS = 'success',
|
|
21
|
+
FAILED = 'failed',
|
|
22
|
+
CANCELLED = 'cancelled',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum TEXT_PROVIDER_FAILURE_KIND {
|
|
26
|
+
TIMEOUT = 'timeout',
|
|
27
|
+
REQUEST = 'request',
|
|
28
|
+
PROVIDER = 'provider',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export enum TEXT_PROVIDER_FALLBACK_REASON {
|
|
32
|
+
NO_CANDIDATES = 'no_candidates',
|
|
33
|
+
QUERY_FAILED = 'query_failed',
|
|
34
|
+
EXHAUSTED = 'exhausted',
|
|
35
|
+
BUDGET = 'budget',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export enum TEXT_PROVIDER_SKIP_REASON {
|
|
39
|
+
INVALID = 'invalid',
|
|
40
|
+
TRAFFIC = 'traffic',
|
|
41
|
+
COOLDOWN = 'cooldown',
|
|
42
|
+
}
|
package/helpers/index.ts
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { TEXT_CHAT_STREAM_EVENT } from '../../constants/text-chat';
|
|
2
|
+
import {
|
|
3
|
+
TextChatStreamFrame,
|
|
4
|
+
TextChatStreamFrameSchema,
|
|
5
|
+
} from '../../models/text-chat/stream.schema';
|
|
6
|
+
|
|
7
|
+
/** UTF-8 NDJSON, one validated JSON frame followed by LF. Not SSE. */
|
|
8
|
+
export function encodeTextChatStreamFrame(frame: TextChatStreamFrame): string {
|
|
9
|
+
return `${JSON.stringify(TextChatStreamFrameSchema.parse(frame))}\n`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** One parser per HTTP response. EOF without a terminal frame is never success. */
|
|
13
|
+
export class TextChatStreamParser {
|
|
14
|
+
private readonly decoder = new TextDecoder('utf-8', { fatal: true });
|
|
15
|
+
private pending = '';
|
|
16
|
+
private sequence = -1;
|
|
17
|
+
private correlation: { chatId: string; messageId: string } | undefined;
|
|
18
|
+
private terminal = false;
|
|
19
|
+
private closed = false;
|
|
20
|
+
private failed = false;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private readonly expected: { chatId?: string; messageId?: string } = {},
|
|
24
|
+
private readonly maxFrameLength = 1024 * 1024,
|
|
25
|
+
) {
|
|
26
|
+
if (!Number.isSafeInteger(maxFrameLength) || maxFrameLength < 1) {
|
|
27
|
+
throw new Error('Invalid stream frame limit');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
push(chunk: Uint8Array): TextChatStreamFrame[] {
|
|
32
|
+
if (this.closed || this.failed) throw new Error('Stream parser is closed');
|
|
33
|
+
try {
|
|
34
|
+
this.pending += this.decoder.decode(chunk, { stream: true });
|
|
35
|
+
const frames: TextChatStreamFrame[] = [];
|
|
36
|
+
let newline: number;
|
|
37
|
+
while ((newline = this.pending.indexOf('\n')) !== -1) {
|
|
38
|
+
if (newline > this.maxFrameLength) throw new Error('Stream frame is too large');
|
|
39
|
+
const line = this.pending.slice(0, newline);
|
|
40
|
+
this.pending = this.pending.slice(newline + 1);
|
|
41
|
+
if (!line.trim()) continue;
|
|
42
|
+
const frame = TextChatStreamFrameSchema.parse(JSON.parse(line));
|
|
43
|
+
this.accept(frame);
|
|
44
|
+
frames.push(frame);
|
|
45
|
+
}
|
|
46
|
+
if (this.pending.length > this.maxFrameLength)
|
|
47
|
+
throw new Error('Stream frame is too large');
|
|
48
|
+
return frames;
|
|
49
|
+
} catch (error: unknown) {
|
|
50
|
+
this.failed = true;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
finish(): void {
|
|
56
|
+
if (this.closed || this.failed) throw new Error('Stream parser is closed');
|
|
57
|
+
this.closed = true;
|
|
58
|
+
this.pending += this.decoder.decode();
|
|
59
|
+
if (this.pending.length) throw new Error('Incomplete stream frame');
|
|
60
|
+
if (!this.terminal) throw new Error('Stream ended without a terminal frame');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private accept(frame: TextChatStreamFrame): void {
|
|
64
|
+
if (this.terminal) throw new Error('Frame received after stream completion');
|
|
65
|
+
if (frame.sequence !== this.sequence + 1) throw new Error('Invalid stream sequence');
|
|
66
|
+
if (!this.correlation) {
|
|
67
|
+
if (frame.type !== TEXT_CHAT_STREAM_EVENT.START)
|
|
68
|
+
throw new Error('Missing stream start');
|
|
69
|
+
if (
|
|
70
|
+
(this.expected.chatId && frame.chatId !== this.expected.chatId) ||
|
|
71
|
+
(this.expected.messageId && frame.messageId !== this.expected.messageId)
|
|
72
|
+
) {
|
|
73
|
+
throw new Error('Unexpected stream correlation');
|
|
74
|
+
}
|
|
75
|
+
this.correlation = { chatId: frame.chatId, messageId: frame.messageId };
|
|
76
|
+
} else {
|
|
77
|
+
if (frame.type === TEXT_CHAT_STREAM_EVENT.START)
|
|
78
|
+
throw new Error('Duplicate stream start');
|
|
79
|
+
if (
|
|
80
|
+
frame.chatId !== this.correlation.chatId ||
|
|
81
|
+
frame.messageId !== this.correlation.messageId
|
|
82
|
+
) {
|
|
83
|
+
throw new Error('Stream correlation changed');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.sequence = frame.sequence;
|
|
87
|
+
this.terminal =
|
|
88
|
+
frame.type === TEXT_CHAT_STREAM_EVENT.FINISH ||
|
|
89
|
+
frame.type === TEXT_CHAT_STREAM_EVENT.ERROR;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TOOL_TYPE } from '../../constants';
|
|
3
|
+
|
|
4
|
+
export const CatalogOrderSchema = z.number().int().min(0).max(2147483647);
|
|
5
|
+
export const CatalogGroupFieldsSchema = z.object({
|
|
6
|
+
title: z.string().trim().min(1).max(200),
|
|
7
|
+
order: CatalogOrderSchema.optional(),
|
|
8
|
+
});
|
|
9
|
+
export const CatalogGroupSchema = z.object({
|
|
10
|
+
uuid: z.string().uuid(),
|
|
11
|
+
title: z.string(),
|
|
12
|
+
order: CatalogOrderSchema,
|
|
13
|
+
});
|
|
14
|
+
const TimestampSchema = z.string().datetime().or(z.date());
|
|
15
|
+
export const CatalogGroupAdminSchema = CatalogGroupSchema.extend({
|
|
16
|
+
siteId: z.string().uuid(),
|
|
17
|
+
createdAt: TimestampSchema,
|
|
18
|
+
updatedAt: TimestampSchema,
|
|
19
|
+
});
|
|
20
|
+
export const CatalogItemSchema = z.object({
|
|
21
|
+
categoryId: z.string().uuid(),
|
|
22
|
+
title: z.string(),
|
|
23
|
+
order: CatalogOrderSchema,
|
|
24
|
+
page: z.object({
|
|
25
|
+
uuid: z.string().uuid(),
|
|
26
|
+
alias: z.string(),
|
|
27
|
+
title: z.string(),
|
|
28
|
+
toolType: z.nativeEnum(TOOL_TYPE).nullable(),
|
|
29
|
+
toolOptionId: z.string().nullable(),
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
export const PublicCatalogGroupSchema = CatalogGroupSchema.extend({
|
|
33
|
+
items: z.array(CatalogItemSchema),
|
|
34
|
+
});
|
|
35
|
+
export type PublicCatalogGroup = z.infer<typeof PublicCatalogGroupSchema>;
|
package/models/index.ts
CHANGED
package/models/page.schema.ts
CHANGED
|
@@ -12,6 +12,7 @@ export const PageSchema = z.object({
|
|
|
12
12
|
alias: z.string().min(3).max(16384),
|
|
13
13
|
toolType: z.nativeEnum(TOOL_TYPE).nullable().optional(),
|
|
14
14
|
toolOptionId: z.string().uuid().nullable().optional(),
|
|
15
|
+
categoryId: z.string().uuid().nullable().optional(),
|
|
15
16
|
createdAt: z.date(),
|
|
16
17
|
updatedAt: z.date(),
|
|
17
18
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TOOL_TYPE } from '../../constants/tools';
|
|
3
|
+
import { CatalogOrderSchema } from '../catalog';
|
|
4
|
+
|
|
5
|
+
export const TextChatCategoryFieldsSchema = z.object({
|
|
6
|
+
title: z.string().trim().min(1).max(200),
|
|
7
|
+
toolType: z
|
|
8
|
+
.enum([TOOL_TYPE.TEXT, TOOL_TYPE.IMAGE_GENERATION, TOOL_TYPE.IMAGE_EDITOR])
|
|
9
|
+
.default(TOOL_TYPE.TEXT),
|
|
10
|
+
placeholder: z.string().trim().max(1000).default(''),
|
|
11
|
+
groupId: z.string().uuid().nullable().optional(),
|
|
12
|
+
order: CatalogOrderSchema.optional(),
|
|
13
|
+
prompt: z
|
|
14
|
+
.string()
|
|
15
|
+
.max(100_000)
|
|
16
|
+
.nullable()
|
|
17
|
+
.optional()
|
|
18
|
+
.transform((value) => (typeof value === 'string' && !value.trim() ? null : value)),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/** Explicit allowlist. Never extend the administrative schema for public responses. */
|
|
22
|
+
export const TextChatCategoryPublicSchema = z.object({
|
|
23
|
+
uuid: z.string().uuid(),
|
|
24
|
+
title: z.string(),
|
|
25
|
+
placeholder: z.string(),
|
|
26
|
+
});
|
|
27
|
+
export type TextChatCategoryPublic = z.infer<typeof TextChatCategoryPublicSchema>;
|
|
28
|
+
|
|
29
|
+
const TimestampSchema = z.string().datetime().or(z.date());
|
|
30
|
+
export const TextChatCategoryAdminSchema = TextChatCategoryPublicSchema.extend({
|
|
31
|
+
siteId: z.string().uuid(),
|
|
32
|
+
toolType: TextChatCategoryFieldsSchema.shape.toolType,
|
|
33
|
+
groupId: z.string().uuid().nullable().default(null),
|
|
34
|
+
order: CatalogOrderSchema.default(0),
|
|
35
|
+
prompt: z.string().nullable(),
|
|
36
|
+
archivedAt: TimestampSchema.nullable(),
|
|
37
|
+
createdAt: TimestampSchema,
|
|
38
|
+
updatedAt: TimestampSchema,
|
|
39
|
+
});
|
|
40
|
+
export type TextChatCategoryAdmin = z.infer<typeof TextChatCategoryAdminSchema>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
FILE_TYPE,
|
|
4
|
+
TEXT_CHAT_ERROR_CODE,
|
|
5
|
+
TEXT_CHAT_MESSAGE_ROLE,
|
|
6
|
+
TEXT_CHAT_MESSAGE_STATUS,
|
|
7
|
+
TOOL_MODEL_STATUS,
|
|
8
|
+
} from '../../constants';
|
|
9
|
+
import { FileSchema } from '../file.schema';
|
|
10
|
+
import { UnlockedBySchema } from '../unlocked-by-subscription.schema';
|
|
11
|
+
|
|
12
|
+
export * from './stream.schema';
|
|
13
|
+
export * from './provider-config.schema';
|
|
14
|
+
export * from './category.schema';
|
|
15
|
+
|
|
16
|
+
const CountSchema = z.number().int().safe().nonnegative();
|
|
17
|
+
const TimestampSchema = z.string().datetime().or(z.date());
|
|
18
|
+
|
|
19
|
+
export const TextChatFileSchema = FileSchema.extend({
|
|
20
|
+
type: z.enum([FILE_TYPE.IMAGE, FILE_TYPE.TEXT, FILE_TYPE.DOCUMENT]),
|
|
21
|
+
contentLength: CountSchema.nullable(),
|
|
22
|
+
});
|
|
23
|
+
export type TextChatFile = z.infer<typeof TextChatFileSchema>;
|
|
24
|
+
|
|
25
|
+
export const TextChatModelSchema = z.object({
|
|
26
|
+
uuid: z.string().uuid(),
|
|
27
|
+
title: z.string(),
|
|
28
|
+
model: z.string(),
|
|
29
|
+
description: z.string(),
|
|
30
|
+
status: z.nativeEnum(TOOL_MODEL_STATUS),
|
|
31
|
+
canUse: z.boolean(),
|
|
32
|
+
unlockedBy: UnlockedBySchema.nullable(),
|
|
33
|
+
supportsImageAttachment: z.boolean(),
|
|
34
|
+
maxPromptLength: CountSchema.positive(),
|
|
35
|
+
maxContextCharacters: CountSchema.positive(),
|
|
36
|
+
order: z.number().int(),
|
|
37
|
+
});
|
|
38
|
+
export type TextChatModel = z.infer<typeof TextChatModelSchema>;
|
|
39
|
+
|
|
40
|
+
export const TextChatConfigSchema = z.object({
|
|
41
|
+
models: z.array(TextChatModelSchema),
|
|
42
|
+
maxFileAttachments: CountSchema.positive(),
|
|
43
|
+
maxImageUploadSize: CountSchema.positive(),
|
|
44
|
+
maxDocumentUploadSize: CountSchema.positive(),
|
|
45
|
+
maxTextUploadSize: CountSchema.positive(),
|
|
46
|
+
isAttachmentAvailable: z.literal(true),
|
|
47
|
+
});
|
|
48
|
+
export type TextChatConfig = z.infer<typeof TextChatConfigSchema>;
|
|
49
|
+
|
|
50
|
+
export const TextChatSchema = z.object({
|
|
51
|
+
uuid: z.string().uuid(),
|
|
52
|
+
title: z.string(),
|
|
53
|
+
// Independent Page.categoryId; immutable after creation.
|
|
54
|
+
categoryId: z.string().uuid().nullable(),
|
|
55
|
+
// Optional for legacy servers; only successful assistant messages determine this value.
|
|
56
|
+
lastUsedAiModelId: z.string().uuid().nullable().optional(),
|
|
57
|
+
createdAt: TimestampSchema,
|
|
58
|
+
updatedAt: TimestampSchema,
|
|
59
|
+
});
|
|
60
|
+
export type TextChat = z.infer<typeof TextChatSchema>;
|
|
61
|
+
|
|
62
|
+
export const TextChatMessageSchema = z.object({
|
|
63
|
+
uuid: z.string().uuid(),
|
|
64
|
+
chatId: z.string().uuid(),
|
|
65
|
+
role: z.nativeEnum(TEXT_CHAT_MESSAGE_ROLE),
|
|
66
|
+
status: z.nativeEnum(TEXT_CHAT_MESSAGE_STATUS),
|
|
67
|
+
text: z.string(),
|
|
68
|
+
modelId: z.string().uuid().nullable(),
|
|
69
|
+
files: z.array(TextChatFileSchema),
|
|
70
|
+
errorCode: z.nativeEnum(TEXT_CHAT_ERROR_CODE).nullable(),
|
|
71
|
+
price: CountSchema.nullable(),
|
|
72
|
+
createdAt: TimestampSchema,
|
|
73
|
+
});
|
|
74
|
+
export type TextChatMessage = z.infer<typeof TextChatMessageSchema>;
|
|
75
|
+
|
|
76
|
+
export const TextChatPriceSchema = z.object({
|
|
77
|
+
price: CountSchema.positive(),
|
|
78
|
+
// Distances from the requested draft inputLength; null means no boundary in that direction.
|
|
79
|
+
charsUntilNextPriceIncrease: CountSchema.positive().nullable(),
|
|
80
|
+
charsUntilNextPriceDecrease: CountSchema.positive().nullable(),
|
|
81
|
+
});
|
|
82
|
+
export type TextChatPrice = z.infer<typeof TextChatPriceSchema>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TEXT_PROVIDER_CODE } from '../../constants/text-provider';
|
|
3
|
+
import { TOOL_MODEL_STATUS } from '../../constants/tools';
|
|
4
|
+
|
|
5
|
+
/** Deployment configuration only. Never accepted as a chat request or exposed by public config. */
|
|
6
|
+
export const TextProviderSeedSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
providers: z
|
|
9
|
+
.array(
|
|
10
|
+
z
|
|
11
|
+
.object({
|
|
12
|
+
code: z.nativeEnum(TEXT_PROVIDER_CODE),
|
|
13
|
+
title: z.string().trim().min(1),
|
|
14
|
+
status: z.nativeEnum(TOOL_MODEL_STATUS),
|
|
15
|
+
})
|
|
16
|
+
.strict(),
|
|
17
|
+
)
|
|
18
|
+
.max(3),
|
|
19
|
+
links: z.array(
|
|
20
|
+
z
|
|
21
|
+
.object({
|
|
22
|
+
modelId: z.string().uuid(),
|
|
23
|
+
providerCode: z.nativeEnum(TEXT_PROVIDER_CODE),
|
|
24
|
+
providerModel: z.string().trim().min(1),
|
|
25
|
+
priority: z.number().int().safe(),
|
|
26
|
+
trafficPercent: z.number().int().min(0).max(100),
|
|
27
|
+
})
|
|
28
|
+
.strict(),
|
|
29
|
+
),
|
|
30
|
+
})
|
|
31
|
+
.strict()
|
|
32
|
+
.superRefine((config, context) => {
|
|
33
|
+
if (
|
|
34
|
+
new Set(config.providers.map((provider) => provider.code)).size !==
|
|
35
|
+
config.providers.length
|
|
36
|
+
) {
|
|
37
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: 'Duplicate provider' });
|
|
38
|
+
}
|
|
39
|
+
const pairs = config.links.map((link) => `${link.modelId}:${link.providerCode}`);
|
|
40
|
+
if (new Set(pairs).size !== pairs.length) {
|
|
41
|
+
context.addIssue({
|
|
42
|
+
code: z.ZodIssueCode.custom,
|
|
43
|
+
message: 'Duplicate model/provider link',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
export type TextProviderSeed = z.infer<typeof TextProviderSeedSchema>;
|