diff --git a/wren-ui/src/apollo/client/graphql/__types__.ts b/wren-ui/src/apollo/client/graphql/__types__.ts index d0700288e..a9b55d7ca 100644 --- a/wren-ui/src/apollo/client/graphql/__types__.ts +++ b/wren-ui/src/apollo/client/graphql/__types__.ts @@ -14,6 +14,32 @@ export type Scalars = { JSON: any; }; +export type AskQuestionResponse = { + __typename?: 'AskQuestionResponse'; + questions: Array>; +}; + +export type AskingTask = { + __typename?: 'AskingTask'; + candidates: Array; + error?: Maybe; + status: AskingTaskStatus; +}; + +export type AskingTaskInput = { + question: Scalars['String']; + threadId?: InputMaybe; +}; + +export enum AskingTaskStatus { + FAILED = 'FAILED', + FINISHED = 'FINISHED', + GENERATING = 'GENERATING', + SEARCHING = 'SEARCHING', + STOPPED = 'STOPPED', + UNDERSTANDING = 'UNDERSTANDING' +} + export type CalculatedFieldInput = { diagram?: InputMaybe; expression: Scalars['String']; @@ -59,6 +85,18 @@ export type CreateSimpleMetricInput = { timeGrain: Array; }; +export type CreateThreadInput = { + question: Scalars['String']; + sql: Scalars['String']; + summary: Scalars['String']; +}; + +export type CreateThreadResponseInput = { + question: Scalars['String']; + sql: Scalars['String']; + summary: Scalars['String']; +}; + export type CustomFieldInput = { expression: Scalars['String']; name: Scalars['String']; @@ -80,6 +118,13 @@ export enum DataSourceName { DUCKDB = 'DUCKDB' } +export type DetailStep = { + __typename?: 'DetailStep'; + cteName?: Maybe; + sql: Scalars['String']; + summary: Scalars['String']; +}; + export type DetailedColumn = { __typename?: 'DetailedColumn'; displayName: Scalars['String']; @@ -117,6 +162,14 @@ export type DetailedRelation = { type: RelationType; }; +export type DetailedThread = { + __typename?: 'DetailedThread'; + id: Scalars['Int']; + responses: Array; + sql: Scalars['String']; + summary: Scalars['String']; +}; + export type Diagram = { __typename?: 'Diagram'; models: Array>; @@ -175,6 +228,14 @@ export type DimensionInput = { type: Scalars['String']; }; +export type Error = { + __typename?: 'Error'; + code?: Maybe; + message?: Maybe; + shortMessage?: Maybe; + stacktrace?: Maybe>>; +}; + export type FieldInfo = { __typename?: 'FieldInfo'; displayName: Scalars['String']; @@ -220,14 +281,31 @@ export type ModelWhereInput = { export type Mutation = { __typename?: 'Mutation'; + cancelAskingTask: Scalars['Boolean']; + createAskingTask: Task; createModel: Scalars['JSON']; + createThread: Thread; + createThreadResponse: ThreadResponse; deleteModel: Scalars['Boolean']; + deleteThread: Scalars['Boolean']; deploy: Scalars['JSON']; + previewData: Scalars['JSON']; saveDataSource: DataSource; saveRelations: Scalars['JSON']; saveTables: Scalars['JSON']; startSampleDataset: Scalars['JSON']; updateModel: Scalars['JSON']; + updateThread: Thread; +}; + + +export type MutationCancelAskingTaskArgs = { + taskId: Scalars['String']; +}; + + +export type MutationCreateAskingTaskArgs = { + data: AskingTaskInput; }; @@ -236,11 +314,32 @@ export type MutationCreateModelArgs = { }; +export type MutationCreateThreadArgs = { + data: CreateThreadInput; +}; + + +export type MutationCreateThreadResponseArgs = { + data: CreateThreadResponseInput; + threadId: Scalars['Int']; +}; + + export type MutationDeleteModelArgs = { where: ModelWhereInput; }; +export type MutationDeleteThreadArgs = { + where: ThreadUniqueWhereInput; +}; + + +export type MutationPreviewDataArgs = { + where: PreviewDataInput; +}; + + export type MutationSaveDataSourceArgs = { data: DataSourceInput; }; @@ -266,6 +365,12 @@ export type MutationUpdateModelArgs = { where: ModelWhereInput; }; + +export type MutationUpdateThreadArgs = { + data: UpdateThreadInput; + where: ThreadUniqueWhereInput; +}; + export enum NodeType { CALCULATED_FIELD = 'CALCULATED_FIELD', FIELD = 'FIELD', @@ -287,8 +392,15 @@ export type OnboardingStatusResponse = { status?: Maybe; }; +export type PreviewDataInput = { + responseId: Scalars['Int']; + stepIndex?: InputMaybe; +}; + export type Query = { __typename?: 'Query'; + askQuestions: AskQuestionResponse; + askingTask: AskingTask; autoGenerateRelation?: Maybe>; diagram: Diagram; listDataSourceTables: Array; @@ -296,6 +408,14 @@ export type Query = { model: DetailedModel; modelSync?: Maybe; onboardingStatus: OnboardingStatusResponse; + thread: DetailedThread; + threadResponse: ThreadResponse; + threads: Array; +}; + + +export type QueryAskingTaskArgs = { + taskId: Scalars['String']; }; @@ -303,6 +423,16 @@ export type QueryModelArgs = { where: ModelWhereInput; }; + +export type QueryThreadArgs = { + threadId: Scalars['Int']; +}; + + +export type QueryThreadResponseArgs = { + responseId: Scalars['Int']; +}; + export type RecommandRelations = { __typename?: 'RecommandRelations'; id: Scalars['Int']; @@ -339,6 +469,12 @@ export enum RelationType { ONE_TO_ONE = 'ONE_TO_ONE' } +export type ResultCandidate = { + __typename?: 'ResultCandidate'; + sql: Scalars['String']; + summary: Scalars['String']; +}; + export type SampleDatasetInput = { name: SampleDatasetName; }; @@ -365,6 +501,38 @@ export type SimpleMeasureInput = { type: Scalars['String']; }; +export type Task = { + __typename?: 'Task'; + id: Scalars['String']; +}; + +export type Thread = { + __typename?: 'Thread'; + id: Scalars['Int']; + sql: Scalars['String']; + summary: Scalars['String']; +}; + +export type ThreadResponse = { + __typename?: 'ThreadResponse'; + detail?: Maybe; + error?: Maybe; + id: Scalars['Int']; + question: Scalars['String']; + status: AskingTaskStatus; +}; + +export type ThreadResponseDetail = { + __typename?: 'ThreadResponseDetail'; + description?: Maybe; + sql?: Maybe; + steps: Array; +}; + +export type ThreadUniqueWhereInput = { + id: Scalars['Int']; +}; + export type TimeGrainInput = { dateParts: Array; name: Scalars['String']; @@ -379,3 +547,7 @@ export type UpdateModelInput = { fields: Array; refreshTime?: InputMaybe; }; + +export type UpdateThreadInput = { + summary?: InputMaybe; +}; diff --git a/wren-ui/src/apollo/client/graphql/home.generated.ts b/wren-ui/src/apollo/client/graphql/home.generated.ts new file mode 100644 index 000000000..53c8b3540 --- /dev/null +++ b/wren-ui/src/apollo/client/graphql/home.generated.ts @@ -0,0 +1,470 @@ +import * as Types from './__types__'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type CommonErrorFragment = { __typename?: 'Error', code?: string | null, shortMessage?: string | null, message?: string | null, stacktrace?: Array | 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 AskingTaskQueryVariables = Types.Exact<{ + taskId: Types.Scalars['String']; +}>; + + +export type AskingTaskQuery = { __typename?: 'Query', askingTask: { __typename?: 'AskingTask', status: Types.AskingTaskStatus, candidates: Array<{ __typename?: 'ResultCandidate', sql: string, summary: string }>, error?: { __typename?: 'Error', code?: string | null, shortMessage?: string | null, message?: string | null, stacktrace?: Array | null } | null } }; + +export type ThreadsQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type ThreadsQuery = { __typename?: 'Query', threads: Array<{ __typename?: 'Thread', id: number, sql: string, summary: string }> }; + +export type ThreadQueryVariables = Types.Exact<{ + threadId: Types.Scalars['Int']; +}>; + + +export type ThreadQuery = { __typename?: 'Query', thread: { __typename?: 'DetailedThread', id: number, sql: string, summary: string, responses: Array<{ __typename?: 'ThreadResponse', id: number, question: string, status: Types.AskingTaskStatus, error?: { __typename?: 'Error', code?: string | null, shortMessage?: string | null, message?: string | null, stacktrace?: Array | null } | null, detail?: { __typename?: 'ThreadResponseDetail', sql?: string | null, description?: string | null, steps: Array<{ __typename?: 'DetailStep', summary: string, sql: string, cteName?: string | null }> } | null }> } }; + +export type ThreadResponseQueryVariables = Types.Exact<{ + responseId: Types.Scalars['Int']; +}>; + + +export type ThreadResponseQuery = { __typename?: 'Query', threadResponse: { __typename?: 'ThreadResponse', id: number, question: string, status: Types.AskingTaskStatus, error?: { __typename?: 'Error', code?: string | null, shortMessage?: string | null, message?: string | null, stacktrace?: Array | null } | null, detail?: { __typename?: 'ThreadResponseDetail', sql?: string | null, description?: string | null, steps: Array<{ __typename?: 'DetailStep', summary: string, sql: string, cteName?: string | null }> } | null } }; + +export type CreateAskingTaskMutationVariables = Types.Exact<{ + data: Types.AskingTaskInput; +}>; + + +export type CreateAskingTaskMutation = { __typename?: 'Mutation', createAskingTask: { __typename?: 'Task', id: string } }; + +export type CancelAskingTaskMutationVariables = Types.Exact<{ + taskId: Types.Scalars['String']; +}>; + + +export type CancelAskingTaskMutation = { __typename?: 'Mutation', cancelAskingTask: boolean }; + +export type CreateThreadMutationVariables = Types.Exact<{ + data: Types.CreateThreadInput; +}>; + + +export type CreateThreadMutation = { __typename?: 'Mutation', createThread: { __typename?: 'Thread', id: number, sql: string, summary: string } }; + +export type CreateThreadResponseMutationVariables = Types.Exact<{ + threadId: Types.Scalars['Int']; + data: Types.CreateThreadResponseInput; +}>; + + +export type CreateThreadResponseMutation = { __typename?: 'Mutation', createThreadResponse: { __typename?: 'ThreadResponse', id: number, question: string, status: Types.AskingTaskStatus, error?: { __typename?: 'Error', code?: string | null, shortMessage?: string | null, message?: string | null, stacktrace?: Array | null } | null, detail?: { __typename?: 'ThreadResponseDetail', sql?: string | null, description?: string | null, steps: Array<{ __typename?: 'DetailStep', summary: string, sql: string, cteName?: string | null }> } | null } }; + +export type UpdateThreadMutationVariables = Types.Exact<{ + where: Types.ThreadUniqueWhereInput; + data: Types.UpdateThreadInput; +}>; + + +export type UpdateThreadMutation = { __typename?: 'Mutation', updateThread: { __typename?: 'Thread', id: number, sql: string, summary: string } }; + +export type DeleteThreadMutationVariables = Types.Exact<{ + where: Types.ThreadUniqueWhereInput; +}>; + + +export type DeleteThreadMutation = { __typename?: 'Mutation', deleteThread: boolean }; + +export const CommonErrorFragmentDoc = gql` + fragment CommonError on Error { + code + shortMessage + message + stacktrace +} + `; +export const CommonResponseFragmentDoc = gql` + fragment CommonResponse on ThreadResponse { + id + question + status + detail { + sql + description + steps { + summary + sql + cteName + } + } +} + `; +export const AskingTaskDocument = gql` + query AskingTask($taskId: String!) { + askingTask(taskId: $taskId) { + status + candidates { + sql + summary + } + error { + ...CommonError + } + } +} + ${CommonErrorFragmentDoc}`; + +/** + * __useAskingTaskQuery__ + * + * To run a query within a React component, call `useAskingTaskQuery` and pass it any options that fit your needs. + * When your component renders, `useAskingTaskQuery` 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 } = useAskingTaskQuery({ + * variables: { + * taskId: // value for 'taskId' + * }, + * }); + */ +export function useAskingTaskQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(AskingTaskDocument, options); + } +export function useAskingTaskLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(AskingTaskDocument, options); + } +export type AskingTaskQueryHookResult = ReturnType; +export type AskingTaskLazyQueryHookResult = ReturnType; +export type AskingTaskQueryResult = Apollo.QueryResult; +export const ThreadsDocument = gql` + query Threads { + threads { + id + sql + summary + } +} + `; + +/** + * __useThreadsQuery__ + * + * To run a query within a React component, call `useThreadsQuery` and pass it any options that fit your needs. + * When your component renders, `useThreadsQuery` 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 } = useThreadsQuery({ + * variables: { + * }, + * }); + */ +export function useThreadsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ThreadsDocument, options); + } +export function useThreadsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ThreadsDocument, options); + } +export type ThreadsQueryHookResult = ReturnType; +export type ThreadsLazyQueryHookResult = ReturnType; +export type ThreadsQueryResult = Apollo.QueryResult; +export const ThreadDocument = gql` + query Thread($threadId: Int!) { + thread(threadId: $threadId) { + id + sql + summary + responses { + ...CommonResponse + error { + ...CommonError + } + } + } +} + ${CommonResponseFragmentDoc} +${CommonErrorFragmentDoc}`; + +/** + * __useThreadQuery__ + * + * To run a query within a React component, call `useThreadQuery` and pass it any options that fit your needs. + * When your component renders, `useThreadQuery` 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 } = useThreadQuery({ + * variables: { + * threadId: // value for 'threadId' + * }, + * }); + */ +export function useThreadQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ThreadDocument, options); + } +export function useThreadLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ThreadDocument, options); + } +export type ThreadQueryHookResult = ReturnType; +export type ThreadLazyQueryHookResult = ReturnType; +export type ThreadQueryResult = Apollo.QueryResult; +export const ThreadResponseDocument = gql` + query ThreadResponse($responseId: Int!) { + threadResponse(responseId: $responseId) { + ...CommonResponse + error { + ...CommonError + } + } +} + ${CommonResponseFragmentDoc} +${CommonErrorFragmentDoc}`; + +/** + * __useThreadResponseQuery__ + * + * To run a query within a React component, call `useThreadResponseQuery` and pass it any options that fit your needs. + * When your component renders, `useThreadResponseQuery` 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 } = useThreadResponseQuery({ + * variables: { + * responseId: // value for 'responseId' + * }, + * }); + */ +export function useThreadResponseQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ThreadResponseDocument, options); + } +export function useThreadResponseLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ThreadResponseDocument, options); + } +export type ThreadResponseQueryHookResult = ReturnType; +export type ThreadResponseLazyQueryHookResult = ReturnType; +export type ThreadResponseQueryResult = Apollo.QueryResult; +export const CreateAskingTaskDocument = gql` + mutation CreateAskingTask($data: AskingTaskInput!) { + createAskingTask(data: $data) { + id + } +} + `; +export type CreateAskingTaskMutationFn = Apollo.MutationFunction; + +/** + * __useCreateAskingTaskMutation__ + * + * To run a mutation, you first call `useCreateAskingTaskMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateAskingTaskMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createAskingTaskMutation, { data, loading, error }] = useCreateAskingTaskMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useCreateAskingTaskMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateAskingTaskDocument, options); + } +export type CreateAskingTaskMutationHookResult = ReturnType; +export type CreateAskingTaskMutationResult = Apollo.MutationResult; +export type CreateAskingTaskMutationOptions = Apollo.BaseMutationOptions; +export const CancelAskingTaskDocument = gql` + mutation CancelAskingTask($taskId: String!) { + cancelAskingTask(taskId: $taskId) +} + `; +export type CancelAskingTaskMutationFn = Apollo.MutationFunction; + +/** + * __useCancelAskingTaskMutation__ + * + * To run a mutation, you first call `useCancelAskingTaskMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCancelAskingTaskMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [cancelAskingTaskMutation, { data, loading, error }] = useCancelAskingTaskMutation({ + * variables: { + * taskId: // value for 'taskId' + * }, + * }); + */ +export function useCancelAskingTaskMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CancelAskingTaskDocument, options); + } +export type CancelAskingTaskMutationHookResult = ReturnType; +export type CancelAskingTaskMutationResult = Apollo.MutationResult; +export type CancelAskingTaskMutationOptions = Apollo.BaseMutationOptions; +export const CreateThreadDocument = gql` + mutation CreateThread($data: CreateThreadInput!) { + createThread(data: $data) { + id + sql + summary + } +} + `; +export type CreateThreadMutationFn = Apollo.MutationFunction; + +/** + * __useCreateThreadMutation__ + * + * To run a mutation, you first call `useCreateThreadMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateThreadMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createThreadMutation, { data, loading, error }] = useCreateThreadMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useCreateThreadMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateThreadDocument, options); + } +export type CreateThreadMutationHookResult = ReturnType; +export type CreateThreadMutationResult = Apollo.MutationResult; +export type CreateThreadMutationOptions = Apollo.BaseMutationOptions; +export const CreateThreadResponseDocument = gql` + mutation CreateThreadResponse($threadId: Int!, $data: CreateThreadResponseInput!) { + createThreadResponse(threadId: $threadId, data: $data) { + ...CommonResponse + error { + code + shortMessage + message + stacktrace + } + } +} + ${CommonResponseFragmentDoc}`; +export type CreateThreadResponseMutationFn = Apollo.MutationFunction; + +/** + * __useCreateThreadResponseMutation__ + * + * To run a mutation, you first call `useCreateThreadResponseMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateThreadResponseMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createThreadResponseMutation, { data, loading, error }] = useCreateThreadResponseMutation({ + * variables: { + * threadId: // value for 'threadId' + * data: // value for 'data' + * }, + * }); + */ +export function useCreateThreadResponseMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateThreadResponseDocument, options); + } +export type CreateThreadResponseMutationHookResult = ReturnType; +export type CreateThreadResponseMutationResult = Apollo.MutationResult; +export type CreateThreadResponseMutationOptions = Apollo.BaseMutationOptions; +export const UpdateThreadDocument = gql` + mutation UpdateThread($where: ThreadUniqueWhereInput!, $data: UpdateThreadInput!) { + updateThread(where: $where, data: $data) { + id + sql + summary + } +} + `; +export type UpdateThreadMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateThreadMutation__ + * + * To run a mutation, you first call `useUpdateThreadMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateThreadMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateThreadMutation, { data, loading, error }] = useUpdateThreadMutation({ + * variables: { + * where: // value for 'where' + * data: // value for 'data' + * }, + * }); + */ +export function useUpdateThreadMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateThreadDocument, options); + } +export type UpdateThreadMutationHookResult = ReturnType; +export type UpdateThreadMutationResult = Apollo.MutationResult; +export type UpdateThreadMutationOptions = Apollo.BaseMutationOptions; +export const DeleteThreadDocument = gql` + mutation DeleteThread($where: ThreadUniqueWhereInput!) { + deleteThread(where: $where) +} + `; +export type DeleteThreadMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteThreadMutation__ + * + * To run a mutation, you first call `useDeleteThreadMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteThreadMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteThreadMutation, { data, loading, error }] = useDeleteThreadMutation({ + * variables: { + * where: // value for 'where' + * }, + * }); + */ +export function useDeleteThreadMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteThreadDocument, options); + } +export type DeleteThreadMutationHookResult = ReturnType; +export type DeleteThreadMutationResult = Apollo.MutationResult; +export type DeleteThreadMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file diff --git a/wren-ui/src/apollo/client/graphql/home.ts b/wren-ui/src/apollo/client/graphql/home.ts new file mode 100644 index 000000000..02cc0a88f --- /dev/null +++ b/wren-ui/src/apollo/client/graphql/home.ts @@ -0,0 +1,146 @@ +import { gql } from '@apollo/client'; + +const COMMON_ERROR = gql` + fragment CommonError on Error { + code + shortMessage + message + stacktrace + } +`; + +const COMMON_RESPONSE = gql` + fragment CommonResponse on ThreadResponse { + id + question + status + detail { + sql + description + steps { + summary + sql + cteName + } + } + } +`; + +export const ASKING_TASK = gql` + query AskingTask($taskId: String!) { + askingTask(taskId: $taskId) { + status + candidates { + sql + summary + } + error { + ...CommonError + } + } + } + ${COMMON_ERROR} +`; + +export const THREADS = gql` + query Threads { + threads { + id + sql + summary + } + } +`; + +export const THREAD = gql` + query Thread($threadId: Int!) { + thread(threadId: $threadId) { + id + sql + summary + responses { + ...CommonResponse + error { + ...CommonError + } + } + } + } + ${COMMON_RESPONSE} + ${COMMON_ERROR} +`; + +export const THREAD_RESPONSE = gql` + query ThreadResponse($responseId: Int!) { + threadResponse(responseId: $responseId) { + ...CommonResponse + error { + ...CommonError + } + } + } + ${COMMON_RESPONSE} + ${COMMON_ERROR} +`; + +export const CREATE_ASKING_TASK = gql` + mutation CreateAskingTask($data: AskingTaskInput!) { + createAskingTask(data: $data) { + id + } + } +`; + +export const CANCEL_ASKING_TASK = gql` + mutation CancelAskingTask($taskId: String!) { + cancelAskingTask(taskId: $taskId) + } +`; + +export const CREATE_THREAD = gql` + mutation CreateThread($data: CreateThreadInput!) { + createThread(data: $data) { + id + sql + summary + } + } +`; + +export const CREATE_THREAD_RESPONSE = gql` + mutation CreateThreadResponse( + $threadId: Int! + $data: CreateThreadResponseInput! + ) { + createThreadResponse(threadId: $threadId, data: $data) { + ...CommonResponse + error { + code + shortMessage + message + stacktrace + } + } + } + ${COMMON_RESPONSE} + ${COMMON_ERROR} +`; + +export const UPDATE_THREAD = gql` + mutation UpdateThread( + $where: ThreadUniqueWhereInput! + $data: UpdateThreadInput! + ) { + updateThread(where: $where, data: $data) { + id + sql + summary + } + } +`; + +export const DELETE_THREAD = gql` + mutation DeleteThread($where: ThreadUniqueWhereInput!) { + deleteThread(where: $where) + } +`; diff --git a/wren-ui/src/apollo/server/adaptors/wrenAIAdaptor.ts b/wren-ui/src/apollo/server/adaptors/wrenAIAdaptor.ts index 99434b23a..0f94c1a18 100644 --- a/wren-ui/src/apollo/server/adaptors/wrenAIAdaptor.ts +++ b/wren-ui/src/apollo/server/adaptors/wrenAIAdaptor.ts @@ -92,6 +92,13 @@ export type AskResult = AskResponse< AskResultStatus >; +const getAISerciceError = (error: any) => { + const { data } = error.response || {}; + return data?.detail + ? `${error.message}, detail: ${data.detail}` + : error.message; +}; + export interface IWrenAIAdaptor { deploy(deployData: deployData): Promise; @@ -136,7 +143,7 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { }); return { queryId: res.data.query_id }; } catch (err: any) { - logger.debug(`Got error when asking wren AI: ${err.message}`); + logger.debug(`Got error when asking wren AI: ${getAISerciceError(err)}`); throw err; } } @@ -144,9 +151,11 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { public async cancelAsk(queryId: string): Promise { // make PATCH request /v1/asks/:query_id to cancel the query try { - await axios.patch(`${this.wrenAIBaseEndpoint}/v1/asks/${queryId}`); + await axios.patch(`${this.wrenAIBaseEndpoint}/v1/asks/${queryId}`, { + status: 'stopped', + }); } catch (err: any) { - logger.debug(`Got error when canceling ask: ${err.message}`); + logger.debug(`Got error when canceling ask: ${getAISerciceError(err)}`); throw err; } } @@ -159,7 +168,9 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { ); return this.transformAskResult(res.data); } catch (err: any) { - logger.debug(`Got error when getting ask result: ${err.message}`); + logger.debug( + `Got error when getting ask result: ${getAISerciceError(err)}`, + ); // throw err; throw Errors.create(Errors.GeneralErrorCodes.INTERNAL_SERVER_ERROR, { originalError: err, @@ -181,7 +192,9 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { ); return { queryId: res.data.query_id }; } catch (err: any) { - logger.debug(`Got error when generating ask detail: ${err.message}`); + logger.debug( + `Got error when generating ask detail: ${getAISerciceError(err)}`, + ); throw err; } } @@ -194,7 +207,9 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { ); return this.transformAskDetailResult(res.data); } catch (err: any) { - logger.debug(`Got error when getting ask detail result: ${err.message}`); + logger.debug( + `Got error when getting ask detail result: ${getAISerciceError(err)}`, + ); throw err; } } @@ -222,7 +237,7 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { } } catch (err: any) { logger.debug( - `Got error when deploying to wren AI, hash: ${hash}. Error: ${err.message}`, + `Got error when deploying to wren AI, hash: ${hash}. Error: ${getAISerciceError(err)}`, ); return { status: WrenAIDeployStatusEnum.FAILED, @@ -269,7 +284,7 @@ export class WrenAIAdaptor implements IWrenAIAdaptor { return res.data?.status.toUpperCase() as WrenAISystemStatus; } catch (err: any) { logger.debug( - `Got error in API /v1/semantics-preparations/${deployId}/status: ${err.message}`, + `Got error in API /v1/semantics-preparations/${deployId}/status: ${getAISerciceError(err)}`, ); throw err; } diff --git a/wren-ui/src/apollo/server/resolvers/askingResolver.ts b/wren-ui/src/apollo/server/resolvers/askingResolver.ts index e72364b01..c46725a64 100644 --- a/wren-ui/src/apollo/server/resolvers/askingResolver.ts +++ b/wren-ui/src/apollo/server/resolvers/askingResolver.ts @@ -247,10 +247,12 @@ export class AskingResolver { public getThreadResponseNestedResolver = () => ({ detail: (parent: ThreadResponse, _args: any, _ctx: IContext) => { // extend sql to detail - return { - ...parent.detail, - sql: format(constructCteSql(parent.detail.steps)), - }; + return parent.detail + ? { + ...parent.detail, + sql: format(constructCteSql(parent.detail.steps)), + } + : null; }, }); diff --git a/wren-ui/src/components/editor/CodeBlock.tsx b/wren-ui/src/components/editor/CodeBlock.tsx index 7732e2e32..b6026d031 100644 --- a/wren-ui/src/components/editor/CodeBlock.tsx +++ b/wren-ui/src/components/editor/CodeBlock.tsx @@ -6,20 +6,24 @@ import '@/components/editor/AceEditor'; const Block = styled.div<{ inline?: boolean }>` position: relative; white-space: pre; + overflow-x: auto; ${(props) => props.inline ? `display: inline; border: none; background: transparent !important; padding: 0;` : `background: var(--gray-1); padding: 4px;`} - .line-number { - user-select: none; - display: inline-block; - min-width: 1.5em; - text-align: right; - margin-right: 1em; - color: var(--gray-6); - font-weight: 700; - font-size: 14px; + .adm-code-line { + ${(props) => (props.inline ? '' : 'display: block;')} + &-number { + user-select: none; + display: inline-block; + min-width: 1.5em; + text-align: right; + margin-right: 1em; + color: var(--gray-6); + font-weight: 700; + font-size: 14px; + } } `; @@ -77,8 +81,10 @@ export default function CodeBlock(props: Props) { }); return ( - - {showLineNumbers && {index + 1}} + + {showLineNumbers && ( + {index + 1} + )} {children} ); diff --git a/wren-ui/src/components/pages/home/prompt/Result.tsx b/wren-ui/src/components/pages/home/prompt/Result.tsx index 971e175ac..96c89e30f 100644 --- a/wren-ui/src/components/pages/home/prompt/Result.tsx +++ b/wren-ui/src/components/pages/home/prompt/Result.tsx @@ -15,6 +15,7 @@ import ViewSQLModal from '@/components/pages/home/prompt/ViewSQLModal'; import EllipsisWrapper from '@/components/EllipsisWrapper'; import useModalAction from '@/hooks/useModalAction'; import useAskProcessState from '@/hooks/useAskProcessState'; +import { AskingTask } from '@/apollo/client/graphql/__types__'; const ResultStyle = styled.div` position: absolute; @@ -52,16 +53,20 @@ const ResultBlock = styled.div` interface Props { processState: ReturnType; - data: any[]; + data: AskingTask['candidates']; error?: any; + onSelect: (payload: { sql: string; summary: string }) => void; onClose: () => void; onStop: () => void; } -const ResultTemplate = ({ index, summary, sql, onShowSQL }) => { +const ResultTemplate = ({ index, summary, sql, onSelect, onShowSQL }) => { return ( - + onSelect({ sql, summary })} + >
Result {index + 1} @@ -69,7 +74,7 @@ const ResultTemplate = ({ index, summary, sql, onShowSQL }) => {
-
{config.description}
- {message && ( +
{config.description || message}
+ {hasStacktrace && ( ( @@ -134,7 +140,7 @@ const makeProcessingError = )} > -
{message}
+
{stacktrace.join('\n')}
)} @@ -144,22 +150,12 @@ const makeProcessingError = const ErrorIcon = () => ; -const UnderstandingFailed = makeProcessingError({ +const Failed = makeProcessingError({ icon: , - 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: , - title: 'Something went wrong', - description: - "Sorry, we encountered an error when we're processing your request.", }); const NoResult = makeProcessingError({ - icon: , + icon: , title: 'Please try again', description: 'No results found. Try providing more details in your question.', }); @@ -167,14 +163,21 @@ const NoResult = makeProcessingError({ const Understanding = makeProcessing('Understanding question'); const Searching = makeProcessing('Searching data'); const Finished = (props: Props) => { - const { data, onClose } = props; + const { data, onClose, onSelect } = props; const viewSQLModal = useModalAction(); - const showSQL = (payload: { sql: string; summary: string }) => { + const showSQL = (event, payload: { sql: string; summary: string }) => { viewSQLModal.openModal(payload); + event.stopPropagation(); }; + const selectResult = (payload: { sql: string; summary: string }) => { + onSelect && onSelect(payload); + }; + + if (data.length === 0) return ; + return (
@@ -193,7 +196,11 @@ const Finished = (props: Props) => {
- +
@@ -205,10 +212,10 @@ const getProcessStateComponent = (state: PROCESS_STATE) => { { [PROCESS_STATE.UNDERSTANDING]: Understanding, [PROCESS_STATE.SEARCHING]: Searching, + // generating no need to change UI + [PROCESS_STATE.GENERATING]: Searching, [PROCESS_STATE.FINISHED]: Finished, - [PROCESS_STATE.UNDERSTANDING_FAILED]: UnderstandingFailed, - [PROCESS_STATE.SEARCHING_FAILED]: SearchingFailed, - [PROCESS_STATE.NO_RESULT]: NoResult, + [PROCESS_STATE.FAILED]: Failed, }[state] || null ); }; diff --git a/wren-ui/src/components/pages/home/prompt/index.tsx b/wren-ui/src/components/pages/home/prompt/index.tsx index 5474ef6cb..766c7cd79 100644 --- a/wren-ui/src/components/pages/home/prompt/index.tsx +++ b/wren-ui/src/components/pages/home/prompt/index.tsx @@ -1,77 +1,93 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, 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'; +import useAskProcessState, { + getIsProcessing, +} from '@/hooks/useAskProcessState'; +import { + AskingTask, + AskingTaskStatus, +} from '@/apollo/client/graphql/__types__'; interface Props { + onSelect: (payload: { + sql: string; + summary: string; + question: string; + }) => void; onStop: () => void; onSubmit: (value: string) => void; - data?: { status: string; result: { sql: string; summary: string }[] }; - error?: any; + data?: AskingTask; } const PromptStyle = styled.div` position: fixed; width: 768px; left: 50%; - margin-left: calc(-384px + 140px); + margin-left: calc(-384px + 133px); bottom: 12px; + z-index: 999; `; 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 convertAskingTaskToProcessState = (data: AskingTask) => { + const processState = { + [AskingTaskStatus.UNDERSTANDING]: PROCESS_STATE.UNDERSTANDING, + [AskingTaskStatus.SEARCHING]: PROCESS_STATE.SEARCHING, + [AskingTaskStatus.GENERATING]: PROCESS_STATE.GENERATING, + [AskingTaskStatus.FINISHED]: PROCESS_STATE.FINISHED, + }[data.status]; -const convertToErrorState = (status: string) => { - return { - understanding: PROCESS_STATE.UNDERSTANDING_FAILED, - searching: PROCESS_STATE.SEARCHING_FAILED, - finished: PROCESS_STATE.NO_RESULT, - }[status]; + if (processState === PROCESS_STATE.FINISHED && data.candidates.length === 0) { + return PROCESS_STATE.NO_RESULT; + } + return processState; }; export default function Prompt(props: Props) { - const { data, error, onSubmit, onStop } = props; + const $promptInput = useRef(null); + const { data, onSubmit, onStop, onSelect } = props; const [inputValue, setInputValue] = useState(''); const askProcessState = useAskProcessState(); - const results = useMemo(() => data.result || [], [data]); + const candidates = useMemo(() => data?.candidates || [], [data?.candidates]); + const error = useMemo(() => data?.error || null, [data?.error]); const question = useMemo(() => inputValue.trim(), [inputValue]); const isProcessing = useMemo( - () => - [PROCESS_STATE.UNDERSTANDING, PROCESS_STATE.SEARCHING].includes( - askProcessState.currentState, - ), + () => getIsProcessing(askProcessState.currentState), [askProcessState.currentState], ); + useEffect(() => { + if (!isProcessing) $promptInput.current?.focus(); + }, [isProcessing]); + useEffect(() => { if (data) { - const processState = convertToProcessState(data.status); + const processState = convertAskingTaskToProcessState(data); askProcessState.setState(processState); } }, [data]); useEffect(() => { if (error) { - // TODO: confirm error state - const errorState = convertToErrorState(error.status); - askProcessState.setState(errorState); + askProcessState.setState(PROCESS_STATE.FAILED); } }, [error]); + const selectResult = (payload) => { + onSelect && onSelect({ ...payload, question }); + closeResult(); + }; + const closeResult = () => { askProcessState.resetState(); + setInputValue(''); }; const stopProcess = () => { @@ -99,12 +115,14 @@ export default function Prompt(props: Props) { return ( diff --git a/wren-ui/src/components/pages/home/AnswerResult.tsx b/wren-ui/src/components/pages/home/promptThread/AnswerResult.tsx similarity index 61% rename from wren-ui/src/components/pages/home/AnswerResult.tsx rename to wren-ui/src/components/pages/home/promptThread/AnswerResult.tsx index 766532987..2ceb6f22a 100644 --- a/wren-ui/src/components/pages/home/AnswerResult.tsx +++ b/wren-ui/src/components/pages/home/promptThread/AnswerResult.tsx @@ -1,32 +1,23 @@ -import { Button, Skeleton, Typography } from 'antd'; +import { 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/pages/home/StepContent'; +import StepContent from '@/components/pages/home/promptThread/StepContent'; const { Title } = Typography; interface Props { loading: boolean; - onOpenSaveAsViewModal: (data: { sql: string }) => void; question: string; description: string; answerResultSteps: Array<{ summary: string; sql: string; }>; - sql: string; + fullSql: string; } export default function AnswerResult(props: Props) { - const { - loading, - onOpenSaveAsViewModal, - question, - description, - answerResultSteps, - sql, - } = props; + const { loading, question, description, answerResultSteps, fullSql } = props; return ( @@ -42,25 +33,16 @@ export default function AnswerResult(props: Props) { {description} - {answerResultSteps.map((step, index) => ( + {(answerResultSteps || []).map((step, index) => ( ))} - ); diff --git a/wren-ui/src/components/pages/home/CollapseContent.tsx b/wren-ui/src/components/pages/home/promptThread/CollapseContent.tsx similarity index 96% rename from wren-ui/src/components/pages/home/CollapseContent.tsx rename to wren-ui/src/components/pages/home/promptThread/CollapseContent.tsx index 0fd87701a..49c2956e1 100644 --- a/wren-ui/src/components/pages/home/CollapseContent.tsx +++ b/wren-ui/src/components/pages/home/promptThread/CollapseContent.tsx @@ -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/pages/home/PreviewData'; +import PreviewData from '@/components/pages/home/promptThread/PreviewData'; const CodeBlock = dynamic(() => import('@/components/editor/CodeBlock'), { ssr: false, diff --git a/wren-ui/src/components/pages/home/PreviewData.tsx b/wren-ui/src/components/pages/home/promptThread/PreviewData.tsx similarity index 100% rename from wren-ui/src/components/pages/home/PreviewData.tsx rename to wren-ui/src/components/pages/home/promptThread/PreviewData.tsx diff --git a/wren-ui/src/components/pages/home/StepContent.tsx b/wren-ui/src/components/pages/home/promptThread/StepContent.tsx similarity index 96% rename from wren-ui/src/components/pages/home/StepContent.tsx rename to wren-ui/src/components/pages/home/promptThread/StepContent.tsx index 2070f0089..62c65cb74 100644 --- a/wren-ui/src/components/pages/home/StepContent.tsx +++ b/wren-ui/src/components/pages/home/promptThread/StepContent.tsx @@ -3,7 +3,7 @@ import FunctionOutlined from '@ant-design/icons/FunctionOutlined'; import { BinocularsIcon } from '@/utils/icons'; import CollapseContent, { Props as CollapseContentProps, -} from '@/components/pages/home/CollapseContent'; +} from '@/components/pages/home/promptThread/CollapseContent'; import useAnswerStepContent from '@/hooks/useAnswerStepContent'; const { Title, Paragraph } = Typography; diff --git a/wren-ui/src/components/pages/home/promptThread/index.tsx b/wren-ui/src/components/pages/home/promptThread/index.tsx new file mode 100644 index 000000000..bc15394b4 --- /dev/null +++ b/wren-ui/src/components/pages/home/promptThread/index.tsx @@ -0,0 +1,97 @@ +import { useEffect, useRef } from 'react'; +import { Alert, Divider } from 'antd'; +import styled from 'styled-components'; +import AnswerResult from './AnswerResult'; +import { makeIterable } from '@/utils/iteration'; +import { + AskingTaskStatus, + DetailedThread, +} from '@/apollo/client/graphql/__types__'; + +interface Props { + data: DetailedThread; +} + +const StyledPromptThread = styled.div` + width: 768px; + margin-left: auto; + margin-right: auto; + + h4.ant-typography { + margin-top: 10px; + } + + .ant-typography pre { + border: none; + border-radius: 4px; + } + + .ace_editor { + border: none; + } + + button { + vertical-align: middle; + } +`; + +const AnswerResultTemplate = ({ + index, + id, + status, + question, + detail, + error, +}) => { + return ( +
+ {index > 0 && } + {error ? ( + + ) : ( + + )} +
+ ); +}; + +const AnswerResultIterator = makeIterable(AnswerResultTemplate); + +export default function PromptThread(props: Props) { + const { data } = props; + const divRef = useRef(null); + + useEffect(() => { + if (divRef.current && data?.responses.length > 0) { + const contentLayout = divRef.current.parentElement; + const lastChild = divRef.current.lastElementChild as HTMLElement; + const lastChildDivider = lastChild.firstElementChild as HTMLElement; + if ( + contentLayout.clientHeight < + lastChild.offsetTop + lastChild.clientHeight + ) { + contentLayout.scrollTo({ + top: lastChildDivider.offsetTop, + behavior: 'smooth', + }); + } + } + }, [divRef, data]); + + return ( + + + + ); +} diff --git a/wren-ui/src/components/sidebar/Home.tsx b/wren-ui/src/components/sidebar/Home.tsx index bc79a5557..2151f7a17 100644 --- a/wren-ui/src/components/sidebar/Home.tsx +++ b/wren-ui/src/components/sidebar/Home.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; +import { useParams } from 'next/navigation'; import { Button } from 'antd'; import styled from 'styled-components'; import { DataNode } from 'antd/es/tree'; @@ -9,7 +10,6 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined'; import SidebarTree, { useSidebarTreeState } from './SidebarTree'; import TreeTitle from '@/components/sidebar/home/TreeTitle'; -// TODO: update it to real thread data type interface ThreadData { id: string; name: string; @@ -18,6 +18,8 @@ interface ThreadData { export interface Props { data: ThreadData[]; onSelect: (selectKeys) => void; + onDelete: (id: string) => Promise; + onRename: (id: string, newName: string) => Promise; } const StyledSidebarTree = styled(SidebarTree)` @@ -39,8 +41,9 @@ const StyledSidebarTree = styled(SidebarTree)` `; export default function Home(props: Props) { - const { data, onSelect } = props; + const { data, onSelect, onRename, onDelete } = props; const router = useRouter(); + const params = useParams<{ id: string }>(); const getThreadGroupNode = createTreeGroupNode({ groupName: 'Thread', @@ -52,8 +55,8 @@ export default function Home(props: Props) { const { treeSelectedKeys, setTreeSelectedKeys } = useSidebarTreeState(); useEffect(() => { - router.query.id && setTreeSelectedKeys([router.query.id] as string[]); - }, [router.query.id]); + params?.id && setTreeSelectedKeys([params.id] as string[]); + }, [params?.id]); // initial workspace useEffect(() => { @@ -70,25 +73,18 @@ export default function Home(props: Props) { { - // TODO: Call API to rename the thread name - console.log( - 'Call API to rename the thread name:', - newThreadName, - ); - }} + onRename={onRename} onDelete={onDeleteThread} /> ), }; }), ); - }, [data]); + }, [params?.id, data]); - const onDeleteThread = (threadId: string) => { - // TODO: Call API to delete the thread result - console.log('Call delete API:', threadId); - if (router.query.id === threadId) { + const onDeleteThread = async (threadId: string) => { + await onDelete(threadId); + if (params?.id == threadId) { router.push(Path.Home); } }; diff --git a/wren-ui/src/components/sidebar/home/TreeTitle.tsx b/wren-ui/src/components/sidebar/home/TreeTitle.tsx index 1b6a0c040..fa21c94d6 100644 --- a/wren-ui/src/components/sidebar/home/TreeTitle.tsx +++ b/wren-ui/src/components/sidebar/home/TreeTitle.tsx @@ -21,7 +21,7 @@ const StyledMenu = styled(Menu)` export default function TreeTitle(props: { threadId: string; onDelete: (threadId: string) => void; - onRename: (newName: string) => void; + onRename: (threadId: string, newName: string) => void; title: string; }) { const { threadId, onDelete, onRename } = props; @@ -36,7 +36,7 @@ export default function TreeTitle(props: { const onChangeTitle = (newThreadTitle: string) => { setIsEditing(false); setTitle(newThreadTitle); - onRename(newThreadTitle); + onRename(threadId, newThreadTitle); }; return isEditing ? ( diff --git a/wren-ui/src/hooks/useAskProcessState.tsx b/wren-ui/src/hooks/useAskProcessState.tsx index 8a7be553e..46059930b 100644 --- a/wren-ui/src/hooks/useAskProcessState.tsx +++ b/wren-ui/src/hooks/useAskProcessState.tsx @@ -1,6 +1,13 @@ import { useState } from 'react'; import { PROCESS_STATE } from '@/utils/enum'; +export const getIsProcessing = (status: PROCESS_STATE) => + [ + PROCESS_STATE.UNDERSTANDING, + PROCESS_STATE.GENERATING, + PROCESS_STATE.SEARCHING, + ].includes(status); + export default function useAskProcessState() { const [currentState, setCurrentState] = useState( PROCESS_STATE.IDLE, @@ -12,7 +19,6 @@ export default function useAskProcessState() { const nextState = () => { setCurrentState(currentState + 1); - console.log(currentState); }; const setState = (state: PROCESS_STATE) => { diff --git a/wren-ui/src/hooks/useAskPrompt.tsx b/wren-ui/src/hooks/useAskPrompt.tsx new file mode 100644 index 000000000..f6c2e6249 --- /dev/null +++ b/wren-ui/src/hooks/useAskPrompt.tsx @@ -0,0 +1,60 @@ +import { useEffect, useMemo } from 'react'; +import { AskingTaskStatus } from '@/apollo/client/graphql/__types__'; +import { + useAskingTaskLazyQuery, + useCancelAskingTaskMutation, + useCreateAskingTaskMutation, +} from '@/apollo/client/graphql/home.generated'; + +export const getIsFinished = (status: AskingTaskStatus) => + [ + AskingTaskStatus.FINISHED, + AskingTaskStatus.FAILED, + AskingTaskStatus.STOPPED, + ].includes(status); + +export default function useAskPrompt(threadId?: number) { + const [createAskingTask, createAskingTaskResult] = + useCreateAskingTaskMutation(); + const [cancelAskingTask] = useCancelAskingTaskMutation(); + const [fetchAskingTask, askingTaskResult] = useAskingTaskLazyQuery({ + pollInterval: 1000, + }); + const data = useMemo( + () => askingTaskResult.data?.askingTask || null, + [askingTaskResult.data], + ); + const isFinished = useMemo(() => getIsFinished(data?.status), [data]); + + useEffect(() => { + if (isFinished) askingTaskResult.stopPolling(); + }, [isFinished]); + + const onStop = () => { + const taskId = createAskingTaskResult.data?.createAskingTask.id; + if (taskId) { + cancelAskingTask({ variables: { taskId } }).catch((error) => + console.error(error), + ); + } + }; + + const onSubmit = async (value) => { + try { + const response = await createAskingTask({ + variables: { data: { question: value, threadId } }, + }); + await fetchAskingTask({ + variables: { taskId: response.data.createAskingTask.id }, + }); + } catch (error) { + console.error(error); + } + }; + + return { + data, + onStop, + onSubmit, + }; +} diff --git a/wren-ui/src/hooks/useHomeSidebar.tsx b/wren-ui/src/hooks/useHomeSidebar.tsx index 9c45fc7c2..76f6fe8de 100644 --- a/wren-ui/src/hooks/useHomeSidebar.tsx +++ b/wren-ui/src/hooks/useHomeSidebar.tsx @@ -1,21 +1,48 @@ +import { useMemo } from 'react'; import { useRouter } from 'next/router'; import { Path } from '@/utils/enum'; +import { + useDeleteThreadMutation, + useThreadsQuery, + useUpdateThreadMutation, +} from '@/apollo/client/graphql/home.generated'; export default function useHomeSidebar() { const router = useRouter(); + const { data, refetch } = useThreadsQuery(); + const [updateThread] = useUpdateThreadMutation(); + const [deleteThread] = useDeleteThreadMutation(); - // TODO: call API to get real thread list data - const data = [ - { name: 'aaaa', id: 'aaa' }, - { name: 'bbbb', id: 'bbb' }, - ]; + const threads = useMemo( + () => + (data?.threads || []).map((thread) => ({ + id: thread.id.toString(), + name: thread.summary, + })), + [data], + ); const onSelect = (selectKeys: string[]) => { router.push(`${Path.Home}/${selectKeys[0]}`); }; + const onRename = async (id: string, newName: string) => { + await updateThread({ + variables: { where: { id: Number(id) }, data: { summary: newName } }, + }); + refetch(); + }; + + const onDelete = async (id) => { + await deleteThread({ variables: { where: { id: Number(id) } } }); + refetch(); + }; + return { - data, + data: threads, onSelect, + onRename, + onDelete, + refetch, }; } diff --git a/wren-ui/src/pages/home/[id].tsx b/wren-ui/src/pages/home/[id].tsx index ba259a8f4..7075eb644 100644 --- a/wren-ui/src/pages/home/[id].tsx +++ b/wren-ui/src/pages/home/[id].tsx @@ -1,152 +1,118 @@ -import { useEffect, useRef, useState } from 'react'; -import { Divider } from 'antd'; -import styled from 'styled-components'; -import { nextTick } from '@/utils/time'; +import { GetServerSideProps } from 'next'; +import { useRouter } from 'next/router'; +import { useEffect, useMemo } from 'react'; +import { Path } from '@/utils/enum'; import useHomeSidebar from '@/hooks/useHomeSidebar'; -import useModalAction from '@/hooks/useModalAction'; import SiderLayout from '@/components/layouts/SiderLayout'; -import AnswerResult from '@/components/pages/home/AnswerResult'; -import SaveAsViewModal from '@/components/modals/SaveAsViewModal'; import Prompt from '@/components/pages/home/prompt'; +import { + useCreateThreadResponseMutation, + useThreadQuery, + useThreadResponseLazyQuery, +} from '@/apollo/client/graphql/home.generated'; +import useAskPrompt, { getIsFinished } from '@/hooks/useAskPrompt'; +import PromptThread from '@/components/pages/home/promptThread'; -const AnswerResultsBlock = styled.div` - width: 768px; - margin-left: auto; - margin-right: auto; - - h4.ant-typography { - margin-top: 10px; - } - - .ant-typography pre { - border: none; - border-radius: 4px; - } - - .ace_editor { - border: none; - } - - button { - vertical-align: middle; - } -`; - -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(null); +export default function HomeThread({ threadId }) { + const router = useRouter(); const homeSidebar = useHomeSidebar(); + const askPrompt = useAskPrompt(threadId); - const saveAsViewModal = useModalAction(); - // TODO: adjust when intergrating with API - const [simulateData, setSimulateData] = useState(testData); - - // TODO: implement scroll when has new answer result - useEffect(() => { - if (divRef.current) { - const contentLayout = divRef.current.parentElement; - const lastChild = divRef.current.lastElementChild as HTMLElement; - const lastChildDivider = lastChild.firstElementChild as HTMLElement; - if ( - contentLayout.clientHeight < - lastChild.offsetTop + lastChild.clientHeight - ) { - contentLayout.scrollTo({ - top: lastChildDivider.offsetTop, - behavior: 'smooth', - }); - } - } - }, [divRef]); - - // TODO: call API to get real answer results - const answerResults = [ - { - status: 'finished', - query: 'What is our MoM of sales revenue in 2023?', - summary: 'MoM of sales revenue in 2023', - sql: 'SELECT * FROM customer', - description: - 'To calculate the Month-over-Month (MoM) growth rate of sales revenue in 2023. We can use `sales` model to calculate MoM.', - steps: [ - { - summary: - 'First, we calculate the total revenue for each month in 2023 with `sales` model.', - sql: `SELECT * FROM Revenue`, - }, - { - summary: - "Then, we calculate the previous month's revenue for each month.", - sql: `WITH Revenue AS (\n SELECT \n custkey,\n orderstatus,\n sum(totalprice) as totalprice\n FROM Orders\n GROUP BY 1, 2\n)\nSELECT * FROM Revenue`, - }, - { - summary: - "At last, we calculate the Month-over-Month growth rate as a percentage. This is done by subtracting the previous month's revenue from the current month's revenue, dividing by the previous month's revenue, and then multiplying by 100 to get a percentage.", - sql: 'SELECT *\nFROM tpch.sf1.lineitem\nlimit 200', - }, - ], + const { + data, + loading, + updateQuery: updateThreadQuery, + } = useThreadQuery({ + variables: { threadId }, + fetchPolicy: 'cache-and-network', + onError: () => router.push(Path.Home), + }); + const [createThreadResponse] = useCreateThreadResponseMutation({ + onCompleted(next) { + const nextResponse = next.createThreadResponse; + updateThreadQuery((prev) => { + return { + ...prev, + thread: { + ...prev.thread, + responses: [...prev.thread.responses, nextResponse], + }, + }; + }); }, - ]; + }); + const [fetchThreadResponse, threadResponseResult] = + useThreadResponseLazyQuery({ + pollInterval: 1000, + onCompleted(next) { + const nextResponse = next.threadResponse; + updateThreadQuery((prev) => ({ + ...prev, + thread: { + ...prev.thread, + responses: prev.thread.responses.map((response) => + response.id === nextResponse.id ? nextResponse : response, + ), + }, + })); + }, + }); - const onStop = () => { - // TODO: send stop asking API - }; + const thread = useMemo(() => data?.thread || null, [data]); + const threadResponse = useMemo( + () => threadResponseResult.data?.threadResponse || null, + [threadResponseResult.data], + ); + const isFinished = useMemo( + () => getIsFinished(threadResponse?.status), + [threadResponse], + ); - const simulateProcess = async () => { - setSimulateData({ ...simulateData, status: 'understanding' }); - await nextTick(3000); - setSimulateData({ ...simulateData, status: 'searching' }); - await nextTick(3000); - setSimulateData({ ...simulateData, status: 'finished' }); - }; + useEffect(() => { + const unfinishedRespose = (thread?.responses || []).find( + (response) => !getIsFinished(response.status), + ); - const onSubmit = async (value) => { - console.log(value); - await simulateProcess(); + if (unfinishedRespose) { + fetchThreadResponse({ variables: { responseId: unfinishedRespose.id } }); + } + }, [thread]); + + useEffect(() => { + if (isFinished) threadResponseResult.stopPolling(); + }, [isFinished]); + + const onSelect = async (payload) => { + try { + const response = await createThreadResponse({ + variables: { threadId: thread.id, data: payload }, + }); + await fetchThreadResponse({ + variables: { responseId: response.data.createThreadResponse.id }, + }); + } catch (error) { + console.error(error); + } }; return ( - - - {answerResults.map((answerResult, index) => ( -
- {index > 0 && } - -
- ))} -
- { - console.log('save as view', values); - }} + + +
+ - ); } + +export const getServerSideProps = (async (context) => { + return { + props: { + threadId: Number(context.params.id), + }, + }; +}) as GetServerSideProps<{ threadId: number }>; diff --git a/wren-ui/src/pages/home/index.tsx b/wren-ui/src/pages/home/index.tsx index 21a1470ac..95f232c99 100644 --- a/wren-ui/src/pages/home/index.tsx +++ b/wren-ui/src/pages/home/index.tsx @@ -1,40 +1,12 @@ -import { useState } from 'react'; -import { nextTick } from '@/utils/time'; +import { useRouter } from 'next/router'; import Image from 'next/image'; +import { Path } from '@/utils/enum'; 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'; - -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)`, -// }, -// }, -// }; +import useAskPrompt from '@/hooks/useAskPrompt'; +import { useCreateThreadMutation } from '@/apollo/client/graphql/home.generated'; const demoData = [ { @@ -53,30 +25,26 @@ const demoData = [ }, ]; -export default function Ask() { +export default function Home() { + const router = useRouter(); const homeSidebar = useHomeSidebar(); + const askPrompt = useAskPrompt(); + + const [createThread] = useCreateThreadMutation({ + onCompleted: () => homeSidebar.refetch(), + }); - // TODO: adjust when intergrating with API - const [simulateData, setSimulateData] = useState(testData); const isDemo = true; 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(); + const onSelect = async (payload) => { + try { + const response = await createThread({ variables: { data: payload } }); + router.push(Path.Home + `/${response.data.createThread.id}`); + } catch (error) { + console.error(error); + } }; return ( @@ -99,10 +67,10 @@ export default function Ask() { {isDemo && }
); diff --git a/wren-ui/src/utils/enum/home.ts b/wren-ui/src/utils/enum/home.ts index 4a1677df1..1a69b595f 100644 --- a/wren-ui/src/utils/enum/home.ts +++ b/wren-ui/src/utils/enum/home.ts @@ -11,7 +11,6 @@ export enum PROCESS_STATE { GENERATING, FINISHED, - UNDERSTANDING_FAILED, - SEARCHING_FAILED, + FAILED, NO_RESULT, } diff --git a/wren-ui/src/utils/errorHandler.tsx b/wren-ui/src/utils/errorHandler.tsx index dbd04cc73..5e4cf108c 100644 --- a/wren-ui/src/utils/errorHandler.tsx +++ b/wren-ui/src/utils/errorHandler.tsx @@ -31,8 +31,61 @@ class SaveRelationsErrorHandler extends ErrorHandler { } } +class CreateAskingTaskErrorHandler extends ErrorHandler { + public getErrorMessage(error: GraphQLError) { + switch (error.extensions?.code) { + default: + return 'Failed to create asking task.'; + } + } +} + +class CreateThreadErrorHandler extends ErrorHandler { + public getErrorMessage(error: GraphQLError) { + switch (error.extensions?.code) { + default: + return 'Failed to create thread.'; + } + } +} + +class UpdateThreadErrorHandler extends ErrorHandler { + public getErrorMessage(error: GraphQLError) { + switch (error.extensions?.code) { + default: + return 'Failed to update thread.'; + } + } +} + +class DeleteThreadErrorHandler extends ErrorHandler { + public getErrorMessage(error: GraphQLError) { + switch (error.extensions?.code) { + default: + return 'Failed to delete thread.'; + } + } +} + +class CreateThreadResponseErrorHandler extends ErrorHandler { + public getErrorMessage(error: GraphQLError) { + switch (error.extensions?.code) { + default: + return 'Failed to create thread response.'; + } + } +} + errorHandlers.set('SaveTables', new SaveTablesErrorHandler()); errorHandlers.set('SaveRelations', new SaveRelationsErrorHandler()); +errorHandlers.set('CreateAskingTask', new CreateAskingTaskErrorHandler()); +errorHandlers.set('CreateThread', new CreateThreadErrorHandler()); +errorHandlers.set('UpdateThread', new UpdateThreadErrorHandler()); +errorHandlers.set('DeleteThread', new DeleteThreadErrorHandler()); +errorHandlers.set( + 'CreateThreadResponse', + new CreateThreadResponseErrorHandler(), +); const errorHandler = (error: ErrorResponse) => { const operationName = error?.operation?.operationName || '';