mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 15:58:38 +01:00
fix
This commit is contained in:
8
TODO
8
TODO
@@ -1,7 +1 @@
|
|||||||
|
- Email login (remove/fix github login)
|
||||||
|
|
||||||
- Reactivity on project delete (update dropdown) + test guest
|
|
||||||
- Event funnel / metadata analyzer / user flow
|
|
||||||
- Live demo
|
|
||||||
- Why can’t guests use the AI chatbot?
|
|
||||||
- Email login
|
|
||||||
@@ -37,6 +37,6 @@ async function showMore() {
|
|||||||
<BarCardBase @showMore="showMore()" @showRawData="goToView()"
|
<BarCardBase @showMore="showMore()" @showRawData="goToView()"
|
||||||
desc="Most frequent user events triggered in this project" @dataReload="eventsData.refresh()"
|
desc="Most frequent user events triggered in this project" @dataReload="eventsData.refresh()"
|
||||||
:data="eventsData.data.value || []" :loading="eventsData.pending.value" label="Top Events"
|
:data="eventsData.data.value || []" :loading="eventsData.pending.value" label="Top Events"
|
||||||
sub-label="Events" :rawButton="!isLiveDemo()"></BarCardBase>
|
sub-label="Events" :rawButton="!isLiveDemo"></BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function goToView() {
|
|||||||
:loading="currentData.pending.value" :label="isPagesView ? 'Top pages' : 'Top Domains'"
|
:loading="currentData.pending.value" :label="isPagesView ? 'Top pages' : 'Top Domains'"
|
||||||
:sub-label="isPagesView ? 'Page' : 'Domains'"
|
:sub-label="isPagesView ? 'Page' : 'Domains'"
|
||||||
:desc="isPagesView ? 'Most visited pages' : 'Most visited domains in this project'"
|
:desc="isPagesView ? 'Most visited pages' : 'Most visited domains in this project'"
|
||||||
:interactive="!isPagesView" :rawButton="!isLiveDemo()" :isDetailView="isPagesView">
|
:interactive="!isPagesView" :rawButton="!isLiveDemo" :isDetailView="isPagesView">
|
||||||
</BarCardBase>
|
</BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,14 +4,8 @@ import type { TProject } from '@schema/ProjectSchema';
|
|||||||
|
|
||||||
const { user } = useLoggedUser()
|
const { user } = useLoggedUser()
|
||||||
|
|
||||||
const { projectList, guestProjectList, actions, project } = useProject();
|
const { projectList, guestProjectList,allProjectList, actions, project } = useProject();
|
||||||
|
|
||||||
const selectorProjects = computed(() => {
|
|
||||||
const result: TProject[] = [];
|
|
||||||
if (projectList.value) result.push(...projectList.value);
|
|
||||||
if (guestProjectList.value) result.push(...guestProjectList.value);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
function isProjectMine(owner?: string) {
|
function isProjectMine(owner?: string) {
|
||||||
if (!owner) return false;
|
if (!owner) return false;
|
||||||
@@ -34,7 +28,7 @@ function onChange(e: TProject) {
|
|||||||
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
|
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
|
||||||
active: '!bg-lyx-widget-lighter'
|
active: '!bg-lyx-widget-lighter'
|
||||||
}
|
}
|
||||||
}" class="w-full" v-if="selectorProjects" @change="onChange" :value="project" :options="selectorProjects">
|
}" class="w-full" v-if="allProjectList" @change="onChange" :value="project" :options="allProjectList">
|
||||||
|
|
||||||
<template #option="{ option, active, selected }">
|
<template #option="{ option, active, selected }">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -241,8 +241,6 @@ const legendClasses = ref<string[]>([
|
|||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
const inLiveDemo = isLiveDemo();
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -254,7 +252,7 @@ const inLiveDemo = isLiveDemo();
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="flex gap-6 w-full justify-between">
|
<div class="flex gap-6 w-full justify-between">
|
||||||
<LyxUiButton type="secondary" :to="inLiveDemo ? '#' : '/analyst'" :disabled="inLiveDemo">
|
<LyxUiButton type="secondary" :to="isLiveDemo ? '#' : '/analyst'" :disabled="isLiveDemo">
|
||||||
<div class="flex items-center gap-2 px-10">
|
<div class="flex items-center gap-2 px-10">
|
||||||
<i class="far fa-sparkles text-yellow-400"></i>
|
<i class="far fa-sparkles text-yellow-400"></i>
|
||||||
<div class="poppins text-lyx-text"> Ask AI </div>
|
<div class="poppins text-lyx-text"> Ask AI </div>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ function transformResponse(input: CustomEventsAggregated[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const eventsData = useFetch(`/api/data/events`, {
|
const eventsData = useFetch(`/api/data/events`, {
|
||||||
method: 'POST', headers: useComputedHeaders({ limit: 6 }), lazy: true, immediate: false, transform: transformResponse
|
headers: useComputedHeaders({ limit: 6 }), lazy: true, immediate: false, transform: transformResponse
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -56,14 +56,35 @@ const chartData = ref<ChartData<'funnel'>>({
|
|||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
data: [],
|
data: [],
|
||||||
backgroundColor: ['#5680F8' + '77'],
|
backgroundColor: [
|
||||||
|
'#5680F877',
|
||||||
|
"#6bbbe377",
|
||||||
|
"#a6d5cb77",
|
||||||
|
"#fae0b977",
|
||||||
|
"#f28e8e77",
|
||||||
|
"#e3a7e477",
|
||||||
|
"#c4a8e177",
|
||||||
|
"#8cc1d877",
|
||||||
|
"#f9c2cd77",
|
||||||
|
"#b4e3b277",
|
||||||
|
"#ffdfba77",
|
||||||
|
"#e9c3b577",
|
||||||
|
"#d5b8d677",
|
||||||
|
"#add7f677",
|
||||||
|
"#ffd1dc77",
|
||||||
|
"#ffe7a177",
|
||||||
|
"#a8e6cf77",
|
||||||
|
"#d4a5a577",
|
||||||
|
"#f3d6e477",
|
||||||
|
"#c3aed677"
|
||||||
|
],
|
||||||
// borderColor: '#0000CC',
|
// borderColor: '#0000CC',
|
||||||
// borderWidth: 4,
|
// borderWidth: 4,
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.45,
|
tension: 0.45,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
pointHoverRadius: 10,
|
pointHoverRadius: 10,
|
||||||
hoverBackgroundColor: '#5680F8',
|
hoverBackgroundColor: '#26262677',
|
||||||
// hoverBorderColor: 'white',
|
// hoverBorderColor: 'white',
|
||||||
// hoverBorderWidth: 2,
|
// hoverBorderWidth: 2,
|
||||||
},
|
},
|
||||||
@@ -89,16 +110,10 @@ onMounted(async () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventsCount = await useFetch<{ _id: string, count: number }[]>(`/api/data/query`, {
|
const eventsData = useFetch(`/api/data/events`, {
|
||||||
lazy: true, headers: useComputedHeaders({
|
headers: useComputedHeaders(), lazy: true, immediate: false
|
||||||
limit: 1000,
|
|
||||||
custom: {
|
|
||||||
'schema': 'events'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const enabledEvents = ref<string[]>([]);
|
const enabledEvents = ref<string[]>([]);
|
||||||
|
|
||||||
async function onEventCheck(eventName: string) {
|
async function onEventCheck(eventName: string) {
|
||||||
@@ -114,7 +129,7 @@ async function onEventCheck(eventName: string) {
|
|||||||
chartData.value.datasets[0].data = [];
|
chartData.value.datasets[0].data = [];
|
||||||
|
|
||||||
for (const enabledEvent of enabledEvents.value) {
|
for (const enabledEvent of enabledEvents.value) {
|
||||||
const target = (eventsCount.data.value ?? []).find(e => e._id == enabledEvent);
|
const target = (eventsData.data.value ?? []).find(e => e._id == enabledEvent);
|
||||||
chartData.value.datasets[0].data.push(target?.count || 0);
|
chartData.value.datasets[0].data.push(target?.count || 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,18 +138,21 @@ async function onEventCheck(eventName: string) {
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CardTitled title="Funnel" sub="Funnel events">
|
<CardTitled title="Funnel"
|
||||||
|
sub="Monitor and analyze the actions your users are performing on your platform to gain insights into their behavior and optimize the user experience">
|
||||||
<div class="flex gap-2 justify-between">
|
<div class="flex gap-2 justify-between">
|
||||||
<div>
|
<div class="flex flex-col gap-1">
|
||||||
<div class="min-w-[20rem]">
|
<div class="min-w-[20rem] text-lyx-text-darker">
|
||||||
Select two or more events
|
Select two or more events
|
||||||
</div>
|
</div>
|
||||||
<div v-for="event of eventsCount.data.value">
|
<div class="flex flex-col gap-1">
|
||||||
<UCheckbox @change="onEventCheck(event._id)" :value="enabledEvents.includes(event._id)"
|
<div v-for="event of eventsData.data.value">
|
||||||
:label="event._id">
|
<UCheckbox color="secondary" @change="onEventCheck(event._id)"
|
||||||
|
:value="enabledEvents.includes(event._id)" :label="event._id">
|
||||||
</UCheckbox>
|
</UCheckbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<FunnelChart :chart-data="chartData" :options="chartOptions"> </FunnelChart>
|
<FunnelChart :chart-data="chartData" :options="chartOptions"> </FunnelChart>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ watch(selectedMetadataField, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function getMetadataFields() {
|
async function getMetadataFields() {
|
||||||
metadataFields.value = await $fetch<string[]>(`/api/metrics/events_data/metadata_fields?name=${selectedEventName.value}`, {
|
metadataFields.value = await $fetch<string[]>(`/api/data/events_data/metadata_fields?name=${selectedEventName.value}`, {
|
||||||
headers: useComputedHeaders().value
|
headers: useComputedHeaders().value
|
||||||
});
|
});
|
||||||
selectedMetadataField.value = undefined;
|
selectedMetadataField.value = undefined;
|
||||||
@@ -42,7 +42,7 @@ async function getMetadataFieldGrouped() {
|
|||||||
|
|
||||||
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
|
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
|
||||||
|
|
||||||
metadataFieldGrouped.value = await $fetch<string[]>(`/api/metrics/events_data/metadata_field_group?${queryParamsString}`, {
|
metadataFieldGrouped.value = await $fetch<string[]>(`/api/data/events_data/metadata_field_group?${queryParamsString}`, {
|
||||||
headers: useComputedHeaders().value
|
headers: useComputedHeaders().value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,76 @@ const canSearch = computed(() => {
|
|||||||
|
|
||||||
<CardTitled title="Event metadata analyzer" sub="Filter events metadata fields to analyze them" class="w-full p-4">
|
<CardTitled title="Event metadata analyzer" sub="Filter events metadata fields to analyze them" class="w-full p-4">
|
||||||
|
|
||||||
<div class="p-2 flex flex-col">
|
<div class="">
|
||||||
|
|
||||||
|
<LyxUiCard class="h-full w-full flex gap-2">
|
||||||
|
|
||||||
|
<div class="flex-[2]">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<USelectMenu :uiMenu="{
|
||||||
|
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
|
||||||
|
base: '!bg-lyx-widget',
|
||||||
|
option: {
|
||||||
|
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
|
||||||
|
active: '!bg-lyx-widget-lighter'
|
||||||
|
}
|
||||||
|
}" searchable searchable-placeholder="Search an event..." class="w-full"
|
||||||
|
placeholder="Select an event" :options="eventNames.data.value || []"
|
||||||
|
v-model="selectedEventName">
|
||||||
|
</USelectMenu>
|
||||||
|
|
||||||
|
<USelectMenu :uiMenu="{
|
||||||
|
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
|
||||||
|
base: '!bg-lyx-widget',
|
||||||
|
option: {
|
||||||
|
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
|
||||||
|
active: '!bg-lyx-widget-lighter'
|
||||||
|
}
|
||||||
|
}" searchable searchable-placeholder="Search a field..." class="w-full"
|
||||||
|
placeholder="Select a field" :options="metadataFields" v-model="selectedMetadataField">
|
||||||
|
</USelectMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-lyx-text-darker poppins mt-4 flex items-center gap-4">
|
||||||
|
<div class="w-[10rem]">
|
||||||
|
Search results: {{ metadataFieldGroupedFiltered.length }}
|
||||||
|
</div>
|
||||||
|
<div v-if="canSearch" class="h-full flex items-center text-[1.2rem]">
|
||||||
|
|
||||||
|
<div class="bg-lyx-widget-light flex items-center rounded-md pl-4">
|
||||||
|
<div><i class="far fa-search"></i></div>
|
||||||
|
<input class="bg-transparent px-4 py-2 text-[1rem] outline-none" type="text"
|
||||||
|
placeholder="Filter by metadata name" v-model="currentSearchText">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2 mt-4">
|
||||||
|
|
||||||
|
<div class="bg-lyx-widget-light text-lyx-text-dark px-3 py-2 rounded-md w-fit"
|
||||||
|
v-for="item of metadataFieldGroupedFiltered">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div> {{ item._id || 'OLD_EVENTS' }} </div>
|
||||||
|
<div> {{ item.count }} </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="border-solid border-[#212121] border-l-[1px]"></div> -->
|
||||||
|
|
||||||
|
<!-- <div class="flex-[1]">
|
||||||
|
<div class="poppins font-semibold"> </div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
</LyxUiCard>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="p-2 flex flex-col">
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
||||||
@@ -103,17 +172,11 @@ const canSearch = computed(() => {
|
|||||||
Search results: {{ metadataFieldGroupedFiltered.length }}
|
Search results: {{ metadataFieldGroupedFiltered.length }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div v-for="item of metadataFieldGroupedFiltered">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<div> {{ item._id || 'OLD_EVENTS' }} </div>
|
|
||||||
<div> {{ item.count }} </div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div> -->
|
||||||
|
|
||||||
</CardTitled>
|
</CardTitled>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,23 +42,30 @@ async function analyzeEvent() {
|
|||||||
sub="Track your user's journey from external links to in-app events, maintaining a complete view of their path from entry to engagement."
|
sub="Track your user's journey from external links to in-app events, maintaining a complete view of their path from entry to engagement."
|
||||||
class="w-full p-4">
|
class="w-full p-4">
|
||||||
|
|
||||||
<div class="p-2 flex flex-col gap-3">
|
<div class="flex flex-col gap-4">
|
||||||
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
|
||||||
placeholder="Select an event" :options="eventNames.data.value || []" v-model="selectedEventName">
|
<div class="py-2 flex items-center gap-3">
|
||||||
|
<USelectMenu :uiMenu="{
|
||||||
|
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
|
||||||
|
base: '!bg-lyx-widget',
|
||||||
|
option: {
|
||||||
|
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
|
||||||
|
active: '!bg-lyx-widget-lighter'
|
||||||
|
}
|
||||||
|
}" searchable searchable-placeholder="Search an event..." class="w-full" placeholder="Select an event"
|
||||||
|
:options="eventNames.data.value || []" v-model="selectedEventName">
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<div v-if="selectedEventName && !analyzing" class="flex justify-center">
|
<div v-if="selectedEventName && !analyzing" class="flex justify-center">
|
||||||
<div @click="analyzeEvent()"
|
<LyxUiButton @click="analyzeEvent()" type="primary" class="w-fit px-8 py-1">
|
||||||
class="bg-bg w-fit px-8 py-2 poppins rounded-lg hover:bg-bg/80 cursor-pointer">
|
|
||||||
Analyze
|
Analyze
|
||||||
|
</LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="analyzing">
|
<div v-if="analyzing"> Analyzing... </div>
|
||||||
Analyzing...
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2" v-if="userFlowData">
|
<div class="flex flex-col gap-2" v-if="userFlowData">
|
||||||
<div class="flex gap-4 items-center bg-bg py-1 px-2 rounded-lg"
|
<div class="flex gap-4 items-center bg-bg py-2 px-2 bg-lyx-widget-light rounded-lg"
|
||||||
v-for="(count, referrer) in userFlowData">
|
v-for="(count, referrer) in userFlowData">
|
||||||
<div class="w-5 h-5 flex items-center justify-center">
|
<div class="w-5 h-5 flex items-center justify-center">
|
||||||
<img :src="`https://s2.googleusercontent.com/s2/favicons?domain=${referrer}&sz=64`"
|
<img :src="`https://s2.googleusercontent.com/s2/favicons?domain=${referrer}&sz=64`"
|
||||||
@@ -69,8 +76,8 @@ async function analyzeEvent() {
|
|||||||
<div> {{ count.toFixed(2).replace('.', ',') }} % </div>
|
<div> {{ count.toFixed(2).replace('.', ',') }} % </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</CardTitled>
|
</CardTitled>
|
||||||
</template>
|
</template>
|
||||||
@@ -86,6 +86,9 @@ async function changeProjectName() {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
async function deleteProject() {
|
async function deleteProject() {
|
||||||
if (!project.value) return;
|
if (!project.value) return;
|
||||||
const sure = confirm(`Are you sure to delete the project ${project.value.name} ?`);
|
const sure = confirm(`Are you sure to delete the project ${project.value.name} ?`);
|
||||||
@@ -108,6 +111,7 @@ async function deleteProject() {
|
|||||||
const firstProjectId = projectList.value?.[0]?._id.toString();
|
const firstProjectId = projectList.value?.[0]?._id.toString();
|
||||||
if (firstProjectId) {
|
if (firstProjectId) {
|
||||||
await actions.setActiveProject(firstProjectId);
|
await actions.setActiveProject(firstProjectId);
|
||||||
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -153,14 +157,15 @@ function copyProjectId() {
|
|||||||
<SettingsTemplate :entries="entries" :key="project?.name || 'NONE'">
|
<SettingsTemplate :entries="entries" :key="project?.name || 'NONE'">
|
||||||
<template #pname>
|
<template #pname>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<LyxUiInput class="w-full px-4 py-2" v-model="projectNameInputVal"></LyxUiInput>
|
<LyxUiInput class="w-full px-4 py-2" :disabled="isGuest" v-model="projectNameInputVal"></LyxUiInput>
|
||||||
<LyxUiButton v-if="!isGuest" @click="changeProjectName()" :disabled="!canChange" type="primary"> Change
|
<LyxUiButton v-if="!isGuest" @click="changeProjectName()" :disabled="!canChange" type="primary"> Change
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #api>
|
<template #api>
|
||||||
<div class="flex items-center gap-4" v-if="apiKeys && apiKeys.length < 5">
|
<div class="flex items-center gap-4" v-if="apiKeys && apiKeys.length < 5">
|
||||||
<LyxUiInput class="grow px-4 py-2" placeholder="ApiKeyName" v-model="newApiKeyName"></LyxUiInput>
|
<LyxUiInput class="grow px-4 py-2" :disabled="isGuest" placeholder="ApiKeyName" v-model="newApiKeyName">
|
||||||
|
</LyxUiInput>
|
||||||
<LyxUiButton v-if="!isGuest" @click="createApiKey()" :disabled="newApiKeyName.length < 3"
|
<LyxUiButton v-if="!isGuest" @click="createApiKey()" :disabled="newApiKeyName.length < 3"
|
||||||
type="primary">
|
type="primary">
|
||||||
<i class="far fa-plus"></i>
|
<i class="far fa-plus"></i>
|
||||||
@@ -172,7 +177,7 @@ function copyProjectId() {
|
|||||||
<div class="flex gap-8 items-center">
|
<div class="flex gap-8 items-center">
|
||||||
<div class="grow">Name: {{ apiKey.apiName }}</div>
|
<div class="grow">Name: {{ apiKey.apiName }}</div>
|
||||||
<div>{{ apiKey.apiKey }}</div>
|
<div>{{ apiKey.apiKey }}</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end" v-if="!isGuest">
|
||||||
<i class="far fa-trash cursor-pointer" @click="deleteApiKey(apiKey._id.toString())"></i>
|
<i class="far fa-trash cursor-pointer" @click="deleteApiKey(apiKey._id.toString())"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ const { visible } = usePricingDrawer();
|
|||||||
|
|
||||||
<SettingsTemplate v-if="!invoicesPending && !planPending" :entries="entries">
|
<SettingsTemplate v-if="!invoicesPending && !planPending" :entries="entries">
|
||||||
<template #info>
|
<template #info>
|
||||||
<div>
|
<div v-if="!isGuest">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<LyxUiInput class="px-2 py-1" placeholder="Address line 1" v-model="currentBillingInfo.line1">
|
<LyxUiInput class="px-2 py-1" placeholder="Address line 1" v-model="currentBillingInfo.line1">
|
||||||
</LyxUiInput>
|
</LyxUiInput>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
export function isLiveDemo() {
|
const route = useRoute();
|
||||||
const route = useRoute();
|
export const isLiveDemo = computed(() => {
|
||||||
return route.path == '/live_demo';
|
return route.path == '/live_demo';
|
||||||
}
|
})
|
||||||
|
|
||||||
const liveDemoData = useFetch('/api/live_demo');
|
const liveDemoData = useFetch('/api/live_demo');
|
||||||
|
|
||||||
export function useLiveDemo() {
|
export function useLiveDemo() { return liveDemoData; }
|
||||||
return liveDemoData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,24 +43,31 @@ const setActiveProject = (project_id: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const project = computed(() => {
|
const project = computed(() => {
|
||||||
if (!projectList.value) return;
|
|
||||||
if (projectList.value.length == 0) return;
|
if (isLiveDemo.value) return useLiveDemo().data.value;
|
||||||
|
|
||||||
|
if (!allProjectList.value) return;
|
||||||
|
if (allProjectList.value.length == 0) return;
|
||||||
|
|
||||||
if (activeProjectId.value) {
|
if (activeProjectId.value) {
|
||||||
const target = projectList.value.find(e => e._id.toString() == activeProjectId.value);
|
const target = allProjectList.value.find(e => e._id.toString() == activeProjectId.value);
|
||||||
if (target) return target;
|
if (target) return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedActive = localStorage.getItem('active_pid');
|
const savedActive = localStorage.getItem('active_pid');
|
||||||
if (savedActive) {
|
if (savedActive) {
|
||||||
const target = projectList.value.find(e => e._id.toString() == savedActive);
|
const target = allProjectList.value.find(e => e._id.toString() == savedActive);
|
||||||
if (target) {
|
if (target) {
|
||||||
activeProjectId.value = savedActive;
|
activeProjectId.value = savedActive;
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activeProjectId.value = projectList.value[0]._id.toString();
|
activeProjectId.value = allProjectList.value[0]._id.toString();
|
||||||
return projectList.value[0];
|
return allProjectList.value[0];
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const projectId = computed(() => project.value?._id.toString())
|
||||||
|
|
||||||
|
|
||||||
const isGuest = computed(() => {
|
const isGuest = computed(() => {
|
||||||
return (projectList.value || []).find(e => e._id.toString() === activeProjectId.value) == undefined;
|
return (projectList.value || []).find(e => e._id.toString() === activeProjectId.value) == undefined;
|
||||||
@@ -73,5 +80,5 @@ export function useProject() {
|
|||||||
setActiveProject
|
setActiveProject
|
||||||
}
|
}
|
||||||
|
|
||||||
return { project, allProjectList, guestProjectList, projectList, actions, projectId: activeProjectId, isGuest }
|
return { project, allProjectList, guestProjectList, projectList, actions, projectId, isGuest }
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
|||||||
|
|
||||||
watch(project, async () => {
|
watch(project, async () => {
|
||||||
await remoteSnapshots.refresh();
|
await remoteSnapshots.refresh();
|
||||||
snapshot.value = isLiveDemo() ? snapshots.value[0] : snapshots.value[1];
|
snapshot.value = isLiveDemo.value ? snapshots.value[0] : snapshots.value[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshots = computed(() => {
|
const snapshots = computed(() => {
|
||||||
@@ -61,7 +61,7 @@ const snapshots = computed(() => {
|
|||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
|
||||||
const snapshot = ref<TProjectSnapshot>(isLiveDemo() ? snapshots.value[0] : snapshots.value[1]);
|
const snapshot = ref<TProjectSnapshot>(isLiveDemo.value ? snapshots.value[0] : snapshots.value[1]);
|
||||||
|
|
||||||
const safeSnapshotDates = computed(() => {
|
const safeSnapshotDates = computed(() => {
|
||||||
const from = new Date(snapshot.value?.from || 0).toISOString();
|
const from = new Date(snapshot.value?.from || 0).toISOString();
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import VueMarkdown from 'vue-markdown-render';
|
|||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
const { project, isGuest } = useProject();
|
const { project } = useProject();
|
||||||
|
|
||||||
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${project.value?._id}/chats_list`, {
|
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/chats_list`, {
|
||||||
...signHeaders()
|
headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const viewChatsList = computed(() => (chatsList.value || []).toReversed());
|
const viewChatsList = computed(() => (chatsList.value || []).toReversed());
|
||||||
|
|
||||||
const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/${project.value?._id}/chats_remaining`, signHeaders());
|
const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/chats_remaining`, {
|
||||||
|
headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
|
});
|
||||||
|
|
||||||
const currentText = ref<string>("");
|
const currentText = ref<string>("");
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
@@ -41,11 +43,15 @@ async function sendMessage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const res = await $fetch(`/api/ai/${project.value._id.toString()}/send_message`, {
|
const res = await $fetch(`/api/ai/send_message`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
...signHeaders({ 'Content-Type': 'application/json' })
|
headers: useComputedHeaders({
|
||||||
|
useSnapshotDates: false,
|
||||||
|
custom: { 'Content-Type': 'application/json' }
|
||||||
|
}).value
|
||||||
});
|
});
|
||||||
|
|
||||||
currentChatMessages.value.push({ role: 'assistant', content: res.content || 'nocontent', charts: res.charts.map(e => JSON.parse(e)) });
|
currentChatMessages.value.push({ role: 'assistant', content: res.content || 'nocontent', charts: res.charts.map(e => JSON.parse(e)) });
|
||||||
|
|
||||||
await reloadChatsRemaining();
|
await reloadChatsRemaining();
|
||||||
@@ -82,7 +88,9 @@ async function openChat(chat_id?: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentChatId.value = chat_id;
|
currentChatId.value = chat_id;
|
||||||
const messages = await $fetch(`/api/ai/${project.value._id}/${chat_id}/get_messages`, signHeaders());
|
const messages = await $fetch(`/api/ai/${chat_id}/get_messages`, {
|
||||||
|
headers: useComputedHeaders({useSnapshotDates:false}).value
|
||||||
|
});
|
||||||
if (!messages) return;
|
if (!messages) return;
|
||||||
|
|
||||||
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;
|
||||||
@@ -123,7 +131,9 @@ async function deleteChat(chat_id: string) {
|
|||||||
currentChatId.value = "";
|
currentChatId.value = "";
|
||||||
currentChatMessages.value = [];
|
currentChatMessages.value = [];
|
||||||
}
|
}
|
||||||
await $fetch(`/api/ai/${project.value._id}/${chat_id}/delete`, signHeaders());
|
await $fetch(`/api/ai/${chat_id}/delete`, {
|
||||||
|
headers: useComputedHeaders({useSnapshotDates:false}).value
|
||||||
|
});
|
||||||
await reloadChatsList();
|
await reloadChatsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +153,10 @@ const { visible: pricingDrawerVisible } = usePricingDrawer()
|
|||||||
<div class="w-[7rem] lg:w-[10rem]">
|
<div class="w-[7rem] lg:w-[10rem]">
|
||||||
<img :src="'analyst.png'" class="w-full h-full">
|
<img :src="'analyst.png'" class="w-full h-full">
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isGuest" class="poppins text-[1.2rem] text-center">
|
<div class="poppins text-[1.2rem] text-center">
|
||||||
Ask me anything about your data
|
Ask me anything about your data
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isGuest" class="poppins text-[1.2rem] text-center">
|
<div class="flex flex-col lg:grid lg:grid-cols-2 gap-4 mt-6">
|
||||||
Im not allowed to help guests :c
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col lg:grid lg:grid-cols-2 gap-4 mt-6" v-if="!isGuest">
|
|
||||||
<div v-for="prompt of defaultPrompts" @click="currentText = prompt"
|
<div v-for="prompt of defaultPrompts" @click="currentText = prompt"
|
||||||
class="bg-lyx-widget-light hover:bg-lyx-widget-lighter cursor-pointer p-4 rounded-lg poppins text-center whitespace-pre-wrap flex items-center justify-center text-[.9rem]">
|
class="bg-lyx-widget-light hover:bg-lyx-widget-lighter cursor-pointer p-4 rounded-lg poppins text-center whitespace-pre-wrap flex items-center justify-center text-[.9rem]">
|
||||||
{{ prompt }}
|
{{ prompt }}
|
||||||
@@ -201,8 +208,7 @@ const { visible: pricingDrawerVisible } = usePricingDrawer()
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!isGuest"
|
<div class="flex gap-2 items-center md:absolute fixed bottom-8 left-0 w-full px-10 xl:px-28">
|
||||||
class="flex gap-2 items-center md:absolute fixed bottom-8 left-0 w-full px-10 xl:px-28">
|
|
||||||
<input @keydown="onKeyDown" v-model="currentText"
|
<input @keydown="onKeyDown" v-model="currentText"
|
||||||
class="bg-lyx-widget-light w-full focus:outline-none px-4 py-2 rounded-lg" type="text">
|
class="bg-lyx-widget-light w-full focus:outline-none px-4 py-2 rounded-lg" type="text">
|
||||||
<div @click="sendMessage()"
|
<div @click="sendMessage()"
|
||||||
|
|||||||
@@ -62,17 +62,22 @@ const eventsData = await useFetch(`/api/data/count`, { headers: useComputedHeade
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="flex">
|
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
<EventsFunnelChart :key="refreshKey" class="w-full"></EventsFunnelChart>
|
<EventsFunnelChart :key="refreshKey" class="w-full"></EventsFunnelChart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<EventsUserFlow :key="refreshKey"></EventsUserFlow>
|
<EventsUserFlow :key="refreshKey"></EventsUserFlow>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<EventsMetadataAnalyzer :key="refreshKey"></EventsMetadataAnalyzer>
|
<EventsMetadataAnalyzer :key="refreshKey"></EventsMetadataAnalyzer>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,18 @@ definePageMeta({ layout: 'dashboard' });
|
|||||||
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { project, projectList } = useProject();
|
const { project, projectList, projectId } = useProject();
|
||||||
|
|
||||||
const justLogged = computed(() => route.query.just_logged);
|
const justLogged = computed(() => route.query.just_logged);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (justLogged.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
location.href = '/'
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const firstInteraction = useFetch<boolean>('/api/project/first_interaction', {
|
const firstInteraction = useFetch<boolean>('/api/project/first_interaction', {
|
||||||
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
|
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const selectLabelsEvents = [
|
|||||||
<div class="flex gap-2 md:pt-0 pt-4">
|
<div class="flex gap-2 md:pt-0 pt-4">
|
||||||
<LyxUiButton link="/" type="primary"
|
<LyxUiButton link="/" type="primary"
|
||||||
class="poppins font-semibold text-[.9rem] lg:text-[1.2rem] flex items-center !px-14 py-4">
|
class="poppins font-semibold text-[.9rem] lg:text-[1.2rem] flex items-center !px-14 py-4">
|
||||||
Get started for free
|
Go to dashboard
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,13 +86,14 @@ const selectLabelsEvents = [
|
|||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<DashboardTopCards></DashboardTopCards>
|
<DashboardTopCards :key="refreshKey"></DashboardTopCards>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row w-full">
|
<div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row w-full">
|
||||||
<DashboardActionableChart></DashboardActionableChart>
|
<DashboardActionableChart :key="refreshKey"></DashboardActionableChart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex gap-6 flex-col xl:flex-row p-6">
|
<div class="flex gap-6 flex-col xl:flex-row p-6">
|
||||||
|
|
||||||
<CardTitled class="p-4 flex-[4] w-full h-full" title="Events" sub="Events stacked bar chart.">
|
<CardTitled class="p-4 flex-[4] w-full h-full" title="Events" sub="Events stacked bar chart.">
|
||||||
@@ -115,47 +116,35 @@ const selectLabelsEvents = [
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex w-full justify-center px-6">
|
|
||||||
<div class="flex w-full gap-6 flex-col lg:flex-row">
|
|
||||||
<div class="flex-1">
|
|
||||||
<DashboardWebsitesBarCard></DashboardWebsitesBarCard>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<DashboardEventsBarCard></DashboardEventsBarCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex w-full justify-center mt-6 px-6">
|
<div class="flex w-full justify-center mt-6 px-6">
|
||||||
<div class="flex w-full gap-6 flex-col lg:flex-row">
|
<div class="flex w-full gap-6 flex-col xl:flex-row">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<DashboardReferrersBarCard></DashboardReferrersBarCard>
|
<BarCardWebsites :key="refreshKey"></BarCardWebsites>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<DashboardBrowsersBarCard></DashboardBrowsersBarCard>
|
<BarCardReferrers :key="refreshKey"></BarCardReferrers>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full justify-center mt-6 px-6">
|
|
||||||
<div class="flex w-full gap-6 flex-col lg:flex-row">
|
|
||||||
<div class="flex-1">
|
|
||||||
<DashboardOssBarCard></DashboardOssBarCard>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<DashboardGeolocationBarCard></DashboardGeolocationBarCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex w-full justify-center mt-6 px-6">
|
<div class="flex w-full justify-center mt-6 px-6">
|
||||||
<div class="flex w-full gap-6 flex-col xl:flex-row">
|
<div class="flex w-full gap-6 flex-col xl:flex-row">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<DashboardDevicesBarCard></DashboardDevicesBarCard>
|
<BarCardBrowsers :key="refreshKey"></BarCardBrowsers>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<!-- <DashboardGeolocationBarCard></DashboardGeolocationBarCard> -->
|
<BarCardOperatingSystems :key="refreshKey"></BarCardOperatingSystems>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full justify-center mt-6 px-6">
|
||||||
|
<div class="flex w-full gap-6 flex-col xl:flex-row">
|
||||||
|
<div class="flex-1">
|
||||||
|
<BarCardGeolocations :key="refreshKey"></BarCardGeolocations>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<BarCardDevices :key="refreshKey"></BarCardDevices>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,22 +158,18 @@ const selectLabelsEvents = [
|
|||||||
|
|
||||||
<div class="text-[1.9rem] lg:text-[2.2rem] text-center lg:text-left px-2 lg:px-0">
|
<div class="text-[1.9rem] lg:text-[2.2rem] text-center lg:text-left px-2 lg:px-0">
|
||||||
<div class="poppins font-semibold text-accent">
|
<div class="poppins font-semibold text-accent">
|
||||||
Do you want this KPIs for your website ?
|
Do you want this analytics for your website ?
|
||||||
</div>
|
</div>
|
||||||
<div class="poppins font-semibold text-text-sub">
|
<div class="poppins font-semibold text-text-sub">
|
||||||
Start now! It's free.
|
Start now and discover more.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2 flex-col md:flex-row">
|
<div class="flex gap-2 flex-col md:flex-row">
|
||||||
<LyxUiButton link="/" type="primary"
|
<LyxUiButton link="/" type="primary"
|
||||||
class="poppins font-semibold text-[1.1rem] lg:text-[1.6rem] flex items-center !px-14">
|
class="poppins font-semibold text-[.9rem] lg:text-[1.2rem] flex items-center !px-14 py-4">
|
||||||
Get started
|
Get started for free
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
<NuxtLink target="_blank" to="https://cal.com/litlyx/30min"
|
|
||||||
class="bg-white hover:bg-white/90 text-black px-14 py-4 poppins font-semibold text-[1.1rem] lg:text-[1.6rem] rounded-lg">
|
|
||||||
Book a demo
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
const activeProjectId = useActiveProjectId();
|
|
||||||
|
|
||||||
const headers = computed(() => {
|
const reportList = useFetch(`/api/security/list`, { headers: useComputedHeaders({ useSnapshotDates: false }) });
|
||||||
return {
|
|
||||||
'Authorization': authorizationHeaderComputed.value,
|
|
||||||
'x-pid': activeProjectId.data.value || ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const reportList = useFetch(`/api/security/list`, { headers });
|
|
||||||
|
|
||||||
const { createAlert } = useAlert();
|
const { createAlert } = useAlert();
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import { AiChatModel } from "@schema/ai/AiChatSchema";
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
const data = await getRequestData(event);
|
||||||
if (!project_id) return;
|
if (!data) return;
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
const { project_id } = data;
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
if (!event.context.params) return;
|
if (!event.context.params) return;
|
||||||
const chat_id = event.context.params['chat_id'];
|
const chat_id = event.context.params['chat_id'];
|
||||||
@@ -4,13 +4,10 @@ import type OpenAI from "openai";
|
|||||||
import { getChartsInMessage } from "~/server/services/AiService";
|
import { getChartsInMessage } from "~/server/services/AiService";
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
const { project_id } = data;
|
||||||
if (!project_id) return;
|
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
if (!event.context.params) return;
|
if (!event.context.params) return;
|
||||||
const chat_id = event.context.params['chat_id'];
|
const chat_id = event.context.params['chat_id'];
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
|
||||||
import { sendMessageOnChat } from "~/server/services/AiService";
|
|
||||||
import { getAiChatRemainings } from "./chats_remaining";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
|
||||||
if (!project_id) return;
|
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
|
||||||
const project = await getUserProjectFromId(project_id, user, false);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
// if (!user?.logged) return;
|
|
||||||
// if (!user.user.roles.includes('ADMIN')) return;
|
|
||||||
|
|
||||||
const { text, chat_id } = await readBody(event);
|
|
||||||
if (!text) return setResponseStatus(event, 400, 'text parameter missing');
|
|
||||||
|
|
||||||
const chatsRemaining = await getAiChatRemainings(project_id);
|
|
||||||
if (chatsRemaining <= 0) return setResponseStatus(event, 400, 'CHAT_LIMIT_REACHED');
|
|
||||||
|
|
||||||
const response = await sendMessageOnChat(text, project._id.toString(), chat_id);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
@@ -3,12 +3,11 @@ import { AiChatModel } from "@schema/ai/AiChatSchema";
|
|||||||
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
const project_id = getRequestProjectId(event);
|
|
||||||
if (!project_id) return;
|
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
const data = await getRequestData(event);
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
if (!data) return;
|
||||||
if (!project) return;
|
|
||||||
|
const { project_id } = data;
|
||||||
|
|
||||||
const chatList = await AiChatModel.find({ project_id }, { _id: 1, title: 1 }, { sort: { updated_at: 1 } });
|
const chatList = await AiChatModel.find({ project_id }, { _id: 1, title: 1 }, { sort: { updated_at: 1 } });
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ProjectLimitModel } from "@schema/ProjectsLimits";
|
import { ProjectLimitModel } from "@schema/ProjectsLimits";
|
||||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
|
||||||
|
|
||||||
export async function getAiChatRemainings(project_id: string) {
|
export async function getAiChatRemainings(project_id: string) {
|
||||||
const limits = await ProjectLimitModel.findOne({ project_id })
|
const limits = await ProjectLimitModel.findOne({ project_id })
|
||||||
@@ -11,13 +10,11 @@ export async function getAiChatRemainings(project_id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
const project_id = getRequestProjectId(event);
|
const data = await getRequestData(event);
|
||||||
if (!project_id) return;
|
if (!data) return;
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
const { pid } = data;
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
const chatsRemaining = await getAiChatRemainings(project_id);
|
const chatsRemaining = await getAiChatRemainings(pid);
|
||||||
return chatsRemaining;
|
return chatsRemaining;
|
||||||
});
|
});
|
||||||
21
dashboard/server/api/ai/send_message.post.ts
Normal file
21
dashboard/server/api/ai/send_message.post.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { sendMessageOnChat } from "~/server/services/AiService";
|
||||||
|
import { getAiChatRemainings } from "./chats_remaining";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { pid } = data;
|
||||||
|
|
||||||
|
const { text, chat_id } = await readBody(event);
|
||||||
|
if (!text) return setResponseStatus(event, 400, 'text parameter missing');
|
||||||
|
|
||||||
|
const chatsRemaining = await getAiChatRemainings(pid);
|
||||||
|
if (chatsRemaining <= 0) return setResponseStatus(event, 400, 'CHAT_LIMIT_REACHED');
|
||||||
|
|
||||||
|
const response = await sendMessageOnChat(text, pid, chat_id);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
@@ -5,7 +5,7 @@ import { Redis } from "~/server/services/CacheService";
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const data = await getRequestData(event, { requireSchema: false });
|
const data = await getRequestData(event);
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
const { project_id } = data;
|
const { project_id } = data;
|
||||||
|
|||||||
@@ -15,12 +15,10 @@ export type SecutityReport = (TSecurityDomainEntry | TSecurityVisitEntry | TSecu
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const project_id = getHeader(event, 'x-pid');
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
if (!project_id) return;
|
if (!data) return;
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
const { project_id } = data;
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
const visits = await AnomalyVisitModel.find({ project_id }, { _id: 0, project_id: 0 });
|
const visits = await AnomalyVisitModel.find({ project_id }, { _id: 0, project_id: 0 });
|
||||||
const events = await AnomalyEventsModel.find({ project_id }, { _id: 0, project_id: 0 });
|
const events = await AnomalyEventsModel.find({ project_id }, { _id: 0, project_id: 0 });
|
||||||
|
|||||||
Reference in New Issue
Block a user