@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,103 @@
1
+ -- =============================================
2
+ -- AI HISTORY METADATA SYSTEM
3
+ -- Plugin: AI
4
+ -- Entity: ai-history
5
+ -- =============================================
6
+ --
7
+ -- This migration adds comprehensive metadata capabilities through ai_history_metas table
8
+ --
9
+ -- Purpose: Flexible metadata storage for AI operations
10
+ -- - Better query performance (indexed key-value lookups)
11
+ -- - Granular auditing (when was each meta value set)
12
+ -- - Flexible schema (any metadata without ALTER TABLE)
13
+ -- - Type hints for client-side parsing
14
+ --
15
+ -- Common metadata keys:
16
+ -- - sourceOperationId: UUID linking to parent operation (for refine chains)
17
+ -- - userInstruction: Custom user instruction for refinements
18
+ -- - temperature: AI generation temperature setting
19
+ -- - tone: Content tone (casual, professional, etc.)
20
+ -- - platform: Social media platform target (instagram, tiktok, etc.)
21
+ -- - audience: Target audience description
22
+ -- - topic: Content topic
23
+ -- - language: Content language (en, es, etc.)
24
+ --
25
+ -- Date: 2025-11-04
26
+ -- =============================================
27
+
28
+ -- =============================================
29
+ -- AI_HISTORY_METAS TABLE
30
+ -- =============================================
31
+
32
+ CREATE TABLE IF NOT EXISTS public."ai_history_metas" (
33
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
34
+ "entityId" TEXT NOT NULL REFERENCES public."ai_history"(id) ON DELETE CASCADE,
35
+ "metaKey" TEXT NOT NULL,
36
+ "metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
37
+ "dataType" TEXT DEFAULT 'json' CHECK ("dataType" IN ('string', 'number', 'boolean', 'json', 'array')),
38
+ "isPublic" BOOLEAN NOT NULL DEFAULT false,
39
+ "isSearchable" BOOLEAN NOT NULL DEFAULT false,
40
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
41
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
42
+
43
+ -- Prevent duplicate keys per history record
44
+ CONSTRAINT ai_history_metas_unique_key UNIQUE ("entityId", "metaKey")
45
+ );
46
+
47
+ -- Trigger for auto-updating updatedAt (using Better Auth's function)
48
+ CREATE TRIGGER ai_history_metas_set_updated_at
49
+ BEFORE UPDATE ON public."ai_history_metas"
50
+ FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
51
+
52
+ -- Indexes for efficient queries
53
+ CREATE INDEX idx_ai_history_metas_entity_id ON public."ai_history_metas"("entityId");
54
+ CREATE INDEX idx_ai_history_metas_key ON public."ai_history_metas"("metaKey");
55
+ CREATE INDEX idx_ai_history_metas_composite ON public."ai_history_metas"("entityId", "metaKey");
56
+
57
+ -- GIN index for JSONB queries (enables @>, ?, ?&, ?| operators)
58
+ CREATE INDEX idx_ai_history_metas_value_gin ON public."ai_history_metas" USING GIN ("metaValue");
59
+
60
+ -- RLS Policies
61
+ ALTER TABLE public."ai_history_metas" ENABLE ROW LEVEL SECURITY;
62
+
63
+ -- Cleanup existing policies
64
+ DROP POLICY IF EXISTS "AI history metas owner can do all" ON public."ai_history_metas";
65
+ DROP POLICY IF EXISTS "AI history metas inherit parent access" ON public."ai_history_metas";
66
+
67
+ -- Policy: Inherit access from parent ai_history record
68
+ CREATE POLICY "AI history metas inherit parent access"
69
+ ON public."ai_history_metas"
70
+ FOR ALL TO authenticated
71
+ USING (
72
+ EXISTS (
73
+ SELECT 1 FROM public."ai_history"
74
+ WHERE "ai_history".id = "ai_history_metas"."entityId"
75
+ AND "ai_history"."userId" = public.get_auth_user_id()
76
+ )
77
+ )
78
+ WITH CHECK (
79
+ EXISTS (
80
+ SELECT 1 FROM public."ai_history"
81
+ WHERE "ai_history".id = "ai_history_metas"."entityId"
82
+ AND "ai_history"."userId" = public.get_auth_user_id()
83
+ )
84
+ );
85
+
86
+ -- Comments for documentation
87
+ COMMENT ON TABLE public."ai_history_metas" IS 'Flexible metadata storage for AI history records. Supports any key-value pairs with JSONB values.';
88
+ COMMENT ON COLUMN public."ai_history_metas"."metaValue" IS 'JSONB value allows storing any data structure (string, number, boolean, object, array)';
89
+ COMMENT ON COLUMN public."ai_history_metas"."dataType" IS 'Hint for parsing metaValue on client side (actual type is always JSONB in DB)';
90
+ COMMENT ON COLUMN public."ai_history_metas"."isPublic" IS 'Whether this meta is publicly accessible or private to owner';
91
+ COMMENT ON COLUMN public."ai_history_metas"."isSearchable" IS 'Whether this meta should be included in search operations';
92
+
93
+ -- =============================================
94
+ -- MIGRATION COMPLETE
95
+ -- =============================================
96
+
97
+ DO $$
98
+ BEGIN
99
+ RAISE NOTICE 'AI History metadata migration completed successfully!';
100
+ RAISE NOTICE 'Created: ai_history_metas table for flexible metadata';
101
+ RAISE NOTICE 'Performance: GIN indexes for fast JSONB queries';
102
+ RAISE NOTICE 'Security: RLS policies enabled on ai_history_metas';
103
+ END $$;
@@ -0,0 +1,379 @@
1
+ import { MetaService } from '@nextsparkjs/core/lib/services/meta.service';
2
+ import { CreateMetaPayload } from '@nextsparkjs/core/types/meta.types';
3
+
4
+ /**
5
+ * AI History Metadata Service
6
+ *
7
+ * Specialized service for managing ai_history metadata using the core MetaService.
8
+ * Provides ai_history-specific helper methods for common metadata operations.
9
+ *
10
+ * IMPORTANT: All metadata is stored in ai_history_metas table via completeOperation(metas).
11
+ * The ai_history table itself has NO metadata column - only structured fields.
12
+ *
13
+ * ============================================
14
+ * METADATA BY ENDPOINT (Standardized)
15
+ * ============================================
16
+ *
17
+ * analyze-brief (/api/v1/theme/content-buddy/analyze-brief):
18
+ * - briefText: Original brief text (for auditing/debugging)
19
+ * - briefLength: Length of brief text
20
+ * - extractedFieldsCount: Number of fields extracted
21
+ * - audiencesCount: Number of audience entities extracted
22
+ * - objectivesCount: Number of objective entities extracted
23
+ * - productsCount: Number of product entities extracted
24
+ * - totalChildEntities: Total child entities extracted
25
+ * - clientName: Extracted client name
26
+ * - clientIndustry: Extracted client industry
27
+ *
28
+ * generate-content (/api/v1/theme/content-buddy/generate-content):
29
+ * Core generation parameters (reproducibility):
30
+ * - temperature: AI model temperature used
31
+ * - tone: Content tone
32
+ * - brandVoice: Brand voice style
33
+ * - audience: Target audience description
34
+ * - topic: Content topic
35
+ * - postType: Type of post
36
+ * - language: Content language code (e.g., "en", "es")
37
+ *
38
+ * Content specifications (analytics):
39
+ * - wordCount: Target word count
40
+ * - keywords: Comma-separated keywords (e.g., "innovation,tech,ai")
41
+ * - additionalRequirements: Custom user instructions
42
+ *
43
+ * Platform analytics:
44
+ * - platforms: Comma-separated platform list (e.g., "instagram,linkedin,twitter")
45
+ * - platformCount: Number of platforms generated for
46
+ *
47
+ * Multimodal analytics (feature usage tracking):
48
+ * - imageCount: Number of images used in generation
49
+ * - isMultimodal: Boolean - whether images were used
50
+ *
51
+ * Entity relationships (CRITICAL for business analytics):
52
+ * - clientId: Which client generated this content
53
+ * - productId: Related product (if specified)
54
+ * - objectiveId: Marketing objective (if specified)
55
+ * - targetAudienceId: Specific audience segment (if specified)
56
+ *
57
+ * refine-content (/api/v1/theme/content-buddy/refine-content):
58
+ * - userInstruction: User's refinement instruction
59
+ * - sourceOperationId: UUID linking to source operation (for workflow tracking)
60
+ * - temperature: AI model temperature used
61
+ * - tone: Desired tone (if specified)
62
+ * - platform: Social media platform being refined for
63
+ *
64
+ * ============================================
65
+ * DEPRECATED METADATA (Use ai_history columns instead)
66
+ * ============================================
67
+ * - tokensInput: Use ai_history.tokensInput column
68
+ * - tokensOutput: Use ai_history.tokensOutput column
69
+ * - operation: Use ai_history.operation column
70
+ * - model: Use ai_history.model column
71
+ * - provider: Use ai_history.provider column
72
+ */
73
+ export class AIHistoryMetaService {
74
+ private static readonly ENTITY_TYPE = 'ai-history'; // ✅ Must match entity registry name (with hyphen, not underscore)
75
+
76
+ /**
77
+ * Get all metadata for an AI history record
78
+ */
79
+ static async getAllMetas(
80
+ historyId: string,
81
+ userId: string,
82
+ includePrivate: boolean = true
83
+ ): Promise<Record<string, unknown>> {
84
+ return MetaService.getEntityMetas(
85
+ this.ENTITY_TYPE,
86
+ historyId,
87
+ userId,
88
+ includePrivate
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Get specific metadata value
94
+ */
95
+ static async getMeta(
96
+ historyId: string,
97
+ metaKey: string,
98
+ userId: string
99
+ ): Promise<unknown> {
100
+ return MetaService.getEntityMeta(
101
+ this.ENTITY_TYPE,
102
+ historyId,
103
+ metaKey,
104
+ userId
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Set a single metadata value
110
+ */
111
+ static async setMeta(
112
+ historyId: string,
113
+ metaKey: string,
114
+ metaValue: unknown,
115
+ userId: string,
116
+ options: Partial<CreateMetaPayload> = {}
117
+ ): Promise<void> {
118
+ return MetaService.setEntityMeta(
119
+ this.ENTITY_TYPE,
120
+ historyId,
121
+ metaKey,
122
+ metaValue,
123
+ userId,
124
+ options
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Set multiple metadata values in batch
130
+ */
131
+ static async setBulkMetas(
132
+ historyId: string,
133
+ metas: Record<string, unknown>,
134
+ userId: string,
135
+ options: Partial<CreateMetaPayload> = {}
136
+ ): Promise<void> {
137
+ return MetaService.setBulkEntityMetas(
138
+ this.ENTITY_TYPE,
139
+ historyId,
140
+ metas,
141
+ userId,
142
+ options
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Delete a metadata key
148
+ */
149
+ static async deleteMeta(
150
+ historyId: string,
151
+ metaKey: string,
152
+ userId: string
153
+ ): Promise<void> {
154
+ return MetaService.deleteEntityMeta(
155
+ this.ENTITY_TYPE,
156
+ historyId,
157
+ metaKey,
158
+ userId
159
+ );
160
+ }
161
+
162
+ /**
163
+ * Delete all metadata for an AI history record
164
+ */
165
+ static async deleteAllMetas(
166
+ historyId: string,
167
+ userId: string
168
+ ): Promise<void> {
169
+ return MetaService.deleteAllEntityMetas(
170
+ this.ENTITY_TYPE,
171
+ historyId,
172
+ userId
173
+ );
174
+ }
175
+
176
+ // ============================================
177
+ // AI History-Specific Helper Methods
178
+ // ============================================
179
+
180
+ /**
181
+ * Set source operation ID (for refine chains)
182
+ * Example: refine(B) → sourceOperationId = generate(A)
183
+ */
184
+ static async setSourceOperationId(
185
+ historyId: string,
186
+ sourceOperationId: string,
187
+ userId: string
188
+ ): Promise<void> {
189
+ return this.setMeta(
190
+ historyId,
191
+ 'sourceOperationId',
192
+ sourceOperationId,
193
+ userId,
194
+ { dataType: 'string', isPublic: false, isSearchable: false }
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Get source operation ID (for workflow tracking)
200
+ */
201
+ static async getSourceOperationId(
202
+ historyId: string,
203
+ userId: string
204
+ ): Promise<string | null> {
205
+ const value = await this.getMeta(historyId, 'sourceOperationId', userId);
206
+ return value as string | null;
207
+ }
208
+
209
+ /**
210
+ * Set user instruction (for refinements)
211
+ */
212
+ static async setUserInstruction(
213
+ historyId: string,
214
+ instruction: string,
215
+ userId: string
216
+ ): Promise<void> {
217
+ return this.setMeta(
218
+ historyId,
219
+ 'userInstruction',
220
+ instruction,
221
+ userId,
222
+ { dataType: 'string', isPublic: false, isSearchable: true }
223
+ );
224
+ }
225
+
226
+ /**
227
+ * Get user instruction
228
+ */
229
+ static async getUserInstruction(
230
+ historyId: string,
231
+ userId: string
232
+ ): Promise<string | null> {
233
+ const value = await this.getMeta(historyId, 'userInstruction', userId);
234
+ return value as string | null;
235
+ }
236
+
237
+ /**
238
+ * Set generation parameters (temperature, tone, platform, etc.)
239
+ */
240
+ static async setGenerationParams(
241
+ historyId: string,
242
+ params: {
243
+ temperature?: number;
244
+ tone?: string;
245
+ platform?: string;
246
+ style?: string;
247
+ audienceType?: string;
248
+ [key: string]: unknown;
249
+ },
250
+ userId: string
251
+ ): Promise<void> {
252
+ const metas: Record<string, unknown> = {};
253
+ const options: Partial<CreateMetaPayload> = {
254
+ isPublic: false,
255
+ isSearchable: true
256
+ };
257
+
258
+ if (params.temperature !== undefined) {
259
+ metas.temperature = params.temperature;
260
+ }
261
+ if (params.tone !== undefined) {
262
+ metas.tone = params.tone;
263
+ }
264
+ if (params.platform !== undefined) {
265
+ metas.platform = params.platform;
266
+ }
267
+ if (params.style !== undefined) {
268
+ metas.style = params.style;
269
+ }
270
+ if (params.audienceType !== undefined) {
271
+ metas.audienceType = params.audienceType;
272
+ }
273
+
274
+ // Include any additional custom params
275
+ Object.keys(params).forEach(key => {
276
+ if (!['temperature', 'tone', 'platform', 'style', 'audienceType'].includes(key)) {
277
+ metas[key] = params[key];
278
+ }
279
+ });
280
+
281
+ if (Object.keys(metas).length > 0) {
282
+ return this.setBulkMetas(historyId, metas, userId, options);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Get generation parameters
288
+ */
289
+ static async getGenerationParams(
290
+ historyId: string,
291
+ userId: string
292
+ ): Promise<Record<string, unknown>> {
293
+ const allMetas = await this.getAllMetas(historyId, userId, true);
294
+ const params: Record<string, unknown> = {};
295
+
296
+ // Extract known generation params
297
+ const knownParams = ['temperature', 'tone', 'platform', 'style', 'audienceType'];
298
+ knownParams.forEach(key => {
299
+ if (key in allMetas) {
300
+ params[key] = allMetas[key];
301
+ }
302
+ });
303
+
304
+ return params;
305
+ }
306
+
307
+ /**
308
+ * Search AI history records by metadata
309
+ * Example: Find all refinements with specific instruction
310
+ */
311
+ static async searchByMeta(
312
+ metaKey: string,
313
+ metaValue: unknown,
314
+ userId: string,
315
+ limit: number = 100,
316
+ offset: number = 0
317
+ ): Promise<{ historyIds: string[], total: number }> {
318
+ const result = await MetaService.searchByMeta(
319
+ this.ENTITY_TYPE,
320
+ metaKey,
321
+ metaValue,
322
+ userId,
323
+ limit,
324
+ offset
325
+ );
326
+
327
+ return {
328
+ historyIds: result.entities,
329
+ total: result.total
330
+ };
331
+ }
332
+
333
+ /**
334
+ * Get metadata for multiple AI history records (bulk)
335
+ * Useful for analytics and reporting
336
+ */
337
+ static async getBulkMetas(
338
+ historyIds: string[],
339
+ userId: string,
340
+ includePrivate: boolean = true
341
+ ): Promise<Record<string, Record<string, unknown>>> {
342
+ return MetaService.getBulkEntityMetas(
343
+ this.ENTITY_TYPE,
344
+ historyIds,
345
+ userId,
346
+ includePrivate
347
+ );
348
+ }
349
+
350
+ /**
351
+ * Get specific metadata keys for multiple AI history records (bulk)
352
+ */
353
+ static async getBulkSpecificMetas(
354
+ historyIds: string[],
355
+ metaKeys: string[],
356
+ userId: string
357
+ ): Promise<Record<string, Record<string, unknown>>> {
358
+ return MetaService.getBulkSpecificEntityMetas(
359
+ this.ENTITY_TYPE,
360
+ historyIds,
361
+ metaKeys,
362
+ userId
363
+ );
364
+ }
365
+
366
+ /**
367
+ * Count metadata entries for an AI history record
368
+ */
369
+ static async countMetas(
370
+ historyId: string,
371
+ userId: string
372
+ ): Promise<number> {
373
+ return MetaService.countEntityMetas(
374
+ this.ENTITY_TYPE,
375
+ historyId,
376
+ userId
377
+ );
378
+ }
379
+ }