mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
fix ai message + add typer
This commit is contained in:
36
dashboard/composables/useTextType.ts
Normal file
36
dashboard/composables/useTextType.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function useTextType(options: { ms: number, increase: number }, onTickAction?: () => any) {
|
||||||
|
|
||||||
|
let interval: any;
|
||||||
|
const index = ref<number>(0);
|
||||||
|
|
||||||
|
function onTick() {
|
||||||
|
index.value += options.increase;
|
||||||
|
onTickAction?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause() {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resume() {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
interval = setInterval(() => onTick(), options.ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
if (interval) clearTimeout(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
index.value = 0;
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
interval = setInterval(() => onTick(), options.ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { start, stop, resume, pause, index, interval }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,13 +26,28 @@ const loading = ref<boolean>(false);
|
|||||||
|
|
||||||
const currentChatId = ref<string>("");
|
const currentChatId = ref<string>("");
|
||||||
const currentChatMessages = ref<{ role: string, content: string, charts?: any[], tool_calls?: any }[]>([]);
|
const currentChatMessages = ref<{ role: string, content: string, charts?: any[], tool_calls?: any }[]>([]);
|
||||||
const currentChatMessageDelta = ref<string>('');
|
const currentChatMessageDelta = ref<string>("");
|
||||||
|
|
||||||
const currentChatMessageDeltaHtml = computed(() => {
|
|
||||||
const lastData = currentChatMessageDelta.value.match(/\[(data:(.*?))\]/g);
|
const typer = useTextType({ ms: 10, increase: 2 }, () => {
|
||||||
const cleanMessage = currentChatMessageDelta.value.replace(/\[(data:(.*?))\]/g, '');
|
const cleanMessage = currentChatMessageDelta.value.replace(/\[(data:(.*?))\]/g, '');
|
||||||
if (!lastData || lastData.length == 0) return cleanMessage;
|
if (typer.index.value >= cleanMessage.length) typer.pause();
|
||||||
return `<div class="flex items-center gap-1"> <i class="fas fa-loader animate-spin"></i> <div> ${lastData.at(-1)}</div> </div> <div> ${cleanMessage} </div>`;
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
typer.stop();
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentChatMessageDeltaTextVisible = computed(() => {
|
||||||
|
const cleanMessage = currentChatMessageDelta.value.replace(/\[(data:(.*?))\]/g, '');
|
||||||
|
const textVisible = cleanMessage.substring(0, typer.index.value);
|
||||||
|
setTimeout(() => scrollToBottom(), 1);
|
||||||
|
return textVisible;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentChatMessageDeltaShowLoader = computed(() => {
|
||||||
|
const lastData = currentChatMessageDelta.value.match(/\[(data:(.*?))\]$/);
|
||||||
|
return lastData != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const scroller = ref<HTMLDivElement | null>(null);
|
const scroller = ref<HTMLDivElement | null>(null);
|
||||||
@@ -51,10 +66,16 @@ async function pollSendMessageStatus(chat_id: string, times: number, updateStatu
|
|||||||
|
|
||||||
updateStatus(res.status);
|
updateStatus(res.status);
|
||||||
|
|
||||||
|
|
||||||
|
typer.resume();
|
||||||
|
|
||||||
|
|
||||||
if (res.completed === false) {
|
if (res.completed === false) {
|
||||||
setTimeout(() => pollSendMessageStatus(chat_id, times + 1, updateStatus), (times > 20 ? 1000 : 500));
|
setTimeout(() => pollSendMessageStatus(chat_id, times + 1, updateStatus), (times > 10 ? 2000 : 1000));
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
typer.stop();
|
||||||
|
|
||||||
const messages = await $fetch(`/api/ai/${chat_id}/get_messages`, {
|
const messages = await $fetch(`/api/ai/${chat_id}/get_messages`, {
|
||||||
headers: useComputedHeaders({ useSnapshotDates: false }).value
|
headers: useComputedHeaders({ useSnapshotDates: false }).value
|
||||||
});
|
});
|
||||||
@@ -62,18 +83,13 @@ async function pollSendMessageStatus(chat_id: string, times: number, updateStatu
|
|||||||
|
|
||||||
currentChatMessages.value = messages.map(e => ({ ...e, charts: e.charts.map(k => JSON.parse(k)) })) as any;
|
currentChatMessages.value = messages.map(e => ({ ...e, charts: e.charts.map(k => JSON.parse(k)) })) as any;
|
||||||
currentChatMessageDelta.value = '';
|
currentChatMessageDelta.value = '';
|
||||||
|
|
||||||
// currentChatMessages.value.push({
|
|
||||||
// role: 'assistant',
|
|
||||||
// content: currentChatMessageDelta.value.replace(/\[data:.*?\]/g, ''),
|
|
||||||
// });
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendMessage() {
|
async function sendMessage() {
|
||||||
|
|
||||||
|
|
||||||
if (loading.value) return;
|
if (loading.value) return;
|
||||||
if (!project.value) return;
|
if (!project.value) return;
|
||||||
|
|
||||||
@@ -100,14 +116,15 @@ async function sendMessage() {
|
|||||||
|
|
||||||
await new Promise(e => setTimeout(e, 200));
|
await new Promise(e => setTimeout(e, 200));
|
||||||
|
|
||||||
|
|
||||||
|
typer.start();
|
||||||
|
|
||||||
await pollSendMessageStatus(res.chat_id, 0, status => {
|
await pollSendMessageStatus(res.chat_id, 0, status => {
|
||||||
if (!status) return;
|
if (!status) return;
|
||||||
if (status.length > 0) loading.value = false;
|
if (status.length > 0) loading.value = false;
|
||||||
currentChatMessageDelta.value = status;
|
currentChatMessageDelta.value = status;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
|
|
||||||
if (ex.message.includes('CHAT_LIMIT_REACHED')) {
|
if (ex.message.includes('CHAT_LIMIT_REACHED')) {
|
||||||
@@ -237,14 +254,14 @@ async function clearAllChats() {
|
|||||||
|
|
||||||
<div class="flex w-full flex-col" v-for="(message, messageIndex) of currentChatMessages">
|
<div class="flex w-full flex-col" v-for="(message, messageIndex) of currentChatMessages">
|
||||||
|
|
||||||
<div class="flex justify-end w-full poppins text-[1.1rem]" v-if="message.role === 'user'">
|
<div v-if="message.role === 'user'" class="flex justify-end w-full poppins text-[1.1rem]">
|
||||||
<div class="bg-lyx-widget-light px-5 py-3 rounded-lg">
|
<div class="bg-lyx-widget-light px-5 py-3 rounded-lg">
|
||||||
{{ message.content }}
|
{{ message.content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem]"
|
<div v-if="message.role === 'assistant' && (debugModeAi ? true : message.content)"
|
||||||
v-if="message.role === 'assistant' && (debugModeAi ? true : message.content)">
|
class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem]">
|
||||||
<div class="flex items-center justify-center shrink-0">
|
<div class="flex items-center justify-center shrink-0">
|
||||||
<img class="h-[3.5rem] w-auto" :src="'analyst.png'">
|
<img class="h-[3.5rem] w-auto" :src="'analyst.png'">
|
||||||
</div>
|
</div>
|
||||||
@@ -256,7 +273,6 @@ async function clearAllChats() {
|
|||||||
}" />
|
}" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div v-if="debugModeAi && !message.content">
|
<div v-if="debugModeAi && !message.content">
|
||||||
<div class="flex flex-col"
|
<div class="flex flex-col"
|
||||||
v-if="message.tool_calls && message.tool_calls.length > 0">
|
v-if="message.tool_calls && message.tool_calls.length > 0">
|
||||||
@@ -285,17 +301,26 @@ async function clearAllChats() {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem]"
|
<div class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem]"
|
||||||
v-if="currentChatMessageDelta">
|
v-if="currentChatMessageDelta">
|
||||||
|
|
||||||
<div class="flex items-center justify-center shrink-0">
|
<div class="flex items-center justify-center shrink-0">
|
||||||
<img class="h-[3.5rem] w-auto" :src="'analyst.png'">
|
<img class="h-[3.5rem] w-auto" :src="'analyst.png'">
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-[70%] text-text/90 ai-message whitespace-pre-wrap">
|
|
||||||
<vue-markdown :source="currentChatMessageDeltaHtml" :options="{
|
<div class="max-w-[70%] text-text/90 ai-message">
|
||||||
|
<div v-if="currentChatMessageDeltaShowLoader" class="flex items-center gap-1">
|
||||||
|
<i class="fas fa-loader animate-spin"></i>
|
||||||
|
<div> Loading </div>
|
||||||
|
</div>
|
||||||
|
<vue-markdown :source="currentChatMessageDeltaTextVisible" :options="{
|
||||||
html: true,
|
html: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
}" />
|
}" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -356,7 +381,8 @@ async function clearAllChats() {
|
|||||||
|
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="poppins font-semibold text-[1.1rem]"> History </div>
|
<div class="poppins font-semibold text-[1.1rem]"> History </div>
|
||||||
<LyxUiButton v-if="chatsList && chatsList.length > 0" @click="clearAllChats()" type="secondary" class="text-center text-[.8rem]">
|
<LyxUiButton v-if="chatsList && chatsList.length > 0" @click="clearAllChats()" type="secondary"
|
||||||
|
class="text-center text-[.8rem]">
|
||||||
Clear all
|
Clear all
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const getSessionsCountsTool: AIPlugin_TTool<'getSessionsCount'> = {
|
|||||||
type: 'function',
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
name: 'getSessionsCount',
|
name: 'getSessionsCount',
|
||||||
description: 'Gets the number of sessions received on a date range',
|
description: 'Gets the number of sessions (unique visitors) received on a date range',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -83,4 +83,4 @@ export class AiSessions extends AIPlugin<['getSessionsCount', 'getSessionsTimeli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ASessionsInstance = new AiSessions();
|
export const AiSessionsInstance = new AiSessions();
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import OpenAI from "openai";
|
import OpenAI from "openai";
|
||||||
import { AiChatModel } from '@schema/ai/AiChatSchema';
|
import { AiChatModel } from '@schema/ai/AiChatSchema';
|
||||||
import { ProjectCountModel } from '@schema/project/ProjectsCounts';
|
|
||||||
import { ProjectLimitModel } from '@schema/project/ProjectsLimits';
|
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 { 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();
|
||||||
@@ -18,13 +18,15 @@ const openai = new OpenAI({ apiKey: AI_KEY, organization: AI_ORG, project: AI_PR
|
|||||||
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
||||||
...AiVisitsInstance.getTools(),
|
...AiVisitsInstance.getTools(),
|
||||||
...AiEventsInstance.getTools(),
|
...AiEventsInstance.getTools(),
|
||||||
...AiComposableChartInstance.getTools()
|
...AiSessionsInstance.getTools(),
|
||||||
|
...AiComposableChartInstance.getTools(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
const functions: any = {
|
const functions: any = {
|
||||||
...AiVisitsInstance.getHandlers(),
|
...AiVisitsInstance.getHandlers(),
|
||||||
...AiEventsInstance.getHandlers(),
|
...AiEventsInstance.getHandlers(),
|
||||||
|
...AiSessionsInstance.getHandlers(),
|
||||||
...AiComposableChartInstance.getHandlers()
|
...AiComposableChartInstance.getHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +190,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: "Today ISO date: " + new Date().toISOString()
|
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