diff --git a/dashboard/pages/analyst.vue b/dashboard/pages/analyst.vue index 3a37cff..021468c 100644 --- a/dashboard/pages/analyst.vue +++ b/dashboard/pages/analyst.vue @@ -77,6 +77,8 @@ async function sendMessage() { if (loading.value) return; if (!project.value) return; + if (currentText.value.length == 0) return; + loading.value = true; const body: any = { text: currentText.value, timeOffset: new Date().getTimezoneOffset() } @@ -194,6 +196,15 @@ async function deleteChat(chat_id: string) { const { visible: pricingDrawerVisible } = usePricingDrawer() +async function clearAllChats() { + const sure = confirm(`Are you sure to delete all ${(chatsList.value?.length || 0)} chats ?`); + if (!sure) return; + await $fetch(`/api/ai/delete_all_chats`, { + headers: useComputedHeaders({ useSnapshotDates: false }).value + }); + await reloadChatsList(); +} + @@ -343,7 +354,12 @@ const { visible: pricingDrawerVisible } = usePricingDrawer() -
History
+
+
History
+ + Clear all + +
= { type: 'function', @@ -46,12 +46,12 @@ export class AiEvents extends AIPlugin<['getEventsCount', 'getEventsTimeline']> super({ 'getEventsCount': { - handler: async (data: { project_id: string, from?: string, to?: string, name?: string, metadata?: string }) => { + handler: async (data: { project_id: string, from: string, to: string, name?: string, metadata?: string }) => { const query: any = { project_id: data.project_id, created_at: { - $gt: data.from ? new Date(data.from).getTime() : new Date(2023).getTime(), - $lt: data.to ? new Date(data.to).getTime() : new Date().getTime(), + $gt: new Date(data.from), + $lt: new Date(data.to), } } if (data.metadata) query.metadata = data.metadata; @@ -62,21 +62,17 @@ export class AiEvents extends AIPlugin<['getEventsCount', 'getEventsTimeline']> tool: getEventsCountTool }, 'getEventsTimeline': { - handler: async (data: { project_id: string, from: string, to: string, name?: string, metadata?: string }) => { - const query: AdvancedTimelineAggregationOptions & { customMatch: Record } = { - projectId: new Types.ObjectId(data.project_id) as any, - model: EventModel, - from: dayjs(data.from).startOf('day').toISOString(), - to: dayjs(data.to).startOf('day').toISOString(), - slice: 'day', - customMatch: {} - } - if (data.metadata) query.customMatch.metadata = data.metadata; - if (data.name) query.customMatch.name = data.name; + handler: async (data: { project_id: string, from: string, to: string, time_offset: number, name?: string, metadata?: string }) => { - const timelineData = await executeAdvancedTimelineAggregation(query); - const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to); - return { data: timelineFilledMerged }; + const timelineData = await executeTimelineAggregation({ + projectId: new Types.ObjectId(data.project_id), + model: EventModel, + from: data.from, + to: data.to, + slice: 'day', + timeOffset: data.time_offset + }); + return { data: timelineData }; }, tool: getEventsTimelineTool } diff --git a/dashboard/server/ai/functions/AI_Sessions.ts b/dashboard/server/ai/functions/AI_Sessions.ts new file mode 100644 index 0000000..0f98906 --- /dev/null +++ b/dashboard/server/ai/functions/AI_Sessions.ts @@ -0,0 +1,86 @@ +import { VisitModel } from "@schema/metrics/VisitSchema"; +import { executeTimelineAggregation } from "~/server/services/TimelineService"; +import { Types } from "mongoose"; +import { AIPlugin, AIPlugin_TTool } from "../Plugin"; +import { SessionModel } from "@schema/metrics/SessionSchema"; + +const getSessionsCountsTool: AIPlugin_TTool<'getSessionsCount'> = { + type: 'function', + function: { + name: 'getSessionsCount', + description: 'Gets the number of sessions received on a date range', + parameters: { + type: 'object', + properties: { + from: { type: 'string', description: 'ISO string of start date' }, + to: { type: 'string', description: 'ISO string of end date' }, + min_duration: { type: 'number', description: 'Minimum duration of the session' }, + max_duration: { type: 'number', description: 'Maximum duration of the session' }, + }, + required: ['from', 'to'] + } + } +} + +const getSessionsTimelineTool: AIPlugin_TTool<'getSessionsTimeline'> = { + type: 'function', + function: { + name: 'getSessionsTimeline', + description: 'Gets an array of date and count for events received on a date range. Should be used to create charts.', + parameters: { + type: 'object', + properties: { + from: { type: 'string', description: 'ISO string of start date' }, + to: { type: 'string', description: 'ISO string of end date' }, + }, + required: ['from', 'to'] + } + } +} + +export class AiSessions extends AIPlugin<['getSessionsCount', 'getSessionsTimeline']> { + + constructor() { + + super({ + 'getSessionsCount': { + handler: async (data: { project_id: string, from: string, to: string, min_duration?: number, max_duration?: number }) => { + + const query: any = { + project_id: data.project_id, + created_at: { + $gt: new Date(data.from), + $lt: new Date(data.to), + }, + duration: { + $gte: data.min_duration || 0, + $lte: data.max_duration || 999_999_999, + } + } + + const result = await VisitModel.countDocuments(query); + return { count: result }; + }, + tool: getSessionsCountsTool + }, + 'getSessionsTimeline': { + handler: async (data: { project_id: string, from: string, to: string, time_offset: number, website?: string, page?: string }) => { + + const timelineData = await executeTimelineAggregation({ + projectId: new Types.ObjectId(data.project_id), + model: SessionModel, + from: data.from, + to: data.to, + slice: 'day', + timeOffset: data.time_offset + }); + return { data: timelineData }; + }, + tool: getSessionsTimelineTool + } + }) + + } +} + +export const ASessionsInstance = new AiSessions(); diff --git a/dashboard/server/ai/functions/AI_Visits.ts b/dashboard/server/ai/functions/AI_Visits.ts index 78ab4fb..495c201 100644 --- a/dashboard/server/ai/functions/AI_Visits.ts +++ b/dashboard/server/ai/functions/AI_Visits.ts @@ -1,13 +1,7 @@ import { VisitModel } from "@schema/metrics/VisitSchema"; -import { AdvancedTimelineAggregationOptions, executeAdvancedTimelineAggregation, executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService"; +import { executeTimelineAggregation } from "~/server/services/TimelineService"; import { Types } from "mongoose"; import { AIPlugin, AIPlugin_TTool } from "../Plugin"; -import dayjs from 'dayjs'; - -import { zodFunction } from "openai/helpers/zod"; -import { z } from 'zod'; -import { Slice } from "@services/DateService"; - const getVisitsCountsTool: AIPlugin_TTool<'getVisitsCount'> = { type: 'function', @@ -39,11 +33,6 @@ const getVisitsTimelineTool: AIPlugin_TTool<'getVisitsTimeline'> = { to: { type: 'string', description: 'ISO string of end date' }, website: { type: 'string', description: 'The website of the visits' }, page: { type: 'string', description: 'The page of the visit' }, - slice: { - type: 'string', - description: 'The slice for the visit data', - enum: ['hour', 'day', 'month', 'year'] - } }, required: ['from', 'to'] } @@ -56,13 +45,13 @@ export class AiVisits extends AIPlugin<['getVisitsCount', 'getVisitsTimeline']> super({ 'getVisitsCount': { - handler: async (data: { project_id: string, from?: string, to?: string, website?: string, page?: string }) => { + handler: async (data: { project_id: string, from: string, to: string, website?: string, page?: string }) => { const query: any = { project_id: data.project_id, created_at: { - $gt: data.from ? new Date(data.from).getTime() : new Date(2023).getTime(), - $lt: data.to ? new Date(data.to).getTime() : new Date().getTime(), + $gt: new Date(data.from), + $lt: new Date(data.to), } } @@ -78,33 +67,17 @@ export class AiVisits extends AIPlugin<['getVisitsCount', 'getVisitsTimeline']> tool: getVisitsCountsTool }, 'getVisitsTimeline': { - handler: async (data: { project_id: string, from: string, to: string, time_offset: number, website?: string, page?: string, slice?: string }) => { + handler: async (data: { project_id: string, from: string, to: string, time_offset: number, website?: string, page?: string }) => { const timelineData = await executeTimelineAggregation({ projectId: new Types.ObjectId(data.project_id), model: VisitModel, from: data.from, to: data.to, - slice: (data.slice || 'day') as Slice, + slice: 'day', timeOffset: data.time_offset }); return { data: timelineData }; - - // const query: AdvancedTimelineAggregationOptions & { customMatch: Record } = { - // projectId: new Types.ObjectId(data.project_id) as any, - // model: VisitModel, - // from: dayjs(data.from).startOf('day').toISOString(), - // to: dayjs(data.to).startOf('day').toISOString(), - // slice: 'day', - // customMatch: {} - // } - - // if (data.website) query.customMatch.website = data.website; - // if (data.page) query.customMatch.page = data.page; - - // const timelineData = await executeAdvancedTimelineAggregation(query); - // const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to); - // return { data: timelineFilledMerged }; }, tool: getVisitsTimelineTool } diff --git a/dashboard/server/api/ai/delete_all_chats.ts b/dashboard/server/api/ai/delete_all_chats.ts new file mode 100644 index 0000000..b25da36 --- /dev/null +++ b/dashboard/server/api/ai/delete_all_chats.ts @@ -0,0 +1,13 @@ + +import { AiChatModel } from "@schema/ai/AiChatSchema"; + +export default defineEventHandler(async event => { + + const data = await getRequestData(event); + if (!data) return; + + const { project_id } = data; + + const result = await AiChatModel.updateMany({ project_id }, { deleted: true }); + return result.modifiedCount > 0; +}); \ No newline at end of file diff --git a/dashboard/server/services/AiService.ts b/dashboard/server/services/AiService.ts index 7203383..714db46 100644 --- a/dashboard/server/services/AiService.ts +++ b/dashboard/server/services/AiService.ts @@ -89,6 +89,9 @@ type ElaborateResponseCallbacks = { async function elaborateResponse(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], pid: string, time_offset: number, chat_id: string, callbacks?: ElaborateResponseCallbacks) { + console.log('[ELABORATING RESPONSE]'); + console.dir(messages, { depth: Infinity }); + const responseStream = await openai.beta.chat.completions.stream({ model: OPENAI_MODEL, messages, n: 1, tools }); const functionCalls: FunctionCall[] = []; @@ -103,50 +106,55 @@ async function elaborateResponse(messages: OpenAI.Chat.Completions.ChatCompletio if (delta.content) await callbacks?.onDelta?.(delta.content); if (delta.tool_calls) { - const toolCall = delta.tool_calls[0]; - if (!toolCall.function) throw Error('Cannot get function from tool_calls'); - const functionName = toolCall.function.name; + for (const toolCall of delta.tool_calls) { - const functionCall: FunctionCall = functionName ? - { name: functionName, argsRaw: [], collecting: true, result: null, tool_call_id: toolCall.id as string } : - functionCalls.at(-1) as FunctionCall; + if (!toolCall.function) throw Error('Cannot get function from tool_calls'); - if (functionName) functionCalls.push(functionCall); + const functionName = toolCall.function.name; - if (functionName) await callbacks?.onFunctionName?.(functionName); + const functionCall: FunctionCall = functionName ? + { name: functionName, argsRaw: [], collecting: true, result: null, tool_call_id: toolCall.id as string } : + functionCalls.at(-1) as FunctionCall; + + if (functionName) functionCalls.push(functionCall); + + if (functionName) await callbacks?.onFunctionName?.(functionName); + + if (toolCall.function.arguments) functionCall.argsRaw.push(toolCall.function.arguments); + } - if (toolCall.function.arguments) functionCall.argsRaw.push(toolCall.function.arguments); } if (finishReason === "tool_calls" && functionCalls.at(-1)?.collecting) { - const functionCall: FunctionCall = functionCalls.at(-1) as FunctionCall; - await callbacks?.onFunctionCall?.(functionCall.name); - const args = JSON.parse(functionCall.argsRaw.join('')); + for (const functionCall of functionCalls) { + await callbacks?.onFunctionCall?.(functionCall.name); + const args = JSON.parse(functionCall.argsRaw.join('')); - const functionResult = await functions[functionCall.name]({ project_id: pid, time_offset, ...args }); - functionCall.result = functionResult; - await callbacks?.onFunctionResult?.(functionCall.name, functionResult); + const functionResult = await functions[functionCall.name]({ project_id: pid, time_offset, ...args }); + functionCall.result = functionResult; + await callbacks?.onFunctionResult?.(functionCall.name, functionResult); - await addMessageToChat({ - role: 'assistant', - content: delta.content, - refusal: delta.refusal, - tool_calls: [ - { - id: functionCall.tool_call_id, type: 'function', - function: { - name: functionCall.name, arguments: functionCall.argsRaw.join('') + await addMessageToChat({ + role: 'assistant', + content: delta.content, + refusal: delta.refusal, + tool_calls: [ + { + id: functionCall.tool_call_id, type: 'function', + function: { + name: functionCall.name, arguments: functionCall.argsRaw.join('') + } } - } - ] - }, chat_id); + ] + }, chat_id); - await addMessageToChat({ tool_call_id: functionCall.tool_call_id, role: 'tool', content: JSON.stringify(functionCall.result) }, chat_id); + await addMessageToChat({ tool_call_id: functionCall.tool_call_id, role: 'tool', content: JSON.stringify(functionCall.result) }, chat_id); + functionCall.collecting = false; + } - functionCall.collecting = false; lastFinishReason = finishReason; }