@nextsparkjs/plugin-ai 0.1.0-beta.1
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/.env.example +79 -0
- package/README.md +529 -0
- package/api/README.md +65 -0
- package/api/ai-history/[id]/route.ts +112 -0
- package/api/embeddings/route.ts +129 -0
- package/api/generate/route.ts +160 -0
- package/docs/01-getting-started/01-introduction.md +237 -0
- package/docs/01-getting-started/02-installation.md +447 -0
- package/docs/01-getting-started/03-configuration.md +416 -0
- package/docs/02-features/01-text-generation.md +523 -0
- package/docs/02-features/02-embeddings.md +241 -0
- package/docs/02-features/03-ai-history.md +549 -0
- package/docs/03-advanced-usage/01-core-utilities.md +500 -0
- package/docs/04-use-cases/01-content-generation.md +453 -0
- package/entities/ai-history/ai-history.config.ts +123 -0
- package/entities/ai-history/ai-history.fields.ts +330 -0
- package/entities/ai-history/messages/en.json +56 -0
- package/entities/ai-history/messages/es.json +56 -0
- package/entities/ai-history/migrations/001_ai_history_table.sql +167 -0
- package/entities/ai-history/migrations/002_ai_history_metas.sql +103 -0
- package/lib/ai-history-meta-service.ts +379 -0
- package/lib/ai-history-service.ts +391 -0
- package/lib/ai-sdk.ts +7 -0
- package/lib/core-utils.ts +217 -0
- package/lib/plugin-env.ts +252 -0
- package/lib/sanitize.ts +122 -0
- package/lib/save-example.ts +237 -0
- package/lib/server-env.ts +104 -0
- package/package.json +23 -0
- package/plugin.config.ts +55 -0
- package/public/docs/login-404-error.png +0 -0
- package/tsconfig.json +47 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/types/ai.types.ts +51 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI History Entity Fields Configuration
|
|
3
|
+
*
|
|
4
|
+
* Generic AI interaction history for all plugins and operations.
|
|
5
|
+
* Tracks AI usage with cost metrics and polymorphic relationships.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - User tracking
|
|
9
|
+
* - Polymorphic relationships (relatedEntityType + relatedEntityId)
|
|
10
|
+
* - Cost and performance metrics
|
|
11
|
+
* - Status tracking through operation lifecycle
|
|
12
|
+
* - Support for multiple AI providers (Anthropic, OpenAI, etc.)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { EntityField } from '@nextsparkjs/core/lib/entities/types'
|
|
16
|
+
|
|
17
|
+
export const aiHistoryFields: EntityField[] = [
|
|
18
|
+
// User Relationship
|
|
19
|
+
{
|
|
20
|
+
name: 'userId',
|
|
21
|
+
type: 'text',
|
|
22
|
+
required: true,
|
|
23
|
+
display: {
|
|
24
|
+
label: 'User ID',
|
|
25
|
+
description: 'User who initiated the AI operation',
|
|
26
|
+
placeholder: 'User ID',
|
|
27
|
+
showInList: true,
|
|
28
|
+
showInDetail: true,
|
|
29
|
+
showInForm: false,
|
|
30
|
+
order: 1,
|
|
31
|
+
columnWidth: 6,
|
|
32
|
+
},
|
|
33
|
+
api: {
|
|
34
|
+
searchable: true,
|
|
35
|
+
sortable: true,
|
|
36
|
+
readOnly: true,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Polymorphic Relationship (Generic Entity Link)
|
|
41
|
+
{
|
|
42
|
+
name: 'relatedEntityType',
|
|
43
|
+
type: 'text',
|
|
44
|
+
required: false,
|
|
45
|
+
display: {
|
|
46
|
+
label: 'Related Entity Type',
|
|
47
|
+
description: 'Type of entity this operation relates to (e.g., "contents", "products", "campaigns")',
|
|
48
|
+
placeholder: 'contents',
|
|
49
|
+
showInList: true,
|
|
50
|
+
showInDetail: true,
|
|
51
|
+
showInForm: false,
|
|
52
|
+
order: 2,
|
|
53
|
+
columnWidth: 6,
|
|
54
|
+
},
|
|
55
|
+
api: {
|
|
56
|
+
searchable: true,
|
|
57
|
+
sortable: true,
|
|
58
|
+
readOnly: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'relatedEntityId',
|
|
63
|
+
type: 'text',
|
|
64
|
+
required: false,
|
|
65
|
+
display: {
|
|
66
|
+
label: 'Related Entity ID',
|
|
67
|
+
description: 'ID of the related entity (polymorphic relationship)',
|
|
68
|
+
placeholder: 'Entity ID',
|
|
69
|
+
showInList: false,
|
|
70
|
+
showInDetail: true,
|
|
71
|
+
showInForm: false,
|
|
72
|
+
order: 3,
|
|
73
|
+
columnWidth: 6,
|
|
74
|
+
},
|
|
75
|
+
api: {
|
|
76
|
+
searchable: true,
|
|
77
|
+
sortable: false,
|
|
78
|
+
readOnly: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// AI Operation Details
|
|
83
|
+
{
|
|
84
|
+
name: 'operation',
|
|
85
|
+
type: 'select',
|
|
86
|
+
required: true,
|
|
87
|
+
options: [
|
|
88
|
+
{ value: 'generate', label: 'Generate' },
|
|
89
|
+
{ value: 'refine', label: 'Refine' },
|
|
90
|
+
{ value: 'analyze', label: 'Analyze' },
|
|
91
|
+
{ value: 'chat', label: 'Chat' },
|
|
92
|
+
{ value: 'completion', label: 'Completion' },
|
|
93
|
+
{ value: 'other', label: 'Other' },
|
|
94
|
+
],
|
|
95
|
+
display: {
|
|
96
|
+
label: 'Operation',
|
|
97
|
+
description: 'Type of AI operation',
|
|
98
|
+
placeholder: 'Select operation...',
|
|
99
|
+
showInList: true,
|
|
100
|
+
showInDetail: true,
|
|
101
|
+
showInForm: false,
|
|
102
|
+
order: 4,
|
|
103
|
+
columnWidth: 4,
|
|
104
|
+
},
|
|
105
|
+
api: {
|
|
106
|
+
searchable: true,
|
|
107
|
+
sortable: true,
|
|
108
|
+
readOnly: true,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'model',
|
|
113
|
+
type: 'text',
|
|
114
|
+
required: true,
|
|
115
|
+
display: {
|
|
116
|
+
label: 'AI Model',
|
|
117
|
+
description: 'AI model used (e.g., claude-3-5-sonnet-20241022, gpt-4)',
|
|
118
|
+
placeholder: 'claude-3-5-sonnet-20241022',
|
|
119
|
+
showInList: true,
|
|
120
|
+
showInDetail: true,
|
|
121
|
+
showInForm: false,
|
|
122
|
+
order: 5,
|
|
123
|
+
columnWidth: 4,
|
|
124
|
+
},
|
|
125
|
+
api: {
|
|
126
|
+
searchable: true,
|
|
127
|
+
sortable: true,
|
|
128
|
+
readOnly: true,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'provider',
|
|
133
|
+
type: 'select',
|
|
134
|
+
required: true,
|
|
135
|
+
defaultValue: 'anthropic',
|
|
136
|
+
options: [
|
|
137
|
+
{ value: 'anthropic', label: 'Anthropic' },
|
|
138
|
+
{ value: 'openai', label: 'OpenAI' },
|
|
139
|
+
{ value: 'google', label: 'Google' },
|
|
140
|
+
{ value: 'azure', label: 'Azure' },
|
|
141
|
+
{ value: 'other', label: 'Other' },
|
|
142
|
+
],
|
|
143
|
+
display: {
|
|
144
|
+
label: 'Provider',
|
|
145
|
+
description: 'AI provider',
|
|
146
|
+
placeholder: 'Select provider...',
|
|
147
|
+
showInList: true,
|
|
148
|
+
showInDetail: true,
|
|
149
|
+
showInForm: false,
|
|
150
|
+
order: 6,
|
|
151
|
+
columnWidth: 4,
|
|
152
|
+
},
|
|
153
|
+
api: {
|
|
154
|
+
searchable: true,
|
|
155
|
+
sortable: true,
|
|
156
|
+
readOnly: true,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Status and Metrics
|
|
161
|
+
{
|
|
162
|
+
name: 'status',
|
|
163
|
+
type: 'select',
|
|
164
|
+
required: true,
|
|
165
|
+
defaultValue: 'pending',
|
|
166
|
+
options: [
|
|
167
|
+
{ value: 'pending', label: 'Pending' },
|
|
168
|
+
{ value: 'processing', label: 'Processing' },
|
|
169
|
+
{ value: 'completed', label: 'Completed' },
|
|
170
|
+
{ value: 'failed', label: 'Failed' },
|
|
171
|
+
],
|
|
172
|
+
display: {
|
|
173
|
+
label: 'Status',
|
|
174
|
+
description: 'Operation processing status',
|
|
175
|
+
placeholder: 'Select status...',
|
|
176
|
+
showInList: true,
|
|
177
|
+
showInDetail: true,
|
|
178
|
+
showInForm: false,
|
|
179
|
+
order: 7,
|
|
180
|
+
columnWidth: 3,
|
|
181
|
+
},
|
|
182
|
+
api: {
|
|
183
|
+
searchable: true,
|
|
184
|
+
sortable: true,
|
|
185
|
+
readOnly: true,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'tokensUsed',
|
|
190
|
+
type: 'number',
|
|
191
|
+
required: false,
|
|
192
|
+
display: {
|
|
193
|
+
label: 'Tokens Used',
|
|
194
|
+
description: 'Total tokens consumed (input + output)',
|
|
195
|
+
placeholder: '0',
|
|
196
|
+
showInList: true,
|
|
197
|
+
showInDetail: true,
|
|
198
|
+
showInForm: false,
|
|
199
|
+
order: 8,
|
|
200
|
+
columnWidth: 3,
|
|
201
|
+
},
|
|
202
|
+
api: {
|
|
203
|
+
searchable: false,
|
|
204
|
+
sortable: true,
|
|
205
|
+
readOnly: true,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'creditsUsed',
|
|
210
|
+
type: 'number',
|
|
211
|
+
required: false,
|
|
212
|
+
display: {
|
|
213
|
+
label: 'Credits Used',
|
|
214
|
+
description: 'Credits deducted for this operation',
|
|
215
|
+
placeholder: '0',
|
|
216
|
+
showInList: true,
|
|
217
|
+
showInDetail: true,
|
|
218
|
+
showInForm: false,
|
|
219
|
+
order: 9,
|
|
220
|
+
columnWidth: 3,
|
|
221
|
+
},
|
|
222
|
+
api: {
|
|
223
|
+
searchable: false,
|
|
224
|
+
sortable: true,
|
|
225
|
+
readOnly: true,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: 'estimatedCost',
|
|
230
|
+
type: 'number',
|
|
231
|
+
required: false,
|
|
232
|
+
display: {
|
|
233
|
+
label: 'Estimated Cost ($)',
|
|
234
|
+
description: 'Cost in USD based on model pricing',
|
|
235
|
+
placeholder: '0.00',
|
|
236
|
+
showInList: true,
|
|
237
|
+
showInDetail: true,
|
|
238
|
+
showInForm: false,
|
|
239
|
+
order: 10,
|
|
240
|
+
columnWidth: 3,
|
|
241
|
+
},
|
|
242
|
+
api: {
|
|
243
|
+
searchable: false,
|
|
244
|
+
sortable: true,
|
|
245
|
+
readOnly: true,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 'balanceAfter',
|
|
250
|
+
type: 'number',
|
|
251
|
+
required: false,
|
|
252
|
+
display: {
|
|
253
|
+
label: 'Balance After',
|
|
254
|
+
description: 'User credit balance after this operation',
|
|
255
|
+
placeholder: '0',
|
|
256
|
+
showInList: false,
|
|
257
|
+
showInDetail: true,
|
|
258
|
+
showInForm: false,
|
|
259
|
+
order: 11,
|
|
260
|
+
columnWidth: 3,
|
|
261
|
+
},
|
|
262
|
+
api: {
|
|
263
|
+
searchable: false,
|
|
264
|
+
sortable: true,
|
|
265
|
+
readOnly: true,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
// Error Tracking
|
|
270
|
+
{
|
|
271
|
+
name: 'errorMessage',
|
|
272
|
+
type: 'textarea',
|
|
273
|
+
required: false,
|
|
274
|
+
display: {
|
|
275
|
+
label: 'Error Message',
|
|
276
|
+
description: 'Error message if operation failed',
|
|
277
|
+
placeholder: 'Error details...',
|
|
278
|
+
showInList: false,
|
|
279
|
+
showInDetail: true,
|
|
280
|
+
showInForm: false,
|
|
281
|
+
order: 12,
|
|
282
|
+
columnWidth: 12,
|
|
283
|
+
},
|
|
284
|
+
api: {
|
|
285
|
+
searchable: false,
|
|
286
|
+
sortable: false,
|
|
287
|
+
readOnly: true,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// Timestamps
|
|
292
|
+
{
|
|
293
|
+
name: 'createdAt',
|
|
294
|
+
type: 'datetime',
|
|
295
|
+
required: false,
|
|
296
|
+
display: {
|
|
297
|
+
label: 'Created At',
|
|
298
|
+
description: 'When the AI operation was initiated',
|
|
299
|
+
showInList: true,
|
|
300
|
+
showInDetail: true,
|
|
301
|
+
showInForm: false,
|
|
302
|
+
order: 97,
|
|
303
|
+
columnWidth: 6,
|
|
304
|
+
},
|
|
305
|
+
api: {
|
|
306
|
+
searchable: false,
|
|
307
|
+
sortable: true,
|
|
308
|
+
readOnly: true,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'completedAt',
|
|
313
|
+
type: 'datetime',
|
|
314
|
+
required: false,
|
|
315
|
+
display: {
|
|
316
|
+
label: 'Completed At',
|
|
317
|
+
description: 'When the AI operation finished (success or failure)',
|
|
318
|
+
showInList: false,
|
|
319
|
+
showInDetail: true,
|
|
320
|
+
showInForm: false,
|
|
321
|
+
order: 98,
|
|
322
|
+
columnWidth: 6,
|
|
323
|
+
},
|
|
324
|
+
api: {
|
|
325
|
+
searchable: false,
|
|
326
|
+
sortable: true,
|
|
327
|
+
readOnly: true,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"name": "AI History",
|
|
4
|
+
"namePlural": "AI History",
|
|
5
|
+
"description": "Generic audit trail for AI interactions with cost and performance metrics"
|
|
6
|
+
},
|
|
7
|
+
"fields": {
|
|
8
|
+
"userId": "User ID",
|
|
9
|
+
"relatedEntityType": "Related Entity Type",
|
|
10
|
+
"relatedEntityId": "Related Entity ID",
|
|
11
|
+
"operation": "Operation",
|
|
12
|
+
"model": "AI Model",
|
|
13
|
+
"provider": "Provider",
|
|
14
|
+
"status": "Status",
|
|
15
|
+
"tokensUsed": "Tokens Used",
|
|
16
|
+
"creditsUsed": "Credits Used",
|
|
17
|
+
"estimatedCost": "Estimated Cost ($)",
|
|
18
|
+
"balanceAfter": "Balance After",
|
|
19
|
+
"errorMessage": "Error Message",
|
|
20
|
+
"createdAt": "Created At",
|
|
21
|
+
"completedAt": "Completed At"
|
|
22
|
+
},
|
|
23
|
+
"operations": {
|
|
24
|
+
"generate": "Generate",
|
|
25
|
+
"refine": "Refine",
|
|
26
|
+
"analyze": "Analyze",
|
|
27
|
+
"chat": "Chat",
|
|
28
|
+
"completion": "Completion",
|
|
29
|
+
"other": "Other"
|
|
30
|
+
},
|
|
31
|
+
"providers": {
|
|
32
|
+
"anthropic": "Anthropic",
|
|
33
|
+
"openai": "OpenAI",
|
|
34
|
+
"google": "Google",
|
|
35
|
+
"azure": "Azure",
|
|
36
|
+
"other": "Other"
|
|
37
|
+
},
|
|
38
|
+
"actions": {
|
|
39
|
+
"create": "Create History Entry",
|
|
40
|
+
"edit": "Edit History Entry",
|
|
41
|
+
"delete": "Delete History Entry",
|
|
42
|
+
"view": "View History Entry"
|
|
43
|
+
},
|
|
44
|
+
"messages": {
|
|
45
|
+
"created": "History entry created successfully",
|
|
46
|
+
"updated": "History entry updated successfully",
|
|
47
|
+
"deleted": "History entry deleted successfully",
|
|
48
|
+
"error": "An error occurred"
|
|
49
|
+
},
|
|
50
|
+
"status": {
|
|
51
|
+
"pending": "Pending",
|
|
52
|
+
"processing": "Processing",
|
|
53
|
+
"completed": "Completed",
|
|
54
|
+
"failed": "Failed"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"name": "Historial IA",
|
|
4
|
+
"namePlural": "Historial IA",
|
|
5
|
+
"description": "Auditoría genérica de interacciones con IA y métricas de costos"
|
|
6
|
+
},
|
|
7
|
+
"fields": {
|
|
8
|
+
"userId": "ID de Usuario",
|
|
9
|
+
"relatedEntityType": "Tipo de Entidad Relacionada",
|
|
10
|
+
"relatedEntityId": "ID de Entidad Relacionada",
|
|
11
|
+
"operation": "Operación",
|
|
12
|
+
"model": "Modelo IA",
|
|
13
|
+
"provider": "Proveedor",
|
|
14
|
+
"status": "Estado",
|
|
15
|
+
"tokensUsed": "Tokens Usados",
|
|
16
|
+
"creditsUsed": "Créditos Usados",
|
|
17
|
+
"estimatedCost": "Costo Estimado ($)",
|
|
18
|
+
"balanceAfter": "Balance Después",
|
|
19
|
+
"errorMessage": "Mensaje de Error",
|
|
20
|
+
"createdAt": "Creado el",
|
|
21
|
+
"completedAt": "Completado el"
|
|
22
|
+
},
|
|
23
|
+
"operations": {
|
|
24
|
+
"generate": "Generar",
|
|
25
|
+
"refine": "Refinar",
|
|
26
|
+
"analyze": "Analizar",
|
|
27
|
+
"chat": "Chat",
|
|
28
|
+
"completion": "Completado",
|
|
29
|
+
"other": "Otro"
|
|
30
|
+
},
|
|
31
|
+
"providers": {
|
|
32
|
+
"anthropic": "Anthropic",
|
|
33
|
+
"openai": "OpenAI",
|
|
34
|
+
"google": "Google",
|
|
35
|
+
"azure": "Azure",
|
|
36
|
+
"other": "Otro"
|
|
37
|
+
},
|
|
38
|
+
"actions": {
|
|
39
|
+
"create": "Crear Entrada de Historial",
|
|
40
|
+
"edit": "Editar Entrada de Historial",
|
|
41
|
+
"delete": "Eliminar Entrada de Historial",
|
|
42
|
+
"view": "Ver Entrada de Historial"
|
|
43
|
+
},
|
|
44
|
+
"messages": {
|
|
45
|
+
"created": "Entrada de historial creada exitosamente",
|
|
46
|
+
"updated": "Entrada de historial actualizada exitosamente",
|
|
47
|
+
"deleted": "Entrada de historial eliminada exitosamente",
|
|
48
|
+
"error": "Ocurrió un error"
|
|
49
|
+
},
|
|
50
|
+
"status": {
|
|
51
|
+
"pending": "Pendiente",
|
|
52
|
+
"processing": "Procesando",
|
|
53
|
+
"completed": "Completado",
|
|
54
|
+
"failed": "Fallido"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
-- =============================================
|
|
2
|
+
-- AI HISTORY ENTITY MIGRATION - PLUGIN ENTITY
|
|
3
|
+
-- Plugin: AI
|
|
4
|
+
-- Entity: ai-history
|
|
5
|
+
-- Generic AI interaction history for all plugins
|
|
6
|
+
-- Date: 2025-10-03
|
|
7
|
+
-- =============================================
|
|
8
|
+
|
|
9
|
+
-- =============================================
|
|
10
|
+
-- MAIN AI_HISTORY TABLE
|
|
11
|
+
-- =============================================
|
|
12
|
+
|
|
13
|
+
DROP TABLE IF EXISTS "ai_history" CASCADE;
|
|
14
|
+
|
|
15
|
+
CREATE TABLE "ai_history" (
|
|
16
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
17
|
+
"userId" TEXT NOT NULL REFERENCES "users"(id) ON DELETE CASCADE,
|
|
18
|
+
|
|
19
|
+
-- Generic polymorphic relationship (flexible for any future use case)
|
|
20
|
+
"relatedEntityType" TEXT,
|
|
21
|
+
"relatedEntityId" TEXT,
|
|
22
|
+
|
|
23
|
+
-- AI operation details
|
|
24
|
+
operation TEXT NOT NULL CHECK (operation IN ('generate', 'refine', 'analyze', 'chat', 'completion', 'other')),
|
|
25
|
+
model TEXT NOT NULL,
|
|
26
|
+
provider TEXT NOT NULL DEFAULT 'anthropic',
|
|
27
|
+
|
|
28
|
+
-- Metrics and cost tracking
|
|
29
|
+
"tokensUsed" INTEGER,
|
|
30
|
+
"tokensInput" INTEGER, -- ✅ Input tokens (prompt) - for precise cost calculation
|
|
31
|
+
"tokensOutput" INTEGER, -- ✅ Output tokens (completion) - for precise cost calculation
|
|
32
|
+
"creditsUsed" INTEGER,
|
|
33
|
+
"estimatedCost" DECIMAL(10, 6),
|
|
34
|
+
"balanceAfter" INTEGER,
|
|
35
|
+
|
|
36
|
+
-- Status tracking
|
|
37
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
|
38
|
+
|
|
39
|
+
-- Error tracking
|
|
40
|
+
"errorMessage" TEXT,
|
|
41
|
+
|
|
42
|
+
-- Timestamps
|
|
43
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
44
|
+
"completedAt" TIMESTAMPTZ
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- =============================================
|
|
48
|
+
-- INDEXES
|
|
49
|
+
-- =============================================
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_user_id ON "ai_history"("userId");
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_entity ON "ai_history"("relatedEntityType", "relatedEntityId") WHERE "relatedEntityType" IS NOT NULL;
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_status ON "ai_history"(status);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_operation ON "ai_history"(operation);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_model ON "ai_history"(model);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_provider ON "ai_history"(provider);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_created_at ON "ai_history"("createdAt" DESC);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_user_status ON "ai_history"("userId", status);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_user_operation ON "ai_history"("userId", operation);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_tokens_input ON "ai_history"("tokensInput") WHERE "tokensInput" IS NOT NULL;
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_ai_history_tokens_output ON "ai_history"("tokensOutput") WHERE "tokensOutput" IS NOT NULL;
|
|
62
|
+
|
|
63
|
+
-- =============================================
|
|
64
|
+
-- ROW LEVEL SECURITY (enableRLS: true)
|
|
65
|
+
-- =============================================
|
|
66
|
+
|
|
67
|
+
-- Enable RLS
|
|
68
|
+
ALTER TABLE "ai_history" ENABLE ROW LEVEL SECURITY;
|
|
69
|
+
|
|
70
|
+
-- Policies for ai_history table
|
|
71
|
+
DROP POLICY IF EXISTS "Users can view own ai history" ON "ai_history";
|
|
72
|
+
CREATE POLICY "Users can view own ai history" ON "ai_history"
|
|
73
|
+
FOR SELECT USING ("userId" = public.get_auth_user_id());
|
|
74
|
+
|
|
75
|
+
DROP POLICY IF EXISTS "Users can insert own ai history" ON "ai_history";
|
|
76
|
+
CREATE POLICY "Users can insert own ai history" ON "ai_history"
|
|
77
|
+
FOR INSERT WITH CHECK ("userId" = public.get_auth_user_id());
|
|
78
|
+
|
|
79
|
+
DROP POLICY IF EXISTS "Users can update own ai history" ON "ai_history";
|
|
80
|
+
CREATE POLICY "Users can update own ai history" ON "ai_history"
|
|
81
|
+
FOR UPDATE USING ("userId" = public.get_auth_user_id())
|
|
82
|
+
WITH CHECK ("userId" = public.get_auth_user_id());
|
|
83
|
+
|
|
84
|
+
DROP POLICY IF EXISTS "Users can delete own ai history" ON "ai_history";
|
|
85
|
+
CREATE POLICY "Users can delete own ai history" ON "ai_history"
|
|
86
|
+
FOR DELETE USING ("userId" = public.get_auth_user_id());
|
|
87
|
+
|
|
88
|
+
-- Admins can manage all ai history
|
|
89
|
+
DROP POLICY IF EXISTS "Admins can manage all ai history" ON "ai_history";
|
|
90
|
+
CREATE POLICY "Admins can manage all ai history" ON "ai_history"
|
|
91
|
+
FOR ALL USING (
|
|
92
|
+
EXISTS (
|
|
93
|
+
SELECT 1 FROM "users"
|
|
94
|
+
WHERE id = public.get_auth_user_id()
|
|
95
|
+
AND role IN ('admin','superadmin')
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
WITH CHECK (
|
|
99
|
+
EXISTS (
|
|
100
|
+
SELECT 1 FROM "users"
|
|
101
|
+
WHERE id = public.get_auth_user_id()
|
|
102
|
+
AND role IN ('admin','superadmin')
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
-- =============================================
|
|
107
|
+
-- TRIGGER: Auto-update completedAt
|
|
108
|
+
-- =============================================
|
|
109
|
+
CREATE OR REPLACE FUNCTION update_ai_history_completed_at()
|
|
110
|
+
RETURNS TRIGGER AS $$
|
|
111
|
+
BEGIN
|
|
112
|
+
IF NEW.status IN ('completed', 'failed') AND OLD.status NOT IN ('completed', 'failed') THEN
|
|
113
|
+
NEW."completedAt" = now();
|
|
114
|
+
END IF;
|
|
115
|
+
RETURN NEW;
|
|
116
|
+
END;
|
|
117
|
+
$$ LANGUAGE plpgsql;
|
|
118
|
+
|
|
119
|
+
CREATE TRIGGER ai_history_completed_at
|
|
120
|
+
BEFORE UPDATE ON "ai_history"
|
|
121
|
+
FOR EACH ROW
|
|
122
|
+
EXECUTE FUNCTION update_ai_history_completed_at();
|
|
123
|
+
|
|
124
|
+
-- =============================================
|
|
125
|
+
-- CONSTRAINT: Token Split Validation
|
|
126
|
+
-- =============================================
|
|
127
|
+
-- Ensures data integrity: tokensUsed = tokensInput + tokensOutput when both present
|
|
128
|
+
ALTER TABLE "ai_history"
|
|
129
|
+
ADD CONSTRAINT chk_tokens_sum
|
|
130
|
+
CHECK (
|
|
131
|
+
("tokensInput" IS NULL AND "tokensOutput" IS NULL)
|
|
132
|
+
OR
|
|
133
|
+
("tokensUsed" = "tokensInput" + "tokensOutput")
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
-- =============================================
|
|
137
|
+
-- COMMENTS
|
|
138
|
+
-- =============================================
|
|
139
|
+
COMMENT ON TABLE "ai_history" IS 'Generic audit trail for AI interactions with cost and performance metrics';
|
|
140
|
+
COMMENT ON COLUMN "ai_history"."userId" IS 'User who initiated the AI operation';
|
|
141
|
+
COMMENT ON COLUMN "ai_history"."relatedEntityType" IS 'Type of entity this operation relates to (e.g., ''contents'', ''products'', ''campaigns'')';
|
|
142
|
+
COMMENT ON COLUMN "ai_history"."relatedEntityId" IS 'ID of the related entity (polymorphic relationship)';
|
|
143
|
+
COMMENT ON COLUMN "ai_history".operation IS 'Type of AI operation: generate, refine, analyze, chat, completion, or other';
|
|
144
|
+
COMMENT ON COLUMN "ai_history".model IS 'AI model used (e.g., claude-3-5-sonnet-20241022, gpt-4)';
|
|
145
|
+
COMMENT ON COLUMN "ai_history".provider IS 'AI provider: anthropic, openai, etc.';
|
|
146
|
+
COMMENT ON COLUMN "ai_history".status IS 'Processing status: pending, processing, completed, or failed';
|
|
147
|
+
COMMENT ON COLUMN "ai_history"."tokensUsed" IS 'Total tokens consumed (input + output) - kept for backward compatibility';
|
|
148
|
+
COMMENT ON COLUMN "ai_history"."tokensInput" IS 'Input tokens (prompt) - for precise cost calculation with asymmetric pricing';
|
|
149
|
+
COMMENT ON COLUMN "ai_history"."tokensOutput" IS 'Output tokens (completion) - for precise cost calculation with asymmetric pricing';
|
|
150
|
+
COMMENT ON COLUMN "ai_history"."creditsUsed" IS 'Credits deducted for this operation';
|
|
151
|
+
COMMENT ON COLUMN "ai_history"."estimatedCost" IS 'Cost in USD based on model pricing';
|
|
152
|
+
COMMENT ON COLUMN "ai_history"."balanceAfter" IS 'User credit balance after this operation';
|
|
153
|
+
COMMENT ON COLUMN "ai_history"."errorMessage" IS 'Error message if operation failed';
|
|
154
|
+
COMMENT ON COLUMN "ai_history"."createdAt" IS 'When the AI operation was initiated';
|
|
155
|
+
COMMENT ON COLUMN "ai_history"."completedAt" IS 'When the AI operation finished (success or failure)';
|
|
156
|
+
|
|
157
|
+
-- =============================================
|
|
158
|
+
-- MIGRATION COMPLETE
|
|
159
|
+
-- =============================================
|
|
160
|
+
|
|
161
|
+
DO $$
|
|
162
|
+
BEGIN
|
|
163
|
+
RAISE NOTICE 'AI History entity migration completed successfully!';
|
|
164
|
+
RAISE NOTICE 'Plugin: ai';
|
|
165
|
+
RAISE NOTICE 'Table created: ai_history';
|
|
166
|
+
RAISE NOTICE 'RLS policies enabled for user isolation';
|
|
167
|
+
END $$;
|