@nextsparkjs/plugin-ai 0.1.0-beta.74 → 0.1.0-beta.76

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.
@@ -80,20 +80,25 @@ const postHandler = async (request: NextRequest) => {
80
80
 
81
81
  // 8. Save example if requested (opt-in)
82
82
  if (saveExample) {
83
+ const teamId = request.headers.get('x-team-id') || ''
83
84
  await saveExampleSafely(
84
85
  {
85
86
  prompt,
86
87
  response: result.text,
87
88
  model: selectedModel.modelName,
88
89
  status: 'completed',
90
+ operation: 'generate',
91
+ provider: selectedModel.provider,
89
92
  metadata: {
90
93
  tokens: tokens.total,
94
+ tokensInput: tokens.input,
95
+ tokensOutput: tokens.output,
91
96
  cost,
92
- provider: selectedModel.provider,
93
97
  isLocal: selectedModel.isLocal
94
98
  }
95
99
  },
96
- authResult.user!.id
100
+ authResult.user!.id,
101
+ teamId
97
102
  )
98
103
  }
99
104
 
@@ -17,7 +17,8 @@ export const aiHistoryEntityConfig: EntityConfig = {
17
17
  // ==========================================
18
18
  // 1. BASIC IDENTIFICATION
19
19
  // ==========================================
20
- slug: 'ai-history', // Single source of truth - derives tableName, apiPath, metaTableName, i18nNamespace
20
+ slug: 'ai-history',
21
+ tableName: 'ai_history', // Explicit table name (underscores, not hyphens)
21
22
  enabled: true,
22
23
  names: {
23
24
  singular: 'AI History',
@@ -1,7 +1,7 @@
1
1
  -- =============================================
2
2
  -- AI HISTORY ENTITY MIGRATION - PLUGIN ENTITY
3
3
  -- Plugin: AI
4
- -- Entity: ai-history
4
+ -- Entity: ai_history
5
5
  -- Generic AI interaction history for all plugins
6
6
  -- Date: 2025-10-03
7
7
  -- =============================================
@@ -10,11 +10,12 @@
10
10
  -- MAIN AI_HISTORY TABLE
11
11
  -- =============================================
12
12
 
13
- DROP TABLE IF EXISTS "ai-history" CASCADE;
13
+ DROP TABLE IF EXISTS "ai_history" CASCADE;
14
14
 
15
- CREATE TABLE "ai-history" (
15
+ CREATE TABLE "ai_history" (
16
16
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
17
17
  "userId" TEXT NOT NULL REFERENCES "users"(id) ON DELETE CASCADE,
18
+ "teamId" TEXT NOT NULL REFERENCES "teams"(id) ON DELETE CASCADE,
18
19
 
19
20
  -- Generic polymorphic relationship (flexible for any future use case)
20
21
  "relatedEntityType" TEXT,
@@ -49,46 +50,48 @@ CREATE TABLE "ai-history" (
49
50
  -- INDEXES
50
51
  -- =============================================
51
52
 
52
- CREATE INDEX IF NOT EXISTS idx_ai-history_user_id ON "ai-history"("userId");
53
- CREATE INDEX IF NOT EXISTS idx_ai-history_entity ON "ai-history"("relatedEntityType", "relatedEntityId") WHERE "relatedEntityType" IS NOT NULL;
54
- CREATE INDEX IF NOT EXISTS idx_ai-history_status ON "ai-history"(status);
55
- CREATE INDEX IF NOT EXISTS idx_ai-history_operation ON "ai-history"(operation);
56
- CREATE INDEX IF NOT EXISTS idx_ai-history_model ON "ai-history"(model);
57
- CREATE INDEX IF NOT EXISTS idx_ai-history_provider ON "ai-history"(provider);
58
- CREATE INDEX IF NOT EXISTS idx_ai-history_created_at ON "ai-history"("createdAt" DESC);
59
- CREATE INDEX IF NOT EXISTS idx_ai-history_user_status ON "ai-history"("userId", status);
60
- CREATE INDEX IF NOT EXISTS idx_ai-history_user_operation ON "ai-history"("userId", operation);
61
- CREATE INDEX IF NOT EXISTS idx_ai-history_tokens_input ON "ai-history"("tokensInput") WHERE "tokensInput" IS NOT NULL;
62
- CREATE INDEX IF NOT EXISTS idx_ai-history_tokens_output ON "ai-history"("tokensOutput") WHERE "tokensOutput" IS NOT NULL;
53
+ CREATE INDEX IF NOT EXISTS idx_ai_history_user_id ON "ai_history"("userId");
54
+ CREATE INDEX IF NOT EXISTS idx_ai_history_team_id ON "ai_history"("teamId");
55
+ CREATE INDEX IF NOT EXISTS idx_ai_history_team_user ON "ai_history"("teamId", "userId");
56
+ CREATE INDEX IF NOT EXISTS idx_ai_history_entity ON "ai_history"("relatedEntityType", "relatedEntityId") WHERE "relatedEntityType" IS NOT NULL;
57
+ CREATE INDEX IF NOT EXISTS idx_ai_history_status ON "ai_history"(status);
58
+ CREATE INDEX IF NOT EXISTS idx_ai_history_operation ON "ai_history"(operation);
59
+ CREATE INDEX IF NOT EXISTS idx_ai_history_model ON "ai_history"(model);
60
+ CREATE INDEX IF NOT EXISTS idx_ai_history_provider ON "ai_history"(provider);
61
+ CREATE INDEX IF NOT EXISTS idx_ai_history_created_at ON "ai_history"("createdAt" DESC);
62
+ CREATE INDEX IF NOT EXISTS idx_ai_history_user_status ON "ai_history"("userId", status);
63
+ CREATE INDEX IF NOT EXISTS idx_ai_history_user_operation ON "ai_history"("userId", operation);
64
+ CREATE INDEX IF NOT EXISTS idx_ai_history_tokens_input ON "ai_history"("tokensInput") WHERE "tokensInput" IS NOT NULL;
65
+ CREATE INDEX IF NOT EXISTS idx_ai_history_tokens_output ON "ai_history"("tokensOutput") WHERE "tokensOutput" IS NOT NULL;
63
66
 
64
67
  -- =============================================
65
68
  -- ROW LEVEL SECURITY (enableRLS: true)
66
69
  -- =============================================
67
70
 
68
71
  -- Enable RLS
69
- ALTER TABLE "ai-history" ENABLE ROW LEVEL SECURITY;
72
+ ALTER TABLE "ai_history" ENABLE ROW LEVEL SECURITY;
70
73
 
71
- -- Policies for ai-history table
72
- DROP POLICY IF EXISTS "Users can view own ai history" ON "ai-history";
73
- CREATE POLICY "Users can view own ai history" ON "ai-history"
74
+ -- Policies for ai_history table
75
+ DROP POLICY IF EXISTS "Users can view own ai history" ON "ai_history";
76
+ CREATE POLICY "Users can view own ai history" ON "ai_history"
74
77
  FOR SELECT USING ("userId" = public.get_auth_user_id());
75
78
 
76
- DROP POLICY IF EXISTS "Users can insert own ai history" ON "ai-history";
77
- CREATE POLICY "Users can insert own ai history" ON "ai-history"
79
+ DROP POLICY IF EXISTS "Users can insert own ai history" ON "ai_history";
80
+ CREATE POLICY "Users can insert own ai history" ON "ai_history"
78
81
  FOR INSERT WITH CHECK ("userId" = public.get_auth_user_id());
79
82
 
80
- DROP POLICY IF EXISTS "Users can update own ai history" ON "ai-history";
81
- CREATE POLICY "Users can update own ai history" ON "ai-history"
83
+ DROP POLICY IF EXISTS "Users can update own ai history" ON "ai_history";
84
+ CREATE POLICY "Users can update own ai history" ON "ai_history"
82
85
  FOR UPDATE USING ("userId" = public.get_auth_user_id())
83
86
  WITH CHECK ("userId" = public.get_auth_user_id());
84
87
 
85
- DROP POLICY IF EXISTS "Users can delete own ai history" ON "ai-history";
86
- CREATE POLICY "Users can delete own ai history" ON "ai-history"
88
+ DROP POLICY IF EXISTS "Users can delete own ai history" ON "ai_history";
89
+ CREATE POLICY "Users can delete own ai history" ON "ai_history"
87
90
  FOR DELETE USING ("userId" = public.get_auth_user_id());
88
91
 
89
92
  -- Admins can manage all ai history
90
- DROP POLICY IF EXISTS "Admins can manage all ai history" ON "ai-history";
91
- CREATE POLICY "Admins can manage all ai history" ON "ai-history"
93
+ DROP POLICY IF EXISTS "Admins can manage all ai history" ON "ai_history";
94
+ CREATE POLICY "Admins can manage all ai history" ON "ai_history"
92
95
  FOR ALL USING (
93
96
  EXISTS (
94
97
  SELECT 1 FROM "users"
@@ -107,14 +110,14 @@ CREATE POLICY "Admins can manage all ai history" ON "ai-history"
107
110
  -- =============================================
108
111
  -- TRIGGER: Auto-update updatedAt
109
112
  -- =============================================
110
- CREATE TRIGGER ai-history_set_updated_at
111
- BEFORE UPDATE ON "ai-history"
113
+ CREATE TRIGGER ai_history_set_updated_at
114
+ BEFORE UPDATE ON "ai_history"
112
115
  FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
113
116
 
114
117
  -- =============================================
115
118
  -- TRIGGER: Auto-update completedAt
116
119
  -- =============================================
117
- CREATE OR REPLACE FUNCTION update_ai-history_completed_at()
120
+ CREATE OR REPLACE FUNCTION update_ai_history_completed_at()
118
121
  RETURNS TRIGGER AS $$
119
122
  BEGIN
120
123
  IF NEW.status IN ('completed', 'failed') AND OLD.status NOT IN ('completed', 'failed') THEN
@@ -124,16 +127,16 @@ BEGIN
124
127
  END;
125
128
  $$ LANGUAGE plpgsql;
126
129
 
127
- CREATE TRIGGER ai-history_completed_at
128
- BEFORE UPDATE ON "ai-history"
130
+ CREATE TRIGGER ai_history_completed_at
131
+ BEFORE UPDATE ON "ai_history"
129
132
  FOR EACH ROW
130
- EXECUTE FUNCTION update_ai-history_completed_at();
133
+ EXECUTE FUNCTION update_ai_history_completed_at();
131
134
 
132
135
  -- =============================================
133
136
  -- CONSTRAINT: Token Split Validation
134
137
  -- =============================================
135
138
  -- Ensures data integrity: tokensUsed = tokensInput + tokensOutput when both present
136
- ALTER TABLE "ai-history"
139
+ ALTER TABLE "ai_history"
137
140
  ADD CONSTRAINT chk_tokens_sum
138
141
  CHECK (
139
142
  ("tokensInput" IS NULL AND "tokensOutput" IS NULL)
@@ -144,24 +147,24 @@ CHECK (
144
147
  -- =============================================
145
148
  -- COMMENTS
146
149
  -- =============================================
147
- COMMENT ON TABLE "ai-history" IS 'Generic audit trail for AI interactions with cost and performance metrics';
148
- COMMENT ON COLUMN "ai-history"."userId" IS 'User who initiated the AI operation';
149
- COMMENT ON COLUMN "ai-history"."relatedEntityType" IS 'Type of entity this operation relates to (e.g., ''contents'', ''products'', ''campaigns'')';
150
- COMMENT ON COLUMN "ai-history"."relatedEntityId" IS 'ID of the related entity (polymorphic relationship)';
151
- COMMENT ON COLUMN "ai-history".operation IS 'Type of AI operation: generate, refine, analyze, chat, completion, or other';
152
- COMMENT ON COLUMN "ai-history".model IS 'AI model used (e.g., claude-3-5-sonnet-20241022, gpt-4)';
153
- COMMENT ON COLUMN "ai-history".provider IS 'AI provider: anthropic, openai, etc.';
154
- COMMENT ON COLUMN "ai-history".status IS 'Processing status: pending, processing, completed, or failed';
155
- COMMENT ON COLUMN "ai-history"."tokensUsed" IS 'Total tokens consumed (input + output) - kept for backward compatibility';
156
- COMMENT ON COLUMN "ai-history"."tokensInput" IS 'Input tokens (prompt) - for precise cost calculation with asymmetric pricing';
157
- COMMENT ON COLUMN "ai-history"."tokensOutput" IS 'Output tokens (completion) - for precise cost calculation with asymmetric pricing';
158
- COMMENT ON COLUMN "ai-history"."creditsUsed" IS 'Credits deducted for this operation';
159
- COMMENT ON COLUMN "ai-history"."estimatedCost" IS 'Cost in USD based on model pricing';
160
- COMMENT ON COLUMN "ai-history"."balanceAfter" IS 'User credit balance after this operation';
161
- COMMENT ON COLUMN "ai-history"."errorMessage" IS 'Error message if operation failed';
162
- COMMENT ON COLUMN "ai-history"."createdAt" IS 'When the AI operation was initiated';
163
- COMMENT ON COLUMN "ai-history"."updatedAt" IS 'When the record was last updated';
164
- COMMENT ON COLUMN "ai-history"."completedAt" IS 'When the AI operation finished (success or failure)';
150
+ COMMENT ON TABLE "ai_history" IS 'Generic audit trail for AI interactions with cost and performance metrics';
151
+ COMMENT ON COLUMN "ai_history"."userId" IS 'User who initiated the AI operation';
152
+ COMMENT ON COLUMN "ai_history"."relatedEntityType" IS 'Type of entity this operation relates to (e.g., ''contents'', ''products'', ''campaigns'')';
153
+ COMMENT ON COLUMN "ai_history"."relatedEntityId" IS 'ID of the related entity (polymorphic relationship)';
154
+ COMMENT ON COLUMN "ai_history".operation IS 'Type of AI operation: generate, refine, analyze, chat, completion, or other';
155
+ COMMENT ON COLUMN "ai_history".model IS 'AI model used (e.g., claude-3-5-sonnet-20241022, gpt-4)';
156
+ COMMENT ON COLUMN "ai_history".provider IS 'AI provider: anthropic, openai, etc.';
157
+ COMMENT ON COLUMN "ai_history".status IS 'Processing status: pending, processing, completed, or failed';
158
+ COMMENT ON COLUMN "ai_history"."tokensUsed" IS 'Total tokens consumed (input + output) - kept for backward compatibility';
159
+ COMMENT ON COLUMN "ai_history"."tokensInput" IS 'Input tokens (prompt) - for precise cost calculation with asymmetric pricing';
160
+ COMMENT ON COLUMN "ai_history"."tokensOutput" IS 'Output tokens (completion) - for precise cost calculation with asymmetric pricing';
161
+ COMMENT ON COLUMN "ai_history"."creditsUsed" IS 'Credits deducted for this operation';
162
+ COMMENT ON COLUMN "ai_history"."estimatedCost" IS 'Cost in USD based on model pricing';
163
+ COMMENT ON COLUMN "ai_history"."balanceAfter" IS 'User credit balance after this operation';
164
+ COMMENT ON COLUMN "ai_history"."errorMessage" IS 'Error message if operation failed';
165
+ COMMENT ON COLUMN "ai_history"."createdAt" IS 'When the AI operation was initiated';
166
+ COMMENT ON COLUMN "ai_history"."updatedAt" IS 'When the record was last updated';
167
+ COMMENT ON COLUMN "ai_history"."completedAt" IS 'When the AI operation finished (success or failure)';
165
168
 
166
169
  -- =============================================
167
170
  -- MIGRATION COMPLETE
@@ -171,6 +174,6 @@ DO $$
171
174
  BEGIN
172
175
  RAISE NOTICE 'AI History entity migration completed successfully!';
173
176
  RAISE NOTICE 'Plugin: ai';
174
- RAISE NOTICE 'Table created: ai-history';
177
+ RAISE NOTICE 'Table created: ai_history';
175
178
  RAISE NOTICE 'RLS policies enabled for user isolation';
176
179
  END $$;
@@ -1,10 +1,10 @@
1
1
  -- =============================================
2
2
  -- AI HISTORY METADATA SYSTEM
3
3
  -- Plugin: AI
4
- -- Entity: ai-history
4
+ -- Entity: ai_history
5
5
  -- =============================================
6
6
  --
7
- -- This migration adds comprehensive metadata capabilities through ai-history_metas table
7
+ -- This migration adds comprehensive metadata capabilities through ai_history_metas table
8
8
  --
9
9
  -- Purpose: Flexible metadata storage for AI operations
10
10
  -- - Better query performance (indexed key-value lookups)
@@ -29,9 +29,9 @@
29
29
  -- AI_HISTORY_METAS TABLE
30
30
  -- =============================================
31
31
 
32
- CREATE TABLE IF NOT EXISTS public."ai-history_metas" (
32
+ CREATE TABLE IF NOT EXISTS public."ai_history_metas" (
33
33
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
34
- "entityId" TEXT NOT NULL REFERENCES public."ai-history"(id) ON DELETE CASCADE,
34
+ "entityId" TEXT NOT NULL REFERENCES public."ai_history"(id) ON DELETE CASCADE,
35
35
  "metaKey" TEXT NOT NULL,
36
36
  "metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
37
37
  "dataType" TEXT DEFAULT 'json' CHECK ("dataType" IN ('string', 'number', 'boolean', 'json', 'array')),
@@ -41,54 +41,54 @@ CREATE TABLE IF NOT EXISTS public."ai-history_metas" (
41
41
  "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
42
42
 
43
43
  -- Prevent duplicate keys per history record
44
- CONSTRAINT ai-history_metas_unique_key UNIQUE ("entityId", "metaKey")
44
+ CONSTRAINT ai_history_metas_unique_key UNIQUE ("entityId", "metaKey")
45
45
  );
46
46
 
47
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"
48
+ CREATE TRIGGER ai_history_metas_set_updated_at
49
+ BEFORE UPDATE ON public."ai_history_metas"
50
50
  FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
51
51
 
52
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");
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
56
 
57
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");
58
+ CREATE INDEX idx_ai_history_metas_value_gin ON public."ai_history_metas" USING GIN ("metaValue");
59
59
 
60
60
  -- RLS Policies
61
- ALTER TABLE public."ai-history_metas" ENABLE ROW LEVEL SECURITY;
61
+ ALTER TABLE public."ai_history_metas" ENABLE ROW LEVEL SECURITY;
62
62
 
63
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";
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
66
 
67
- -- Policy: Inherit access from parent ai-history record
67
+ -- Policy: Inherit access from parent ai_history record
68
68
  CREATE POLICY "AI history metas inherit parent access"
69
- ON public."ai-history_metas"
69
+ ON public."ai_history_metas"
70
70
  FOR ALL TO authenticated
71
71
  USING (
72
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()
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
76
  )
77
77
  )
78
78
  WITH CHECK (
79
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()
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
83
  )
84
84
  );
85
85
 
86
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';
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
92
 
93
93
  -- =============================================
94
94
  -- MIGRATION COMPLETE
@@ -97,7 +97,7 @@ COMMENT ON COLUMN public."ai-history_metas"."isSearchable" IS 'Whether this meta
97
97
  DO $$
98
98
  BEGIN
99
99
  RAISE NOTICE 'AI History metadata migration completed successfully!';
100
- RAISE NOTICE 'Created: ai-history_metas table for flexible metadata';
100
+ RAISE NOTICE 'Created: ai_history_metas table for flexible metadata';
101
101
  RAISE NOTICE 'Performance: GIN indexes for fast JSONB queries';
102
- RAISE NOTICE 'Security: RLS policies enabled on ai-history_metas';
102
+ RAISE NOTICE 'Security: RLS policies enabled on ai_history_metas';
103
103
  END $$;
@@ -1,28 +1,31 @@
1
1
  /**
2
- * AI Example Saving - PostgreSQL Implementation
2
+ * AI History Saving - PostgreSQL Implementation
3
3
  *
4
4
  * Uses project's PostgreSQL connection with RLS support.
5
- * Self-contained plugin with direct DB access.
5
+ * Saves AI interactions to the ai_history table.
6
6
  */
7
7
 
8
8
  import { mutateWithRLS, queryWithRLS } from '@nextsparkjs/core/lib/db'
9
9
  import { sanitizePrompt, sanitizeResponse } from './sanitize'
10
10
 
11
11
  export interface AIExampleData {
12
- title?: string
13
12
  prompt: string
14
13
  response: string
15
14
  model: string
16
15
  status: 'pending' | 'processing' | 'completed' | 'failed'
17
- userId?: string
16
+ operation?: 'generate' | 'refine' | 'analyze' | 'chat' | 'completion' | 'other'
17
+ provider?: string
18
18
  metadata?: {
19
19
  tokens?: number
20
+ tokensInput?: number
21
+ tokensOutput?: number
20
22
  cost?: number
21
23
  duration?: number
22
- provider?: string
23
24
  isLocal?: boolean
24
25
  platforms?: number
25
26
  imagesProcessed?: number
27
+ relatedEntityType?: string
28
+ relatedEntityId?: string
26
29
  }
27
30
  }
28
31
 
@@ -34,11 +37,12 @@ export interface SaveExampleResult {
34
37
  }
35
38
 
36
39
  /**
37
- * Save AI example to PostgreSQL database
40
+ * Save AI interaction to ai_history table
38
41
  */
39
42
  export async function saveAIExample(
40
43
  data: AIExampleData,
41
- userId: string
44
+ userId: string,
45
+ teamId: string
42
46
  ): Promise<SaveExampleResult> {
43
47
  try {
44
48
  // 1. Sanitize sensitive data
@@ -49,25 +53,35 @@ export async function saveAIExample(
49
53
  sanitizedPrompt !== data.prompt ||
50
54
  sanitizedResponse !== data.response
51
55
 
52
- // 2. Generate title if not provided
53
- const title = data.title || generateTitle(sanitizedPrompt)
56
+ // 2. Extract token info
57
+ const tokensInput = data.metadata?.tokensInput || 0
58
+ const tokensOutput = data.metadata?.tokensOutput || 0
59
+ const tokensUsed = data.metadata?.tokens || (tokensInput + tokensOutput)
54
60
 
55
- // 3. Insert into database with RLS
61
+ // 3. Insert into ai_history table with RLS
56
62
  const query = `
57
- INSERT INTO ai_example (
58
- title, prompt, response, model, status, "userId", metadata, "createdAt", "updatedAt"
59
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
63
+ INSERT INTO ai_history (
64
+ "userId", "teamId", operation, model, provider, status,
65
+ "tokensUsed", "tokensInput", "tokensOutput", "estimatedCost",
66
+ "relatedEntityType", "relatedEntityId",
67
+ "createdAt", "updatedAt"
68
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW())
60
69
  RETURNING id
61
70
  `
62
71
 
63
72
  const params = [
64
- title,
65
- sanitizedPrompt,
66
- sanitizedResponse,
73
+ userId,
74
+ teamId,
75
+ data.operation || 'generate',
67
76
  data.model,
77
+ data.provider || 'ollama',
68
78
  data.status,
69
- userId,
70
- JSON.stringify(data.metadata || {})
79
+ tokensUsed || null,
80
+ tokensInput || null,
81
+ tokensOutput || null,
82
+ data.metadata?.cost || null,
83
+ data.metadata?.relatedEntityType || null,
84
+ data.metadata?.relatedEntityId || null
71
85
  ]
72
86
 
73
87
  const result = await mutateWithRLS<{ id: string }>(query, params, userId)
@@ -75,13 +89,14 @@ export async function saveAIExample(
75
89
  if (result.rowCount === 0 || !result.rows[0]) {
76
90
  return {
77
91
  success: false,
78
- error: 'Failed to insert example'
92
+ error: 'Failed to insert ai_history record'
79
93
  }
80
94
  }
81
95
 
82
- console.log('[AI Plugin] Example saved to database:', {
96
+ console.log('[AI Plugin] History saved to database:', {
83
97
  id: result.rows[0].id,
84
- title,
98
+ operation: data.operation || 'generate',
99
+ model: data.model,
85
100
  sanitizationApplied
86
101
  })
87
102
 
@@ -92,7 +107,7 @@ export async function saveAIExample(
92
107
  }
93
108
 
94
109
  } catch (error) {
95
- console.error('[AI Plugin] Save example failed:', error)
110
+ console.error('[AI Plugin] Save history failed:', error)
96
111
  return {
97
112
  success: false,
98
113
  error: error instanceof Error ? error.message : 'Unknown error'
@@ -101,29 +116,44 @@ export async function saveAIExample(
101
116
  }
102
117
 
103
118
  /**
104
- * Get AI examples from PostgreSQL
119
+ * Get AI history from PostgreSQL
105
120
  */
106
121
  export async function getAIExamples(params: {
107
122
  userId?: string
123
+ teamId?: string
108
124
  limit?: number
109
125
  offset?: number
110
126
  status?: string
127
+ operation?: string
111
128
  }) {
112
129
  try {
113
- const { userId, limit = 50, offset = 0, status } = params
130
+ const { userId, teamId, limit = 50, offset = 0, status, operation } = params
114
131
 
115
132
  let query = `
116
- SELECT id, title, prompt, response, model, status, "userId", metadata, "createdAt", "updatedAt"
117
- FROM ai_example
133
+ SELECT id, "userId", "teamId", operation, model, provider, status,
134
+ "tokensUsed", "tokensInput", "tokensOutput", "estimatedCost",
135
+ "relatedEntityType", "relatedEntityId",
136
+ "createdAt", "updatedAt", "completedAt"
137
+ FROM ai_history
118
138
  WHERE 1=1
119
139
  `
120
140
  const queryParams: (string | number)[] = []
121
141
 
142
+ if (teamId) {
143
+ queryParams.push(teamId)
144
+ query += ` AND "teamId" = $${queryParams.length}`
145
+ }
146
+
122
147
  if (status) {
123
148
  queryParams.push(status)
124
149
  query += ` AND status = $${queryParams.length}`
125
150
  }
126
151
 
152
+ if (operation) {
153
+ queryParams.push(operation)
154
+ query += ` AND operation = $${queryParams.length}`
155
+ }
156
+
127
157
  query += ` ORDER BY "createdAt" DESC LIMIT $${queryParams.length + 1} OFFSET $${queryParams.length + 2}`
128
158
  queryParams.push(limit, offset)
129
159
 
@@ -134,7 +164,7 @@ export async function getAIExamples(params: {
134
164
  data: rows
135
165
  }
136
166
  } catch (error) {
137
- console.error('[AI Plugin] Get examples failed:', error)
167
+ console.error('[AI Plugin] Get history failed:', error)
138
168
  return {
139
169
  success: false,
140
170
  error: error instanceof Error ? error.message : 'Unknown error',
@@ -144,13 +174,16 @@ export async function getAIExamples(params: {
144
174
  }
145
175
 
146
176
  /**
147
- * Get single AI example by ID from PostgreSQL
177
+ * Get single AI history record by ID
148
178
  */
149
179
  export async function getAIExampleById(id: string, userId?: string) {
150
180
  try {
151
181
  const query = `
152
- SELECT id, title, prompt, response, model, status, "userId", metadata, "createdAt", "updatedAt"
153
- FROM ai_example
182
+ SELECT id, "userId", "teamId", operation, model, provider, status,
183
+ "tokensUsed", "tokensInput", "tokensOutput", "estimatedCost",
184
+ "relatedEntityType", "relatedEntityId",
185
+ "createdAt", "updatedAt", "completedAt"
186
+ FROM ai_history
154
187
  WHERE id = $1
155
188
  `
156
189
 
@@ -159,7 +192,7 @@ export async function getAIExampleById(id: string, userId?: string) {
159
192
  if (rows.length === 0) {
160
193
  return {
161
194
  success: false,
162
- error: 'Example not found'
195
+ error: 'History record not found'
163
196
  }
164
197
  }
165
198
 
@@ -168,7 +201,7 @@ export async function getAIExampleById(id: string, userId?: string) {
168
201
  data: rows[0]
169
202
  }
170
203
  } catch (error) {
171
- console.error('[AI Plugin] Get example by ID failed:', error)
204
+ console.error('[AI Plugin] Get history by ID failed:', error)
172
205
  return {
173
206
  success: false,
174
207
  error: error instanceof Error ? error.message : 'Unknown error'
@@ -177,18 +210,18 @@ export async function getAIExampleById(id: string, userId?: string) {
177
210
  }
178
211
 
179
212
  /**
180
- * Delete AI example from PostgreSQL
213
+ * Delete AI history record
181
214
  */
182
215
  export async function deleteAIExample(id: string, userId: string) {
183
216
  try {
184
- const query = `DELETE FROM ai_example WHERE id = $1 RETURNING id`
217
+ const query = `DELETE FROM ai_history WHERE id = $1 RETURNING id`
185
218
 
186
219
  const result = await mutateWithRLS<{ id: string }>(query, [id], userId)
187
220
 
188
221
  if (result.rowCount === 0) {
189
222
  return {
190
223
  success: false,
191
- error: 'Example not found or unauthorized'
224
+ error: 'History record not found or unauthorized'
192
225
  }
193
226
  }
194
227
 
@@ -197,7 +230,7 @@ export async function deleteAIExample(id: string, userId: string) {
197
230
  id: result.rows[0].id
198
231
  }
199
232
  } catch (error) {
200
- console.error('[AI Plugin] Delete example failed:', error)
233
+ console.error('[AI Plugin] Delete history failed:', error)
201
234
  return {
202
235
  success: false,
203
236
  error: error instanceof Error ? error.message : 'Unknown error'
@@ -206,32 +239,23 @@ export async function deleteAIExample(id: string, userId: string) {
206
239
  }
207
240
 
208
241
  /**
209
- * Generate title from prompt (helper)
210
- */
211
- function generateTitle(prompt: string): string {
212
- const cleaned = prompt.replace(/\n/g, ' ').trim()
213
- return cleaned.length > 50
214
- ? cleaned.substring(0, 50) + '...'
215
- : cleaned
216
- }
217
-
218
- /**
219
- * Save example with automatic error handling (fire-and-forget)
242
+ * Save history with automatic error handling (fire-and-forget)
220
243
  */
221
244
  export async function saveExampleSafely(
222
245
  data: AIExampleData,
223
- userId: string
246
+ userId: string,
247
+ teamId: string
224
248
  ): Promise<void> {
225
249
  try {
226
- const result = await saveAIExample(data, userId)
250
+ const result = await saveAIExample(data, userId, teamId)
227
251
 
228
252
  if (!result.success) {
229
- console.error('[AI Plugin] Example save failed:', result.error)
253
+ console.error('[AI Plugin] History save failed:', result.error)
230
254
  } else if (result.sanitizationApplied) {
231
- console.warn('[AI Plugin] Sanitization was applied to saved example')
255
+ console.warn('[AI Plugin] Sanitization was applied to saved history')
232
256
  }
233
257
  } catch (error) {
234
258
  // Silent fail - don't break the main request
235
- console.error('[AI Plugin] Example save error:', error)
259
+ console.error('[AI Plugin] History save error:', error)
236
260
  }
237
- }
261
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/plugin-ai",
3
- "version": "0.1.0-beta.74",
3
+ "version": "0.1.0-beta.76",
4
4
  "private": false,
5
5
  "main": "./plugin.config.ts",
6
6
  "requiredPlugins": [],