@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.
Files changed (34) hide show
  1. package/.env.example +79 -0
  2. package/README.md +529 -0
  3. package/api/README.md +65 -0
  4. package/api/ai-history/[id]/route.ts +112 -0
  5. package/api/embeddings/route.ts +129 -0
  6. package/api/generate/route.ts +160 -0
  7. package/docs/01-getting-started/01-introduction.md +237 -0
  8. package/docs/01-getting-started/02-installation.md +447 -0
  9. package/docs/01-getting-started/03-configuration.md +416 -0
  10. package/docs/02-features/01-text-generation.md +523 -0
  11. package/docs/02-features/02-embeddings.md +241 -0
  12. package/docs/02-features/03-ai-history.md +549 -0
  13. package/docs/03-advanced-usage/01-core-utilities.md +500 -0
  14. package/docs/04-use-cases/01-content-generation.md +453 -0
  15. package/entities/ai-history/ai-history.config.ts +123 -0
  16. package/entities/ai-history/ai-history.fields.ts +330 -0
  17. package/entities/ai-history/messages/en.json +56 -0
  18. package/entities/ai-history/messages/es.json +56 -0
  19. package/entities/ai-history/migrations/001_ai_history_table.sql +167 -0
  20. package/entities/ai-history/migrations/002_ai_history_metas.sql +103 -0
  21. package/lib/ai-history-meta-service.ts +379 -0
  22. package/lib/ai-history-service.ts +391 -0
  23. package/lib/ai-sdk.ts +7 -0
  24. package/lib/core-utils.ts +217 -0
  25. package/lib/plugin-env.ts +252 -0
  26. package/lib/sanitize.ts +122 -0
  27. package/lib/save-example.ts +237 -0
  28. package/lib/server-env.ts +104 -0
  29. package/package.json +23 -0
  30. package/plugin.config.ts +55 -0
  31. package/public/docs/login-404-error.png +0 -0
  32. package/tsconfig.json +47 -0
  33. package/tsconfig.tsbuildinfo +1 -0
  34. 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 $$;