mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
add ai plugins
This commit is contained in:
@@ -3,12 +3,13 @@ import type OpenAI from 'openai'
|
|||||||
|
|
||||||
|
|
||||||
export type AIPlugin_TTool<T extends string> = (OpenAI.Chat.Completions.ChatCompletionTool & { function: { name: T } });
|
export type AIPlugin_TTool<T extends string> = (OpenAI.Chat.Completions.ChatCompletionTool & { function: { name: T } });
|
||||||
export type AIPlugin_TFunction<T extends string> = (...args: any[]) => any;
|
|
||||||
|
export type AIPlugin_TFunction = (...args: any[]) => any;
|
||||||
|
|
||||||
type AIPlugin_Constructor<Items extends string[]> = {
|
type AIPlugin_Constructor<Items extends string[]> = {
|
||||||
[Key in Items[number]]: {
|
[Key in Items[number]]: {
|
||||||
tool: AIPlugin_TTool<Key>,
|
tool: AIPlugin_TTool<Key>,
|
||||||
handler: AIPlugin_TFunction<Key>
|
handler: AIPlugin_TFunction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
127
dashboard/server/ai/functions/AI_Billing.ts
Normal file
127
dashboard/server/ai/functions/AI_Billing.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
|
||||||
|
import { ProjectLimitModel } from "@schema/project/ProjectsLimits";
|
||||||
|
import { AIPlugin } from "../Plugin";
|
||||||
|
import { MAX_LOG_LIMIT_PERCENT } from "@data/broker/Limits";
|
||||||
|
import { ProjectModel } from "@schema/project/ProjectSchema";
|
||||||
|
import StripeService from "~/server/services/StripeService";
|
||||||
|
import { InvoiceData } from "~/server/api/pay/invoices";
|
||||||
|
|
||||||
|
export class AiBilling extends AIPlugin<[
|
||||||
|
'getBillingInfo',
|
||||||
|
'getLimits',
|
||||||
|
'getInvoices'
|
||||||
|
]> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
|
||||||
|
'getInvoices': {
|
||||||
|
handler: async (data: { project_id: string }) => {
|
||||||
|
|
||||||
|
const project = await ProjectModel.findOne({ _id: data.project_id });
|
||||||
|
if (!project) return { error: 'Project not found' };
|
||||||
|
const invoices = await StripeService.getInvoices(project.customer_id);
|
||||||
|
if (!invoices) return [];
|
||||||
|
|
||||||
|
return invoices?.data.map(e => {
|
||||||
|
const result: InvoiceData = {
|
||||||
|
link: e.invoice_pdf || '',
|
||||||
|
id: e.number || '',
|
||||||
|
date: e.created * 1000,
|
||||||
|
status: e.status || 'NO_STATUS',
|
||||||
|
cost: e.amount_due
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tool: {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'getInvoices',
|
||||||
|
description: 'Gets the invoices of the user project',
|
||||||
|
parameters: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'getBillingInfo': {
|
||||||
|
handler: async (data: { project_id: string }) => {
|
||||||
|
|
||||||
|
const project = await ProjectModel.findOne({ _id: data.project_id });
|
||||||
|
if (!project) return { error: 'Project not found' };
|
||||||
|
|
||||||
|
if (project.subscription_id === 'onetime') {
|
||||||
|
|
||||||
|
const projectLimits = await ProjectLimitModel.findOne({ project_id: data.project_id });
|
||||||
|
if (!projectLimits) return { error: 'Limits not found' }
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
premium: project.premium,
|
||||||
|
premium_type: project.premium_type,
|
||||||
|
billing_start_at: projectLimits.billing_start_at,
|
||||||
|
billing_expire_at: projectLimits.billing_expire_at,
|
||||||
|
limit: projectLimits.limit,
|
||||||
|
count: projectLimits.events + projectLimits.visits,
|
||||||
|
subscription_status: StripeService.isDisabled() ? 'Disabled mode' : ('One time payment')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = await StripeService.getSubscription(project.subscription_id);
|
||||||
|
|
||||||
|
const projectLimits = await ProjectLimitModel.findOne({ project_id: data.project_id });
|
||||||
|
if (!projectLimits) return { error: 'Limits not found' }
|
||||||
|
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
premium: project.premium,
|
||||||
|
premium_type: project.premium_type,
|
||||||
|
billing_start_at: projectLimits.billing_start_at,
|
||||||
|
billing_expire_at: projectLimits.billing_expire_at,
|
||||||
|
limit: projectLimits.limit,
|
||||||
|
count: projectLimits.events + projectLimits.visits,
|
||||||
|
subscription_status: StripeService.isDisabled() ? 'Disabled mode' : (subscription?.status ?? '?')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
tool: {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'getBillingInfo',
|
||||||
|
description: 'Gets the informations about the billing of the user project, limits, count, subscription_status, is premium, premium type, billing start at, billing expire at',
|
||||||
|
parameters: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'getLimits': {
|
||||||
|
handler: async (data: { project_id: string }) => {
|
||||||
|
const projectLimits = await ProjectLimitModel.findOne({ project_id: data.project_id });
|
||||||
|
if (!projectLimits) return { error: 'Project limits not found' };
|
||||||
|
const TOTAL_COUNT = projectLimits.events + projectLimits.visits;
|
||||||
|
const COUNT_LIMIT = projectLimits.limit;
|
||||||
|
return {
|
||||||
|
total: TOTAL_COUNT,
|
||||||
|
limit: COUNT_LIMIT,
|
||||||
|
limited: TOTAL_COUNT > COUNT_LIMIT * MAX_LOG_LIMIT_PERCENT,
|
||||||
|
percent: Math.round(100 / COUNT_LIMIT * TOTAL_COUNT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tool: {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'getLimits',
|
||||||
|
description: 'Gets the informations about the limits of the user project',
|
||||||
|
parameters: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AiBillingInstance = new AiBilling();
|
||||||
78
dashboard/server/ai/functions/AI_Snapshots.ts
Normal file
78
dashboard/server/ai/functions/AI_Snapshots.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
import { AIPlugin } from "../Plugin";
|
||||||
|
import { ProjectModel } from "@schema/project/ProjectSchema";
|
||||||
|
import { ProjectSnapshotModel } from "@schema/project/ProjectSnapshot";
|
||||||
|
|
||||||
|
export class AiSnapshot extends AIPlugin<[
|
||||||
|
'getSnapshots',
|
||||||
|
'createSnapshot',
|
||||||
|
]> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
|
||||||
|
'getSnapshots': {
|
||||||
|
handler: async (data: { project_id: string }) => {
|
||||||
|
const snapshots = await ProjectSnapshotModel.find({ project_id: data.project_id });
|
||||||
|
return snapshots.map(e => e.toJSON());
|
||||||
|
},
|
||||||
|
tool: {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'getSnapshots',
|
||||||
|
description: 'Gets the snapshots list',
|
||||||
|
parameters: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'createSnapshot': {
|
||||||
|
handler: async (data: { project_id: string, from: string, to: string, color: string, name: string }) => {
|
||||||
|
|
||||||
|
if (!data.name) return { error: 'SnapshotName too short' }
|
||||||
|
if (data.name.length == 0) return { error: 'SnapshotName too short' }
|
||||||
|
|
||||||
|
if (!data.from) return { error: 'from is required' }
|
||||||
|
if (!data.to) return { error: 'to is required' }
|
||||||
|
if (!data.color) return { error: 'color is required' }
|
||||||
|
|
||||||
|
const project = await ProjectModel.findById(data.project_id);
|
||||||
|
if (!project) return { error: 'Project not found' }
|
||||||
|
|
||||||
|
|
||||||
|
const newSnapshot = await ProjectSnapshotModel.create({
|
||||||
|
name: data.name,
|
||||||
|
from: new Date(data.from),
|
||||||
|
to: new Date(data.to),
|
||||||
|
color: data.color,
|
||||||
|
project_id: data.project_id
|
||||||
|
});
|
||||||
|
|
||||||
|
return newSnapshot.id;
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
tool: {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'createSnapshot',
|
||||||
|
description: 'Create a snapshot',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
from: { type: 'string', description: 'ISO string of start date' },
|
||||||
|
to: { type: 'string', description: 'ISO string of end date' },
|
||||||
|
color: { type: 'string', description: 'Color of the snapshot in HEX' },
|
||||||
|
name: { type: 'string', description: 'Name of the snapshot' }
|
||||||
|
},
|
||||||
|
required: ['from', 'to', 'color', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AiSnapshotInstance = new AiSnapshot();
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ProjectModel } from "@schema/project/ProjectSchema";
|
import { ProjectModel } from "@schema/project/ProjectSchema";
|
||||||
import { ProjectSnapshotModel } from "@schema/project/ProjectSnapshot";
|
import { ProjectSnapshotModel } from "@schema/project/ProjectSnapshot";
|
||||||
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const data = await getRequestData(event, { requireSchema: false, allowGuests: true, requireRange: false });
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: true, requireRange: false });
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { ProjectLimitModel } from '@schema/project/ProjectsLimits';
|
|||||||
import { AiEventsInstance } from '../ai/functions/AI_Events';
|
import { AiEventsInstance } from '../ai/functions/AI_Events';
|
||||||
import { AiVisitsInstance } from '../ai/functions/AI_Visits';
|
import { AiVisitsInstance } from '../ai/functions/AI_Visits';
|
||||||
import { AiSessionsInstance } from '../ai/functions/AI_Sessions';
|
import { AiSessionsInstance } from '../ai/functions/AI_Sessions';
|
||||||
|
import { AiBillingInstance } from '../ai/functions/AI_Billing';
|
||||||
|
import { AiSnapshotInstance } from '../ai/functions/AI_Snapshots';
|
||||||
import { AiComposableChartInstance } from '../ai/functions/AI_ComposableChart';
|
import { AiComposableChartInstance } from '../ai/functions/AI_ComposableChart';
|
||||||
|
|
||||||
const { AI_KEY, AI_ORG, AI_PROJECT } = useRuntimeConfig();
|
const { AI_KEY, AI_ORG, AI_PROJECT } = useRuntimeConfig();
|
||||||
@@ -19,6 +21,8 @@ const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
|||||||
...AiVisitsInstance.getTools(),
|
...AiVisitsInstance.getTools(),
|
||||||
...AiEventsInstance.getTools(),
|
...AiEventsInstance.getTools(),
|
||||||
...AiSessionsInstance.getTools(),
|
...AiSessionsInstance.getTools(),
|
||||||
|
...AiBillingInstance.getTools(),
|
||||||
|
...AiSnapshotInstance.getTools(),
|
||||||
...AiComposableChartInstance.getTools(),
|
...AiComposableChartInstance.getTools(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -27,6 +31,8 @@ const functions: any = {
|
|||||||
...AiVisitsInstance.getHandlers(),
|
...AiVisitsInstance.getHandlers(),
|
||||||
...AiEventsInstance.getHandlers(),
|
...AiEventsInstance.getHandlers(),
|
||||||
...AiSessionsInstance.getHandlers(),
|
...AiSessionsInstance.getHandlers(),
|
||||||
|
...AiBillingInstance.getHandlers(),
|
||||||
|
...AiSnapshotInstance.getHandlers(),
|
||||||
...AiComposableChartInstance.getHandlers()
|
...AiComposableChartInstance.getHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +196,7 @@ export async function sendMessageOnChat(text: string, pid: string, time_offset:
|
|||||||
} else {
|
} else {
|
||||||
const roleMessage: OpenAI.Chat.Completions.ChatCompletionMessageParam = {
|
const roleMessage: OpenAI.Chat.Completions.ChatCompletionMessageParam = {
|
||||||
role: 'system',
|
role: 'system',
|
||||||
content: "You are an AI Data Analyst and Growth Hacker specialized in helping users analyze data collected within Litlyx and providing strategies to grow their website, app, or business. Your scope is strictly limited to data creation, visualization, and growth-related advice. If a user asks something outside this domain, politely inform them that you are not designed to answer such questions. Today ISO date is " + new Date().toISOString() + "take this in count when the user ask relative dates"
|
content: "You are an AI Data Analyst and Growth Hacker specialized in helping users analyze data collected within Litlyx and providing strategies to grow their website, app, or business. Your scope is strictly limited to data creation, visualization, and growth-related advice. If a user asks something outside this domain, politely inform them that you are not designed to answer such questions. Today ISO date is " + new Date().toISOString() + " take this in count when the user ask relative dates"
|
||||||
}
|
}
|
||||||
messages.push(roleMessage);
|
messages.push(roleMessage);
|
||||||
await addMessageToChat(roleMessage, chat_id);
|
await addMessageToChat(roleMessage, chat_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user