From 25532da344a58a2daa920a21fa6e49c72ab416c6 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Wed, 5 Mar 2025 10:01:05 -0800 Subject: [PATCH 01/13] update add and delete custom model endpoints Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 30 ++++++++++++++++++++++++++- src/types/index.ts | 5 ++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 2f5e2e16..8c4c0081 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -32,16 +32,26 @@ import { } from '@patternfly/react-core'; import { EyeSlashIcon, EyeIcon } from '@patternfly/react-icons'; +enum ModelStatus { + AVAILABLE = "available", + UNAVAILABLE = "unavailable", + UNKNOWN = "unknown", +} + interface ExtendedEndpoint extends Endpoint { isApiKeyVisible?: boolean; + status?: ModelStatus; } const EndpointsPage: React.FC = () => { const [endpoints, setEndpoints] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); const [currentEndpoint, setCurrentEndpoint] = useState | null>(null); + const [endpointName, setEndpointName] = useState(''); + const [endpointDescription, setEndpointDescription] = useState(''); const [url, setUrl] = useState(''); const [modelName, setModelName] = useState(''); + const [modelDescription, setModelDescription] = useState(''); const [apiKey, setApiKey] = useState(''); useEffect(() => { @@ -70,8 +80,11 @@ const EndpointsPage: React.FC = () => { if (currentEndpoint) { const updatedEndpoint: ExtendedEndpoint = { id: currentEndpoint.id || uuidv4(), + name: endpointName, + description: endpointDescription, url: updatedUrl, modelName: modelName, + modelDescription: modelDescription, apiKey: apiKey, isApiKeyVisible: false }; @@ -83,8 +96,11 @@ const EndpointsPage: React.FC = () => { setEndpoints(updatedEndpoints); localStorage.setItem('endpoints', JSON.stringify(updatedEndpoints)); setCurrentEndpoint(null); + setEndpointName(''); + setEndpointDescription(''); setUrl(''); setModelName(''); + setModelDescription(''); setApiKey(''); handleModalToggle(); } @@ -98,16 +114,22 @@ const EndpointsPage: React.FC = () => { const handleEditEndpoint = (endpoint: ExtendedEndpoint) => { setCurrentEndpoint(endpoint); + setEndpointName(endpoint.name) + setEndpointDescription(endpoint.description) setUrl(endpoint.url); setModelName(endpoint.modelName); + setModelDescription(endpoint.modelDescription); setApiKey(endpoint.apiKey); handleModalToggle(); }; const handleAddEndpoint = () => { - setCurrentEndpoint({ id: '', url: '', modelName: '', apiKey: '', isApiKeyVisible: false }); + setCurrentEndpoint({ id: '', name: '', description: '', url: '', modelName: '', modelDescription: '', apiKey: '', isApiKeyVisible: false }); + setEndpointName(''); + setEndpointDescription(''); setUrl(''); setModelName(''); + setModelDescription(''); setApiKey(''); handleModalToggle(); }; @@ -163,6 +185,12 @@ const EndpointsPage: React.FC = () => { + Endpoint Name: {endpoint.name} + , + + Endpoint Description: {endpoint.description} + , URL: {endpoint.url} , diff --git a/src/types/index.ts b/src/types/index.ts index 2afaefa9..72d4dbbe 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,9 +2,12 @@ import { ValidatedOptions } from '@patternfly/react-core'; export interface Endpoint { id: string; + name: string; + description: string; url: string; - apiKey: string; modelName: string; + modelDescription: string; + apiKey: string; } export interface Model { From 4722bf557101bb32441aedb8a252c10954762c5a Mon Sep 17 00:00:00 2001 From: greg pereira Date: Thu, 6 Mar 2025 07:26:47 -0800 Subject: [PATCH 02/13] more progress Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 53 +++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 8c4c0081..9b679610 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -32,15 +32,46 @@ import { } from '@patternfly/react-core'; import { EyeSlashIcon, EyeIcon } from '@patternfly/react-icons'; -enum ModelStatus { +enum ModelEndpointStatus { AVAILABLE = "available", UNAVAILABLE = "unavailable", UNKNOWN = "unknown", + UNINITIALIZED = "uninitialized" +} + +async function checkEndpointStatus( + endpointURL: string, + modelName: string, + apiKey: string +): Promise { + let headers; + if (apiKey != "") { + headers = { + "Content-Type": "application/json", + "Authorization": `Bearer ${apiKey}` + }; + } else { + headers = { + "Content-Type": "application/json", + }; + } + try { + const response = await fetch(`${endpointURL}/v1/models/${modelName}`, { + headers: headers + }); + if (response.ok) { + return ModelEndpointStatus.AVAILABLE; + } else { + return ModelEndpointStatus.UNAVAILABLE; + } + } catch (error) { + return ModelEndpointStatus.UNKNOWN; + } } interface ExtendedEndpoint extends Endpoint { isApiKeyVisible?: boolean; - status?: ModelStatus; + status?: ModelEndpointStatus; } const EndpointsPage: React.FC = () => { @@ -53,6 +84,7 @@ const EndpointsPage: React.FC = () => { const [modelName, setModelName] = useState(''); const [modelDescription, setModelDescription] = useState(''); const [apiKey, setApiKey] = useState(''); + const [endpointStatus, setEndpointStatus] = useState(''); useEffect(() => { const storedEndpoints = localStorage.getItem('endpoints'); @@ -75,8 +107,9 @@ const EndpointsPage: React.FC = () => { return inputUrl; }; - const handleSaveEndpoint = () => { + async function handleSaveEndpoint () { const updatedUrl = removeTrailingSlash(url); + const status = await checkEndpointStatus(updatedUrl, modelName, apiKey) if (currentEndpoint) { const updatedEndpoint: ExtendedEndpoint = { id: currentEndpoint.id || uuidv4(), @@ -86,7 +119,8 @@ const EndpointsPage: React.FC = () => { modelName: modelName, modelDescription: modelDescription, apiKey: apiKey, - isApiKeyVisible: false + isApiKeyVisible: false, + status: status, }; const updatedEndpoints = currentEndpoint.id @@ -102,6 +136,7 @@ const EndpointsPage: React.FC = () => { setModelName(''); setModelDescription(''); setApiKey(''); + setEndpointStatus(ModelEndpointStatus.UNINITIALIZED) handleModalToggle(); } }; @@ -112,25 +147,29 @@ const EndpointsPage: React.FC = () => { localStorage.setItem('endpoints', JSON.stringify(updatedEndpoints)); }; - const handleEditEndpoint = (endpoint: ExtendedEndpoint) => { + async function handleEditEndpoint (endpoint: ExtendedEndpoint) { + const updatedUrl = removeTrailingSlash(endpoint.url); + const status = await checkEndpointStatus(updatedUrl, endpoint.modelName, endpoint.apiKey) setCurrentEndpoint(endpoint); setEndpointName(endpoint.name) setEndpointDescription(endpoint.description) - setUrl(endpoint.url); + setUrl(updatedUrl); setModelName(endpoint.modelName); setModelDescription(endpoint.modelDescription); setApiKey(endpoint.apiKey); + setEndpointStatus(status) handleModalToggle(); }; const handleAddEndpoint = () => { - setCurrentEndpoint({ id: '', name: '', description: '', url: '', modelName: '', modelDescription: '', apiKey: '', isApiKeyVisible: false }); + setCurrentEndpoint({ id: '', name: '', description: '', url: '', modelName: '', modelDescription: '', apiKey: '', isApiKeyVisible: false, status: ModelEndpointStatus.UNINITIALIZED}); setEndpointName(''); setEndpointDescription(''); setUrl(''); setModelName(''); setModelDescription(''); setApiKey(''); + setEndpointStatus(ModelEndpointStatus.UNINITIALIZED) handleModalToggle(); }; From 853d716c46ed9c0ffd9735716e28f949c1944159 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Thu, 6 Mar 2025 08:33:35 -0800 Subject: [PATCH 03/13] finished endpoint add Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 81 +++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 9b679610..41662f23 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -27,10 +27,11 @@ import { ModalVariant, PageBreadcrumb, PageSection, + Popover, TextInput, Title } from '@patternfly/react-core'; -import { EyeSlashIcon, EyeIcon } from '@patternfly/react-icons'; +import { EyeSlashIcon, EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons'; enum ModelEndpointStatus { AVAILABLE = "available", @@ -225,7 +226,10 @@ const EndpointsPage: React.FC = () => { - Endpoint Name: {endpoint.name} + Endpoint Name: {endpoint.name} + , + + Endpoint Status: {endpoint.name} , Endpoint Description: {endpoint.description} @@ -268,9 +272,57 @@ const EndpointsPage: React.FC = () => { > +
+

+ Add a custom model endpoint for chat the interface. Use it to compare or interact with remote hosted models. +

+
- - setUrl(value)} placeholder="Enter URL" /> + + setModelName(value)} + placeholder="Enter name" + /> + + + setModelName(value)} + placeholder="Enter description" + /> + + + + + } + > + setUrl(value)} + placeholder="Enter URL" + /> { placeholder="Enter Model Name" /> - + + setModelName(value)} + placeholder="Enter description" + /> + + + + + }> Date: Sun, 9 Mar 2025 09:23:57 -0700 Subject: [PATCH 04/13] fixing color and status icon Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 56 +++++++++++++++++---------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 41662f23..ef01e688 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect } from 'react'; +import React, { ReactNode, useState, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { AppLayout } from '@/components/AppLayout'; import { Endpoint } from '@/types'; @@ -31,20 +31,36 @@ import { TextInput, Title } from '@patternfly/react-core'; -import { EyeSlashIcon, EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons'; +import { BanIcon, CheckCircleIcon, EyeSlashIcon, EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons'; +import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon'; -enum ModelEndpointStatus { - AVAILABLE = "available", - UNAVAILABLE = "unavailable", - UNKNOWN = "unknown", - UNINITIALIZED = "uninitialized" +interface ModelEndpointStatus { + status: string; + icon: ReactNode; } +const availableModelEndpointStatus: ModelEndpointStatus = { + status: "available", + icon: , +}; + +const unavailableModelEndpointStatus: ModelEndpointStatus = { + status: "unavailable", + icon: , +}; + +const unknownModelEndpointStatus: ModelEndpointStatus = { + status: "unknown", + icon: , +}; + + async function checkEndpointStatus( endpointURL: string, modelName: string, apiKey: string ): Promise { + console.log("checking the model endpoint") let headers; if (apiKey != "") { headers = { @@ -61,12 +77,12 @@ async function checkEndpointStatus( headers: headers }); if (response.ok) { - return ModelEndpointStatus.AVAILABLE; + return availableModelEndpointStatus; } else { - return ModelEndpointStatus.UNAVAILABLE; + return unavailableModelEndpointStatus; } } catch (error) { - return ModelEndpointStatus.UNKNOWN; + return unknownModelEndpointStatus; } } @@ -85,7 +101,7 @@ const EndpointsPage: React.FC = () => { const [modelName, setModelName] = useState(''); const [modelDescription, setModelDescription] = useState(''); const [apiKey, setApiKey] = useState(''); - const [endpointStatus, setEndpointStatus] = useState(''); + const [endpointStatus, setEndpointStatus] = useState(unknownModelEndpointStatus); useEffect(() => { const storedEndpoints = localStorage.getItem('endpoints'); @@ -137,7 +153,7 @@ const EndpointsPage: React.FC = () => { setModelName(''); setModelDescription(''); setApiKey(''); - setEndpointStatus(ModelEndpointStatus.UNINITIALIZED) + setEndpointStatus(unknownModelEndpointStatus) handleModalToggle(); } }; @@ -163,14 +179,14 @@ const EndpointsPage: React.FC = () => { }; const handleAddEndpoint = () => { - setCurrentEndpoint({ id: '', name: '', description: '', url: '', modelName: '', modelDescription: '', apiKey: '', isApiKeyVisible: false, status: ModelEndpointStatus.UNINITIALIZED}); + setCurrentEndpoint({ id: '', name: '', description: '', url: '', modelName: '', modelDescription: '', apiKey: '', isApiKeyVisible: false, status: unknownModelEndpointStatus}); setEndpointName(''); setEndpointDescription(''); setUrl(''); setModelName(''); setModelDescription(''); setApiKey(''); - setEndpointStatus(ModelEndpointStatus.UNINITIALIZED) + setEndpointStatus(unknownModelEndpointStatus) handleModalToggle(); }; @@ -198,7 +214,6 @@ const EndpointsPage: React.FC = () => { Custom Model Endpoints - @@ -229,7 +244,8 @@ const EndpointsPage: React.FC = () => { Endpoint Name: {endpoint.name} , - Endpoint Status: {endpoint.name} + Endpoint Status: {endpoint.status?.status} + {endpoint.status?.icon} , Endpoint Description: {endpoint.description} @@ -250,7 +266,7 @@ const EndpointsPage: React.FC = () => { /> + + + + Endpoint Name + , + + Endpoint Status + , + + Endpoint Description + , + + URL + , + + Model Name + , + + API Key + + ]} + /> + + {endpoints.map((endpoint) => ( - - + + - Endpoint Name: {endpoint.name} - , - - Endpoint Status: {endpoint.status?.status} - {endpoint.status?.icon} - , - - Endpoint Description: {endpoint.description} - , - - URL: {endpoint.url} - , - - Model Name: {endpoint.modelName} - , - - API Key: {renderApiKey(endpoint.apiKey, endpoint.isApiKeyVisible || false)} + {endpoint.name} , + {endpoint.status?.status} {endpoint.status?.icon} , + {endpoint.description} , + {endpoint.url} , + {endpoint.modelName} , + + {renderApiKey(endpoint.apiKey, endpoint.isApiKeyVisible || false)} @@ -265,12 +309,61 @@ const EndpointsPage: React.FC = () => { ]} /> - - + {/* {endpointOptionsOpen && endpointOptionsOpen == endpoint.id && ( + setEndpointOptionsOpen(endpointId)} + toggle={(toggleRef: React.Ref) => ( + + )} + ouiaId="DownloadDropdown" + shouldFocusToggleOnSelect + > + + + + + } + > + {' '} + YAML Content + + {isGithubMode && ( + + + + } + > + {' '} + Attribution Content + + )} + + + )} */} diff --git a/src/types/index.ts b/src/types/index.ts index 72d4dbbe..dfdc0ece 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,13 +3,15 @@ import { ValidatedOptions } from '@patternfly/react-core'; export interface Endpoint { id: string; name: string; - description: string; + description?: string; url: string; modelName: string; - modelDescription: string; + modelDescription?: string; apiKey: string; } +export const EndpointRequiredFields: (keyof Endpoint)[] = ["name", "url", "modelName"]; + export interface Model { isDefault?: boolean; name: string; From d9fcecb83c1d6fb447645d8a3673642b26f8bbd2 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Mon, 17 Mar 2025 16:23:43 -0700 Subject: [PATCH 06/13] trying to fix elipsis dropdown Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 45 ++++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 5a3649c1..2ab4aea8 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -23,6 +23,7 @@ import { Form, FormGroup, InputGroup, + MenuToggleElement, Modal, ModalBody, ModalFooter, @@ -92,6 +93,7 @@ async function checkEndpointStatus( interface ExtendedEndpoint extends Endpoint { isApiKeyVisible?: boolean; status?: ModelEndpointStatus; + optionsOpen?: boolean; } const EndpointsPage: React.FC = () => { @@ -105,7 +107,7 @@ const EndpointsPage: React.FC = () => { const [modelDescription, setModelDescription] = useState(''); const [apiKey, setApiKey] = useState(''); const [endpointStatus, setEndpointStatus] = useState(unknownModelEndpointStatus); - const [endpointOptionsOpen, setEndpointOptionsOpen] = useState('') + const [endpointOptionsOpen, setEndpointOptionsOpen] = useState(false) useEffect(() => { const storedEndpoints = localStorage.getItem('endpoints'); @@ -118,17 +120,13 @@ const EndpointsPage: React.FC = () => { setIsModalOpen(!isModalOpen); }; - const handleEndpointOptionsToggle = (endpointId: string) => { - console.log("endpoint has been toggled: ", endpointId) - if (endpointOptionsOpen != '') { - setEndpointOptionsOpen('') - } else { - setEndpointOptionsOpen(endpointId) - } + const handleEndpointOptionsToggle = (endpoint: ExtendedEndpoint) => { + console.log("endpoint has been toggled: ", endpoint.id) + setEndpointOptionsOpen(!endpoint.optionsOpen) } const handleDeselectEndpointOptions = () => { - setEndpointOptionsOpen('') + setEndpointOptionsOpen(false) } const removeTrailingSlash = (inputUrl: string): string => { @@ -274,9 +272,6 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { Endpoint Status , - - Endpoint Description - , URL , @@ -295,11 +290,18 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { {endpoint.name} , + +

{endpoint.name}

+
+

{endpoint.description}

+
, {endpoint.status?.status} {endpoint.status?.icon} , - {endpoint.description} , {endpoint.url} , - {endpoint.modelName} , + +

{endpoint.modelName}

+
+

{endpoint.modelDescription}

+
, {renderApiKey(endpoint.apiKey, endpoint.isApiKeyVisible || false)} - - {/* {endpointOptionsOpen && endpointOptionsOpen == endpoint.id && ( + {/* {endpointOptionsOpen && ( setEndpointOptionsOpen(endpointId)} + onOpenChange={(endpointOptionsOpen: boolean) => setEndpointOptionsOpen(endpointOptionsOpen)} toggle={(toggleRef: React.Ref) => ( - From fb2e69b63eade43f1eaf1241f8cfa3d40233b765 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Mon, 31 Mar 2025 19:52:35 -0700 Subject: [PATCH 07/13] more wip Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 103 ++++++++++++-------------- 1 file changed, 47 insertions(+), 56 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 2ab4aea8..5466e064 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -23,7 +23,7 @@ import { Form, FormGroup, InputGroup, - MenuToggleElement, + MenuToggle, Modal, ModalBody, ModalFooter, @@ -107,7 +107,8 @@ const EndpointsPage: React.FC = () => { const [modelDescription, setModelDescription] = useState(''); const [apiKey, setApiKey] = useState(''); const [endpointStatus, setEndpointStatus] = useState(unknownModelEndpointStatus); - const [endpointOptionsOpen, setEndpointOptionsOpen] = useState(false) + const [endpointOptionsOpen, setEndpointOptionsOpen] = React.useState(false); + const [deleteEndpointModalOpen, setDeleteEndpointModalOpen] = useState(false) useEffect(() => { const storedEndpoints = localStorage.getItem('endpoints'); @@ -122,12 +123,11 @@ const EndpointsPage: React.FC = () => { const handleEndpointOptionsToggle = (endpoint: ExtendedEndpoint) => { console.log("endpoint has been toggled: ", endpoint.id) - setEndpointOptionsOpen(!endpoint.optionsOpen) - } - - const handleDeselectEndpointOptions = () => { - setEndpointOptionsOpen(false) - } + console.log("endpoint's setting open?", !endpoint.optionsOpen) + console.log("endpoint options open before?: ", endpointOptionsOpen) + setEndpointOptionsOpen(!endpointOptionsOpen) + console.log("endpoint options open before?: ", endpointOptionsOpen) + }; const removeTrailingSlash = (inputUrl: string): string => { if (typeof inputUrl !== 'string') { @@ -311,60 +311,51 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { ]} /> - - - {/* {endpointOptionsOpen && ( + {/* {deleteEndpointModalOpen ? ( + + + + some text here + + + ) : null} */} + {endpointOptionsOpen ? ( setEndpointOptionsOpen(endpointOptionsOpen)} - toggle={(toggleRef: React.Ref) => ( - + onOpenChange={(isEndpointOptionsOpen) => setEndpointOptionsOpen(isEndpointOptionsOpen)} + onSelect={() => setEndpointOptionsOpen(false)} + toggle={(toggleRef) => ( + setEndpointOptionsOpen(!endpointOptionsOpen)} + isExpanded={endpointOptionsOpen} + > +

SOMETHING SHOW UP

+ +
)} - ouiaId="DownloadDropdown" - shouldFocusToggleOnSelect + isOpen={endpointOptionsOpen} + ouiaId="ModelEndpointDropdown" > - - - - - } - > - {' '} - YAML Content - - {isGithubMode && ( - - - - } - > - {' '} - Attribution Content - - )} - + + Clear chat + {/* {onClose ? Close chat : null} */} +
- )} */} + ) : null }
From a68f7dbbc697defabd6edaa63b6a5428f0ca59b0 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Tue, 1 Apr 2025 07:26:59 -0700 Subject: [PATCH 08/13] options show up but all toggle not one Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 5466e064..c0ef6b25 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -33,7 +33,8 @@ import { PageSection, Popover, TextInput, - Title + Title, + ValidatedOptions } from '@patternfly/react-core'; import { BanIcon, CheckCircleIcon, EyeSlashIcon, EllipsisVIcon , EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons'; @@ -333,14 +334,16 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { ) : null} */} {endpointOptionsOpen ? ( setEndpointOptionsOpen(isEndpointOptionsOpen)} + onOpenChange={() => setEndpointOptionsOpen(true)} onSelect={() => setEndpointOptionsOpen(false)} toggle={(toggleRef) => ( setEndpointOptionsOpen(!endpointOptionsOpen)} + onClick={() => { + setEndpointOptionsOpen(!endpointOptionsOpen)} + } isExpanded={endpointOptionsOpen} >

SOMETHING SHOW UP

@@ -350,10 +353,10 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { isOpen={endpointOptionsOpen} ouiaId="ModelEndpointDropdown" > - + {/* Clear chat - {/* {onClose ? Close chat : null} */} - + {onClose ? Close chat : null} + */}
) : null } From ea46f50b13fce043936e4740d3721dfc732daa7d Mon Sep 17 00:00:00 2001 From: greg pereira Date: Tue, 1 Apr 2025 08:18:30 -0700 Subject: [PATCH 09/13] delete endpoint implementation Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 56 ++++++++++++++++++++------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index c0ef6b25..6f4dc0eb 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -109,7 +109,8 @@ const EndpointsPage: React.FC = () => { const [apiKey, setApiKey] = useState(''); const [endpointStatus, setEndpointStatus] = useState(unknownModelEndpointStatus); const [endpointOptionsOpen, setEndpointOptionsOpen] = React.useState(false); - const [deleteEndpointModalOpen, setDeleteEndpointModalOpen] = useState(false) + const [deleteEndpointModalOpen, setDeleteEndpointModalOpen] = React.useState(false); + const [deleteEndpointName, setDeleteEndpointName] = useState(''); useEffect(() => { const storedEndpoints = localStorage.getItem('endpoints'); @@ -187,10 +188,16 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { } }; - const handleDeleteEndpoint = (id: string) => { - const updatedEndpoints = endpoints.filter((ep) => ep.id !== id); - setEndpoints(updatedEndpoints); - localStorage.setItem('endpoints', JSON.stringify(updatedEndpoints)); + const handleDeleteEndpoint = (id: string, endpointName: string) => { + if (deleteEndpointName && deleteEndpointName == endpointName) { + const updatedEndpoints = endpoints.filter((ep) => ep.id !== id); + setEndpoints(updatedEndpoints); + localStorage.setItem('endpoints', JSON.stringify(updatedEndpoints)); + setDeleteEndpointModalOpen(false) + setDeleteEndpointName('') + } else { + alert("error: endpoint name did not match!") + } }; async function handleEditEndpoint (endpoint: ExtendedEndpoint) { @@ -312,26 +319,49 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { ]} /> - - {/* {deleteEndpointModalOpen ? ( + {deleteEndpointModalOpen ? ( setDeleteEndpointModalOpen(false)} aria-labelledby="confirm-delete-custom-model-endpoint" aria-describedby="show-yaml-body-variant" > - some text here +

The {endpoint.name} custom model endpoint will be deleted.

+
+

Type {endpoint.name} to confirm.

+ setDeleteEndpointName(value)} + />
+ + + , + +
- ) : null} */} + ) : null} {endpointOptionsOpen ? ( setEndpointOptionsOpen(true)} @@ -353,10 +383,6 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { isOpen={endpointOptionsOpen} ouiaId="ModelEndpointDropdown" > - {/* - Clear chat - {onClose ? Close chat : null} - */} ) : null }
From 0edd197fef48a7893bc92e34bcc9c6861cfcf505 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Wed, 2 Apr 2025 10:05:48 -0700 Subject: [PATCH 10/13] starting implementation on model endpoint disabling Signed-off-by: greg pereira --- src/app/playground/endpoints/page.tsx | 99 +++++++++++++++++---------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index 6f4dc0eb..fda3f924 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -59,6 +59,20 @@ const unknownModelEndpointStatus: ModelEndpointStatus = { icon: , }; +interface ModelDisabledStatus { + disabled: boolean; + color: string; +} + +const modelEnabled: ModelDisabledStatus = { + disabled: false, + color: "#999999" +} + +const modelDisabled: ModelDisabledStatus = { + disabled: true, + color: "transparent" +} async function checkEndpointStatus( endpointURL: string, @@ -94,7 +108,7 @@ async function checkEndpointStatus( interface ExtendedEndpoint extends Endpoint { isApiKeyVisible?: boolean; status?: ModelEndpointStatus; - optionsOpen?: boolean; + disabled?: ModelDisabledStatus; } const EndpointsPage: React.FC = () => { @@ -109,9 +123,18 @@ const EndpointsPage: React.FC = () => { const [apiKey, setApiKey] = useState(''); const [endpointStatus, setEndpointStatus] = useState(unknownModelEndpointStatus); const [endpointOptionsOpen, setEndpointOptionsOpen] = React.useState(false); + const [endpointOptionsID, setEndpointOptionsID] = React.useState(''); const [deleteEndpointModalOpen, setDeleteEndpointModalOpen] = React.useState(false); const [deleteEndpointName, setDeleteEndpointName] = useState(''); + const disableEndpoint = (endpoint: ExtendedEndpoint) => { + endpoint.disabled = modelDisabled + } + + const enableEndpoint = (endpoint: ExtendedEndpoint) => { + endpoint.disabled = modelEnabled + } + useEffect(() => { const storedEndpoints = localStorage.getItem('endpoints'); if (storedEndpoints) { @@ -123,14 +146,6 @@ const EndpointsPage: React.FC = () => { setIsModalOpen(!isModalOpen); }; - const handleEndpointOptionsToggle = (endpoint: ExtendedEndpoint) => { - console.log("endpoint has been toggled: ", endpoint.id) - console.log("endpoint's setting open?", !endpoint.optionsOpen) - console.log("endpoint options open before?: ", endpointOptionsOpen) - setEndpointOptionsOpen(!endpointOptionsOpen) - console.log("endpoint options open before?: ", endpointOptionsOpen) - }; - const removeTrailingSlash = (inputUrl: string): string => { if (typeof inputUrl !== 'string') { throw new Error('Invalid url'); @@ -141,15 +156,15 @@ const EndpointsPage: React.FC = () => { return inputUrl; }; -const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { - let returnValue = true - EndpointRequiredFields.forEach((requiredField) => { - if (endpoint[requiredField]?.trim().length == 0) { - returnValue = false - } - }) - return returnValue -} + const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { + let returnValue = true + EndpointRequiredFields.forEach((requiredField) => { + if (endpoint[requiredField]?.trim().length == 0) { + returnValue = false + } + }) + return returnValue + } async function handleSaveEndpoint () { const updatedUrl = removeTrailingSlash(url); @@ -294,21 +309,21 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { {endpoints.map((endpoint) => ( - - + + -

{endpoint.name}

-
-

{endpoint.description}

+

{endpoint.name}

+
+

{endpoint.description}

, {endpoint.status?.status} {endpoint.status?.icon} , {endpoint.url} , -

{endpoint.modelName}

+

{endpoint.modelName}


-

{endpoint.modelDescription}

+

{endpoint.modelDescription}

, {renderApiKey(endpoint.apiKey, endpoint.isApiKeyVisible || false)} @@ -319,12 +334,23 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { ]} /> + {endpoint.disabled?.disabled == false ? ( + + ): ( + + )} - {deleteEndpointModalOpen ? ( @@ -362,27 +388,30 @@ const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { ) : null} - {endpointOptionsOpen ? ( + {endpointOptionsOpen && endpointOptionsID == endpoint.id ? ( setEndpointOptionsOpen(true)} - onSelect={() => setEndpointOptionsOpen(false)} + onOpenChange={() => { setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(true)}} + onSelect={() => {setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(false)}} toggle={(toggleRef) => ( { + setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(!endpointOptionsOpen)} } isExpanded={endpointOptionsOpen} > -

SOMETHING SHOW UP

- -
+ )} isOpen={endpointOptionsOpen} ouiaId="ModelEndpointDropdown" > + + handleEditEndpoint(endpoint)}>Edit Endpoint + setDeleteEndpointModalOpen(true)}>Delete Endpoint +
) : null }
From 5ec55db3167afb1a9b46ea0368fcba277d7020ce Mon Sep 17 00:00:00 2001 From: greg pereira Date: Tue, 15 Apr 2025 16:35:33 -0700 Subject: [PATCH 11/13] disabling / enabling model Signed-off-by: greg pereira --- api-server/rhelai-install/rhelai-install.sh | 10 +- src/app/playground/endpoints/endpointPage.css | 7 + src/app/playground/endpoints/page.tsx | 123 ++++++++++-------- src/components/Chat/ChatBotComponent.tsx | 21 ++- src/components/Chat/ModelsContext.tsx | 8 +- src/types/index.ts | 11 ++ 6 files changed, 112 insertions(+), 68 deletions(-) create mode 100644 src/app/playground/endpoints/endpointPage.css diff --git a/api-server/rhelai-install/rhelai-install.sh b/api-server/rhelai-install/rhelai-install.sh index 5bedeb60..162260f6 100755 --- a/api-server/rhelai-install/rhelai-install.sh +++ b/api-server/rhelai-install/rhelai-install.sh @@ -22,11 +22,11 @@ if [ -d "/tmp/api-server" ]; then fi mkdir -p /tmp/api-server -cd /tmp/ui/api-server -wget https://instructlab-ui.s3.us-east-1.amazonaws.com/apiserver/apiserver-linux-amd64.tar.gz -tar -xzf apiserver-linux-amd64.tar.gz -mv apiserver-linux-amd64/ilab-apiserver /usr/local/sbin -rm -rf apiserver-linux-amd64 apiserver-linux-amd64.tar.gz +cd /tmp/api-server +curl -sLO https://instructlab-ui.s3.us-east-1.amazonaws.com/apiserver/apiserver-linux-amd64.tar.gz +tar -xzf apiserver-linux-amd64.tar.gz +mv apiserver-linux-amd64/ilab-apiserver $HOME/.local/bin +rm -rf apiserver-linux-amd64 apiserver-linux-amd64.tar.gz /tmp/api-server CUDA_FLAG="" diff --git a/src/app/playground/endpoints/endpointPage.css b/src/app/playground/endpoints/endpointPage.css new file mode 100644 index 00000000..7246f61f --- /dev/null +++ b/src/app/playground/endpoints/endpointPage.css @@ -0,0 +1,7 @@ +.disabled-endpoint { + opacity: 0.4; /* Faded look */ + pointer-events: none; /* Prevent clicks, hovers, etc. */ + user-select: none; /* Disables text selection */ + background-color: #f5f5f5; /* Light gray background (optional) */ + color: #6a6e73; /* PatternFly disabled text color */ + } diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index fda3f924..ecc4249a 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -3,7 +3,7 @@ import React, { ReactNode, useState, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { AppLayout } from '@/components/AppLayout'; -import { Endpoint, EndpointRequiredFields } from '@/types'; +import { Endpoint, EndpointRequiredFields, ModelEndpointStatus } from '@/types'; import { Breadcrumb, BreadcrumbItem, @@ -38,41 +38,14 @@ import { } from '@patternfly/react-core'; import { BanIcon, CheckCircleIcon, EyeSlashIcon, EllipsisVIcon , EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons'; +import './endpointPage.css'; -interface ModelEndpointStatus { - status: string; - icon: ReactNode; -} - -const availableModelEndpointStatus: ModelEndpointStatus = { - status: "available", - icon: , -}; - -const unavailableModelEndpointStatus: ModelEndpointStatus = { - status: "unavailable", - icon: , -}; - -const unknownModelEndpointStatus: ModelEndpointStatus = { - status: "unknown", - icon: , -}; +const availableModelEndpointStatusIcon: ReactNode = -interface ModelDisabledStatus { - disabled: boolean; - color: string; -} +const unavailableModelEndpointStatusIcon: ReactNode = -const modelEnabled: ModelDisabledStatus = { - disabled: false, - color: "#999999" -} +const unknownModelEndpointStatusIcon: ReactNode = -const modelDisabled: ModelDisabledStatus = { - disabled: true, - color: "transparent" -} async function checkEndpointStatus( endpointURL: string, @@ -96,19 +69,17 @@ async function checkEndpointStatus( headers: headers }); if (response.ok) { - return availableModelEndpointStatus; + return ModelEndpointStatus.available; } else { - return unavailableModelEndpointStatus; + return ModelEndpointStatus.unavailable; } } catch (error) { - return unknownModelEndpointStatus; + return ModelEndpointStatus.unknown; } } interface ExtendedEndpoint extends Endpoint { isApiKeyVisible?: boolean; - status?: ModelEndpointStatus; - disabled?: ModelDisabledStatus; } const EndpointsPage: React.FC = () => { @@ -121,18 +92,28 @@ const EndpointsPage: React.FC = () => { const [modelName, setModelName] = useState(''); const [modelDescription, setModelDescription] = useState(''); const [apiKey, setApiKey] = useState(''); - const [endpointStatus, setEndpointStatus] = useState(unknownModelEndpointStatus); + const [endpointStatus, setEndpointStatus] = useState(ModelEndpointStatus.unknown); + const [endpointEnabled, setEndpointEnabled] = useState(true); const [endpointOptionsOpen, setEndpointOptionsOpen] = React.useState(false); const [endpointOptionsID, setEndpointOptionsID] = React.useState(''); const [deleteEndpointModalOpen, setDeleteEndpointModalOpen] = React.useState(false); const [deleteEndpointName, setDeleteEndpointName] = useState(''); - const disableEndpoint = (endpoint: ExtendedEndpoint) => { - endpoint.disabled = modelDisabled - } - - const enableEndpoint = (endpoint: ExtendedEndpoint) => { - endpoint.disabled = modelEnabled + const toggleEndpointEnabled = (endpointId: string, endpointEnabled: boolean) => { + var newEndpointEnabledStatus: boolean + var updatedEndpoints: ExtendedEndpoint[] + endpointEnabled ? newEndpointEnabledStatus = false : newEndpointEnabledStatus = true + updatedEndpoints = endpoints.map(endpoint => { + if (endpoint.id === endpointId) { + return { + ...endpoint, + enabled: newEndpointEnabledStatus, + }; + } + return endpoint; + }); + setEndpoints(updatedEndpoints) + localStorage.setItem('endpoints', JSON.stringify(updatedEndpoints)); } useEffect(() => { @@ -159,8 +140,10 @@ const EndpointsPage: React.FC = () => { const validateEndpointData = (endpoint: ExtendedEndpoint): boolean => { let returnValue = true EndpointRequiredFields.forEach((requiredField) => { - if (endpoint[requiredField]?.trim().length == 0) { - returnValue = false + if (requiredField != "enabled" && requiredField != "status") { + if (endpoint[requiredField]?.trim().length == 0) { + returnValue = false + } } }) return returnValue @@ -180,6 +163,7 @@ const EndpointsPage: React.FC = () => { apiKey: apiKey, isApiKeyVisible: false, status: status, + enabled: true }; if (validateEndpointData(updatedEndpoint) == true) { const updatedEndpoints = currentEndpoint.id @@ -195,7 +179,8 @@ const EndpointsPage: React.FC = () => { setModelName(''); setModelDescription(''); setApiKey(''); - setEndpointStatus(unknownModelEndpointStatus) + setEndpointStatus(ModelEndpointStatus.unknown) + setEndpointEnabled(true) handleModalToggle(); } else { alert("error: please make sure all the required fields are set!") @@ -225,19 +210,33 @@ const EndpointsPage: React.FC = () => { setModelName(endpoint.modelName); setModelDescription(endpoint.modelDescription || ''); setApiKey(endpoint.apiKey); - setEndpointStatus(status) + setEndpointStatus(status) handleModalToggle(); }; const handleAddEndpoint = () => { - setCurrentEndpoint({ id: '', name: '', description: '', url: '', modelName: '', modelDescription: '', apiKey: '', isApiKeyVisible: false, status: unknownModelEndpointStatus}); + setCurrentEndpoint( + { + id: '', + name: '', + description: '', + url: '', + modelName: '', + modelDescription: '', + apiKey: '', + isApiKeyVisible: false, + status: ModelEndpointStatus.unknown, + enabled: true + } + ); setEndpointName(''); setEndpointDescription(''); setUrl(''); setModelName(''); setModelDescription(''); setApiKey(''); - setEndpointStatus(unknownModelEndpointStatus) + setEndpointStatus(ModelEndpointStatus.unknown) + setEndpointEnabled(true) handleModalToggle(); }; @@ -309,7 +308,7 @@ const EndpointsPage: React.FC = () => {
{endpoints.map((endpoint) => ( - + {

{endpoint.description}

, - {endpoint.status?.status} {endpoint.status?.icon} , + {ModelEndpointStatus[endpoint.status]} {(() => { + switch (endpoint.status) { + case ModelEndpointStatus.available: + return availableModelEndpointStatusIcon; + case ModelEndpointStatus.unavailable: + return unavailableModelEndpointStatusIcon; + case ModelEndpointStatus.unknown: + return unknownModelEndpointStatusIcon; + default: + return unknownModelEndpointStatusIcon; + } + })()} , {endpoint.url} ,

{endpoint.modelName}

@@ -334,15 +344,16 @@ const EndpointsPage: React.FC = () => { ]} /> - {endpoint.disabled?.disabled == false ? ( - ): ( diff --git a/src/components/Chat/ChatBotComponent.tsx b/src/components/Chat/ChatBotComponent.tsx index f35f7532..efabf562 100644 --- a/src/components/Chat/ChatBotComponent.tsx +++ b/src/components/Chat/ChatBotComponent.tsx @@ -12,6 +12,7 @@ import { DropdownList, MenuToggle, MenuToggleElement, + Popover, Select, SelectList, SelectOption, @@ -33,7 +34,7 @@ import { Model } from '@/types'; import { modelFetcher } from '@/components/Chat/modelService'; const botAvatar = '/bot-icon-chat-32x32.svg'; -import { EllipsisVIcon, TimesIcon } from '@patternfly/react-icons'; +import { EllipsisVIcon, QuestionCircleIcon, TimesIcon } from '@patternfly/react-icons'; import styles from '@/components/Chat/chat.module.css'; import { ModelsContext } from '@/components/Chat/ModelsContext'; import { useSession } from 'next-auth/react'; @@ -184,14 +185,14 @@ const ChatBotComponent: React.FunctionComponent = ({ const toggle = (toggleRef: React.Ref) => ( - {model ? model.name : 'Select a model'} + {model && model.enabled ? model.name : 'Select a model'} ); const dropdownItems = React.useMemo( () => availableModels.map((model, index) => ( - + {model.name} )), @@ -227,6 +228,20 @@ const ChatBotComponent: React.FunctionComponent = ({ > {dropdownItems} + Can't select your model?} + bodyContent={ +
If you model is not selectable, that means you have disabled the custom model endpoint. + + To change this please see the Custom Model Endpoints page. +
+ } + > + +
{showCompare ? ( diff --git a/src/components/Chat/ModelsContext.tsx b/src/components/Chat/ModelsContext.tsx index 57c2c6f1..c8bca842 100644 --- a/src/components/Chat/ModelsContext.tsx +++ b/src/components/Chat/ModelsContext.tsx @@ -29,18 +29,18 @@ const ModelsContextProvider: React.FC = ({ children }) => { const envConfig = await response.json(); const defaultModels: Model[] = [ - { isDefault: true, name: 'Granite-7b', apiURL: envConfig.GRANITE_API, modelName: envConfig.GRANITE_MODEL_NAME }, - { isDefault: true, name: 'Merlinite-7b', apiURL: envConfig.MERLINITE_API, modelName: envConfig.MERLINITE_MODEL_NAME } + { isDefault: true, name: 'Granite-7b', apiURL: envConfig.GRANITE_API, modelName: envConfig.GRANITE_MODEL_NAME, enabled: true }, + { isDefault: true, name: 'Merlinite-7b', apiURL: envConfig.MERLINITE_API, modelName: envConfig.MERLINITE_MODEL_NAME, enabled: true } ]; const storedEndpoints = localStorage.getItem('endpoints'); - const customModels = storedEndpoints ? JSON.parse(storedEndpoints).map((endpoint: Endpoint) => ({ isDefault: false, name: endpoint.modelName, apiURL: `${endpoint.url}`, - modelName: endpoint.modelName + modelName: endpoint.modelName, + enabled: endpoint.enabled })) : []; diff --git a/src/types/index.ts b/src/types/index.ts index dfdc0ece..957fbaf5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,13 @@ +import { ReactNode } from 'react'; import { ValidatedOptions } from '@patternfly/react-core'; + +export enum ModelEndpointStatus { + available, + unavailable, + unknown +} + export interface Endpoint { id: string; name: string; @@ -8,6 +16,8 @@ export interface Endpoint { modelName: string; modelDescription?: string; apiKey: string; + status: ModelEndpointStatus; + enabled: boolean; } export const EndpointRequiredFields: (keyof Endpoint)[] = ["name", "url", "modelName"]; @@ -17,6 +27,7 @@ export interface Model { name: string; apiURL: string; modelName: string; + enabled: boolean; } export interface Label { From 56933de10ac50d181d42f7c4f9f0cab19a38bb50 Mon Sep 17 00:00:00 2001 From: greg pereira Date: Wed, 16 Apr 2025 09:01:05 -0700 Subject: [PATCH 12/13] update model status async every 10 minutes Signed-off-by: greg pereira --- src/app/playground/endpoints/endpointPage.css | 2 - src/app/playground/endpoints/page.tsx | 96 ++++++++++++------- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/app/playground/endpoints/endpointPage.css b/src/app/playground/endpoints/endpointPage.css index 7246f61f..444cb2da 100644 --- a/src/app/playground/endpoints/endpointPage.css +++ b/src/app/playground/endpoints/endpointPage.css @@ -1,7 +1,5 @@ .disabled-endpoint { opacity: 0.4; /* Faded look */ - pointer-events: none; /* Prevent clicks, hovers, etc. */ - user-select: none; /* Disables text selection */ background-color: #f5f5f5; /* Light gray background (optional) */ color: #6a6e73; /* PatternFly disabled text color */ } diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index ecc4249a..d95d0651 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -39,6 +39,7 @@ import { import { BanIcon, CheckCircleIcon, EyeSlashIcon, EllipsisVIcon , EyeIcon, QuestionCircleIcon } from '@patternfly/react-icons'; import './endpointPage.css'; +import { status } from 'isomorphic-git'; const availableModelEndpointStatusIcon: ReactNode = @@ -99,6 +100,41 @@ const EndpointsPage: React.FC = () => { const [deleteEndpointModalOpen, setDeleteEndpointModalOpen] = React.useState(false); const [deleteEndpointName, setDeleteEndpointName] = useState(''); + useEffect(() => { + const storedEndpoints = localStorage.getItem('endpoints'); + if (storedEndpoints) { + setEndpoints(JSON.parse(storedEndpoints)); + } + }, []); + + useEffect(() => { + async function updateEndpointStatuses() { + const updatedEndpoints = await Promise.all( + endpoints.map(async (endpoint) => { + const status = await checkEndpointStatus( + endpoint.url, + endpoint.modelName, + endpoint.apiKey + ); + + return { + ...endpoint, + status, + }; + }) + ); + + setEndpoints(updatedEndpoints); + } + + const interval = setInterval(() => { + console.log("Running update endpoints") + updateEndpointStatuses(); + }, 10 * 60 * 1000); // run every 10 minutes in miliseconds + + return () => clearInterval(interval); // cleanup on unmount + }, [endpoints]); + const toggleEndpointEnabled = (endpointId: string, endpointEnabled: boolean) => { var newEndpointEnabledStatus: boolean var updatedEndpoints: ExtendedEndpoint[] @@ -116,13 +152,6 @@ const EndpointsPage: React.FC = () => { localStorage.setItem('endpoints', JSON.stringify(updatedEndpoints)); } - useEffect(() => { - const storedEndpoints = localStorage.getItem('endpoints'); - if (storedEndpoints) { - setEndpoints(JSON.parse(storedEndpoints)); - } - }, []); - const handleModalToggle = () => { setIsModalOpen(!isModalOpen); }; @@ -346,7 +375,6 @@ const EndpointsPage: React.FC = () => { {endpoint.enabled == true ? ( + {endpointOptionsOpen && endpointOptionsID == endpoint.id ? ( + { setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(true)}} + onSelect={() => {setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(false)}} + toggle={(toggleRef) => ( + { + setEndpointOptionsID(endpoint.id); + setEndpointOptionsOpen(!endpointOptionsOpen)} + } + isExpanded={endpointOptionsOpen} + > + + )} + isOpen={endpointOptionsOpen} + ouiaId="ModelEndpointDropdown" + > + + handleEditEndpoint(endpoint)}>Edit Endpoint + setDeleteEndpointModalOpen(true)}>Delete Endpoint + + + ) : null } {deleteEndpointModalOpen ? ( { ) : null} - {endpointOptionsOpen && endpointOptionsID == endpoint.id ? ( - { setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(true)}} - onSelect={() => {setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(false)}} - toggle={(toggleRef) => ( - { - setEndpointOptionsID(endpoint.id); - setEndpointOptionsOpen(!endpointOptionsOpen)} - } - isExpanded={endpointOptionsOpen} - > - - )} - isOpen={endpointOptionsOpen} - ouiaId="ModelEndpointDropdown" - > - - handleEditEndpoint(endpoint)}>Edit Endpoint - setDeleteEndpointModalOpen(true)}>Delete Endpoint - - - ) : null }
From c8c92ccc0609b1eef5b955b17d164e0d4bd65fad Mon Sep 17 00:00:00 2001 From: greg pereira Date: Wed, 16 Apr 2025 09:32:13 -0700 Subject: [PATCH 13/13] requests should be made with apikey Signed-off-by: greg pereira --- src/app/api/playground/chat/route.ts | 33 ++++++++++++---- src/app/playground/endpoints/page.tsx | 57 ++++++++++++--------------- src/components/Chat/ModelsContext.tsx | 3 +- src/components/Chat/modelService.ts | 18 +++++++-- src/types/index.ts | 1 + 5 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/app/api/playground/chat/route.ts b/src/app/api/playground/chat/route.ts index beb9f65e..84f4b66d 100644 --- a/src/app/api/playground/chat/route.ts +++ b/src/app/api/playground/chat/route.ts @@ -10,6 +10,7 @@ export async function POST(req: NextRequest) { const { question, systemRole } = await req.json(); const apiURL = req.nextUrl.searchParams.get('apiURL'); const modelName = req.nextUrl.searchParams.get('modelName'); + const apiKey = req.nextUrl.searchParams.get('apiKey') if (!apiURL || !modelName) { return new NextResponse('Missing API URL or Model Name', { status: 400 }); @@ -26,16 +27,34 @@ export async function POST(req: NextRequest) { stream: true }; - const agent = new https.Agent({ - rejectUnauthorized: false - }); + let agent: https.Agent + if (apiKey && apiKey != "" ) { + agent = new https.Agent({ + rejectUnauthorized: true + }); + } else { + agent = new https.Agent({ + rejectUnauthorized: false + }); + } + + var headers: HeadersInit + if (apiKey && apiKey != "" ) { + headers = { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream', + 'Authorization': `Bearer: ${apiKey}` + } + } else { + headers = { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream' + } + } const chatResponse = await fetch(`${apiURL}/v1/chat/completions`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - accept: 'application/json' - }, + headers: headers, body: JSON.stringify(requestData), agent: apiURL.startsWith('https') ? agent : undefined }); diff --git a/src/app/playground/endpoints/page.tsx b/src/app/playground/endpoints/page.tsx index d95d0651..946ec337 100644 --- a/src/app/playground/endpoints/page.tsx +++ b/src/app/playground/endpoints/page.tsx @@ -386,38 +386,31 @@ const EndpointsPage: React.FC = () => { enable )} - - {endpointOptionsOpen && endpointOptionsID == endpoint.id ? ( - { setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(true)}} - onSelect={() => {setEndpointOptionsID(endpoint.id); setEndpointOptionsOpen(false)}} - toggle={(toggleRef) => ( - { - setEndpointOptionsID(endpoint.id); - setEndpointOptionsOpen(!endpointOptionsOpen)} - } - isExpanded={endpointOptionsOpen} - > - - )} - isOpen={endpointOptionsOpen} - ouiaId="ModelEndpointDropdown" - > - - handleEditEndpoint(endpoint)}>Edit Endpoint - setDeleteEndpointModalOpen(true)}>Delete Endpoint - - - ) : null } + setEndpointOptionsOpen(false)} + toggle={(toggleRef) => ( + { + setEndpointOptionsOpen(!endpointOptionsOpen); + setEndpointOptionsID(endpoint.id); + }} + isExpanded={endpointOptionsOpen && endpointOptionsID === endpoint.id} + > + + + )} + isOpen={endpointOptionsOpen && endpointOptionsID === endpoint.id} + popperProps={{ position: 'right' }} + ouiaId="ModelEndpointDropdown" + > + + handleEditEndpoint(endpoint)}>Edit Endpoint + setDeleteEndpointModalOpen(true)}>Delete Endpoint + + {deleteEndpointModalOpen ? ( = ({ children }) => { name: endpoint.modelName, apiURL: `${endpoint.url}`, modelName: endpoint.modelName, - enabled: endpoint.enabled + enabled: endpoint.enabled, + apiKey: endpoint.apiKey })) : []; diff --git a/src/components/Chat/modelService.ts b/src/components/Chat/modelService.ts index 068d4d6c..a1240bf2 100644 --- a/src/components/Chat/modelService.ts +++ b/src/components/Chat/modelService.ts @@ -26,12 +26,22 @@ export const customModelFetcher = async ( // Client-side fetch if the selected model is a custom endpoint try { + var headers: HeadersInit + if (selectedModel.apiKey && selectedModel.apiKey != "" ) { + headers = { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream', + 'Authorization': `Bearer: ${selectedModel.apiKey}` + } + } else { + headers = { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream' + } + } const response = await fetch(`${selectedModel.apiURL}/v1/chat/completions`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'text/event-stream' - }, + headers: headers, body: JSON.stringify(requestData), signal: newController.signal }); diff --git a/src/types/index.ts b/src/types/index.ts index 957fbaf5..2fc7e5f0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -28,6 +28,7 @@ export interface Model { apiURL: string; modelName: string; enabled: boolean; + apiKey?: string; } export interface Label {