From 38b4e01718d4aa69487b4602be1cac131238c263 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 20 Feb 2025 16:46:38 +0200 Subject: [PATCH 01/19] ai component --- package-lock.json | 126 +++++++++++ package.json | 2 + src/components/AskAiButton/ChatWindow.tsx | 203 ++++++++++++++++++ src/components/AskAiButton/index.tsx | 54 +++++ src/components/AskAiButton/useChat.ts | 66 ++++++ src/css/custom.css | 16 ++ src/theme/Navbar/ColorModeToggle/index.tsx | 28 +++ .../Navbar/ColorModeToggle/styles.module.css | 3 + src/theme/Navbar/Content/index.tsx | 96 +++++++++ src/theme/Navbar/Content/styles.module.css | 8 + src/theme/Navbar/Layout/index.tsx | 56 +++++ src/theme/Navbar/Layout/styles.module.css | 7 + src/theme/Navbar/Logo/index.tsx | 12 ++ .../Navbar/MobileSidebar/Header/index.tsx | 33 +++ .../Navbar/MobileSidebar/Layout/index.tsx | 24 +++ .../MobileSidebar/PrimaryMenu/index.tsx | 31 +++ .../MobileSidebar/SecondaryMenu/index.tsx | 32 +++ .../Navbar/MobileSidebar/Toggle/index.tsx | 23 ++ src/theme/Navbar/MobileSidebar/index.tsx | 26 +++ src/theme/Navbar/Search/index.tsx | 16 ++ src/theme/Navbar/Search/styles.module.css | 21 ++ src/theme/Navbar/index.tsx | 11 + static/ask_ai/ask_ai_user_avatar.png | Bin 0 -> 95982 bytes 23 files changed, 894 insertions(+) create mode 100644 src/components/AskAiButton/ChatWindow.tsx create mode 100644 src/components/AskAiButton/index.tsx create mode 100644 src/components/AskAiButton/useChat.ts create mode 100644 src/theme/Navbar/ColorModeToggle/index.tsx create mode 100644 src/theme/Navbar/ColorModeToggle/styles.module.css create mode 100644 src/theme/Navbar/Content/index.tsx create mode 100644 src/theme/Navbar/Content/styles.module.css create mode 100644 src/theme/Navbar/Layout/index.tsx create mode 100644 src/theme/Navbar/Layout/styles.module.css create mode 100644 src/theme/Navbar/Logo/index.tsx create mode 100644 src/theme/Navbar/MobileSidebar/Header/index.tsx create mode 100644 src/theme/Navbar/MobileSidebar/Layout/index.tsx create mode 100644 src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx create mode 100644 src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx create mode 100644 src/theme/Navbar/MobileSidebar/Toggle/index.tsx create mode 100644 src/theme/Navbar/MobileSidebar/index.tsx create mode 100644 src/theme/Navbar/Search/index.tsx create mode 100644 src/theme/Navbar/Search/styles.module.css create mode 100644 src/theme/Navbar/index.tsx create mode 100644 static/ask_ai/ask_ai_user_avatar.png diff --git a/package-lock.json b/package-lock.json index 1e268f567..c134127d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,8 @@ "prism-react-renderer": "2.3.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-markdown": "^9.0.3", + "react-modal": "^3.16.3", "rehype-katex": "7.0.0", "remark-math": "6.0.0" }, @@ -7300,6 +7302,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==", + "license": "BSD-3-Clause" + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -8693,6 +8701,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -14537,6 +14555,12 @@ "react": "^16.13.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, "node_modules/react-loadable": { "name": "@docusaurus/react-loadable", "version": "6.0.0", @@ -14564,6 +14588,48 @@ "webpack": ">=4.41.1 || 5.x" } }, + "node_modules/react-markdown": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.3.tgz", + "integrity": "sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==", + "license": "MIT", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" + } + }, "node_modules/react-router": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", @@ -17300,6 +17366,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", @@ -23384,6 +23459,11 @@ "strip-final-newline": "^2.0.0" } }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -24414,6 +24494,11 @@ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==" }, + "html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" + }, "html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -28034,6 +28119,11 @@ "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", "requires": {} }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-loadable": { "version": "npm:@docusaurus/react-loadable@6.0.0", "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", @@ -28050,6 +28140,34 @@ "@babel/runtime": "^7.10.3" } }, + "react-markdown": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.3.tgz", + "integrity": "sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==", + "requires": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, + "react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==", + "requires": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + } + }, "react-router": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", @@ -30031,6 +30149,14 @@ "unist-util-stringify-position": "^4.0.0" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", diff --git a/package.json b/package.json index 061ad725e..04c5a80fb 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "prism-react-renderer": "2.3.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-markdown": "^9.0.3", + "react-modal": "^3.16.3", "rehype-katex": "7.0.0", "remark-math": "6.0.0" }, diff --git a/src/components/AskAiButton/ChatWindow.tsx b/src/components/AskAiButton/ChatWindow.tsx new file mode 100644 index 000000000..57f6606aa --- /dev/null +++ b/src/components/AskAiButton/ChatWindow.tsx @@ -0,0 +1,203 @@ +import React, { useRef, useState } from "react"; +import ReactMarkdown from "react-markdown"; +import CodeBlock from "@theme/CodeBlock"; // Docusaurus built–in CodeBlock +import userAvatar from "@site/static/ask_ai/ask_ai_user_avatar.png"; +import assistantAvatar from "@site/static/img/favicons/android-chrome-192x192.png"; +import { useChat } from "./useChat"; + +const ChatWindow = ({ onClose }) => { + // Initial hardcoded message from the assistant which includes a code snippet. + const [messages, setMessages] = useState< + Array<{ role: string; content: string }> + >([{ role: "assistant", content: "Ask me anything about MultiversX!" }]); + const [input, setInput] = useState(""); + + const { sendMessage, isLoading, error } = useChat(); + + // Called when the user hits "Send". Adds a user message then simulates an assistant reply. + const chatBodyRef = useRef(null); + const handleSend = async (message?: string) => { + console.log("hererere", message, input); + const msg = message || input; + if (!msg || isLoading) return; + + // Append the user's message + const userMessage = { role: "user", content: msg }; + setMessages((prevMessages) => [...prevMessages, userMessage]); + setInput(""); + + setTimeout(() => { + chatBodyRef.current?.scrollTo({ + top: chatBodyRef.current.scrollHeight * 10, + behavior: "smooth", + }); + }, 200); + + const response = await sendMessage(msg); + + if (!response) return; + + setMessages((prevMessages) => [...prevMessages, response]); + + setTimeout(() => { + chatBodyRef.current?.scrollTo({ + top: chatBodyRef.current.scrollHeight * 10, + behavior: "smooth", + }); + }, 200); + }; + + return ( +
+ {/* Modal Header */} +
+
+ Ask MultiversX AI +
+ +
+ + {/* Chat Messages Area */} +
+
+ {messages.map((msg, index) => ( +
+
+ {msg.role === "assistant" ? ( + + ) : ( + + )} + + {String(children).replace(/\n$/, "")} + + ) : ( + + {children} + + ); + }, + }} + > + {msg.content} + +
+
+ ))} + {isLoading &&
Thinking...
} +
+
+ + {/* Input Area */} +
+ setInput(e.target.value)} + style={{ + height: "56px", + fontSize: "16px", + flex: 1, + padding: "16px", + outline: "none", + border: "1px solid #333", + borderRadius: "12px", + background: "transparent", + }} + onKeyDown={(e) => { + if (e.key === "Enter") handleSend(); + }} + /> + +
+
+ ); +}; + +export default ChatWindow; diff --git a/src/components/AskAiButton/index.tsx b/src/components/AskAiButton/index.tsx new file mode 100644 index 000000000..a35401eb1 --- /dev/null +++ b/src/components/AskAiButton/index.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { useState } from "react"; +import Modal from "react-modal"; +import ChatWindow from "./ChatWindow"; +import assistantAvatar from "@site/static/img/favicons/android-chrome-192x192.png"; + +const AskAiButton = () => { + const [modalIsOpen, setModalIsOpen] = useState(false); + + const openModal = () => { + document.body.classList.add("no-scroll"); + setModalIsOpen(true); + }; + + const closeModal = () => { + document.body.classList.remove("no-scroll"); + + setModalIsOpen(false); + }; + + return ( +
+ + + + +
+ ); +}; + +export default AskAiButton; diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts new file mode 100644 index 000000000..6970579c2 --- /dev/null +++ b/src/components/AskAiButton/useChat.ts @@ -0,0 +1,66 @@ +import { useState } from "react"; + +interface ChatResponse { + role: string; + content: string; + threadId: string; +} + +interface ChatError { + message: string; + status?: number; +} + +export const useChat = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [threadId, setThreadId] = useState(); + + const sendMessage = async (message: string) => { + setIsLoading(true); + setError(null); + console.log({ + message, + threadId, + }); + try { + const response = await fetch( + // "https://kv0txnlt-3005.euw.devtunnels.ms/ai-docs-api/chat", + "https://tools.multiversx.com/ai-docs-api/chat", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message, + threadId, + }), + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: ChatResponse = await response.json(); + setThreadId(data.threadId); + + return data; + } catch (err) { + console.log("eeeeerrr"); + const error = err as Error; + setError({ message: error.message }); + return null; + } finally { + setIsLoading(false); + } + }; + + return { + sendMessage, + isLoading, + error, + threadId, + }; +}; diff --git a/src/css/custom.css b/src/css/custom.css index 1139b1eda..4489eabc5 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -478,6 +478,22 @@ html[data-theme="dark"] .DocSearch { font-weight: 600; } +body.no-scroll { + overflow: hidden !important; + height: 100vh; +} + +.chat-send-button { + transition: all 200ms ease; +} + +.chat-send-button:hover { + opacity: 0.75; +} +/* .chat-modal-body::-webkit-scrollbar { +display: none; +} */ + /* -------------------- End Docsearch ------------------- */ /* ====================================== GLOBAL ================================ */ diff --git a/src/theme/Navbar/ColorModeToggle/index.tsx b/src/theme/Navbar/ColorModeToggle/index.tsx new file mode 100644 index 000000000..b0d400777 --- /dev/null +++ b/src/theme/Navbar/ColorModeToggle/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import {useColorMode, useThemeConfig} from '@docusaurus/theme-common'; +import ColorModeToggle from '@theme/ColorModeToggle'; +import type {Props} from '@theme/Navbar/ColorModeToggle'; +import styles from './styles.module.css'; + +export default function NavbarColorModeToggle({ + className, +}: Props): JSX.Element | null { + const navbarStyle = useThemeConfig().navbar.style; + const disabled = useThemeConfig().colorMode.disableSwitch; + const {colorMode, setColorMode} = useColorMode(); + + if (disabled) { + return null; + } + + return ( + + ); +} diff --git a/src/theme/Navbar/ColorModeToggle/styles.module.css b/src/theme/Navbar/ColorModeToggle/styles.module.css new file mode 100644 index 000000000..7bd077a6b --- /dev/null +++ b/src/theme/Navbar/ColorModeToggle/styles.module.css @@ -0,0 +1,3 @@ +.darkNavbarColorModeToggle:hover { + background: var(--ifm-color-gray-800); +} diff --git a/src/theme/Navbar/Content/index.tsx b/src/theme/Navbar/Content/index.tsx new file mode 100644 index 000000000..735288d4e --- /dev/null +++ b/src/theme/Navbar/Content/index.tsx @@ -0,0 +1,96 @@ +import React, { type ReactNode } from "react"; +import { useThemeConfig, ErrorCauseBoundary } from "@docusaurus/theme-common"; +import { + splitNavbarItems, + useNavbarMobileSidebar, +} from "@docusaurus/theme-common/internal"; +import NavbarItem, { type Props as NavbarItemConfig } from "@theme/NavbarItem"; +import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle"; +import SearchBar from "@theme/SearchBar"; +import NavbarMobileSidebarToggle from "@theme/Navbar/MobileSidebar/Toggle"; +import NavbarLogo from "@theme/Navbar/Logo"; +import NavbarSearch from "@theme/Navbar/Search"; + +import styles from "./styles.module.css"; +import AskAiButton from "../../../components/AskAiButton"; + +function useNavbarItems() { + // TODO temporary casting until ThemeConfig type is improved + return useThemeConfig().navbar.items as NavbarItemConfig[]; +} + +function NavbarItems({ items }: { items: NavbarItemConfig[] }): JSX.Element { + return ( + <> + {items.map((item, i) => ( + + new Error( + `A theme navbar item failed to render. +Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config: +${JSON.stringify(item, null, 2)}`, + { cause: error } + ) + } + > + + + ))} + + ); +} + +function NavbarContentLayout({ + left, + right, +}: { + left: ReactNode; + right: ReactNode; +}) { + return ( +
+
{left}
+
{right}
+
+ ); +} + +export default function NavbarContent(): JSX.Element { + const mobileSidebar = useNavbarMobileSidebar(); + + const items = useNavbarItems(); + const [leftItems, rightItems] = splitNavbarItems(items); + + const searchBarItem = items.find((item) => item.type === "search"); + + return ( + + {!mobileSidebar.disabled && } + + + + + } + right={ + // TODO stop hardcoding items? + // Ask the user to add the respective navbar items => more flexible + <> + {/* */} + + + + + {!searchBarItem && ( + + + + )} + + } + /> + ); +} diff --git a/src/theme/Navbar/Content/styles.module.css b/src/theme/Navbar/Content/styles.module.css new file mode 100644 index 000000000..4c9471e10 --- /dev/null +++ b/src/theme/Navbar/Content/styles.module.css @@ -0,0 +1,8 @@ +/* +Hide color mode toggle in small viewports + */ +@media (max-width: 996px) { + .colorModeToggle { + display: none; + } +} diff --git a/src/theme/Navbar/Layout/index.tsx b/src/theme/Navbar/Layout/index.tsx new file mode 100644 index 000000000..e383d2cb4 --- /dev/null +++ b/src/theme/Navbar/Layout/index.tsx @@ -0,0 +1,56 @@ +import React, {type ComponentProps} from 'react'; +import clsx from 'clsx'; +import {useThemeConfig} from '@docusaurus/theme-common'; +import { + useHideableNavbar, + useNavbarMobileSidebar, +} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import NavbarMobileSidebar from '@theme/Navbar/MobileSidebar'; +import type {Props} from '@theme/Navbar/Layout'; + +import styles from './styles.module.css'; + +function NavbarBackdrop(props: ComponentProps<'div'>) { + return ( +
+ ); +} + +export default function NavbarLayout({children}: Props): JSX.Element { + const { + navbar: {hideOnScroll, style}, + } = useThemeConfig(); + const mobileSidebar = useNavbarMobileSidebar(); + const {navbarRef, isNavbarVisible} = useHideableNavbar(hideOnScroll); + return ( + + ); +} diff --git a/src/theme/Navbar/Layout/styles.module.css b/src/theme/Navbar/Layout/styles.module.css new file mode 100644 index 000000000..e72891a44 --- /dev/null +++ b/src/theme/Navbar/Layout/styles.module.css @@ -0,0 +1,7 @@ +.navbarHideable { + transition: transform var(--ifm-transition-fast) ease; +} + +.navbarHidden { + transform: translate3d(0, calc(-100% - 2px), 0); +} diff --git a/src/theme/Navbar/Logo/index.tsx b/src/theme/Navbar/Logo/index.tsx new file mode 100644 index 000000000..db71564b0 --- /dev/null +++ b/src/theme/Navbar/Logo/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import Logo from '@theme/Logo'; + +export default function NavbarLogo(): JSX.Element { + return ( + + ); +} diff --git a/src/theme/Navbar/MobileSidebar/Header/index.tsx b/src/theme/Navbar/MobileSidebar/Header/index.tsx new file mode 100644 index 000000000..d1f1659bb --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/Header/index.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle'; +import IconClose from '@theme/Icon/Close'; +import NavbarLogo from '@theme/Navbar/Logo'; + +function CloseButton() { + const mobileSidebar = useNavbarMobileSidebar(); + return ( + + ); +} + +export default function NavbarMobileSidebarHeader(): JSX.Element { + return ( +
+ + + +
+ ); +} diff --git a/src/theme/Navbar/MobileSidebar/Layout/index.tsx b/src/theme/Navbar/MobileSidebar/Layout/index.tsx new file mode 100644 index 000000000..6107f5105 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/Layout/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import clsx from 'clsx'; +import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal'; +import type {Props} from '@theme/Navbar/MobileSidebar/Layout'; + +export default function NavbarMobileSidebarLayout({ + header, + primaryMenu, + secondaryMenu, +}: Props): JSX.Element { + const {shown: secondaryMenuShown} = useNavbarSecondaryMenu(); + return ( +
+ {header} +
+
{primaryMenu}
+
{secondaryMenu}
+
+
+ ); +} diff --git a/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx b/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx new file mode 100644 index 000000000..db30be497 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import {useThemeConfig} from '@docusaurus/theme-common'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem'; + +function useNavbarItems() { + // TODO temporary casting until ThemeConfig type is improved + return useThemeConfig().navbar.items as NavbarItemConfig[]; +} + +// The primary menu displays the navbar items +export default function NavbarMobilePrimaryMenu(): JSX.Element { + const mobileSidebar = useNavbarMobileSidebar(); + + // TODO how can the order be defined for mobile? + // Should we allow providing a different list of items? + const items = useNavbarItems(); + + return ( +
    + {items.map((item, i) => ( + mobileSidebar.toggle()} + key={i} + /> + ))} +
+ ); +} diff --git a/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx b/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx new file mode 100644 index 000000000..757f59673 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx @@ -0,0 +1,32 @@ +import React, {type ComponentProps} from 'react'; +import {useThemeConfig} from '@docusaurus/theme-common'; +import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal'; +import Translate from '@docusaurus/Translate'; + +function SecondaryMenuBackButton(props: ComponentProps<'button'>) { + return ( + + ); +} + +// The secondary menu slides from the right and shows contextual information +// such as the docs sidebar +export default function NavbarMobileSidebarSecondaryMenu(): JSX.Element | null { + const isPrimaryMenuEmpty = useThemeConfig().navbar.items.length === 0; + const secondaryMenu = useNavbarSecondaryMenu(); + return ( + <> + {/* edge-case: prevent returning to the primaryMenu when it's empty */} + {!isPrimaryMenuEmpty && ( + secondaryMenu.hide()} /> + )} + {secondaryMenu.content} + + ); +} diff --git a/src/theme/Navbar/MobileSidebar/Toggle/index.tsx b/src/theme/Navbar/MobileSidebar/Toggle/index.tsx new file mode 100644 index 000000000..9691f103c --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/Toggle/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import IconMenu from '@theme/Icon/Menu'; + +export default function MobileSidebarToggle(): JSX.Element { + const {toggle, shown} = useNavbarMobileSidebar(); + return ( + + ); +} diff --git a/src/theme/Navbar/MobileSidebar/index.tsx b/src/theme/Navbar/MobileSidebar/index.tsx new file mode 100644 index 000000000..484b319b9 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/index.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { + useLockBodyScroll, + useNavbarMobileSidebar, +} from '@docusaurus/theme-common/internal'; +import NavbarMobileSidebarLayout from '@theme/Navbar/MobileSidebar/Layout'; +import NavbarMobileSidebarHeader from '@theme/Navbar/MobileSidebar/Header'; +import NavbarMobileSidebarPrimaryMenu from '@theme/Navbar/MobileSidebar/PrimaryMenu'; +import NavbarMobileSidebarSecondaryMenu from '@theme/Navbar/MobileSidebar/SecondaryMenu'; + +export default function NavbarMobileSidebar(): JSX.Element | null { + const mobileSidebar = useNavbarMobileSidebar(); + useLockBodyScroll(mobileSidebar.shown); + + if (!mobileSidebar.shouldRender) { + return null; + } + + return ( + } + primaryMenu={} + secondaryMenu={} + /> + ); +} diff --git a/src/theme/Navbar/Search/index.tsx b/src/theme/Navbar/Search/index.tsx new file mode 100644 index 000000000..788b3f1cd --- /dev/null +++ b/src/theme/Navbar/Search/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import clsx from 'clsx'; +import type {Props} from '@theme/Navbar/Search'; + +import styles from './styles.module.css'; + +export default function NavbarSearch({ + children, + className, +}: Props): JSX.Element { + return ( +
+ {children} +
+ ); +} diff --git a/src/theme/Navbar/Search/styles.module.css b/src/theme/Navbar/Search/styles.module.css new file mode 100644 index 000000000..9eeb2934d --- /dev/null +++ b/src/theme/Navbar/Search/styles.module.css @@ -0,0 +1,21 @@ +/* +Workaround to avoid rendering empty search container +See https://github.com/facebook/docusaurus/pull/9385 +*/ +.navbarSearchContainer:empty { + display: none; +} + +@media (max-width: 996px) { + .navbarSearchContainer { + position: absolute; + right: var(--ifm-navbar-padding-horizontal); + } +} + +@media (min-width: 997px) { + .navbarSearchContainer { + padding: var(--ifm-navbar-item-padding-vertical) + var(--ifm-navbar-item-padding-horizontal); + } +} diff --git a/src/theme/Navbar/index.tsx b/src/theme/Navbar/index.tsx new file mode 100644 index 000000000..abb62510b --- /dev/null +++ b/src/theme/Navbar/index.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import NavbarLayout from "@theme/Navbar/Layout"; +import NavbarContent from "@theme/Navbar/Content"; + +export default function Navbar(): JSX.Element { + return ( + + + + ); +} diff --git a/static/ask_ai/ask_ai_user_avatar.png b/static/ask_ai/ask_ai_user_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..3c617d3fb9c2fafda508946a78c5f7fa551059c4 GIT binary patch literal 95982 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�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 literal 0 HcmV?d00001 From 431a8ec60e3368ff12a7dda6ce2879977bf49a3c Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 20 Feb 2025 16:50:32 +0200 Subject: [PATCH 02/19] chat endpoint --- src/components/AskAiButton/useChat.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts index 6970579c2..34d2b4a47 100644 --- a/src/components/AskAiButton/useChat.ts +++ b/src/components/AskAiButton/useChat.ts @@ -11,6 +11,7 @@ interface ChatError { status?: number; } +const CHAT_ENDPOINT = "https://tools.multiversx.com/ai-docs-api/chat"; export const useChat = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -24,20 +25,16 @@ export const useChat = () => { threadId, }); try { - const response = await fetch( - // "https://kv0txnlt-3005.euw.devtunnels.ms/ai-docs-api/chat", - "https://tools.multiversx.com/ai-docs-api/chat", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - message, - threadId, - }), - } - ); + const response = await fetch(CHAT_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message, + threadId, + }), + }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); From ecbf4182e478a74b4b5f0dc482f9a9cbece4b55c Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 20 Feb 2025 17:15:48 +0200 Subject: [PATCH 03/19] rem logs --- src/components/AskAiButton/ChatWindow.tsx | 1 - src/components/AskAiButton/useChat.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/AskAiButton/ChatWindow.tsx b/src/components/AskAiButton/ChatWindow.tsx index 57f6606aa..21c11f944 100644 --- a/src/components/AskAiButton/ChatWindow.tsx +++ b/src/components/AskAiButton/ChatWindow.tsx @@ -17,7 +17,6 @@ const ChatWindow = ({ onClose }) => { // Called when the user hits "Send". Adds a user message then simulates an assistant reply. const chatBodyRef = useRef(null); const handleSend = async (message?: string) => { - console.log("hererere", message, input); const msg = message || input; if (!msg || isLoading) return; diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts index 34d2b4a47..de99f3b66 100644 --- a/src/components/AskAiButton/useChat.ts +++ b/src/components/AskAiButton/useChat.ts @@ -20,10 +20,7 @@ export const useChat = () => { const sendMessage = async (message: string) => { setIsLoading(true); setError(null); - console.log({ - message, - threadId, - }); + try { const response = await fetch(CHAT_ENDPOINT, { method: "POST", @@ -45,7 +42,6 @@ export const useChat = () => { return data; } catch (err) { - console.log("eeeeerrr"); const error = err as Error; setError({ message: error.message }); return null; From 8267464c5b8a359507018bcdee7833aa59ef8bdb Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 20 Feb 2025 17:40:06 +0200 Subject: [PATCH 04/19] ask ai button style --- src/components/AskAiButton/index.tsx | 16 ++++++++++++++-- src/theme/Navbar/Content/index.tsx | 4 +--- src/theme/Navbar/Search/styles.module.css | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/AskAiButton/index.tsx b/src/components/AskAiButton/index.tsx index a35401eb1..e3720372f 100644 --- a/src/components/AskAiButton/index.tsx +++ b/src/components/AskAiButton/index.tsx @@ -2,7 +2,8 @@ import React from "react"; import { useState } from "react"; import Modal from "react-modal"; import ChatWindow from "./ChatWindow"; -import assistantAvatar from "@site/static/img/favicons/android-chrome-192x192.png"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faRobot } from "@fortawesome/free-solid-svg-icons"; const AskAiButton = () => { const [modalIsOpen, setModalIsOpen] = useState(false); @@ -20,9 +21,20 @@ const AskAiButton = () => { return (
- */} + + } - } right={ // TODO stop hardcoding items? // Ask the user to add the respective navbar items => more flexible <> - {/* */} - + {!searchBarItem && ( diff --git a/src/theme/Navbar/Search/styles.module.css b/src/theme/Navbar/Search/styles.module.css index 9eeb2934d..28560a925 100644 --- a/src/theme/Navbar/Search/styles.module.css +++ b/src/theme/Navbar/Search/styles.module.css @@ -8,7 +8,7 @@ See https://github.com/facebook/docusaurus/pull/9385 @media (max-width: 996px) { .navbarSearchContainer { - position: absolute; + /* position: absolute; */ right: var(--ifm-navbar-padding-horizontal); } } From 423b6a1c512cbc2f91044eca7951d0b5aadf78c9 Mon Sep 17 00:00:00 2001 From: Iuga Mihai <50499646+miiu96@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:06:59 +0200 Subject: [PATCH 05/19] Update elastic indexer documentation (#1016) * update documentation es indices * fixes * fix * fixes * fixes after second review * links --- docs/sdk-and-tools/elastic-search.md | 43 ++++---- docs/sdk-and-tools/indices/blocks.md | 2 +- docs/sdk-and-tools/indices/events.md | 79 +++++++++++++++ docs/sdk-and-tools/indices/logs.md | 9 ++ docs/sdk-and-tools/indices/operations.md | 108 +++++++++++++++++++++ docs/sdk-and-tools/indices/scresults.md | 12 ++- docs/sdk-and-tools/indices/transactions.md | 12 ++- sidebars.js | 1 + 8 files changed, 242 insertions(+), 24 deletions(-) create mode 100644 docs/sdk-and-tools/indices/events.md diff --git a/docs/sdk-and-tools/elastic-search.md b/docs/sdk-and-tools/elastic-search.md index 68b3dfa09..46960d009 100644 --- a/docs/sdk-and-tools/elastic-search.md +++ b/docs/sdk-and-tools/elastic-search.md @@ -125,27 +125,28 @@ Each entry in an Elasticsearch index will have a format similar to this: } ``` -| Name | Description | -| -------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -| [transactions](/sdk-and-tools/indices/es-index-transactions) | Contains all transactions. | -| [blocks](/sdk-and-tools/indices/es-index-blocks) | Contains all executed blocks. | -| [validators](/sdk-and-tools/indices/es-index-validators) | Contains the public keys of the validators grouped by epoch and shard. | -| [rating](/sdk-and-tools/indices/es-index-rating) | Contains the validators' rating for every epoch. | -| [miniblocks](/sdk-and-tools/indices/es-index-miniblocks) | Contains all executed minblocks. | -| [rounds](/sdk-and-tools/indices/es-index-rounds) | Contains details of each round that has passed. | -| [accounts](/sdk-and-tools/indices/es-index-accounts) | Contains the addresses' balances and the timestamp when they were modified. | -| [accountshistory](/sdk-and-tools/indices/es-index-accountshistory) | Contains historical information about the address balances. | -| [receipts](/sdk-and-tools/indices/es-index-receipts) | Contains all generated receipts. | -| [scresults](/sdk-and-tools/indices/es-index-scresults) | Contains all generated smart contract results. | -| [accountsesdt](/sdk-and-tools/indices/es-index-accountsesdt) | Contains the addresses' ESDT balances. | -| [accountsesdthistory](/sdk-and-tools/indices/es-index-accountsesdthistory) | Contains historical information about the address ESDT balances. | -| [epochinfo](/sdk-and-tools/indices/es-index-epochinfo) | Contains the accumulated fees and the developer fees grouped by epochs. | -| [scdeploys](/sdk-and-tools/indices/es-index-scdeploys) | Contains details about all the deployed smart contracts. | -| [tokens](/sdk-and-tools/indices/es-index-tokens) | Contains all created ESDT tokens. | -| [tags](/sdk-and-tools/indices/es-index-tags) | Contains the NFTs' tags. | -| [logs](/sdk-and-tools/indices/es-index-logs) | Contains all the logs generated by transactions and smart contract results. | -| [delegators](/sdk-and-tools/indices/es-index-delegators) | Contains details about all the delegators. | -| [operations](/sdk-and-tools/indices/es-index-operations) | Contains all transactions and smart contract results. | +| Name | Description | +|----------------------------------------------------------------------------|-------------------------------------------------------------------------------| +| [transactions](/sdk-and-tools/indices/es-index-transactions) | Contains all transactions. | +| [blocks](/sdk-and-tools/indices/es-index-blocks) | Contains all executed blocks. | +| [validators](/sdk-and-tools/indices/es-index-validators) | Contains the public keys of the validators grouped by epoch and shard. | +| [rating](/sdk-and-tools/indices/es-index-rating) | Contains the validators' rating for every epoch. | +| [miniblocks](/sdk-and-tools/indices/es-index-miniblocks) | Contains all executed minblocks. | +| [rounds](/sdk-and-tools/indices/es-index-rounds) | Contains details of each round that has passed. | +| [accounts](/sdk-and-tools/indices/es-index-accounts) | Contains the addresses' balances and the timestamp when they were modified. | +| [accountshistory](/sdk-and-tools/indices/es-index-accountshistory) | Contains historical information about the address balances. | +| [receipts](/sdk-and-tools/indices/es-index-receipts) | Contains all generated receipts. | +| [scresults](/sdk-and-tools/indices/es-index-scresults) | Contains all generated smart contract results. | +| [accountsesdt](/sdk-and-tools/indices/es-index-accountsesdt) | Contains the addresses' ESDT balances. | +| [accountsesdthistory](/sdk-and-tools/indices/es-index-accountsesdthistory) | Contains historical information about the address ESDT balances. | +| [epochinfo](/sdk-and-tools/indices/es-index-epochinfo) | Contains the accumulated fees and the developer fees grouped by epochs. | +| [scdeploys](/sdk-and-tools/indices/es-index-scdeploys) | Contains details about all the deployed smart contracts. | +| [tokens](/sdk-and-tools/indices/es-index-tokens) | Contains all created ESDT tokens. | +| [tags](/sdk-and-tools/indices/es-index-tags) | Contains the NFTs' tags. | +| [logs](/sdk-and-tools/indices/es-index-logs) | Contains all the logs generated by transactions and smart contract results. | +| [events](/sdk-and-tools/indices/es-index-events) | Contains all the events generated by transactions and smart contract results. | +| [delegators](/sdk-and-tools/indices/es-index-delegators) | Contains details about all the delegators. | +| [operations](/sdk-and-tools/indices/es-index-operations) | Contains all transactions and smart contract results. | [comment]: # (mx-context-auto) diff --git a/docs/sdk-and-tools/indices/blocks.md b/docs/sdk-and-tools/indices/blocks.md index f65a3415b..6d4a9ac84 100644 --- a/docs/sdk-and-tools/indices/blocks.md +++ b/docs/sdk-and-tools/indices/blocks.md @@ -38,7 +38,7 @@ The `_id` field of this index is represented by the block hash, in a hexadecimal | shardId | The shardId field represents the shard this block belongs to. | | txCount | The txCount field represents the number of transactions that were executed in the block. | | notarizedTxsCount | The notarizedTxsCount field represents the number of transactions that were notarized in the block. | -| accumulatedFees | The accumulatedFees field represents the accumulated fees that were paid in the block. | +| accumulatedFees | The accumulatedFees field represents the accumulated fees that were paid in the block. | | developerFees | The developerFees field represents the developer fees that were accumulated in the block. | | epochStartBlock | The epochStartBlock field is true if the current block is an epoch-start block. | | epochStartInfo | The epochStartInfo field is a structure that contains economic data, such as total supply. | diff --git a/docs/sdk-and-tools/indices/events.md b/docs/sdk-and-tools/indices/events.md new file mode 100644 index 000000000..5f47d870f --- /dev/null +++ b/docs/sdk-and-tools/indices/events.md @@ -0,0 +1,79 @@ +--- +id: es-index-events +title: events +--- + +[comment]: # (mx-abstract) + +This page describes the structure of the `events` index (Elasticsearch), and also depicts a few examples of how to query it. + +[comment]: # (mx-context-auto) + +## _id + +The `_id` field for this index is composed of hex-encoded hash of the transaction or the smart contract result that generated the log plus shard ID and order of event + +example: `{hash}-{shardID}-{order}` (example: `abcd-2-1`). + +[comment]: # (mx-context-auto) + +## Fields + +| Field | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| logAddress | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the log or the address of the receiver of the transaction. | +| address | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the event or the address of the receiver of the transaction. | +| txHash | The txHash field field for this index is composed of hex-encoded hash of the transaction or the smart contract result that generated the log. | +| originalTxHash | The originalTxHash field holds the hex-encoded hash of the initial transaction. When this field is not empty the log is generated by a smart contract result and this field represents the hash of the initial transaction. | +| timestamp | The timestamp field represents the timestamp of the block in which the log was generated. | +| identifier | This field represents the identifier of the event. | +| topics | The topics field holds a list with extra information, hex-encoded. They don't have a specific order because the smart contract is free to log anything that could be helpful. | +| data | The data field can contain information added by the smart contract that generated the event, hex-encoded. | +| order | The order field represents the index of the event indicating the execution order. | +| txOrder | The txOrder field represents the execution order of transaction/smart contract who generated this event. | +| timestamp | The timestamp field represents the timestamp of the block in which the event was generated. | +| shardID | The shardID field represents the shard this events belongs to. | + + +## Query examples + +### Fetch all the events generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/events/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "txHash":"d6.." + } + } +}' +``` + +### Fetch all the events generated by a transaction and the smart contract results triggered by it + +``` +curl --request GET \ + --url ${ES_URL}/events/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "should": [ + { + "match": { + "_id":"d6.." + } + }, + { + "match": { + "originalTxHash": "d6.." + } + } + ] + } + } +}' +``` diff --git a/docs/sdk-and-tools/indices/logs.md b/docs/sdk-and-tools/indices/logs.md index 6b8e1bdc4..62613f6fb 100644 --- a/docs/sdk-and-tools/indices/logs.md +++ b/docs/sdk-and-tools/indices/logs.md @@ -11,6 +11,15 @@ This page describes the structure of the `logs` index (Elasticsearch), and also ## _id +:::warning Important + +**The `logs` index will be deprecated and removed in the near future.** +We recommend using the [events](/sdk-and-tools/indices/es-index-events) index, which contains all the events included in a log. + +Please make the necessary updates to ensure a smooth transition. +If you need further assistance, feel free to reach out. +::: + The `_id` field for this index is composed of hex-encoded hash of the transaction of the smart contract result that generated the log. [comment]: # (mx-context-auto) diff --git a/docs/sdk-and-tools/indices/operations.md b/docs/sdk-and-tools/indices/operations.md index ed8b20fe7..5ec2ce0f1 100644 --- a/docs/sdk-and-tools/indices/operations.md +++ b/docs/sdk-and-tools/indices/operations.md @@ -17,6 +17,42 @@ The _id field of this index is represented by the transactions OR smart contract ## Fields +| Field | Description | +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| miniBlockHash | The miniBlockHash field represents the hash of the miniblock in which the transaction was included. | +| nonce | The nonce field represents the transaction sequence number of the sender address. | +| round | The round field represents the round of the block when the transaction was executed. | +| value | The value field represents the amount of EGLD to be sent from the sender to the receiver. | +| receiver | The receiver field represents the destination address of the transaction. | +| sender | The sender field represents the address of the transaction sender. | +| receiverShard | The receiverShard field represents the shard ID of the receiver address. | +| senderShard | The senderShard field represents the shard ID of the sender address. | +| gasPrice | The gasPrice field represents the amount to be paid for each gas unit. | +| gasLimit | The gasLimit field represents the maximum gas units the sender is willing to pay for. | +| gasUsed | The gasUsed field represents the amount of gas used by the transaction. | +| fee | The fee field represents the amount of EGLD the sender paid for the transaction. | +| initialPaidFee | The initialPaidFee field represents the initial amount of EGLD the sender paid for the transaction, before the refund. | +| data | The data field holds additional information for a transaction. It can contain a simple message, a function call, an ESDT transfer payload, and so on. | +| signature | The signature of the transaction, hex-encoded. | +| timestamp | The timestamp field represents the timestamp of the block in which the transaction was executed. | +| status | The status field represents the status of the transaction. | +| senderUserName | The senderUserName field represents the username of the sender address. | +| receiverUserName | The receiverUserName field represents the username of the receiver address. | +| hasScResults | The hasScResults field is true if the transaction has smart contract results. | +| isScCall | The isScCall field is true if the transaction is a smart contract call. | +| hasOperations | The hasOperations field is true if the transaction has smart contract results. | +| tokens | The tokens field contains a list of ESDT tokens that are transferred based on the data field. The indices from the `tokens` list are linked with the indices from `esdtValues` list. | +| esdtValues | The esdtValues field contains a list of ESDT values that are transferred based on the data field. | +| receivers | The receivers field contains a list of receiver addresses in case of ESDTNFTTransfer or MultiESDTTransfer. | +| receiversShardIDs | The receiversShardIDs field contains a list of receiver addresses' shard IDs. | +| type | The type field represents the type of the transaction based on the data field. | +| operation | The operation field represents the operation of the transaction based on the data field. | +| function | The function field holds the name of the function that is called in case of a smart contract call. | +| isRelayed | The isRelayed field is true if the transaction is a relayed transaction. | +| version | The version field represents the version of the transaction. | +| hasLogs | The hasLogs field is true if the transaction has logs. | + + This index contains both transactions and smart contract results. This is useful because one can query both of them in a single request. The unified structure will contain an extra field in order to be able to differentiate between them. @@ -33,6 +69,51 @@ The unified structure will contain an extra field in order to be able to differe [comment]: # (mx-context-auto) +### Fetch the latest transactions of an address + +``` +curl --request GET \ + --url ${ES_URL}/operations/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "must": [ + { + "match": { + "type": "normal" + } + } + ], + "should": [ + { + "match": { + "sender": "erd..." + } + }, + { + "match": { + "receiver": "erd..." + } + }, + { + "match": { + "receivers": "erd..." + } + } + ] + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ] +}' +``` + ### Fetch the latest operations of an address ``` @@ -72,3 +153,30 @@ curl --request GET \ ] }' ``` + + +### Fetch all the smart contract results generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/operations/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "must": [ + { + "match": { + "originalTxHash": "d6.." + } + }, + { + "match": { + "type": "unsigned" + } + } + ] + } + } +}' +``` diff --git a/docs/sdk-and-tools/indices/scresults.md b/docs/sdk-and-tools/indices/scresults.md index 7f4cb3966..288f4a4f6 100644 --- a/docs/sdk-and-tools/indices/scresults.md +++ b/docs/sdk-and-tools/indices/scresults.md @@ -11,7 +11,17 @@ This page describes the structure of the `sc-results` index (Elasticsearch), and ## _id -The `_id` field for this index is composed of hex encoded smart contract result hash. +:::warning Important + +**The `scresults` index will be deprecated and removed in the near future.** +We recommend using the [operations](/sdk-and-tools/indices/es-index-operations) index, which contains all the smart contract results data. +The only change required in your queries is to include the `type` field with the value `normal` to fetch all smart contract results. + +Please make the necessary updates to ensure a smooth transition. +If you need further assistance, feel free to reach out. +::: + +The `_id` field for this index is composed of hex-encoded smart contract result hash. (example: `cbd4692a092226d68fde24840586bdf36b30e02dc4bf2a73516730867545d53c`) [comment]: # (mx-context-auto) diff --git a/docs/sdk-and-tools/indices/transactions.md b/docs/sdk-and-tools/indices/transactions.md index 2707d0a27..3b59135cb 100644 --- a/docs/sdk-and-tools/indices/transactions.md +++ b/docs/sdk-and-tools/indices/transactions.md @@ -11,7 +11,17 @@ This page describes the structure of the `transactions` index (Elasticsearch), a ## _id -The `_id` field for this index is composed of hex encoded transaction hash. +:::warning Important + +**The `transactions` index will be deprecated and removed in the near future.** +We recommend using the [operations](/sdk-and-tools/indices/es-index-operations) index, which contains all the transaction data. +The only change required in your queries is to include the `type` field with the value `normal` to fetch all transactions. + +Please make the necessary updates to ensure a smooth transition. +If you need further assistance, feel free to reach out. +::: + +The `_id` field for this index is composed of hex-encoded transaction hash. (example: `cad4692a092226d68fde24840586bdf36b30e02dc4bf2a73516730867545d53c`) [comment]: # (mx-context-auto) diff --git a/sidebars.js b/sidebars.js index 852367c15..950311499 100644 --- a/sidebars.js +++ b/sidebars.js @@ -339,6 +339,7 @@ const sidebars = { "sdk-and-tools/indices/es-index-tokens", "sdk-and-tools/indices/es-index-transactions", "sdk-and-tools/indices/es-index-validators", + "sdk-and-tools/indices/es-index-events", ], }, ], From 25cc2d803d35b6a7e0b801450ee7409ab51677d0 Mon Sep 17 00:00:00 2001 From: axenteoctavian Date: Mon, 24 Feb 2025 16:35:01 +0200 Subject: [PATCH 06/19] Fix sovereign links --- docs/sovereign/overview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sovereign/overview.md b/docs/sovereign/overview.md index 3cdaae898..a0f05ef42 100644 --- a/docs/sovereign/overview.md +++ b/docs/sovereign/overview.md @@ -17,8 +17,8 @@ This guide provides detailed instructions on setting up, deploying, and managing 1. [Introduction](/sovereign/concept) 2. [Prerequisites](/sovereign/system-requirements) -3. [Setup Guide](/sovereign/setup) -4. [Custom Configurations](/sovereign/deployment) +3. [Setup Guide](/sovereign/local-setup) +4. [Custom Configurations](/sovereign/custom-configurations) 5. [Managing a Sovereign Chain](/sovereign/managing-sovereign) 6. [Economics](/sovereign/token-economics) 7. [Governance](/sovereign/governance) From 6ac4aef1947790588576850cc76cdd854f965e67 Mon Sep 17 00:00:00 2001 From: Iulia Cimpeanu <72752718+iuliacimpeanu@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:10:57 +0200 Subject: [PATCH 07/19] Added copy as markdown functionality on docs. (#1066) * Add copy as markdown button on docs. * Fixes after review. --- package.json | 1 + src/components/CopyMarkdownButton/index.jsx | 45 +++++++++++++++++++++ src/css/custom.css | 29 +++++++++++++ src/theme/MDXContent/index.js | 12 ++++++ 4 files changed, 87 insertions(+) create mode 100644 src/components/CopyMarkdownButton/index.jsx create mode 100644 src/theme/MDXContent/index.js diff --git a/package.json b/package.json index 04c5a80fb..e440202a2 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@docusaurus/theme-mermaid": "3.5.2", "@fortawesome/fontawesome-svg-core": "6.5.2", "@fortawesome/free-brands-svg-icons": "6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.2", "@mdx-js/react": "3.0.1", diff --git a/src/components/CopyMarkdownButton/index.jsx b/src/components/CopyMarkdownButton/index.jsx new file mode 100644 index 000000000..d1453ea43 --- /dev/null +++ b/src/components/CopyMarkdownButton/index.jsx @@ -0,0 +1,45 @@ +import React, { useState } from "react"; +import { useLocation } from "@docusaurus/router"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faClone } from "@fortawesome/free-regular-svg-icons"; +import { faCheck } from "@fortawesome/free-solid-svg-icons"; + +const CopyMarkdownButton = () => { + const [ isCopied, setIsCopied ] = useState(false); + const location = useLocation(); + + if(location.pathname === '/'){ + return; + } + + const url = `https://raw.githubusercontent.com/multiversx/mx-docs/refs/heads/main/docs${location.pathname}.md`; + + const copyMarkdownToClipboard = async () => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error("Markdown file not found!"); + } + + const markdown = await response.text(); + await navigator.clipboard.writeText(markdown); + setIsCopied(true); + + setTimeout(() => { + setIsCopied(false); + }, 1000); + } catch (error) { + console.error("Error copying markdown: ", error); + } +}; + + return ( + + ); +}; + +export default CopyMarkdownButton; \ No newline at end of file diff --git a/src/css/custom.css b/src/css/custom.css index 4489eabc5..6d7102c59 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -494,6 +494,35 @@ body.no-scroll { display: none; } */ +.copy-as-markdown-button { + position: absolute; + right: 0; + top: -7.3rem; + color: #737373; + cursor: pointer; +} + +.copy-as-markdown-button:hover { + color: #23f7dd; + transition: color 0.2s; +} + +.copy-as-markdown-button.check { + color: #23f7dd; +} + +@media (max-width: 996px) { + .copy-as-markdown-button { + top: -10.6rem; + } +} + +@media (max-width: 375px) { + .copy-as-markdown-button { + top: -13rem; + } +} + /* -------------------- End Docsearch ------------------- */ /* ====================================== GLOBAL ================================ */ diff --git a/src/theme/MDXContent/index.js b/src/theme/MDXContent/index.js new file mode 100644 index 000000000..a3c52d96b --- /dev/null +++ b/src/theme/MDXContent/index.js @@ -0,0 +1,12 @@ +import React from "react"; +import MDXContent from "@theme-original/MDXContent"; +import CopyMarkdownButton from "@site/src/components/CopyMarkdownButton"; + +const MDXContentWrapper = (props) => ( +
+ + +
+); + +export default MDXContentWrapper; From b7d2d4db581d48a50bfc5fd9a188ee4e7e8e666a Mon Sep 17 00:00:00 2001 From: Iulian Pascalau Date: Tue, 25 Feb 2025 13:44:59 +0200 Subject: [PATCH 08/19] - added token operation warnings on the bridge --- docs/bridge/whitelist-requirements.md | 11 ++++++++- package-lock.json | 35 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/bridge/whitelist-requirements.md b/docs/bridge/whitelist-requirements.md index 74330fbb7..0e9093bc2 100644 --- a/docs/bridge/whitelist-requirements.md +++ b/docs/bridge/whitelist-requirements.md @@ -62,4 +62,13 @@ the EVM-compatible chain side (depending on the ERC20 contract variant, `addMint For reference, this is the list of the known smart contracts: * **Wrapper** `erd1qqqqqqqqqqqqqpgq305jfaqrdxpzjgf9y5gvzh60mergh866yfkqzqjv2h` * **Ethereum Safe** `erd1qqqqqqqqqqqqqpgqf2cu60ffz9v68r0h62sufxxf67n7xprue3yq4ap4k2` -* **BSC Safe** `erd1qqqqqqqqqqqqqpgqa89ts8s3un2tpxcml340phcgypyyr609e3yqv4d8nz` \ No newline at end of file +* **BSC Safe** `erd1qqqqqqqqqqqqqpgqa89ts8s3un2tpxcml340phcgypyyr609e3yqv4d8nz` + +:::warning +To ensure the correct functioning of the bridge, as a MultiversX token owner please ensure the following points are met: +* if you make use of the transfer-role on your token, remember to grant the role also on the **Safe**, **MultiTransfer**, **BridgedTokensWrapper**, and **BridgeProxy** contracts; +* do not freeze the above-mentioned contracts; +* do not wipe tokens on the above-mentioned contracts. + +Failure to comply with these rules will force the bridge owner to blacklist the token in order to restore the correct functioning of the bridge. +::: diff --git a/package-lock.json b/package-lock.json index c134127d0..392d519ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@docusaurus/theme-mermaid": "3.5.2", "@fortawesome/fontawesome-svg-core": "6.5.2", "@fortawesome/free-brands-svg-icons": "6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.2", "@mdx-js/react": "3.0.1", @@ -2988,6 +2989,25 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", + "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", @@ -20303,6 +20323,21 @@ "@fortawesome/fontawesome-common-types": "6.5.2" } }, + "@fortawesome/free-regular-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", + "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "dependencies": { + "@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==" + } + } + }, "@fortawesome/free-solid-svg-icons": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", From 9c8ebeedabb323c1709b5cf4cf32f43df23255c8 Mon Sep 17 00:00:00 2001 From: Iulia Cimpeanu <72752718+iuliacimpeanu@users.noreply.github.com> Date: Tue, 25 Feb 2025 18:01:07 +0200 Subject: [PATCH 09/19] Fixed copy markdown button at refresh. (#1069) * Merge branch 'origin/development' into 'ic/feature/copy-as-markdown' * Fixed copy markdown button on refresh. --- src/components/CopyMarkdownButton/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/CopyMarkdownButton/index.jsx b/src/components/CopyMarkdownButton/index.jsx index d1453ea43..ca925d91b 100644 --- a/src/components/CopyMarkdownButton/index.jsx +++ b/src/components/CopyMarkdownButton/index.jsx @@ -38,6 +38,8 @@ const CopyMarkdownButton = () => { className={`copy-as-markdown-button ${isCopied ? 'check' : ''}`} onClick={copyMarkdownToClipboard} icon={!isCopied ? faClone : faCheck} + width="16px" + height="16px" /> ); }; From 85533596412e185eaacb948f0c8cdd47d527421c Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 26 Feb 2025 13:27:03 +0200 Subject: [PATCH 10/19] add streaming --- src/components/AskAiButton/ChatWindow.tsx | 59 ++++++++++++++++++++++- src/components/AskAiButton/useChat.ts | 52 +++++++++++++++++--- 2 files changed, 102 insertions(+), 9 deletions(-) diff --git a/src/components/AskAiButton/ChatWindow.tsx b/src/components/AskAiButton/ChatWindow.tsx index 21c11f944..68f96c1bc 100644 --- a/src/components/AskAiButton/ChatWindow.tsx +++ b/src/components/AskAiButton/ChatWindow.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from "react"; +import React, { useRef, useState, useEffect } from "react"; import ReactMarkdown from "react-markdown"; import CodeBlock from "@theme/CodeBlock"; // Docusaurus built–in CodeBlock import userAvatar from "@site/static/ask_ai/ask_ai_user_avatar.png"; @@ -12,7 +12,31 @@ const ChatWindow = ({ onClose }) => { >([{ role: "assistant", content: "Ask me anything about MultiversX!" }]); const [input, setInput] = useState(""); - const { sendMessage, isLoading, error } = useChat(); + const [streamMessage, setStreamMessage] = useState< + { role: string; content: string } | undefined + >(undefined); + + const { sendMessage, isLoading, error, messages: chatMessages } = useChat(); + + // Update the messages state as new data arrives + useEffect(() => { + setStreamMessage({ + role: "assistant", + content: chatMessages.map((msg) => msg.content).join(" "), + }); + + chatBodyRef.current?.scrollTo({ + top: chatBodyRef.current.scrollHeight * 10, + behavior: "smooth", + }); + + if (isLoading) return; + + if (!isLoading && streamMessage) { + setMessages([...messages, streamMessage]); + setStreamMessage(undefined); + } + }, [isLoading, chatMessages]); // Called when the user hits "Send". Adds a user message then simulates an assistant reply. const chatBodyRef = useRef(null); @@ -141,6 +165,37 @@ const ChatWindow = ({ onClose }) => {
))} + + {streamMessage && streamMessage.content?.length > 0 && ( +
+
+ + + {String(children).replace(/\n$/, "")} + + ) : ( + + {children} + + ); + }, + }} + > + {streamMessage.content} + +
+
+ )} {isLoading &&
Thinking...
} diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts index de99f3b66..36cc16b1c 100644 --- a/src/components/AskAiButton/useChat.ts +++ b/src/components/AskAiButton/useChat.ts @@ -11,21 +11,24 @@ interface ChatError { status?: number; } -const CHAT_ENDPOINT = "https://tools.multiversx.com/ai-docs-api/chat"; +const STREAM_ENDPOINT = "http://localhost:3005/ai-docs-api/chat/stream"; + export const useChat = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [threadId, setThreadId] = useState(); + const [messages, setMessages] = useState([]); const sendMessage = async (message: string) => { setIsLoading(true); setError(null); try { - const response = await fetch(CHAT_ENDPOINT, { + const response = await fetch(STREAM_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", + Accept: "text/event-stream", }, body: JSON.stringify({ message, @@ -37,16 +40,50 @@ export const useChat = () => { throw new Error(`HTTP error! status: ${response.status}`); } - const data: ChatResponse = await response.json(); - setThreadId(data.threadId); + const reader = response.body?.getReader(); + const decoder = new TextDecoder("utf-8"); + + let accumulatedContent = ""; + + while (true) { + const { done, value } = (await reader?.read()) || {}; + if (done) break; + + let chunk = decoder.decode(value, { stream: true }); + + console.log(chunk); + try { + const { threadId } = JSON.parse(chunk); + setThreadId(threadId); + chunk = ""; + } catch {} + accumulatedContent += chunk; - return data; + // Update the last message with the new content + setMessages((prevMessages) => { + const lastMessage = prevMessages[prevMessages.length - 1]; + const updatedMessage = { + ...lastMessage, + content: accumulatedContent, + }; + return [...prevMessages.slice(0, -1), updatedMessage]; + }); + + // If the response is complete, close the stream + if (chunk.includes("done")) { + break; + } + } } catch (err) { const error = err as Error; setError({ message: error.message }); - return null; } finally { - setIsLoading(false); + setTimeout(() => { + setIsLoading(false); + }); + setTimeout(() => { + setMessages([]); + }); } }; @@ -54,6 +91,7 @@ export const useChat = () => { sendMessage, isLoading, error, + messages, threadId, }; }; From 17b77fe667da9a11fb9f4447eb61618e1d910160 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 26 Feb 2025 13:50:07 +0200 Subject: [PATCH 11/19] change ai service url --- src/components/AskAiButton/useChat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts index 36cc16b1c..bd53d0545 100644 --- a/src/components/AskAiButton/useChat.ts +++ b/src/components/AskAiButton/useChat.ts @@ -11,7 +11,7 @@ interface ChatError { status?: number; } -const STREAM_ENDPOINT = "http://localhost:3005/ai-docs-api/chat/stream"; +const STREAM_ENDPOINT = "https://tools.multiversx.com/ai-docs-api/chat/stream"; export const useChat = () => { const [isLoading, setIsLoading] = useState(false); From 5587ddd6c7cb53784d89352ffb86d8004a635118 Mon Sep 17 00:00:00 2001 From: Iulia Cimpeanu <72752718+iuliacimpeanu@users.noreply.github.com> Date: Wed, 26 Feb 2025 13:52:37 +0200 Subject: [PATCH 12/19] Fixed pathname on copy markdown button. (#1070) * Add copy as markdown button on docs. * Fixes after review. * Merge branch 'origin/development' into 'ic/feature/copy-as-markdown' * Fixed copy markdown button on refresh. * Fixed pathname on copy markdown button. --- src/components/CopyMarkdownButton/index.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/CopyMarkdownButton/index.jsx b/src/components/CopyMarkdownButton/index.jsx index ca925d91b..e074a38ea 100644 --- a/src/components/CopyMarkdownButton/index.jsx +++ b/src/components/CopyMarkdownButton/index.jsx @@ -12,13 +12,21 @@ const CopyMarkdownButton = () => { return; } - const url = `https://raw.githubusercontent.com/multiversx/mx-docs/refs/heads/main/docs${location.pathname}.md`; + const pathname = location.pathname.replace(/\/$/, ""); + const docsUrl = 'https://raw.githubusercontent.com/multiversx/mx-docs/refs/heads/main/docs' + const mdUrl = `${docsUrl}${pathname}.md` + const mdxUrl = `${docsUrl}${pathname}.mdx` const copyMarkdownToClipboard = async () => { try { - const response = await fetch(url); + let response = await fetch(mdUrl); + if (!response.ok) { + response = await fetch(mdxUrl); + + if (!response.ok) { throw new Error("Markdown file not found!"); + } } const markdown = await response.text(); From 0fddca86e509ec54d74cfcb71099f35f189c146d Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 27 Feb 2025 15:04:56 +0200 Subject: [PATCH 13/19] light theme --- src/components/AskAiButton/ChatWindow.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/AskAiButton/ChatWindow.tsx b/src/components/AskAiButton/ChatWindow.tsx index 68f96c1bc..bd6d2356c 100644 --- a/src/components/AskAiButton/ChatWindow.tsx +++ b/src/components/AskAiButton/ChatWindow.tsx @@ -72,20 +72,17 @@ const ChatWindow = ({ onClose }) => { return (
{/* Modal Header */}
{ display: "flex", position: "relative", padding: "0px 16px 32px", - boxShadow: " 0px -8px 32px 0px rgba(23,23,23,1)", + // boxShadow: " 0px -8px 32px 0px rgba(23,23,23,1)", }} > Date: Thu, 27 Feb 2025 15:15:10 +0200 Subject: [PATCH 14/19] rem log --- src/components/AskAiButton/useChat.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts index bd53d0545..fe846170e 100644 --- a/src/components/AskAiButton/useChat.ts +++ b/src/components/AskAiButton/useChat.ts @@ -51,7 +51,6 @@ export const useChat = () => { let chunk = decoder.decode(value, { stream: true }); - console.log(chunk); try { const { threadId } = JSON.parse(chunk); setThreadId(threadId); From 1981ab363d64afd7a6a0a5ed6ae26da5fb67c20c Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 27 Feb 2025 15:31:20 +0200 Subject: [PATCH 15/19] buton text color --- src/components/AskAiButton/ChatWindow.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/AskAiButton/ChatWindow.tsx b/src/components/AskAiButton/ChatWindow.tsx index bd6d2356c..a5c092896 100644 --- a/src/components/AskAiButton/ChatWindow.tsx +++ b/src/components/AskAiButton/ChatWindow.tsx @@ -99,7 +99,6 @@ const ChatWindow = ({ onClose }) => { style={{ background: "transparent", border: "none", - color: "#fff", fontWeight: "bold", fontSize: "18px", cursor: "pointer", @@ -234,7 +233,6 @@ const ChatWindow = ({ onClose }) => { padding: "8px 16px", fontSize: "16px", border: "none", - color: "#fff", cursor: "pointer", backgroundColor: "transparent", borderRadius: "12px", From e738875a28bc80e5fa9b10596c99dd4ba47c3a70 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 27 Feb 2025 15:35:17 +0200 Subject: [PATCH 16/19] remove file_search response prefix --- src/components/AskAiButton/useChat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AskAiButton/useChat.ts b/src/components/AskAiButton/useChat.ts index fe846170e..f79882135 100644 --- a/src/components/AskAiButton/useChat.ts +++ b/src/components/AskAiButton/useChat.ts @@ -56,7 +56,7 @@ export const useChat = () => { setThreadId(threadId); chunk = ""; } catch {} - accumulatedContent += chunk; + accumulatedContent += chunk === "file_search" ? "" : chunk; // Update the last message with the new content setMessages((prevMessages) => { From a4cd8169815832e3c65ec025e27a35f788665efa Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 28 Feb 2025 14:13:56 +0200 Subject: [PATCH 17/19] beta badge progress --- src/components/AskAiButton/index.tsx | 13 ++++++++ src/css/custom.css | 47 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/components/AskAiButton/index.tsx b/src/components/AskAiButton/index.tsx index e3720372f..9e7d65b4a 100644 --- a/src/components/AskAiButton/index.tsx +++ b/src/components/AskAiButton/index.tsx @@ -29,12 +29,25 @@ const AskAiButton = () => { tfoot:not(:last-child) { } /* -------------------- End Dynamic Theme Support ------------------- */ + + +.betaBadge-wrapper { + position: relative; + background-color: var(--docsearch-hit-color); + border-radius: 5px; + z-index: 1; + + + /* background-color: var(--intense-bg); + + &:before { + background: linear-gradient(to right, #00ba56, #0096ab, #c958a9); + opacity: 1; + border: none; + z-index: -1; + } */ + +} + +.betaBadge { + font-size: 10px; + line-height: 1; + font-family: 'Roobert Medium', sans-serif; + padding: 4px 8px; + border-radius: 50%; + background-clip: padding-box; + border-radius: 16px; + border: 1px solid transparent; + position: relative; + background-color: var(--body-bg); + color: var(--body-color); + font-weight: normal; + + &:before { + position: absolute; + pointer-events: none; + top: -1px; + bottom: -1px; + left: -1px; + content: ''; + border-radius: 16px; + right: -1px; + border: 1px solid var(--heading-color); + opacity: 0.25; + } +} \ No newline at end of file From f1ff5256bf3c9d5b21252a82eb5d700a3b89e52d Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 28 Feb 2025 16:34:05 +0200 Subject: [PATCH 18/19] add beta badge --- src/components/AskAiButton/ChatWindow.tsx | 11 +++++- src/components/AskAiButton/index.tsx | 17 +++------ src/css/custom.css | 46 ----------------------- 3 files changed, 15 insertions(+), 59 deletions(-) diff --git a/src/components/AskAiButton/ChatWindow.tsx b/src/components/AskAiButton/ChatWindow.tsx index a5c092896..e72751085 100644 --- a/src/components/AskAiButton/ChatWindow.tsx +++ b/src/components/AskAiButton/ChatWindow.tsx @@ -91,8 +91,15 @@ const ChatWindow = ({ onClose }) => { alignItems: "center", }} > -
- Ask MultiversX AI +
+ Ask MultiversX AI
tfoot:not(:last-child) { /* -------------------- End Dynamic Theme Support ------------------- */ - -.betaBadge-wrapper { - position: relative; - background-color: var(--docsearch-hit-color); - border-radius: 5px; - z-index: 1; - - - /* background-color: var(--intense-bg); - - &:before { - background: linear-gradient(to right, #00ba56, #0096ab, #c958a9); - opacity: 1; - border: none; - z-index: -1; - } */ - -} - -.betaBadge { - font-size: 10px; - line-height: 1; - font-family: 'Roobert Medium', sans-serif; - padding: 4px 8px; - border-radius: 50%; - background-clip: padding-box; - border-radius: 16px; - border: 1px solid transparent; - position: relative; - background-color: var(--body-bg); - color: var(--body-color); - font-weight: normal; - - &:before { - position: absolute; - pointer-events: none; - top: -1px; - bottom: -1px; - left: -1px; - content: ''; - border-radius: 16px; - right: -1px; - border: 1px solid var(--heading-color); - opacity: 0.25; - } -} \ No newline at end of file From 80fd7f25f85aa9bf0faeac27280f3c1afa470647 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 28 Feb 2025 16:47:22 +0200 Subject: [PATCH 19/19] add beta badge icon fix --- src/components/AskAiButton/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/AskAiButton/index.tsx b/src/components/AskAiButton/index.tsx index 52eea742c..328661bd4 100644 --- a/src/components/AskAiButton/index.tsx +++ b/src/components/AskAiButton/index.tsx @@ -27,13 +27,15 @@ const AskAiButton = () => { */}