Skip to content

Commit

Permalink
feat: add Application analytics and prompt config component
Browse files Browse the repository at this point in the history
  • Loading branch information
Yatanvesh committed Oct 20, 2023
1 parent 9806d62 commit 8571d06
Show file tree
Hide file tree
Showing 16 changed files with 497 additions and 54 deletions.
15 changes: 4 additions & 11 deletions .idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions frontend/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,21 @@
"submitButtonHelperText": "Create a new project and application with the given names",
"optional": "optional",
"errorComment": "There was an error creating your project"
},
"application": {
"application": "Application",
"overview": "Overview",
"settings": "Settings",
"tokens": "Tokens",
"status": "Status",
"apiCalls": "API Calls",
"modelsCost": "Models Cost",
"promptConfiguration": "Prompt Configuration",
"name": "Name",
"type": "Type",
"model": "Model",
"test": "Test",
"edit": "Edit",
"newConfiguration": "New Configuration"
}
}
10 changes: 6 additions & 4 deletions frontend/src/api/applications-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { type DateType } from 'react-tailwindcss-datepicker';

import { fetcher } from '@/api/fetcher';
import { HttpMethod } from '@/constants';
import {
Expand Down Expand Up @@ -80,15 +82,15 @@ export async function handleApplicationAnalytics({
}: {
applicationId: string;
projectId: string;
fromDate?: string;
toDate?: string;
fromDate?: DateType;
toDate?: DateType;
}): Promise<ApplicationAnalytics> {
return await fetcher<ApplicationAnalytics>({
url: `projects/${projectId}/applications/${applicationId}/analytics`,
method: HttpMethod.Get,
queryParams: {
fromDate,
toDate,
fromDate: fromDate ? new Date(fromDate).toISOString() : undefined,
toDate: toDate ? new Date(toDate).toISOString() : undefined,
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,37 @@

import { useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import {
Activity,
Cash,
Front,
PencilFill,
Plus,
Search,
} from 'react-bootstrap-icons';
import { DateValueType } from 'react-tailwindcss-datepicker';

import { handleApplicationAnalytics, handleRetrievePromptConfigs } from '@/api';
import { DataCard } from '@/components/dashboard/data-card';
import { DatePicker } from '@/components/dashboard/date-picker';
import { Navigation } from '@/constants';
import { useAuthenticatedUser } from '@/hooks/use-authenticated-user';
import { useGetApplication } from '@/stores/project-store';
import {
useGetApplication,
useGetPromptConfig,
useSetPromptConfig,
} from '@/stores/project-store';
import { ApplicationAnalytics } from '@/types';
import { copyToClipboard } from '@/utils/helpers';

export default function Application({
params: { projectId, applicationId },
}: {
params: { projectId: string; applicationId: string };
}) {
useAuthenticatedUser();
const t = useTranslations('navrail');
const t = useTranslations('application');
const router = useRouter();
const application = useGetApplication()(projectId, applicationId);

Expand All @@ -33,6 +51,160 @@ export default function Application({
<h1 className="text-2xl font-semibold text-base-content">
{t('application')} / {application.name}
</h1>
<div className="mt-3.5 h-11 text-xl">Overview(stub)</div>
<ApplicationAnalytics
applicationId={applicationId}
projectId={projectId}
/>
<ApplicationPromptConfigs
applicationId={applicationId}
projectId={projectId}
/>
</div>
);
}

export function ApplicationAnalytics({
projectId,
applicationId,
}: {
projectId: string;
applicationId: string;
}) {
const t = useTranslations('application');

const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const [dateRange, setDateRange] = useState<DateValueType>({
startDate: oneWeekAgo,
endDate: new Date(),
});

const [analytics, setAnalytics] = useState<ApplicationAnalytics | null>(
null,
);

useEffect(() => {
(async () => {
const applicationAnalytics = await handleApplicationAnalytics({
applicationId,
projectId,
fromDate: dateRange?.startDate,
toDate: dateRange?.endDate,
});
setAnalytics(applicationAnalytics);
})();
}, [dateRange]);

return (
<div className="mt-9">
<div className="flex justify-between items-center">
<h2 className="font-semibold text-white text-xl ">
{t('status')}
</h2>
<DatePicker
displayFormat="DD/MM/YYYY"
showShortcuts={true}
useRange={true}
value={dateRange}
onValueChange={setDateRange}
/>
</div>
<div className="mt-3.5 rounded-3xl w-full flex items-center justify-between bg-base-200 py-8 px-32">
<DataCard
imageSrc={<Activity className="text-secondary w-6 h-6" />}
metric={t('apiCalls')}
totalValue={analytics?.totalRequests ?? ''}
/>
<div className="w-px h-12 bg-gray-200 mx-4"></div>
<DataCard
imageSrc={<Cash className="text-secondary w-6 h-6" />}
metric={t('modelsCost')}
totalValue={`${analytics?.projectedCost ?? ''}$`}
/>
</div>
</div>
);
}

export function ApplicationPromptConfigs({
projectId,
applicationId,
}: {
projectId: string;
applicationId: string;
}) {
const t = useTranslations('application');
const setPromptConfig = useSetPromptConfig();
const promptConfigs = useGetPromptConfig();

async function fetchPromptConfig() {
const promptConfigRes = await handleRetrievePromptConfigs({
applicationId,
projectId,
});
setPromptConfig(applicationId, promptConfigRes);
}

useEffect(() => {
void fetchPromptConfig();
}, []);

return (
<div className="mt-9">
<h2 className="font-semibold text-white text-xl ">
{t('promptConfiguration')}
</h2>
<div className="mt-3.5 rounded-3xl w-full bg-base-200 py-8 px-16">
<table className="custom-table">
<thead>
<tr>
<th>{t('name')}</th>
<th>{t('type')}</th>
<th>{t('model')}</th>
<th>ID</th>
<th>{t('test')}</th>
<th>{t('edit')}</th>
</tr>
</thead>
<tbody>
{promptConfigs[applicationId]?.map(
({ name, modelType, modelVendor, id }) => (
<tr key={id}>
<td>{name}</td>
<td>{modelType}</td>
<td>{modelVendor}</td>
<td>
<button
data-testid="prompt-config-copy-btn"
onClick={() => {
copyToClipboard(id);
// TODO: add a toast
}}
>
<Front className="w-3.5 h-3.5 text-secondary" />
</button>
</td>
<td>
<button>
<Search className="w-3.5 h-3.5 text-secondary" />
</button>
</td>
<td>
<button>
<PencilFill className="w-3.5 h-3.5 text-secondary" />
</button>
</td>
</tr>
),
)}
</tbody>
</table>
<button className="mt-16 flex gap-2 items-center text-secondary hover:brightness-90">
<Plus className="text-secondary w-4 h-4 hover:brightness-90" />
<span>{t('newConfiguration')}</span>
</button>
</div>
</div>
);
}
33 changes: 22 additions & 11 deletions frontend/src/components/dashboard/data-card.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ReactElement } from 'react';
import { ArrowUpRight } from 'react-bootstrap-icons';

export interface DataCardProps {
imageSrc: ReactElement;
metric: string;
totalValue: string;
currentValue: string;
percentage: string;
totalValue: string | number;
currentValue?: string;
percentage?: string;
}

export function DataCard({
Expand All @@ -16,15 +17,25 @@ export function DataCard({
percentage,
}: DataCardProps) {
return (
<div className="flex flex-col">
<div className="text-neutral-content">{metric}</div>
<div className="text-2xl font-semibold text-neutral-content flex">
{totalValue}
<div className="ml-5">{imageSrc}</div>
</div>
<div className="text-neutral-content">
{currentValue} ({percentage})
<div className="flex items-center gap-4">
<div>
<p className="text-neutral-content text-sm">{metric}</p>
<h1
data-testid="data-card-total-value"
className="text-3xl font-semibold text-neutral-content"
>
{totalValue}
</h1>
{currentValue && percentage && (
<p className="text-neutral-content text-xs font-light flex gap-1.5 items-center">
<ArrowUpRight className="w-3 h-3 text-neutral-content" />
<span>
{currentValue} ({percentage})
</span>
</p>
)}
</div>
{imageSrc}
</div>
);
}
29 changes: 12 additions & 17 deletions frontend/src/components/dashboard/date-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import { useState } from 'react';
import Datepicker, { DateValueType } from 'react-tailwindcss-datepicker';

export interface DatePickerProps {
showShortcuts: boolean;
useRange: boolean;
displayFormat: string;
showShortcuts?: boolean;
useRange?: boolean;
displayFormat?: string;
value: DateValueType;
onValueChange: (value: DateValueType) => void;
}

export function DatePicker({
showShortcuts,
useRange,
displayFormat,
showShortcuts = true,
useRange = true,
displayFormat = 'DD/MM/YYYY',
value,
onValueChange,
}: DatePickerProps) {
const [value, setValue] = useState<DateValueType>({
startDate: new Date(),
endDate: new Date().setMonth(11).toString(),
});

const handleValueChange = (newValue: DateValueType) => {
setValue(newValue);
};

return (
<div data-testid="datepicker">
<Datepicker
inputClassName="bg-transparent relative transition-all duration-300 py-2.5 pl-4 pr-14 w-full text-neutral-content tracking-medium font-medium text-sm placeholder-gray-400 disabled:opacity-40 disabled:cursor-not-allowed"
value={value}
onChange={handleValueChange}
onChange={onValueChange}
showShortcuts={showShortcuts}
useRange={useRange}
displayFormat={displayFormat}
Expand Down
Loading

0 comments on commit 8571d06

Please sign in to comment.