balibabu
commited on
Commit
·
739dd81
1
Parent(s):
43cb979
feat: Add component TuShare #1739 (#2745)
Browse files### What problem does this PR solve?
feat: Add component TuShare #1739
### Type of change
- [ ] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):
- web/.umirc.ts +1 -1
- web/src/assets/svg/tushare.svg +10 -0
- web/src/locales/en.ts +17 -0
- web/src/locales/zh-traditional.ts +17 -0
- web/src/locales/zh.ts +17 -0
- web/src/pages/flow/constant.tsx +24 -0
- web/src/pages/flow/flow-drawer/index.tsx +2 -0
- web/src/pages/flow/hooks.ts +2 -0
- web/src/pages/flow/tushare-form/index.tsx +82 -0
web/.umirc.ts
CHANGED
|
@@ -30,7 +30,7 @@ export default defineConfig({
|
|
| 30 |
copy: ['src/conf.json'],
|
| 31 |
proxy: {
|
| 32 |
'/v1': {
|
| 33 |
-
target: 'http://127.0.0.1:
|
| 34 |
changeOrigin: true,
|
| 35 |
ws: true,
|
| 36 |
logger: console,
|
|
|
|
| 30 |
copy: ['src/conf.json'],
|
| 31 |
proxy: {
|
| 32 |
'/v1': {
|
| 33 |
+
target: 'http://127.0.0.1:9456/',
|
| 34 |
changeOrigin: true,
|
| 35 |
ws: true,
|
| 36 |
logger: console,
|
web/src/assets/svg/tushare.svg
ADDED
|
|
web/src/locales/en.ts
CHANGED
|
@@ -968,6 +968,23 @@ The above is the content you need to summarize.`,
|
|
| 968 |
concentrator: 'Concentrator',
|
| 969 |
concentratorDescription:
|
| 970 |
'A component that receives the output from the upstream component and passes it on as input to the downstream components.',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 971 |
},
|
| 972 |
footer: {
|
| 973 |
profile: 'All rights reserved @ React',
|
|
|
|
| 968 |
concentrator: 'Concentrator',
|
| 969 |
concentratorDescription:
|
| 970 |
'A component that receives the output from the upstream component and passes it on as input to the downstream components.',
|
| 971 |
+
tuShare: 'TuShare',
|
| 972 |
+
tuShareDescription:
|
| 973 |
+
'This component can be used to obtain financial news briefs from mainstream financial websites, aiding industry and quantitative research.',
|
| 974 |
+
tuShareSrcOptions: {
|
| 975 |
+
sina: 'Sina',
|
| 976 |
+
wallstreetcn: 'wallstreetcn',
|
| 977 |
+
'10jqka': 'Straight flush',
|
| 978 |
+
eastmoney: 'Eastmoney',
|
| 979 |
+
yuncaijing: 'YUNCAIJING',
|
| 980 |
+
fenghuang: 'FENGHUANG',
|
| 981 |
+
jinrongjie: 'JRJ',
|
| 982 |
+
},
|
| 983 |
+
token: 'Token',
|
| 984 |
+
src: 'Source',
|
| 985 |
+
startDate: 'Start date',
|
| 986 |
+
endDate: 'End date',
|
| 987 |
+
keyword: 'Keyword',
|
| 988 |
},
|
| 989 |
footer: {
|
| 990 |
profile: 'All rights reserved @ React',
|
web/src/locales/zh-traditional.ts
CHANGED
|
@@ -920,6 +920,23 @@ export default {
|
|
| 920 |
concentrator: '集線器',
|
| 921 |
concentratorDescription:
|
| 922 |
'此組件可用於連接多個下游組件。它接收來自上游組件的輸入並將其傳遞給每個下游組件。 ',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 923 |
},
|
| 924 |
footer: {
|
| 925 |
profile: '“保留所有權利 @ react”',
|
|
|
|
| 920 |
concentrator: '集線器',
|
| 921 |
concentratorDescription:
|
| 922 |
'此組件可用於連接多個下游組件。它接收來自上游組件的輸入並將其傳遞給每個下游組件。 ',
|
| 923 |
+
tuShare: 'TuShare',
|
| 924 |
+
tuShareDescription:
|
| 925 |
+
'該組件可用於從主流金融網站獲取金融新聞簡報,輔助行業和量化研究。 ',
|
| 926 |
+
tuShareSrcOptions: {
|
| 927 |
+
sina: '新浪財經',
|
| 928 |
+
wallstreetcn: '華爾街見聞',
|
| 929 |
+
'10jqka': '同花順',
|
| 930 |
+
eastmoney: '東方財富',
|
| 931 |
+
yuncaijing: '雲財經',
|
| 932 |
+
fenghuang: '鳳凰新聞',
|
| 933 |
+
jinrongjie: '金融界',
|
| 934 |
+
},
|
| 935 |
+
token: 'Token',
|
| 936 |
+
src: '來源',
|
| 937 |
+
startDate: '開始日期',
|
| 938 |
+
endDate: '結束日期',
|
| 939 |
+
keyword: '關鍵字',
|
| 940 |
},
|
| 941 |
footer: {
|
| 942 |
profile: '“保留所有權利 @ react”',
|
web/src/locales/zh.ts
CHANGED
|
@@ -938,6 +938,23 @@ export default {
|
|
| 938 |
concentrator: '集线器',
|
| 939 |
concentratorDescription:
|
| 940 |
'该组件可用于连接多个下游组件。它接收来自上游组件的输入并将其传递给每个下游组件。',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 941 |
},
|
| 942 |
footer: {
|
| 943 |
profile: 'All rights reserved @ React',
|
|
|
|
| 938 |
concentrator: '集线器',
|
| 939 |
concentratorDescription:
|
| 940 |
'该组件可用于连接多个下游组件。它接收来自上游组件的输入并将其传递给每个下游组件。',
|
| 941 |
+
tuShare: 'TuShare',
|
| 942 |
+
tuShareDescription:
|
| 943 |
+
'该组件可用于从主流金融网站获取金融新闻简报,辅助行业和量化研究。',
|
| 944 |
+
tuShareSrcOptions: {
|
| 945 |
+
sina: '新浪财经',
|
| 946 |
+
wallstreetcn: '华尔街见闻',
|
| 947 |
+
'10jqka': '同花顺',
|
| 948 |
+
eastmoney: '东方财富',
|
| 949 |
+
yuncaijing: '云财经',
|
| 950 |
+
fenghuang: '凤凰新闻',
|
| 951 |
+
jinrongjie: '金融界',
|
| 952 |
+
},
|
| 953 |
+
token: 'Token',
|
| 954 |
+
src: '源',
|
| 955 |
+
startDate: '开始日期',
|
| 956 |
+
endDate: '结束日期',
|
| 957 |
+
keyword: '关键字',
|
| 958 |
},
|
| 959 |
footer: {
|
| 960 |
profile: 'All rights reserved @ React',
|
web/src/pages/flow/constant.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { ReactComponent as KeywordIcon } from '@/assets/svg/keyword.svg';
|
|
| 15 |
import { ReactComponent as PubMedIcon } from '@/assets/svg/pubmed.svg';
|
| 16 |
import { ReactComponent as QWeatherIcon } from '@/assets/svg/qweather.svg';
|
| 17 |
import { ReactComponent as SwitchIcon } from '@/assets/svg/switch.svg';
|
|
|
|
| 18 |
import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
|
| 19 |
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
|
| 20 |
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
|
@@ -69,6 +70,7 @@ export enum Operator {
|
|
| 69 |
YahooFinance = 'YahooFinance',
|
| 70 |
Jin10 = 'Jin10',
|
| 71 |
Concentrator = 'Concentrator',
|
|
|
|
| 72 |
}
|
| 73 |
|
| 74 |
export const operatorIconMap = {
|
|
@@ -100,6 +102,7 @@ export const operatorIconMap = {
|
|
| 100 |
[Operator.YahooFinance]: YahooFinanceIcon,
|
| 101 |
[Operator.Jin10]: Jin10Icon,
|
| 102 |
[Operator.Concentrator]: ConcentratorIcon,
|
|
|
|
| 103 |
};
|
| 104 |
|
| 105 |
export const operatorMap: Record<
|
|
@@ -221,6 +224,7 @@ export const operatorMap: Record<
|
|
| 221 |
fontSize: 10,
|
| 222 |
iconFontSize: 16,
|
| 223 |
},
|
|
|
|
| 224 |
};
|
| 225 |
|
| 226 |
export const componentMenuList = [
|
|
@@ -305,6 +309,9 @@ export const componentMenuList = [
|
|
| 305 |
{
|
| 306 |
name: Operator.Jin10,
|
| 307 |
},
|
|
|
|
|
|
|
|
|
|
| 308 |
];
|
| 309 |
|
| 310 |
export const initialRetrievalValues = {
|
|
@@ -467,6 +474,12 @@ export const initialJin10Values = {
|
|
| 467 |
|
| 468 |
export const initialConcentratorValues = {};
|
| 469 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
export const CategorizeAnchorPointPositions = [
|
| 471 |
{ top: 1, right: 34 },
|
| 472 |
{ top: 8, right: 18 },
|
|
@@ -542,6 +555,7 @@ export const RestrictedUpstreamMap = {
|
|
| 542 |
[Operator.YahooFinance]: [Operator.Begin],
|
| 543 |
[Operator.Jin10]: [Operator.Begin],
|
| 544 |
[Operator.Concentrator]: [Operator.Begin],
|
|
|
|
| 545 |
};
|
| 546 |
|
| 547 |
export const NodeMap = {
|
|
@@ -573,6 +587,7 @@ export const NodeMap = {
|
|
| 573 |
[Operator.AkShare]: 'ragNode',
|
| 574 |
[Operator.YahooFinance]: 'ragNode',
|
| 575 |
[Operator.Jin10]: 'ragNode',
|
|
|
|
| 576 |
};
|
| 577 |
|
| 578 |
export const LanguageOptions = [
|
|
@@ -2750,3 +2765,12 @@ export const Jin10CalendarTypeOptions = ['cj', 'qh', 'hk', 'us'];
|
|
| 2750 |
export const Jin10CalendarDatashapeOptions = ['data', 'event', 'holiday'];
|
| 2751 |
export const Jin10SymbolsTypeOptions = ['GOODS', 'FOREX', 'FUTURE', 'CRYPTO'];
|
| 2752 |
export const Jin10SymbolsDatatypeOptions = ['symbols', 'quotes'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
import { ReactComponent as PubMedIcon } from '@/assets/svg/pubmed.svg';
|
| 16 |
import { ReactComponent as QWeatherIcon } from '@/assets/svg/qweather.svg';
|
| 17 |
import { ReactComponent as SwitchIcon } from '@/assets/svg/switch.svg';
|
| 18 |
+
import { ReactComponent as TuShareIcon } from '@/assets/svg/tushare.svg';
|
| 19 |
import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
|
| 20 |
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
|
| 21 |
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
|
|
|
| 70 |
YahooFinance = 'YahooFinance',
|
| 71 |
Jin10 = 'Jin10',
|
| 72 |
Concentrator = 'Concentrator',
|
| 73 |
+
TuShare = 'TuShare',
|
| 74 |
}
|
| 75 |
|
| 76 |
export const operatorIconMap = {
|
|
|
|
| 102 |
[Operator.YahooFinance]: YahooFinanceIcon,
|
| 103 |
[Operator.Jin10]: Jin10Icon,
|
| 104 |
[Operator.Concentrator]: ConcentratorIcon,
|
| 105 |
+
[Operator.TuShare]: TuShareIcon,
|
| 106 |
};
|
| 107 |
|
| 108 |
export const operatorMap: Record<
|
|
|
|
| 224 |
fontSize: 10,
|
| 225 |
iconFontSize: 16,
|
| 226 |
},
|
| 227 |
+
[Operator.TuShare]: { backgroundColor: '#f8cfa0' },
|
| 228 |
};
|
| 229 |
|
| 230 |
export const componentMenuList = [
|
|
|
|
| 309 |
{
|
| 310 |
name: Operator.Jin10,
|
| 311 |
},
|
| 312 |
+
{
|
| 313 |
+
name: Operator.TuShare,
|
| 314 |
+
},
|
| 315 |
];
|
| 316 |
|
| 317 |
export const initialRetrievalValues = {
|
|
|
|
| 474 |
|
| 475 |
export const initialConcentratorValues = {};
|
| 476 |
|
| 477 |
+
export const initialTuShareValues = {
|
| 478 |
+
token: 'xxx',
|
| 479 |
+
src: 'eastmoney',
|
| 480 |
+
start_date: '2024-01-01 09:00:00',
|
| 481 |
+
};
|
| 482 |
+
|
| 483 |
export const CategorizeAnchorPointPositions = [
|
| 484 |
{ top: 1, right: 34 },
|
| 485 |
{ top: 8, right: 18 },
|
|
|
|
| 555 |
[Operator.YahooFinance]: [Operator.Begin],
|
| 556 |
[Operator.Jin10]: [Operator.Begin],
|
| 557 |
[Operator.Concentrator]: [Operator.Begin],
|
| 558 |
+
[Operator.TuShare]: [Operator.Begin],
|
| 559 |
};
|
| 560 |
|
| 561 |
export const NodeMap = {
|
|
|
|
| 587 |
[Operator.AkShare]: 'ragNode',
|
| 588 |
[Operator.YahooFinance]: 'ragNode',
|
| 589 |
[Operator.Jin10]: 'ragNode',
|
| 590 |
+
[Operator.TuShare]: 'ragNode',
|
| 591 |
};
|
| 592 |
|
| 593 |
export const LanguageOptions = [
|
|
|
|
| 2765 |
export const Jin10CalendarDatashapeOptions = ['data', 'event', 'holiday'];
|
| 2766 |
export const Jin10SymbolsTypeOptions = ['GOODS', 'FOREX', 'FUTURE', 'CRYPTO'];
|
| 2767 |
export const Jin10SymbolsDatatypeOptions = ['symbols', 'quotes'];
|
| 2768 |
+
export const TuShareSrcOptions = [
|
| 2769 |
+
'sina',
|
| 2770 |
+
'wallstreetcn',
|
| 2771 |
+
'10jqka',
|
| 2772 |
+
'eastmoney',
|
| 2773 |
+
'yuncaijing',
|
| 2774 |
+
'fenghuang',
|
| 2775 |
+
'jinrongjie',
|
| 2776 |
+
];
|
web/src/pages/flow/flow-drawer/index.tsx
CHANGED
|
@@ -29,6 +29,7 @@ import RelevantForm from '../relevant-form';
|
|
| 29 |
import RetrievalForm from '../retrieval-form';
|
| 30 |
import RewriteQuestionForm from '../rewrite-question-form';
|
| 31 |
import SwitchForm from '../switch-form';
|
|
|
|
| 32 |
import WenCaiForm from '../wencai-form';
|
| 33 |
import WikipediaForm from '../wikipedia-form';
|
| 34 |
|
|
@@ -68,6 +69,7 @@ const FormMap = {
|
|
| 68 |
[Operator.AkShare]: AkShareForm,
|
| 69 |
[Operator.YahooFinance]: YahooFinanceForm,
|
| 70 |
[Operator.Jin10]: Jin10Form,
|
|
|
|
| 71 |
};
|
| 72 |
|
| 73 |
const EmptyContent = () => <div>empty</div>;
|
|
|
|
| 29 |
import RetrievalForm from '../retrieval-form';
|
| 30 |
import RewriteQuestionForm from '../rewrite-question-form';
|
| 31 |
import SwitchForm from '../switch-form';
|
| 32 |
+
import TuShareForm from '../tushare-form';
|
| 33 |
import WenCaiForm from '../wencai-form';
|
| 34 |
import WikipediaForm from '../wikipedia-form';
|
| 35 |
|
|
|
|
| 69 |
[Operator.AkShare]: AkShareForm,
|
| 70 |
[Operator.YahooFinance]: YahooFinanceForm,
|
| 71 |
[Operator.Jin10]: Jin10Form,
|
| 72 |
+
[Operator.TuShare]: TuShareForm,
|
| 73 |
};
|
| 74 |
|
| 75 |
const EmptyContent = () => <div>empty</div>;
|
web/src/pages/flow/hooks.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
| 55 |
initialRetrievalValues,
|
| 56 |
initialRewriteQuestionValues,
|
| 57 |
initialSwitchValues,
|
|
|
|
| 58 |
initialWenCaiValues,
|
| 59 |
initialWikipediaValues,
|
| 60 |
initialYahooFinanceValues,
|
|
@@ -123,6 +124,7 @@ export const useInitializeOperatorParams = () => {
|
|
| 123 |
[Operator.YahooFinance]: initialYahooFinanceValues,
|
| 124 |
[Operator.Jin10]: initialJin10Values,
|
| 125 |
[Operator.Concentrator]: initialConcentratorValues,
|
|
|
|
| 126 |
};
|
| 127 |
}, [llmId]);
|
| 128 |
|
|
|
|
| 55 |
initialRetrievalValues,
|
| 56 |
initialRewriteQuestionValues,
|
| 57 |
initialSwitchValues,
|
| 58 |
+
initialTuShareValues,
|
| 59 |
initialWenCaiValues,
|
| 60 |
initialWikipediaValues,
|
| 61 |
initialYahooFinanceValues,
|
|
|
|
| 124 |
[Operator.YahooFinance]: initialYahooFinanceValues,
|
| 125 |
[Operator.Jin10]: initialJin10Values,
|
| 126 |
[Operator.Concentrator]: initialConcentratorValues,
|
| 127 |
+
[Operator.TuShare]: initialTuShareValues,
|
| 128 |
};
|
| 129 |
}, [llmId]);
|
| 130 |
|
web/src/pages/flow/tushare-form/index.tsx
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useTranslate } from '@/hooks/common-hooks';
|
| 2 |
+
import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd';
|
| 3 |
+
import dayjs from 'dayjs';
|
| 4 |
+
import { useCallback, useMemo } from 'react';
|
| 5 |
+
import { TuShareSrcOptions } from '../constant';
|
| 6 |
+
import { IOperatorForm } from '../interface';
|
| 7 |
+
|
| 8 |
+
const DateTimePicker = ({
|
| 9 |
+
onChange,
|
| 10 |
+
value,
|
| 11 |
+
}: {
|
| 12 |
+
onChange?: (val: number | undefined) => void;
|
| 13 |
+
value?: number | undefined;
|
| 14 |
+
}) => {
|
| 15 |
+
const handleChange: DatePickerProps['onChange'] = useCallback(
|
| 16 |
+
(val: any) => {
|
| 17 |
+
const nextVal = val?.format('YYYY-MM-DD HH:mm:ss');
|
| 18 |
+
onChange?.(nextVal ? nextVal : undefined);
|
| 19 |
+
},
|
| 20 |
+
[onChange],
|
| 21 |
+
);
|
| 22 |
+
// The value needs to be converted into a string and saved to the backend
|
| 23 |
+
const nextValue = useMemo(() => {
|
| 24 |
+
if (value) {
|
| 25 |
+
return dayjs(value);
|
| 26 |
+
}
|
| 27 |
+
return undefined;
|
| 28 |
+
}, [value]);
|
| 29 |
+
|
| 30 |
+
return (
|
| 31 |
+
<DatePicker
|
| 32 |
+
showTime
|
| 33 |
+
format="YYYY-MM-DD HH:mm:ss"
|
| 34 |
+
onChange={handleChange}
|
| 35 |
+
value={nextValue}
|
| 36 |
+
/>
|
| 37 |
+
);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
const TuShareForm = ({ onValuesChange, form }: IOperatorForm) => {
|
| 41 |
+
const { t } = useTranslate('flow');
|
| 42 |
+
|
| 43 |
+
const tuShareSrcOptions = useMemo(() => {
|
| 44 |
+
return TuShareSrcOptions.map((x) => ({
|
| 45 |
+
value: x,
|
| 46 |
+
label: t(`tuShareSrcOptions.${x}`),
|
| 47 |
+
}));
|
| 48 |
+
}, [t]);
|
| 49 |
+
|
| 50 |
+
return (
|
| 51 |
+
<Form
|
| 52 |
+
name="basic"
|
| 53 |
+
labelCol={{ span: 6 }}
|
| 54 |
+
wrapperCol={{ span: 18 }}
|
| 55 |
+
autoComplete="off"
|
| 56 |
+
form={form}
|
| 57 |
+
onValuesChange={onValuesChange}
|
| 58 |
+
>
|
| 59 |
+
<Form.Item
|
| 60 |
+
label={t('token')}
|
| 61 |
+
name={'token'}
|
| 62 |
+
tooltip={'Get from https://tushare.pro/'}
|
| 63 |
+
>
|
| 64 |
+
<Input></Input>
|
| 65 |
+
</Form.Item>
|
| 66 |
+
<Form.Item label={t('src')} name={'src'}>
|
| 67 |
+
<Select options={tuShareSrcOptions}></Select>
|
| 68 |
+
</Form.Item>
|
| 69 |
+
<Form.Item label={t('startDate')} name={'start_date'}>
|
| 70 |
+
<DateTimePicker />
|
| 71 |
+
</Form.Item>
|
| 72 |
+
<Form.Item label={t('endDate')} name={'end_date'}>
|
| 73 |
+
<DateTimePicker />
|
| 74 |
+
</Form.Item>
|
| 75 |
+
<Form.Item label={t('keyword')} name={'keyword'}>
|
| 76 |
+
<Input></Input>
|
| 77 |
+
</Form.Item>
|
| 78 |
+
</Form>
|
| 79 |
+
);
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
export default TuShareForm;
|