diff --git a/README.md b/README.md index a504aa3..9aa1077 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,14 @@ Built using: python build/start_application.py ``` +## [Technical Overview](docs/technical_overview.md): + +The given document gives overall capabilities, tech stack, and general idea to get started on this application. + +## Technical Guides: +### [Generation Workflow](docs/guides/sft_workflow.md) +### [Evaluation Workflow](docs/guides/evaluation_workflow.md) + ## Legal Notice diff --git a/app/client/src/Container.tsx b/app/client/src/Container.tsx index 998b831..219b415 100644 --- a/app/client/src/Container.tsx +++ b/app/client/src/Container.tsx @@ -10,6 +10,8 @@ import { Pages } from './types'; import { QueryClient, QueryClientProvider } from 'react-query'; import React, { useMemo } from 'react'; import { GithubOutlined, MailOutlined } from '@ant-design/icons'; +import { Upgrade } from '@mui/icons-material'; +import UpgradeButton from './pages/Home/UpgradeButton'; const { Text } = Typography; const { Header, Content } = Layout; @@ -38,7 +40,7 @@ const BrandingTitle = styled(Typography)` line-height: 0.75; letter-spacing: normal; text-align: left; - color: #fff; + color: rgba(255, 255, 255, 0.65); ` const BrandingTextContainer = styled(Flex)` padding-top: 5px; @@ -86,7 +88,12 @@ const pages: MenuItem[] = [ - +
+ + + Join the discussion on GitHub + +

@@ -96,10 +103,17 @@ const pages: MenuItem[] = [ {LABELS[Pages.FEEDBACK]} - ), + ) + }, + { + key: Pages.UPGRADE, + label: ( + + ) } ] + const NotificationContext = React.createContext({messagePlacement: 'topRight'}); const Container = () => { @@ -128,7 +142,6 @@ const Container = () => { disabledOverflow={true} items={pages} mode="horizontal" - // onClick={handleMenuClick} selectable={false} selectedKeys={[location.pathname]} theme="dark" diff --git a/app/client/src/pages/DataGenerator/Parameters.tsx b/app/client/src/pages/DataGenerator/Parameters.tsx index bcb0948..9b82ae9 100644 --- a/app/client/src/pages/DataGenerator/Parameters.tsx +++ b/app/client/src/pages/DataGenerator/Parameters.tsx @@ -84,9 +84,10 @@ const Parameters = () => { form.setFieldsValue({ model_parameters: { [field]: value }}); }; - if (loadingDefaultParams) { - return - } + // if (loadingDefaultParams) { + // return + // } + return ( <> diff --git a/app/client/src/pages/DataGenerator/Prompt.tsx b/app/client/src/pages/DataGenerator/Prompt.tsx index 4a7d6c3..71c654d 100644 --- a/app/client/src/pages/DataGenerator/Prompt.tsx +++ b/app/client/src/pages/DataGenerator/Prompt.tsx @@ -14,6 +14,7 @@ import { Usecases, WorkflowType } from './types'; import { useWizardCtx } from './utils'; import { useDatasetSize, useGetPromptByUseCase } from './hooks'; import CustomPromptButton from './CustomPromptButton'; +import get from 'lodash/get'; const { Title } = Typography; @@ -81,7 +82,7 @@ const Prompt = () => { // Page Bootstrap requests and useEffect const { data: defaultTopics, loading: topicsLoading } = usefetchTopics(useCase); const { data: defaultSchema, loading: schemaLoading } = useFetchDefaultSchema(); - const { data: dataset_size, isLoading: datasetSizeLoading } = useDatasetSize( + const { data: dataset_size, isLoading: datasetSizeLoadin, isError, error } = useDatasetSize( workflow_type, doc_paths, input_key, @@ -89,6 +90,16 @@ const Prompt = () => { output_key ); + useEffect(() => { + if (isError) { + notification.error({ + message: 'Error fetching the dataset size', + description: get(error, 'error'), + }); + } + + }, [error, isError]); + useEffect(() => { if (defaultTopics) { // customTopics is a client-side only fieldValue that persists custom topics added diff --git a/app/client/src/pages/DataGenerator/constants.ts b/app/client/src/pages/DataGenerator/constants.ts index 4e614e4..4e5549a 100644 --- a/app/client/src/pages/DataGenerator/constants.ts +++ b/app/client/src/pages/DataGenerator/constants.ts @@ -1,4 +1,4 @@ -import { ModelProviders } from './types'; +import { ModelProviders, ModelProvidersDropdownOpts } from './types'; export const MODEL_PROVIDER_LABELS = { [ModelProviders.BEDROCK]: 'AWS Bedrock', @@ -8,4 +8,35 @@ export const MODEL_PROVIDER_LABELS = { export const MIN_SEED_INSTRUCTIONS = 1 export const MAX_SEED_INSTRUCTIONS = 500; export const MAX_NUM_QUESTION = 100; -export const DEMO_MODE_THRESHOLD = 25 +export const DEMO_MODE_THRESHOLD = 25; + + +export const USECASE_OPTIONS = [ + { label: 'Code Generation', value: 'code_generation' }, + { label: 'Text to SQL', value: 'text2sql' }, + { label: 'Custom', value: 'custom' } +]; + +export const WORKFLOW_OPTIONS = [ + { label: 'Supervised Fine-Tuning', value: 'sft' }, + { label: 'Custom Data Generation', value: 'custom' } +]; + +export const MODEL_TYPE_OPTIONS: ModelProvidersDropdownOpts = [ + { label: MODEL_PROVIDER_LABELS[ModelProviders.BEDROCK], value: ModelProviders.BEDROCK}, + { label: MODEL_PROVIDER_LABELS[ModelProviders.CAII], value: ModelProviders.CAII }, +]; + + +export const getModelProvider = (provider: ModelProviders) => { + return MODEL_PROVIDER_LABELS[provider]; +}; + +export const getWorkflowType = (value: string) => { + return WORKFLOW_OPTIONS.find((option) => option.value === value)?.label; +}; + +export const getUsecaseType = (value: string) => { + return USECASE_OPTIONS.find((option) => option.value === value)?.label; +}; + diff --git a/app/client/src/pages/DataGenerator/hooks.ts b/app/client/src/pages/DataGenerator/hooks.ts index 59b7876..0e6e34d 100644 --- a/app/client/src/pages/DataGenerator/hooks.ts +++ b/app/client/src/pages/DataGenerator/hooks.ts @@ -166,6 +166,10 @@ export const useGetProjectFiles = (paths: string[]) => { }, body: JSON.stringify(params), }); + if (resp.status !== 200) { + const body_error = await resp.json(); + throw new Error('Error fetching dataset size' + get(body_error, 'error')); + } const body = await resp.json(); return get(body, 'dataset_size'); } @@ -197,6 +201,7 @@ export const useDatasetSize = ( }, ); + console.log('--------------error', error); if (isError) { console.log('data', error); notification.error({ diff --git a/app/client/src/pages/DatasetDetails/ConfigurationTab.tsx b/app/client/src/pages/DatasetDetails/ConfigurationTab.tsx new file mode 100644 index 0000000..89330f5 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/ConfigurationTab.tsx @@ -0,0 +1,195 @@ +import get from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; +import React from 'react'; +import { Dataset } from '../Evaluator/types'; +import { Col, Flex, Modal, Row, Space, Table, Tag, Typography } from 'antd'; +import ExampleModal from './ExampleModal'; +import { QuestionSolution } from '../DataGenerator/types'; +import styled from 'styled-components'; + +const { Text } = Typography; + +interface Props { + dataset: Dataset; +} + +const StyledTable = styled(Table)` + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-table-thead > tr > th { + color: #5a656d; + border-bottom: 1px solid #eaebec; + font-weight: 500; + text-align: left; + // background: #ffffff; + border-bottom: 1px solid #eaebec; + transition: background 0.3s ease; + } + .ant-table-row { + cursor: pointer; + } + .ant-table-row > td.ant-table-cell { + padding: 8px; + padding-left: 16px; + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-typography { + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + } + } +`; + +const StyledTitle = styled.div` + margin-bottom: 4px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-size: 16px; + font-weight: 500; + margin-left: 4px; + +`; + +const Container = styled.div` + padding: 16px; + background-color: #ffffff; +`; + +export const TagsContainer = styled.div` + min-height: 30px; + display: block; + margin-bottom: 4px; + margin-top: 4px; + .ant-tag { + max-width: 150px; + } + .tag-title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } +`; + + +const ConfigurationTab: React.FC = ({ dataset }) => { + const topics = get(dataset, 'topics', []); + + const exampleColummns = [ + { + title: 'Prompts', + dataIndex: 'prompts', + ellipsis: true, + render: (_text: QuestionSolution, record: QuestionSolution) => <>{record.question} + }, + { + title: 'Completions', + dataIndex: 'completions', + ellipsis: true, + render: (_text: QuestionSolution, record: QuestionSolution) => <>{record.solution} + }, + ] + + const parameterColummns = [ + { + title: 'Temperature', + dataIndex: 'temperature', + ellipsis: true, + render: (temperature: number) => <>{temperature} + }, + { + title: 'Top K', + dataIndex: 'top_k', + ellipsis: true, + render: (top_k: number) => <>{top_k} + }, + { + title: 'Top P', + dataIndex: 'top_p', + ellipsis: true, + render: (top_p: number) => <>{top_p} + }, + + ]; + console.log('topics:', topics); + console.log('dataset:', dataset); + console.log('examples:', dataset.examples); + + return ( + + + + + Custom Prompt + + {dataset?.custom_prompt} + + + + + {!isEmpty(topics) && + + + + Seed Instructions + + + {topics.map((tag: string) => ( + +
+ {tag} +
+
+ ))} +
+
+
+ +
} + + + + Examples + ({ + onClick: () => Modal.info({ + title: 'View Details', + content: , + icon: undefined, + maskClosable: false, + width: 1000 + }) + })} + rowKey={(_record, index) => `summary-examples-table-${index}`} + /> + + + + + + + Parameters + `parameters-table-${index}`} + /> + + + +
+ + ); +}; + +export default ConfigurationTab; + + diff --git a/app/client/src/pages/DatasetDetails/CustomGenerationTable.tsx b/app/client/src/pages/DatasetDetails/CustomGenerationTable.tsx new file mode 100644 index 0000000..21d2449 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/CustomGenerationTable.tsx @@ -0,0 +1,91 @@ +import React, { SyntheticEvent, useEffect } from 'react'; + +import { Col, Input, Row, Table } from 'antd'; +import { CustomResult } from '../DataGenerator/types'; +import { DatasetGeneration } from '../Home/types'; +import { sortItemsByKey } from '../../utils/sortutils'; +import { SearchProps } from 'antd/es/input/Search'; +import { throttle } from 'lodash'; + +const { Search } = Input; + +interface Props { + results: DatasetGeneration[] +} + + +const CustomGenerationTable: React.FC = ({ results }) => { + console.log('CustomGenerationTable > results', results); + const [searchQuery, setSearchQuery] = React.useState(null); + const [filteredResults, setFilteredResults] = React.useState(results || []); + + useEffect(() => { + if (searchQuery) { + const filtered = results.filter((result: DatasetGeneration) => { + // clean up the filter logic + return result?.Prompt?.toLowerCase().includes(searchQuery.toLowerCase()) || result?.Completion?.toLowerCase().includes(searchQuery.toLowerCase()); + }); + setFilteredResults(filtered); + } else { + setFilteredResults(results); + } + }, [results, searchQuery]); + + const columns = [ + { + title: 'Prompt', + ellipsis: true, + sorter: sortItemsByKey('Prompt'), + render: (record: DatasetGeneration) => { + const question = record?.question || record?.Prompt || record?.prompt; + return <>{question}; + } + }, + { + title: 'Completion', + ellipsis: true, + sorter: sortItemsByKey('Completion'), + render: (record: DatasetGeneration) => { + const solution = record?.solution || record?.Completion || record?.completion; + return <>{solution}; + } + } + ]; + + const onSearch: SearchProps['onSearch'] = (value, _e, info) => { + throttle((value: string) => setSearchQuery(value), 500)(value); + } + + const onChange = (event: SyntheticEvent) => { + const value = event.target?.value; + throttle((value: string) => setSearchQuery(value), 500)(value); + } + + return ( + <> + + + + + + 'hover-pointer'} + rowKey={(_record, index) => `generation-table-${index}`} + pagination={{ + showSizeChanger: true, + showQuickJumper: false, + hideOnSinglePage: true + }} + /> + + + ) +} + +export default CustomGenerationTable; diff --git a/app/client/src/pages/DatasetDetails/DatasetDetailsPage.tsx b/app/client/src/pages/DatasetDetails/DatasetDetailsPage.tsx index cd46217..3ef5175 100644 --- a/app/client/src/pages/DatasetDetails/DatasetDetailsPage.tsx +++ b/app/client/src/pages/DatasetDetails/DatasetDetailsPage.tsx @@ -1,34 +1,293 @@ -import { Flex, Layout, Typography } from "antd"; +import get from 'lodash/get'; +import { Avatar, Button, Card, Col, Divider, Dropdown, Flex, Layout, List, Row, Space, Tabs, TabsProps, Tag, Typography } from "antd"; import styled from "styled-components"; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; -import { useParams } from "react-router-dom"; -import { useGetDataset } from "../Evaluator/hooks"; +import QueryStatsIcon from '@mui/icons-material/QueryStats'; +import { Link, useParams } from "react-router-dom"; +import { useGetDatasetDetails } from "./hooks"; +import Loading from "../Evaluator/Loading"; +import { nextStepsList } from './constants'; +import { getModelProvider, getUsecaseType, getWorkflowType } from '../DataGenerator/constants'; +import { useState } from 'react'; +import ConfigurationTab, { TagsContainer } from './ConfigurationTab'; +import DatasetGenerationTab from './DatasetGenerationTab'; +import { + ArrowLeftOutlined, + DownOutlined, + FolderViewOutlined, + ThunderboltOutlined +} from '@ant-design/icons'; +import { Pages } from '../../types'; +import isEmpty from 'lodash/isEmpty'; +import { getFilesURL } from '../Evaluator/util'; const { Content } = Layout; const { Title } = Typography; -const StyleContent = styled(Content)` + +const StyledHeader = styled.div` + height: 28px; + flex-grow: 0; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + font-size: 24px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.4; + letter-spacing: normal; + text-align: left; +`; + +const StyledLabel = styled.div` + margin-bottom: 4px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-weight: 500; + margin-bottom: 4px; + display: block; + font-size: 14px; + color: #5a656d; +`; + +const StyledContent = styled(Content)` + // background-color: #ffffff; margin: 24px; + .ant-table { + overflow-y: scroll; + } `; +const StyledValue = styled.div` + // color: #1b2329; + color: #5a656d; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-size: 12px; + font-variant: tabular-nums; + line-height: 1.4285; + list-style: none; + font-feature-settings: 'tnum'; +`; + +const StyledPageHeader = styled.div` + height: 28px; + align-self: stretch; + flex-grow: 0; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-size: 20px; + // font-weight: 600; + font-stretch: normal; + font-style: normal; + line-height: 1.4; + letter-spacing: normal; + text-align: left; + color: rgba(0, 0, 0, 0.88); +`; + +const StyledButton = styled(Button)` + padding-left: 0; +` + +enum ViewType { + CONFIURATION = 'configuration', + GENERATION = 'generation', +} + const DatasetDetailsPage: React.FC = () => { const { generate_file_name } = useParams(); - const { dataset, prompt, examples } = useGetDataset(generate_file_name as string); - console.log('DatasetDetailsPage > dataset', dataset); + const [tabViewType, setTabViewType] = useState(ViewType.GENERATION); + const { data, error, isLoading } = useGetDatasetDetails(generate_file_name as string); + const dataset = get(data, 'dataset'); + const datasetDetails = get(data, 'datasetDetails'); + const total_count = get(dataset, 'total_count', []); + + if (isLoading) { + return ( + + + + ); + } + + const items: TabsProps['items'] = [ + { + key: ViewType.GENERATION, + label: 'Generation', + children: , + }, + { + key: ViewType.CONFIURATION, + label: 'Parameter & Examples', + children: , + }, + ]; + + const menuActions: MenuProps['items'] = [ + { + key: 'view-in-preview', + label: ( + + View in Preview + + ), + icon: , + }, + { + key: 'generate-dataset', + label: ( + + Generate Dataset + + ), + icon: , + }, + { + key: 'evaluate-dataset', + label: ( + + Evaluate Dataset + + ), + icon: , + } + ]; + + + const onTabChange = (key: string) => + setTabViewType(key as ViewType); + return ( - - - <Flex align='center' gap={10}> - <CheckCircleIcon style={{ color: '#178718' }}/> - {'Success'} + <StyledContent> + <Row> + <Col sm={24}> + <StyledButton type="link" onClick={() => window.history.back()} style={{ color: '#1677ff' }} icon={<ArrowLeftOutlined />}> + Back to Home + </StyledButton> + </Col> + </Row> + <Row style={{ marginBottom: '16px', marginTop: '16px' }}> + <Col sm={20}> + <StyledPageHeader>{dataset?.display_name}</StyledPageHeader> + </Col> + <Col sm={4}> + <Flex style={{ flexDirection: 'row-reverse' }}> + <Dropdown menu={{ items: menuActions }}> + <Button onClick={(e) => e.preventDefault()}> + <Space> + Actions + <DownOutlined /> + </Space> + </Button> + </Dropdown> </Flex> - - + + + + + + + + Model ID + {dataset?.model_id} + + + + + Model Provider + {getModelProvider(dataset?.inference_type)} + + + + + + + Workflow + {getWorkflowType(dataset?.technique)} + + + + + Template + {getUsecaseType(dataset?.use_case)} + + + + {dataset?.technique === 'sft' && !isEmpty(dataset?.doc_paths) && ( + + + + Files + {/* {dataset?.custom_prompt} */} + + + {dataset?.doc_paths?.map((file: string) => ( + +
+ {file} +
+
+ ))} +
+
+
+ + + + Input Key + {dataset?.input_key} + + + + + Output Value + {dataset?.output_value} + + + + )} + + + + Total Dataset Size + {total_count} + + + + +
+
+ + + + + + +
+
+ + {'Next Steps'} + ( + + } + title={item.title} + description={item.description} + /> + + )} + /> + ); diff --git a/app/client/src/pages/DatasetDetails/DatasetGenerationTab.tsx b/app/client/src/pages/DatasetDetails/DatasetGenerationTab.tsx new file mode 100644 index 0000000..9341cf2 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/DatasetGenerationTab.tsx @@ -0,0 +1,47 @@ +import { Col, Row } from 'antd'; +import get from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; +import styled from 'styled-components'; +import { Dataset } from '../Evaluator/types'; +import CustomGenerationTable from './CustomGenerationTable'; +import DatasetGenerationTopics from './DatasetGenerationTopics'; +import { CustomResult } from "../DataGenerator/types"; +import { DatasetDetails, DatasetGeneration } from '../Home/types'; + + + +interface Props { + dataset: Dataset; + datasetDetails: DatasetDetails; +} + +const Container = styled.div` + padding: 16px; + background-color: #ffffff; +`; + + + +const DatasetGenerationTab: React.FC = ({ dataset, datasetDetails }) => { + console.log(`DatasetGenerationTab > dataset`, dataset); + console.log(` datasetDetails`, datasetDetails); + const topics = get(dataset, 'topics', []); + console.log(` topics`, topics); + const hasCustomSeeds = !Array.isArray(datasetDetails?.generation) || isEmpty(topics) || topics !== null; + console.log(` hasCustomSeeds`, hasCustomSeeds); + + return ( + + +
+ {hasCustomSeeds && } + {!hasCustomSeeds && } + + + + + ); +} + +export default DatasetGenerationTab; + diff --git a/app/client/src/pages/DatasetDetails/DatasetGenerationTopics.tsx b/app/client/src/pages/DatasetDetails/DatasetGenerationTopics.tsx new file mode 100644 index 0000000..3d5d529 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/DatasetGenerationTopics.tsx @@ -0,0 +1,87 @@ +import get from 'lodash/get'; +import { Card, Table, Tabs, Typography } from "antd"; +import { DatasetGeneration } from "../Home/types"; +import TopicGenerationTable from "./TopicGenerationTable"; +import isEmpty from "lodash/isEmpty"; +import styled from "styled-components"; +import { Dataset } from '../Evaluator/types'; + +interface Props { + data: DatasetGeneration; + dataset: Dataset; +} + +const StyledTable = styled(Table)` + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-table-thead > tr > th { + color: #5a656d; + border-bottom: 1px solid #eaebec; + font-weight: 500; + text-align: left; + // background: #ffffff; + border-bottom: 1px solid #eaebec; + transition: background 0.3s ease; + } + .ant-table-row { + cursor: pointer; + } + .ant-table-row > td.ant-table-cell { + padding: 8px; + padding-left: 16px; + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-typography { + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + } + } +`; + +const TabsContainer = styled(Card)` + .ant-card-body { + padding: 0; + } + margin: 20px 0px 35px; +`; + +const getTopicTree = (data: DatasetGeneration, topics: string[]) => { + const topicTree = {}; + if (!isEmpty(data)) { + topics.forEach(topic => { + topicTree[topic] = data.filter(result => get(result, 'Seeds') === topic); + }); + } + return topicTree; +} + + +const DatasetGenerationTable: React.FC = ({ data, dataset }) => { + const topics = get(dataset, 'topics', []); + const topicTree = getTopicTree(data, topics); + + let topicTabs = []; + if (!isEmpty(topics)) { + topicTabs = topicTree && Object.keys(topicTree).map((topic, i) => ({ + key: `${topic}-${i}`, + label: {topic}, + value: topic, + children: + })); + } + + return ( + <> + {!isEmpty(topicTabs) && + ( + + + + ) + } + + ); +} + +export default DatasetGenerationTable; \ No newline at end of file diff --git a/app/client/src/pages/DatasetDetails/ExampleModal.tsx b/app/client/src/pages/DatasetDetails/ExampleModal.tsx new file mode 100644 index 0000000..8443537 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/ExampleModal.tsx @@ -0,0 +1,54 @@ +import { Flex, Form, Typography } from 'antd'; +import styled from 'styled-components'; + +import Markdown from '../../components/Markdown'; +import TooltipIcon from '../../components/TooltipIcon'; + + +const { Title } = Typography; + +interface Props { + question: string; + solution: string; +} + + +const Container = styled(Flex)` + margin-top: 15px +` + +const StyledTitle = styled(Title)` + margin-top: 0; + margin-bottom: 0 !important; +`; +const TitleGroup = styled(Flex)` + margin-top: 10px; + margin-bottom: 10px; +`; + +const ExampleModal: React.FC = ({ question, solution }) => { + return ( + + {question && ( +
+ + {'Prompt'} + + + +
+ )} + {solution && ( +
+ + {'Completion'} + + + +
+ )} +
+ ) +} + +export default ExampleModal; \ No newline at end of file diff --git a/app/client/src/pages/DatasetDetails/ExamplesSection.tsx b/app/client/src/pages/DatasetDetails/ExamplesSection.tsx new file mode 100644 index 0000000..aaf5d52 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/ExamplesSection.tsx @@ -0,0 +1,151 @@ +import { Collapse, Descriptions, Flex, Modal, Table, Typography } from "antd"; +import styled from "styled-components"; +import Markdown from "../../Markdown"; +import { DatasetResponse } from "../../../api/Datasets/response"; +import { QuestionSolution } from "../../../pages/DataGenerator/types"; +import { MODEL_PARAMETER_LABELS, ModelParameters, Usecases } from "../../../types"; +import { Dataset } from "../../../pages/Evaluator/types"; +import PCModalContent from "../../../pages/DataGenerator/PCModalContent"; + +import ExampleModal from "./ExampleModal"; + +const { Text, Title } = Typography; +const Panel = Collapse.Panel; + + +const StyledTable = styled(Table)` + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-table-thead > tr > th { + color: #5a656d; + border-bottom: 1px solid #eaebec; + font-weight: 500; + text-align: left; + // background: #ffffff; + border-bottom: 1px solid #eaebec; + transition: background 0.3s ease; + } + .ant-table-row { + cursor: pointer; + } + .ant-table-row > td.ant-table-cell { + padding: 8px; + padding-left: 16px; + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-typography { + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + } + } +`; + +const MarkdownWrapper = styled.div` + border: 1px solid #d9d9d9; + border-radius: 6px; + padding: 4px 11px; +`; + +const StyledLabel = styled.div` + font-size: 16px; + padding-top: 8px; +`; + +const StyledCollapse = styled(Collapse)` + .ant-collapse-content > .ant-collapse-content-box { + padding: 0; + } + .ant-collapse-item > .ant-collapse-header .ant-collapse-expand-icon { + height: 28px; + display: flex; + align-items: center; + padding-inline-end: 12px; + } +`; + +const Label = styled.div` + font-size: 18px; + padding-top: 8px; +`; + +export type DatasetDetailProps = { + datasetDetails: DatasetResponse | Dataset; +} + +const ExamplesSection= ({ datasetDetails }: DatasetDetailProps) => { + + const exampleCols = [ + { + title: 'Prompts', + dataIndex: 'prompts', + ellipsis: true, + render: (_text: QuestionSolution, record: QuestionSolution) => <>{record.question} + }, + { + title: 'Completions', + dataIndex: 'completions', + ellipsis: true, + render: (_text: QuestionSolution, record: QuestionSolution) => <>{record.solution} + }, + ] + + return ( + + + Examples} + style={{ padding: 0 }} + > + + ({ + onClick: () => Modal.info({ + title: 'View Details', + content: , + icon: undefined, + maskClosable: false, + width: 1000 + }) + })} + rowKey={(_record, index) => `summary-examples-table-${index}`} + /> + + {/* Model Parameters + ({ + label: MODEL_PARAMETER_LABELS[modelParameterKey as ModelParameters], + children: datasetDetails.model_parameters[modelParameterKey as ModelParameters], + })) + : []}> + + {(datasetDetails.schema && datasetDetails.use_case === Usecases.TEXT2SQL) && ( +
+ {'DB Schema'} + + + +
+ )} */} + +
+
+
+ ) +} + +export default ExamplesSection; \ No newline at end of file diff --git a/app/client/src/pages/DatasetDetails/TopicGenerationTable.tsx b/app/client/src/pages/DatasetDetails/TopicGenerationTable.tsx new file mode 100644 index 0000000..3626f55 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/TopicGenerationTable.tsx @@ -0,0 +1,91 @@ +import React, { SyntheticEvent, useEffect } from 'react'; + +import { Col, Input, Row, Table } from 'antd'; +import { CustomResult } from '../DataGenerator/types'; +import { DatasetGeneration } from '../Home/types'; +import throttle from 'lodash/throttle'; +import { SearchProps } from 'antd/es/input'; +import { sortItemsByKey } from '../../utils/sortutils'; + +const { Search } = Input; + + +interface Props { + results: DatasetGeneration[] +} + + +const TopicGenerationTable: React.FC = ({ results }) => { + const [searchQuery, setSearchQuery] = React.useState(null); + const [filteredResults, setFilteredResults] = React.useState(results || []); + + useEffect(() => { + if (searchQuery) { + const filtered = results.filter((result: DatasetGeneration) => { + // clean up the filter logic + return result?.Prompt?.toLowerCase().includes(searchQuery.toLowerCase()) || result?.Completion?.toLowerCase().includes(searchQuery.toLowerCase()); + }); + setFilteredResults(filtered); + } else { + setFilteredResults(results); + } + }, [results, searchQuery]); + + const columns = [ + { + title: 'Prompt', + key: 'Prompt', + dataIndex: 'Prompt', + ellipsis: true, + sorter: sortItemsByKey('Prompt'), + render: (prompt: string) => { + return <>{prompt} + } + }, + { + title: 'Completion', + key: 'Completion', + dataIndex: 'Completion', + ellipsis: true, + sorter: sortItemsByKey('Completion'), + render: (completion: string) => <>{completion} + } + ]; + + const onSearch: SearchProps['onSearch'] = (value, _e, info) => { + throttle((value: string) => setSearchQuery(value), 500)(value); + } + + const onChange = (event: SyntheticEvent) => { + const value = event.target?.value; + throttle((value: string) => setSearchQuery(value), 500)(value); + } + + return ( + <> + +
+ + + +
'hover-pointer'} + rowKey={(_record, index) => `topic-generation-table-${index}`} + pagination={{ + showSizeChanger: true, + showQuickJumper: false, + hideOnSinglePage: true + }} + /> + + + ) +} + +export default TopicGenerationTable; \ No newline at end of file diff --git a/app/client/src/pages/DatasetDetails/constants.tsx b/app/client/src/pages/DatasetDetails/constants.tsx new file mode 100644 index 0000000..4faa3b4 --- /dev/null +++ b/app/client/src/pages/DatasetDetails/constants.tsx @@ -0,0 +1,27 @@ +import { HomeOutlined, PageviewOutlined } from '@mui/icons-material'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle' +import GradingIcon from '@mui/icons-material/Grading'; +import ModelTrainingIcon from '@mui/icons-material/ModelTraining'; + + +export const nextStepsList = [ + { + avatar: '', + title: 'Review Dataset', + description: 'Review your dataset to ensure it properly fits your usecase.', + icon: + }, + { + avatar: '', + title: 'Evaluate Dataset', + description: 'Use an LLM as a judge to evaluate and score your dataset.', + icon: , + }, + { + avatar: '', + title: 'Fine Tuning Studio', + description: 'Bring your dataset to Fine Tuning Studio AMP to start fine tuning your models in Cloudera AI Workbench.', + icon: , + }, +] \ No newline at end of file diff --git a/app/client/src/pages/DatasetDetails/hooks.tsx b/app/client/src/pages/DatasetDetails/hooks.tsx index e69de29..a403ae8 100644 --- a/app/client/src/pages/DatasetDetails/hooks.tsx +++ b/app/client/src/pages/DatasetDetails/hooks.tsx @@ -0,0 +1,63 @@ +import get from 'lodash/get'; +import { notification } from 'antd'; +import { useQuery } from 'react-query'; + + +const BASE_API_URL = import.meta.env.VITE_AMP_URL; + + +const { + VITE_WORKBENCH_URL, + VITE_PROJECT_OWNER, + VITE_CDSW_PROJECT +} = import.meta.env + + + +const fetchDatasetDetails = async (generate_file_name: string) => { + const dataset_details__resp = await fetch(`${BASE_API_URL}/dataset_details/${generate_file_name}`, { + method: 'GET', + }); + const datasetDetails = await dataset_details__resp.json(); + const dataset__resp = await fetch(`${BASE_API_URL}/generations/${generate_file_name}`, { + method: 'GET', + }); + const dataset = await dataset__resp.json(); + + return { + dataset, + datasetDetails + }; +}; + + + + +export const useGetDatasetDetails = (generate_file_name: string) => { + const { data, isLoading, isError, error } = useQuery( + ["data", fetchDatasetDetails], + () => fetchDatasetDetails(generate_file_name), + { + keepPreviousData: true, + }, + ); + + const dataset = get(data, 'dataset'); + console.log('data:', data); + console.log('error:', error); + + if (error) { + notification.error({ + message: 'Error', + description: `An error occurred while fetching the dataset details:\n ${error}` + }); + } + + return { + data, + dataset, + isLoading, + isError, + error + }; +} \ No newline at end of file diff --git a/app/client/src/pages/EvaluationDetails/EvaluationConfigurationTab.tsx b/app/client/src/pages/EvaluationDetails/EvaluationConfigurationTab.tsx new file mode 100644 index 0000000..c445348 --- /dev/null +++ b/app/client/src/pages/EvaluationDetails/EvaluationConfigurationTab.tsx @@ -0,0 +1,157 @@ +import { Badge, Col, Flex, Modal, Row, Table, Typography } from "antd"; +import { Evaluation } from "../Evaluator/types"; +import styled from "styled-components"; +import { QuestionSolution } from "../DataGenerator/types"; +import isEmpty from "lodash/isEmpty"; +import ExampleModal from "../DatasetDetails/ExampleModal"; +import { getColorCode } from "../Evaluator/util"; + +const { Text } = Typography; + +interface Props { + evaluation: Evaluation; + evaluationDetails: EvaluationDetails; +} + +const Container = styled.div` + padding: 16px; + background-color: #ffffff; +`; + +const StyledTitle = styled.div` + margin-bottom: 4px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-size: 16px; + font-weight: 500; + margin-left: 4px; + +`; + +const StyledTable = styled(Table)` + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-table-thead > tr > th { + color: #5a656d; + border-bottom: 1px solid #eaebec; + font-weight: 500; + text-align: left; + // background: #ffffff; + border-bottom: 1px solid #eaebec; + transition: background 0.3s ease; + } + .ant-table-row { + cursor: pointer; + } + .ant-table-row > td.ant-table-cell { + padding: 8px; + padding-left: 16px; + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-typography { + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + } + } +`; + + +const EvaluationConfigurationTab: React.FC = ({ evaluation }) => { + + const exampleColummns = [ + { + title: 'Justification', + dataIndex: 'justification', + ellipsis: true, + width: '70%', + render: (justification: string) => <>{justification} + + }, + { + title: 'Score', + dataIndex: 'score', + ellipsis: true, + width: '20%', + render: (score: number) => <> + } + ] + + const parameterColummns = [ + { + title: 'Temperature', + dataIndex: 'temperature', + ellipsis: true, + render: (temperature: number) => <>{temperature} + }, + { + title: 'Top K', + dataIndex: 'top_k', + ellipsis: true, + render: (top_k: number) => <>{top_k} + }, + { + title: 'Top P', + dataIndex: 'top_p', + ellipsis: true, + render: (top_p: number) => <>{top_p} + }, + + ]; + + return ( + + + + + Custom Prompt + + {evaluation?.custom_prompt} + + + + + + + + Examples + ({ + onClick: () => Modal.info({ + title: 'View Details', + content: , + icon: undefined, + maskClosable: false, + width: 1000 + }) + })} + rowKey={(_record, index) => `summary-examples-table-${index}`} + /> + + + + + + + Parameters + `parameters-table-${index}`} + /> + + + + + ); +} + +export default EvaluationConfigurationTab; \ No newline at end of file diff --git a/app/client/src/pages/EvaluationDetails/EvaluationDetailsPage.tsx b/app/client/src/pages/EvaluationDetails/EvaluationDetailsPage.tsx new file mode 100644 index 0000000..588c05d --- /dev/null +++ b/app/client/src/pages/EvaluationDetails/EvaluationDetailsPage.tsx @@ -0,0 +1,187 @@ +import get from 'lodash/get'; +import { useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { Badge, Button, Card, Col, Flex, Layout, Row, Tabs, TabsProps } from 'antd'; +import styled from 'styled-components'; +import { + ArrowLeftOutlined + } from '@ant-design/icons'; +import { useGetEvaluationDetails } from './hooks'; +import Loading from '../Evaluator/Loading'; +import { getModelProvider, getUsecaseType } from '../DataGenerator/constants'; +import { getColorCode } from '../Evaluator/util'; +import { Evaluation } from '../Evaluator/types'; +import EvaluationConfigurationTab from './EvaluationConfigurationTab'; +import EvaluationGenerationTab from './EvaluationGenerationTab'; + + +const { Content } = Layout; + +const StyledContent = styled(Content)` + // background-color: #ffffff; + margin: 24px; + .ant-table { + overflow-y: scroll; + } +`; + +const StyledLabel = styled.div` + margin-bottom: 4px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-weight: 500; + margin-bottom: 4px; + display: block; + font-size: 14px; + color: #5a656d; +`; + +const StyledValue = styled.div` + // color: #1b2329; + color: #5a656d; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-size: 12px; + font-variant: tabular-nums; + line-height: 1.4285; + list-style: none; + font-feature-settings: 'tnum'; +`; + + +const StyledPageHeader = styled.div` + height: 28px; + align-self: stretch; + flex-grow: 0; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + font-size: 20px; + // font-weight: 600; + font-stretch: normal; + font-style: normal; + line-height: 1.4; + letter-spacing: normal; + text-align: left; + color: rgba(0, 0, 0, 0.88); +`; + +const StyledButton = styled(Button)` + padding-left: 0; +` + +enum ViewType { + CONFIURATION = 'configuration', + GENERATION = 'generation', + } + + + +const EvaluationDetailsPage: React.FC = () => { + const { evaluate_file_name } = useParams(); + const [tabViewType, setTabViewType] = useState(ViewType.GENERATION); + const { data, error, isLoading } = useGetEvaluationDetails(evaluate_file_name as string); + console.log('----------------------------data:', data, error, isLoading); + const evaluation = get(data, 'evaluation'); + const evaluationDetails = get(data, 'evaluationDetails'); + const dataset = get(data, 'dataset'); + + if (isLoading) { + return ( + + + + ); + } + + + const items: TabsProps['items'] = [ + { + key: ViewType.GENERATION, + label: 'Generation', + children: , + }, + { + key: ViewType.CONFIURATION, + label: 'Parameter & Examples', + children: , + }, + ]; + console.log('tabViewType', tabViewType); + + + const onTabChange = (key: string) => + setTabViewType(key as ViewType); + + return ( + + + + + window.history.back()} style={{ color: '#1677ff' }} icon={}> + Back to Home + + + + + + {evaluation?.display_name} + + {/* + + + + + + */} + + + + + + + Model ID + {evaluation?.model_id} + + + + + Model Provider + {getModelProvider(evaluation?.inference_type)} + + + + + + + Average Score + + + + + + + + Template + {getUsecaseType(evaluation?.use_case)} + + + + +
+
+ + + + + + + + ); +}; + +export default EvaluationDetailsPage; \ No newline at end of file diff --git a/app/client/src/pages/EvaluationDetails/EvaluationGenerationTab.tsx b/app/client/src/pages/EvaluationDetails/EvaluationGenerationTab.tsx new file mode 100644 index 0000000..f6140d0 --- /dev/null +++ b/app/client/src/pages/EvaluationDetails/EvaluationGenerationTab.tsx @@ -0,0 +1,64 @@ +import get from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; +import { Col, Row, Tabs } from 'antd'; +import { Dataset, Evaluation, EvaluationDetails } from '../Evaluator/types'; +import styled from 'styled-components'; +import { getTopicMap } from '../Evaluator/util'; +import EvaluateTopicTable from '../Evaluator/EvaluateTopicTable'; + + +interface Props { + dataset: Dataset + evaluation: Evaluation; + evaluationDetails: EvaluationDetails; +} + +const Container = styled.div` + padding: 16px; + background-color: #ffffff; +`; + +const EvaluationGenerationTab: React.FC = ({ dataset, evaluation, evaluationDetails }) => { + console.log('--------EvaluationGenerationTab', evaluation, evaluationDetails); + const result = get(evaluationDetails, 'evaluation'); + console.log('result', result); + + + let topicTabs: any[] = []; + const { topics, topicMap } = getTopicMap({ result }); + console.log('topicMap', topicMap); + console.log('topics', topics); + if (dataset.topics !== null && !isEmpty(dataset.topics)) { + topicTabs = topics.map((topicName: string, index: number) => ({ + key: `${topicName}-${index}`, + label: topicName, + value: topicName, + children: + })); + } + + console.log('topicTabs', topicTabs); + if (isEmpty(topicTabs)) { + const values = Object.values(topicMap); + return ( + + + + + + ); + } + + + return ( + + + + + + + + ); +} + +export default EvaluationGenerationTab; \ No newline at end of file diff --git a/app/client/src/pages/EvaluationDetails/hooks.ts b/app/client/src/pages/EvaluationDetails/hooks.ts new file mode 100644 index 0000000..4492376 --- /dev/null +++ b/app/client/src/pages/EvaluationDetails/hooks.ts @@ -0,0 +1,59 @@ +import get from 'lodash/get'; +import { notification } from 'antd'; +import { useQuery } from 'react-query'; + + +const BASE_API_URL = import.meta.env.VITE_AMP_URL; + + + +const fetchEvaluationDetails = async (evaluate_file_name: string) => { + const evaluation__resp = await fetch(`${BASE_API_URL}/evaluations/${evaluate_file_name}`, { + method: 'GET', + }); + const evaluation = await evaluation__resp.json(); + + const dataset__resp = await fetch(`${BASE_API_URL}/generations/${evaluation.generate_file_name}`, { + method: 'GET', + }); + const dataset = await dataset__resp.json(); + + const evaluation_details__resp = await fetch(`${BASE_API_URL}/dataset_details/${evaluate_file_name}`, { + method: 'GET', + }); + const evaluationDetails = await evaluation_details__resp.json(); + + return { + dataset, + evaluation, + evaluationDetails + }; + }; + + export const useGetEvaluationDetails = (generate_file_name: string) => { + const { data, isLoading, isError, error } = useQuery( + ["data", fetchEvaluationDetails], + () => fetchEvaluationDetails(generate_file_name), + { + keepPreviousData: true, + }, + ); + + // const dataset = get(data, 'dataset'); + console.log('data:', data); + console.log('error:', error); + + if (error) { + notification.error({ + message: 'Error', + description: `An error occurred while fetching the dataset details:\n ${error}` + }); + } + + return { + data, + isLoading, + isError, + error + }; + } \ No newline at end of file diff --git a/app/client/src/pages/Evaluator/EvaluateTopicTable.tsx b/app/client/src/pages/Evaluator/EvaluateTopicTable.tsx index a0d072b..f86e2dc 100644 --- a/app/client/src/pages/Evaluator/EvaluateTopicTable.tsx +++ b/app/client/src/pages/Evaluator/EvaluateTopicTable.tsx @@ -103,11 +103,11 @@ const EvaluateTopicTable: React.FC = ({ data, topic, topicResult }) => { return ( <> - +
Average Score
-
{average_score}
+
diff --git a/app/client/src/pages/Evaluator/EvaluatorPage.tsx b/app/client/src/pages/Evaluator/EvaluatorPage.tsx index aee2052..0b8a003 100644 --- a/app/client/src/pages/Evaluator/EvaluatorPage.tsx +++ b/app/client/src/pages/Evaluator/EvaluatorPage.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; import { useMutation } from 'react-query'; import { useParams } from 'react-router-dom'; import { ModelParameters } from '../../types'; -import { Form, FormInstance } from 'antd'; +import { Button, Form, FormInstance, Result } from 'antd'; import { useGetDataset, useModels } from './hooks'; import EvaluateDataset from './EvaluateDataset'; import EvaluatorSuccess from './EvaluatorSuccess'; @@ -18,7 +18,7 @@ const EvaluatorPage: React.FC = () => { const [form] = Form.useForm(); const { generate_file_name } = useParams(); const [viewType, setViewType] = useState(ViewType.EVALUATE_F0RM) - const [ ,setErrorMessage] = useState(null); + const [ errorMessage, setErrorMessage] = useState(null); const [loading, setLoading] = useState(false); const [evaluateResult, setEvaluateResult] = useState(null); const { dataset, prompt, examples } = useGetDataset(generate_file_name as string); @@ -88,7 +88,7 @@ const onSubmit = async () => { const resp = await evaluateDataset(formData); console.log('resp', resp); if (!isEmpty(resp.status) && resp.status === 'failed') { - setErrorMessage(resp.error); + setErrorMessage(resp.error || resp.message); } setLoading(false); if (resp.output_path || resp.job_name) { @@ -101,6 +101,23 @@ const onSubmit = async () => { setLoading(false); } } + + if (errorMessage) { + return ( + <> + + {'Start Over'} + + } + /> + + ) + } return ( <> diff --git a/app/client/src/pages/Evaluator/EvaluatorSuccess.tsx b/app/client/src/pages/Evaluator/EvaluatorSuccess.tsx index 3a7ad17..06ece55 100644 --- a/app/client/src/pages/Evaluator/EvaluatorSuccess.tsx +++ b/app/client/src/pages/Evaluator/EvaluatorSuccess.tsx @@ -117,7 +117,7 @@ const EvaluatorSuccess: React.FC = ({ result, dataset, demo }) => { diff --git a/app/client/src/pages/Evaluator/ReevaluatorPage.tsx b/app/client/src/pages/Evaluator/ReevaluatorPage.tsx index 5eee54e..5351eda 100644 --- a/app/client/src/pages/Evaluator/ReevaluatorPage.tsx +++ b/app/client/src/pages/Evaluator/ReevaluatorPage.tsx @@ -1,6 +1,6 @@ import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; -import { Form, FormInstance } from "antd"; +import { Button, Form, FormInstance, Result } from "antd"; import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { useGetEvaluate, useModels } from "./hooks"; @@ -15,7 +15,7 @@ const ReevaluatorPage: React.FC = () => { const { evaluate_file_name } = useParams(); const [viewType, setViewType] = useState(ViewType.REEVALUATE_F0RM); const [loading, setLoading] = useState(false); - const [ ,setErrorMessage] = useState(null); + const [ errorMessage, setErrorMessage] = useState(null); const [evaluateResult, setEvaluateResult] = useState(null); const { evaluate, @@ -85,7 +85,7 @@ const ReevaluatorPage: React.FC = () => { setLoading(true); const resp = await evaluateDataset(formData); if (!isEmpty(resp.status) && resp.status === 'failed') { - setErrorMessage(resp.error); + setErrorMessage(resp.error || resp.message); } setLoading(false); if (resp.output_path) { @@ -99,6 +99,23 @@ const ReevaluatorPage: React.FC = () => { } } + if (errorMessage) { + return ( + <> + + {'Start Over'} + + } + /> + + ) + } + return ( <> {viewType === ViewType.REEVALUATE_F0RM && diff --git a/app/client/src/pages/Evaluator/SeedEvaluateTable.tsx b/app/client/src/pages/Evaluator/SeedEvaluateTable.tsx index b272f79..1e45847 100644 --- a/app/client/src/pages/Evaluator/SeedEvaluateTable.tsx +++ b/app/client/src/pages/Evaluator/SeedEvaluateTable.tsx @@ -54,7 +54,7 @@ const SeedEvaluateTable: React.FC = ({ results }) => { title: 'Score', key: 'score', dataIndex: 'score', - width: 20, + width: 100, render: (score: number) => { return <> } diff --git a/app/client/src/pages/Evaluator/hooks.ts b/app/client/src/pages/Evaluator/hooks.ts index 576e953..8a650ef 100644 --- a/app/client/src/pages/Evaluator/hooks.ts +++ b/app/client/src/pages/Evaluator/hooks.ts @@ -129,8 +129,8 @@ export const useGetEvaluate = (evaluate_file_name: string) => { const evaluate = get(data, 'evaluate'); const dataset = get(data, 'dataset'); - const prompt = get(data, 'prompt'); - const examples = get(data, 'examples'); + const prompt = get(evaluate, 'prompt') || get(evaluate, 'custom_prompt'); + const examples = get(evaluate, 'examples'); return { data, diff --git a/app/client/src/pages/Evaluator/types.ts b/app/client/src/pages/Evaluator/types.ts index 686585a..74d698f 100644 --- a/app/client/src/pages/Evaluator/types.ts +++ b/app/client/src/pages/Evaluator/types.ts @@ -85,4 +85,8 @@ export enum ViewType { EVALUATE_F0RM = 'EVALUATE_F0RM', REEVALUATE_F0RM = 'REEVALUATE_F0RM', SUCCESS_VIEW = 'SUCCESS_VIEW' +} + +export interface EvaluationDetails { + evaluation: EvaluateResult; } \ No newline at end of file diff --git a/app/client/src/pages/Evaluator/util.ts b/app/client/src/pages/Evaluator/util.ts index 701c955..4875207 100644 --- a/app/client/src/pages/Evaluator/util.ts +++ b/app/client/src/pages/Evaluator/util.ts @@ -206,6 +206,7 @@ export const EVALUATION_RESULT = { export const getTopicMap = (evaluateResult: EvaluateResult) => { + console.log('---evaluateResult', evaluateResult); const result = get(evaluateResult, 'result'); let topicMap = {}; const topics = []; diff --git a/app/client/src/pages/Home/DatasetActions.tsx b/app/client/src/pages/Home/DatasetActions.tsx index 30320ed..28e373e 100644 --- a/app/client/src/pages/Home/DatasetActions.tsx +++ b/app/client/src/pages/Home/DatasetActions.tsx @@ -76,12 +76,16 @@ const DatasetActions: React.FC = ({ dataset, refetch, setTo const menuActions: MenuProps['items'] = [ { key: '1', - label: ( - + // label: ( + // + // View Dataset Details + // + // ), + label: + View Dataset Details - - ), - onClick: () => setShowModal(true), + , + // onClick: () => setShowModal(true), icon: }, { diff --git a/app/client/src/pages/Home/EvaluateActions.tsx b/app/client/src/pages/Home/EvaluateActions.tsx index e8c8e27..0aeb1bd 100644 --- a/app/client/src/pages/Home/EvaluateActions.tsx +++ b/app/client/src/pages/Home/EvaluateActions.tsx @@ -69,11 +69,10 @@ const EvaluationActions: React.FC = ({ evaluation, refetch }) => { { key: '1', label: ( - + View Evaluation Details - + ), - onClick: () => setShowModal(true), icon: }, { diff --git a/app/client/src/pages/Home/UpgradeButton.tsx b/app/client/src/pages/Home/UpgradeButton.tsx new file mode 100644 index 0000000..e0da02e --- /dev/null +++ b/app/client/src/pages/Home/UpgradeButton.tsx @@ -0,0 +1,74 @@ +import isEmpty from 'lodash/isEmpty'; +import { Alert, Button, Flex, Modal, Spin, Typography } from 'antd'; +import React, { useEffect } from 'react'; +import { useUpgradeStatus, useUpgradeSynthesisStudio } from './hooks'; +import { LoadingOutlined } from '@ant-design/icons'; + +const { Text } = Typography; + + +const UpgradeButton: React.FC = () => { + const [showModal, setShowModal] = React.useState(false); + const [loading, setLoading] = React.useState(false); + const [enableUpgrade, setEnableUpgrade] = React.useState(false); + const { data, isError } = useUpgradeStatus(); + const { upgradeStudio, isLoading } = useUpgradeSynthesisStudio(); + console.log("Upgrade data", data); + + useEffect(() => { + if (!isEmpty(data)) { + setEnableUpgrade(data?.updates_available); + } + },[data, isLoading, isError]); + + const onClick = () => setShowModal(true); + + + if (!enableUpgrade) { + return null; + } + console.log("Upgrade available", enableUpgrade); + + const onFinish = async () => { + // logic to handle upgrade + upgradeStudio(); + setLoading(true); + } + + return ( + <> + {enableUpgrade && } + {showModal && ( + setShowModal(false)} + onOk={() => onFinish()} + okButtonProps={{disabled: loading}} + width={550}> + + {`Are you sure you want to upgrade Synthesis Studio?`} + + + {loading && + + } fullscreen /> + } + + )} + + + ) + +} + +export default UpgradeButton; diff --git a/app/client/src/pages/Home/hooks.ts b/app/client/src/pages/Home/hooks.ts index 15860a5..f791a37 100644 --- a/app/client/src/pages/Home/hooks.ts +++ b/app/client/src/pages/Home/hooks.ts @@ -1,6 +1,7 @@ +import { notification } from 'antd'; import isEmpty from 'lodash/isEmpty'; import { useState } from 'react'; -import { useQuery } from 'react-query'; +import { useMutation, useQuery } from 'react-query'; const BASE_API_URL = import.meta.env.VITE_AMP_URL; @@ -93,4 +94,60 @@ export const useEvaluations = () => { searchQuery, setSearchQuery }; -} \ No newline at end of file +} + +const fetchUpgradeStatus = async () => { + const upgrade_resp = await fetch(`${BASE_API_URL}/synthesis-studio/check-upgrade`, { + method: 'GET', + }); + const upgradeStatus = await upgrade_resp.json(); + return upgradeStatus; +} + +export const useUpgradeStatus = () => { + const { data, isLoading, isError, refetch } = useQuery( + ["fetchUpgradeStatus", fetchUpgradeStatus], + () => fetchUpgradeStatus(), + { + keepPreviousData: false, + refetchInterval: 30000 + }, + ); + + return { + data, + isLoading, + isError, + refetch + }; +} + +const upgradeSynthesisStudio = async () => { + const upgrade_resp = await fetch(`${BASE_API_URL}/synthesis-studio/upgrade`, { + method: 'POST', + }); + const body = await upgrade_resp.json(); + console.log('upgradeSynthesisStudio', body); + return body; +}; + +export const useUpgradeSynthesisStudio = () => { + const mutation = useMutation({ + mutationFn: upgradeSynthesisStudio + }); + + if (mutation.isError) { + notification.error({ + message: 'Error', + description: `An error occurred while starting the upgrade action.\n` + }); + } + + return { + upgradeStudio: mutation.mutate, + fetching: mutation.isLoading, + error: mutation.error, + isError: mutation.isError, + data: mutation.data + }; +} diff --git a/app/client/src/pages/Home/types.ts b/app/client/src/pages/Home/types.ts index b30dc16..d5c31b2 100644 --- a/app/client/src/pages/Home/types.ts +++ b/app/client/src/pages/Home/types.ts @@ -23,4 +23,12 @@ export interface Evaluation { job_id: string; job_name: string; job_status: string; +} + +export interface DatasetDetails { + generation: DatasetGeneration; +} + +export interface DatasetGeneration { + [key: string]: string; } \ No newline at end of file diff --git a/app/client/src/routes.tsx b/app/client/src/routes.tsx index cac2e23..21e1719 100644 --- a/app/client/src/routes.tsx +++ b/app/client/src/routes.tsx @@ -8,6 +8,7 @@ import ReevaluatorPage from "./pages/Evaluator/ReevaluatorPage"; import DatasetDetailsPage from "./pages/DatasetDetails/DatasetDetailsPage"; import WelcomePage from "./pages/Home/WelcomePage"; import ErrorPage from "./pages/ErrorPage"; +import EvaluationDetailsPage from "./pages/EvaluationDetails/EvaluationDetailsPage"; const router = createBrowserRouter([ @@ -49,6 +50,12 @@ const router = createBrowserRouter([ errorElement: , loader: async () => null }, + { + path: `evaluation/:evaluate_file_name`, + element: , + errorElement: , + loader: async () => null + }, { path: `welcome`, element: , diff --git a/app/client/src/types.ts b/app/client/src/types.ts index 4f4ff69..04b3c40 100644 --- a/app/client/src/types.ts +++ b/app/client/src/types.ts @@ -5,7 +5,8 @@ export enum Pages { HOME = 'home', DATASETS = 'datasets', WELCOME = 'welcome', - FEEDBACK = 'feedback' + FEEDBACK = 'feedback', + UPGRADE = 'upgrade' } export enum ModelParameters { diff --git a/docs/guides/evaluation_workflow.md b/docs/guides/evaluation_workflow.md new file mode 100644 index 0000000..a6396b4 --- /dev/null +++ b/docs/guides/evaluation_workflow.md @@ -0,0 +1,100 @@ +# Supervised Finetuning Workflow: + +In this workflow we will see how we can evaluate synthetic data generated in previous steps using Large Language Model as a judge. + +User can Trigger evaluation via List view where they can chose evaluation to begin from the dropdown. + + + + +## Evaluation Workflow + +Similar to generation evaluation also allows users to specify following + +1. #### Display Name +2. #### Model Provider: AWS Bedrock or Cloudera AI Inference +3. #### Model ID: Calude , LLAMA , Mistral etc. + +The code generation and text2sql are templates which allow users to select from already curated prompts, and examples to evaluate datasets. + +Custom template on the other hand allows users to define everything from scratch to evaluate created synthteic dataset from previous step. + +The screen shot for the same can be seen below: + + + +### Prompt and Model Parameters + +#### Prompt: +This step allows user to curate their prompts manuallly, or chose from given templates or let LLM curate a prompt based on their description of use case. + +```json +{ +"""Below is a Python coding Question and Solution pair generated by an LLM. Evaluate its quality as a Senior Developer would, considering its suitability for professional use. Use the additive 5-point scoring system described below. + +Points are accumulated based on the satisfaction of each criterion: + 1. Add 1 point if the code implements basic functionality and solves the core problem, even if it includes some minor issues or non-optimal approaches. + 2. Add another point if the implementation is generally correct but lacks refinement in style or fails to follow some best practices. It might use inconsistent naming conventions or have occasional inefficiencies. + 3. Award a third point if the code is appropriate for professional use and accurately implements the required functionality. It demonstrates good understanding of Python concepts and common patterns, though it may not be optimal. It resembles the work of a competent developer but may have room for improvement in efficiency or organization. + 4. Grant a fourth point if the code is highly efficient and follows Python best practices, exhibiting consistent style and appropriate documentation. It could be similar to the work of an experienced developer, offering robust error handling, proper type hints, and effective use of built-in features. The result is maintainable, well-structured, and valuable for production use. + 5. Bestow a fifth point if the code is outstanding, demonstrating mastery of Python and software engineering principles. It includes comprehensive error handling, efficient algorithms, proper testing considerations, and excellent documentation. The solution is scalable, performant, and shows attention to edge cases and security considerations.""" +} +``` + + +#### Model Parameters + +We let user decide on following model Parameters: + +- **Temperature** +- **TopK** +- **TopP** + + + +### Examples: + +In the next step user can specify examples they would want to give for their evaluation of dataset so that LLM can follow same format and Judge/rate datasets accordingly. + +The examples for evaluation would be like following: + +The **scoring** and **Justification** can be defined by user within the prompt and example for example in this use case we use a 5 point rating system, user can make it **10 point ratings**, **Boolean** , **subjective ("bad", "Good", "Average")** etc. + +```json +{ + "score": 3, + "justification": """The code achieves 3 points by implementing core functionality correctly (1), + showing generally correct implementation with proper syntax (2), + and being suitable for professional use with good Python patterns and accurate functionality (3). + While it demonstrates competent development practices, it lacks the robust error handling + and type hints needed for point 4, and could benefit from better efficiency optimization and code organization.""" + }, + { + "score": 4, + "justification": """ + The code earns 4 points by implementing basic functionality (1), showing correct implementation (2), + being production-ready (3), and demonstrating high efficiency with Python best practices + including proper error handling, type hints, and clear documentation (4). + It exhibits experienced developer qualities with well-structured code and maintainable design, though + it lacks the comprehensive testing and security considerations needed for a perfect score.""" +} +``` + + + +### Final Output: + +Finally user can see how their output looks like with corresponding Scores and Justifications. + +The output will be saved in Project File System within Cloudera environment. + + + +The output and corresponding metadata (scores,model etc.) can be seen on the **Evaluations** list view as well as shown in screen shot below. + + + + + + + diff --git a/docs/guides/screenshots/evaluate_home_page.png b/docs/guides/screenshots/evaluate_home_page.png new file mode 100644 index 0000000..501d105 Binary files /dev/null and b/docs/guides/screenshots/evaluate_home_page.png differ diff --git a/docs/guides/screenshots/evaluate_list.png b/docs/guides/screenshots/evaluate_list.png new file mode 100644 index 0000000..caf1635 Binary files /dev/null and b/docs/guides/screenshots/evaluate_list.png differ diff --git a/docs/guides/screenshots/evaluate_output.png b/docs/guides/screenshots/evaluate_output.png new file mode 100644 index 0000000..655898c Binary files /dev/null and b/docs/guides/screenshots/evaluate_output.png differ diff --git a/docs/guides/screenshots/evaluation_sds.png b/docs/guides/screenshots/evaluation_sds.png new file mode 100644 index 0000000..4167cbc Binary files /dev/null and b/docs/guides/screenshots/evaluation_sds.png differ diff --git a/docs/guides/screenshots/export_list.png b/docs/guides/screenshots/export_list.png new file mode 100644 index 0000000..6e0d079 Binary files /dev/null and b/docs/guides/screenshots/export_list.png differ diff --git a/docs/guides/screenshots/sds_examples.png b/docs/guides/screenshots/sds_examples.png new file mode 100644 index 0000000..cc84918 Binary files /dev/null and b/docs/guides/screenshots/sds_examples.png differ diff --git a/docs/guides/screenshots/sds_export.png b/docs/guides/screenshots/sds_export.png new file mode 100644 index 0000000..5ecc272 Binary files /dev/null and b/docs/guides/screenshots/sds_export.png differ diff --git a/docs/guides/screenshots/sds_generation.png b/docs/guides/screenshots/sds_generation.png new file mode 100644 index 0000000..6f3aaa1 Binary files /dev/null and b/docs/guides/screenshots/sds_generation.png differ diff --git a/docs/guides/screenshots/sds_hf_export.png b/docs/guides/screenshots/sds_hf_export.png new file mode 100644 index 0000000..825c381 Binary files /dev/null and b/docs/guides/screenshots/sds_hf_export.png differ diff --git a/docs/guides/screenshots/sds_home_page.png b/docs/guides/screenshots/sds_home_page.png new file mode 100644 index 0000000..c5215c5 Binary files /dev/null and b/docs/guides/screenshots/sds_home_page.png differ diff --git a/docs/guides/screenshots/sds_output.png b/docs/guides/screenshots/sds_output.png new file mode 100644 index 0000000..42dfc2f Binary files /dev/null and b/docs/guides/screenshots/sds_output.png differ diff --git a/docs/guides/screenshots/sds_prompt.png b/docs/guides/screenshots/sds_prompt.png new file mode 100644 index 0000000..9712460 Binary files /dev/null and b/docs/guides/screenshots/sds_prompt.png differ diff --git a/docs/guides/screenshots/sds_summary.png b/docs/guides/screenshots/sds_summary.png new file mode 100644 index 0000000..9632a7e Binary files /dev/null and b/docs/guides/screenshots/sds_summary.png differ diff --git a/docs/guides/sft_workflow.md b/docs/guides/sft_workflow.md new file mode 100644 index 0000000..c360d28 --- /dev/null +++ b/docs/guides/sft_workflow.md @@ -0,0 +1,116 @@ +# Generation Workflow: + +In this workflow we will see how we can create synthetic data for finetuning our models. The users in this workflow can chose from provided templates like. + +## Templates + +1. **Code Generation** +2. **Text to SQL** +3. **Custom** + +The code generation and text2sql are templates which allow users to select from already curated prompts, seeds(more on it below) and examples to produce datasets. + +Custom template on the other hand allows users to define everything from scratch and create synthteic dataset for their custom Enterprise use cases. + +## Workflow Example: Code Generation + +### Home Page +On home Page user can click on Create Datasets to Get Started + + + +### Generate Configuration: In the next step user gets to specify following fields: + +1. #### Display Name +2. #### Model Provider: AWS Bedrock or Cloudera AI Inference +3. #### Model ID: Calude , LLAMA , Mistral etc. +4. #### Workflow + a. Supervised Finetuning :- Generate Prompt and Completion Pairs with or without documents(pdfs, docs, txt etc.) + b. Custom Data Curation:- Use Input as json array(which can be uploaded) from the user and generate response based on that. In this case user can have their own inputs, instructions and get customised generated output for corresponding input. +5. #### Files: Input Files user can chose from their project file system for above workflows + + + +### Prompt and Model Parameters + +#### Prompt: +This step allows user to curate their prompts manuallly, or chose from given templates or let LLM curate a prompt based on their description of use case. + +```json +{ +"""Write a programming question-pair for the following topic: + +Requirements: +- Each solution must include working code examples +- Include explanations with the code +- Follow the same format as the examples +- Ensure code is properly formatted with appropriate indentation""" +} +``` + +#### Seeds: + +This helps LLM diversify dataset user wants to generate. We drew inspiration from **[Self Intruct Paper](https://huggingface.co/papers/2212.10560)** + , where 175 hand crafted human seed instructions were used to diversify curation of dataset. + + For example, for code generation, seeds can be: +- **Algorithms for Operation Research** +- **Web Development with Flask** +- **PyTorch for Reinforcement Learning** + +Similarly for language translation, seeds can be: +- **Poems** +- **Greetings in Formal Communication** +- **Haikus** + +#### Model Parameters + +We let user decide on following model Parameters: + +- **Temperature** +- **TopK** +- **TopP** + +#### Dataset Size + + + +### Examples: + +In the next step user can specify examples they would want to give for their synthetic dataset generation so that LLM can follow same format and create datasets accordingly. + +The examples for code geneartion would be like following: + +```json +{ + "question": "How do you read a CSV file into a pandas DataFrame?", + "solution": """You can use pandas.read_csv(). Here's an example + + import pandas as pd + df = pd.read_csv('data.csv') + print(df.head()) + print(df.info()) +""" +} +``` + + + + +### Summary: + +This allows user to finally look at prompt, seeds, dataset size and other parameters they have selected for data generation. + + + +### Final Output: + +Finally user can see how their output looks like with corresponding Prompts and Completions. + +The output will be saved in Project File System within Cloudera environment. + + + + + +The output and corresponding metadata (scores,model etc.) can be seen on the **Generations** list view as well as shown in screen shot below. diff --git a/docs/technical_overview.md b/docs/technical_overview.md index 7c7027c..abf9bdf 100644 --- a/docs/technical_overview.md +++ b/docs/technical_overview.md @@ -165,7 +165,7 @@ df = pd.read_csv('data.csv')\n """}] -Write a programming question-pair for the following topic: +Write a programming question-answer pair for the following topic: Requirements: - Each solution must include working code examples - Include explanations with the code