mirror of
https://github.com/Canner/WrenAI.git
synced 2026-09-24 23:29:49 +08:00
feat(wren-ui): Integrating home & home sidebar (#55)
* feat(wren-ui): add home API schema * feat(wren-ui): integrating query thread to sidebar * feat(wren-ui): integrating asking task & thread response to home & home/:id * feat(wren-ui): refine home flow & add thread rename, delete * fix(wren-ui): response detail steps null error in nested resolver * style(wren-ui): refine home components style * style(wren-ui): change no result icon color to gold-6 * refactor(wren-ui): extract asking task to hook & refine thread components arrangement & add error alert to thread response * feat(wren-ui): add error handlers to askingTask & thread mutation * fix(wren-ui): fix effect issue when router redirect after thread delete * fix(wren-ui): fix enum to uppercase & remove unused after rebase conflict * fix(wren-ui): add status to cancel ask API body & fix 422 error * chore(wren-ui): extract AI service error detail to logger --------- Co-authored-by: shimin <shimin@shimins-MacBook-Pro.local>
This commit is contained in:
@@ -14,6 +14,32 @@ export type Scalars = {
|
||||
JSON: any;
|
||||
};
|
||||
|
||||
export type AskQuestionResponse = {
|
||||
__typename?: 'AskQuestionResponse';
|
||||
questions: Array<Maybe<Scalars['String']>>;
|
||||
};
|
||||
|
||||
export type AskingTask = {
|
||||
__typename?: 'AskingTask';
|
||||
candidates: Array<ResultCandidate>;
|
||||
error?: Maybe<Error>;
|
||||
status: AskingTaskStatus;
|
||||
};
|
||||
|
||||
export type AskingTaskInput = {
|
||||
question: Scalars['String'];
|
||||
threadId?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export enum AskingTaskStatus {
|
||||
FAILED = 'FAILED',
|
||||
FINISHED = 'FINISHED',
|
||||
GENERATING = 'GENERATING',
|
||||
SEARCHING = 'SEARCHING',
|
||||
STOPPED = 'STOPPED',
|
||||
UNDERSTANDING = 'UNDERSTANDING'
|
||||
}
|
||||
|
||||
export type CalculatedFieldInput = {
|
||||
diagram?: InputMaybe<Scalars['JSON']>;
|
||||
expression: Scalars['String'];
|
||||
@@ -59,6 +85,18 @@ export type CreateSimpleMetricInput = {
|
||||
timeGrain: Array<TimeGrainInput>;
|
||||
};
|
||||
|
||||
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<Scalars['String']>;
|
||||
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<ThreadResponse>;
|
||||
sql: Scalars['String'];
|
||||
summary: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Diagram = {
|
||||
__typename?: 'Diagram';
|
||||
models: Array<Maybe<DiagramModel>>;
|
||||
@@ -175,6 +228,14 @@ export type DimensionInput = {
|
||||
type: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Error = {
|
||||
__typename?: 'Error';
|
||||
code?: Maybe<Scalars['String']>;
|
||||
message?: Maybe<Scalars['String']>;
|
||||
shortMessage?: Maybe<Scalars['String']>;
|
||||
stacktrace?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
};
|
||||
|
||||
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<OnboardingStatus>;
|
||||
};
|
||||
|
||||
export type PreviewDataInput = {
|
||||
responseId: Scalars['Int'];
|
||||
stepIndex?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
askQuestions: AskQuestionResponse;
|
||||
askingTask: AskingTask;
|
||||
autoGenerateRelation?: Maybe<Array<RecommandRelations>>;
|
||||
diagram: Diagram;
|
||||
listDataSourceTables: Array<CompactTable>;
|
||||
@@ -296,6 +408,14 @@ export type Query = {
|
||||
model: DetailedModel;
|
||||
modelSync?: Maybe<ModelSyncResponse>;
|
||||
onboardingStatus: OnboardingStatusResponse;
|
||||
thread: DetailedThread;
|
||||
threadResponse: ThreadResponse;
|
||||
threads: Array<Thread>;
|
||||
};
|
||||
|
||||
|
||||
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<ThreadResponseDetail>;
|
||||
error?: Maybe<Error>;
|
||||
id: Scalars['Int'];
|
||||
question: Scalars['String'];
|
||||
status: AskingTaskStatus;
|
||||
};
|
||||
|
||||
export type ThreadResponseDetail = {
|
||||
__typename?: 'ThreadResponseDetail';
|
||||
description?: Maybe<Scalars['String']>;
|
||||
sql?: Maybe<Scalars['String']>;
|
||||
steps: Array<DetailStep>;
|
||||
};
|
||||
|
||||
export type ThreadUniqueWhereInput = {
|
||||
id: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type TimeGrainInput = {
|
||||
dateParts: Array<Scalars['String']>;
|
||||
name: Scalars['String'];
|
||||
@@ -379,3 +547,7 @@ export type UpdateModelInput = {
|
||||
fields: Array<Scalars['String']>;
|
||||
refreshTime?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateThreadInput = {
|
||||
summary?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
@@ -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<string | null> | 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<string | null> | 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<string | null> | 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<string | null> | 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<string | null> | 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<AskingTaskQuery, AskingTaskQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<AskingTaskQuery, AskingTaskQueryVariables>(AskingTaskDocument, options);
|
||||
}
|
||||
export function useAskingTaskLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AskingTaskQuery, AskingTaskQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<AskingTaskQuery, AskingTaskQueryVariables>(AskingTaskDocument, options);
|
||||
}
|
||||
export type AskingTaskQueryHookResult = ReturnType<typeof useAskingTaskQuery>;
|
||||
export type AskingTaskLazyQueryHookResult = ReturnType<typeof useAskingTaskLazyQuery>;
|
||||
export type AskingTaskQueryResult = Apollo.QueryResult<AskingTaskQuery, AskingTaskQueryVariables>;
|
||||
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<ThreadsQuery, ThreadsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ThreadsQuery, ThreadsQueryVariables>(ThreadsDocument, options);
|
||||
}
|
||||
export function useThreadsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ThreadsQuery, ThreadsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ThreadsQuery, ThreadsQueryVariables>(ThreadsDocument, options);
|
||||
}
|
||||
export type ThreadsQueryHookResult = ReturnType<typeof useThreadsQuery>;
|
||||
export type ThreadsLazyQueryHookResult = ReturnType<typeof useThreadsLazyQuery>;
|
||||
export type ThreadsQueryResult = Apollo.QueryResult<ThreadsQuery, ThreadsQueryVariables>;
|
||||
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<ThreadQuery, ThreadQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ThreadQuery, ThreadQueryVariables>(ThreadDocument, options);
|
||||
}
|
||||
export function useThreadLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ThreadQuery, ThreadQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ThreadQuery, ThreadQueryVariables>(ThreadDocument, options);
|
||||
}
|
||||
export type ThreadQueryHookResult = ReturnType<typeof useThreadQuery>;
|
||||
export type ThreadLazyQueryHookResult = ReturnType<typeof useThreadLazyQuery>;
|
||||
export type ThreadQueryResult = Apollo.QueryResult<ThreadQuery, ThreadQueryVariables>;
|
||||
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<ThreadResponseQuery, ThreadResponseQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ThreadResponseQuery, ThreadResponseQueryVariables>(ThreadResponseDocument, options);
|
||||
}
|
||||
export function useThreadResponseLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ThreadResponseQuery, ThreadResponseQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ThreadResponseQuery, ThreadResponseQueryVariables>(ThreadResponseDocument, options);
|
||||
}
|
||||
export type ThreadResponseQueryHookResult = ReturnType<typeof useThreadResponseQuery>;
|
||||
export type ThreadResponseLazyQueryHookResult = ReturnType<typeof useThreadResponseLazyQuery>;
|
||||
export type ThreadResponseQueryResult = Apollo.QueryResult<ThreadResponseQuery, ThreadResponseQueryVariables>;
|
||||
export const CreateAskingTaskDocument = gql`
|
||||
mutation CreateAskingTask($data: AskingTaskInput!) {
|
||||
createAskingTask(data: $data) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type CreateAskingTaskMutationFn = Apollo.MutationFunction<CreateAskingTaskMutation, CreateAskingTaskMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<CreateAskingTaskMutation, CreateAskingTaskMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateAskingTaskMutation, CreateAskingTaskMutationVariables>(CreateAskingTaskDocument, options);
|
||||
}
|
||||
export type CreateAskingTaskMutationHookResult = ReturnType<typeof useCreateAskingTaskMutation>;
|
||||
export type CreateAskingTaskMutationResult = Apollo.MutationResult<CreateAskingTaskMutation>;
|
||||
export type CreateAskingTaskMutationOptions = Apollo.BaseMutationOptions<CreateAskingTaskMutation, CreateAskingTaskMutationVariables>;
|
||||
export const CancelAskingTaskDocument = gql`
|
||||
mutation CancelAskingTask($taskId: String!) {
|
||||
cancelAskingTask(taskId: $taskId)
|
||||
}
|
||||
`;
|
||||
export type CancelAskingTaskMutationFn = Apollo.MutationFunction<CancelAskingTaskMutation, CancelAskingTaskMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<CancelAskingTaskMutation, CancelAskingTaskMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CancelAskingTaskMutation, CancelAskingTaskMutationVariables>(CancelAskingTaskDocument, options);
|
||||
}
|
||||
export type CancelAskingTaskMutationHookResult = ReturnType<typeof useCancelAskingTaskMutation>;
|
||||
export type CancelAskingTaskMutationResult = Apollo.MutationResult<CancelAskingTaskMutation>;
|
||||
export type CancelAskingTaskMutationOptions = Apollo.BaseMutationOptions<CancelAskingTaskMutation, CancelAskingTaskMutationVariables>;
|
||||
export const CreateThreadDocument = gql`
|
||||
mutation CreateThread($data: CreateThreadInput!) {
|
||||
createThread(data: $data) {
|
||||
id
|
||||
sql
|
||||
summary
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type CreateThreadMutationFn = Apollo.MutationFunction<CreateThreadMutation, CreateThreadMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<CreateThreadMutation, CreateThreadMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateThreadMutation, CreateThreadMutationVariables>(CreateThreadDocument, options);
|
||||
}
|
||||
export type CreateThreadMutationHookResult = ReturnType<typeof useCreateThreadMutation>;
|
||||
export type CreateThreadMutationResult = Apollo.MutationResult<CreateThreadMutation>;
|
||||
export type CreateThreadMutationOptions = Apollo.BaseMutationOptions<CreateThreadMutation, CreateThreadMutationVariables>;
|
||||
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<CreateThreadResponseMutation, CreateThreadResponseMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<CreateThreadResponseMutation, CreateThreadResponseMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateThreadResponseMutation, CreateThreadResponseMutationVariables>(CreateThreadResponseDocument, options);
|
||||
}
|
||||
export type CreateThreadResponseMutationHookResult = ReturnType<typeof useCreateThreadResponseMutation>;
|
||||
export type CreateThreadResponseMutationResult = Apollo.MutationResult<CreateThreadResponseMutation>;
|
||||
export type CreateThreadResponseMutationOptions = Apollo.BaseMutationOptions<CreateThreadResponseMutation, CreateThreadResponseMutationVariables>;
|
||||
export const UpdateThreadDocument = gql`
|
||||
mutation UpdateThread($where: ThreadUniqueWhereInput!, $data: UpdateThreadInput!) {
|
||||
updateThread(where: $where, data: $data) {
|
||||
id
|
||||
sql
|
||||
summary
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateThreadMutationFn = Apollo.MutationFunction<UpdateThreadMutation, UpdateThreadMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<UpdateThreadMutation, UpdateThreadMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateThreadMutation, UpdateThreadMutationVariables>(UpdateThreadDocument, options);
|
||||
}
|
||||
export type UpdateThreadMutationHookResult = ReturnType<typeof useUpdateThreadMutation>;
|
||||
export type UpdateThreadMutationResult = Apollo.MutationResult<UpdateThreadMutation>;
|
||||
export type UpdateThreadMutationOptions = Apollo.BaseMutationOptions<UpdateThreadMutation, UpdateThreadMutationVariables>;
|
||||
export const DeleteThreadDocument = gql`
|
||||
mutation DeleteThread($where: ThreadUniqueWhereInput!) {
|
||||
deleteThread(where: $where)
|
||||
}
|
||||
`;
|
||||
export type DeleteThreadMutationFn = Apollo.MutationFunction<DeleteThreadMutation, DeleteThreadMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<DeleteThreadMutation, DeleteThreadMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<DeleteThreadMutation, DeleteThreadMutationVariables>(DeleteThreadDocument, options);
|
||||
}
|
||||
export type DeleteThreadMutationHookResult = ReturnType<typeof useDeleteThreadMutation>;
|
||||
export type DeleteThreadMutationResult = Apollo.MutationResult<DeleteThreadMutation>;
|
||||
export type DeleteThreadMutationOptions = Apollo.BaseMutationOptions<DeleteThreadMutation, DeleteThreadMutationVariables>;
|
||||
@@ -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)
|
||||
}
|
||||
`;
|
||||
@@ -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<WrenAIDeployResponse>;
|
||||
|
||||
@@ -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<void> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<span className="ace_line" key={`${line}-${index}`}>
|
||||
{showLineNumbers && <span className="line-number">{index + 1}</span>}
|
||||
<span className="adm-code-line ace_line" key={`${line}-${index}`}>
|
||||
{showLineNumbers && (
|
||||
<span className="adm-code-line-number">{index + 1}</span>
|
||||
)}
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -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<typeof useAskProcessState>;
|
||||
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 (
|
||||
<Col span={8}>
|
||||
<ResultBlock className="border border-gray-5 rounded px-3 pt-3 pb-4 cursor-pointer">
|
||||
<ResultBlock
|
||||
className="border border-gray-5 rounded px-3 pt-3 pb-4 cursor-pointer"
|
||||
onClick={() => onSelect({ sql, summary })}
|
||||
>
|
||||
<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}
|
||||
@@ -69,7 +74,7 @@ const ResultTemplate = ({ index, summary, sql, onShowSQL }) => {
|
||||
<Button
|
||||
className="adm-btn-no-style gray-6 text-sm px-1"
|
||||
type="text"
|
||||
onClick={() => onShowSQL({ sql, summary })}
|
||||
onClick={(event) => onShowSQL(event, { sql, summary })}
|
||||
>
|
||||
<FunctionOutlined className="-mr-1" />
|
||||
View SQL
|
||||
@@ -104,16 +109,17 @@ const makeProcessing = (text: string) => (props: Props) => {
|
||||
};
|
||||
|
||||
const makeProcessingError =
|
||||
(config: { icon: ReactNode; title: string; description: string }) =>
|
||||
(config: { icon: ReactNode; title?: string; description?: string }) =>
|
||||
(props: Props) => {
|
||||
const { onClose, error } = props;
|
||||
const { message } = error || {};
|
||||
const { message, shortMessage, stacktrace } = error || {};
|
||||
const hasStacktrace = !!stacktrace;
|
||||
return (
|
||||
<div>
|
||||
<div className="d-flex justify-space-between text-medium mb-2">
|
||||
<div className="d-flex align-center">
|
||||
{config.icon}
|
||||
{config.title}
|
||||
{config.title || shortMessage}
|
||||
</div>
|
||||
<Button
|
||||
className="adm-btn-no-style gray-7 bg-gray-3 text-sm px-2"
|
||||
@@ -125,8 +131,8 @@ const makeProcessingError =
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
<div className="gray-7">{config.description}</div>
|
||||
{message && (
|
||||
<div className="gray-7">{config.description || message}</div>
|
||||
{hasStacktrace && (
|
||||
<StyledCollapse
|
||||
ghost
|
||||
expandIcon={({ isActive }) => (
|
||||
@@ -134,7 +140,7 @@ const makeProcessingError =
|
||||
)}
|
||||
>
|
||||
<Collapse.Panel key="1" header="Show error messages">
|
||||
<pre className="mb-0">{message}</pre>
|
||||
<pre className="mb-0">{stacktrace.join('\n')}</pre>
|
||||
</Collapse.Panel>
|
||||
</StyledCollapse>
|
||||
)}
|
||||
@@ -144,22 +150,12 @@ const makeProcessingError =
|
||||
|
||||
const ErrorIcon = () => <CloseCircleFilled className="mr-2 red-5 text-lg" />;
|
||||
|
||||
const UnderstandingFailed = makeProcessingError({
|
||||
const Failed = 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" />,
|
||||
icon: <WarningOutlined className="mr-2 text-lg gold-6" />,
|
||||
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 <NoResult {...props} />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="d-flex justify-space-between mb-3">
|
||||
@@ -193,7 +196,11 @@ const Finished = (props: Props) => {
|
||||
</Button>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
<ResultColumnIterator data={data} onShowSQL={showSQL} />
|
||||
<ResultColumnIterator
|
||||
data={data}
|
||||
onShowSQL={showSQL}
|
||||
onSelect={selectResult}
|
||||
/>
|
||||
</Row>
|
||||
<ViewSQLModal {...viewSQLModal.state} onClose={viewSQLModal.closeModal} />
|
||||
</div>
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<HTMLTextAreaElement>(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 (
|
||||
<PromptStyle className="d-flex align-end bg-gray-2 p-3 border border-gray-3 rounded">
|
||||
<Input.TextArea
|
||||
ref={$promptInput}
|
||||
size="large"
|
||||
autoSize
|
||||
placeholder="Ask to explore your data"
|
||||
value={inputValue}
|
||||
onInput={syncInputValue}
|
||||
onPressEnter={inputEnter}
|
||||
disabled={isProcessing}
|
||||
/>
|
||||
<PromptButton
|
||||
type="primary"
|
||||
@@ -117,9 +135,10 @@ export default function Prompt(props: Props) {
|
||||
</PromptButton>
|
||||
|
||||
<PromptResult
|
||||
data={results}
|
||||
data={candidates}
|
||||
error={error}
|
||||
processState={askProcessState}
|
||||
onSelect={selectResult}
|
||||
onClose={closeResult}
|
||||
onStop={stopProcess}
|
||||
/>
|
||||
|
||||
+6
-24
@@ -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 (
|
||||
<Skeleton active loading={loading}>
|
||||
@@ -42,25 +33,16 @@ export default function AnswerResult(props: Props) {
|
||||
<Title level={5} style={{ fontWeight: 400 }}>
|
||||
{description}
|
||||
</Title>
|
||||
{answerResultSteps.map((step, index) => (
|
||||
{(answerResultSteps || []).map((step, index) => (
|
||||
<StepContent
|
||||
isLastStep={index === answerResultSteps.length - 1}
|
||||
key={`${step.summary}-${index}`}
|
||||
sql={step.sql}
|
||||
fullSql={sql}
|
||||
fullSql={fullSql}
|
||||
stepNumber={index + 1}
|
||||
summary={step.summary}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
className="mt-2 gray-6"
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={() => onOpenSaveAsViewModal({ sql })}
|
||||
>
|
||||
Save as view
|
||||
</Button>
|
||||
</Typography>
|
||||
</Skeleton>
|
||||
);
|
||||
+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/pages/home/PreviewData';
|
||||
import PreviewData from '@/components/pages/home/promptThread/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/pages/home/CollapseContent';
|
||||
} from '@/components/pages/home/promptThread/CollapseContent';
|
||||
import useAnswerStepContent from '@/hooks/useAnswerStepContent';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
@@ -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 (
|
||||
<div key={`${id}-${index}`}>
|
||||
{index > 0 && <Divider />}
|
||||
{error ? (
|
||||
<Alert
|
||||
message={error.shortMessage}
|
||||
description={error.message}
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<AnswerResult
|
||||
answerResultSteps={detail?.steps}
|
||||
description={detail?.description}
|
||||
loading={status !== AskingTaskStatus.FINISHED}
|
||||
question={question}
|
||||
fullSql={detail?.sql}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AnswerResultIterator = makeIterable(AnswerResultTemplate);
|
||||
|
||||
export default function PromptThread(props: Props) {
|
||||
const { data } = props;
|
||||
const divRef = useRef<HTMLDivElement>(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 (
|
||||
<StyledPromptThread className="mt-12" ref={divRef}>
|
||||
<AnswerResultIterator data={data?.responses || []} sql={data?.sql} />
|
||||
</StyledPromptThread>
|
||||
);
|
||||
}
|
||||
@@ -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<void>;
|
||||
onRename: (id: string, newName: string) => Promise<void>;
|
||||
}
|
||||
|
||||
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) {
|
||||
<TreeTitle
|
||||
threadId={nodeKey}
|
||||
title={thread.name}
|
||||
onRename={(newThreadName) => {
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>(
|
||||
PROCESS_STATE.IDLE,
|
||||
@@ -12,7 +19,6 @@ export default function useAskProcessState() {
|
||||
|
||||
const nextState = () => {
|
||||
setCurrentState(currentState + 1);
|
||||
console.log(currentState);
|
||||
};
|
||||
|
||||
const setState = (state: PROCESS_STATE) => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+101
-135
@@ -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<HTMLDivElement>(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 (
|
||||
<SiderLayout loading={false} sidebar={homeSidebar}>
|
||||
<AnswerResultsBlock className="mt-12 mb-15" ref={divRef}>
|
||||
{answerResults.map((answerResult, index) => (
|
||||
<div key={`${answerResult.query}-${index}`}>
|
||||
{index > 0 && <Divider />}
|
||||
<AnswerResult
|
||||
answerResultSteps={answerResult.steps}
|
||||
description={answerResult.description}
|
||||
loading={answerResult.status !== 'finished'}
|
||||
onOpenSaveAsViewModal={saveAsViewModal.openModal}
|
||||
question={answerResult.query}
|
||||
sql={answerResult.sql}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</AnswerResultsBlock>
|
||||
<SaveAsViewModal
|
||||
{...saveAsViewModal.state}
|
||||
onClose={saveAsViewModal.closeModal}
|
||||
onSubmit={async (values) => {
|
||||
console.log('save as view', values);
|
||||
}}
|
||||
<SiderLayout loading={loading} sidebar={homeSidebar}>
|
||||
<PromptThread data={thread} />
|
||||
<div className="py-10" />
|
||||
<Prompt
|
||||
data={askPrompt.data}
|
||||
onSubmit={askPrompt.onSubmit}
|
||||
onStop={askPrompt.onStop}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
<Prompt data={simulateData} onSubmit={onSubmit} onStop={onStop} />
|
||||
</SiderLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = (async (context) => {
|
||||
return {
|
||||
props: {
|
||||
threadId: Number(context.params.id),
|
||||
},
|
||||
};
|
||||
}) as GetServerSideProps<{ threadId: number }>;
|
||||
|
||||
@@ -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 && <DemoPrompt demo={demoData} onSelect={onDemoSelect} />}
|
||||
</div>
|
||||
<Prompt
|
||||
data={simulateData}
|
||||
error={errorData?.extensions.data}
|
||||
onSubmit={onSubmit}
|
||||
onStop={onStop}
|
||||
data={askPrompt.data}
|
||||
onSubmit={askPrompt.onSubmit}
|
||||
onStop={askPrompt.onStop}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</SiderLayout>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,6 @@ export enum PROCESS_STATE {
|
||||
GENERATING,
|
||||
FINISHED,
|
||||
|
||||
UNDERSTANDING_FAILED,
|
||||
SEARCHING_FAILED,
|
||||
FAILED,
|
||||
NO_RESULT,
|
||||
}
|
||||
|
||||
@@ -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 || '';
|
||||
|
||||
Reference in New Issue
Block a user