mirror of
https://github.com/Canner/WrenAI.git
synced 2026-10-29 17:51:48 +08:00
feat(wren-ui): Integrating suggested questios API (#73)
* feat(wren-ui): integrating suggested questios API * fix(wren-ui): fix suggested questions types in resolver --------- Co-authored-by: shimin <shimin@shimins-MacBook-Pro.local>
This commit is contained in:
@@ -14,11 +14,6 @@ export type Scalars = {
|
||||
JSON: any;
|
||||
};
|
||||
|
||||
export type AskQuestionResponse = {
|
||||
__typename?: 'AskQuestionResponse';
|
||||
questions: Array<Maybe<Scalars['String']>>;
|
||||
};
|
||||
|
||||
export type AskingTask = {
|
||||
__typename?: 'AskingTask';
|
||||
candidates: Array<ResultCandidate>;
|
||||
@@ -399,7 +394,6 @@ export type PreviewDataInput = {
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
askQuestions: AskQuestionResponse;
|
||||
askingTask: AskingTask;
|
||||
autoGenerateRelation?: Maybe<Array<RecommandRelations>>;
|
||||
diagram: Diagram;
|
||||
@@ -408,6 +402,7 @@ export type Query = {
|
||||
model: DetailedModel;
|
||||
modelSync?: Maybe<ModelSyncResponse>;
|
||||
onboardingStatus: OnboardingStatusResponse;
|
||||
suggestedQuestions: SuggestedQuestionResponse;
|
||||
thread: DetailedThread;
|
||||
threadResponse: ThreadResponse;
|
||||
threads: Array<Thread>;
|
||||
@@ -501,6 +496,17 @@ export type SimpleMeasureInput = {
|
||||
type: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SuggestedQuestion = {
|
||||
__typename?: 'SuggestedQuestion';
|
||||
label: Scalars['String'];
|
||||
question: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SuggestedQuestionResponse = {
|
||||
__typename?: 'SuggestedQuestionResponse';
|
||||
questions: Array<Maybe<SuggestedQuestion>>;
|
||||
};
|
||||
|
||||
export type Task = {
|
||||
__typename?: 'Task';
|
||||
id: Scalars['String'];
|
||||
|
||||
@@ -7,6 +7,11 @@ export type CommonErrorFragment = { __typename?: 'Error', code?: string | null,
|
||||
|
||||
export type CommonResponseFragment = { __typename?: 'ThreadResponse', id: number, question: string, status: Types.AskingTaskStatus, detail?: { __typename?: 'ThreadResponseDetail', sql?: string | null, description?: string | null, steps: Array<{ __typename?: 'DetailStep', summary: string, sql: string, cteName?: string | null }> } | null };
|
||||
|
||||
export type SuggestedQuestionsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type SuggestedQuestionsQuery = { __typename?: 'Query', suggestedQuestions: { __typename?: 'SuggestedQuestionResponse', questions: Array<{ __typename?: 'SuggestedQuestion', label: string, question: string } | null> } };
|
||||
|
||||
export type AskingTaskQueryVariables = Types.Exact<{
|
||||
taskId: Types.Scalars['String'];
|
||||
}>;
|
||||
@@ -101,6 +106,43 @@ export const CommonResponseFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const SuggestedQuestionsDocument = gql`
|
||||
query SuggestedQuestions {
|
||||
suggestedQuestions {
|
||||
questions {
|
||||
label
|
||||
question
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useSuggestedQuestionsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSuggestedQuestionsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSuggestedQuestionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSuggestedQuestionsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSuggestedQuestionsQuery(baseOptions?: Apollo.QueryHookOptions<SuggestedQuestionsQuery, SuggestedQuestionsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<SuggestedQuestionsQuery, SuggestedQuestionsQueryVariables>(SuggestedQuestionsDocument, options);
|
||||
}
|
||||
export function useSuggestedQuestionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SuggestedQuestionsQuery, SuggestedQuestionsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<SuggestedQuestionsQuery, SuggestedQuestionsQueryVariables>(SuggestedQuestionsDocument, options);
|
||||
}
|
||||
export type SuggestedQuestionsQueryHookResult = ReturnType<typeof useSuggestedQuestionsQuery>;
|
||||
export type SuggestedQuestionsLazyQueryHookResult = ReturnType<typeof useSuggestedQuestionsLazyQuery>;
|
||||
export type SuggestedQuestionsQueryResult = Apollo.QueryResult<SuggestedQuestionsQuery, SuggestedQuestionsQueryVariables>;
|
||||
export const AskingTaskDocument = gql`
|
||||
query AskingTask($taskId: String!) {
|
||||
askingTask(taskId: $taskId) {
|
||||
|
||||
@@ -26,6 +26,17 @@ const COMMON_RESPONSE = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const SUGGESTED_QUESTIONS = gql`
|
||||
query SuggestedQuestions {
|
||||
suggestedQuestions {
|
||||
questions {
|
||||
label
|
||||
question
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ASKING_TASK = gql`
|
||||
query AskingTask($taskId: String!) {
|
||||
askingTask(taskId: $taskId) {
|
||||
|
||||
@@ -64,16 +64,16 @@ export const sampleDatasets: Record<string, SampleDataset> = {
|
||||
],
|
||||
questions: [
|
||||
{
|
||||
question: 'What are the top 5 selling albums in the US',
|
||||
label: 'Ranking ',
|
||||
question: 'What are the top 5 selling albums in the US?',
|
||||
label: 'Ranking',
|
||||
},
|
||||
{
|
||||
question: 'What is the total revenue generated from each genre',
|
||||
question: 'What is the total revenue generated from each genre?',
|
||||
label: 'Aggregation',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'Which customers have made purchases of tracks from albums in each genre',
|
||||
'Which customers have made purchases of tracks from albums in each genre?',
|
||||
label: 'General',
|
||||
},
|
||||
],
|
||||
@@ -160,17 +160,17 @@ export const sampleDatasets: Record<string, SampleDataset> = {
|
||||
questions: [
|
||||
{
|
||||
question:
|
||||
'What are the top 3 value for orders placed by customers in each city',
|
||||
'What are the top 3 value for orders placed by customers in each city?',
|
||||
label: 'Ranking',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'What is the average score of reviews submitted for orders placed by customers in each city',
|
||||
'What is the average score of reviews submitted for orders placed by customers in each city?',
|
||||
label: 'Aggregation',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'What is the total value of payments made by customers from each state',
|
||||
'What is the total value of payments made by customers from each state?',
|
||||
label: 'Aggregation',
|
||||
},
|
||||
],
|
||||
@@ -241,17 +241,17 @@ export const sampleDatasets: Record<string, SampleDataset> = {
|
||||
questions: [
|
||||
{
|
||||
question:
|
||||
'How many three-pointers were made by each player in each game',
|
||||
'How many three-pointers were made by each player in each game?',
|
||||
label: 'Aggregation',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'What is the differences in turnover rates between teams with high and low average scores',
|
||||
'What is the differences in turnover rates between teams with high and low average scores?',
|
||||
label: 'Comparison',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'Which teams had the highest average points scored per game throughout the season',
|
||||
'Which teams had the highest average points scored per game throughout the season?',
|
||||
label: 'Ranking',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -9,14 +9,19 @@ import { IContext } from '../types';
|
||||
import { getLogger } from '@server/utils';
|
||||
import { format } from 'sql-formatter';
|
||||
import { constructCteSql } from '../services/askingService';
|
||||
import { SampleDatasetName, getSampleAskQuestions } from '../data';
|
||||
import {
|
||||
SuggestedQuestion,
|
||||
SampleDatasetName,
|
||||
getSampleAskQuestions,
|
||||
} from '../data';
|
||||
|
||||
const logger = getLogger('AskingResolver');
|
||||
logger.level = 'debug';
|
||||
|
||||
export interface AskQuestion {
|
||||
questions: string[];
|
||||
export interface SuggestedQuestionResponse {
|
||||
questions: SuggestedQuestion[];
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
}
|
||||
@@ -57,7 +62,7 @@ export class AskingResolver {
|
||||
_root: any,
|
||||
_args: any,
|
||||
ctx: IContext,
|
||||
): Promise<AskQuestion> {
|
||||
): Promise<SuggestedQuestionResponse> {
|
||||
const project = await ctx.projectService.getCurrentProject();
|
||||
const { sampleDataset } = project;
|
||||
if (!sampleDataset) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { makeIterable } from '@/utils/iteration';
|
||||
import EllipsisWrapper from '@/components/EllipsisWrapper';
|
||||
|
||||
const DemoBlock = styled.div`
|
||||
user-select: none;
|
||||
height: 150px;
|
||||
&:hover {
|
||||
border-color: var(--geekblue-6) !important;
|
||||
transition: border-color ease 0.2s;
|
||||
@@ -15,17 +17,17 @@ interface Props {
|
||||
onSelect: (data: any) => void;
|
||||
}
|
||||
|
||||
const DemoTemplate = ({ title, summary, onSelect }) => {
|
||||
const DemoTemplate = ({ label, question, onSelect }) => {
|
||||
return (
|
||||
<Col span={8}>
|
||||
<DemoBlock
|
||||
className="border border-gray-5 rounded px-3 pt-3 pb-4 cursor-pointer"
|
||||
onClick={() => onSelect({ title, summary })}
|
||||
onClick={() => onSelect({ label, question })}
|
||||
>
|
||||
<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 className="border border-gray-5 px-2 rounded-pill">{label}</div>
|
||||
</div>
|
||||
<EllipsisWrapper multipleLine={3} text={summary} />
|
||||
<EllipsisWrapper multipleLine={4} text={question} />
|
||||
</DemoBlock>
|
||||
</Col>
|
||||
);
|
||||
|
||||
@@ -45,6 +45,8 @@ const StyledCollapse = styled(Collapse)`
|
||||
`;
|
||||
|
||||
const ResultBlock = styled.div`
|
||||
user-select: none;
|
||||
height: 129px;
|
||||
&:hover {
|
||||
border-color: var(--geekblue-6) !important;
|
||||
transition: border-color ease 0.2s;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import { Input, Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { PROCESS_STATE } from '@/utils/enum';
|
||||
@@ -22,6 +29,11 @@ interface Props {
|
||||
data?: AskingTask;
|
||||
}
|
||||
|
||||
interface Attributes {
|
||||
setValue: (value: string) => void;
|
||||
submit: () => void;
|
||||
}
|
||||
|
||||
const PromptStyle = styled.div`
|
||||
position: fixed;
|
||||
width: 768px;
|
||||
@@ -49,7 +61,7 @@ const convertAskingTaskToProcessState = (data: AskingTask) => {
|
||||
return processState;
|
||||
};
|
||||
|
||||
export default function Prompt(props: Props) {
|
||||
export default forwardRef<Attributes, Props>(function Prompt(props, ref) {
|
||||
const $promptInput = useRef<HTMLTextAreaElement>(null);
|
||||
const { data, onSubmit, onStop, onSelect } = props;
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
@@ -102,16 +114,24 @@ export default function Prompt(props: Props) {
|
||||
const inputEnter = (event) => {
|
||||
if (event.shiftKey) return;
|
||||
event.preventDefault();
|
||||
|
||||
if (!isProcessing) submitAsk();
|
||||
submitAsk();
|
||||
};
|
||||
|
||||
const submitAsk = async () => {
|
||||
if (question) {
|
||||
onSubmit && (await onSubmit(question));
|
||||
}
|
||||
if (isProcessing || !question) return;
|
||||
|
||||
onSubmit && (await onSubmit(question));
|
||||
};
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
setValue: (value: string) => setInputValue(value),
|
||||
submit: submitAsk,
|
||||
}),
|
||||
[question, isProcessing, setInputValue],
|
||||
);
|
||||
|
||||
return (
|
||||
<PromptStyle className="d-flex align-end bg-gray-2 p-3 border border-gray-3 rounded">
|
||||
<Input.TextArea
|
||||
@@ -144,4 +164,4 @@ export default function Prompt(props: Props) {
|
||||
/>
|
||||
</PromptStyle>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,42 +1,41 @@
|
||||
import { ComponentRef, useMemo, useRef } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Image from 'next/image';
|
||||
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';
|
||||
import useHomeSidebar from '@/hooks/useHomeSidebar';
|
||||
import useAskPrompt from '@/hooks/useAskPrompt';
|
||||
import { useCreateThreadMutation } from '@/apollo/client/graphql/home.generated';
|
||||
|
||||
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.",
|
||||
},
|
||||
];
|
||||
import {
|
||||
useSuggestedQuestionsQuery,
|
||||
useCreateThreadMutation,
|
||||
} from '@/apollo/client/graphql/home.generated';
|
||||
|
||||
export default function Home() {
|
||||
const $prompt = useRef<ComponentRef<typeof Prompt>>(null);
|
||||
const router = useRouter();
|
||||
const homeSidebar = useHomeSidebar();
|
||||
const askPrompt = useAskPrompt();
|
||||
|
||||
const { data: suggestedQuestionsData } = useSuggestedQuestionsQuery();
|
||||
const [createThread] = useCreateThreadMutation({
|
||||
onCompleted: () => homeSidebar.refetch(),
|
||||
});
|
||||
|
||||
const isDemo = true;
|
||||
const sampleQuestions = useMemo(
|
||||
() => suggestedQuestionsData?.suggestedQuestions.questions || [],
|
||||
[suggestedQuestionsData],
|
||||
);
|
||||
|
||||
const onDemoSelect = () => {};
|
||||
const isSampleDataset = sampleQuestions.length > 0;
|
||||
|
||||
const onDemoSelect = async ({ question }) => {
|
||||
$prompt.current.setValue(question);
|
||||
await nextTick();
|
||||
$prompt.current.submit();
|
||||
};
|
||||
|
||||
const onSelect = async (payload) => {
|
||||
try {
|
||||
@@ -64,9 +63,12 @@ export default function Home() {
|
||||
Know more about your data
|
||||
</div>
|
||||
|
||||
{isDemo && <DemoPrompt demo={demoData} onSelect={onDemoSelect} />}
|
||||
{isSampleDataset && (
|
||||
<DemoPrompt demo={sampleQuestions} onSelect={onDemoSelect} />
|
||||
)}
|
||||
</div>
|
||||
<Prompt
|
||||
ref={$prompt}
|
||||
data={askPrompt.data}
|
||||
onSubmit={askPrompt.onSubmit}
|
||||
onStop={askPrompt.onStop}
|
||||
|
||||
Reference in New Issue
Block a user