feat: agents route replacing chatbots

- Removed API Keys tab from SettingsBar and adjusted tab layout.
- Improved styling for tab scrolling buttons and gradient indicators.
- Introduced AgentDetailsModal for displaying agent access details.
- Updated Analytics component to fetch agent data and handle analytics for selected agent.
- Refactored Logs component to accept agentId as a prop for filtering logs.
- Enhanced type definitions for InputProps to include textSize.
- Cleaned up unused imports and optimized component structure across various files.
This commit is contained in:
Siddhant Rai
2025-04-11 17:24:22 +05:30
parent 94c7bba168
commit fa1f9d7009
29 changed files with 2001 additions and 579 deletions

View File

@@ -7,11 +7,12 @@ import {
Title,
Tooltip,
} from 'chart.js';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Bar } from 'react-chartjs-2';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Agent } from '../agents/types';
import userService from '../api/services/userService';
import Dropdown from '../components/Dropdown';
import SkeletonLoader from '../components/SkeletonLoader';
@@ -19,7 +20,6 @@ import { useLoaderState } from '../hooks';
import { selectToken } from '../preferences/preferenceSlice';
import { htmlLegendPlugin } from '../utils/chartUtils';
import { formatDate } from '../utils/dateTimeUtils';
import { APIKeyData } from './types';
import type { ChartData } from 'chart.js';
ChartJS.register(
@@ -31,7 +31,11 @@ ChartJS.register(
Legend,
);
export default function Analytics() {
type AnalyticsProps = {
agentId?: string;
};
export default function Analytics({ agentId }: AnalyticsProps) {
const { t } = useTranslation();
const token = useSelector(selectToken);
@@ -67,8 +71,7 @@ export default function Analytics() {
string,
{ positive: number; negative: number }
> | null>(null);
const [chatbots, setChatbots] = useState<APIKeyData[]>([]);
const [selectedChatbot, setSelectedChatbot] = useState<APIKeyData | null>();
const [agent, setAgent] = useState<Agent>();
const [messagesFilter, setMessagesFilter] = useState<{
label: string;
value: string;
@@ -94,37 +97,33 @@ export default function Analytics() {
const [loadingMessages, setLoadingMessages] = useLoaderState(true);
const [loadingTokens, setLoadingTokens] = useLoaderState(true);
const [loadingFeedback, setLoadingFeedback] = useLoaderState(true);
const [loadingChatbots, setLoadingChatbots] = useLoaderState(true);
const [loadingAgent, setLoadingAgent] = useLoaderState(true);
const fetchChatbots = async () => {
setLoadingChatbots(true);
const fetchAgent = async (agentId: string) => {
setLoadingAgent(true);
try {
const response = await userService.getAPIKeys(token);
if (!response.ok) {
throw new Error('Failed to fetch Chatbots');
}
const chatbots = await response.json();
setChatbots(chatbots);
const response = await userService.getAgent(agentId ?? '', token);
if (!response.ok) throw new Error('Failed to fetch Chatbots');
const agent = await response.json();
setAgent(agent);
} catch (error) {
console.error(error);
} finally {
setLoadingChatbots(false);
setLoadingAgent(false);
}
};
const fetchMessagesData = async (chatbot_id?: string, filter?: string) => {
const fetchMessagesData = async (agent_id?: string, filter?: string) => {
setLoadingMessages(true);
try {
const response = await userService.getMessageAnalytics(
{
api_key_id: chatbot_id,
api_key_id: agent_id,
filter_option: filter,
},
token,
);
if (!response.ok) {
throw new Error('Failed to fetch analytics data');
}
if (!response.ok) throw new Error('Failed to fetch analytics data');
const data = await response.json();
setMessagesData(data.messages);
} catch (error) {
@@ -134,19 +133,17 @@ export default function Analytics() {
}
};
const fetchTokenData = async (chatbot_id?: string, filter?: string) => {
const fetchTokenData = async (agent_id?: string, filter?: string) => {
setLoadingTokens(true);
try {
const response = await userService.getTokenAnalytics(
{
api_key_id: chatbot_id,
api_key_id: agent_id,
filter_option: filter,
},
token,
);
if (!response.ok) {
throw new Error('Failed to fetch analytics data');
}
if (!response.ok) throw new Error('Failed to fetch analytics data');
const data = await response.json();
setTokenUsageData(data.token_usage);
} catch (error) {
@@ -156,19 +153,17 @@ export default function Analytics() {
}
};
const fetchFeedbackData = async (chatbot_id?: string, filter?: string) => {
const fetchFeedbackData = async (agent_id?: string, filter?: string) => {
setLoadingFeedback(true);
try {
const response = await userService.getFeedbackAnalytics(
{
api_key_id: chatbot_id,
api_key_id: agent_id,
filter_option: filter,
},
token,
);
if (!response.ok) {
throw new Error('Failed to fetch analytics data');
}
if (!response.ok) throw new Error('Failed to fetch analytics data');
const data = await response.json();
setFeedbackData(data.feedback);
} catch (error) {
@@ -179,229 +174,182 @@ export default function Analytics() {
};
useEffect(() => {
fetchChatbots();
if (agentId) fetchAgent(agentId);
}, []);
useEffect(() => {
const id = selectedChatbot?.id;
const id = agent?.id;
const filter = messagesFilter;
fetchMessagesData(id, filter?.value);
}, [selectedChatbot, messagesFilter]);
}, [agent, messagesFilter]);
useEffect(() => {
const id = selectedChatbot?.id;
const id = agent?.id;
const filter = tokenUsageFilter;
fetchTokenData(id, filter?.value);
}, [selectedChatbot, tokenUsageFilter]);
}, [agent, tokenUsageFilter]);
useEffect(() => {
const id = selectedChatbot?.id;
const id = agent?.id;
const filter = feedbackFilter;
fetchFeedbackData(id, filter?.value);
}, [selectedChatbot, feedbackFilter]);
}, [agent, feedbackFilter]);
return (
<div className="mt-12">
<div className="flex flex-col items-start">
{loadingChatbots ? (
<SkeletonLoader component="dropdown" />
) : (
<div className="flex flex-col gap-3">
{/* Messages Analytics */}
<div className="mt-8 flex w-full flex-col gap-3 [@media(min-width:1080px)]:flex-row">
<div className="h-[345px] w-full overflow-hidden rounded-2xl border border-silver px-6 py-5 dark:border-silver/40 [@media(min-width:1080px)]:w-1/2">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.filterByChatbot')}
{t('settings.analytics.messages')}
</p>
<Dropdown
size="w-[55vw] sm:w-[360px]"
options={[
...chatbots.map((chatbot) => ({
label: chatbot.name,
value: chatbot.id,
})),
{ label: t('settings.analytics.none'), value: '' },
]}
placeholder={t('settings.analytics.selectChatbot')}
onSelect={(chatbot: { label: string; value: string }) => {
setSelectedChatbot(
chatbots.find((item) => item.id === chatbot.value),
);
size="w-[125px]"
options={filterOptions}
placeholder={t('settings.analytics.filterPlaceholder')}
onSelect={(selectedOption: { label: string; value: string }) => {
setMessagesFilter(selectedOption);
}}
selectedValue={
(selectedChatbot && {
label: selectedChatbot.name,
value: selectedChatbot.id,
}) ||
null
}
selectedValue={messagesFilter ?? null}
rounded="3xl"
border="border"
darkBorderColor="dim-gray"
contentSize="text-sm"
/>
</div>
)}
{/* Messages Analytics */}
<div className="mt-8 w-full flex flex-col [@media(min-width:1080px)]:flex-row gap-3">
<div className="h-[345px] [@media(min-width:1080px)]:w-1/2 w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.messages')}
</p>
<Dropdown
size="w-[125px]"
options={filterOptions}
placeholder={t('settings.analytics.filterPlaceholder')}
onSelect={(selectedOption: {
label: string;
value: string;
}) => {
setMessagesFilter(selectedOption);
<div className="relative mt-px h-[245px] w-full">
<div
id="legend-container-1"
className="flex flex-row items-center justify-end"
></div>
{loadingMessages ? (
<SkeletonLoader count={1} component={'analysis'} />
) : (
<AnalyticsChart
data={{
labels: Object.keys(messagesData || {}).map((item) =>
formatDate(item),
),
datasets: [
{
label: t('settings.analytics.messages'),
data: Object.values(messagesData || {}),
backgroundColor: '#7D54D1',
},
],
}}
selectedValue={messagesFilter ?? null}
rounded="3xl"
border="border"
contentSize="text-sm"
legendID="legend-container-1"
maxTicksLimitInX={8}
isStacked={false}
/>
</div>
<div className="mt-px relative h-[245px] w-full">
<div
id="legend-container-1"
className="flex flex-row items-center justify-end"
></div>
{loadingMessages ? (
<SkeletonLoader count={1} component={'analysis'} />
) : (
<AnalyticsChart
data={{
labels: Object.keys(messagesData || {}).map((item) =>
formatDate(item),
),
datasets: [
{
label: t('settings.analytics.messages'),
data: Object.values(messagesData || {}),
backgroundColor: '#7D54D1',
},
],
}}
legendID="legend-container-1"
maxTicksLimitInX={8}
isStacked={false}
/>
)}
</div>
</div>
{/* Token Usage Analytics */}
<div className="h-[345px] [@media(min-width:1080px)]:w-1/2 w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.tokenUsage')}
</p>
<Dropdown
size="w-[125px]"
options={filterOptions}
placeholder={t('settings.analytics.filterPlaceholder')}
onSelect={(selectedOption: {
label: string;
value: string;
}) => {
setTokenUsageFilter(selectedOption);
}}
selectedValue={tokenUsageFilter ?? null}
rounded="3xl"
border="border"
contentSize="text-sm"
/>
</div>
<div className="mt-px relative h-[245px] w-full">
<div
id="legend-container-2"
className="flex flex-row items-center justify-end"
></div>
{loadingTokens ? (
<SkeletonLoader count={1} component={'analysis'} />
) : (
<AnalyticsChart
data={{
labels: Object.keys(tokenUsageData || {}).map((item) =>
formatDate(item),
),
datasets: [
{
label: t('settings.analytics.tokenUsage'),
data: Object.values(tokenUsageData || {}),
backgroundColor: '#7D54D1',
},
],
}}
legendID="legend-container-2"
maxTicksLimitInX={8}
isStacked={false}
/>
)}
</div>
)}
</div>
</div>
{/* Feedback Analytics */}
<div className="mt-8 w-full flex flex-col gap-3">
<div className="h-[345px] w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.userFeedback')}
</p>
<Dropdown
size="w-[125px]"
options={filterOptions}
placeholder={t('settings.analytics.filterPlaceholder')}
onSelect={(selectedOption: {
label: string;
value: string;
}) => {
setFeedbackFilter(selectedOption);
{/* Token Usage Analytics */}
<div className="h-[345px] w-full overflow-hidden rounded-2xl border border-silver px-6 py-5 dark:border-silver/40 [@media(min-width:1080px)]:w-1/2">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.tokenUsage')}
</p>
<Dropdown
size="w-[125px]"
options={filterOptions}
placeholder={t('settings.analytics.filterPlaceholder')}
onSelect={(selectedOption: { label: string; value: string }) => {
setTokenUsageFilter(selectedOption);
}}
selectedValue={tokenUsageFilter ?? null}
rounded="3xl"
border="border"
contentSize="text-sm"
/>
</div>
<div className="relative mt-px h-[245px] w-full">
<div
id="legend-container-2"
className="flex flex-row items-center justify-end"
></div>
{loadingTokens ? (
<SkeletonLoader count={1} component={'analysis'} />
) : (
<AnalyticsChart
data={{
labels: Object.keys(tokenUsageData || {}).map((item) =>
formatDate(item),
),
datasets: [
{
label: t('settings.analytics.tokenUsage'),
data: Object.values(tokenUsageData || {}),
backgroundColor: '#7D54D1',
},
],
}}
selectedValue={feedbackFilter ?? null}
rounded="3xl"
border="border"
contentSize="text-sm"
legendID="legend-container-2"
maxTicksLimitInX={8}
isStacked={false}
/>
</div>
<div className="mt-px relative h-[245px] w-full">
<div
id="legend-container-3"
className="flex flex-row items-center justify-end"
></div>
{loadingFeedback ? (
<SkeletonLoader count={1} component={'analysis'} />
) : (
<AnalyticsChart
data={{
labels: Object.keys(feedbackData || {}).map((item) =>
formatDate(item),
),
datasets: [
{
label: t('settings.analytics.positiveFeedback'),
data: Object.values(feedbackData || {}).map(
(item) => item.positive,
),
backgroundColor: '#7D54D1',
},
{
label: t('settings.analytics.negativeFeedback'),
data: Object.values(feedbackData || {}).map(
(item) => item.negative,
),
backgroundColor: '#FF6384',
},
],
}}
legendID="legend-container-3"
maxTicksLimitInX={8}
isStacked={false}
/>
)}
</div>
)}
</div>
</div>
</div>
{/* Feedback Analytics */}
<div className="mt-8 flex w-full flex-col gap-3">
<div className="h-[345px] w-full overflow-hidden rounded-2xl border border-silver px-6 py-5 dark:border-silver/40">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.userFeedback')}
</p>
<Dropdown
size="w-[125px]"
options={filterOptions}
placeholder={t('settings.analytics.filterPlaceholder')}
onSelect={(selectedOption: { label: string; value: string }) => {
setFeedbackFilter(selectedOption);
}}
selectedValue={feedbackFilter ?? null}
rounded="3xl"
border="border"
contentSize="text-sm"
/>
</div>
<div className="relative mt-px h-[245px] w-full">
<div
id="legend-container-3"
className="flex flex-row items-center justify-end"
></div>
{loadingFeedback ? (
<SkeletonLoader count={1} component={'analysis'} />
) : (
<AnalyticsChart
data={{
labels: Object.keys(feedbackData || {}).map((item) =>
formatDate(item),
),
datasets: [
{
label: t('settings.analytics.positiveFeedback'),
data: Object.values(feedbackData || {}).map(
(item) => item.positive,
),
backgroundColor: '#7D54D1',
},
{
label: t('settings.analytics.negativeFeedback'),
data: Object.values(feedbackData || {}).map(
(item) => item.negative,
),
backgroundColor: '#FF6384',
},
],
}}
legendID="legend-container-3"
maxTicksLimitInX={8}
isStacked={false}
/>
)}
</div>
</div>
</div>