Skip to content

Commit

Permalink
feat(JAQPOT-432): docker llm UI (#87)
Browse files Browse the repository at this point in the history
* feat(JAQPOT-432): docker llm ui

* feat: current progress of llm form

* feat: send whole dataset for streaming prediction

* feat: add chat grid component

* feat: working version of streaming prediction

* fix: remove console.logs

* fix: remove obsolete code
  • Loading branch information
alarv authored Dec 17, 2024
1 parent cac53b7 commit 0e4161b
Show file tree
Hide file tree
Showing 19 changed files with 624 additions and 230 deletions.
5 changes: 2 additions & 3 deletions src/app/SessionChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ export default function SessionChecker() {
revalidateOnReconnect: false,
});

const silentlySignOut = useCallback(() => {
signOut({ redirect: false });
const silentlySignOut = useCallback(async () => {
await signOut({ redirect: false });
clearUserSettings();
}, []);

if (hasRun) return null;
setHasRun(true);

if (isLoading) return null;
if (!res) {
Expand Down
62 changes: 59 additions & 3 deletions src/app/api.schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ export interface paths {
*/
get: operations["getDatasets"];
};
"/v1/user/models/{modelId}/datasets": {
/**
* Get Datasets by Model ID
* @description Retrieve all datasets associated with a specific model ID
*/
get: operations["getDatasetsByModelId"];
};
"/v1/datasets/{id}": {
/**
* Get a Dataset
Expand Down Expand Up @@ -286,6 +293,7 @@ export interface components {
crossValidation?: components["schemas"]["Scores"][];
};
rPbpkConfig?: components["schemas"]["RPbpkConfig"];
dockerConfig?: components["schemas"]["DockerConfig"];
/**
* Format: date-time
* @description The date and time when the feature was created.
Expand Down Expand Up @@ -379,6 +387,18 @@ export interface components {
RPbpkConfig: {
odeSolver?: string;
};
DockerConfig: {
/**
* @description Unique identifier used for internal service discovery
* @example my-docker-model
*/
appName: string;
/**
* @description Reference to the Docker image (for admin documentation)
* @example my-repo/my-docker-model:1.0.0
*/
dockerImage?: string;
};
OrganizationSummary: {
/**
* Format: int64
Expand All @@ -389,7 +409,7 @@ export interface components {
name: string;
};
/** @enum {string} */
ModelType: "SKLEARN_ONNX" | "TORCH_SEQUENCE_ONNX" | "TORCH_GEOMETRIC_ONNX" | "TORCHSCRIPT" | "R_BNLEARN_DISCRETE" | "R_CARET" | "R_GBM" | "R_NAIVE_BAYES" | "R_PBPK" | "R_RF" | "R_RPART" | "R_SVM" | "R_TREE_CLASS" | "R_TREE_REGR" | "QSAR_TOOLBOX_CALCULATOR" | "QSAR_TOOLBOX_QSAR_MODEL" | "QSAR_TOOLBOX_PROFILER";
ModelType: "SKLEARN_ONNX" | "TORCH_SEQUENCE_ONNX" | "TORCH_GEOMETRIC_ONNX" | "TORCHSCRIPT" | "R_BNLEARN_DISCRETE" | "R_CARET" | "R_GBM" | "R_NAIVE_BAYES" | "R_PBPK" | "R_RF" | "R_RPART" | "R_SVM" | "R_TREE_CLASS" | "R_TREE_REGR" | "DOCKER" | "DOCKER_LLM" | "QSAR_TOOLBOX_CALCULATOR" | "QSAR_TOOLBOX_QSAR_MODEL" | "QSAR_TOOLBOX_PROFILER";
/** @description A preprocessor for the model */
Transformer: {
/** Format: int64 */
Expand Down Expand Up @@ -570,13 +590,15 @@ export interface components {
* @example PREDICTION
* @enum {string}
*/
DatasetType: "PREDICTION";
DatasetType: "PREDICTION" | "CHAT";
Dataset: {
/**
* Format: int64
* @example 1
*/
id?: number;
/** @example My Dataset */
name?: string;
type: components["schemas"]["DatasetType"];
/**
* @example ARRAY
Expand Down Expand Up @@ -707,7 +729,7 @@ export interface components {
* Format: int64
* @description Unique identifier for the prediction model
*/
id?: number | null;
id: number;
/** @description List of dependent features for the model */
dependentFeatures: components["schemas"]["Feature"][];
/** @description List of independent features for the model */
Expand Down Expand Up @@ -1359,6 +1381,40 @@ export interface operations {
};
};
};
/**
* Get Datasets by Model ID
* @description Retrieve all datasets associated with a specific model ID
*/
getDatasetsByModelId: {
parameters: {
query?: {
page?: number;
size?: number;
sort?: string[];
};
path: {
modelId: number;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": {
content?: components["schemas"]["Dataset"][];
totalElements?: number;
totalPages?: number;
pageSize?: number;
pageNumber?: number;
};
};
};
/** @description Model or datasets not found */
404: {
content: never;
};
};
};
/**
* Get a Dataset
* @description Retrieve a single dataset by its ID
Expand Down
1 change: 1 addition & 0 deletions src/app/api.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { components, operations } from './api.schema.d';

export type ModelDto = components['schemas']['Model'];
export type ModelTypeDto = components['schemas']['ModelType'];
export type ModelSummaryDto = components['schemas']['ModelSummary'];
export type FeatureDto = components['schemas']['Feature'];
export type FeaturePossibleValueDto =
Expand Down
59 changes: 59 additions & 0 deletions src/app/api/models/[modelId]/predict/stream/[datasetId]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { NextResponse } from 'next/server';
import {
ApiResponse,
errorResponse,
handleApiResponse,
} from '@/app/util/response';
import { auth } from '@/auth';
import { isAuthenticated } from '@/app/util/auth';
import { DatasetDto } from '@/app/api.types';
import { NextApiResponse } from 'next';

function iteratorToStream(reader: any) {
return new ReadableStream({
async pull(controller) {
const { value, done } = await reader.read();

if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
});
}

export async function POST(
request: Request,
{ params }: { params: { modelId: string; datasetId: string } },
) {
const session = await auth();
if (!isAuthenticated(session)) {
return errorResponse(
'You need to be authenticated to access this endpoint',
401,
);
}

const body = await request.json();

const streamResponse = await fetch(
`${process.env.API_URL}/v1/models/${params.modelId}/predict/stream/${params.datasetId}`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${session!.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
},
);

const reader = streamResponse
.body!.pipeThrough(new TextDecoderStream())
.getReader();

const stream = iteratorToStream(reader);

return new Response(stream);
}
65 changes: 65 additions & 0 deletions src/app/api/user/models/[modelId]/datasets/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { auth } from '@/auth';
import { NextRequest, NextResponse } from 'next/server';
import {
ApiResponse,
errorResponse,
handleApiResponse,
} from '@/app/util/response';
import { isAuthenticated } from '@/app/util/auth';
import { generatePaginationAndSortingSearchParams } from '@/app/util/sort';

export async function POST(
request: NextRequest,
{ params }: { params: { modelId: string } },
): Promise<NextResponse<ApiResponse>> {
const session = await auth();
if (!isAuthenticated(session)) {
return errorResponse(
'You need to be authenticated to access this endpoint',
401,
);
}

const data = await request.json();

const res = await fetch(
`${process.env.API_URL}/v1/user/models/${params.modelId}/datasets`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${session!.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
);

return handleApiResponse(res);
}

export async function GET(
request: NextRequest,
{ params }: { params: { modelId: string } },
): Promise<NextResponse<ApiResponse>> {
const session = await auth();
if (!isAuthenticated(session)) {
return errorResponse(
'You need to be authenticated to access this endpoint',
401,
);
}

const searchParams = generatePaginationAndSortingSearchParams(request);

const res = await fetch(
`${process.env.API_URL}/v1/user/models/${params.modelId}/datasets?${searchParams}`,
{
headers: {
Authorization: `Bearer ${session!.token}`,
'Content-Type': 'application/json',
},
},
);

return handleApiResponse(res);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Page from '../page';

export default Page;
1 change: 1 addition & 0 deletions src/app/dashboard/models/[modelId]/[tabName]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export async function getModel(modelId: string): Promise<ModelDto | undefined> {

interface ModelPageParams {
modelId: string;
datasetId?: string;
}

async function retrieveModelOrLegacy(modelId: string): Promise<ModelDto> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export default function DynamicForm({ schema, onSubmit }: DynamicFormProps) {
</div>
))}
</div>
<Button type="submit" color="primary" className="mt-5">
<Button type="submit" color="primary">
Submit
</Button>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ export default function ModelPredictionForm({
onCSVFormSubmit,
includeIndependentFeatures = false,
}: ModelPredictionFormProps) {
const { data: session } = useSession();
const [predictionUploadType, setPredictionUploadType] = useState('form');

const predictionFormSchema = generatePredictionFormSchema(
Expand All @@ -129,13 +128,6 @@ export default function ModelPredictionForm({

return (
<>
{!isAuthenticated(session) && (
<Alert
type="warning"
title="Authentication required!"
description="You need to be logged in to make predictions"
/>
)}
<RadioGroup
label="Choose Your Prediction Input Method"
orientation="horizontal"
Expand Down
53 changes: 37 additions & 16 deletions src/app/dashboard/models/[modelId]/components/ModelTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,36 @@
import { useParams, usePathname } from 'next/navigation';
import { Tab, Tabs } from '@nextui-org/tabs';
import { ModelDto } from '@/app/api.types';
import FeaturesTab from '@/app/dashboard/models/[modelId]/components/tabs/FeaturesTab';
import ModelFeaturesTab from '@/app/dashboard/models/[modelId]/components/tabs/ModelFeaturesTab';
import ModelPredictTab from '@/app/dashboard/models/[modelId]/components/tabs/ModelPredictTab';
import ModelEditTab from '@/app/dashboard/models/[modelId]/components/tabs/ModelEditTab';
import MarkdownRenderer from '@/app/dashboard/models/[modelId]/components/MarkdownRenderer';
import ModelAdminTab from '@/app/dashboard/models/[modelId]/components/tabs/ModelAdminTab';
import {
AdjustmentsVerticalIcon,
ChartBarIcon,
ChatBubbleLeftEllipsisIcon,
ChatBubbleLeftRightIcon,
PencilSquareIcon,
RocketLaunchIcon,
ShieldCheckIcon,
} from '@heroicons/react/24/solid';
import { InformationCircleIcon } from '@heroicons/react/24/outline';
import ModelMetricsTab from '@/app/dashboard/models/[modelId]/components/tabs/ModelMetricsTab';
import ModelChatTab from '@/app/dashboard/models/[modelId]/components/tabs/ModelChatTab';

interface ModelTabsProps {
model: ModelDto;
}

export default function ModelTabs({ model }: ModelTabsProps) {
const pathname = usePathname();
const params = useParams<{ tabName: string }>();
const params = useParams<{ tabName: string; datasetId?: string }>();

const pathnameWithoutTab = pathname.substring(0, pathname.lastIndexOf('/'));
const pathnameWithoutTab = pathname.substring(
0,
pathname.lastIndexOf(`/${params.tabName}`),
);

return (
<Tabs
Expand Down Expand Up @@ -70,20 +75,36 @@ export default function ModelTabs({ model }: ModelTabsProps) {
}
href={`${pathnameWithoutTab}/features`}
>
<FeaturesTab model={model} />
</Tab>
<Tab
key="predict"
title={
<div className="flex items-center space-x-1">
<RocketLaunchIcon className="size-5" />
<span>Predict</span>
</div>
}
href={`${pathnameWithoutTab}/predict`}
>
<ModelPredictTab model={model} />
<ModelFeaturesTab model={model} />
</Tab>
{model.type === 'DOCKER_LLM' && (
<Tab
key="chat"
title={
<div className="flex items-center space-x-1">
<ChatBubbleLeftEllipsisIcon className="size-5" />
<span>Chat</span>
</div>
}
href={`${pathnameWithoutTab}/chat`}
>
<ModelChatTab model={model} datasetId={params.datasetId} />
</Tab>
)}
{model.type !== 'DOCKER_LLM' && (
<Tab
key="predict"
title={
<div className="flex items-center space-x-1">
<RocketLaunchIcon className="size-5" />
<span>Predict</span>
</div>
}
href={`${pathnameWithoutTab}/predict`}
>
<ModelPredictTab model={model} />
</Tab>
)}
<Tab
key="metrics"
title={
Expand Down
Loading

0 comments on commit 0e4161b

Please sign in to comment.