@wrcb/cb-common 1.0.877 → 1.0.879
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/build/events/ai/aiRequestMadeEvent.d.ts +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +2 -0
- package/build/services/WasabiUploader.d.ts +51 -1
- package/build/services/WasabiUploader.js +94 -12
- package/build/services/ai/AIClient.d.ts +22 -0
- package/build/services/ai/AIClient.js +76 -8
- package/build/types/crm.d.ts +207 -0
- package/build/types/crm.js +230 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Subjects } from '../subjects';
|
|
2
2
|
export type AIProvider = 'openai' | 'cerebras';
|
|
3
|
-
export type AIRequestType = 'chat' | 'tts' | 'transcribe' | 'image-generate' | 'image-edit';
|
|
3
|
+
export type AIRequestType = 'chat' | 'tts' | 'transcribe' | 'image-generate' | 'image-edit' | 'embeddings';
|
|
4
4
|
export interface AIRequestMadeEvent {
|
|
5
5
|
subject: Subjects.AIRequestMade;
|
|
6
6
|
data: {
|
package/build/index.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export * from './types/betOutcome';
|
|
|
56
56
|
export * from './types/betStatus';
|
|
57
57
|
export * from './types/predictionType';
|
|
58
58
|
export * from './types/trabalhoHomeOffice';
|
|
59
|
+
export * from './types/crm';
|
|
59
60
|
export * from './events/subjects';
|
|
60
61
|
export * from './events/baseListener';
|
|
61
62
|
export * from './events/basePublisher';
|
package/build/index.js
CHANGED
|
@@ -83,6 +83,8 @@ __exportStar(require("./types/betStatus"), exports);
|
|
|
83
83
|
__exportStar(require("./types/predictionType"), exports);
|
|
84
84
|
// trabalho-home-office
|
|
85
85
|
__exportStar(require("./types/trabalhoHomeOffice"), exports);
|
|
86
|
+
// crm — núcleo multi-tenant (pipeline configurável + agente de IA)
|
|
87
|
+
__exportStar(require("./types/crm"), exports);
|
|
86
88
|
__exportStar(require("./events/subjects"), exports);
|
|
87
89
|
__exportStar(require("./events/baseListener"), exports);
|
|
88
90
|
__exportStar(require("./events/basePublisher"), exports);
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export type WasabiAcl = 'public-read' | 'private';
|
|
2
|
+
export interface WasabiCredentials {
|
|
3
|
+
accessKeyId: string;
|
|
4
|
+
secretAccessKey: string;
|
|
5
|
+
bucketName: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
}
|
|
1
9
|
export interface UploadToWasabiParams {
|
|
2
10
|
fileBuffer: Buffer;
|
|
3
11
|
originalName: string;
|
|
@@ -10,5 +18,47 @@ export interface UploadToWasabiParams {
|
|
|
10
18
|
contentType?: string;
|
|
11
19
|
extension?: string;
|
|
12
20
|
cacheControl?: string;
|
|
21
|
+
acl?: WasabiAcl;
|
|
22
|
+
}
|
|
23
|
+
export interface WasabiObject {
|
|
24
|
+
key: string;
|
|
25
|
+
bucket: string;
|
|
26
|
+
url: string | null;
|
|
27
|
+
contentType: string;
|
|
28
|
+
sizeBytes: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sobe um arquivo e devolve o objeto completo (chave, bucket, tamanho).
|
|
32
|
+
*
|
|
33
|
+
* Prefira esta função a `uploadToWasabi` em código novo: ela devolve a chave,
|
|
34
|
+
* que é o que permite assinar URL, apagar e aplicar retenção depois.
|
|
35
|
+
*/
|
|
36
|
+
export declare function uploadFileToWasabi({ fileBuffer, originalName, accessKeyId, secretAccessKey, bucketName, region, endpoint, folder, contentType: contentTypeOverride, extension: extensionOverride, cacheControl, acl }: UploadToWasabiParams): Promise<WasabiObject>;
|
|
37
|
+
/**
|
|
38
|
+
* Assinatura histórica: sobe público e devolve só a URL.
|
|
39
|
+
*
|
|
40
|
+
* Mantida para não quebrar os serviços que já a usam. Em código novo use
|
|
41
|
+
* `uploadFileToWasabi`, e para dado pessoal use-a com `acl: 'private'`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function uploadToWasabi(params: UploadToWasabiParams): Promise<string>;
|
|
44
|
+
export interface SignedUrlParams extends WasabiCredentials {
|
|
45
|
+
key: string;
|
|
46
|
+
expiresInSeconds?: number;
|
|
47
|
+
downloadFileName?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gera uma URL temporária de leitura para um objeto privado.
|
|
51
|
+
*
|
|
52
|
+
* É assim que a foto de documento chega ao painel: o operador abre o lead, o
|
|
53
|
+
* backend assina na hora, o link morre em minutos.
|
|
54
|
+
*/
|
|
55
|
+
export declare function getWasabiSignedUrl({ key, expiresInSeconds, downloadFileName, ...credentials }: SignedUrlParams): Promise<string>;
|
|
56
|
+
export interface DeleteFromWasabiParams extends WasabiCredentials {
|
|
57
|
+
key: string;
|
|
13
58
|
}
|
|
14
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Apaga um objeto. Usado pelo expurgo por retenção (LGPD): o arquivo some, o
|
|
61
|
+
* registro no banco fica marcado como expurgado para a auditoria continuar
|
|
62
|
+
* rastreável.
|
|
63
|
+
*/
|
|
64
|
+
export declare function deleteFromWasabi({ key, ...credentials }: DeleteFromWasabiParams): Promise<void>;
|
|
@@ -8,31 +8,113 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
23
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
24
|
};
|
|
14
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.uploadFileToWasabi = uploadFileToWasabi;
|
|
15
27
|
exports.uploadToWasabi = uploadToWasabi;
|
|
28
|
+
exports.getWasabiSignedUrl = getWasabiSignedUrl;
|
|
29
|
+
exports.deleteFromWasabi = deleteFromWasabi;
|
|
16
30
|
// cb-common/server/services/wasabiUploader.ts
|
|
17
31
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
32
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
18
33
|
const uuid_1 = require("uuid");
|
|
19
34
|
const mime_types_1 = __importDefault(require("mime-types"));
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
const DEFAULT_REGION = 'us-east-1';
|
|
36
|
+
const DEFAULT_ENDPOINT = 'https://s3.us-east-1.wasabisys.com';
|
|
37
|
+
function buildClient({ accessKeyId, secretAccessKey, region = DEFAULT_REGION, endpoint = DEFAULT_ENDPOINT }) {
|
|
38
|
+
return new client_s3_1.S3Client({
|
|
39
|
+
region,
|
|
40
|
+
endpoint,
|
|
41
|
+
forcePathStyle: true,
|
|
42
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sobe um arquivo e devolve o objeto completo (chave, bucket, tamanho).
|
|
47
|
+
*
|
|
48
|
+
* Prefira esta função a `uploadToWasabi` em código novo: ela devolve a chave,
|
|
49
|
+
* que é o que permite assinar URL, apagar e aplicar retenção depois.
|
|
50
|
+
*/
|
|
51
|
+
function uploadFileToWasabi(_a) {
|
|
52
|
+
return __awaiter(this, arguments, void 0, function* ({ fileBuffer, originalName, accessKeyId, secretAccessKey, bucketName, region = DEFAULT_REGION, endpoint = DEFAULT_ENDPOINT, folder = 'uploads', contentType: contentTypeOverride, extension: extensionOverride, cacheControl, acl = 'public-read' }) {
|
|
22
53
|
const extension = extensionOverride || originalName.split('.').pop() || 'jpg';
|
|
23
54
|
const contentType = contentTypeOverride || mime_types_1.default.lookup(extension) || 'application/octet-stream';
|
|
24
|
-
const
|
|
25
|
-
const wasabiClient =
|
|
55
|
+
const key = `${folder}/${(0, uuid_1.v4)()}.${extension}`;
|
|
56
|
+
const wasabiClient = buildClient({
|
|
57
|
+
accessKeyId,
|
|
58
|
+
secretAccessKey,
|
|
59
|
+
bucketName,
|
|
26
60
|
region,
|
|
27
|
-
endpoint
|
|
28
|
-
forcePathStyle: true,
|
|
29
|
-
credentials: {
|
|
30
|
-
accessKeyId,
|
|
31
|
-
secretAccessKey
|
|
32
|
-
}
|
|
61
|
+
endpoint
|
|
33
62
|
});
|
|
34
|
-
const params = Object.assign({ Bucket: bucketName, Key:
|
|
63
|
+
const params = Object.assign({ Bucket: bucketName, Key: key, Body: fileBuffer, ContentType: contentType, ACL: acl }, (cacheControl ? { CacheControl: cacheControl } : {}));
|
|
35
64
|
yield wasabiClient.send(new client_s3_1.PutObjectCommand(params));
|
|
36
|
-
return
|
|
65
|
+
return {
|
|
66
|
+
key,
|
|
67
|
+
bucket: bucketName,
|
|
68
|
+
url: acl === 'public-read'
|
|
69
|
+
? `${endpoint.replace(/\/$/, '')}/${bucketName}/${key}`
|
|
70
|
+
: null,
|
|
71
|
+
contentType,
|
|
72
|
+
sizeBytes: fileBuffer.length
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Assinatura histórica: sobe público e devolve só a URL.
|
|
78
|
+
*
|
|
79
|
+
* Mantida para não quebrar os serviços que já a usam. Em código novo use
|
|
80
|
+
* `uploadFileToWasabi`, e para dado pessoal use-a com `acl: 'private'`.
|
|
81
|
+
*/
|
|
82
|
+
function uploadToWasabi(params) {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
const object = yield uploadFileToWasabi(params);
|
|
85
|
+
if (!object.url) {
|
|
86
|
+
throw new Error('uploadToWasabi: objeto privado não tem URL pública — use uploadFileToWasabi + getWasabiSignedUrl');
|
|
87
|
+
}
|
|
88
|
+
return object.url;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Gera uma URL temporária de leitura para um objeto privado.
|
|
93
|
+
*
|
|
94
|
+
* É assim que a foto de documento chega ao painel: o operador abre o lead, o
|
|
95
|
+
* backend assina na hora, o link morre em minutos.
|
|
96
|
+
*/
|
|
97
|
+
function getWasabiSignedUrl(_a) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
var { key, expiresInSeconds = 300, downloadFileName } = _a, credentials = __rest(_a, ["key", "expiresInSeconds", "downloadFileName"]);
|
|
100
|
+
const wasabiClient = buildClient(credentials);
|
|
101
|
+
const command = new client_s3_1.GetObjectCommand(Object.assign({ Bucket: credentials.bucketName, Key: key }, (downloadFileName
|
|
102
|
+
? {
|
|
103
|
+
ResponseContentDisposition: `attachment; filename="${downloadFileName}"`
|
|
104
|
+
}
|
|
105
|
+
: {})));
|
|
106
|
+
return (0, s3_request_presigner_1.getSignedUrl)(wasabiClient, command, { expiresIn: expiresInSeconds });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Apaga um objeto. Usado pelo expurgo por retenção (LGPD): o arquivo some, o
|
|
111
|
+
* registro no banco fica marcado como expurgado para a auditoria continuar
|
|
112
|
+
* rastreável.
|
|
113
|
+
*/
|
|
114
|
+
function deleteFromWasabi(_a) {
|
|
115
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
var { key } = _a, credentials = __rest(_a, ["key"]);
|
|
117
|
+
const wasabiClient = buildClient(credentials);
|
|
118
|
+
yield wasabiClient.send(new client_s3_1.DeleteObjectCommand({ Bucket: credentials.bucketName, Key: key }));
|
|
37
119
|
});
|
|
38
120
|
}
|
|
@@ -10,6 +10,14 @@ export interface ChatParams {
|
|
|
10
10
|
responseFormat?: {
|
|
11
11
|
type: 'json_object' | 'text';
|
|
12
12
|
};
|
|
13
|
+
tools?: OpenAI.ChatCompletionTool[];
|
|
14
|
+
toolChoice?: OpenAI.ChatCompletionToolChoiceOption;
|
|
15
|
+
account?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface EmbeddingsParams {
|
|
18
|
+
model: string;
|
|
19
|
+
input: string[];
|
|
20
|
+
dimensions?: number;
|
|
13
21
|
account?: string;
|
|
14
22
|
}
|
|
15
23
|
export interface TTSParams {
|
|
@@ -44,10 +52,16 @@ export interface ImageEditParams {
|
|
|
44
52
|
}
|
|
45
53
|
export interface ChatResult {
|
|
46
54
|
content: string;
|
|
55
|
+
toolCalls: OpenAI.ChatCompletionMessageToolCall[];
|
|
56
|
+
finishReason: string | null;
|
|
47
57
|
promptTokens: number;
|
|
48
58
|
completionTokens: number;
|
|
49
59
|
totalTokens: number;
|
|
50
60
|
}
|
|
61
|
+
export interface EmbeddingsResult {
|
|
62
|
+
vectors: number[][];
|
|
63
|
+
promptTokens: number;
|
|
64
|
+
}
|
|
51
65
|
export declare class AIClient {
|
|
52
66
|
private readonly natsClient;
|
|
53
67
|
private readonly service;
|
|
@@ -60,6 +74,14 @@ export declare class AIClient {
|
|
|
60
74
|
private get cerebras();
|
|
61
75
|
private emit;
|
|
62
76
|
chat(params: ChatParams): Promise<ChatResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Gera embeddings para indexar a base de conhecimento do agente.
|
|
79
|
+
*
|
|
80
|
+
* Passa pelo mesmo caminho de auditoria das outras chamadas: indexar uma
|
|
81
|
+
* apostila grande custa dinheiro e precisa aparecer no relatório junto com
|
|
82
|
+
* o resto.
|
|
83
|
+
*/
|
|
84
|
+
embeddings(params: EmbeddingsParams): Promise<EmbeddingsResult>;
|
|
63
85
|
tts(params: TTSParams): Promise<Buffer>;
|
|
64
86
|
transcribe(params: TranscribeParams): Promise<string>;
|
|
65
87
|
generateImage(params: ImageGenerateParams): Promise<string>;
|
|
@@ -46,12 +46,42 @@ const COST_PER_1K = {
|
|
|
46
46
|
'dall-e-3': { input: 0.040, output: 0 },
|
|
47
47
|
'dall-e-2': { input: 0.020, output: 0 },
|
|
48
48
|
'gpt-image-1': { input: 0.067, output: 0 },
|
|
49
|
+
// embeddings — cobrados só na entrada (USD por 1K tokens)
|
|
50
|
+
'text-embedding-3-small': { input: 0.00002, output: 0 },
|
|
51
|
+
'text-embedding-3-large': { input: 0.00013, output: 0 },
|
|
49
52
|
};
|
|
50
53
|
function calcCost(model, promptTokens, completionTokens) {
|
|
51
54
|
var _a;
|
|
52
55
|
const rates = (_a = COST_PER_1K[model]) !== null && _a !== void 0 ? _a : { input: 0, output: 0 };
|
|
53
56
|
return (promptTokens / 1000) * rates.input + (completionTokens / 1000) * rates.output;
|
|
54
57
|
}
|
|
58
|
+
// Resumo do prompt para o evento de auditoria.
|
|
59
|
+
//
|
|
60
|
+
// O conteúdo de uma mensagem pode ser string ou lista de partes (texto +
|
|
61
|
+
// imagem). Uma imagem vira só o rótulo `[image]`: além de não caber, um data
|
|
62
|
+
// URI de foto de documento no log de auditoria seria exatamente o vazamento
|
|
63
|
+
// que o armazenamento privado existe para evitar.
|
|
64
|
+
function summarizeMessages(messages) {
|
|
65
|
+
return messages
|
|
66
|
+
.map((message) => {
|
|
67
|
+
const { content } = message;
|
|
68
|
+
let text;
|
|
69
|
+
if (typeof content === 'string') {
|
|
70
|
+
text = content;
|
|
71
|
+
}
|
|
72
|
+
else if (Array.isArray(content)) {
|
|
73
|
+
text = content
|
|
74
|
+
.map((part) => (part.type === 'text' ? part.text : `[${part.type}]`))
|
|
75
|
+
.join(' ');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
text = '';
|
|
79
|
+
}
|
|
80
|
+
return `[${message.role}] ${text.slice(0, 200)}`;
|
|
81
|
+
})
|
|
82
|
+
.join(' | ')
|
|
83
|
+
.slice(0, 500);
|
|
84
|
+
}
|
|
55
85
|
class AIClient {
|
|
56
86
|
constructor(natsClient, service, openaiApiKey, cerebrasApiKey) {
|
|
57
87
|
this.natsClient = natsClient;
|
|
@@ -99,21 +129,24 @@ class AIClient {
|
|
|
99
129
|
}
|
|
100
130
|
chat(params) {
|
|
101
131
|
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
103
|
-
const { provider, model, messages, maxTokens, temperature, responseFormat, account } = params;
|
|
132
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
133
|
+
const { provider, model, messages, maxTokens, temperature, responseFormat, tools, toolChoice, account } = params;
|
|
104
134
|
const client = provider === 'cerebras' ? this.cerebras : this.openai;
|
|
105
135
|
const requestedAt = new Date().toISOString();
|
|
106
136
|
const t0 = Date.now();
|
|
107
|
-
const promptSummary = messages
|
|
137
|
+
const promptSummary = summarizeMessages(messages);
|
|
108
138
|
try {
|
|
109
|
-
const response = yield client.chat.completions.create(Object.assign(Object.assign(Object.assign({ model,
|
|
110
|
-
messages }, (maxTokens !== undefined && { max_tokens: maxTokens })), (temperature !== undefined && { temperature })), (responseFormat !== undefined && { response_format: responseFormat })));
|
|
139
|
+
const response = yield client.chat.completions.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ model,
|
|
140
|
+
messages }, (maxTokens !== undefined && { max_tokens: maxTokens })), (temperature !== undefined && { temperature })), (responseFormat !== undefined && { response_format: responseFormat })), (tools !== undefined && { tools })), (toolChoice !== undefined && { tool_choice: toolChoice })));
|
|
111
141
|
const promptTokens = (_b = (_a = response.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
112
142
|
const completionTokens = (_d = (_c = response.usage) === null || _c === void 0 ? void 0 : _c.completion_tokens) !== null && _d !== void 0 ? _d : 0;
|
|
113
143
|
const costUSD = calcCost(model, promptTokens, completionTokens);
|
|
114
|
-
yield this.emit({ provider, type: 'chat', model, promptTokens, completionTokens, costUSD, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, success: true });
|
|
144
|
+
yield this.emit({ provider, type: 'chat', model, promptTokens, completionTokens, costUSD, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: true });
|
|
145
|
+
const choice = response.choices[0];
|
|
115
146
|
return {
|
|
116
|
-
content: (
|
|
147
|
+
content: (_f = (_e = choice === null || choice === void 0 ? void 0 : choice.message) === null || _e === void 0 ? void 0 : _e.content) !== null && _f !== void 0 ? _f : '',
|
|
148
|
+
toolCalls: (_h = (_g = choice === null || choice === void 0 ? void 0 : choice.message) === null || _g === void 0 ? void 0 : _g.tool_calls) !== null && _h !== void 0 ? _h : [],
|
|
149
|
+
finishReason: (_j = choice === null || choice === void 0 ? void 0 : choice.finish_reason) !== null && _j !== void 0 ? _j : null,
|
|
117
150
|
promptTokens,
|
|
118
151
|
completionTokens,
|
|
119
152
|
totalTokens: promptTokens + completionTokens,
|
|
@@ -121,7 +154,42 @@ class AIClient {
|
|
|
121
154
|
}
|
|
122
155
|
catch (err) {
|
|
123
156
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
124
|
-
yield this.emit({ provider, type: 'chat', model, promptTokens: 0, completionTokens: 0, costUSD: 0, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, success: false, errorMessage });
|
|
157
|
+
yield this.emit({ provider, type: 'chat', model, promptTokens: 0, completionTokens: 0, costUSD: 0, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: false, errorMessage });
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Gera embeddings para indexar a base de conhecimento do agente.
|
|
164
|
+
*
|
|
165
|
+
* Passa pelo mesmo caminho de auditoria das outras chamadas: indexar uma
|
|
166
|
+
* apostila grande custa dinheiro e precisa aparecer no relatório junto com
|
|
167
|
+
* o resto.
|
|
168
|
+
*/
|
|
169
|
+
embeddings(params) {
|
|
170
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
171
|
+
var _a, _b;
|
|
172
|
+
const { model, input, dimensions, account } = params;
|
|
173
|
+
const requestedAt = new Date().toISOString();
|
|
174
|
+
const t0 = Date.now();
|
|
175
|
+
const promptSummary = `${input.length} textos, ${input.reduce((total, text) => total + text.length, 0)} chars`;
|
|
176
|
+
try {
|
|
177
|
+
const response = yield this.openai.embeddings.create(Object.assign({ model,
|
|
178
|
+
input }, (dimensions !== undefined && { dimensions })));
|
|
179
|
+
const promptTokens = (_b = (_a = response.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
180
|
+
const costUSD = calcCost(model, promptTokens, 0);
|
|
181
|
+
yield this.emit({ provider: 'openai', type: 'embeddings', model, promptTokens, completionTokens: 0, costUSD, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: true });
|
|
182
|
+
// A API não garante a ordem da resposta; `index` é quem amarra cada
|
|
183
|
+
// vetor ao texto que o originou.
|
|
184
|
+
const vectors = new Array(input.length);
|
|
185
|
+
for (const item of response.data) {
|
|
186
|
+
vectors[item.index] = item.embedding;
|
|
187
|
+
}
|
|
188
|
+
return { vectors, promptTokens };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
192
|
+
yield this.emit({ provider: 'openai', type: 'embeddings', model, promptTokens: 0, completionTokens: 0, costUSD: 0, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: false, errorMessage });
|
|
125
193
|
throw err;
|
|
126
194
|
}
|
|
127
195
|
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
export declare enum CrmChannel {
|
|
2
|
+
WhatsAppCloud = "WhatsAppCloud",
|
|
3
|
+
WhatsAppEvolution = "WhatsAppEvolution"
|
|
4
|
+
}
|
|
5
|
+
export declare enum CrmActor {
|
|
6
|
+
Lead = "Lead",
|
|
7
|
+
Ai = "Ai",
|
|
8
|
+
Human = "Human",
|
|
9
|
+
System = "System"
|
|
10
|
+
}
|
|
11
|
+
export declare enum CrmDirection {
|
|
12
|
+
Inbound = "Inbound",
|
|
13
|
+
Outbound = "Outbound"
|
|
14
|
+
}
|
|
15
|
+
export declare enum CrmMessageType {
|
|
16
|
+
Text = "Text",
|
|
17
|
+
Image = "Image",
|
|
18
|
+
Audio = "Audio",
|
|
19
|
+
Video = "Video",
|
|
20
|
+
Document = "Document",
|
|
21
|
+
Template = "Template",
|
|
22
|
+
InternalNote = "InternalNote"
|
|
23
|
+
}
|
|
24
|
+
export declare enum CrmMessageStatus {
|
|
25
|
+
Queued = "Queued",
|
|
26
|
+
Sent = "Sent",
|
|
27
|
+
Delivered = "Delivered",
|
|
28
|
+
Read = "Read",
|
|
29
|
+
Failed = "Failed"
|
|
30
|
+
}
|
|
31
|
+
export declare enum CrmConnectionState {
|
|
32
|
+
Connected = "Connected",
|
|
33
|
+
Connecting = "Connecting",
|
|
34
|
+
Disconnected = "Disconnected",
|
|
35
|
+
Unknown = "Unknown"
|
|
36
|
+
}
|
|
37
|
+
export declare enum CrmWindowState {
|
|
38
|
+
Open = "Open",
|
|
39
|
+
Closed = "Closed"
|
|
40
|
+
}
|
|
41
|
+
export interface CrmStage {
|
|
42
|
+
key: string;
|
|
43
|
+
label: string;
|
|
44
|
+
order: number;
|
|
45
|
+
color: string;
|
|
46
|
+
aiDescription: string;
|
|
47
|
+
aiEnabled: boolean;
|
|
48
|
+
isTerminal: boolean;
|
|
49
|
+
isWon: boolean;
|
|
50
|
+
isLost: boolean;
|
|
51
|
+
slaMinutes: number | null;
|
|
52
|
+
}
|
|
53
|
+
export declare enum CrmStageChangedBy {
|
|
54
|
+
Ai = "Ai",
|
|
55
|
+
Human = "Human",
|
|
56
|
+
Automation = "Automation"
|
|
57
|
+
}
|
|
58
|
+
export declare enum CrmLeadSource {
|
|
59
|
+
Form = "Form",
|
|
60
|
+
InboundMessage = "InboundMessage",
|
|
61
|
+
Manual = "Manual",
|
|
62
|
+
Import = "Import"
|
|
63
|
+
}
|
|
64
|
+
export type CrmFieldValue = string | number | boolean | null | string[];
|
|
65
|
+
export type CrmCustomFields = Record<string, CrmFieldValue>;
|
|
66
|
+
export declare enum CrmControl {
|
|
67
|
+
Ai = "Ai",
|
|
68
|
+
Human = "Human"
|
|
69
|
+
}
|
|
70
|
+
export declare enum CrmHandoffReason {
|
|
71
|
+
LeadRequested = "LeadRequested",
|
|
72
|
+
OutOfScope = "OutOfScope",
|
|
73
|
+
Sensitive = "Sensitive",
|
|
74
|
+
DocumentBlocked = "DocumentBlocked",
|
|
75
|
+
ManualTakeover = "ManualTakeover",
|
|
76
|
+
SlaBreach = "SlaBreach"
|
|
77
|
+
}
|
|
78
|
+
export declare enum CrmAgentTool {
|
|
79
|
+
SearchKnowledge = "SearchKnowledge",
|
|
80
|
+
MoveStage = "MoveStage",
|
|
81
|
+
ScheduleFollowUp = "ScheduleFollowUp",
|
|
82
|
+
RecordField = "RecordField",
|
|
83
|
+
RequestDocument = "RequestDocument",
|
|
84
|
+
RequestHuman = "RequestHuman"
|
|
85
|
+
}
|
|
86
|
+
export interface CrmAgentToolCall {
|
|
87
|
+
tool: CrmAgentTool;
|
|
88
|
+
arguments: Record<string, CrmFieldValue>;
|
|
89
|
+
result: string;
|
|
90
|
+
succeeded: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface CrmGuardrails {
|
|
93
|
+
forbiddenTopics: string[];
|
|
94
|
+
blockUnsourcedRates: boolean;
|
|
95
|
+
handoffOnSensitiveData: boolean;
|
|
96
|
+
mandatoryDisclaimer: string | null;
|
|
97
|
+
maxAiTurnsBeforeHandoff: number | null;
|
|
98
|
+
}
|
|
99
|
+
export declare enum CrmKnowledgeStatus {
|
|
100
|
+
Uploaded = "Uploaded",
|
|
101
|
+
Processing = "Processing",
|
|
102
|
+
Ready = "Ready",
|
|
103
|
+
Failed = "Failed"
|
|
104
|
+
}
|
|
105
|
+
export declare enum CrmKnowledgeFormat {
|
|
106
|
+
Pdf = "Pdf",
|
|
107
|
+
Docx = "Docx",
|
|
108
|
+
Txt = "Txt",
|
|
109
|
+
Markdown = "Markdown",
|
|
110
|
+
Inline = "Inline"
|
|
111
|
+
}
|
|
112
|
+
export interface CrmDocumentRequirement {
|
|
113
|
+
key: string;
|
|
114
|
+
label: string;
|
|
115
|
+
aiInstruction: string;
|
|
116
|
+
validationHints: string[];
|
|
117
|
+
required: boolean;
|
|
118
|
+
acceptedMimeTypes: string[];
|
|
119
|
+
maxFiles: number;
|
|
120
|
+
maxRejections: number;
|
|
121
|
+
}
|
|
122
|
+
export declare enum CrmLeadDocumentStatus {
|
|
123
|
+
Received = "Received",
|
|
124
|
+
Validating = "Validating",
|
|
125
|
+
Approved = "Approved",
|
|
126
|
+
Rejected = "Rejected",
|
|
127
|
+
PendingReview = "PendingReview",
|
|
128
|
+
Purged = "Purged"
|
|
129
|
+
}
|
|
130
|
+
export declare enum CrmDocumentRejectionReason {
|
|
131
|
+
Unreadable = "Unreadable",
|
|
132
|
+
WrongDocument = "WrongDocument",
|
|
133
|
+
Incomplete = "Incomplete",
|
|
134
|
+
Expired = "Expired",
|
|
135
|
+
SuspectedTampering = "SuspectedTampering"
|
|
136
|
+
}
|
|
137
|
+
export interface CrmCadenceStep {
|
|
138
|
+
afterHours: number;
|
|
139
|
+
stageKeys: string[];
|
|
140
|
+
template: string | null;
|
|
141
|
+
aiInstruction: string | null;
|
|
142
|
+
whatsappTemplateName: string | null;
|
|
143
|
+
}
|
|
144
|
+
export interface CrmFirstContact {
|
|
145
|
+
enabled: boolean;
|
|
146
|
+
afterMinutes: number;
|
|
147
|
+
text: string | null;
|
|
148
|
+
followUpText: string | null;
|
|
149
|
+
whatsappTemplateName: string | null;
|
|
150
|
+
}
|
|
151
|
+
export declare enum CrmCadenceStopReason {
|
|
152
|
+
LeadReplied = "LeadReplied",
|
|
153
|
+
StepsExhausted = "StepsExhausted",
|
|
154
|
+
StageTerminal = "StageTerminal",
|
|
155
|
+
OptedOut = "OptedOut",
|
|
156
|
+
HumanTookOver = "HumanTookOver"
|
|
157
|
+
}
|
|
158
|
+
export interface CrmSchedule {
|
|
159
|
+
timezone: string;
|
|
160
|
+
activeDays: number[];
|
|
161
|
+
windowStart: number;
|
|
162
|
+
windowEnd: number;
|
|
163
|
+
holidays: string[];
|
|
164
|
+
}
|
|
165
|
+
export interface CrmTypingProfile {
|
|
166
|
+
minSeconds: number;
|
|
167
|
+
maxSeconds: number;
|
|
168
|
+
perCharMs: number;
|
|
169
|
+
jitterPercent: number;
|
|
170
|
+
splitLongMessages: boolean;
|
|
171
|
+
splitThreshold: number;
|
|
172
|
+
}
|
|
173
|
+
export interface CrmLeadIngestPayload {
|
|
174
|
+
externalId: string;
|
|
175
|
+
sourceSystem: string;
|
|
176
|
+
sourceTable: string;
|
|
177
|
+
name: string;
|
|
178
|
+
phone: string;
|
|
179
|
+
email: string | null;
|
|
180
|
+
customFields: CrmCustomFields;
|
|
181
|
+
utmSource: string | null;
|
|
182
|
+
utmMedium: string | null;
|
|
183
|
+
utmCampaign: string | null;
|
|
184
|
+
utmTerm: string | null;
|
|
185
|
+
utmContent: string | null;
|
|
186
|
+
trackingCode: string | null;
|
|
187
|
+
occurredAt: string;
|
|
188
|
+
}
|
|
189
|
+
export declare enum CrmEventType {
|
|
190
|
+
LeadCreated = "LeadCreated",
|
|
191
|
+
StageChanged = "StageChanged",
|
|
192
|
+
MessageSent = "MessageSent",
|
|
193
|
+
MessageReceived = "MessageReceived",
|
|
194
|
+
ControlChanged = "ControlChanged",
|
|
195
|
+
DocumentReceived = "DocumentReceived",
|
|
196
|
+
DocumentReviewed = "DocumentReviewed",
|
|
197
|
+
CadenceStepFired = "CadenceStepFired",
|
|
198
|
+
CadenceStopped = "CadenceStopped",
|
|
199
|
+
OptedOut = "OptedOut",
|
|
200
|
+
AgentToolCalled = "AgentToolCalled",
|
|
201
|
+
GuardrailTriggered = "GuardrailTriggered",
|
|
202
|
+
DailyLimitReached = "DailyLimitReached",
|
|
203
|
+
FirstContactSent = "FirstContactSent",
|
|
204
|
+
ConnectionLost = "ConnectionLost",
|
|
205
|
+
ConnectionRestored = "ConnectionRestored",
|
|
206
|
+
MessageRecovered = "MessageRecovered"
|
|
207
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// crm — núcleo de CRM multi-tenant com agente de IA
|
|
3
|
+
//
|
|
4
|
+
// Convenção: prefixo Crm em tudo, para não colidir com os tipos do
|
|
5
|
+
// leadAutomator (LeadStatus, LeadMessageDirection...), que continuam existindo
|
|
6
|
+
// enquanto o gn-social-media não for portado para este núcleo.
|
|
7
|
+
//
|
|
8
|
+
// Regra de ouro deste módulo: o que varia por tenant é DADO, não código.
|
|
9
|
+
// Por isso estágio de funil, passo de cadência, requisito de documento e
|
|
10
|
+
// ferramenta do agente são interfaces/configuração — nunca enum fechado.
|
|
11
|
+
// Enum aqui é só para o que é genuinamente fixo (canal, direção, ator).
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CrmEventType = exports.CrmCadenceStopReason = exports.CrmDocumentRejectionReason = exports.CrmLeadDocumentStatus = exports.CrmKnowledgeFormat = exports.CrmKnowledgeStatus = exports.CrmAgentTool = exports.CrmHandoffReason = exports.CrmControl = exports.CrmLeadSource = exports.CrmStageChangedBy = exports.CrmWindowState = exports.CrmConnectionState = exports.CrmMessageStatus = exports.CrmMessageType = exports.CrmDirection = exports.CrmActor = exports.CrmChannel = void 0;
|
|
14
|
+
// ============================================
|
|
15
|
+
// CANAL E MENSAGEM
|
|
16
|
+
// ============================================
|
|
17
|
+
var CrmChannel;
|
|
18
|
+
(function (CrmChannel) {
|
|
19
|
+
// WhatsApp Business Platform (Cloud API da Meta). Canal padrão para lead
|
|
20
|
+
// inbound: número não corre risco de banimento e tem status de entrega.
|
|
21
|
+
CrmChannel["WhatsAppCloud"] = "WhatsAppCloud";
|
|
22
|
+
// Evolution API / Baileys. Não-oficial. Mantido para tenant que faz
|
|
23
|
+
// prospecção fria ou não tem conta Meta Business verificada.
|
|
24
|
+
CrmChannel["WhatsAppEvolution"] = "WhatsAppEvolution";
|
|
25
|
+
})(CrmChannel || (exports.CrmChannel = CrmChannel = {}));
|
|
26
|
+
var CrmActor;
|
|
27
|
+
(function (CrmActor) {
|
|
28
|
+
CrmActor["Lead"] = "Lead";
|
|
29
|
+
CrmActor["Ai"] = "Ai";
|
|
30
|
+
CrmActor["Human"] = "Human";
|
|
31
|
+
// Mensagem gerada por regra (cadência, template de reengajamento) sem
|
|
32
|
+
// passar pelo modelo.
|
|
33
|
+
CrmActor["System"] = "System";
|
|
34
|
+
})(CrmActor || (exports.CrmActor = CrmActor = {}));
|
|
35
|
+
var CrmDirection;
|
|
36
|
+
(function (CrmDirection) {
|
|
37
|
+
CrmDirection["Inbound"] = "Inbound";
|
|
38
|
+
CrmDirection["Outbound"] = "Outbound";
|
|
39
|
+
})(CrmDirection || (exports.CrmDirection = CrmDirection = {}));
|
|
40
|
+
var CrmMessageType;
|
|
41
|
+
(function (CrmMessageType) {
|
|
42
|
+
CrmMessageType["Text"] = "Text";
|
|
43
|
+
CrmMessageType["Image"] = "Image";
|
|
44
|
+
CrmMessageType["Audio"] = "Audio";
|
|
45
|
+
CrmMessageType["Video"] = "Video";
|
|
46
|
+
CrmMessageType["Document"] = "Document";
|
|
47
|
+
// Template aprovado (HSM). Único tipo que pode ser enviado fora da
|
|
48
|
+
// janela de 24h do WhatsApp.
|
|
49
|
+
CrmMessageType["Template"] = "Template";
|
|
50
|
+
// Nota interna do operador — não é enviada ao lead, aparece só no painel.
|
|
51
|
+
CrmMessageType["InternalNote"] = "InternalNote";
|
|
52
|
+
})(CrmMessageType || (exports.CrmMessageType = CrmMessageType = {}));
|
|
53
|
+
var CrmMessageStatus;
|
|
54
|
+
(function (CrmMessageStatus) {
|
|
55
|
+
CrmMessageStatus["Queued"] = "Queued";
|
|
56
|
+
CrmMessageStatus["Sent"] = "Sent";
|
|
57
|
+
CrmMessageStatus["Delivered"] = "Delivered";
|
|
58
|
+
CrmMessageStatus["Read"] = "Read";
|
|
59
|
+
CrmMessageStatus["Failed"] = "Failed";
|
|
60
|
+
})(CrmMessageStatus || (exports.CrmMessageStatus = CrmMessageStatus = {}));
|
|
61
|
+
// Estado do vínculo entre o canal e o WhatsApp.
|
|
62
|
+
//
|
|
63
|
+
// Só faz sentido em canal com sessão (Evolution/Baileys, que pareia como
|
|
64
|
+
// aparelho conectado e pode ser desligado pelo WhatsApp a qualquer momento).
|
|
65
|
+
// Na Cloud API não existe sessão: o canal está sempre `Connected`.
|
|
66
|
+
var CrmConnectionState;
|
|
67
|
+
(function (CrmConnectionState) {
|
|
68
|
+
CrmConnectionState["Connected"] = "Connected";
|
|
69
|
+
CrmConnectionState["Connecting"] = "Connecting";
|
|
70
|
+
CrmConnectionState["Disconnected"] = "Disconnected";
|
|
71
|
+
CrmConnectionState["Unknown"] = "Unknown";
|
|
72
|
+
})(CrmConnectionState || (exports.CrmConnectionState = CrmConnectionState = {}));
|
|
73
|
+
// Estado da janela de atendimento de 24h da Meta. Fora dela, só template.
|
|
74
|
+
var CrmWindowState;
|
|
75
|
+
(function (CrmWindowState) {
|
|
76
|
+
CrmWindowState["Open"] = "Open";
|
|
77
|
+
CrmWindowState["Closed"] = "Closed";
|
|
78
|
+
})(CrmWindowState || (exports.CrmWindowState = CrmWindowState = {}));
|
|
79
|
+
var CrmStageChangedBy;
|
|
80
|
+
(function (CrmStageChangedBy) {
|
|
81
|
+
CrmStageChangedBy["Ai"] = "Ai";
|
|
82
|
+
CrmStageChangedBy["Human"] = "Human";
|
|
83
|
+
// Regra do sistema (ex.: checklist de documentos completou).
|
|
84
|
+
CrmStageChangedBy["Automation"] = "Automation";
|
|
85
|
+
})(CrmStageChangedBy || (exports.CrmStageChangedBy = CrmStageChangedBy = {}));
|
|
86
|
+
// ============================================
|
|
87
|
+
// LEAD
|
|
88
|
+
// ============================================
|
|
89
|
+
var CrmLeadSource;
|
|
90
|
+
(function (CrmLeadSource) {
|
|
91
|
+
// Formulário do site, via rota de ingestão. O payload carrega o id da
|
|
92
|
+
// linha no Postgres para casar os dois lados.
|
|
93
|
+
CrmLeadSource["Form"] = "Form";
|
|
94
|
+
// Lead que iniciou a conversa no WhatsApp sem ter passado pelo formulário
|
|
95
|
+
// (ou cujo telefone não casou com nenhum registro).
|
|
96
|
+
CrmLeadSource["InboundMessage"] = "InboundMessage";
|
|
97
|
+
// Criado à mão no painel.
|
|
98
|
+
CrmLeadSource["Manual"] = "Manual";
|
|
99
|
+
// Carga em lote (CSV, prospecção).
|
|
100
|
+
CrmLeadSource["Import"] = "Import";
|
|
101
|
+
})(CrmLeadSource || (exports.CrmLeadSource = CrmLeadSource = {}));
|
|
102
|
+
// Quem está conduzindo a conversa agora.
|
|
103
|
+
var CrmControl;
|
|
104
|
+
(function (CrmControl) {
|
|
105
|
+
CrmControl["Ai"] = "Ai";
|
|
106
|
+
CrmControl["Human"] = "Human";
|
|
107
|
+
})(CrmControl || (exports.CrmControl = CrmControl = {}));
|
|
108
|
+
// Motivo pelo qual o agente pediu um humano. Vira etiqueta no kanban, então
|
|
109
|
+
// é fechado de propósito: lista aberta viraria texto livre inútil para filtro.
|
|
110
|
+
var CrmHandoffReason;
|
|
111
|
+
(function (CrmHandoffReason) {
|
|
112
|
+
// O lead pediu explicitamente falar com uma pessoa.
|
|
113
|
+
CrmHandoffReason["LeadRequested"] = "LeadRequested";
|
|
114
|
+
// A pergunta saiu do que a base de conhecimento cobre.
|
|
115
|
+
CrmHandoffReason["OutOfScope"] = "OutOfScope";
|
|
116
|
+
// Assunto sensível (reclamação, ameaça de processo, dado bancário).
|
|
117
|
+
CrmHandoffReason["Sensitive"] = "Sensitive";
|
|
118
|
+
// Documento reprovado mais vezes que o limite configurado.
|
|
119
|
+
CrmHandoffReason["DocumentBlocked"] = "DocumentBlocked";
|
|
120
|
+
// Um operador assumiu pelo painel, sem a IA ter pedido.
|
|
121
|
+
CrmHandoffReason["ManualTakeover"] = "ManualTakeover";
|
|
122
|
+
// Estourou o SLA do estágio.
|
|
123
|
+
CrmHandoffReason["SlaBreach"] = "SlaBreach";
|
|
124
|
+
})(CrmHandoffReason || (exports.CrmHandoffReason = CrmHandoffReason = {}));
|
|
125
|
+
// ============================================
|
|
126
|
+
// AGENTE
|
|
127
|
+
// ============================================
|
|
128
|
+
// Ferramentas que o agente pode receber. Habilitadas por tenant: um CRM de
|
|
129
|
+
// empréstimo usa coleta de documento, um de agendamento não.
|
|
130
|
+
var CrmAgentTool;
|
|
131
|
+
(function (CrmAgentTool) {
|
|
132
|
+
// Busca vetorial na base de conhecimento do tenant (apostila, tabela de
|
|
133
|
+
// produtos, script de objeções).
|
|
134
|
+
CrmAgentTool["SearchKnowledge"] = "SearchKnowledge";
|
|
135
|
+
// Move o lead de estágio, informando o motivo.
|
|
136
|
+
CrmAgentTool["MoveStage"] = "MoveStage";
|
|
137
|
+
// Agenda um novo contato para uma data/hora.
|
|
138
|
+
CrmAgentTool["ScheduleFollowUp"] = "ScheduleFollowUp";
|
|
139
|
+
// Grava um dado coletado na conversa em customFields.
|
|
140
|
+
CrmAgentTool["RecordField"] = "RecordField";
|
|
141
|
+
// Pede um documento da checklist ao lead.
|
|
142
|
+
CrmAgentTool["RequestDocument"] = "RequestDocument";
|
|
143
|
+
// Devolve a conversa para um humano.
|
|
144
|
+
CrmAgentTool["RequestHuman"] = "RequestHuman";
|
|
145
|
+
})(CrmAgentTool || (exports.CrmAgentTool = CrmAgentTool = {}));
|
|
146
|
+
// ============================================
|
|
147
|
+
// BASE DE CONHECIMENTO (RAG)
|
|
148
|
+
// ============================================
|
|
149
|
+
var CrmKnowledgeStatus;
|
|
150
|
+
(function (CrmKnowledgeStatus) {
|
|
151
|
+
// Arquivo no Wasabi, ainda não processado.
|
|
152
|
+
CrmKnowledgeStatus["Uploaded"] = "Uploaded";
|
|
153
|
+
// Extraindo texto / gerando embeddings.
|
|
154
|
+
CrmKnowledgeStatus["Processing"] = "Processing";
|
|
155
|
+
// Chunks indexados, disponível para busca.
|
|
156
|
+
CrmKnowledgeStatus["Ready"] = "Ready";
|
|
157
|
+
// Falhou (PDF protegido, formato não suportado, arquivo corrompido).
|
|
158
|
+
CrmKnowledgeStatus["Failed"] = "Failed";
|
|
159
|
+
})(CrmKnowledgeStatus || (exports.CrmKnowledgeStatus = CrmKnowledgeStatus = {}));
|
|
160
|
+
var CrmKnowledgeFormat;
|
|
161
|
+
(function (CrmKnowledgeFormat) {
|
|
162
|
+
CrmKnowledgeFormat["Pdf"] = "Pdf";
|
|
163
|
+
CrmKnowledgeFormat["Docx"] = "Docx";
|
|
164
|
+
CrmKnowledgeFormat["Txt"] = "Txt";
|
|
165
|
+
CrmKnowledgeFormat["Markdown"] = "Markdown";
|
|
166
|
+
// Texto colado direto no painel, sem arquivo.
|
|
167
|
+
CrmKnowledgeFormat["Inline"] = "Inline";
|
|
168
|
+
})(CrmKnowledgeFormat || (exports.CrmKnowledgeFormat = CrmKnowledgeFormat = {}));
|
|
169
|
+
var CrmLeadDocumentStatus;
|
|
170
|
+
(function (CrmLeadDocumentStatus) {
|
|
171
|
+
// Recebido e armazenado, ainda não analisado.
|
|
172
|
+
CrmLeadDocumentStatus["Received"] = "Received";
|
|
173
|
+
// Em análise pelo modelo de visão.
|
|
174
|
+
CrmLeadDocumentStatus["Validating"] = "Validating";
|
|
175
|
+
// Aprovado automaticamente ou por um operador.
|
|
176
|
+
CrmLeadDocumentStatus["Approved"] = "Approved";
|
|
177
|
+
// Ilegível, documento errado ou fora de validade.
|
|
178
|
+
CrmLeadDocumentStatus["Rejected"] = "Rejected";
|
|
179
|
+
// Aguardando conferência humana (o modelo não teve confiança suficiente).
|
|
180
|
+
CrmLeadDocumentStatus["PendingReview"] = "PendingReview";
|
|
181
|
+
// Removido por retenção/pedido do titular (LGPD). O registro permanece
|
|
182
|
+
// para auditoria; o arquivo no Wasabi já não existe.
|
|
183
|
+
CrmLeadDocumentStatus["Purged"] = "Purged";
|
|
184
|
+
})(CrmLeadDocumentStatus || (exports.CrmLeadDocumentStatus = CrmLeadDocumentStatus = {}));
|
|
185
|
+
var CrmDocumentRejectionReason;
|
|
186
|
+
(function (CrmDocumentRejectionReason) {
|
|
187
|
+
CrmDocumentRejectionReason["Unreadable"] = "Unreadable";
|
|
188
|
+
CrmDocumentRejectionReason["WrongDocument"] = "WrongDocument";
|
|
189
|
+
CrmDocumentRejectionReason["Incomplete"] = "Incomplete";
|
|
190
|
+
CrmDocumentRejectionReason["Expired"] = "Expired";
|
|
191
|
+
CrmDocumentRejectionReason["SuspectedTampering"] = "SuspectedTampering";
|
|
192
|
+
})(CrmDocumentRejectionReason || (exports.CrmDocumentRejectionReason = CrmDocumentRejectionReason = {}));
|
|
193
|
+
var CrmCadenceStopReason;
|
|
194
|
+
(function (CrmCadenceStopReason) {
|
|
195
|
+
CrmCadenceStopReason["LeadReplied"] = "LeadReplied";
|
|
196
|
+
CrmCadenceStopReason["StepsExhausted"] = "StepsExhausted";
|
|
197
|
+
CrmCadenceStopReason["StageTerminal"] = "StageTerminal";
|
|
198
|
+
CrmCadenceStopReason["OptedOut"] = "OptedOut";
|
|
199
|
+
CrmCadenceStopReason["HumanTookOver"] = "HumanTookOver";
|
|
200
|
+
})(CrmCadenceStopReason || (exports.CrmCadenceStopReason = CrmCadenceStopReason = {}));
|
|
201
|
+
// ============================================
|
|
202
|
+
// EVENTOS DE AUDITORIA
|
|
203
|
+
// ============================================
|
|
204
|
+
// Tudo que muda um lead vira um CrmEvent. É o que permite responder "por que
|
|
205
|
+
// este lead foi para 'perdido' às 3h da manhã" — e é exigência prática de
|
|
206
|
+
// LGPD quando há decisão automatizada sobre crédito.
|
|
207
|
+
var CrmEventType;
|
|
208
|
+
(function (CrmEventType) {
|
|
209
|
+
CrmEventType["LeadCreated"] = "LeadCreated";
|
|
210
|
+
CrmEventType["StageChanged"] = "StageChanged";
|
|
211
|
+
CrmEventType["MessageSent"] = "MessageSent";
|
|
212
|
+
CrmEventType["MessageReceived"] = "MessageReceived";
|
|
213
|
+
CrmEventType["ControlChanged"] = "ControlChanged";
|
|
214
|
+
CrmEventType["DocumentReceived"] = "DocumentReceived";
|
|
215
|
+
CrmEventType["DocumentReviewed"] = "DocumentReviewed";
|
|
216
|
+
CrmEventType["CadenceStepFired"] = "CadenceStepFired";
|
|
217
|
+
CrmEventType["CadenceStopped"] = "CadenceStopped";
|
|
218
|
+
CrmEventType["OptedOut"] = "OptedOut";
|
|
219
|
+
CrmEventType["AgentToolCalled"] = "AgentToolCalled";
|
|
220
|
+
CrmEventType["GuardrailTriggered"] = "GuardrailTriggered";
|
|
221
|
+
// Envio ativo barrado pelo limite diário do canal (proteção de warmup).
|
|
222
|
+
CrmEventType["DailyLimitReached"] = "DailyLimitReached";
|
|
223
|
+
CrmEventType["FirstContactSent"] = "FirstContactSent";
|
|
224
|
+
// Sessão do canal caiu. Enquanto durar, nada entra nem sai — e é por isso
|
|
225
|
+
// que existe alerta: queda silenciosa é lead perdido sem ninguém saber.
|
|
226
|
+
CrmEventType["ConnectionLost"] = "ConnectionLost";
|
|
227
|
+
CrmEventType["ConnectionRestored"] = "ConnectionRestored";
|
|
228
|
+
// Mensagem que chegou durante a queda e foi recuperada na sincronização.
|
|
229
|
+
CrmEventType["MessageRecovered"] = "MessageRecovered";
|
|
230
|
+
})(CrmEventType || (exports.CrmEventType = CrmEventType = {}));
|