Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add(FR-261): draft for model player UI derived from WebUI #2878

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
NODE_ENV: 'production'
}
};
globalThis.packageVersion = "24.09.0-alpha.1";
globalThis.buildNumber = "6011";
globalThis.packageVersion = "25.1.0-alpha.1";
globalThis.buildNumber = "6441";

</script>
<!-- DO NOT CHANGE BELOW LINE -->
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"manifest_version": 9,
"name": "Backend.AI Web UI",
"short_name": "BackendAIWebUI",
"version": "24.09.0-alpha.1",
"version": "25.1.0-alpha.1",
"start_url": "/",
"display": "standalone",
"background_color": "#fff",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "backend.ai-webui",
"productName": "Backend.AI Desktop",
"version": "24.09.0-alpha.1",
"version": "25.1.0-alpha.1",
"repository": "https://github.com/lablup/backend.ai-webui.git",
"author": "Lablup Inc. <[email protected]>",
"license": "LGPL-3.0-or-later",
Expand Down
2 changes: 1 addition & 1 deletion react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "backend-ai-webui-react",
"version": "24.09.0-alpha.1",
"version": "25.1.0-alpha.1",
"private": true,
"dependencies": {
"@ai-sdk/openai": "^1.0.11",
Expand Down
26 changes: 23 additions & 3 deletions react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ const ComputeSessionList = React.lazy(
const AgentSummaryPage = React.lazy(() => import('./pages/AgentSummaryPage'));
const MaintenancePage = React.lazy(() => import('./pages/MaintenancePage'));

/**
* Pages for Model Player
*/
const PlaygroundPage = React.lazy(
() => import('./components/lablupTalkativotUI/LLMPlaygroundPage'),
);
const ModelStorePage = React.lazy(() => import('./pages/ModelStorePage'));
interface CustomHandle {
title?: string;
labelKey?: string;
Expand Down Expand Up @@ -107,17 +114,17 @@ const router = createBrowserRouter([
children: [
{
path: '/',
element: <WebUINavigate to="/summary" replace />,
element: <WebUINavigate to="/playground" replace />,
},
{
//for electron dev mode
path: '/build/electron-app/app/index.html',
element: <WebUINavigate to="/summary" replace />,
element: <WebUINavigate to="/playground" replace />,
},
{
//for electron prod mode
path: '/app/index.html',
element: <WebUINavigate to="/summary" replace />,
element: <WebUINavigate to="/playground" replace />,
},
{
path: '/summary',
Expand Down Expand Up @@ -365,6 +372,19 @@ const router = createBrowserRouter([
path: '*',
element: <></>,
},
/**
* Pages for Model Player
*/
{
path: '/playground',
handle: { labelKey: 'webui.menu.Playground' },
Component: PlaygroundPage,
},
{
path: '/model-store',
handle: { labelKey: 'webui.menu.ModelStore' },
Component: ModelStorePage,
},
],
},
]);
Expand Down
23 changes: 23 additions & 0 deletions react/src/components/BAIIcons/VLLMIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ReactComponent as logo } from './vllm-color.svg';
import Icon from '@ant-design/icons';
import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';

interface CustomIconProps
extends Omit<CustomIconComponentProps, 'width' | 'height' | 'fill'> {
size?: number;
}

const VLLMIcon: React.FC<CustomIconProps> = (props) => {
return (
<Icon
component={logo}
{...props}
style={{
fontSize: props.size,
...props.style,
}}
/>
);
};

export default VLLMIcon;
1 change: 1 addition & 0 deletions react/src/components/BAIIcons/vllm-color.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 122 additions & 0 deletions react/src/components/ChatContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useTanQuery } from '../hooks/reactQueryAlias';
import { ChatContentEndpointDetailQuery } from './__generated__/ChatContentEndpointDetailQuery.graphql';
import { Model } from './lablupTalkativotUI/ChatUIModal';
import LLMChatCard from './lablupTalkativotUI/LLMChatCard';
import { ReloadOutlined } from '@ant-design/icons';
import { Alert, Button } from 'antd';
import graphql from 'babel-plugin-relay/macro';
import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useLazyLoadQuery } from 'react-relay/hooks';

interface ChatContentProps {
endpointId: string;
endpointUrl: string;
basePath: string;
}

const ChatContent: React.FC<ChatContentProps> = ({
endpointId,
endpointUrl,
basePath,
}) => {
const { t } = useTranslation();

const { endpoint_token_list } =
useLazyLoadQuery<ChatContentEndpointDetailQuery>(
graphql`
query ChatContentEndpointDetailQuery(
$endpointId: UUID!
$tokenListOffset: Int!
$tokenListLimit: Int!
) {
endpoint_token_list(
limit: $tokenListLimit
offset: $tokenListOffset
endpoint_id: $endpointId
) {
total_count
items {
id
token
endpoint_id
created_at
valid_until
}
}
}
`,
{
tokenListLimit: 100,
tokenListOffset: 0,
endpointId: endpointId as string,
},
{
fetchPolicy: 'network-only',
},
);

const newestValidToken =
_.orderBy(endpoint_token_list?.items, ['valid_until'], ['desc'])[0]
?.token ?? '';

const {
data: modelsResult,
// error,
refetch,
} = useTanQuery<{
data: Array<Model>;
}>({
queryKey: ['models', endpointUrl],
queryFn: () => {
return fetch(new URL(basePath + '/models', endpointUrl).toString(), {
headers: {
Authorization: `BackendAI ${newestValidToken}`,
},
})
.then((res) => res.json())
.catch((err) => {
console.log(err);
});
},
});

return (
<LLMChatCard
endpointId={endpointId || ''}
baseURL={new URL(basePath, endpointUrl).toString()}
models={_.map(modelsResult?.data, (m) => ({
id: m.id,
name: m.id,
}))}
apiKey={newestValidToken}
fetchOnClient
style={{ flex: 1 }}
allowCustomModel={false}
alert={
_.isEmpty(modelsResult?.data) && (
<Alert
type="warning"
showIcon
message={t('chatui.CannotFindModel')}
action={
<Button
icon={<ReloadOutlined />}
onClick={() => {
refetch();
}}
>
{t('button.Refresh')}
</Button>
}
/>
)
}
modelId={modelsResult?.data?.[0].id ?? 'custom'}
modelToken={newestValidToken}
/>
);
};

export default ChatContent;
61 changes: 61 additions & 0 deletions react/src/components/ImportFromHuggingFacePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Flex from '../components/Flex';
import BAICard from './BAICard';
import { App, Button, Input, theme } from 'antd';
import type { GetProps } from 'antd';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';

const ImportFromHuggingFacePanel: React.FC = () => {
const { t } = useTranslation();

Check warning on line 9 in react/src/components/ImportFromHuggingFacePanel.tsx

View workflow job for this annotation

GitHub Actions / coverage

't' is assigned a value but never used
const { token } = theme.useToken();
const { message } = App.useApp();
const [search, setSearch] = useState<string>('');

Check warning on line 12 in react/src/components/ImportFromHuggingFacePanel.tsx

View workflow job for this annotation

GitHub Actions / coverage

'search' is assigned a value but never used
type SearchProps = GetProps<typeof Input.Search>;

const { Search } = Input;
const onSearch: SearchProps['onSearch'] = (value, _e, info) => {
// TODO: download model from hugging face by URL
setSearch(value);
};

return (
<BAICard
style={{
backgroundImage:
'linear-gradient(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.6)), url(/resources/images/model-player/hugging-face-background.jpg)',
}}
>
<Flex
direction="row"
align="center"
justify="center"
style={{ padding: '20px' }}
gap={'sm'}
>
<Search
placeholder="Import From Hugging Face"
allowClear
enterButton={
<Button
type="primary"
onClick={() => {
message.info({
key: 'import-from-hugging-face',
content: 'Only available for administrators.',
});
}}
// FIXME: Temporary use hardcoded color for the button background
style={{ color: token.colorBgBase, backgroundColor: '#FF7A00' }}
>
Download
</Button>
}
size="large"
onSearch={onSearch}
/>
</Flex>
</BAICard>
);
};

export default ImportFromHuggingFacePanel;
Loading
Loading