mirror of
https://github.com/Canner/WrenAI.git
synced 2026-10-31 02:57:15 +08:00
feat(wren-ui): add Prompt UI to home & home/[id] page (#20)
* feat(wren-ui): move ask components to pages/home folder * feat(wren-ui): add home page to integrate prompt component & demo prompt result * feat(wren-ui): add prompt to home/[id] page, rename iterated component * fix(wren-ui): block hover style, typo, ask flow --------- Co-authored-by: shimin <shimin@shimins-MacBook-Pro.local>
This commit is contained in:
@@ -1,12 +1,29 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
text?: string;
|
||||
children?: ReactNode;
|
||||
multipleLine?: number;
|
||||
}
|
||||
|
||||
const Wrapper = styled.div<{ multipleLine?: number }>`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${(props) =>
|
||||
props.multipleLine
|
||||
? `
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${props.multipleLine};
|
||||
-webkit-box-orient: vertical;
|
||||
`
|
||||
: `
|
||||
white-space: nowrap;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default function EllipsisWrapper(props: Props) {
|
||||
const { text, children } = props;
|
||||
const { text, multipleLine, children } = props;
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [width, setWidth] = useState(undefined);
|
||||
const hasWidth = width !== undefined;
|
||||
@@ -28,8 +45,13 @@ export default function EllipsisWrapper(props: Props) {
|
||||
const title = Array.isArray(text) ? text.join('') : text;
|
||||
|
||||
return (
|
||||
<div ref={ref} className="text-truncate" title={title} style={{ width }}>
|
||||
<Wrapper
|
||||
ref={ref}
|
||||
title={title}
|
||||
multipleLine={multipleLine}
|
||||
style={{ width }}
|
||||
>
|
||||
{hasWidth ? renderContent() : null}
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@ import { Button, Skeleton, Typography } from 'antd';
|
||||
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import QuestionCircleOutlined from '@ant-design/icons/QuestionCircleOutlined';
|
||||
import StepContent from '@/components/ask/StepContent';
|
||||
import StepContent from '@/components/pages/home/StepContent';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
onOpenSaveAsViewModal: ({ sql: string }) => void;
|
||||
onOpenSaveAsViewModal: (data: { sql: string }) => void;
|
||||
question: string;
|
||||
description: string;
|
||||
answerResultSteps: Array<{
|
||||
+1
-1
@@ -2,7 +2,7 @@ import dynamic from 'next/dynamic';
|
||||
import { Button, Typography } from 'antd';
|
||||
import CopyOutlined from '@ant-design/icons/lib/icons/CopyOutlined';
|
||||
import UpCircleOutlined from '@ant-design/icons/UpCircleOutlined';
|
||||
import PreviewData from '@/components/ask/PreviewData';
|
||||
import PreviewData from '@/components/pages/home/PreviewData';
|
||||
|
||||
const CodeBlock = dynamic(() => import('@/components/editor/CodeBlock'), {
|
||||
ssr: false,
|
||||
+1
-1
@@ -3,7 +3,7 @@ import FunctionOutlined from '@ant-design/icons/FunctionOutlined';
|
||||
import { BinocularsIcon } from '@/utils/icons';
|
||||
import CollapseContent, {
|
||||
Props as CollapseContentProps,
|
||||
} from '@/components/ask/CollapseContent';
|
||||
} from '@/components/pages/home/CollapseContent';
|
||||
import useAnswerStepContent from '@/hooks/useAnswerStepContent';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Row, Col } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { makeIterable } from '@/utils/iteration';
|
||||
import EllipsisWrapper from '@/components/EllipsisWrapper';
|
||||
|
||||
const DemoBlock = styled.div`
|
||||
&:hover {
|
||||
border-color: var(--geekblue-6) !important;
|
||||
transition: border-color ease 0.2s;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
demo: any[];
|
||||
onSelect: (data: any) => void;
|
||||
}
|
||||
|
||||
const DemoTemplate = ({ title, summary, onSelect }) => {
|
||||
return (
|
||||
<Col span={8}>
|
||||
<DemoBlock
|
||||
className="border border-gray-5 rounded px-3 pt-3 pb-4 cursor-pointer"
|
||||
onClick={() => onSelect({ title, summary })}
|
||||
>
|
||||
<div className="d-flex justify-space-between align-center text-sm mb-3">
|
||||
<div className="border border-gray-5 px-2 rounded-pill">{title}</div>
|
||||
</div>
|
||||
<EllipsisWrapper multipleLine={3} text={summary} />
|
||||
</DemoBlock>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
const DemoColumnIterator = makeIterable(DemoTemplate);
|
||||
|
||||
export default function DemoPrompt(props: Props) {
|
||||
const { demo, onSelect } = props;
|
||||
return (
|
||||
<div style={{ width: 580 }}>
|
||||
<div className="text-center mt-3 mb-2">Try asking...</div>
|
||||
<Row gutter={16}>
|
||||
<DemoColumnIterator data={demo} onSelect={onSelect} />
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Row, Col, Button, Collapse } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { PROCESS_STATE } from '@/utils/enum';
|
||||
import { makeIterable } from '@/utils/iteration';
|
||||
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
|
||||
import FunctionOutlined from '@ant-design/icons/FunctionOutlined';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import StopOutlined from '@ant-design/icons/StopFilled';
|
||||
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
|
||||
import WarningOutlined from '@ant-design/icons/WarningOutlined';
|
||||
import CaretRightOutlined from '@ant-design/icons/CaretRightOutlined';
|
||||
import ViewSQLModal from '@/components/pages/home/prompt/ViewSQLModal';
|
||||
import EllipsisWrapper from '@/components/EllipsisWrapper';
|
||||
import useModalAction from '@/hooks/useModalAction';
|
||||
import useAskProcessState from '@/hooks/useAskProcessState';
|
||||
|
||||
const ResultStyle = styled.div`
|
||||
position: absolute;
|
||||
bottom: calc(100% + 12px);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: white;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
`;
|
||||
|
||||
const StyledCollapse = styled(Collapse)`
|
||||
.ant-collapse-item {
|
||||
> .ant-collapse-header {
|
||||
color: var(--gray-7);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
.ant-collapse-arrow {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
> .ant-collapse-content .ant-collapse-content-box {
|
||||
color: var(--gray-7);
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ResultBlock = styled.div`
|
||||
&:hover {
|
||||
border-color: var(--geekblue-6) !important;
|
||||
transition: border-color ease 0.2s;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
processState: ReturnType<typeof useAskProcessState>;
|
||||
data: any[];
|
||||
error?: any;
|
||||
onClose: () => void;
|
||||
onStop: () => void;
|
||||
}
|
||||
|
||||
const ResultTemplate = ({ index, summary, sql, onShowSQL }) => {
|
||||
return (
|
||||
<Col span={8}>
|
||||
<ResultBlock className="border border-gray-5 rounded px-3 pt-3 pb-4 cursor-pointer">
|
||||
<div className="d-flex justify-space-between align-center text-sm mb-3">
|
||||
<div className="border border-gray-5 px-2 rounded-pill">
|
||||
Result {index + 1}
|
||||
</div>
|
||||
<Button
|
||||
className="adm-btn-no-style gray-6 text-sm px-1"
|
||||
type="text"
|
||||
onClick={() => onShowSQL({ sql, summary })}
|
||||
>
|
||||
<FunctionOutlined className="-mr-1" />
|
||||
View SQL
|
||||
</Button>
|
||||
</div>
|
||||
<EllipsisWrapper multipleLine={3} text={summary} />
|
||||
</ResultBlock>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
const ResultColumnIterator = makeIterable(ResultTemplate);
|
||||
|
||||
const makeProcessing = (text: string) => (props: Props) => {
|
||||
const { onStop } = props;
|
||||
return (
|
||||
<div className="d-flex justify-space-between">
|
||||
<span>
|
||||
<LoadingOutlined className="mr-2 geekblue-6 text-lg" spin />
|
||||
{text}
|
||||
</span>
|
||||
<Button
|
||||
className="adm-btn-no-style gray-7 bg-gray-3 text-sm px-2"
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopOutlined className="-mr-1" />
|
||||
Stop
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const makeProcessingError =
|
||||
(config: { icon: ReactNode; title: string; description: string }) =>
|
||||
(props: Props) => {
|
||||
const { onClose, error } = props;
|
||||
const { message } = error || {};
|
||||
return (
|
||||
<div>
|
||||
<div className="d-flex justify-space-between text-medium mb-2">
|
||||
<div className="d-flex align-center">
|
||||
{config.icon}
|
||||
{config.title}
|
||||
</div>
|
||||
<Button
|
||||
className="adm-btn-no-style gray-7 bg-gray-3 text-sm px-2"
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseOutlined className="-mr-1" />
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
<div className="gray-7">{config.description}</div>
|
||||
{message && (
|
||||
<StyledCollapse
|
||||
ghost
|
||||
expandIcon={({ isActive }) => (
|
||||
<CaretRightOutlined rotate={isActive ? 90 : 0} />
|
||||
)}
|
||||
>
|
||||
<Collapse.Panel key="1" header="Show error messages">
|
||||
<pre className="mb-0">{message}</pre>
|
||||
</Collapse.Panel>
|
||||
</StyledCollapse>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorIcon = () => <CloseCircleFilled className="mr-2 red-5 text-lg" />;
|
||||
|
||||
const UnderstandingFailed = makeProcessingError({
|
||||
icon: <ErrorIcon />,
|
||||
title: 'Failed to understand',
|
||||
description:
|
||||
'Sorry, I cannot understand your question. Could you please rephrase your question or provide more context?',
|
||||
});
|
||||
|
||||
const SearchingFailed = makeProcessingError({
|
||||
icon: <ErrorIcon />,
|
||||
title: 'Something went wrong',
|
||||
description:
|
||||
"Sorry, we encountered an error when we're processing your request.",
|
||||
});
|
||||
|
||||
const NoResult = makeProcessingError({
|
||||
icon: <WarningOutlined className="mr-2 text-lg" />,
|
||||
title: 'Please try again',
|
||||
description: 'No results found. Try providing more details in your question.',
|
||||
});
|
||||
|
||||
const Understanding = makeProcessing('Understanding question');
|
||||
const Searching = makeProcessing('Searching data');
|
||||
const Finished = (props: Props) => {
|
||||
const { data, onClose } = props;
|
||||
|
||||
const viewSQLModal = useModalAction();
|
||||
|
||||
const showSQL = (payload: { sql: string; summary: string }) => {
|
||||
viewSQLModal.openModal(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="d-flex justify-space-between mb-3">
|
||||
<div className="d-flex align-center text-medium">
|
||||
<CheckCircleOutlined className="mr-2 green-7 text-lg" /> {data.length}{' '}
|
||||
result(s) found
|
||||
</div>
|
||||
<Button
|
||||
className="adm-btn-no-style gray-7 bg-gray-3 text-sm px-2"
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseOutlined className="-mr-1" />
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
<ResultColumnIterator data={data} onShowSQL={showSQL} />
|
||||
</Row>
|
||||
<ViewSQLModal {...viewSQLModal.state} onClose={viewSQLModal.closeModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getProcessStateComponent = (state: PROCESS_STATE) => {
|
||||
return (
|
||||
{
|
||||
[PROCESS_STATE.UNDERSTANDING]: Understanding,
|
||||
[PROCESS_STATE.SEARCHING]: Searching,
|
||||
[PROCESS_STATE.FINISHED]: Finished,
|
||||
[PROCESS_STATE.UNDERSTANDING_FAILED]: UnderstandingFailed,
|
||||
[PROCESS_STATE.SEARCHING_FAILED]: SearchingFailed,
|
||||
[PROCESS_STATE.NO_RESULT]: NoResult,
|
||||
}[state] || null
|
||||
);
|
||||
};
|
||||
|
||||
export default function PromptResult(props: Props) {
|
||||
const { processState } = props;
|
||||
|
||||
const StateComponent = getProcessStateComponent(processState.currentState);
|
||||
|
||||
if (StateComponent === null) return null;
|
||||
|
||||
return (
|
||||
<ResultStyle className="border border-gray-3 rounded p-4">
|
||||
<StateComponent {...props} />
|
||||
</ResultStyle>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Modal } from 'antd';
|
||||
import CodeBlock from '@/components/editor/CodeBlock';
|
||||
|
||||
export default function ShowSQLModal(props) {
|
||||
const { visible, onClose, defaultValue } = props;
|
||||
const { sql, summary } = defaultValue || {};
|
||||
return (
|
||||
<Modal
|
||||
title="SQL statement"
|
||||
width={560}
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
destroyOnClose
|
||||
centered
|
||||
footer={null}
|
||||
>
|
||||
<div className="mb-3">{summary}</div>
|
||||
<CodeBlock code={sql} showLineNumbers />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Input, Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { PROCESS_STATE } from '@/utils/enum';
|
||||
import PromptResult from '@/components/pages/home/prompt/Result';
|
||||
import useAskProcessState from '@/hooks/useAskProcessState';
|
||||
|
||||
interface Props {
|
||||
onStop: () => void;
|
||||
onSubmit: (value: string) => void;
|
||||
data?: { status: string; result: { sql: string; summary: string }[] };
|
||||
error?: any;
|
||||
}
|
||||
|
||||
const PromptStyle = styled.div`
|
||||
position: fixed;
|
||||
width: 768px;
|
||||
left: 50%;
|
||||
margin-left: calc(-384px + 140px);
|
||||
bottom: 12px;
|
||||
`;
|
||||
|
||||
const PromptButton = styled(Button)`
|
||||
min-width: 72px;
|
||||
`;
|
||||
|
||||
const convertToProcessState = (status: string) => {
|
||||
return {
|
||||
understanding: PROCESS_STATE.UNDERSTANDING,
|
||||
searching: PROCESS_STATE.SEARCHING,
|
||||
finished: PROCESS_STATE.FINISHED,
|
||||
}[status];
|
||||
};
|
||||
|
||||
const convertToErrorState = (status: string) => {
|
||||
return {
|
||||
understanding: PROCESS_STATE.UNDERSTANDING_FAILED,
|
||||
searching: PROCESS_STATE.SEARCHING_FAILED,
|
||||
finished: PROCESS_STATE.NO_RESULT,
|
||||
}[status];
|
||||
};
|
||||
|
||||
export default function Prompt(props: Props) {
|
||||
const { data, error, onSubmit, onStop } = props;
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const askProcessState = useAskProcessState();
|
||||
|
||||
const results = useMemo(() => data.result || [], [data]);
|
||||
const question = useMemo(() => inputValue.trim(), [inputValue]);
|
||||
const isProcessing = useMemo(
|
||||
() =>
|
||||
[PROCESS_STATE.UNDERSTANDING, PROCESS_STATE.SEARCHING].includes(
|
||||
askProcessState.currentState,
|
||||
),
|
||||
[askProcessState.currentState],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const processState = convertToProcessState(data.status);
|
||||
askProcessState.setState(processState);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
// TODO: confirm error state
|
||||
const errorState = convertToErrorState(error.status);
|
||||
askProcessState.setState(errorState);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const closeResult = () => {
|
||||
askProcessState.resetState();
|
||||
};
|
||||
|
||||
const stopProcess = () => {
|
||||
askProcessState.resetState();
|
||||
onStop && onStop();
|
||||
};
|
||||
|
||||
const syncInputValue = (event) => {
|
||||
setInputValue(event.target.value);
|
||||
};
|
||||
|
||||
const inputEnter = (event) => {
|
||||
if (event.shiftKey) return;
|
||||
event.preventDefault();
|
||||
|
||||
if (!isProcessing) submitAsk();
|
||||
};
|
||||
|
||||
const submitAsk = async () => {
|
||||
if (question) {
|
||||
onSubmit && (await onSubmit(question));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PromptStyle className="d-flex align-end bg-gray-2 p-3 border border-gray-3 rounded">
|
||||
<Input.TextArea
|
||||
size="large"
|
||||
autoSize
|
||||
placeholder="Ask to explore your data"
|
||||
value={inputValue}
|
||||
onInput={syncInputValue}
|
||||
onPressEnter={inputEnter}
|
||||
/>
|
||||
<PromptButton
|
||||
type="primary"
|
||||
size="large"
|
||||
className="ml-3"
|
||||
onClick={submitAsk}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
Ask
|
||||
</PromptButton>
|
||||
|
||||
<PromptResult
|
||||
data={results}
|
||||
error={error}
|
||||
processState={askProcessState}
|
||||
onClose={closeResult}
|
||||
onStop={stopProcess}
|
||||
/>
|
||||
</PromptStyle>
|
||||
);
|
||||
}
|
||||
@@ -63,6 +63,8 @@ const SelectModelTemplate: IterableComponent = ({ index, name, fields }) => (
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const SelectModelIterator = makeIterable(SelectModelTemplate);
|
||||
|
||||
export default function CreateModels(props: Props) {
|
||||
const { onBack, onNext, selectedModels, tables } = props;
|
||||
|
||||
@@ -78,8 +80,6 @@ export default function CreateModels(props: Props) {
|
||||
});
|
||||
}, [selectedModels, tables]);
|
||||
|
||||
const SelectModelIterator = makeIterable(SelectModelTemplate);
|
||||
|
||||
const submit = () => {
|
||||
form
|
||||
.validateFields()
|
||||
|
||||
@@ -75,12 +75,12 @@ const SelectRelationTemplate: IterableComponent = ({
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const SelectRelationIterator = makeIterable(SelectRelationTemplate);
|
||||
|
||||
export default function RecommendRelations(props: Props) {
|
||||
const { recommendRelations, onBack, onNext } = props;
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const SelectRelationIterator = makeIterable(SelectRelationTemplate);
|
||||
|
||||
const submit = () => {
|
||||
form
|
||||
.validateFields()
|
||||
|
||||
@@ -68,14 +68,14 @@ const ButtonTemplate = (
|
||||
);
|
||||
};
|
||||
|
||||
const DataSourceIterator = makeIterable(ButtonTemplate);
|
||||
const TemplatesIterator = makeIterable(ButtonTemplate);
|
||||
|
||||
export default function Starter(props) {
|
||||
const { onNext } = props;
|
||||
const dataSources = getDataSources();
|
||||
const templates = getTemplates();
|
||||
|
||||
const DataSourceIterator = makeIterable(ButtonTemplate);
|
||||
const TemplatesIterator = makeIterable(ButtonTemplate);
|
||||
|
||||
const onSelectDataSource = (value: DATA_SOURCES) => {
|
||||
onNext && onNext({ dataSource: value });
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from 'react';
|
||||
import { PROCESS_STATE } from '@/utils/enum';
|
||||
|
||||
export default function useAskProcessState() {
|
||||
const [currentState, setCurrentState] = useState<PROCESS_STATE>(
|
||||
PROCESS_STATE.IDLE,
|
||||
);
|
||||
|
||||
const resetState = () => {
|
||||
setCurrentState(PROCESS_STATE.IDLE);
|
||||
};
|
||||
|
||||
const nextState = () => {
|
||||
setCurrentState(currentState + 1);
|
||||
console.log(currentState);
|
||||
};
|
||||
|
||||
const setState = (state: PROCESS_STATE) => {
|
||||
setCurrentState(state);
|
||||
};
|
||||
|
||||
return {
|
||||
currentState,
|
||||
resetState,
|
||||
nextState,
|
||||
setState,
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import styled from 'styled-components';
|
||||
import { Divider } from 'antd';
|
||||
import { Path } from '@/utils/enum';
|
||||
import { nextTick } from '@/utils/time';
|
||||
import useModalAction from '@/hooks/useModalAction';
|
||||
import SiderLayout from '@/components/layouts/SiderLayout';
|
||||
import AnswerResult from '@/components/ask/AnswerResult';
|
||||
import AnswerResult from '@/components/pages/home/AnswerResult';
|
||||
import SaveAsViewModal from '@/components/modals/SaveAsViewModal';
|
||||
import Prompt from '@/components/pages/home/prompt';
|
||||
|
||||
const AnswerResultsBlock = styled.div`
|
||||
width: 768px;
|
||||
@@ -31,10 +33,30 @@ const AnswerResultsBlock = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const testData = {
|
||||
status: '',
|
||||
result: [
|
||||
{
|
||||
summary: 'Top 10 customer with most order from global customer in 2024',
|
||||
sql: 'SELECT * FROM customer',
|
||||
},
|
||||
{
|
||||
summary: 'Top 10 customer with most order from global customer in 2024',
|
||||
sql: 'SELECT * FROM customer',
|
||||
},
|
||||
{
|
||||
summary: 'Top 10 customer with most order from global customer in 2024',
|
||||
sql: 'SELECT * FROM customer',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function AnswerBlock() {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
const saveAsViewModal = useModalAction();
|
||||
// TODO: adjust when intergrating with API
|
||||
const [simulateData, setSimulateData] = useState(testData);
|
||||
|
||||
// TODO: implement scroll when has new answer result
|
||||
useEffect(() => {
|
||||
@@ -58,7 +80,7 @@ export default function AnswerBlock() {
|
||||
const data = [];
|
||||
|
||||
const onSelect = (selectKeys: string[]) => {
|
||||
router.push(`${Path.ASK}/${selectKeys[0]}`);
|
||||
router.push(`${Path.Home}/${selectKeys[0]}`);
|
||||
};
|
||||
|
||||
// TODO: call API to get real answer results
|
||||
@@ -90,6 +112,23 @@ export default function AnswerBlock() {
|
||||
},
|
||||
];
|
||||
|
||||
const onStop = () => {
|
||||
// TODO: send stop asking API
|
||||
};
|
||||
|
||||
const simulateProcess = async () => {
|
||||
setSimulateData({ ...simulateData, status: 'understanding' });
|
||||
await nextTick(3000);
|
||||
setSimulateData({ ...simulateData, status: 'searching' });
|
||||
await nextTick(3000);
|
||||
setSimulateData({ ...simulateData, status: 'finished' });
|
||||
};
|
||||
|
||||
const onSubmit = async (value) => {
|
||||
console.log(value);
|
||||
await simulateProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<SiderLayout loading={false} sidebar={{ data, onSelect }}>
|
||||
<AnswerResultsBlock className="mt-12 mb-15" ref={divRef}>
|
||||
@@ -114,6 +153,7 @@ export default function AnswerBlock() {
|
||||
console.log('save as view', values);
|
||||
}}
|
||||
/>
|
||||
<Prompt data={simulateData} onSubmit={onSubmit} onStop={onStop} />
|
||||
</SiderLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Path } from '@/utils/enum';
|
||||
import { nextTick } from '@/utils/time';
|
||||
import SiderLayout from '@/components/layouts/SiderLayout';
|
||||
import Prompt from '@/components/pages/home/prompt';
|
||||
import DemoPrompt from '@/components/pages/home/prompt/DemoPrompt';
|
||||
|
||||
const testData = {
|
||||
status: 'searching',
|
||||
result: [
|
||||
{
|
||||
summary: 'Top 10 customer with most order from global customer in 2024',
|
||||
sql: 'SELECT * FROM customer',
|
||||
},
|
||||
{
|
||||
summary: 'Top 10 customer with most order from global customer in 2024',
|
||||
sql: 'SELECT * FROM customer',
|
||||
},
|
||||
{
|
||||
summary: 'Top 10 customer with most order from global customer in 2024',
|
||||
sql: 'SELECT * FROM customer',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const errorData = undefined;
|
||||
// {
|
||||
// message: '',
|
||||
// extensions: {
|
||||
// code: '000',
|
||||
// data: {
|
||||
// status: 'finished',
|
||||
// // message: `Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10\n at Main.main(Main.java:4)`,
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
const demoData = [
|
||||
{
|
||||
title: 'General question',
|
||||
summary: 'Show me top 10 best-selling product last month.',
|
||||
},
|
||||
{
|
||||
title: 'Drill into metrics',
|
||||
summary:
|
||||
'List the name of the users who successfully convert to paying customers last week.',
|
||||
},
|
||||
{
|
||||
title: 'Aggregate data',
|
||||
summary:
|
||||
"Help me categorize customers' ages into groups segmented by every 10 years.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Ask() {
|
||||
const router = useRouter();
|
||||
// TODO: adjust when intergrating with API
|
||||
const [simulateData, setSimulateData] = useState(testData);
|
||||
const data = [];
|
||||
const isDemo = true;
|
||||
|
||||
const onSelect = (selectKeys: string[]) => {
|
||||
router.push(`${Path.Home}/${selectKeys[0]}`);
|
||||
};
|
||||
|
||||
const onDemoSelect = () => {};
|
||||
|
||||
const onStop = () => {
|
||||
// TODO: send stop asking API
|
||||
};
|
||||
|
||||
const simulateProcess = async () => {
|
||||
setSimulateData({ ...simulateData, status: 'understanding' });
|
||||
await nextTick(3000);
|
||||
setSimulateData({ ...simulateData, status: 'searching' });
|
||||
await nextTick(3000);
|
||||
setSimulateData({ ...simulateData, status: 'finished' });
|
||||
};
|
||||
|
||||
const onSubmit = async (value) => {
|
||||
console.log(value);
|
||||
await simulateProcess();
|
||||
};
|
||||
|
||||
return (
|
||||
<SiderLayout loading={false} sidebar={{ data, onSelect }}>
|
||||
<div
|
||||
className="d-flex align-center justify-center flex-column"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<img src="https://picsum.photos/45/45" alt="home-logo" />
|
||||
<div className="text-md text-medium mt-3">
|
||||
Know more about your data
|
||||
</div>
|
||||
|
||||
{isDemo && <DemoPrompt demo={demoData} onSelect={onDemoSelect} />}
|
||||
</div>
|
||||
<Prompt
|
||||
data={simulateData}
|
||||
error={errorData?.extensions.data}
|
||||
onSubmit={onSubmit}
|
||||
onStop={onStop}
|
||||
/>
|
||||
</SiderLayout>
|
||||
);
|
||||
}
|
||||
@@ -14,3 +14,11 @@
|
||||
.button-disabled();
|
||||
}
|
||||
}
|
||||
|
||||
.adm-btn-no-style {
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
line-height: 1.2;
|
||||
height: auto;
|
||||
border: none;
|
||||
}
|
||||
@@ -23,3 +23,7 @@
|
||||
.rounded {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.rounded-pill {
|
||||
border-radius: 999px !important;
|
||||
}
|
||||
@@ -25,6 +25,14 @@
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.text-medium {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.make-text-classes();
|
||||
|
||||
.text-truncate {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum COLLAPSE_CONTENT_TYPE {
|
||||
NONE = 'none',
|
||||
VIEW_SQL = 'view_sql',
|
||||
PREVIEW_DATA = 'preview_data',
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export enum COLLAPSE_CONTENT_TYPE {
|
||||
NONE = 'none',
|
||||
VIEW_SQL = 'view_sql',
|
||||
PREVIEW_DATA = 'preview_data',
|
||||
}
|
||||
|
||||
export enum PROCESS_STATE {
|
||||
IDLE,
|
||||
UNDERSTANDING,
|
||||
SEARCHING,
|
||||
GENERATING,
|
||||
FINISHED,
|
||||
|
||||
UNDERSTANDING_FAILED,
|
||||
SEARCHING_FAILED,
|
||||
NO_RESULT,
|
||||
}
|
||||
@@ -5,4 +5,4 @@ export * from './columnType';
|
||||
export * from './modeling';
|
||||
export * from './path';
|
||||
export * from './diagram';
|
||||
export * from './ask';
|
||||
export * from './home';
|
||||
|
||||
@@ -2,5 +2,5 @@ export enum Path {
|
||||
Exploration = '/exploration',
|
||||
Modeling = '/modeling',
|
||||
Onboarding = '/setup',
|
||||
ASK = '/ask',
|
||||
Home = '/home',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user