mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-04 08:50:24 +02:00
add personal assistant usage stats (#3543)
This commit is contained in:
parent
62302e3faf
commit
6c018cb53f
@ -2,6 +2,7 @@ import datetime
|
|||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from sqlalchemy import and_
|
||||||
from sqlalchemy import case
|
from sqlalchemy import case
|
||||||
from sqlalchemy import cast
|
from sqlalchemy import cast
|
||||||
from sqlalchemy import Date
|
from sqlalchemy import Date
|
||||||
@ -14,6 +15,9 @@ from onyx.configs.constants import MessageType
|
|||||||
from onyx.db.models import ChatMessage
|
from onyx.db.models import ChatMessage
|
||||||
from onyx.db.models import ChatMessageFeedback
|
from onyx.db.models import ChatMessageFeedback
|
||||||
from onyx.db.models import ChatSession
|
from onyx.db.models import ChatSession
|
||||||
|
from onyx.db.models import Persona
|
||||||
|
from onyx.db.models import User
|
||||||
|
from onyx.db.models import UserRole
|
||||||
|
|
||||||
|
|
||||||
def fetch_query_analytics(
|
def fetch_query_analytics(
|
||||||
@ -234,3 +238,121 @@ def fetch_persona_unique_users(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return [tuple(row) for row in db_session.execute(query).all()]
|
return [tuple(row) for row in db_session.execute(query).all()]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_assistant_message_analytics(
|
||||||
|
db_session: Session,
|
||||||
|
assistant_id: int,
|
||||||
|
start: datetime.datetime,
|
||||||
|
end: datetime.datetime,
|
||||||
|
) -> list[tuple[int, datetime.date]]:
|
||||||
|
"""
|
||||||
|
Gets the daily message counts for a specific assistant in the given time range.
|
||||||
|
"""
|
||||||
|
query = (
|
||||||
|
select(
|
||||||
|
func.count(ChatMessage.id),
|
||||||
|
cast(ChatMessage.time_sent, Date),
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
ChatSession,
|
||||||
|
ChatMessage.chat_session_id == ChatSession.id,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
or_(
|
||||||
|
ChatMessage.alternate_assistant_id == assistant_id,
|
||||||
|
ChatSession.persona_id == assistant_id,
|
||||||
|
),
|
||||||
|
ChatMessage.time_sent >= start,
|
||||||
|
ChatMessage.time_sent <= end,
|
||||||
|
ChatMessage.message_type == MessageType.ASSISTANT,
|
||||||
|
)
|
||||||
|
.group_by(cast(ChatMessage.time_sent, Date))
|
||||||
|
.order_by(cast(ChatMessage.time_sent, Date))
|
||||||
|
)
|
||||||
|
|
||||||
|
return [tuple(row) for row in db_session.execute(query).all()]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_assistant_unique_users(
|
||||||
|
db_session: Session,
|
||||||
|
assistant_id: int,
|
||||||
|
start: datetime.datetime,
|
||||||
|
end: datetime.datetime,
|
||||||
|
) -> list[tuple[int, datetime.date]]:
|
||||||
|
"""
|
||||||
|
Gets the daily unique user counts for a specific assistant in the given time range.
|
||||||
|
"""
|
||||||
|
query = (
|
||||||
|
select(
|
||||||
|
func.count(func.distinct(ChatSession.user_id)),
|
||||||
|
cast(ChatMessage.time_sent, Date),
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
ChatSession,
|
||||||
|
ChatMessage.chat_session_id == ChatSession.id,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
or_(
|
||||||
|
ChatMessage.alternate_assistant_id == assistant_id,
|
||||||
|
ChatSession.persona_id == assistant_id,
|
||||||
|
),
|
||||||
|
ChatMessage.time_sent >= start,
|
||||||
|
ChatMessage.time_sent <= end,
|
||||||
|
ChatMessage.message_type == MessageType.ASSISTANT,
|
||||||
|
)
|
||||||
|
.group_by(cast(ChatMessage.time_sent, Date))
|
||||||
|
.order_by(cast(ChatMessage.time_sent, Date))
|
||||||
|
)
|
||||||
|
|
||||||
|
return [tuple(row) for row in db_session.execute(query).all()]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_assistant_unique_users_total(
|
||||||
|
db_session: Session,
|
||||||
|
assistant_id: int,
|
||||||
|
start: datetime.datetime,
|
||||||
|
end: datetime.datetime,
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Gets the total number of distinct users who have sent or received messages from
|
||||||
|
the specified assistant in the given time range.
|
||||||
|
"""
|
||||||
|
query = (
|
||||||
|
select(func.count(func.distinct(ChatSession.user_id)))
|
||||||
|
.select_from(ChatMessage)
|
||||||
|
.join(
|
||||||
|
ChatSession,
|
||||||
|
ChatMessage.chat_session_id == ChatSession.id,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
or_(
|
||||||
|
ChatMessage.alternate_assistant_id == assistant_id,
|
||||||
|
ChatSession.persona_id == assistant_id,
|
||||||
|
),
|
||||||
|
ChatMessage.time_sent >= start,
|
||||||
|
ChatMessage.time_sent <= end,
|
||||||
|
ChatMessage.message_type == MessageType.ASSISTANT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = db_session.execute(query).scalar()
|
||||||
|
return result if result else 0
|
||||||
|
|
||||||
|
|
||||||
|
# Users can view assistant stats if they created the persona,
|
||||||
|
# or if they are an admin
|
||||||
|
def user_can_view_assistant_stats(
|
||||||
|
db_session: Session, user: User | None, assistant_id: int
|
||||||
|
) -> bool:
|
||||||
|
# If user is None, assume the user is an admin or auth is disabled
|
||||||
|
if user is None or user.role == UserRole.ADMIN:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if the user created the persona
|
||||||
|
stmt = select(Persona).where(
|
||||||
|
and_(Persona.id == assistant_id, Persona.user_id == user.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
persona = db_session.execute(stmt).scalar_one_or_none()
|
||||||
|
return persona is not None
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
|
from fastapi import HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from ee.onyx.db.analytics import fetch_assistant_message_analytics
|
||||||
|
from ee.onyx.db.analytics import fetch_assistant_unique_users
|
||||||
|
from ee.onyx.db.analytics import fetch_assistant_unique_users_total
|
||||||
from ee.onyx.db.analytics import fetch_onyxbot_analytics
|
from ee.onyx.db.analytics import fetch_onyxbot_analytics
|
||||||
from ee.onyx.db.analytics import fetch_per_user_query_analytics
|
from ee.onyx.db.analytics import fetch_per_user_query_analytics
|
||||||
from ee.onyx.db.analytics import fetch_persona_message_analytics
|
from ee.onyx.db.analytics import fetch_persona_message_analytics
|
||||||
from ee.onyx.db.analytics import fetch_persona_unique_users
|
from ee.onyx.db.analytics import fetch_persona_unique_users
|
||||||
from ee.onyx.db.analytics import fetch_query_analytics
|
from ee.onyx.db.analytics import fetch_query_analytics
|
||||||
|
from ee.onyx.db.analytics import user_can_view_assistant_stats
|
||||||
from onyx.auth.users import current_admin_user
|
from onyx.auth.users import current_admin_user
|
||||||
|
from onyx.auth.users import current_user
|
||||||
from onyx.db.engine import get_session
|
from onyx.db.engine import get_session
|
||||||
from onyx.db.models import User
|
from onyx.db.models import User
|
||||||
|
|
||||||
@ -191,3 +198,74 @@ def get_persona_unique_users(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return unique_user_counts
|
return unique_user_counts
|
||||||
|
|
||||||
|
|
||||||
|
class AssistantDailyUsageResponse(BaseModel):
|
||||||
|
date: datetime.date
|
||||||
|
total_messages: int
|
||||||
|
total_unique_users: int
|
||||||
|
|
||||||
|
|
||||||
|
class AssistantStatsResponse(BaseModel):
|
||||||
|
daily_stats: List[AssistantDailyUsageResponse]
|
||||||
|
total_messages: int
|
||||||
|
total_unique_users: int
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/assistant/{assistant_id}/stats")
|
||||||
|
def get_assistant_stats(
|
||||||
|
assistant_id: int,
|
||||||
|
start: datetime.datetime | None = None,
|
||||||
|
end: datetime.datetime | None = None,
|
||||||
|
user: User | None = Depends(current_user),
|
||||||
|
db_session: Session = Depends(get_session),
|
||||||
|
) -> AssistantStatsResponse:
|
||||||
|
"""
|
||||||
|
Returns daily message and unique user counts for a user's assistant,
|
||||||
|
along with the overall total messages and total distinct users.
|
||||||
|
"""
|
||||||
|
start = start or (
|
||||||
|
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
|
||||||
|
)
|
||||||
|
end = end or datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
if not user_can_view_assistant_stats(db_session, user, assistant_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403, detail="Not allowed to access this assistant's stats."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pull daily usage from the DB calls
|
||||||
|
messages_data = fetch_assistant_message_analytics(
|
||||||
|
db_session, assistant_id, start, end
|
||||||
|
)
|
||||||
|
unique_users_data = fetch_assistant_unique_users(
|
||||||
|
db_session, assistant_id, start, end
|
||||||
|
)
|
||||||
|
|
||||||
|
# Map each day => (messages, unique_users).
|
||||||
|
daily_messages_map = {date: count for count, date in messages_data}
|
||||||
|
daily_unique_users_map = {date: count for count, date in unique_users_data}
|
||||||
|
all_dates = set(daily_messages_map.keys()) | set(daily_unique_users_map.keys())
|
||||||
|
|
||||||
|
# Merge both sets of metrics by date
|
||||||
|
daily_results: list[AssistantDailyUsageResponse] = []
|
||||||
|
for date in sorted(all_dates):
|
||||||
|
daily_results.append(
|
||||||
|
AssistantDailyUsageResponse(
|
||||||
|
date=date,
|
||||||
|
total_messages=daily_messages_map.get(date, 0),
|
||||||
|
total_unique_users=daily_unique_users_map.get(date, 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now pull a single total distinct user count across the entire time range
|
||||||
|
total_msgs = sum(d.total_messages for d in daily_results)
|
||||||
|
total_users = fetch_assistant_unique_users_total(
|
||||||
|
db_session, assistant_id, start, end
|
||||||
|
)
|
||||||
|
|
||||||
|
return AssistantStatsResponse(
|
||||||
|
daily_stats=daily_results,
|
||||||
|
total_messages=total_msgs,
|
||||||
|
total_unique_users=total_users,
|
||||||
|
)
|
||||||
|
@ -99,6 +99,9 @@ def _add_user_filters(
|
|||||||
return stmt.where(where_clause)
|
return stmt.where(where_clause)
|
||||||
|
|
||||||
|
|
||||||
|
# fetch_persona_by_id is used to fetch a persona by its ID. It is used to fetch a persona by its ID.
|
||||||
|
|
||||||
|
|
||||||
def fetch_persona_by_id(
|
def fetch_persona_by_id(
|
||||||
db_session: Session, persona_id: int, user: User | None, get_editable: bool = True
|
db_session: Session, persona_id: int, user: User | None, get_editable: bool = True
|
||||||
) -> Persona:
|
) -> Persona:
|
||||||
|
@ -6,6 +6,7 @@ import { Persona } from "@/app/admin/assistants/interfaces";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {
|
import {
|
||||||
|
FiBarChart,
|
||||||
FiEdit2,
|
FiEdit2,
|
||||||
FiList,
|
FiList,
|
||||||
FiMinus,
|
FiMinus,
|
||||||
@ -59,6 +60,7 @@ import { MakePublicAssistantModal } from "@/app/chat/modal/MakePublicAssistantMo
|
|||||||
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
|
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
|
||||||
import { useAssistants } from "@/components/context/AssistantsContext";
|
import { useAssistants } from "@/components/context/AssistantsContext";
|
||||||
import { useUser } from "@/components/user/UserProvider";
|
import { useUser } from "@/components/user/UserProvider";
|
||||||
|
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||||
|
|
||||||
function DraggableAssistantListItem({ ...props }: any) {
|
function DraggableAssistantListItem({ ...props }: any) {
|
||||||
const {
|
const {
|
||||||
@ -116,7 +118,9 @@ function AssistantListItem({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [showSharingModal, setShowSharingModal] = useState(false);
|
const [showSharingModal, setShowSharingModal] = useState(false);
|
||||||
|
|
||||||
|
const isEnterpriseEnabled = usePaidEnterpriseFeaturesEnabled();
|
||||||
const isOwnedByUser = checkUserOwnsAssistant(user, assistant);
|
const isOwnedByUser = checkUserOwnsAssistant(user, assistant);
|
||||||
|
const { isAdmin } = useUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -243,6 +247,18 @@ function AssistantListItem({
|
|||||||
<FiPlus size={18} className="text-text-800" /> Add
|
<FiPlus size={18} className="text-text-800" /> Add
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
|
|
||||||
|
(isOwnedByUser || isAdmin) && isEnterpriseEnabled ? (
|
||||||
|
<button
|
||||||
|
key="view-stats"
|
||||||
|
className="flex items-center gap-x-2 px-4 py-2 hover:bg-gray-100 w-full text-left"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/assistants/stats/${assistant.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FiBarChart size={18} /> View Stats
|
||||||
|
</button>
|
||||||
|
) : null,
|
||||||
isOwnedByUser ? (
|
isOwnedByUser ? (
|
||||||
<button
|
<button
|
||||||
key="delete"
|
key="delete"
|
||||||
@ -373,7 +389,6 @@ export function AssistantsList() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{makePublicPersona && (
|
{makePublicPersona && (
|
||||||
<MakePublicAssistantModal
|
<MakePublicAssistantModal
|
||||||
isPublic={makePublicPersona.is_public}
|
isPublic={makePublicPersona.is_public}
|
||||||
|
@ -172,8 +172,6 @@ export function PersonaMessagesChart({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedPersona = personaList?.find((p) => p.id === selectedPersonaId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardSection className="mt-8">
|
<CardSection className="mt-8">
|
||||||
<Title>Persona Analytics</Title>
|
<Title>Persona Analytics</Title>
|
||||||
|
188
web/src/app/ee/assistants/stats/[id]/AssistantStats.tsx
Normal file
188
web/src/app/ee/assistants/stats/[id]/AssistantStats.tsx
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import { ThreeDotsLoader } from "@/components/Loading";
|
||||||
|
import { getDatesList } from "@/app/ee/admin/performance/lib";
|
||||||
|
import Text from "@/components/ui/text";
|
||||||
|
import Title from "@/components/ui/title";
|
||||||
|
import CardSection from "@/components/admin/CardSection";
|
||||||
|
import { AreaChartDisplay } from "@/components/ui/areaChart";
|
||||||
|
import { useEffect, useState, useMemo } from "react";
|
||||||
|
import {
|
||||||
|
DateRangeSelector,
|
||||||
|
DateRange,
|
||||||
|
} from "@/app/ee/admin/performance/DateRangeSelector";
|
||||||
|
import { useAssistants } from "@/components/context/AssistantsContext";
|
||||||
|
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||||
|
|
||||||
|
type AssistantDailyUsageEntry = {
|
||||||
|
date: string;
|
||||||
|
total_messages: number;
|
||||||
|
total_unique_users: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AssistantStatsResponse = {
|
||||||
|
daily_stats: AssistantDailyUsageEntry[];
|
||||||
|
total_messages: number;
|
||||||
|
total_unique_users: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AssistantStats({ assistantId }: { assistantId: number }) {
|
||||||
|
const [assistantStats, setAssistantStats] =
|
||||||
|
useState<AssistantStatsResponse | null>(null);
|
||||||
|
const { assistants } = useAssistants();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [dateRange, setDateRange] = useState<DateRange>({
|
||||||
|
from: new Date(new Date().setDate(new Date().getDate() - 30)),
|
||||||
|
to: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const assistant = useMemo(() => {
|
||||||
|
return assistants.find((a) => a.id === assistantId);
|
||||||
|
}, [assistants, assistantId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchStats() {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/analytics/assistant/${assistantId}/stats?start=${
|
||||||
|
dateRange?.from?.toISOString() || ""
|
||||||
|
}&end=${dateRange?.to?.toISOString() || ""}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 403) {
|
||||||
|
throw new Error("You don't have permission to view these stats.");
|
||||||
|
}
|
||||||
|
throw new Error("Failed to fetch assistant stats");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as AssistantStatsResponse;
|
||||||
|
setAssistantStats(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(
|
||||||
|
err instanceof Error ? err.message : "An unknown error occurred"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats();
|
||||||
|
}, [assistantId, dateRange]);
|
||||||
|
|
||||||
|
const chartData = useMemo(() => {
|
||||||
|
if (!assistantStats?.daily_stats?.length || !dateRange) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialDate =
|
||||||
|
dateRange.from ||
|
||||||
|
new Date(
|
||||||
|
Math.min(
|
||||||
|
...assistantStats.daily_stats.map((entry) =>
|
||||||
|
new Date(entry.date).getTime()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const endDate = dateRange.to || new Date();
|
||||||
|
|
||||||
|
const dateRangeList = getDatesList(initialDate);
|
||||||
|
|
||||||
|
const statsMap = new Map(
|
||||||
|
assistantStats.daily_stats.map((entry) => [entry.date, entry])
|
||||||
|
);
|
||||||
|
|
||||||
|
return dateRangeList
|
||||||
|
.filter((date) => new Date(date) <= endDate)
|
||||||
|
.map((dateStr) => {
|
||||||
|
const dayData = statsMap.get(dateStr);
|
||||||
|
return {
|
||||||
|
Day: dateStr,
|
||||||
|
Messages: dayData?.total_messages || 0,
|
||||||
|
"Unique Users": dayData?.total_unique_users || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [assistantStats, dateRange]);
|
||||||
|
|
||||||
|
const totalMessages = assistantStats?.total_messages ?? 0;
|
||||||
|
const totalUniqueUsers = assistantStats?.total_unique_users ?? 0;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (isLoading || !assistant) {
|
||||||
|
content = (
|
||||||
|
<div className="h-80 flex flex-col">
|
||||||
|
<ThreeDotsLoader />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (error) {
|
||||||
|
content = (
|
||||||
|
<div className="h-80 text-red-600 text-bold flex flex-col">
|
||||||
|
<p className="m-auto">{error}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (!assistantStats?.daily_stats?.length) {
|
||||||
|
content = (
|
||||||
|
<div className="h-80 text-gray-500 flex flex-col">
|
||||||
|
<p className="m-auto">
|
||||||
|
No data found for this assistant in the selected date range
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (chartData) {
|
||||||
|
content = (
|
||||||
|
<AreaChartDisplay
|
||||||
|
className="mt-4"
|
||||||
|
data={chartData}
|
||||||
|
categories={["Messages", "Unique Users"]}
|
||||||
|
index="Day"
|
||||||
|
colors={["indigo", "fuchsia"]}
|
||||||
|
yAxisWidth={60}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardSection className="mt-8">
|
||||||
|
<div className="flex justify-between items-start mb-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Title>Assistant Analytics</Title>
|
||||||
|
<Text>
|
||||||
|
Messages and unique users per day for the assistant{" "}
|
||||||
|
<b>{assistant?.name}</b>
|
||||||
|
</Text>
|
||||||
|
<DateRangeSelector value={dateRange} onValueChange={setDateRange} />
|
||||||
|
</div>
|
||||||
|
{assistant && (
|
||||||
|
<div className="bg-gray-100 p-4 rounded-lg shadow-sm">
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<AssistantIcon
|
||||||
|
disableToolip
|
||||||
|
size="medium"
|
||||||
|
assistant={assistant}
|
||||||
|
/>
|
||||||
|
<Title className="text-lg ml-3">{assistant?.name}</Title>
|
||||||
|
</div>
|
||||||
|
<Text className="text-gray-600 text-sm">
|
||||||
|
{assistant?.description}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<Text className="font-semibold">Total Messages</Text>
|
||||||
|
<Text>{totalMessages}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text className="font-semibold">Total Unique Users</Text>
|
||||||
|
<Text>{totalUniqueUsers}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{content}
|
||||||
|
</CardSection>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
import SidebarWrapper from "../../../../assistants/SidebarWrapper";
|
||||||
|
import { AssistantStats } from "./AssistantStats";
|
||||||
|
|
||||||
|
export default function WrappedAssistantsStats({
|
||||||
|
initiallyToggled,
|
||||||
|
assistantId,
|
||||||
|
}: {
|
||||||
|
initiallyToggled: boolean;
|
||||||
|
assistantId: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SidebarWrapper page="chat" initiallyToggled={initiallyToggled}>
|
||||||
|
<AssistantStats assistantId={assistantId} />
|
||||||
|
</SidebarWrapper>
|
||||||
|
);
|
||||||
|
}
|
68
web/src/app/ee/assistants/stats/[id]/page.tsx
Normal file
68
web/src/app/ee/assistants/stats/[id]/page.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
|
||||||
|
|
||||||
|
import { fetchChatData } from "@/lib/chat/fetchChatData";
|
||||||
|
import { unstable_noStore as noStore } from "next/cache";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import { WelcomeModal } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { ChatProvider } from "@/components/context/ChatContext";
|
||||||
|
import WrappedAssistantsStats from "./WrappedAssistantsStats";
|
||||||
|
|
||||||
|
export default async function GalleryPage(props: {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}) {
|
||||||
|
const params = await props.params;
|
||||||
|
noStore();
|
||||||
|
const requestCookies = await cookies();
|
||||||
|
|
||||||
|
const data = await fetchChatData({});
|
||||||
|
|
||||||
|
if ("redirect" in data) {
|
||||||
|
redirect(data.redirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
user,
|
||||||
|
chatSessions,
|
||||||
|
folders,
|
||||||
|
openedFolders,
|
||||||
|
toggleSidebar,
|
||||||
|
shouldShowWelcomeModal,
|
||||||
|
availableSources,
|
||||||
|
ccPairs,
|
||||||
|
documentSets,
|
||||||
|
tags,
|
||||||
|
llmProviders,
|
||||||
|
defaultAssistantId,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatProvider
|
||||||
|
value={{
|
||||||
|
chatSessions,
|
||||||
|
availableSources,
|
||||||
|
ccPairs,
|
||||||
|
documentSets,
|
||||||
|
tags,
|
||||||
|
availableDocumentSets: documentSets,
|
||||||
|
availableTags: tags,
|
||||||
|
llmProviders,
|
||||||
|
folders,
|
||||||
|
openedFolders,
|
||||||
|
shouldShowWelcomeModal,
|
||||||
|
defaultAssistantId,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shouldShowWelcomeModal && (
|
||||||
|
<WelcomeModal user={user} requestCookies={requestCookies} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<InstantSSRAutoRefresh />
|
||||||
|
<WrappedAssistantsStats
|
||||||
|
initiallyToggled={toggleSidebar}
|
||||||
|
assistantId={parseInt(params.id)}
|
||||||
|
/>
|
||||||
|
</ChatProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -12,6 +12,7 @@ export const config = {
|
|||||||
"/admin/whitelabeling/:path*",
|
"/admin/whitelabeling/:path*",
|
||||||
"/admin/performance/custom-analytics/:path*",
|
"/admin/performance/custom-analytics/:path*",
|
||||||
"/admin/standard-answer/:path*",
|
"/admin/standard-answer/:path*",
|
||||||
|
"/assistants/stats/:path*",
|
||||||
|
|
||||||
// Cloud only
|
// Cloud only
|
||||||
"/admin/billing/:path*",
|
"/admin/billing/:path*",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user