mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-12-02 18:13:13 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -5,53 +5,35 @@ import { useSelector } from 'react-redux';
|
||||
import userService from '../api/services/userService';
|
||||
import ChevronRight from '../assets/chevron-right.svg';
|
||||
import CopyButton from '../components/CopyButton';
|
||||
import Dropdown from '../components/Dropdown';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
import { useLoaderState } from '../hooks';
|
||||
import { selectToken } from '../preferences/preferenceSlice';
|
||||
import { APIKeyData, LogData } from './types';
|
||||
import { LogData } from './types';
|
||||
|
||||
export default function Logs() {
|
||||
const { t } = useTranslation();
|
||||
type LogsProps = {
|
||||
agentId?: string;
|
||||
tableHeader?: string;
|
||||
};
|
||||
|
||||
export default function Logs({ agentId, tableHeader }: LogsProps) {
|
||||
const token = useSelector(selectToken);
|
||||
const [chatbots, setChatbots] = useState<APIKeyData[]>([]);
|
||||
const [selectedChatbot, setSelectedChatbot] = useState<APIKeyData | null>();
|
||||
const [logs, setLogs] = useState<LogData[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [loadingChatbots, setLoadingChatbots] = useLoaderState(true);
|
||||
const [loadingLogs, setLoadingLogs] = useLoaderState(true);
|
||||
|
||||
const fetchChatbots = async () => {
|
||||
setLoadingChatbots(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);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoadingChatbots(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchLogs = async () => {
|
||||
setLoadingLogs(true);
|
||||
try {
|
||||
const response = await userService.getLogs(
|
||||
{
|
||||
page: page,
|
||||
api_key_id: selectedChatbot?.id,
|
||||
api_key_id: agentId,
|
||||
page_size: 10,
|
||||
},
|
||||
token,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch logs');
|
||||
}
|
||||
if (!response.ok) throw new Error('Failed to fetch logs');
|
||||
const olderLogs = await response.json();
|
||||
setLogs((prevLogs) => [...prevLogs, ...olderLogs.logs]);
|
||||
setHasMore(olderLogs.has_more);
|
||||
@@ -62,62 +44,18 @@ export default function Logs() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchChatbots();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMore) fetchLogs();
|
||||
}, [page, selectedChatbot]);
|
||||
|
||||
}, [page, agentId]);
|
||||
return (
|
||||
<div className="mt-12">
|
||||
<div className="flex flex-col items-start">
|
||||
{loadingChatbots ? (
|
||||
<SkeletonLoader component="dropdown" />
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<label
|
||||
id="chatbot-filter-label"
|
||||
className="font-bold text-jet dark:text-bright-gray"
|
||||
>
|
||||
{t('settings.logs.filterByChatbot')}
|
||||
</label>
|
||||
<Dropdown
|
||||
size="w-[55vw] sm:w-[360px]"
|
||||
options={[
|
||||
...chatbots.map((chatbot) => ({
|
||||
label: chatbot.name,
|
||||
value: chatbot.id,
|
||||
})),
|
||||
{ label: t('settings.logs.none'), value: '' },
|
||||
]}
|
||||
placeholder={t('settings.logs.selectChatbot')}
|
||||
onSelect={(chatbot: { label: string; value: string }) => {
|
||||
setSelectedChatbot(
|
||||
chatbots.find((item) => item.id === chatbot.value),
|
||||
);
|
||||
setLogs([]);
|
||||
setPage(1);
|
||||
setHasMore(true);
|
||||
}}
|
||||
selectedValue={
|
||||
(selectedChatbot && {
|
||||
label: selectedChatbot.name,
|
||||
value: selectedChatbot.id,
|
||||
}) ||
|
||||
null
|
||||
}
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
darkBorderColor="dim-gray"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<LogsTable logs={logs} setPage={setPage} loading={loadingLogs} />
|
||||
<LogsTable
|
||||
logs={logs}
|
||||
setPage={setPage}
|
||||
loading={loadingLogs}
|
||||
tableHeader={tableHeader}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -127,8 +65,9 @@ type LogsTableProps = {
|
||||
logs: LogData[];
|
||||
setPage: React.Dispatch<React.SetStateAction<number>>;
|
||||
loading: boolean;
|
||||
tableHeader?: string;
|
||||
};
|
||||
function LogsTable({ logs, setPage, loading }: LogsTableProps) {
|
||||
function LogsTable({ logs, setPage, loading, tableHeader }: LogsTableProps) {
|
||||
const { t } = useTranslation();
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
const [openLogId, setOpenLogId] = useState<string | null>(null);
|
||||
@@ -171,13 +110,13 @@ function LogsTable({ logs, setPage, loading }: LogsTableProps) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="logs-table rounded-xl h-[55vh] w-full overflow-hidden bg-white dark:bg-black border border-light-silver dark:border-transparent">
|
||||
<div className="h-8 bg-black/10 dark:bg-[#191919] flex flex-col items-start justify-center">
|
||||
<div className="logs-table h-[55vh] w-full overflow-hidden rounded-xl border border-light-silver bg-white dark:border-transparent dark:bg-black">
|
||||
<div className="flex h-8 flex-col items-start justify-center bg-black/10 dark:bg-[#191919]">
|
||||
<p className="px-3 text-xs dark:text-gray-6000">
|
||||
{t('settings.logs.tableHeader')}
|
||||
{tableHeader ? tableHeader : t('settings.logs.tableHeader')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-2 p-4">
|
||||
<div className="flex h-[51vh] flex-grow flex-col items-start gap-2 overflow-y-auto bg-transparent p-4">
|
||||
{logs?.map((log, index) => {
|
||||
if (index === logs.length - 1) {
|
||||
return (
|
||||
@@ -211,18 +150,18 @@ function Log({
|
||||
return (
|
||||
<details
|
||||
id={log.id}
|
||||
className="group bg-transparent [&_summary::-webkit-details-marker]:hidden w-full hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal rounded-xl group-open:opacity-80 [&[open]]:border [&[open]]:border-[#d9d9d9]"
|
||||
className="group w-full rounded-xl bg-transparent hover:bg-[#F9F9F9] group-open:opacity-80 hover:dark:bg-dark-charcoal [&[open]]:border [&[open]]:border-[#d9d9d9] [&_summary::-webkit-details-marker]:hidden"
|
||||
onToggle={(e) => {
|
||||
if ((e.target as HTMLDetailsElement).open) {
|
||||
onToggle(log.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<summary className="flex flex-row items-start gap-2 text-gray-900 cursor-pointer px-4 py-3 group-open:bg-[#F1F1F1] dark:group-open:bg-[#1B1B1B] group-open:rounded-t-xl p-2">
|
||||
<summary className="flex cursor-pointer flex-row items-start gap-2 p-2 px-4 py-3 text-gray-900 group-open:rounded-t-xl group-open:bg-[#F1F1F1] dark:group-open:bg-[#1B1B1B]">
|
||||
<img
|
||||
src={ChevronRight}
|
||||
alt="Expand log entry"
|
||||
className="mt-[3px] w-3 h-3 transition duration-300 group-open:rotate-90"
|
||||
className="mt-[3px] h-3 w-3 transition duration-300 group-open:rotate-90"
|
||||
/>
|
||||
<span className="flex flex-row gap-2">
|
||||
<h2 className="text-xs text-black/60 dark:text-bright-gray">{`${log.timestamp}`}</h2>
|
||||
@@ -236,8 +175,8 @@ function Log({
|
||||
</h2>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="px-4 py-3 group-open:bg-[#F1F1F1] dark:group-open:bg-[#1B1B1B] group-open:rounded-b-xl">
|
||||
<p className="px-2 leading-relaxed text-gray-700 dark:text-gray-400 text-xs break-words">
|
||||
<div className="px-4 py-3 group-open:rounded-b-xl group-open:bg-[#F1F1F1] dark:group-open:bg-[#1B1B1B]">
|
||||
<p className="break-words px-2 text-xs leading-relaxed text-gray-700 dark:text-gray-400">
|
||||
{JSON.stringify(filteredLog, null, 2)}
|
||||
</p>
|
||||
<div className="my-px w-fit">
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation, useNavigate, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import {
|
||||
Navigate,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import SettingsBar from '../components/SettingsBar';
|
||||
@@ -10,12 +16,11 @@ import { Doc } from '../models/misc';
|
||||
import {
|
||||
selectPaginatedDocuments,
|
||||
selectSourceDocs,
|
||||
selectToken,
|
||||
setPaginatedDocuments,
|
||||
setSourceDocs,
|
||||
selectToken,
|
||||
} from '../preferences/preferenceSlice';
|
||||
import Analytics from './Analytics';
|
||||
import APIKeys from './APIKeys';
|
||||
import Documents from './Documents';
|
||||
import General from './General';
|
||||
import Logs from './Logs';
|
||||
@@ -27,13 +32,16 @@ export default function Settings() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [widgetScreenshot, setWidgetScreenshot] = React.useState<File | null>(null);
|
||||
const [widgetScreenshot, setWidgetScreenshot] = React.useState<File | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const getActiveTabFromPath = () => {
|
||||
const path = location.pathname;
|
||||
if (path.includes('/settings/documents')) return t('settings.documents.label');
|
||||
if (path.includes('/settings/apikeys')) return t('settings.apiKeys.label');
|
||||
if (path.includes('/settings/analytics')) return t('settings.analytics.label');
|
||||
if (path.includes('/settings/documents'))
|
||||
return t('settings.documents.label');
|
||||
if (path.includes('/settings/analytics'))
|
||||
return t('settings.analytics.label');
|
||||
if (path.includes('/settings/logs')) return t('settings.logs.label');
|
||||
if (path.includes('/settings/tools')) return t('settings.tools.label');
|
||||
if (path.includes('/settings/widgets')) return 'Widgets';
|
||||
@@ -45,9 +53,10 @@ export default function Settings() {
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab);
|
||||
if (tab === t('settings.general.label')) navigate('/settings');
|
||||
else if (tab === t('settings.documents.label')) navigate('/settings/documents');
|
||||
else if (tab === t('settings.apiKeys.label')) navigate('/settings/apikeys');
|
||||
else if (tab === t('settings.analytics.label')) navigate('/settings/analytics');
|
||||
else if (tab === t('settings.documents.label'))
|
||||
navigate('/settings/documents');
|
||||
else if (tab === t('settings.analytics.label'))
|
||||
navigate('/settings/analytics');
|
||||
else if (tab === t('settings.logs.label')) navigate('/settings/logs');
|
||||
else if (tab === t('settings.tools.label')) navigate('/settings/tools');
|
||||
else if (tab === 'Widgets') navigate('/settings/widgets');
|
||||
@@ -93,29 +102,37 @@ export default function Settings() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-12 h-full overflow-auto">
|
||||
<div className="h-full overflow-auto p-4 md:p-12">
|
||||
<p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.label')}
|
||||
</p>
|
||||
<SettingsBar activeTab={activeTab} setActiveTab={(tab) => handleTabChange(tab as string)} />
|
||||
<SettingsBar
|
||||
activeTab={activeTab}
|
||||
setActiveTab={(tab) => handleTabChange(tab as string)}
|
||||
/>
|
||||
<Routes>
|
||||
<Route index element={<General />} />
|
||||
<Route path="documents" element={
|
||||
<Documents
|
||||
paginatedDocuments={paginatedDocuments}
|
||||
handleDeleteDocument={handleDeleteClick}
|
||||
/>
|
||||
} />
|
||||
<Route path="apikeys" element={<APIKeys />} />
|
||||
<Route
|
||||
path="documents"
|
||||
element={
|
||||
<Documents
|
||||
paginatedDocuments={paginatedDocuments}
|
||||
handleDeleteDocument={handleDeleteClick}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="analytics" element={<Analytics />} />
|
||||
<Route path="logs" element={<Logs />} />
|
||||
<Route path="tools" element={<Tools />} />
|
||||
<Route path="widgets" element={
|
||||
<Widgets
|
||||
widgetScreenshot={widgetScreenshot}
|
||||
onWidgetScreenshotChange={updateWidgetScreenshot}
|
||||
/>
|
||||
} />
|
||||
<Route
|
||||
path="widgets"
|
||||
element={
|
||||
<Widgets
|
||||
widgetScreenshot={widgetScreenshot}
|
||||
onWidgetScreenshotChange={updateWidgetScreenshot}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<Navigate to="/settings" replace />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user