Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions wren-ui/src/apollo/client/graphql/__types__.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,48 @@ export type AdjustmentTask = {
traceId?: Maybe<Scalars['String']>;
};

export type ApiHistoryFilterInput = {
apiType?: InputMaybe<ApiType>;
endDate?: InputMaybe<Scalars['String']>;
projectId?: InputMaybe<Scalars['Int']>;
startDate?: InputMaybe<Scalars['String']>;
statusCode?: InputMaybe<Scalars['Int']>;
threadId?: InputMaybe<Scalars['String']>;
};

export type ApiHistoryPaginatedResponse = {
__typename?: 'ApiHistoryPaginatedResponse';
hasMore: Scalars['Boolean'];
items: Array<ApiHistoryResponse>;
total: Scalars['Int'];
};

export type ApiHistoryPaginationInput = {
limit: Scalars['Int'];
offset: Scalars['Int'];
};

export type ApiHistoryResponse = {
__typename?: 'ApiHistoryResponse';
apiType: ApiType;
createdAt: Scalars['String'];
durationMs?: Maybe<Scalars['Int']>;
headers?: Maybe<Scalars['JSON']>;
id: Scalars['String'];
projectId: Scalars['Int'];
requestPayload?: Maybe<Scalars['JSON']>;
responsePayload?: Maybe<Scalars['JSON']>;
statusCode?: Maybe<Scalars['Int']>;
threadId?: Maybe<Scalars['String']>;
updatedAt: Scalars['String'];
};

export enum ApiType {
GENERATE_SQL = 'GENERATE_SQL',
GENERATE_VEGA_SPEC = 'GENERATE_VEGA_SPEC',
RUN_SQL = 'RUN_SQL'
}

export type AskingTask = {
__typename?: 'AskingTask';
candidates: Array<ResultCandidate>;
Expand Down Expand Up @@ -1012,6 +1054,7 @@ export enum ProjectLanguage {
export type Query = {
__typename?: 'Query';
adjustmentTask?: Maybe<AdjustmentTask>;
apiHistory: ApiHistoryPaginatedResponse;
askingTask?: Maybe<AskingTask>;
autoGenerateRelation: Array<RecommendRelations>;
dashboardItems: Array<DashboardItem>;
Expand Down Expand Up @@ -1045,6 +1088,12 @@ export type QueryAdjustmentTaskArgs = {
};


export type QueryApiHistoryArgs = {
filter?: InputMaybe<ApiHistoryFilterInput>;
pagination?: InputMaybe<ApiHistoryPaginationInput>;
};


export type QueryAskingTaskArgs = {
taskId: Scalars['String'];
};
Expand Down
64 changes: 64 additions & 0 deletions wren-ui/src/apollo/client/graphql/apiHistory.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as Types from './__types__';

import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ApiHistoryQueryVariables = Types.Exact<{
filter?: Types.InputMaybe<Types.ApiHistoryFilterInput>;
pagination: Types.ApiHistoryPaginationInput;
}>;


export type ApiHistoryQuery = { __typename?: 'Query', apiHistory: { __typename?: 'ApiHistoryPaginatedResponse', total: number, hasMore: boolean, items: Array<{ __typename?: 'ApiHistoryResponse', id: string, projectId: number, apiType: Types.ApiType, threadId?: string | null, headers?: any | null, requestPayload?: any | null, responsePayload?: any | null, statusCode?: number | null, durationMs?: number | null, createdAt: string, updatedAt: string }> } };


export const ApiHistoryDocument = gql`
query ApiHistory($filter: ApiHistoryFilterInput, $pagination: ApiHistoryPaginationInput!) {
apiHistory(filter: $filter, pagination: $pagination) {
items {
id
projectId
apiType
threadId
headers
requestPayload
responsePayload
statusCode
durationMs
createdAt
updatedAt
}
total
hasMore
}
}
`;

/**
* __useApiHistoryQuery__
*
* To run a query within a React component, call `useApiHistoryQuery` and pass it any options that fit your needs.
* When your component renders, `useApiHistoryQuery` 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 } = useApiHistoryQuery({
* variables: {
* filter: // value for 'filter'
* pagination: // value for 'pagination'
* },
* });
*/
export function useApiHistoryQuery(baseOptions: Apollo.QueryHookOptions<ApiHistoryQuery, ApiHistoryQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ApiHistoryQuery, ApiHistoryQueryVariables>(ApiHistoryDocument, options);
}
export function useApiHistoryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ApiHistoryQuery, ApiHistoryQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ApiHistoryQuery, ApiHistoryQueryVariables>(ApiHistoryDocument, options);
}
export type ApiHistoryQueryHookResult = ReturnType<typeof useApiHistoryQuery>;
export type ApiHistoryLazyQueryHookResult = ReturnType<typeof useApiHistoryLazyQuery>;
export type ApiHistoryQueryResult = Apollo.QueryResult<ApiHistoryQuery, ApiHistoryQueryVariables>;
26 changes: 26 additions & 0 deletions wren-ui/src/apollo/client/graphql/apiHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { gql } from '@apollo/client';

export const API_HISTORY = gql`
query ApiHistory(
$filter: ApiHistoryFilterInput
$pagination: ApiHistoryPaginationInput!
) {
apiHistory(filter: $filter, pagination: $pagination) {
items {
id
projectId
apiType
threadId
headers
requestPayload
responsePayload
statusCode
durationMs
createdAt
updatedAt
}
total
hasMore
}
}
`;
8 changes: 8 additions & 0 deletions wren-ui/src/components/HeaderBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ export default function HeaderBar() {
>
Knowledge
</StyledButton>
<StyledButton
shape="round"
size="small"
$isHighlight={pathname.startsWith(Path.APIManagement)}
onClick={() => router.push(Path.APIManagementHistory)}
>
API
</StyledButton>
</Space>
)}
</Space>
Expand Down
201 changes: 201 additions & 0 deletions wren-ui/src/components/code/BaseCodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { useEffect } from 'react';
import styled from 'styled-components';
import { Button, Typography } from 'antd';
import CheckOutlined from '@ant-design/icons/CheckOutlined';
import CopyOutlined from '@ant-design/icons/CopyOutlined';
import { Loading } from '@/components/PageLoading';
import '@/components/editor/AceEditor';

export interface BaseProps {
code: string;
copyable?: boolean;
inline?: boolean;
loading?: boolean;
maxHeight?: string;
showLineNumbers?: boolean;
backgroundColor?: string;
}

const getBlockStyles = (props: {
inline?: boolean;
backgroundColor?: string;
}) => {
if (props.inline) {
return `
display: inline;
border: none;
background: transparent !important;
padding: 0;
* { display: inline !important; }
`;
}
return `
background: ${props.backgroundColor || 'var(--gray-1)'} !important;
padding: 8px;
`;
};

export const Block = styled.div<{
maxHeight?: string;
inline?: boolean;
backgroundColor?: string;
}>`
position: relative;
white-space: pre;
font-size: 13px;
border: 1px var(--gray-4) solid;
border-radius: 4px;
font-family: 'Source Code Pro', monospace;
user-select: text;
cursor: text;
&:focus {
outline: none;
}
${getBlockStyles}

.adm-code-wrap {
${(props) => (props.inline ? '' : 'overflow: auto;')}
${(props) => (props.maxHeight ? `max-height: ${props.maxHeight}px;` : ``)}
user-select: text;
}

.adm-code-line {
display: block;
user-select: text;
&-number {
user-select: none;
display: inline-block;
min-width: 14px;
text-align: right;
margin-right: 1em;
color: var(--gray-6);
font-weight: 700;
font-size: 12px;
}
}
`;

export const CopyText = styled(Typography.Text)`
position: absolute;
top: 0;
right: 0;
font-size: 0;
button {
background: var(--gray-1) !important;
}

.ant-typography-copy {
font-size: 12px;
}

.ant-btn:not(:hover) {
color: var(--gray-8);
}
`;

export const addThemeStyleManually = (cssText: string) => {
const id = 'ace-tomorrow';
const themeElement = document.getElementById(id);
if (!themeElement) {
const styleElement = document.createElement('style');
styleElement.id = id;
document.head.appendChild(styleElement);
styleElement.appendChild(document.createTextNode(cssText));
}
};

export const createCodeBlock = (HighlightRules: any) => {
return function CodeBlock(props: BaseProps) {
const {
code,
copyable,
maxHeight,
inline,
loading,
showLineNumbers,
backgroundColor,
} = props;
const { ace } = window as any;
const { Tokenizer } = ace.require('ace/tokenizer');
const rules = new HighlightRules();
const tokenizer = new Tokenizer(rules.getRules());

useEffect(() => {
const { cssText } = ace.require('ace/theme/tomorrow');
addThemeStyleManually(cssText);
}, []);

const lines = (code || '').split('\n').map((line, index) => {
const tokens = tokenizer.getLineTokens(line).tokens;
const children = tokens.map((token, index) => {
const classNames = token.type.split('.').map((name) => `ace_${name}`);
return (
<span key={index} className={classNames.join(' ')}>
{token.value}
</span>
);
});

return (
<span className="adm-code-line ace_line" key={`${line}-${index}`}>
{showLineNumbers && (
<span className="adm-code-line-number">{index + 1}</span>
)}
{children}
</span>
);
});

const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'a') {
e.preventDefault();
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(
e.currentTarget.querySelector('.adm-code-wrap') || e.currentTarget,
);
selection?.removeAllRanges();
selection?.addRange(range);
}
};

return (
<Block
className="ace_editor ace-tomorrow adm_code-block"
maxHeight={maxHeight}
inline={inline}
backgroundColor={backgroundColor}
tabIndex={0}
onKeyDown={handleKeyDown}
>
<Loading spinning={loading}>
<div className="adm-code-wrap">
{lines}
{copyable && (
<CopyText
copyable={{
icon: [
<Button
key="copy-icon"
icon={<CopyOutlined />}
size="small"
style={{ backgroundColor: 'transparent' }}
/>,
<Button
key="copied-icon"
icon={<CheckOutlined className="green-6" />}
size="small"
/>,
],
text: code,
}}
>
{code}
</CopyText>
)}
</div>
</Loading>
</Block>
);
};
};
14 changes: 14 additions & 0 deletions wren-ui/src/components/code/JsonCodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createCodeBlock, BaseProps } from './BaseCodeBlock';

const JsonCodeBlock = (props: BaseProps) => {
const { code, ...rest } = props;
const { ace } = window as any;
const formattedJson =
typeof code === 'string' ? code : JSON.stringify(code, null, 2);

const { JsonHighlightRules } = ace.require('ace/mode/json_highlight_rules');
const BaseCodeBlock = createCodeBlock(JsonHighlightRules);
return <BaseCodeBlock code={formattedJson} {...rest} />;
};

export default JsonCodeBlock;
Loading