diff --git a/src/components/ContactUsModal/index.tsx b/src/components/ContactUsModal/index.tsx
index 68bfcc929..f90ce8e9c 100644
--- a/src/components/ContactUsModal/index.tsx
+++ b/src/components/ContactUsModal/index.tsx
@@ -1,7 +1,7 @@
import { Flex, Text, Input, Textarea } from '@chakra-ui/react';
import BaseModal from '../BaseModal';
import s from './styles2.module.scss';
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import { isEmpty } from 'lodash';
import { submitContact } from '@/services/api/l2services';
@@ -12,6 +12,7 @@ import { Select } from '@chakra-ui/react';
import { useL2ServiceTracking } from '@/hooks/useL2ServiceTracking';
import { DATA_BRAND } from '@/modules/landingV3/data-sections';
import Image from 'next/image';
+import { throttle } from 'lodash';
const SUBJECT_LIST = [
`I'd like to build a Rollup on Bitcoin`,
@@ -106,8 +107,17 @@ const ContactUsModal = ({
}
};
- const submitHandler = async () => {
+ const submitHandler = async (
+ methodInputStr: any,
+ methodContact: METHODS_CONTACT_ENUM,
+ ) => {
+ // console.log('submitHandler Params ', {
+ // methodInputStr,
+ // methodContact,
+ // });
+
tracking('SUBMIT_CONTACT_US');
+
try {
let valid = true;
// if (!valideYourXAcc(yourXAcc)) {
@@ -120,12 +130,12 @@ const ContactUsModal = ({
// valid = false;
// }
- if (!validateMethodContact(methodInput)) {
+ if (!validateMethodContact(methodInputStr)) {
valid = false;
}
- // console.log('valid ', valid);
- // console.log('methodContact ', methodContact);
+ // console.log('LOG valid ', valid);
+ // console.log('LOG methodContact ', methodContact);
if (valid) {
let submitParams: SubmitFormParams = {
@@ -144,21 +154,21 @@ const ContactUsModal = ({
if (methodContact === METHODS_CONTACT_ENUM.Email) {
submitParams = {
...submitParams,
- email: methodInput,
+ email: methodInputStr,
};
}
if (methodContact === METHODS_CONTACT_ENUM.Telegram) {
submitParams = {
...submitParams,
- telegram: methodInput,
+ telegram: methodInputStr,
};
}
if (methodContact === METHODS_CONTACT_ENUM.Twitter) {
submitParams = {
...submitParams,
- twName: methodInput,
+ twName: methodInputStr,
};
}
@@ -176,6 +186,14 @@ const ContactUsModal = ({
}
};
+ const submitHanlderDebouce = useCallback(
+ throttle(submitHandler, 500, {
+ trailing: true,
+ leading: false,
+ }),
+ [],
+ );
+
const renderXfield = () => {
return (
*/}
{/* */}
-
+
submitHanlderDebouce(methodInput, methodContact)}
+ >
Submit
diff --git a/src/components/MagicIcon/index.tsx b/src/components/MagicIcon/index.tsx
new file mode 100644
index 000000000..57a8adf5a
--- /dev/null
+++ b/src/components/MagicIcon/index.tsx
@@ -0,0 +1,43 @@
+import { ReactElement } from 'react';
+
+export default function MagicIcon({
+ color,
+}: {
+ color: 'white' | 'black';
+}): ReactElement {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index 76e737c89..38564e6f4 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -8,13 +8,9 @@ const MULTIPLE_POINT_SYMBOL = 'SHARD';
const BVM_TOKEN_SYMBOL = '$BVM';
export {
- MIN_DECIMAL,
- MAX_DECIMAL,
- NATIVE_ETH_ADDRESS,
- METAMASK_DOWNLOAD_PAGE,
- MULTIPLE_POINT_SYMBOL,
- BVM_TOKEN_SYMBOL,
+ BVM_TOKEN_SYMBOL, MAX_DECIMAL, METAMASK_DOWNLOAD_PAGE, MIN_DECIMAL, MULTIPLE_POINT_SYMBOL, NATIVE_ETH_ADDRESS
};
+
export const ALLOWED_ATTRIBUTES = {
'*': ['style'],
diff --git a/src/modules/blockchains/Buy/component4/Lego/index.tsx b/src/modules/blockchains/Buy/component4/Lego/index.tsx
index 88545fb23..18ed4e923 100644
--- a/src/modules/blockchains/Buy/component4/Lego/index.tsx
+++ b/src/modules/blockchains/Buy/component4/Lego/index.tsx
@@ -1,16 +1,15 @@
-import React from 'react';
import cn from 'classnames';
+import React from 'react';
import SvgInset from '@/components/SvgInset';
import { adjustBrightness } from '../../utils';
-import styles from './styles.module.scss';
-import { Box, Flex, Image, Tooltip } from '@chakra-ui/react';
-import { FieldModel } from '@/types/customize-model';
-import { legoDragging } from '@/modules/blockchains/dapp/ui-helper/LegoDragging';
-import { useCaptureStore } from '@/modules/blockchains/Buy/stores/index_v3';
import { iconToolNames } from '@/modules/blockchains/Buy/Buy.data';
+import { useCaptureStore } from '@/modules/blockchains/Buy/stores/index_v3';
+import { FieldModel } from '@/types/customize-model';
+import { Flex, Image, Tooltip } from '@chakra-ui/react';
+import styles from './styles.module.scss';
type Position =
| {
@@ -50,6 +49,7 @@ type Props = {
children?: React.ReactNode;
preview?: boolean;
checked?: boolean;
+ legoAI?: boolean;
fields?: FieldModel[];
infoLego?: {
title: string;
@@ -64,6 +64,7 @@ const Lego = (props: Props) => {
background = '#c4513a',
icon,
title,
+ legoAI,
tooltip,
titleInLeft = false,
titleInRight = false,
@@ -117,7 +118,13 @@ const Lego = (props: Props) => {
}}
ref={legoRef}
>
-
+
diff --git a/src/modules/blockchains/Buy/component4/Lego/styles.module.scss b/src/modules/blockchains/Buy/component4/Lego/styles.module.scss
index 7f18d35d0..0c4e28e97 100644
--- a/src/modules/blockchains/Buy/component4/Lego/styles.module.scss
+++ b/src/modules/blockchains/Buy/component4/Lego/styles.module.scss
@@ -28,7 +28,7 @@
left: 11px;
&__top {
- top: -1px;
+ top: -1.5px;
svg path {
fill: #ffffff !important;
}
@@ -106,7 +106,7 @@
.titleSingle {
width: max-content;
- padding-top: .15em;
+ padding-top: 0.15em;
display: block;
// font-size: 14px;
// font-weight: 500;
@@ -122,3 +122,13 @@
height: 100%;
}
}
+
+.lego__piece__top__ai {
+ top: -1px;
+ svg path {
+ fill: #ffeee8 !important;
+ }
+ &::after {
+ background-color: #ffeee8;
+ }
+}
diff --git a/src/modules/blockchains/Buy/components3/ComputerNameInput/index_v2.tsx b/src/modules/blockchains/Buy/components3/ComputerNameInput/index_v2.tsx
index ad1866388..880c5b19b 100644
--- a/src/modules/blockchains/Buy/components3/ComputerNameInput/index_v2.tsx
+++ b/src/modules/blockchains/Buy/components3/ComputerNameInput/index_v2.tsx
@@ -19,6 +19,7 @@ const ComputerNameInput = () => {
useChainProvider();
const {
computerName,
+ isComputerNameFocused,
setComputerName,
setComputerNameFocused,
setComputerNameErrMsg,
@@ -56,15 +57,19 @@ const ComputerNameInput = () => {
};
useEffect(() => {
- let computerName;
+ let computerNameStr;
if (isCreateChainFlow) {
- computerName = `${PREFIX} ${chainID}`;
+ if (isComputerNameFocused) {
+ computerNameStr = `${computerName}`;
+ } else {
+ computerNameStr = `${PREFIX} ${chainID}`;
+ }
} else {
- computerName = order?.chainName || '';
+ computerNameStr = order?.chainName || '';
}
- setChainName(computerName);
- setComputerName(computerName);
- }, [isCreateChainFlow, order, chainID]);
+ setChainName(computerNameStr);
+ setComputerName(computerNameStr);
+ }, [isCreateChainFlow, order, chainID, isComputerNameFocused, computerName]);
return (
@@ -77,6 +82,7 @@ const ComputerNameInput = () => {
onChange={(e: any) => {
const text = e.target.value;
onChangeHandler(text);
+ setComputerNameFocused(true);
}}
onBlur={(e: any) => {
const text = e.target.value;
diff --git a/src/modules/blockchains/Buy/components3/NetworkDropdown/index.tsx b/src/modules/blockchains/Buy/components3/NetworkDropdown/index.tsx
index e61d730b7..e37e42c88 100644
--- a/src/modules/blockchains/Buy/components3/NetworkDropdown/index.tsx
+++ b/src/modules/blockchains/Buy/components3/NetworkDropdown/index.tsx
@@ -78,6 +78,12 @@ const NetworkDropdown = ({}: Props) => {
[currentValue?.icon],
);
+ React.useEffect(() => {
+ if (field['network']?.value === null) {
+ setField('network', options[0].key, true);
+ }
+ }, [field['network']?.value]);
+
return (
{
const { field } = useOrderFormStoreV3();
const { dappCount } = useFormDappToFormChain();
+ console.log('[useFormChain] field', field);
+
const getDynamicForm = () => {
- if (!categories)
+ if (!categories) {
return {
dynamicForm: [],
allOptionKeyDragged: [],
allRequiredForKey: [],
optionMapping: {},
};
+ }
const ignoreKeys = ['bridge_apps', 'wallet', 'gaming_apps'];
const dynamicForm: IModelCategory[] = [];
@@ -97,6 +100,8 @@ const useFormChain = () => {
field.options = uniqBy(field.options, 'key');
});
+ console.log('[useFormChain] getDynamicForm', dynamicForm);
+
return {
dynamicForm,
allOptionKeyDragged,
diff --git a/src/modules/blockchains/Buy/hooks/useNodeFlowControl.ts b/src/modules/blockchains/Buy/hooks/useNodeFlowControl.ts
index fa3b4914d..c4485bbf5 100644
--- a/src/modules/blockchains/Buy/hooks/useNodeFlowControl.ts
+++ b/src/modules/blockchains/Buy/hooks/useNodeFlowControl.ts
@@ -31,6 +31,7 @@ import useModelCategoriesStore from '../stores/useModelCategoriesStore';
import handleStatusEdges from '@utils/helpers';
import { useAAModule } from '@/modules/blockchains/detail_v4/hook/useAAModule';
import { useBridgesModule } from '@/modules/blockchains/detail_v4/hook/useBridgesModule';
+import { IModelOption } from '@/types/customize-model';
export default function useNodeFlowControl() {
const { dapps } = useDapps();
@@ -96,7 +97,7 @@ export default function useNodeFlowControl() {
};
useSignalEffect(() => {
- console.log('[useNodeFlowControl]', {nodes});
+ console.log('[useNodeFlowControl]', { nodes });
needReactFlowRenderSignal.value = true;
@@ -128,7 +129,7 @@ export default function useNodeFlowControl() {
title: thisDapp.title,
dapp: thisDapp,
baseIndex: draggedIds2D.length - 1,
- categoryOption,
+ categoryOption: categoryOption as IModelOption,
ids: draggedIds2D[draggedIds2D.length - 1],
targetHandles: [`account_abstraction-t-${rootNode}`],
sourceHandles: [],
@@ -189,7 +190,7 @@ export default function useNodeFlowControl() {
title: thisDapp.title,
dapp: thisDapp,
baseIndex: 0,
- categoryOption: {},
+ categoryOption: {} as IModelOption,
ids: [],
targetHandles: [`bridge_apps-t-${rootNode}`],
sourceHandles: [],
@@ -248,7 +249,7 @@ export default function useNodeFlowControl() {
title: thisDapp.title,
dapp: thisDapp,
baseIndex: 0,
- categoryOption: {},
+ categoryOption: {} as IModelOption,
ids: [],
targetHandles: [`gaming_apps-t-${rootNode}`],
sourceHandles: [],
@@ -417,7 +418,7 @@ export default function useNodeFlowControl() {
title: thisDapp.title,
dapp: thisDapp,
baseIndex: draggedIds2D.length - 1,
- categoryOption,
+ categoryOption: categoryOption as IModelOption,
ids: draggedIds2D[draggedIds2D.length - 1],
targetHandles: [`${newNodeId}-t-${rootNode}`],
sourceHandles: [],
diff --git a/src/modules/blockchains/Buy/hooks/useSetDefaultDapp.ts b/src/modules/blockchains/Buy/hooks/useSetDefaultDapp.ts
index f37fe0629..9f88af24c 100644
--- a/src/modules/blockchains/Buy/hooks/useSetDefaultDapp.ts
+++ b/src/modules/blockchains/Buy/hooks/useSetDefaultDapp.ts
@@ -16,6 +16,7 @@ import useModelCategoriesStore from '../stores/useModelCategoriesStore';
import { needReactFlowRenderSignal } from '../studio/ReactFlowRender';
import { dappKeyToChainKey } from '../utils';
import useDapps from './useDapps';
+import { IModelOption } from '@/types/customize-model';
const useSetDefaultDapp = () => {
const searchParams = useSearchParams();
@@ -26,7 +27,7 @@ const useSetDefaultDapp = () => {
const { draggedIds2D, setDraggedIds2D } = useDraggedId2DStore();
- const [loaded, setLoaded] = useState
(false)
+ const [loaded, setLoaded] = useState(false);
const updateBaseDapp = (dappIndex: number) => {
const thisDapp = dapps[dappIndex];
@@ -96,7 +97,7 @@ const useSetDefaultDapp = () => {
title: thisDapp.title,
dapp: thisDapp,
baseIndex: 0,
- categoryOption,
+ categoryOption: categoryOption as IModelOption,
ids,
targetHandles: [`${newNodeId}-t-${rootNode}`],
sourceHandles: [],
@@ -168,8 +169,8 @@ const useSetDefaultDapp = () => {
};
React.useEffect(() => {
- if(nodes.length === 0 ) return;
- if(loaded) return;
+ if (nodes.length === 0) return;
+ if (loaded) return;
if (!categories || categories.length === 0 || dapps.length <= 2) return;
diff --git a/src/modules/blockchains/Buy/hooks/useTemplate.ts b/src/modules/blockchains/Buy/hooks/useTemplate.ts
index 5b3566b29..c93ef35e6 100644
--- a/src/modules/blockchains/Buy/hooks/useTemplate.ts
+++ b/src/modules/blockchains/Buy/hooks/useTemplate.ts
@@ -4,20 +4,22 @@ import useModelCategoriesStore from '@/modules/blockchains/Buy/stores/useModelCa
import { IModelCategory } from '@/types/customize-model';
import { useSearchParams } from 'next/navigation';
import { cloneDeep } from '../utils';
-import {
- useOptionInputStore
-} from '@/modules/blockchains/Buy/component4/DappRenderer/OptionInputValue/useOptionInputStore';
+import { useOptionInputStore } from '@/modules/blockchains/Buy/component4/DappRenderer/OptionInputValue/useOptionInputStore';
export default function useTemplate() {
const searchParams = useSearchParams();
const { setDraggedFields } = useDragStore();
const { parsedCategories, categoriesTemplates } = useModelCategoriesStore();
const { field, setField, setFields } = useOrderFormStoreV3();
- const {setValue} = useOptionInputStore();
+ const { setValue } = useOptionInputStore();
// console.log('useTemplate -> field', field);
const setTemplate = (template: IModelCategory[]) => {
+ if (template.length === 0) {
+ return;
+ }
+
const newFields = cloneDeep(field);
template.forEach((_field) => {
@@ -44,11 +46,11 @@ export default function useTemplate() {
newFields[_field.key].value = _field.options[0].key;
newFields[_field.key].dragged = true;
}
- _field.options.forEach(option=>{
- if(option.addOnInputs){
+ _field.options.forEach((option) => {
+ if (option.addOnInputs) {
setValue(option.key, option.addOnInputs.attrs?.value);
}
- })
+ });
_draggedFields.push(_field.key);
});
diff --git a/src/modules/blockchains/Buy/studio/ButtonStartChat/index.tsx b/src/modules/blockchains/Buy/studio/ButtonStartChat/index.tsx
new file mode 100644
index 000000000..2ba937fa4
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/ButtonStartChat/index.tsx
@@ -0,0 +1,49 @@
+import MagicIcon from '@/components/MagicIcon';
+import { gsap } from 'gsap';
+import { ReactElement, useEffect, useRef } from 'react';
+import Chatbox from '../Chatbox';
+import useChatBoxState from '../Chatbox/chatbox-store';
+import styles from './styles.module.scss';
+
+export default function ButtonStartChat(): ReactElement {
+ const { isChatboxOpen, setIsChatboxOpen } = useChatBoxState((state) => state);
+ const chatboxRef = useRef(null);
+
+ useEffect(() => {
+ if (chatboxRef.current) {
+ if (isChatboxOpen) {
+ gsap.fromTo(
+ chatboxRef.current,
+ { x: '100%' },
+ { x: '0%', duration: 0.5, ease: 'power2.out' },
+ );
+ } else {
+ gsap.to(chatboxRef.current, {
+ x: '100%',
+ duration: 0.5,
+ ease: 'power2.in',
+ });
+ }
+ }
+ }, [isChatboxOpen]);
+
+ return (
+ <>
+ setIsChatboxOpen(true)}>
+ Ai voice prompt
+
+
+
+
+ >
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/ButtonStartChat/styles.module.scss b/src/modules/blockchains/Buy/studio/ButtonStartChat/styles.module.scss
new file mode 100644
index 000000000..5e6d48469
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/ButtonStartChat/styles.module.scss
@@ -0,0 +1,27 @@
+.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 15px;
+ background: linear-gradient(360deg, #00789F 0%, #32009B 14.56%, #7500D2 44.15%, #DA00DE 84.23%, #FEDEC0 100%);
+ color: white;
+ border: none;
+ border-radius: 100px;
+ font-size: 14px;
+ font-weight: 600;
+ font-family: 'SF Pro Display', sans-serif;
+ text-transform: uppercase;
+ cursor: pointer;
+ transition: background 0.3s ease;
+ position: absolute;
+ top: 10px;
+ right: 10px;
+
+ &:hover {
+ background: linear-gradient(360deg, #005F7F 0%, #26007B 14.56%, #5E00A8 44.15%, #AE00B2 84.23%, #CBB299 100%);
+ }
+
+ svg {
+ margin-left: 8px;
+ }
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonApply/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonApply/index.tsx
new file mode 100644
index 000000000..27b88deba
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonApply/index.tsx
@@ -0,0 +1,38 @@
+import useTemplate from '@/modules/blockchains/Buy/hooks/useTemplate';
+import useChatBoxState, { ChatBoxStatus } from '../../chatbox-store';
+import styles from './styles.module.scss';
+
+const ButtonApply = () => {
+ const { setTemplate } = useTemplate();
+ const { prepareCategoryTemplate, setChatBoxStatus } = useChatBoxState();
+
+ const handleApply = () => {
+ setChatBoxStatus({
+ status: ChatBoxStatus.Close,
+ isGenerating: false,
+ isComplete: false,
+ isListening: false,
+ });
+ setTemplate(prepareCategoryTemplate);
+ };
+
+ return (
+
+
+
+
+ Apply
+
+ );
+};
+
+export default ButtonApply;
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonApply/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonApply/styles.module.scss
new file mode 100644
index 000000000..826b1e789
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonApply/styles.module.scss
@@ -0,0 +1,19 @@
+.applyButton {
+ background-color: #FA4E0E;
+ color: #ffffff;
+ border-radius: 4px;
+ border: none;
+ cursor: pointer;
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: right;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding-left: 8px;
+ padding-right: 8px;
+ justify-content: center;
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonCancle/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonCancle/index.tsx
new file mode 100644
index 000000000..61d2ddf97
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonCancle/index.tsx
@@ -0,0 +1,31 @@
+import useChatBoxState from '../../chatbox-store';
+import styles from './styles.module.scss';
+
+export default function ButtonCancel({ onClick }: { onClick: () => void }) {
+ const { setIsChatboxOpen } = useChatBoxState();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Cancle
+
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonCancle/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonCancle/styles.module.scss
new file mode 100644
index 000000000..32b391244
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonCancle/styles.module.scss
@@ -0,0 +1,14 @@
+.voiceButton {
+ border: none;
+ cursor: pointer;
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: right;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 32px;
+ color: #555555;
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonClsoe/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonClsoe/index.tsx
new file mode 100644
index 000000000..24ebc0da0
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonClsoe/index.tsx
@@ -0,0 +1,34 @@
+import useChatBoxState from '../../chatbox-store';
+import styles from './styles.module.scss';
+
+export default function ButtonClose() {
+ const { setIsChatboxOpen } = useChatBoxState();
+
+ return (
+ setIsChatboxOpen(false)}
+ >
+
+
+
+
+
+
+
+
+
+
+ Close
+
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonClsoe/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonClsoe/styles.module.scss
new file mode 100644
index 000000000..a2c02c19a
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonClsoe/styles.module.scss
@@ -0,0 +1,14 @@
+.buttonClose {
+ border: none;
+ cursor: pointer;
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: right;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 32px;
+ color: #555555;
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonStop/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonStop/index.tsx
new file mode 100644
index 000000000..ce3e117c8
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonStop/index.tsx
@@ -0,0 +1,46 @@
+import useChatBoxState, { ChatBoxStatus } from '../../chatbox-store';
+import styles from './styles.module.scss';
+
+const ButtonStop = () => {
+ const { setChatBoxStatus } = useChatBoxState((state) => state);
+ return (
+
+ setChatBoxStatus({
+ status: ChatBoxStatus.Close,
+ isGenerating: false,
+ isComplete: false,
+ isListening: false,
+ })
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+ Stop chat
+
+ );
+};
+
+export default ButtonStop;
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonStop/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonStop/styles.module.scss
new file mode 100644
index 000000000..ce057f670
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Actions/ButtonStop/styles.module.scss
@@ -0,0 +1,14 @@
+.buttonStop {
+ border: none;
+ cursor: pointer;
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: right;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 32px;
+ color: #555555;
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/LabelListening/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/LabelListening/index.tsx
new file mode 100644
index 000000000..ebcceff69
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/LabelListening/index.tsx
@@ -0,0 +1,25 @@
+import styles from './styles.module.scss';
+
+export default function LabelListening() {
+ return (
+
+
+
+
+
+
+
+
+
Listening...
+
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/LabelListening/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/LabelListening/styles.module.scss
new file mode 100644
index 000000000..dfe5dd90c
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/LabelListening/styles.module.scss
@@ -0,0 +1,14 @@
+.listeningOverlay {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+ display: flex;
+ align-items: center;
+ font-family: SF Pro Display;
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: left;
+ color: #333333;
+ gap: 8px;
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Message/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/Message/index.tsx
new file mode 100644
index 000000000..e9c42570b
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Message/index.tsx
@@ -0,0 +1,120 @@
+import { IModelCategory } from '@/types/customize-model';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import Lego from '../../../component4/Lego';
+import useChatBoxState, { ChatBoxStatus } from '../chatbox-store';
+import styles from './styles.module.scss';
+
+export default function Message({
+ message,
+ template,
+ onUpdateScroll,
+}: {
+ message: string;
+ template: IModelCategory[];
+ onUpdateScroll: () => void;
+}) {
+ const { setChatBoxStatus, isGenerating, prepareCategoryTemplate } =
+ useChatBoxState((state) => state);
+ const [isRendered, setIsRendered] = useState(false);
+
+ const refRender = useRef();
+ const [displayedMessage, setDisplayedMessage] = useState('');
+ const [displayedTemplate, setDisplayedTemplate] = useState(
+ [],
+ );
+
+ const refTextRender = useRef('');
+
+ const animateMessage = useCallback(() => {
+ let messageIndex = 0;
+ let templateIndex = 0;
+ let optionIndex = 0;
+
+ refRender.current = setInterval(() => {
+ if (messageIndex < message.length) {
+ refTextRender.current += message[messageIndex];
+ setDisplayedMessage(refTextRender.current);
+ messageIndex++;
+ } else if (templateIndex < template.length) {
+ const currentTemplate = template[templateIndex];
+
+ if (optionIndex < currentTemplate.options.length) {
+ setDisplayedTemplate((prev) => {
+ const updatedTemplate = [...prev];
+
+ if (!updatedTemplate[templateIndex]) {
+ updatedTemplate[templateIndex] = {
+ ...currentTemplate,
+ options: [currentTemplate.options[optionIndex]],
+ };
+ } else {
+ updatedTemplate[templateIndex].options.push(
+ currentTemplate.options[optionIndex],
+ );
+ }
+
+ optionIndex++;
+
+ return updatedTemplate;
+ });
+ } else {
+ templateIndex++;
+ optionIndex = 0;
+ }
+ } else {
+ refRender.current && clearInterval(refRender.current);
+ setIsRendered(true);
+ setChatBoxStatus({
+ status:
+ prepareCategoryTemplate.length > 0
+ ? ChatBoxStatus.Complete
+ : ChatBoxStatus.Cancel,
+ isGenerating: false,
+ isComplete: prepareCategoryTemplate.length > 0,
+ isListening: false,
+ });
+ }
+ onUpdateScroll();
+ }, 30);
+ }, [message, template]);
+
+ useEffect(() => {
+ if (isRendered) return;
+
+ if (isGenerating) {
+ animateMessage();
+ } else {
+ setIsRendered(true);
+ refRender.current && clearInterval(refRender.current);
+ }
+ }, [isGenerating]);
+
+ return (
+
+
{displayedMessage}
+
+
+ {displayedTemplate.map((item) => (
+
+
Generated {item.title}
+
+
+ {item.options.map((option) => (
+
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/Message/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/Message/styles.module.scss
new file mode 100644
index 000000000..e56b9d93c
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/Message/styles.module.scss
@@ -0,0 +1,23 @@
+.categories {
+ margin-top: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .category {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ .categoryTitle {
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ }
+
+ .categoryOptions {
+ display: flex;
+ gap: 8px;
+ }
+ }
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/TextInput/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/TextInput/index.tsx
new file mode 100644
index 000000000..b88c52535
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/TextInput/index.tsx
@@ -0,0 +1,44 @@
+import { ReactElement } from 'react';
+import useChatBoxState from '../chatbox-store';
+import LabelListening from '../LabelListening';
+import styles from './styles.module.scss';
+
+export default function TextInput({
+ handleSendMessage,
+}: {
+ handleSendMessage: any;
+}): ReactElement {
+ const { inputMessage, isListening, isGenerating, setInputMessage } =
+ useChatBoxState((state) => state);
+ return (
+
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/TextInput/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/TextInput/styles.module.scss
new file mode 100644
index 000000000..f2a8dc64b
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/TextInput/styles.module.scss
@@ -0,0 +1,92 @@
+.input {
+ display: flex;
+ flex-direction: column;
+ padding: 15px;
+ position: relative;
+ padding-top: 0px;
+
+ &_heading {
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 20px;
+ text-align: left;
+ color: black;
+ margin-bottom: 8px;
+ }
+}
+
+.inputWrapper {
+ flex: 1;
+ position: relative;
+}
+
+.inputField {
+ width: 100%;
+ height: 150px;
+ padding: 15px;
+ border: 1px solid #dadada;
+ border-radius: 8px;
+ color: #333333;
+ background-color: #ffffff;
+ resize: none;
+ outline: none;
+ box-shadow: none;
+
+ font-family: SF Pro Display;
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: left;
+}
+
+.buttonWrapper {
+ display: flex;
+ gap: 8px;
+ position: absolute;
+ bottom: 16px;
+ right: 16px;
+}
+
+.sendButton {
+ background: none;
+ border: 1px solid #808080;
+ border-radius: 100px;
+ cursor: pointer;
+ padding-left: 16px;
+ padding-right: 16px;
+ color: #808080;
+ transition: background-color 0.3s, color 0.3s;
+ z-index: 10;
+ height: 32px;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: #f0f0f0;
+ }
+
+ &:disabled {
+ border-color: #e0e0e0;
+ color: #e0e0e0;
+ cursor: not-allowed;
+ }
+}
+
+.inputOverlay {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: calc(24 / 16);
+ text-align: left;
+ pointer-events: none;
+ color: #777777;
+
+ strong {
+ color: #fa4e0e;
+ font-weight: initial;
+ }
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/chatbox-store.ts b/src/modules/blockchains/Buy/studio/Chatbox/chatbox-store.ts
new file mode 100644
index 000000000..53d7b5067
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/chatbox-store.ts
@@ -0,0 +1,60 @@
+import { IModelCategory } from '@/types/customize-model';
+import { create } from 'zustand';
+import { SetChatBoxStatusParams } from './types';
+export enum ChatBoxStatus {
+ Generating = 'Generating...',
+ Cancel = 'Esc to cancel',
+ Complete = 'Completed',
+ Close = 'Esc to close',
+}
+
+interface ChatBoxState {
+ messages: Array<{ text: string; sender: string; template: IModelCategory[] }>;
+ inputMessage: string;
+ isListening: boolean;
+ isGenerating: boolean;
+ isComplete: boolean;
+ isChatboxOpen: boolean;
+ status: ChatBoxStatus;
+ prepareCategoryTemplate: IModelCategory[];
+ setMessages: (
+ messages: Array<{
+ text: string;
+ sender: string;
+ template: IModelCategory[];
+ }>,
+ ) => void;
+ setInputMessage: (inputMessage: string) => void;
+ setIsListening: (isListening: boolean) => void;
+ setIsGenerating: (isGenerating: boolean) => void;
+ setIsComplete: (isComplete: boolean) => void;
+ setIsChatboxOpen: (isChatboxOpen: boolean) => void;
+ setStatus: (status: ChatBoxStatus) => void;
+ setPrepareCategoryTemplate: (
+ prepareCategoryTemplate: IModelCategory[],
+ ) => void;
+ setChatBoxStatus: (params: SetChatBoxStatusParams) => void;
+}
+
+const useChatBoxState = create((set) => ({
+ messages: [],
+ inputMessage: '',
+ isListening: false,
+ isGenerating: false,
+ isComplete: false,
+ isChatboxOpen: false,
+ status: ChatBoxStatus.Close,
+ prepareCategoryTemplate: [],
+ setMessages: (messages) => set({ messages }),
+ setInputMessage: (inputMessage) => set({ inputMessage }),
+ setIsListening: (isListening) => set({ isListening }),
+ setIsGenerating: (isGenerating) => set({ isGenerating }),
+ setIsComplete: (isComplete) => set({ isComplete }),
+ setIsChatboxOpen: (isChatboxOpen) => set({ isChatboxOpen }),
+ setStatus: (status) => set({ status }),
+ setPrepareCategoryTemplate: (prepareCategoryTemplate) =>
+ set({ prepareCategoryTemplate }),
+ setChatBoxStatus: (params: SetChatBoxStatusParams) => set(params),
+}));
+
+export default useChatBoxState;
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/hooks/useChatBoxService.ts b/src/modules/blockchains/Buy/studio/Chatbox/hooks/useChatBoxService.ts
new file mode 100644
index 000000000..66a9b74ed
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/hooks/useChatBoxService.ts
@@ -0,0 +1,125 @@
+import { IModelCategory } from "@/types/customize-model";
+import { uniqBy } from "lodash";
+import { useEffect } from "react";
+import useFormChain from "../../../hooks/useFormChain";
+import useModelCategoriesStore from "../../../stores/useModelCategoriesStore";
+import useChatBoxState, { ChatBoxStatus } from "../chatbox-store";
+import { sendPrompt } from "../services/prompt";
+import { CategoryAction, PromptCategory, SendPromptBodyRequest } from "../types";
+import { modelCategoryToPromptCategory, promptCategoryToModelCategory } from "../utils/convertApiUtils";
+
+export default function useChatBoxService({
+ focusChatBox
+}: {
+ focusChatBox: () => void;
+}) {
+ const { categories } = useModelCategoriesStore();
+ const { getDynamicForm } = useFormChain();
+ const {setChatBoxStatus, setMessages, setPrepareCategoryTemplate, messages} = useChatBoxState();
+
+ const handleSendPrompt = async (message: string) => {
+ if (
+ messages.length > 0 &&
+ messages[messages.length - 1].sender === 'user'
+ ) {
+ setChatBoxStatus({
+ status: ChatBoxStatus.Generating,
+ isGenerating: true,
+ isComplete: false,
+ isListening: false,
+ });
+
+ const currentTemplate = getDynamicForm().dynamicForm;
+ const current_state: PromptCategory[] = currentTemplate.map(
+ modelCategoryToPromptCategory,
+ );
+ const prompt_body: SendPromptBodyRequest = {
+ command: message,
+ current_state,
+ };
+ const response = (await sendPrompt(prompt_body)).data;
+
+ console.log('[handleSendPrompt] response', response);
+
+ const newTemplate = currentTemplate.filter((category) => {
+ const promptCategory = response.actions.find(
+ (action) => action.category.key === category.key,
+ );
+
+ console.log('[handleSendPrompt] remove', { category, promptCategory });
+
+ return (
+ !promptCategory ||
+ promptCategory.action_type !== CategoryAction.REMOVE
+ );
+ });
+
+ // if (
+ // response.actions.filter(
+ // (action) => action.action_type !== CategoryAction.UNKNOWN,
+ // ).length === 0) {
+ // }
+
+ newTemplate.push(
+ ...response.actions
+ .filter((action) => action.action_type === CategoryAction.ADD)
+ .map((act) =>
+ promptCategoryToModelCategory(
+ act.category,
+ (categories || []).find(
+ (cate) => cate.key === act.category.key,
+ ) as IModelCategory,
+ ),
+ ),
+ );
+
+ newTemplate.forEach((category, index) => {
+ const indexInResponse = response.actions.findIndex(
+ (action) => action.category.key === category.key,
+ );
+ const categoryInModel = (categories || []).find(
+ (cate) => cate.key === category.key,
+ );
+
+ if (indexInResponse === -1 || !categoryInModel) return;
+
+ console.log('[handleSendPrompt] pre-update', {
+ indexInResponse,
+ action: response.actions[indexInResponse],
+ });
+
+ const action = response.actions[indexInResponse];
+
+ if (action.action_type === CategoryAction.UPDATE) {
+ console.log('[handleSendPrompt] update', {
+ action,
+ new: promptCategoryToModelCategory(action.category, category),
+ });
+
+ newTemplate[index] = promptCategoryToModelCategory(
+ action.category,
+ categoryInModel,
+ );
+ }
+ });
+
+ setMessages([
+ ...messages,
+ {
+ text: response.message,
+ template: newTemplate,
+ sender: 'bot',
+ },
+ ]);
+ setPrepareCategoryTemplate(uniqBy(newTemplate, 'key'));
+ focusChatBox();
+ }};
+
+ useEffect(() => {
+ if (messages.length > 0) {
+ handleSendPrompt(messages[messages.length - 1].text);
+ }
+ }, [messages]);
+
+
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/index.tsx b/src/modules/blockchains/Buy/studio/Chatbox/index.tsx
new file mode 100644
index 000000000..9cc4ea32a
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/index.tsx
@@ -0,0 +1,196 @@
+import MagicIcon from '@/components/MagicIcon';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import ButtonApply from './Actions/ButtonApply';
+import ButtonCancel from './Actions/ButtonCancle';
+import ButtonClose from './Actions/ButtonClsoe';
+import ButtonStop from './Actions/ButtonStop';
+import useChatBoxState, { ChatBoxStatus } from './chatbox-store';
+import useChatBoxService from './hooks/useChatBoxService';
+import Message from './Message';
+import styles from './styles.module.scss';
+import TextInput from './TextInput';
+
+export default function Chatbox() {
+ const {
+ messages,
+ setMessages,
+ inputMessage,
+ setInputMessage,
+ isListening,
+ isGenerating,
+ isComplete,
+ status,
+ isChatboxOpen,
+ setIsChatboxOpen,
+ setChatBoxStatus,
+ } = useChatBoxState();
+
+ const elChatBox = useRef(null);
+ const [recognition, setRecognition] = useState(null);
+
+ const focusChatBox = () => {
+ setTimeout(() => {
+ if (elChatBox.current)
+ elChatBox.current.scrollTo(0, elChatBox.current.scrollHeight);
+ }, 5);
+ };
+
+ const handleSendMessage = () => {
+ if (inputMessage.trim() !== '') {
+ setMessages([
+ ...messages,
+ { text: inputMessage, template: [], sender: 'user' },
+ ]);
+ setInputMessage('');
+ focusChatBox();
+ }
+ };
+
+ const stopVoiceInput = useCallback(() => {
+ if (recognition) {
+ recognition.stop();
+ setChatBoxStatus({
+ status: ChatBoxStatus.Cancel,
+ isGenerating: false,
+ isComplete: false,
+ isListening: false,
+ });
+ setRecognition(null);
+ }
+ }, [recognition]);
+
+ const isClose = useMemo(() => {
+ return !isComplete && !isGenerating && !isListening;
+ }, [isComplete, isGenerating, isListening]);
+
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ if (isClose) {
+ setIsChatboxOpen(false);
+ } else if (isListening) {
+ stopVoiceInput();
+ } else if (isGenerating) {
+ setChatBoxStatus({
+ status: ChatBoxStatus.Cancel,
+ isGenerating: false,
+ isComplete: false,
+ isListening: false,
+ });
+ }
+ } else if (event.ctrlKey && event.shiftKey && event.key === 'V') {
+ !isGenerating && handleVoiceInput();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [stopVoiceInput, isClose, isGenerating, isListening]);
+
+ const handleVoiceInput = () => {
+ setChatBoxStatus({
+ status: ChatBoxStatus.Cancel,
+ isGenerating: false,
+ isComplete: false,
+ isListening: true,
+ });
+ setInputMessage('');
+ const SpeechRecognition =
+ (window as any).SpeechRecognition ||
+ (window as any).webkitSpeechRecognition;
+ const newRecognition = new SpeechRecognition();
+
+ newRecognition.continuous = false;
+ newRecognition.interimResults = false;
+ newRecognition.lang = 'en-US'; // Set language to English
+
+ newRecognition.onresult = (event: any) => {
+ const transcript = event.results[0][0].transcript;
+ setInputMessage(transcript);
+ setChatBoxStatus({
+ status: ChatBoxStatus.Cancel,
+ isGenerating: false,
+ isComplete: false,
+ isListening: false,
+ });
+ setRecognition(null);
+ };
+
+ newRecognition.onerror = (event: any) => {
+ setChatBoxStatus({
+ status: ChatBoxStatus.Cancel,
+ isGenerating: false,
+ isComplete: false,
+ isListening: false,
+ });
+ setRecognition(null);
+ };
+
+ newRecognition.start();
+ setRecognition(newRecognition);
+ };
+
+ const isOpenVoice = useMemo(() => {
+ return isChatboxOpen;
+ }, [isChatboxOpen]);
+
+ useChatBoxService({ focusChatBox });
+ useEffect(() => {
+ if (isOpenVoice) {
+ handleVoiceInput();
+ }
+
+ return () => {
+ stopVoiceInput();
+ };
+ }, [isOpenVoice]);
+
+ return (
+
+
+
+
+
+
+ {messages.map((message, index) => (
+
+ {message.sender === 'bot' ? (
+
+ ) : (
+ message.text
+ )}
+
+ ))}
+
+
+
{status}
+
+ {isListening && }
+ {isComplete && }
+ {isGenerating && }
+ {isClose && }
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/mockup/categoryTemplate.ts b/src/modules/blockchains/Buy/studio/Chatbox/mockup/categoryTemplate.ts
new file mode 100644
index 000000000..99ad57702
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/mockup/categoryTemplate.ts
@@ -0,0 +1,420 @@
+import { IModelCategory } from '@/types/customize-model';
+
+export const categoryTemplate: IModelCategory[] = [
+ {
+ id: '66ab4cb5ab0ec87655a8e084',
+ created_at: '2024-07-11T07:50:23.589Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'layers',
+ title: 'Scaling Solutions',
+ required: true,
+ tooltip:
+ "Bitcoin Layer 1 (L1): \n EVM Metaprotocol: \n Enables the deployment of EVM-compatible smart contracts while leveraging Bitcoin's security. Ideal for\n developers familiar with Ethereum tools who seek the stability and security of Bitcoin.\n \n Est time: 48h \n \n Bitcoin Layer 2 (L2): \n ZK Rollup: \n Implements Zero-Knowledge (ZK) technology on Bitcoin as the base layer to enhance scalability and transaction\n efficiency. This option is perfect for developers focused on scaling while maintaining top-tier security on\n Bitcoin’s L1.\n \n Est time: 1h \n \n \n Optimistic Rollup: \n Processes transactions off-chain under the assumption of validity, which lowers costs and boosts scalability.\n Best suited for developers seeking efficient scaling with minimal delays in transaction finality.\n \n Est time: 1h \n\n \n Bitcoin Layer 3 (L3): \n ZK Rollup: \n Aggregates transactions off-chain and submits a single proof to Bitcoin, greatly improving scalability and\n reducing fees. Ideal for developers needing high transaction throughput with strong security guarantees.\n \n Est time: 90m ",
+ options: [
+ {
+ key: 'layer2_hybrid',
+ title: 'ZK Rollup Hybrid',
+ value: 21,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 1071,
+ priceBVM: 2585,
+ priceUSDTestnet: 0,
+ priceBVMTestnet: 0,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: [],
+ supportLayer: '',
+ supportLayers: null,
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-l2.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '5h',
+ deployTimeTestnet: '1h',
+ order: 0,
+ },
+ ],
+ color: '#368cdc',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: false,
+ confuseWord: true,
+ confuseIcon: '',
+ confuseTitle: '',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 0,
+ },
+ {
+ id: '668f8ebe88f822fe3ebd346e',
+ created_at: '2024-07-11T07:50:22.365Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'network',
+ title: 'Networks',
+ required: true,
+ tooltip:
+ 'Choose whether to test on the testnet for trials and development or deploy on the mainnet for production and live use.',
+ options: [
+ {
+ key: 'mainnet',
+ title: 'Mainnet',
+ value: 1,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 0,
+ priceBVM: 0,
+ priceUSDTestnet: null,
+ priceBVMTestnet: null,
+ tooltip: '',
+ supportNetwork: '',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: null,
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-mainnet.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#FF7A41',
+ type: 'module',
+ disable: false,
+ hidden: true,
+ multiChoice: false,
+ confuseWord: false,
+ confuseIcon: '',
+ confuseTitle: '',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 1,
+ },
+ {
+ id: '668f8ebf88f822fe3ebd3474',
+ created_at: '2024-07-11T07:50:23.919Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'block_gas_limit',
+ title: 'Gas',
+ required: true,
+ tooltip:
+ 'The block gas limit defines the maximum amount of gas that all transactions in a single block can consume.',
+ options: [
+ {
+ key: '1m',
+ title: '1,000,000,000',
+ value: 1000000000,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 0,
+ priceBVM: 0,
+ priceUSDTestnet: null,
+ priceBVMTestnet: null,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: null,
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-gas-min.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#FB9F00',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: false,
+ confuseWord: true,
+ confuseIcon: '',
+ confuseTitle: 'Block Gas Limit',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 5,
+ },
+ {
+ id: '668f8ebf88f822fe3ebd346f',
+ created_at: '2024-07-11T07:50:23.442Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'hardware',
+ title: 'Hardware',
+ required: true,
+ tooltip:
+ 'Select your hardware configuration to match the specific needs of your blockchain. Consider factors such as RAM, CPU cores, and storage capacity to ensure optimal performance and scalability.',
+ options: [
+ {
+ key: '16g',
+ title: '16 GB RAM, 8 cores, 320 GB SSD',
+ value: 16785728,
+ valueStr: '1',
+ selectable: true,
+ hidden: false,
+ priceUSD: 99,
+ priceBVM: 240,
+ priceUSDTestnet: null,
+ priceBVMTestnet: null,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: null,
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-hardware.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#12DAC2',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: false,
+ confuseWord: false,
+ confuseIcon: '',
+ confuseTitle: '',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 2,
+ },
+ {
+ id: '668f8ebf88f822fe3ebd3472',
+ created_at: '2024-07-11T07:50:23.774Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'storage',
+ title: 'Data Availability',
+ required: true,
+ tooltip:
+ 'Ensure easy access to transaction details and smart contracts for all nodes on L1, L2, and even L3 networks. Using a separate DA layer reduces mainnet congestion, enhancing scalability, security, and transaction cost-efficiency.',
+ options: [
+ {
+ key: 'polygon',
+ title: 'Polygon',
+ value: 10,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 0,
+ priceBVM: 0,
+ priceUSDTestnet: 0,
+ priceBVMTestnet: 0,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: ['layer2_op', 'layer2_hybrid'],
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-polygon.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#15C888',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: false,
+ confuseWord: true,
+ confuseIcon: '',
+ confuseTitle: 'DA',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 4,
+ },
+ {
+ id: '668f8ebf88f822fe3ebd3475',
+ created_at: '2024-07-11T07:50:23.993Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'withdrawal_time',
+ title: 'Withdrawals',
+ required: true,
+ tooltip:
+ 'The withdrawal period is the time frame during which your users can withdraw their assets from your blockchain back to the L1.',
+ options: [
+ {
+ key: 'withdrawal_time_3',
+ title: '24-hour',
+ value: 86400,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 0,
+ priceBVM: 0,
+ priceUSDTestnet: null,
+ priceBVMTestnet: null,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: ['layer3', 'layer2', 'layer2_hybrid', 'layer2_op'],
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-time-slow.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#F200F2',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: false,
+ confuseWord: true,
+ confuseIcon: '',
+ confuseTitle: 'Withdrawal Time',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 6,
+ },
+ {
+ id: '66bb30a7ab0ec87655a8e094',
+ created_at: '2024-07-11T07:50:23.589Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'tools',
+ title: 'Tools',
+ required: false,
+ tooltip: '',
+ options: [
+ {
+ key: 'explorer',
+ title: 'Explorer',
+ value: 0,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 0,
+ priceBVM: 0,
+ priceUSDTestnet: null,
+ priceBVMTestnet: null,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: null,
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-explorer.svg',
+ logo: 'https://storage.googleapis.com/bvm-network/image/account_abstraction_ic.png',
+ setupLogo:
+ 'https://storage.googleapis.com/bvm-network/image/app_account_abstraction_ic.png',
+ needContactUs: false,
+ needConfig: true,
+ needInstall: true,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#db7d7d',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: true,
+ confuseWord: true,
+ confuseIcon: '',
+ confuseTitle: '',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 7,
+ },
+ {
+ id: '668f8ebf88f822fe3ebd3470',
+ created_at: '2024-07-11T07:50:23.589Z',
+ updated_at: '0001-01-01T00:00:00Z',
+ key: 'settlement',
+ title: 'Base Chains',
+ required: true,
+ tooltip:
+ 'Select the base layer on which you want to build your blockchain. This will determine the base protocol and network characteristics, such as security, scalability, and consensus mechanisms.',
+ options: [
+ {
+ key: 'bitcoin',
+ title: 'Bitcoin',
+ value: 0,
+ valueStr: '',
+ selectable: true,
+ hidden: false,
+ priceUSD: 0,
+ priceBVM: 0,
+ priceUSDTestnet: null,
+ priceBVMTestnet: null,
+ tooltip: '',
+ supportNetwork: 'both',
+ requiredFor: null,
+ supportLayer: '',
+ supportLayers: null,
+ icon: 'https://storage.googleapis.com/bvm-network/icons-tool/icon-btc.svg',
+ logo: '',
+ setupLogo: '',
+ needContactUs: false,
+ needConfig: false,
+ needInstall: false,
+ appTemplateUrl: '',
+ addOnInputs: null,
+ deployTime: '',
+ deployTimeTestnet: '',
+ order: 0,
+ },
+ ],
+ color: '#F8B200',
+ type: 'module',
+ disable: false,
+ hidden: false,
+ multiChoice: false,
+ confuseWord: true,
+ confuseIcon: '',
+ confuseTitle: 'Base Chain',
+ updatable: false,
+ whitelistAddress: null,
+ isChain: true,
+ order: 0,
+ },
+];
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/mockup/promtResponse.ts b/src/modules/blockchains/Buy/studio/Chatbox/mockup/promtResponse.ts
new file mode 100644
index 000000000..420b36e16
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/mockup/promtResponse.ts
@@ -0,0 +1,132 @@
+import { CategoryAction, SendPromptResponse } from '../types';
+
+export const mockupPromptResponses: SendPromptResponse[] = [
+ {
+ status: 1,
+ data: {
+ message:
+ 'I have updated your settlement layer to Bitcoin. Please let me know if you need any further adjustments or have additional requests!',
+ is_clear: false,
+ actions: [
+ {
+ action_type: CategoryAction.ADD,
+ category: {
+ key: 'settlement',
+ options: [
+ {
+ key: 'bitcoin',
+ title: 'Bitcoin',
+ value: 0,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ status: 1,
+ data: {
+ message:
+ 'I have updated your settlement layer to Bitcoin. Please let me know if you need any further adjustments or have additional requests!',
+ is_clear: false,
+ actions: [
+ {
+ action_type: CategoryAction.ADD,
+ category: {
+ key: 'hardware',
+ options: [
+ {
+ key: '16g',
+ title: 'Bitcoin',
+ value: 0,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ status: 1,
+ data: {
+ message:
+ 'I have updated your settlement layer to Bitcoin. Please let me know if you need any further adjustments or have additional requests!',
+ is_clear: false,
+ actions: [
+ {
+ action_type: CategoryAction.ADD,
+ category: {
+ key: 'block_gas_limit',
+ options: [
+ {
+ key: '1m',
+ title: 'Bitcoin',
+ value: 0,
+ },
+ ],
+ },
+ },
+ {
+ action_type: CategoryAction.ADD,
+ category: {
+ key: 'withdrawal_time',
+ options: [
+ {
+ key: 'withdrawal_time_3',
+ title: 'Bitcoin',
+ value: 0,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ status: 1,
+ data: {
+ message:
+ 'I have updated your settlement layer to Bitcoin. Please let me know if you need any further adjustments or have additional requests!',
+ is_clear: false,
+ actions: [
+ {
+ action_type: CategoryAction.UPDATE,
+ category: {
+ key: 'hardware',
+ options: [
+ {
+ key: '32g',
+ title: 'Bitcoin',
+ value: 0,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ status: 1,
+ data: {
+ message:
+ 'I have updated your settlement layer to Bitcoin. Please let me know if you need any further adjustments or have additional requests!',
+ is_clear: false,
+ actions: [
+ {
+ action_type: CategoryAction.REMOVE,
+ category: {
+ key: 'hardware',
+ options: [
+ {
+ key: '32g',
+ title: 'Bitcoin',
+ value: 0,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+];
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/services/prompt.ts b/src/modules/blockchains/Buy/studio/Chatbox/services/prompt.ts
new file mode 100644
index 000000000..873b22b67
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/services/prompt.ts
@@ -0,0 +1,27 @@
+import { SendPromptBodyRequest, SendPromptResponse } from '../types';
+
+export const sendPrompt = async (
+ body: SendPromptBodyRequest,
+): Promise => {
+ console.log('[sendPrompt] body', body);
+
+ try {
+ const response = await fetch(
+ 'https://api-dojo2.eternalai.org/api/chat/assistant',
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ },
+ );
+
+ const data = await response.json();
+
+ return data;
+ } catch (error) {
+ console.error('[sendPrompt] error', error);
+ throw error;
+ }
+};
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/styles.module.scss b/src/modules/blockchains/Buy/studio/Chatbox/styles.module.scss
new file mode 100644
index 000000000..a34f11554
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/styles.module.scss
@@ -0,0 +1,129 @@
+.chatbox {
+ width: 560px;
+ height: var(--height-studio);
+ margin: 0 auto;
+ border-radius: 8px;
+ overflow: hidden;
+ background-color: #f6f6f6;
+}
+
+.chatboxInner {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.header {
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 20px;
+ text-align: left;
+ margin-bottom: 8px;
+}
+
+.title {
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 20px;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.body {
+ padding: 15px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ &_inner {
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background-color: #ffffff;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+.chats {
+ flex: 0 0 calc(var(--height-studio) - 313px);
+ overflow-y: auto;
+ padding: 15px;
+ display: flex;
+ flex-direction: column;
+
+ /* width */
+ &::-webkit-scrollbar {
+ width: 5px;
+ }
+
+ /* Track */
+ &::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ }
+
+ /* Handle */
+ &::-webkit-scrollbar-thumb {
+ background: #888;
+ }
+
+ /* Handle on hover */
+ &::-webkit-scrollbar-thumb:hover {
+ background: #555;
+ }
+}
+
+.message {
+ margin-bottom: 15px;
+ padding: 10px 15px;
+ border-radius: 8px;
+ max-width: 80%;
+ background-color: #ffffff;
+ font-family: SF Pro Display;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ text-align: left;
+ white-space: pre-wrap;
+}
+
+.user {
+ align-self: flex-end;
+ margin-left: auto;
+ background-color: #eeeeee;
+ color: #555555;
+ font-size: 16px;
+ line-height: calc(24 / 16);
+}
+
+.bot {
+ align-self: flex-start;
+ background-color: #ffeee8;
+ font-size: 16px;
+ line-height: calc(24 / 16);
+ color: #000;
+ padding: 10px 15px 20px 15px;
+}
+
+.status {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 16px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.statusInner {
+ color: #555555;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: calc(24 / 16);
+}
+
+.statusButtons {
+ position: relative;
+}
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/types.ts b/src/modules/blockchains/Buy/studio/Chatbox/types.ts
new file mode 100644
index 000000000..8d6db5693
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/types.ts
@@ -0,0 +1,41 @@
+import { ChatBoxStatus } from './chatbox-store';
+
+export type SetChatBoxStatusParams = {
+ status: ChatBoxStatus;
+ isGenerating: boolean;
+ isComplete: boolean;
+ isListening: boolean;
+};
+
+export type PromptCategory = {
+ key: string;
+ options: {
+ key: string;
+ title: string;
+ value: string | number;
+ }[];
+};
+
+export type SendPromptBodyRequest = {
+ command: string;
+ current_state: PromptCategory[];
+};
+
+export enum CategoryAction {
+ UNKNOWN = 'unknown',
+ ADD = 'add',
+ REMOVE = 'remove',
+ UPDATE = 'update',
+}
+
+export type SendPromptResponse = {
+ status: number;
+ data: {
+ message: string;
+ actions: {
+ action_type: CategoryAction;
+ category: PromptCategory;
+ }[];
+ is_clear: boolean;
+ };
+};
diff --git a/src/modules/blockchains/Buy/studio/Chatbox/utils/convertApiUtils.ts b/src/modules/blockchains/Buy/studio/Chatbox/utils/convertApiUtils.ts
new file mode 100644
index 000000000..1cd23ad00
--- /dev/null
+++ b/src/modules/blockchains/Buy/studio/Chatbox/utils/convertApiUtils.ts
@@ -0,0 +1,27 @@
+import { IModelCategory } from '@/types/customize-model';
+import { PromptCategory } from '../types';
+
+export const modelCategoryToPromptCategory = (
+ modelCategory: IModelCategory,
+): PromptCategory => {
+ return {
+ key: modelCategory.key,
+ options: modelCategory.options.map((option) => ({
+ key: option.key,
+ title: option.title,
+ value: option.value as string | number,
+ })),
+ };
+};
+
+export const promptCategoryToModelCategory = (
+ promptCategory: PromptCategory,
+ modelCategory: IModelCategory,
+): IModelCategory => {
+ return {
+ ...modelCategory,
+ options: modelCategory.options.filter((option) =>
+ promptCategory.options.some((pOption) => pOption.key === option.key),
+ ),
+ };
+};
diff --git a/src/modules/blockchains/Buy/studio/WorkArea/index.tsx b/src/modules/blockchains/Buy/studio/WorkArea/index.tsx
index d89fca1ea..1097c6d58 100644
--- a/src/modules/blockchains/Buy/studio/WorkArea/index.tsx
+++ b/src/modules/blockchains/Buy/studio/WorkArea/index.tsx
@@ -2,6 +2,7 @@ import ActionsWorkArea from '@/modules/blockchains/Buy/studio/ActionsWorkArea';
import TopWorkArea from '@/modules/blockchains/Buy/studio/TopWorkArea';
import RightContent from '@/modules/blockchains/Buy/studio/WorkArea/RightContent';
import s from '@/modules/blockchains/Buy/styles_v6.module.scss';
+// import ButtonStartChat from '../ButtonStartChat';
import LoadingOverlay from './LoadingOverlay';
export default function WorkArea() {
@@ -14,6 +15,7 @@ export default function WorkArea() {
+ {/* */}
);
diff --git a/src/modules/blockchains/Buy/utils.ts b/src/modules/blockchains/Buy/utils.ts
index f5d76afdb..5c4f9378a 100644
--- a/src/modules/blockchains/Buy/utils.ts
+++ b/src/modules/blockchains/Buy/utils.ts
@@ -459,36 +459,15 @@ export const isChainOptionDisabled = (
item: IModelCategory,
currentOption: IModelOption,
) => {
- // if (currentOption.key === 'zk') {
- // console.log('isChainOptionDisabled', {
- // currentOption,
- // item,
- // field,
- // supportLayers: currentOption.supportLayers,
- // layers: field['layers']?.value,
- // con1:
- // !!currentOption.supportLayers &&
- // !currentOption.supportLayers.includes(field['layers']?.value as any),
- // });
- // }
-
return (
(!!currentOption.supportLayers &&
currentOption.supportLayers.length > 0 &&
!currentOption.supportLayers.includes(field['layers']?.value as any)) ||
- // !!(
- // currentOption.supportLayers &&
- // currentOption.supportLayers !== 'both' &&
- // currentOption.supportLayer !== field['layers']?.value
- // ) ||
!!(
currentOption.supportNetwork &&
currentOption.supportNetwork !== 'both' &&
currentOption.supportNetwork !== field['network']?.value
) ||
- // field[item.key].dragged ||
- // (!item.disable && currentOption.selectable && field[item.key].dragged) ||
- // (item.required && !field[item.key].dragged) ||
item.disable ||
!currentOption.selectable
);
diff --git a/src/modules/blockchains/components/Body/L2Instance/BodyInfor_V2/BlockchainSection.tsx b/src/modules/blockchains/components/Body/L2Instance/BodyInfor_V2/BlockchainSection.tsx
index c53a3d1ff..1f21cb273 100644
--- a/src/modules/blockchains/components/Body/L2Instance/BodyInfor_V2/BlockchainSection.tsx
+++ b/src/modules/blockchains/components/Body/L2Instance/BodyInfor_V2/BlockchainSection.tsx
@@ -26,6 +26,9 @@ const BlockchainSection = (props: Props) => {
chainId,
serviceType,
blockTime,
+ chainName,
+ domain,
+ isMainnet,
} = item;
const formatWithdrawalPeriod = useMemo(() => {
@@ -101,6 +104,14 @@ const BlockchainSection = (props: Props) => {
title="Withdrawal period"
content={`${formatWithdrawalPeriod || '--'}`}
/>
+ {!isMainnet && (
+
+ )}
);
diff --git a/src/modules/l2-rollup-detail/TokenTransferTab/index.tsx b/src/modules/l2-rollup-detail/TokenTransferTab/index.tsx
index 03d1792e8..d5613ff41 100644
--- a/src/modules/l2-rollup-detail/TokenTransferTab/index.tsx
+++ b/src/modules/l2-rollup-detail/TokenTransferTab/index.tsx
@@ -182,9 +182,8 @@ const TokenTransferTab = (props: IProps) => {
_hover={{
textDecoration: 'underline',
}}
- onClick={() => {
- router.push(`${HEART_BEAT}/tx/${data.transaction_hash}`);
- }}
+ as={'a'}
+ href={`${HEART_BEAT}/tx/${data.transaction_hash}`}
>
diff --git a/src/modules/l2-rollup-detail/TokenTransferTabBitcoin/index.tsx b/src/modules/l2-rollup-detail/TokenTransferTabBitcoin/index.tsx
index 031283917..9f6c2cecc 100644
--- a/src/modules/l2-rollup-detail/TokenTransferTabBitcoin/index.tsx
+++ b/src/modules/l2-rollup-detail/TokenTransferTabBitcoin/index.tsx
@@ -135,9 +135,8 @@ const TokenTransferTabBitcoin = (props: IProps) => {
_hover={{
textDecoration: 'underline',
}}
- onClick={() => {
- router.push(`${HEART_BEAT}/tx/${data.tx_id}`);
- }}
+ as={'a'}
+ href={`${HEART_BEAT}/tx/${data.tx_id}`}
>
diff --git a/src/modules/l2-rollup-detail/TransactionsTab/index.tsx b/src/modules/l2-rollup-detail/TransactionsTab/index.tsx
index 971fb8083..890be654f 100644
--- a/src/modules/l2-rollup-detail/TransactionsTab/index.tsx
+++ b/src/modules/l2-rollup-detail/TransactionsTab/index.tsx
@@ -183,9 +183,8 @@ const TransactionsTab = (props: IProps) => {
_hover={{
textDecoration: 'underline',
}}
- onClick={() => {
- router.push(`${HEART_BEAT}/tx/${data.hash}`);
- }}
+ as={'a'}
+ href={`${HEART_BEAT}/tx/${data.hash}`}
>
{shortCryptoAddress(data.hash)}
diff --git a/src/modules/l2-rollup-detail/TransactionsTabBitcoin/index.tsx b/src/modules/l2-rollup-detail/TransactionsTabBitcoin/index.tsx
index fb7d5a6c6..4014ca7e9 100644
--- a/src/modules/l2-rollup-detail/TransactionsTabBitcoin/index.tsx
+++ b/src/modules/l2-rollup-detail/TransactionsTabBitcoin/index.tsx
@@ -128,9 +128,8 @@ const TransactionsTabBitcoin = (props: IProps) => {
_hover={{
textDecoration: 'underline',
}}
- onClick={() => {
- window.open(`${HEART_BEAT}/tx/${data.tx_id}`);
- }}
+ as={'a'}
+ href={`${HEART_BEAT}/tx/${data.tx_id}`}
>
diff --git a/src/modules/l2-rollup-detail/index.tsx b/src/modules/l2-rollup-detail/index.tsx
index 3db4eaca6..2dd6f92ec 100644
--- a/src/modules/l2-rollup-detail/index.tsx
+++ b/src/modules/l2-rollup-detail/index.tsx
@@ -143,7 +143,12 @@ const L2RollupDetail = () => {
)}
-
+
{
-
+
{isLoadingAI ? (
diff --git a/src/modules/l2-rollup-detail/styles.module.scss b/src/modules/l2-rollup-detail/styles.module.scss
index 8a660f4e2..8f12bcf07 100644
--- a/src/modules/l2-rollup-detail/styles.module.scss
+++ b/src/modules/l2-rollup-detail/styles.module.scss
@@ -146,7 +146,7 @@
.boxAi {
border: 1px solid #f58257;
- padding: 12px 0px 16px 0px;
+ padding: 0px 0px 16px 0px;
border-radius: 12px;
width: 100%;
}