@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,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
|
+
}
|