You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@resilientdb.apache.org by ju...@apache.org on 2025/11/24 23:18:18 UTC
(incubator-resilientdb) branch master updated: Adding AI tools to Monorepo (#200)
This is an automated email from the ASF dual-hosted git repository.
junchao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-resilientdb.git
The following commit(s) were added to refs/heads/master by this push:
new 80db8b9c Adding AI tools to Monorepo (#200)
80db8b9c is described below
commit 80db8b9cdbe60b4facbd371ea660f98e32338309
Author: Bismanpal-Singh <83...@users.noreply.github.com>
AuthorDate: Mon Nov 24 15:18:14 2025 -0800
Adding AI tools to Monorepo (#200)
* Added login logout basic func
* Added API test func
* Added test api function
* Added API calls to CLI functions
* CLI updates
* Update README.md
* Action Test
* Added Apache release files
* Initial commit from Create Next App
* Add utility functions, update configuration, and clean up assets
* Update chat message background color for improved styling
* first commit
* Update Welcome and Placeholder pages with new content and styling enhancements
* Fix import order in button component for consistency
* Refactor Welcome.module.css styles for title: updated color to black, adjusted font size and weight, and modified media query breakpoint.
* Adding Python IDE
* Update PythonPlayground component and styles; refactor code for consistency, enhance output display, and add subtitle styling in Welcome component.
* Refactor PythonPlayground component for improved code formatting and output handling; adjust Welcome component structure to enhance title styling.
* Trying to fix IDE
* Fixed SDK to working model for a KV Txn with ResDB
* Refining it to further mimic the actual SDK
* Finalized the SDK interactive component.
* Add sitemap generation functionality and remove deprecated API version route; clean up documentation references and remove unused content.
* Enhance application with new AI-powered features; integrate FloatingAssistant component for interactive code assistance, update PythonPlayground and TypeScriptPlayground for improved user experience, and refine project structure by adding new GraphQL setup documentation and example templates.
* Update README link in contributing section to point to the correct repository for ResilientDB GraphQL.
* Add GitHub comment system with production safeguards: - Add NextAuth GitHub integration - Implement comment-to-issue conversion - Add rate limiting and security checks - Move auth pages to pages directory - Add production environment checks
* Fix: Remove duplicate API routes from app directory
* Fix: Move AuthProviders to components directory - Remove duplicate providers file from pages - Update import paths in auth pages - Clean up build structure
* Fixed build issues for Vercel deployment
* fix: add missing peer dependencies
* Fixed IDE and Authentication issues
* Remove fallback API key and clean up layout component by removing commented footer
* Add Markdown rendering support and update layout styles
* Add loader component and enhance chat message rendering with loading states
* Enhance MarkdownRenderer with horizontal rule support and improve code block rendering; limit chat source nodes to a maximum of 3
* Add document management API endpoints and update layout styles; enhance .gitignore for parsed documents cache
* Added mongoDB query generator
* Python Cache added
* Added ResDB Node Cache
* Added ResDB ORM, CLI and ResContract
* fixed deployment issue
* Fixed rescontract and added resvault
* Add PocketFlowChatbot component and integrate into layout; update package-lock.json for @codemirror/view and add new dependency
* CSS font size changes
* update notice
* Update .asf.yaml
* update note
* Update .asf.yaml
* Implement ResearchChatPage component for document-based chat functionality; update MainNavBar to link to Researcher page and comment out feature cards in WelcomePage.
* Add HuggingFace integration to document indexing and chat services; update package dependencies for @llamaindex/huggingface
* #6 shadcn
* template #6
* #6 resolve nitpick
* Update card styles in ResearchChatPage and CardHeader for improved UI; adjust background opacity and margin for better layout consistency. #6
* consolidated header size #6
* mv DISCLAIMER-WIP DISCLAIMER
* updatte license
* add license
* update header
* Enhance document handling in ResearchChatPage by adding multi-document selection support; update API endpoints to handle multiple document paths for indexing and querying. Introduce new UI components for document source attribution and multi-document selector. Update package dependencies for @radix-ui/react-tabs and related libraries. #5
* #5 lint
* #5 remove method
* Add comprehensive API and project documentation for ResAI, detailing endpoints, system architecture, and component interactions. This includes new markdown files for API usage, project overview, and system architecture diagrams to enhance understanding of the application structure and functionality. #5
* Refactor PDF preview visibility logic in ResearchChatPage for clarity; update max context length in MultiDocumentQueryEngine to accommodate deepseek requirements and improve separator handling in document truncation logic. #5
* lint #5
* Update system architecture documentation to implement dark mode agnostic styling for improved readability and accessibility across components.
* #9 migrate parsed data to psql; eliminate the file directory dependency for parsed data storage
* #9 reduce line complexity
* #9 lint esline
* #10 code composer
* Refactor code generation handling in ResearchChatPage; introduce new types for Document and CodeGeneration; implement streaming updates for code composer responses; enhance preview panel with separate tabs for code generations and PDF previews; improve accessibility and styling across components.
* Remove unused handleClearTool function from ChatInput component in Research page to streamline code and improve maintainability. #13
* Enhance ChatInput component by adding a vertical separator for improved layout when using the code composer tool. Update language style guide to require pseudocode and implementation sections to be wrapped in triple backticks for clarity and consistency. Introduce ensureCodeBlock function to standardize code block formatting in responses.
* #13 refactor post
* Refactor Tabs component: reorder export statements and add cursor pointer and hover effects to TabsTrigger for improved user interaction and accessibility. #13
* Update package dependencies and integrate React Query: Added @tanstack/react-query to package.json and package-lock.json for improved data fetching capabilities. Refactored document loading in ResearchChatPage to utilize useDocuments hook, enhancing error handling and state management. Removed unused Document interface from types.ts to streamline code. Updated PDFPreviewTab and MultiDocumentSelector components to use the new Document type from useDocuments. Improved accessibility fe [...]
* Enhance CodeReranker class: Add comments to clarify the score enhancement formula, detailing how the original score is boosted based on code relevance and the boost factor.
* Integrate Radix UI Accordion component: Added @radix-ui/react-accordion and @radix-ui/react-collapsible to package.json and package-lock.json. Refactored CodeGenerationTab component to utilize Accordion for displaying plan, pseudocode, and implementation sections, enhancing user experience and accessibility. Implemented copy-to-clipboard functionality for each section with visual feedback. Improved code organization and readability.
* Remove unused extractImplementationCode function from code-composer-prompts.ts to streamline code and improve maintainability.
* Refactor ResearchChatPage and introduce ChatInput component: Streamlined message handling by moving ChatInput logic into a separate component, enhancing code organization and maintainability. Implemented code composer streaming updates and improved accessibility features. Removed unused imports and code segments for cleaner structure. #13
* #13 topics section
* Minor adjustment in code-generation-tab.ts to remove unnecessary whitespace in the AccordionContent component for improved layout.
* Add sources property to CodeGeneration interface and update ResearchChatPage to include source information in code generations. Integrate MultiDocumentSourceBadge in TopicSection for improved source display #13
* #13 major refactor to reranker
* #13 possibly remove reranker
* #13 add agent reranker
* #13 reduce json size
* #13 types
* Update CodeGeneration interface to include 'reading-documents' section, adjust topK value for code composer, and enhance ResearchChatPageContent with early code generation handling. Introduce ReadingDocumentsSection in code-generation-tab for improved user experience during document processing. #13
* #13 update page
* #13 remove reranker
* #13 update gitignore
* #13 refine prompts and generation screen
* Refactor imports to remove type annotations for Document and CodeGeneration, and encapsulate implementation cleanup logic in a new utility function. Update code generation logic to utilize the cleanUpImplementation function for better readability and maintainability. #13
* Refactor ResearchChatPageContent to include handleCloseCodeGeneration function for improved code generation management. Update PreviewPanel to utilize onCloseCodeGeneration prop for handling tab closures. Adjust CodeGenerationTabs to support tab closing functionality and enhance PDF preview tab styling. #13
* #13 remove consts
* Refactor CodeComposerAgent to utilize MAX_TOKENS constant for maxTokens configuration. Update AnalyzedChunk interface to use concise property names (ir, cq, qa, ce, r) for improved clarity. Adjust scoring logic to reflect new property names and enhance readability. #13
* #13 update sources
* upgrade llamaindex
* #13 add comment
* #12 initial commit
* #12 cleanup
* #12 cleanup
* #12 cleanup
* #12 clean up
* #12 cleanup
* #12 new vector stores
* #12 cleanup
* #12 scripts added
* #12 add test
* #12 clean up todo: see if theres a better way to get missing indicies
* #12 update name
* #12 fix pg client leak
* #12 add citations
* #12 the return
* remove unneded code #12
* #12 remove dnc
* #12 remove sceripts
* #12 citations
* #12 delete md
* #12 cleanup
* #12 update name
* #12 update lock
* #12 landing page
* Add TypeScript types for three.js and update package dependencies; remove unused navbar component
* #12 add route back in
* #12 rm parsing cache
* #12 update blob
* #12 cleanup
* #12 env
* embed fix
* #18 add memory
* #18 session manager
* #18 fix performance
* Fixed some build issues for deployment
* #18 fix uuid
* #18 fix storage
* Fixed deployment issues p2
* Some more build issues
* #20 add gemini
* #17 agentic
* #17 add agent class
* fix node modules
* lint
* #remove session manager
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* #17 agent updates
* fix build issue
* #17 remove file
* #17 remove sample
* Updated UI Styling and landing page
* Fixed some build issues
* Fixed internal styling to align with UI
* Fixed UI bugs
* #17 added agent components
* #17 update input
* #17 page updates
* #17 update mode
* #17 update prompt
* #17 padding
* Added Reslens, Updated About page, Updated footer and other UI components, Added license workflow. Pending : License headers
* Added Apache License Headers to all files
* Added Missing headers
* Updated paths-ignore to include .mdx files and declared it in NOTICE as mdx does not support standard licensing
* Fixed the remaining licensing issues
* #17 style update
* #17 update memory configuration and role assignment
* code agent v1 #17
* Refactor chat response handling to improve tool call delimiters and remove sample messages. Updated message state management to prevent display of incomplete control blocks and ensure proper rendering of tool call parts.
* #17 fix tool calls
* major linting to resolve build
* Enhance model selection by disabling the "code" option in production environments to prevent unintended usage.
* #23 add supabase
* #23 add cache
* #23 remove duplicate vall to vector index
* Fixing the UI and making it cleaner
* Added missing header, fixed a UI bug
* #23 update
* remove condition
* Update cache handling in LlamaService to include VERCEL environment check
* Refactor model selection disabling logic to use a boolean check for VERCEL environment variable.
* Refactor model selection disabling logic to check for development environment instead of VERCEL variable.
* REMOVE AUTO REPARSING
* fix streaming
* Refactor code generation logic for code-composer flows, removing unused source info parsing and enhancing phase management. Update UI components to reflect changes in state handling and remove deprecated topic section from code generation tab.
* Update system prompt in CodeAgent to emphasize concise and structured pseudocode creation, enhancing clarity and focus on essential steps for implementation.
* build
* Remove API and documentation files for chat and research endpoints, streamlining the codebase and eliminating outdated references.
* Update blog.md
* implemented cloud storage, files are now read from a google drive folder #26
* got rid of some useless console logs #26
* Enhance landing page with new components and styles; integrate ShaderBackground, Header, HeroContent, and PulsingCircle; update global styles for a sleek design; add framer-motion and @paper-design/shaders-react dependencies.
* Enhance FloatingAssistant component with new settings for highlight mode; update GitHubAuth and PythonPlayground components for improved UI; add postbuild script to package.json for pagefind integration; refine styles across various components for a cohesive design.
* Update next.config.mjs with licensing information and performance optimizations; remove unused search input from Header component; add About section to metadata and create about.mdx file for documentation.
* Remove About section from metadata in _meta.ts
* Integrate Pagefind search functionality; update package dependencies to version 1.4.0; enhance global styles for search components; refactor SearchBar to utilize PagefindSearch component; improve documentation chapter titles for clarity.
* Implement AI search functionality with new API endpoint; enhance Pagefind search component to support agent mode; add document indexing capabilities; update package dependencies; refactor search results display with highlighting; improve user experience with keyboard shortcuts and mode toggling.
* changed file reterival from different folders now, and different sections #26
* Update installation.mdx
* Revise installation guide and prerequisites section
Updated installation instructions and removed unnecessary notes.
* updated the doucment retrival system #26
* feat:improved outliners & dropdowns and prevent tab-traps
* Update index.mdx
* Updated resvault page
* fixed the issues and now the chat works with the pdfs #26
* fixed the PR comments #26
* fixed the lint issues #26
* Refactor document handling in ResearchChatPageContent and add document update functionality in NexusAgent. Updated the way document counts are retrieved and logged agent research prompts. Implemented a new method to add document updates to memory with structured logging for better traceability.
* Add web search preference to chat request handling and update agent prompt logic. Enhanced ResearchChatPageContent to manage web search toggle and integrated contextual query building in agent response handling. Updated AGENT_RESEARCH_PROMPT to reflect new behavior for document and web search prioritization.
* blog update
* Remove incorrect Beacon import
* Remove AI-Tooling folder to reset Beacon import
* Cleanup before correct Beacon import
* Squashed 'ecosystem/AI-Tooling/beacon/' content from commit 1f54f35
git-subtree-dir: ecosystem/AI-Tooling/beacon
git-subtree-split: 1f54f3566f38b56f64cc218886062c6dac2457d7
* Squashed 'ecosystem/AI-Tooling/nexus/' content from commit 9148a40
git-subtree-dir: ecosystem/AI-Tooling/nexus
git-subtree-split: 9148a407d227def07651adf99bd79025f024637d
* Cleanup Nexus folder before subtree import
---------
Co-authored-by: Gopal Nambiar <go...@gmail.com>
Co-authored-by: gopuman <go...@node0.gopuman-214913.resilientdb-pg0.utah.cloudlab.us>
Co-authored-by: Apratim Shukla <ap...@gmail.com>
Co-authored-by: aibrahim <ab...@fticonsulting.com>
Co-authored-by: Abdel Ibrahim <ab...@gmail.com>
Co-authored-by: harish876 <ha...@gmail.com>
Co-authored-by: Ubuntu <ic...@gmail.com>
Co-authored-by: abdeliibrahim <82...@users.noreply.github.com>
Co-authored-by: PNejr10 <pa...@gmail.com>
Co-authored-by: winter4 <kr...@gmail.com>
Co-authored-by: Nabeel Hussain M N <68...@users.noreply.github.com>
Co-authored-by: Abdel <ab...@Abdels-MacBook-Pro.local>
---
ecosystem/AI-Tooling/beacon/.env.local.example | 9 +
.../beacon/.github/assets/beacon-hero.png | Bin 0 -> 463895 bytes
.../beacon/.github/workflows/license.yml | 37 +
.../AI-Tooling/beacon/.github/workflows/loc.yml | 61 +
ecosystem/AI-Tooling/beacon/.gitignore | 134 +
ecosystem/AI-Tooling/beacon/.licenserc.yaml | 27 +
ecosystem/AI-Tooling/beacon/.npmrc | 3 +
ecosystem/AI-Tooling/beacon/.nvmrc | 1 +
ecosystem/AI-Tooling/beacon/.prettierignore | 1 +
ecosystem/AI-Tooling/beacon/.prettierrc.mjs | 35 +
ecosystem/AI-Tooling/beacon/.storybook/main.ts | 16 +
ecosystem/AI-Tooling/beacon/.storybook/preview.tsx | 33 +
ecosystem/AI-Tooling/beacon/.stylelintignore | 2 +
ecosystem/AI-Tooling/beacon/.stylelintrc.json | 28 +
ecosystem/AI-Tooling/beacon/.vscode/settings.json | 4 +
ecosystem/AI-Tooling/beacon/CODE_OF_CONDUCT.md | 95 +
ecosystem/AI-Tooling/beacon/DISCLAIMER.txt | 38 +
ecosystem/AI-Tooling/beacon/LICENSE | 201 +
ecosystem/AI-Tooling/beacon/NOTICE | 10 +
ecosystem/AI-Tooling/beacon/README.md | 67 +
ecosystem/AI-Tooling/beacon/app/_meta.ts | 43 +
.../AI-Tooling/beacon/app/api/ai-search/route.ts | 556 +
ecosystem/AI-Tooling/beacon/app/api/chat/route.ts | 131 +
.../AI-Tooling/beacon/app/api/compile/route.ts | 117 +
.../beacon/app/api/github-releases/route.ts | 36 +
.../beacon/app/docs/[[...mdxPath]]/page.tsx | 43 +
ecosystem/AI-Tooling/beacon/app/global.css | 286 +
ecosystem/AI-Tooling/beacon/app/layout.tsx | 120 +
ecosystem/AI-Tooling/beacon/app/lib/constants.ts | 350 +
ecosystem/AI-Tooling/beacon/app/lib/utils.ts | 22 +
ecosystem/AI-Tooling/beacon/app/page.module.css | 361 +
ecosystem/AI-Tooling/beacon/app/page.tsx | 17 +
ecosystem/AI-Tooling/beacon/app/providers.tsx | 26 +
ecosystem/AI-Tooling/beacon/app/search/page.tsx | 25 +
ecosystem/AI-Tooling/beacon/app/sitemap.ts | 95 +
ecosystem/AI-Tooling/beacon/asf.yaml | 42 +
.../AI-Tooling/beacon/components/AskAIModal.tsx | 137 +
.../AI-Tooling/beacon/components/AuthProviders.tsx | 32 +
.../ColorSchemeControl.module.css | 46 +
.../ColorSchemeControl.story.tsx | 26 +
.../ColorSchemeControl/ColorSchemeControl.tsx | 49 +
.../ColorSchemeControl/HeaderControl.module.css | 50 +
.../ColorSchemeControl/HeaderControl.tsx | 51 +
.../ColorSchemeToggle/ColorSchemeToggle.tsx | 62 +
.../beacon/components/CommentSection.tsx | 133 +
.../beacon/components/Content/Content.story.tsx | 28 +
.../beacon/components/Content/Content.tsx | 74 +
.../beacon/components/FloatingAssistant.tsx | 832 +
.../AI-Tooling/beacon/components/GitHubAuth.tsx | 182 +
.../beacon/components/InstallationForm.tsx | 128 +
.../beacon/components/Logo/Logo.story.tsx | 26 +
.../AI-Tooling/beacon/components/Logo/Logo.tsx | 24 +
.../components/MantineFooter/MantineFooter.tsx | 384 +
.../components/MantineNavBar/MantineNavBar.tsx | 28 +
.../MantineNextraThemeObserver.tsx | 43 +
.../beacon/components/PocketFlowChatbot.tsx | 130 +
.../AI-Tooling/beacon/components/PythonIDE.tsx | 114 +
.../beacon/components/PythonPlayground.tsx | 773 +
.../components/ReleaseNotes/ReleaseNotes.tsx | 148 +
.../beacon/components/ResContractPlayground.tsx | 316 +
.../beacon/components/SearchBar/PagefindSearch.tsx | 725 +
.../beacon/components/SearchBar/SearchBar.tsx | 7 +
.../beacon/components/TypeScriptPlayground.tsx | 800 +
.../beacon/components/Welcome/Welcome.module.css | 32 +
.../beacon/components/Welcome/Welcome.story.tsx | 26 +
.../beacon/components/Welcome/Welcome.test.tsx | 31 +
.../beacon/components/Welcome/Welcome.tsx | 91 +
.../components/cache/NodeConfigGenerator.tsx | 155 +
.../beacon/components/cache/NodeQueryBuilder.tsx | 145 +
.../components/cache/PythonConfigGenerator.tsx | 155 +
.../beacon/components/cache/PythonQueryBuilder.tsx | 164 +
ecosystem/AI-Tooling/beacon/components/index.ts | 30 +
.../beacon/components/landing/Header.tsx | 220 +
.../beacon/components/landing/HeroContent.tsx | 130 +
.../beacon/components/landing/PulsingCircle.tsx | 54 +
.../beacon/components/landing/ShaderBackground.tsx | 136 +
.../AI-Tooling/beacon/components/ui/terminal.tsx | 44 +
.../beacon/components/utils/loadPyodide.ts | 56 +
ecosystem/AI-Tooling/beacon/content/_meta.ts | 68 +
ecosystem/AI-Tooling/beacon/content/about.mdx | 53 +
ecosystem/AI-Tooling/beacon/content/index.mdx | 114 +
.../AI-Tooling/beacon/content/installation.mdx | 224 +
ecosystem/AI-Tooling/beacon/content/node_cache.mdx | 221 +
.../AI-Tooling/beacon/content/python_cache.mdx | 278 +
ecosystem/AI-Tooling/beacon/content/pythonsdk.mdx | 74 +
ecosystem/AI-Tooling/beacon/content/rescli.mdx | 138 +
.../AI-Tooling/beacon/content/rescontract.mdx | 217 +
ecosystem/AI-Tooling/beacon/content/resdb_orm.mdx | 195 +
.../resilientdb/chapters/01_client_interaction.md | 284 +
.../chapters/02_network_communication.md | 291 +
.../chapters/03_consensus_management.md | 214 +
.../chapters/04_message_transaction_collection.md | 314 +
.../chapters/05_transaction_execution.md | 286 +
.../resilientdb/chapters/06_storage_layer.md | 297 +
.../resilientdb/chapters/07_checkpointing.md | 440 +
.../chapters/08_resilientdb_configuration.md | 356 +
.../beacon/content/resilientdb/index.mdx | 50 +
.../beacon/content/resilientdb_graphql/index.mdx | 351 +
.../beacon/content/resilientdb_graphql/setup.mdx | 123 +
ecosystem/AI-Tooling/beacon/content/reslens.mdx | 160 +
ecosystem/AI-Tooling/beacon/content/resvault.mdx | 252 +
ecosystem/AI-Tooling/beacon/eslint.config.mjs | 30 +
ecosystem/AI-Tooling/beacon/jest.config.cjs | 35 +
ecosystem/AI-Tooling/beacon/jest.setup.cjs | 46 +
ecosystem/AI-Tooling/beacon/lib/documentIndexer.ts | 480 +
ecosystem/AI-Tooling/beacon/mdx-components.ts | 31 +
ecosystem/AI-Tooling/beacon/next-env.d.ts | 6 +
ecosystem/AI-Tooling/beacon/next.config.mjs | 54 +
ecosystem/AI-Tooling/beacon/package-lock.json | 24993 +++++++++++++++++++
ecosystem/AI-Tooling/beacon/package.json | 100 +
.../beacon/pages/api/auth/[...nextauth].ts | 83 +
.../beacon/pages/api/create-github-issue.ts | 131 +
ecosystem/AI-Tooling/beacon/pages/auth/_meta.json | 8 +
ecosystem/AI-Tooling/beacon/pages/auth/error.tsx | 50 +
ecosystem/AI-Tooling/beacon/pages/auth/signin.tsx | 72 +
ecosystem/AI-Tooling/beacon/postcss.config.cjs | 34 +
ecosystem/AI-Tooling/beacon/public/favicon.svg | 18 +
ecosystem/AI-Tooling/beacon/public/lighthouse.png | Bin 0 -> 21666 bytes
ecosystem/AI-Tooling/beacon/public/logo.png | Bin 0 -> 8982 bytes
.../public/mantine+nextjs+nextra-template.png | Bin 0 -> 606702 bytes
ecosystem/AI-Tooling/beacon/public/resdb_sdk.py | 290 +
ecosystem/AI-Tooling/beacon/scripts/build-index.js | 47 +
ecosystem/AI-Tooling/beacon/scripts/build-index.ts | 41 +
.../beacon/styles/installation.module.css | 45 +
ecosystem/AI-Tooling/beacon/test-utils/index.ts | 35 +
ecosystem/AI-Tooling/beacon/test-utils/render.tsx | 30 +
ecosystem/AI-Tooling/beacon/theme.ts | 93 +
ecosystem/AI-Tooling/beacon/tsconfig.json | 50 +
.../beacon/types/codemirror__lang-javascript.d.ts | 27 +
ecosystem/AI-Tooling/beacon/types/next-auth.d.ts | 33 +
.../AI-Tooling/beacon/types/react-markdown.d.ts | 32 +
.../beacon/types/react-syntax-highlighter.d.ts | 37 +
.../nexus/.github/pull_request_template.md | 24 +
ecosystem/AI-Tooling/nexus/.gitignore | 54 +
ecosystem/AI-Tooling/nexus/README.md | 148 +
ecosystem/AI-Tooling/nexus/RESEARCH_SETUP.md | 163 +
ecosystem/AI-Tooling/nexus/components.json | 21 +
ecosystem/AI-Tooling/nexus/docs/DB_SETUP.md | 19 +
.../AI-Tooling/nexus/docs/SYSTEM_ARCHITECTURE.md | 123 +
ecosystem/AI-Tooling/nexus/docs/blog.md | 208 +
ecosystem/AI-Tooling/nexus/docs/images/agent.png | Bin 0 -> 666638 bytes
.../AI-Tooling/nexus/docs/images/research.png | Bin 0 -> 1518383 bytes
.../nexus/documents/bchain-transaction-pro.pdf | Bin 0 -> 886238 bytes
ecosystem/AI-Tooling/nexus/documents/rcc.pdf | Bin 0 -> 332119 bytes
.../AI-Tooling/nexus/documents/resilientdb.pdf | Bin 0 -> 1145477 bytes
ecosystem/AI-Tooling/nexus/eslint.config.mjs | 29 +
ecosystem/AI-Tooling/nexus/next.config.mjs | 29 +
ecosystem/AI-Tooling/nexus/next.config.ts | 7 +
ecosystem/AI-Tooling/nexus/package-lock.json | 12461 +++++++++
ecosystem/AI-Tooling/nexus/package.json | 86 +
ecosystem/AI-Tooling/nexus/parsing-cache.json | 1 +
ecosystem/AI-Tooling/nexus/postcss.config.mjs | 5 +
ecosystem/AI-Tooling/nexus/public/expolab-icon.png | Bin 0 -> 133234 bytes
ecosystem/AI-Tooling/nexus/public/resdb-logo.svg | 24 +
.../AI-Tooling/nexus/public/resilientdb-icon.png | Bin 0 -> 4036 bytes
.../nexus/src/app/api/research/chat/route.ts | 213 +
.../nexus/src/app/api/research/documents/route.ts | 45 +
.../src/app/api/research/files/[...path]/route.ts | 61 +
.../src/app/api/research/prepare-index/route.ts | 119 +
ecosystem/AI-Tooling/nexus/src/app/favicon.ico | Bin 0 -> 25931 bytes
ecosystem/AI-Tooling/nexus/src/app/globals.css | 357 +
ecosystem/AI-Tooling/nexus/src/app/layout.tsx | 21 +
ecosystem/AI-Tooling/nexus/src/app/page.tsx | 37 +
.../src/app/research/components/chat-input.tsx | 190 +
.../research/components/code-generation-tab.tsx | 336 +
.../app/research/components/pdf-preview-tab.tsx | 60 +
.../src/app/research/components/preview-panel.tsx | 180 +
.../AI-Tooling/nexus/src/app/research/page.tsx | 1149 +
.../AI-Tooling/nexus/src/app/research/types.ts | 56 +
.../src/components/ai-elements/code-block.tsx | 156 +
.../src/components/ai-elements/prompt-input.tsx | 287 +
.../nexus/src/components/ai-elements/response.tsx | 321 +
.../nexus/src/components/ai-elements/tool.tsx | 131 +
.../nexus/src/components/context/ToolContext.tsx | 23 +
.../src/components/providers/QueryProvider.tsx | 28 +
.../nexus/src/components/ui/accordion.tsx | 66 +
.../nexus/src/components/ui/animated-ai-chat.tsx | 311 +
.../AI-Tooling/nexus/src/components/ui/badge.tsx | 46 +
.../AI-Tooling/nexus/src/components/ui/button.tsx | 59 +
.../AI-Tooling/nexus/src/components/ui/card.tsx | 128 +
.../nexus/src/components/ui/citation-badge.tsx | 49 +
.../nexus/src/components/ui/collapsible.tsx | 33 +
.../AI-Tooling/nexus/src/components/ui/dialog.tsx | 143 +
.../src/components/ui/document-source-badge.tsx | 251 +
.../nexus/src/components/ui/fluid-blob.tsx | 175 +
.../AI-Tooling/nexus/src/components/ui/label.tsx | 24 +
.../AI-Tooling/nexus/src/components/ui/loader.tsx | 498 +
.../nexus/src/components/ui/main-navbar.tsx | 85 +
.../src/components/ui/multi-document-selector.tsx | 377 +
.../AI-Tooling/nexus/src/components/ui/popover.tsx | 48 +
.../nexus/src/components/ui/resizable.tsx | 56 +
.../nexus/src/components/ui/scroll-area.tsx | 58 +
.../AI-Tooling/nexus/src/components/ui/select.tsx | 185 +
.../nexus/src/components/ui/separator.tsx | 28 +
.../AI-Tooling/nexus/src/components/ui/sheet.tsx | 139 +
.../AI-Tooling/nexus/src/components/ui/switch.tsx | 31 +
.../AI-Tooling/nexus/src/components/ui/tabs.tsx | 67 +
.../nexus/src/components/ui/textarea.tsx | 18 +
.../AI-Tooling/nexus/src/components/ui/tooltip.tsx | 61 +
.../AI-Tooling/nexus/src/config/environment.ts | 35 +
.../AI-Tooling/nexus/src/hooks/useDocuments.ts | 72 +
ecosystem/AI-Tooling/nexus/src/lib/agent.ts | 281 +
.../nexus/src/lib/code-composer-agent.ts | 362 +
.../nexus/src/lib/config/llama-settings.ts | 91 +
ecosystem/AI-Tooling/nexus/src/lib/constants.ts | 39 +
ecosystem/AI-Tooling/nexus/src/lib/google-drive.ts | 159 +
.../AI-Tooling/nexus/src/lib/llama-service.ts | 596 +
.../AI-Tooling/nexus/src/lib/robust-fact-memory.ts | 146 +
.../AI-Tooling/nexus/src/lib/token-estimator.ts | 38 +
ecosystem/AI-Tooling/nexus/src/lib/utils.ts | 44 +
.../AI-Tooling/nexus/src/tests/test-router.ts | 124 +
ecosystem/AI-Tooling/nexus/tsconfig.json | 27 +
ecosystem/tools/rescli/.asf.yaml | 42 +
.../tools/rescli/.github/workflows/license.yml | 37 +
.../tools/rescli/.github/workflows/make-binary.yml | 61 +
.../tools/rescli/.github/workflows/release.yml | 42 +
ecosystem/tools/rescli/.licenserc.yaml | 26 +
ecosystem/tools/rescli/CODE_OF_CONDUCT.md | 95 +
ecosystem/tools/rescli/DISCLAIMER | 21 +
ecosystem/tools/rescli/Gemfile | 24 +
ecosystem/tools/rescli/LICENSE | 201 +
ecosystem/tools/rescli/NOTICE | 5 +
ecosystem/tools/rescli/README.md | 109 +
ecosystem/tools/rescli/Rakefile | 23 +
ecosystem/tools/rescli/config.ini | 18 +
ecosystem/tools/rescli/dev/.rat-excludes | 19 +
ecosystem/tools/rescli/dev/check-license | 86 +
ecosystem/tools/rescli/lib/res_cli/cli.rb | 322 +
ecosystem/tools/rescli/res_cli | 27 +
ecosystem/tools/rescli/res_cli.gemspec | 32 +
230 files changed, 65132 insertions(+)
diff --git a/ecosystem/AI-Tooling/beacon/.env.local.example b/ecosystem/AI-Tooling/beacon/.env.local.example
new file mode 100644
index 00000000..ac270344
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.env.local.example
@@ -0,0 +1,9 @@
+NEXT_PUBLIC_RESDB_REST_URL=
+
+# GitHub OAuth App credentials (use your production OAuth App credentials)
+GITHUB_CLIENT_ID=
+GITHUB_CLIENT_SECRET=
+
+# NextAuth configuration
+NEXTAUTH_URL=
+NEXTAUTH_SECRET=
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/.github/assets/beacon-hero.png b/ecosystem/AI-Tooling/beacon/.github/assets/beacon-hero.png
new file mode 100644
index 00000000..c25901b1
Binary files /dev/null and b/ecosystem/AI-Tooling/beacon/.github/assets/beacon-hero.png differ
diff --git a/ecosystem/AI-Tooling/beacon/.github/workflows/license.yml b/ecosystem/AI-Tooling/beacon/.github/workflows/license.yml
new file mode 100644
index 00000000..f3d4206d
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.github/workflows/license.yml
@@ -0,0 +1,37 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+name: Check Apache License
+
+on:
+ push
+
+jobs:
+ ubuntu-build:
+ name: check license
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ repository: ${{github.repository}}
+ ref: ${{ env.BRANCH_NAME }}
+
+ - name: Check License Header
+ uses: apache/skywalking-eyes/header@main
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/.github/workflows/loc.yml b/ecosystem/AI-Tooling/beacon/.github/workflows/loc.yml
new file mode 100644
index 00000000..37a47eee
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.github/workflows/loc.yml
@@ -0,0 +1,61 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+name: LOC
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ repository: ${{github.repository}}
+ ref: ${{ env.BRANCH_NAME }}
+
+ - name: NPM Init
+ run: npm init -y
+
+ - name: NPM Install
+ run: npm install badgen @actions/core glob-gitignore
+
+ - name: Launch the local action
+ id: badge
+ uses: ./third_party/loc_script/ # Uses an action in the root directory
+ with:
+ debug: true
+ directory: ./
+ patterns: '**/*.h|**/*.cpp'
+ badge: ./output/badge.svg
+ ignore: 'node_modules/|README'
+
+ - name: Deploy to image-data branch
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ publish_dir: ./output
+ publish_branch: image-data
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ user_name: 'github-actions[bot]'
+ user_email: 'github-actions[bot]@users.noreply.github.com'
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/.gitignore b/ecosystem/AI-Tooling/beacon/.gitignore
new file mode 100644
index 00000000..40b6542c
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.gitignore
@@ -0,0 +1,134 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+_pagefind/
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+.DS_Store
+.history
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/.licenserc.yaml b/ecosystem/AI-Tooling/beacon/.licenserc.yaml
new file mode 100644
index 00000000..a54a83fb
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.licenserc.yaml
@@ -0,0 +1,27 @@
+header:
+ license:
+ spdx-id: Apache-2.0
+ copyright-owner: Apache Software Foundation
+
+ paths-ignore:
+ - '**/*.mdx'
+ - '.*'
+ - '.**/**'
+ - 'CNAME'
+ - '**/*.conf'
+ - '**/*.config'
+ - '**/*.json'
+ - '**/*.sol'
+ - '**/*.pri'
+ - '**/*.pub'
+ - 'dev/.rat-excludes'
+ - 'DISCLAIMER'
+ - 'NOTICE'
+ - 'LICENSE'
+ - 'documents/doxygen/.gitignore'
+ - 'third_party/loc_script/src/index.js'
+
+
+
+ comment: on-failure
+
diff --git a/ecosystem/AI-Tooling/beacon/.npmrc b/ecosystem/AI-Tooling/beacon/.npmrc
new file mode 100644
index 00000000..bacf9bc0
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.npmrc
@@ -0,0 +1,3 @@
+strict-peer-dependencies=false
+shell-emulator=true
+enable-pre-post-scripts=true
diff --git a/ecosystem/AI-Tooling/beacon/.nvmrc b/ecosystem/AI-Tooling/beacon/.nvmrc
new file mode 100644
index 00000000..bb8c76c6
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.nvmrc
@@ -0,0 +1 @@
+v22.11.0
diff --git a/ecosystem/AI-Tooling/beacon/.prettierignore b/ecosystem/AI-Tooling/beacon/.prettierignore
new file mode 100644
index 00000000..d0d878e4
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.prettierignore
@@ -0,0 +1 @@
+.next
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/.prettierrc.mjs b/ecosystem/AI-Tooling/beacon/.prettierrc.mjs
new file mode 100644
index 00000000..31e43b8a
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.prettierrc.mjs
@@ -0,0 +1,35 @@
+/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */
+const config = {
+ printWidth: 100,
+ singleQuote: true,
+ trailingComma: 'es5',
+ plugins: ['@ianvs/prettier-plugin-sort-imports'],
+ importOrder: [
+ '.*styles.css$',
+ '',
+ 'dayjs',
+ '^react$',
+ '^next$',
+ '^next/.*$',
+ '<BUILTIN_MODULES>',
+ '<THIRD_PARTY_MODULES>',
+ '^@mantine/(.*)$',
+ '^@mantinex/(.*)$',
+ '^@mantine-tests/(.*)$',
+ '^@docs/(.*)$',
+ '^@/.*$',
+ '^../(?!.*.css$).*$',
+ '^./(?!.*.css$).*$',
+ '\\.css$',
+ ],
+ overrides: [
+ {
+ files: '*.mdx',
+ options: {
+ printWidth: 70,
+ },
+ },
+ ],
+};
+
+export default config;
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/.storybook/main.ts b/ecosystem/AI-Tooling/beacon/.storybook/main.ts
new file mode 100644
index 00000000..dc5c706c
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.storybook/main.ts
@@ -0,0 +1,16 @@
+import type { StorybookConfig } from '@storybook/nextjs';
+
+const config: StorybookConfig = {
+ core: {
+ disableWhatsNewNotifications: true,
+ disableTelemetry: true,
+ enableCrashReports: false,
+ },
+ stories: ['../components/**/*.(stories|story).@(js|jsx|ts|tsx)'],
+ addons: ['storybook-dark-mode'],
+ framework: {
+ name: '@storybook/nextjs',
+ options: {},
+ },
+};
+export default config;
diff --git a/ecosystem/AI-Tooling/beacon/.storybook/preview.tsx b/ecosystem/AI-Tooling/beacon/.storybook/preview.tsx
new file mode 100644
index 00000000..522af1ac
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.storybook/preview.tsx
@@ -0,0 +1,33 @@
+import '@mantine/core/styles.css';
+
+import React, { useEffect } from 'react';
+import { addons } from '@storybook/preview-api';
+import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
+import { MantineProvider, useMantineColorScheme } from '@mantine/core';
+import { theme } from '../theme';
+
+export const parameters = {
+ layout: 'fullscreen',
+ options: {
+ showPanel: false,
+ },
+};
+
+const channel = addons.getChannel();
+
+function ColorSchemeWrapper({ children }: { children: React.ReactNode }) {
+ const { setColorScheme } = useMantineColorScheme();
+ const handleColorScheme = (value: boolean) => setColorScheme(value ? 'dark' : 'light');
+
+ useEffect(() => {
+ channel.on(DARK_MODE_EVENT_NAME, handleColorScheme);
+ return () => channel.off(DARK_MODE_EVENT_NAME, handleColorScheme);
+ }, [channel]);
+
+ return <>{children}</>;
+}
+
+export const decorators = [
+ (renderStory: any) => <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>,
+ (renderStory: any) => <MantineProvider theme={theme}>{renderStory()}</MantineProvider>,
+];
diff --git a/ecosystem/AI-Tooling/beacon/.stylelintignore b/ecosystem/AI-Tooling/beacon/.stylelintignore
new file mode 100644
index 00000000..2b3533c7
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.stylelintignore
@@ -0,0 +1,2 @@
+.next
+out
diff --git a/ecosystem/AI-Tooling/beacon/.stylelintrc.json b/ecosystem/AI-Tooling/beacon/.stylelintrc.json
new file mode 100644
index 00000000..4ea6506d
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.stylelintrc.json
@@ -0,0 +1,28 @@
+{
+ "extends": ["stylelint-config-standard-scss"],
+ "rules": {
+ "custom-property-pattern": null,
+ "selector-class-pattern": null,
+ "scss/no-duplicate-mixins": null,
+ "declaration-empty-line-before": null,
+ "declaration-block-no-redundant-longhand-properties": null,
+ "alpha-value-notation": null,
+ "custom-property-empty-line-before": null,
+ "property-no-vendor-prefix": null,
+ "color-function-notation": null,
+ "length-zero-no-unit": null,
+ "selector-not-notation": null,
+ "no-descending-specificity": null,
+ "comment-empty-line-before": null,
+ "scss/at-mixin-pattern": null,
+ "scss/at-rule-no-unknown": null,
+ "value-keyword-case": null,
+ "media-feature-range-notation": null,
+ "selector-pseudo-class-no-unknown": [
+ true,
+ {
+ "ignorePseudoClasses": ["global"]
+ }
+ ]
+ }
+}
diff --git a/ecosystem/AI-Tooling/beacon/.vscode/settings.json b/ecosystem/AI-Tooling/beacon/.vscode/settings.json
new file mode 100644
index 00000000..7dd4ff3a
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "prettier.configPath": ".prettierrc.mjs",
+ "search.useParentIgnoreFiles": true
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/CODE_OF_CONDUCT.md b/ecosystem/AI-Tooling/beacon/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..5f198c5c
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/CODE_OF_CONDUCT.md
@@ -0,0 +1,95 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at support@resilientdb.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/ecosystem/AI-Tooling/beacon/DISCLAIMER.txt b/ecosystem/AI-Tooling/beacon/DISCLAIMER.txt
new file mode 100644
index 00000000..e03ab51f
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/DISCLAIMER.txt
@@ -0,0 +1,38 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+Apache ResilientDB is an effort undergoing incubation at the Apache Software
+Foundation (ASF), sponsored by the Apache Incubator PMC.
+
+Incubation is required of all newly accepted projects until a further review
+indicates that the infrastructure, communications, and decision making process
+have stabilized in a manner consistent with other successful ASF projects.
+
+While incubation status is not necessarily a reflection of the completeness
+or stability of the code, it does indicate that the project has yet to be
+fully endorsed by the ASF.
+
+Some of the incubating project’s releases may not be fully compliant with ASF policy.
+For example, releases may have incomplete or un-reviewed licensing conditions.
+What follows is a list of known issues the project is currently aware of
+(note that this list, by definition, is likely to be incomplete):
+
+If you are planning to incorporate this work into your product/project, please
+be aware that you will need to conduct a thorough licensing review to determine
+the overall implications of including this work. For the current status of this
+project through the Apache Incubator visit:
+https://incubator.apache.org/projects/resilientdb.html
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/LICENSE b/ecosystem/AI-Tooling/beacon/LICENSE
new file mode 100644
index 00000000..f49a4e16
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/NOTICE b/ecosystem/AI-Tooling/beacon/NOTICE
new file mode 100644
index 00000000..0a7c06a9
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/NOTICE
@@ -0,0 +1,10 @@
+Apache ResilientDB (Incubating)
+Copyright 2023-2025 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+The `.mdx` files under the `/content` directory are documentation sources.
+These files do not contain per-file license headers due to technical limitations
+of the MDX format. However, they are part of this distribution and are
+licensed under the Apache License, Version 2.0.
diff --git a/ecosystem/AI-Tooling/beacon/README.md b/ecosystem/AI-Tooling/beacon/README.md
new file mode 100644
index 00000000..8e9dc583
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/README.md
@@ -0,0 +1,67 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+# Beacon – Documentation and AI Assistant for the ResilientDB Ecosystem
+
+Beacon is the home for ResilientDB documentation, examples, and learning resources. It brings everything into one place—clear guides, curated ecosystem docs, interactive playgrounds—and adds an AI assistant that helps you find answers faster and build with confidence.
+
+
+
+Visit the live site: https://beacon.resilientdb.com
+
+## What Beacon Does
+
+- Centralizes official docs and ecosystem projects in a single, easy‑to‑navigate site
+- Helps new contributors and users onboard quickly with focused guides and examples
+- Surfaces best practices and reference architectures for building with ResilientDB
+- Provides live demos and playgrounds to learn by doing
+
+## Key Features
+
+- AI Companion and Chatbot
+ - Ask context‑aware questions across docs
+ - Generate examples, commands, and “first steps” checklists
+ - Summarize long pages and compare options
+- Unified Docs & Guides
+ - Clear navigation across core, ecosystem tools, and SDKs
+ - MDX‑based content, diagrams, callouts, and code blocks
+- Interactive Playgrounds
+ - TypeScript and Python examples, terminal‑style demos
+ - Step‑by‑step tutorials that you can follow and modify
+- Powerful Search
+ - Quickly find APIs, guides, and ecosystem resources
+- Thoughtful Design
+ - Consistent light/dark modes, accessible typography, responsive layout
+
+## Getting Started (Local Development)
+
+```bash
+npm i
+npm run dev
+```
+
+Build for production:
+
+```bash
+npm run build
+```
+
+## Contributing
+
+Contributions are welcome! Open an issue or pull request with focused edits. If you’re adding or editing docs, prefer small, incremental changes with clear examples. If you’re enhancing the AI assistant behavior, include a short rationale and a quick way to test.
diff --git a/ecosystem/AI-Tooling/beacon/app/_meta.ts b/ecosystem/AI-Tooling/beacon/app/_meta.ts
new file mode 100644
index 00000000..26e110cc
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/_meta.ts
@@ -0,0 +1,43 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+export default {
+ index: {
+ display: 'hidden',
+ },
+ docs: {
+ type: 'page',
+ title: 'Documentation',
+ },
+ community: {
+ title: 'Community',
+ type: 'menu',
+ items: {
+ blog: {
+ title: 'Blog',
+ href: 'https://blog.resilientdb.com/',
+ },
+ },
+ },
+ about: {
+ type: 'page',
+ title: 'About',
+ href: '/docs/about',
+ },
+};
diff --git a/ecosystem/AI-Tooling/beacon/app/api/ai-search/route.ts b/ecosystem/AI-Tooling/beacon/app/api/ai-search/route.ts
new file mode 100644
index 00000000..e363965e
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/api/ai-search/route.ts
@@ -0,0 +1,556 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { NextResponse } from 'next/server';
+import { documentIndexer, DocumentChunk } from '../../../lib/documentIndexer';
+
+interface AISearchResult {
+ id: string;
+ title: string;
+ url: string;
+ excerpt: string;
+ relevanceScore: number;
+ metadata: {
+ description?: string;
+ parent?: string;
+ nav_order?: number;
+ tags?: string[];
+ };
+ matchedContent: string;
+ sections: Array<{
+ id: string;
+ title: string;
+ level: number;
+ content: string;
+ url: string;
+ }>;
+ matchedSection?: {
+ id: string;
+ title: string;
+ level: number;
+ content: string;
+ url: string;
+ };
+}
+
+export async function POST(req: Request) {
+ try {
+ const { query, limit = 10 } = await req.json();
+
+ if (!query || typeof query !== 'string') {
+ return NextResponse.json(
+ { error: 'Query is required and must be a string' },
+ { status: 400 }
+ );
+ }
+
+ // Load or create document index
+ let index = documentIndexer.loadIndex();
+ if (!index || documentIndexer.needsUpdate()) {
+ console.log('Index not found or needs update, creating new index...');
+ index = await documentIndexer.indexDocuments();
+ }
+
+ if (!index || index.documents.length === 0) {
+ return NextResponse.json(
+ { error: 'No documents found in index' },
+ { status: 404 }
+ );
+ }
+
+ // Use hybrid search (AI + keyword)
+ const searchResults = await performHybridSearch(query, index.documents, limit);
+
+ return NextResponse.json({
+ results: searchResults,
+ totalFound: searchResults.length,
+ query,
+ timestamp: new Date().toISOString()
+ });
+
+ } catch (error) {
+ console.error('AI Search API error:', error);
+ return NextResponse.json(
+ { error: 'Failed to perform AI search' },
+ { status: 500 }
+ );
+ }
+}
+
+async function performHybridSearch(
+ query: string,
+ documents: DocumentChunk[],
+ limit: number
+): Promise<AISearchResult[]> {
+
+ // Extract search terms from query
+ const searchTerms = query.toLowerCase()
+ .replace(/[^\w\s-]/g, ' ')
+ .split(/\s+/)
+ .filter(word => word.length > 2);
+
+ // Get AI-generated search terms
+ const aiTerms = await generateSearchTerms(query);
+
+ // Combine all search terms
+ const allSearchTerms = Array.from(new Set([...searchTerms, ...aiTerms]));
+
+ // Get keyword-based scores
+ const keywordResults = scoreDocumentsByKeywords(query, searchTerms, documents);
+
+ // Get AI-enhanced scores
+ const aiResults = scoreDocumentsByAI(query, allSearchTerms, documents);
+
+ // Combine scores using weighted average
+ const combinedResults = combineSearchResults(keywordResults, aiResults);
+
+ // Rank and return top results
+ return combinedResults
+ .sort((a, b) => b.relevanceScore - a.relevanceScore)
+ .slice(0, limit)
+ .map(doc => {
+ // Find the best matching section
+ const matchedSection = findBestMatchingSection(query, allSearchTerms, doc.sections);
+
+ return {
+ id: doc.id,
+ title: doc.title,
+ url: matchedSection ? matchedSection.url : doc.url,
+ excerpt: doc.excerpt,
+ relevanceScore: doc.relevanceScore,
+ metadata: doc.metadata,
+ matchedContent: doc.matchedContent,
+ sections: doc.sections,
+ matchedSection
+ };
+ });
+}
+
+function findBestMatchingSection(
+ query: string,
+ searchTerms: string[],
+ sections: Array<{
+ id: string;
+ title: string;
+ level: number;
+ content: string;
+ url: string;
+ }>
+): any | null {
+ if (!sections || sections.length === 0) return null;
+
+ let bestSection = null;
+ let bestScore = 0;
+
+ const queryLower = query.toLowerCase();
+
+ for (const section of sections) {
+ let score = 0;
+ const titleLower = section.title.toLowerCase();
+ const contentLower = section.content.toLowerCase();
+
+ // Title matching (highest weight)
+ if (titleLower.includes(queryLower)) {
+ score += 10;
+ }
+
+ // Search terms in title
+ searchTerms.forEach(term => {
+ if (titleLower.includes(term.toLowerCase())) {
+ score += 5;
+ }
+ });
+
+ // Search terms in content
+ searchTerms.forEach(term => {
+ const termLower = term.toLowerCase();
+ if (contentLower.includes(termLower)) {
+ score += 3;
+ }
+ });
+
+ // Word frequency in content
+ const queryWords = queryLower.split(/\s+/);
+ queryWords.forEach(word => {
+ if (word.length > 2) {
+ const contentMatches = (contentLower.match(new RegExp(word, 'g')) || []).length;
+ score += contentMatches;
+ }
+ });
+
+ // Prefer more specific headings (h2, h3 over h1) for better precision
+ // H1 headings are usually too general, prefer H2 and H3 for specific topics
+ if (section.level === 1) {
+ score -= 2; // Penalize H1 headings as they're usually too general
+ } else if (section.level === 2) {
+ score += 3; // Prefer H2 headings
+ } else if (section.level === 3) {
+ score += 2; // Also good for H3 headings
+ }
+
+ if (score > bestScore) {
+ bestScore = score;
+ bestSection = section;
+ }
+ }
+
+ // Only return if score is meaningful and we have a good match
+ // Require higher scores for H1 headings since they're usually too general
+ const minScore = bestSection && bestSection.level === 1 ? 5 : 2;
+ return bestScore > minScore ? bestSection : null;
+}
+
+// Synonym mapping for better search results
+const SYNONYM_MAP: Record<string, string[]> = {
+ 'caching': ['cache', 'node cache', 'python cache', 'memory', 'storage', 'performance'],
+ 'cache': ['caching', 'node cache', 'python cache', 'memory', 'storage', 'performance'],
+ 'storage': ['cache', 'caching', 'database', 'persistence', 'data'],
+ 'database': ['storage', 'data', 'persistence', 'resilientdb'],
+ 'consensus': ['agreement', 'pbft', 'consensus management', 'voting'],
+ 'network': ['communication', 'network communication', 'messaging', 'replica'],
+ 'transaction': ['txn', 'transaction execution', 'execution', 'processing'],
+ 'client': ['client interaction', 'kv client', 'utxo client', 'contract client'],
+ 'configuration': ['config', 'setup', 'resdbconfig', 'settings'],
+ 'checkpointing': ['checkpoint', 'recovery', 'backup', 'snapshot'],
+ 'graphql': ['resilientdb graphql', 'api', 'query', 'mutation'],
+ 'orm': ['resdb orm', 'object relational mapping', 'database orm'],
+ 'vault': ['resvault', 'secure storage', 'encryption'],
+ 'lens': ['reslens', 'monitoring', 'observability', 'metrics'],
+ 'cli': ['rescli', 'command line', 'terminal', 'tools']
+};
+
+function expandSearchTerms(query: string): string[] {
+ const terms = query.toLowerCase().split(/\s+/);
+ const expandedTerms = new Set<string>();
+
+ // Add original terms
+ terms.forEach(term => expandedTerms.add(term));
+
+ // Add synonyms
+ terms.forEach(term => {
+ if (SYNONYM_MAP[term]) {
+ SYNONYM_MAP[term].forEach(synonym => expandedTerms.add(synonym));
+ }
+ });
+
+ // Add partial matches (e.g., "caching" should match "node cache")
+ terms.forEach(term => {
+ Object.keys(SYNONYM_MAP).forEach(key => {
+ if (key.includes(term) || term.includes(key)) {
+ SYNONYM_MAP[key].forEach(synonym => expandedTerms.add(synonym));
+ }
+ });
+ });
+
+ return Array.from(expandedTerms);
+}
+
+async function generateSearchTerms(query: string): Promise<string[]> {
+ try {
+ // First, expand with synonyms
+ const expandedTerms = expandSearchTerms(query);
+
+ const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`,
+ },
+ body: JSON.stringify({
+ model: 'deepseek-chat',
+ messages: [
+ {
+ role: 'system',
+ content: `You are a search term generator for ResilientDB documentation. Given a user query, generate 5-8 relevant search terms that would help find related documentation content.
+
+Focus on:
+- Technical terms and concepts
+- Related features and components
+- Synonyms and alternative terms
+- Specific ResilientDB terminology
+- Include terms like "node cache", "python cache", "resilientdb graphql", etc.
+
+Return only the search terms, one per line, without any other text.`
+ },
+ {
+ role: 'user',
+ content: `Generate search terms for: "${query}"`
+ }
+ ],
+ max_tokens: 200,
+ temperature: 0.3
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to generate search terms');
+ }
+
+ const data = await response.json();
+ const aiTerms = data.choices[0]?.message?.content
+ ?.split('\n')
+ .map((term: string) => term.trim())
+ .filter((term: string) => term.length > 0) || [];
+
+ // Combine AI terms with expanded terms
+ const allTerms = [...expandedTerms, ...aiTerms];
+ const uniqueTerms = Array.from(new Set(allTerms));
+
+ return uniqueTerms.slice(0, 12); // Increased limit for better coverage
+ } catch (error) {
+ console.error('Error generating search terms:', error);
+ // Fallback to expanded terms
+ return expandSearchTerms(query);
+ }
+}
+
+function scoreDocumentsByKeywords(
+ query: string,
+ searchTerms: string[],
+ documents: DocumentChunk[]
+): (DocumentChunk & { relevanceScore: number; matchedContent: string })[] {
+
+ const scoredDocs = documents.map(doc => {
+ let score = 0;
+ let matchedContent = '';
+
+ const queryLower = query.toLowerCase();
+ const contentLower = doc.content.toLowerCase();
+ const titleLower = doc.title.toLowerCase();
+ const urlLower = doc.url.toLowerCase();
+
+ // Keyword matching (highest priority)
+ searchTerms.forEach(term => {
+ const termLower = term.toLowerCase();
+
+ // Check if term matches any keyword
+ const keywordMatch = doc.keywords.some(keyword =>
+ keyword.includes(termLower) || termLower.includes(keyword)
+ );
+
+ if (keywordMatch) {
+ score += 20; // High score for keyword match
+
+ // Find which keyword matched
+ const matchedKeyword = doc.keywords.find(keyword =>
+ keyword.includes(termLower) || termLower.includes(keyword)
+ );
+
+ if (matchedKeyword && !matchedContent) {
+ matchedContent = `Keyword match: ${matchedKeyword}`;
+ }
+ }
+
+ // Exact title match
+ if (titleLower === termLower) {
+ score += 25;
+ matchedContent = doc.title;
+ } else if (titleLower.includes(termLower)) {
+ score += 15;
+ if (!matchedContent) matchedContent = doc.title;
+ }
+
+ // URL/path matching (e.g., "node cache" in URL)
+ if (urlLower.includes(termLower)) {
+ score += 18;
+ if (!matchedContent) matchedContent = `URL match: ${doc.url}`;
+ }
+
+ // Content matching
+ if (contentLower.includes(termLower)) {
+ score += 8;
+
+ if (!matchedContent) {
+ const startIndex = contentLower.indexOf(termLower);
+ matchedContent = doc.content.substring(
+ Math.max(0, startIndex - 50),
+ Math.min(doc.content.length, startIndex + termLower.length + 50)
+ );
+ }
+ }
+ });
+
+ // Exact phrase matching in title
+ if (titleLower.includes(queryLower)) {
+ score += 30;
+ matchedContent = doc.title;
+ }
+
+ // Exact phrase matching in content
+ if (contentLower.includes(queryLower)) {
+ score += 12;
+ if (!matchedContent) {
+ const startIndex = contentLower.indexOf(queryLower);
+ matchedContent = doc.content.substring(
+ Math.max(0, startIndex - 50),
+ Math.min(doc.content.length, startIndex + queryLower.length + 50)
+ );
+ }
+ }
+
+ // Use excerpt if no specific content matched
+ if (!matchedContent) {
+ matchedContent = doc.excerpt;
+ }
+
+ return {
+ ...doc,
+ relevanceScore: score,
+ matchedContent: matchedContent.trim()
+ };
+ });
+
+ // Filter out documents with very low scores
+ return scoredDocs.filter(doc => doc.relevanceScore > 0);
+}
+
+function scoreDocumentsByAI(
+ query: string,
+ searchTerms: string[],
+ documents: DocumentChunk[]
+): (DocumentChunk & { relevanceScore: number; matchedContent: string })[] {
+
+ const scoredDocs = documents.map(doc => {
+ let score = 0;
+ let matchedContent = '';
+
+ const queryLower = query.toLowerCase();
+ const contentLower = doc.content.toLowerCase();
+ const titleLower = doc.title.toLowerCase();
+ const urlLower = doc.url.toLowerCase();
+
+ // AI-enhanced semantic matching
+ searchTerms.forEach(term => {
+ const termLower = term.toLowerCase();
+
+ // Semantic similarity scoring (AI-generated terms get higher weight)
+ const isAiTerm = searchTerms.length > 3 && searchTerms.indexOf(term) >= 3; // Assume AI terms come after direct terms
+
+ // Title semantic matching
+ if (titleLower.includes(termLower)) {
+ score += isAiTerm ? 12 : 8;
+ if (!matchedContent) matchedContent = doc.title;
+ }
+
+ // URL semantic matching
+ if (urlLower.includes(termLower)) {
+ score += isAiTerm ? 15 : 10;
+ if (!matchedContent) matchedContent = `URL match: ${doc.url}`;
+ }
+
+ // Content semantic matching
+ if (contentLower.includes(termLower)) {
+ score += isAiTerm ? 6 : 4;
+
+ if (!matchedContent) {
+ const startIndex = contentLower.indexOf(termLower);
+ matchedContent = doc.content.substring(
+ Math.max(0, startIndex - 50),
+ Math.min(doc.content.length, startIndex + termLower.length + 50)
+ );
+ }
+ }
+
+ // Keyword semantic matching
+ const keywordMatch = doc.keywords.some(keyword =>
+ keyword.includes(termLower) || termLower.includes(keyword)
+ );
+
+ if (keywordMatch) {
+ score += isAiTerm ? 10 : 6;
+ if (!matchedContent) {
+ const matchedKeyword = doc.keywords.find(keyword =>
+ keyword.includes(termLower) || termLower.includes(keyword)
+ );
+ matchedContent = `Semantic match: ${matchedKeyword}`;
+ }
+ }
+ });
+
+ // Exact phrase matching (higher weight for AI)
+ if (titleLower.includes(queryLower)) {
+ score += 20;
+ matchedContent = doc.title;
+ }
+
+ if (contentLower.includes(queryLower)) {
+ score += 10;
+ if (!matchedContent) {
+ const startIndex = contentLower.indexOf(queryLower);
+ matchedContent = doc.content.substring(
+ Math.max(0, startIndex - 50),
+ Math.min(doc.content.length, startIndex + queryLower.length + 50)
+ );
+ }
+ }
+
+ // Use excerpt if no specific content matched
+ if (!matchedContent) {
+ matchedContent = doc.excerpt;
+ }
+
+ return {
+ ...doc,
+ relevanceScore: score,
+ matchedContent: matchedContent.trim()
+ };
+ });
+
+ return scoredDocs.filter(doc => doc.relevanceScore > 0);
+}
+
+function combineSearchResults(
+ keywordResults: (DocumentChunk & { relevanceScore: number; matchedContent: string })[],
+ aiResults: (DocumentChunk & { relevanceScore: number; matchedContent: string })[]
+): (DocumentChunk & { relevanceScore: number; matchedContent: string })[] {
+
+ const combinedMap = new Map<string, DocumentChunk & { relevanceScore: number; matchedContent: string }>();
+
+ // Add keyword results with weight 0.6
+ keywordResults.forEach(doc => {
+ const combined = {
+ ...doc,
+ relevanceScore: doc.relevanceScore * 0.6,
+ matchedContent: doc.matchedContent
+ };
+ combinedMap.set(doc.id, combined);
+ });
+
+ // Add AI results with weight 0.4, combining scores if document exists
+ aiResults.forEach(doc => {
+ const existing = combinedMap.get(doc.id);
+ if (existing) {
+ // Combine scores
+ existing.relevanceScore += doc.relevanceScore * 0.4;
+ // Prefer more specific matched content
+ if (doc.matchedContent.length > existing.matchedContent.length) {
+ existing.matchedContent = doc.matchedContent;
+ }
+ } else {
+ // Add new result
+ combinedMap.set(doc.id, {
+ ...doc,
+ relevanceScore: doc.relevanceScore * 0.4,
+ matchedContent: doc.matchedContent
+ });
+ }
+ });
+
+ return Array.from(combinedMap.values());
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/api/chat/route.ts b/ecosystem/AI-Tooling/beacon/app/api/chat/route.ts
new file mode 100644
index 00000000..6f63d145
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/api/chat/route.ts
@@ -0,0 +1,131 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { NextResponse } from 'next/server';
+
+export async function POST(req: Request) {
+ try {
+ const { messages, code } = await req.json();
+
+ const systemPrompt = `You are a helpful assistant for ResilientDB documentation. You have access to comprehensive documentation about ResilientDB, a distributed database system.
+
+Your role is to:
+1. Answer questions about ResilientDB concepts, architecture, and usage
+2. Provide accurate information based on the documentation
+3. Explain complex concepts in clear, understandable terms
+4. Offer practical examples and use cases when relevant
+5. Reference specific documentation sections when helpful
+
+Guidelines:
+- Be accurate and factual in your responses
+- Use markdown formatting for better readability
+- Provide detailed explanations for technical concepts
+- Include relevant examples from the documentation
+- If you're unsure about something, say so rather than guessing
+- Focus on helping users understand ResilientDB better
+
+Format your responses with:
+- **Bold** for emphasis
+- \`code blocks\` for technical terms and code
+- Bullet points for lists
+- > for important notes or tips
+- Clear headings for different sections
+
+Always aim to be helpful, accurate, and educational.`;
+
+ const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`,
+ },
+ body: JSON.stringify({
+ model: 'deepseek-chat',
+ messages: [
+ { role: 'system', content: systemPrompt },
+ ...messages,
+ ],
+ stream: true,
+ }),
+ });
+
+ if (!response.ok) {
+ return new Response('Error from DeepSeek API', { status: response.status });
+ }
+
+ // Create a TransformStream to process the response
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ const stream = new ReadableStream({
+ async start(controller) {
+ const reader = response.body?.getReader();
+ if (!reader) {
+ controller.close();
+ return;
+ }
+
+ try {
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const chunk = decoder.decode(value);
+ const lines = chunk.split('\n').filter(line => line.trim() !== '');
+
+ for (const line of lines) {
+ if (line.startsWith('data: ')) {
+ const data = line.slice(6);
+ if (data === '[DONE]') continue;
+
+ try {
+ const parsed = JSON.parse(data);
+ const content = parsed.choices[0]?.delta?.content || '';
+ if (content) {
+ controller.enqueue(encoder.encode(content));
+ }
+ } catch (e) {
+ console.error('Error parsing JSON:', e);
+ }
+ }
+ }
+ }
+ } catch (e) {
+ console.error('Stream processing error:', e);
+ controller.error(e);
+ } finally {
+ controller.close();
+ }
+ },
+ });
+
+ return new Response(stream, {
+ headers: {
+ 'Content-Type': 'text/plain; charset=utf-8',
+ 'Transfer-Encoding': 'chunked',
+ },
+ });
+ } catch (error) {
+ console.error('Chat API error:', error);
+ return NextResponse.json(
+ { error: 'Failed to process chat request' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/app/api/compile/route.ts b/ecosystem/AI-Tooling/beacon/app/api/compile/route.ts
new file mode 100644
index 00000000..8e523c99
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/api/compile/route.ts
@@ -0,0 +1,117 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { NextResponse } from 'next/server';
+import solc from 'solc';
+
+interface CompilationInput {
+ language: string;
+ sources: {
+ [key: string]: {
+ content: string;
+ };
+ };
+ settings: {
+ outputSelection: {
+ [key: string]: {
+ [key: string]: string[];
+ };
+ };
+ };
+}
+
+interface CompilationOutput {
+ success: boolean;
+ output: string;
+ abi?: any;
+ bytecode?: string;
+}
+
+export async function POST(request: Request) {
+ try {
+ const { code } = await request.json();
+
+ if (!code || typeof code !== 'string') {
+ return NextResponse.json({
+ success: false,
+ output: 'Invalid contract code provided',
+ });
+ }
+
+ // Configure solc input
+ const input: CompilationInput = {
+ language: 'Solidity',
+ sources: {
+ 'contract.sol': {
+ content: code,
+ },
+ },
+ settings: {
+ outputSelection: {
+ '*': {
+ '*': ['*'],
+ },
+ },
+ },
+ };
+
+ // Compile the contract
+ const output = JSON.parse(solc.compile(JSON.stringify(input)));
+
+ // Check for errors
+ if (output.errors) {
+ const errors = output.errors
+ .filter((error: any) => error.severity === 'error')
+ .map((error: any) => error.formattedMessage);
+
+ if (errors.length > 0) {
+ return NextResponse.json({
+ success: false,
+ output: errors.join('\n'),
+ });
+ }
+ }
+
+ // Get the contract name (assuming it's the first contract in the file)
+ const contractName = Object.keys(output.contracts['contract.sol'])[0];
+ const contract = output.contracts['contract.sol'][contractName];
+
+ if (!contract) {
+ return NextResponse.json({
+ success: false,
+ output: 'No contract found in the provided code',
+ });
+ }
+
+ const response: CompilationOutput = {
+ success: true,
+ output: 'Contract compiled successfully!',
+ abi: contract.abi,
+ bytecode: contract.evm.bytecode.object,
+ };
+
+ return NextResponse.json(response);
+ } catch (error) {
+ console.error('Compilation error:', error);
+ return NextResponse.json({
+ success: false,
+ output: 'Failed to compile contract. Please check your code and try again.',
+ });
+ }
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/app/api/github-releases/route.ts b/ecosystem/AI-Tooling/beacon/app/api/github-releases/route.ts
new file mode 100644
index 00000000..4b741029
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/api/github-releases/route.ts
@@ -0,0 +1,36 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+export async function GET() {
+ const response = await fetch(
+ 'https://api.github.com/repos/gfazioli/next-app-nextra-template/releases',
+ {
+ headers: {
+ Accept: 'application/vnd.github+json',
+ // Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, // Optional for rate limit
+ },
+ }
+ );
+ if (!response.ok) {
+ return Response.json({ error: 'Failed to fetch releases' }, { status: 500 });
+ }
+ const releases = await response.json();
+
+ return Response.json({ releases, status: 'ok' });
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/docs/[[...mdxPath]]/page.tsx b/ecosystem/AI-Tooling/beacon/app/docs/[[...mdxPath]]/page.tsx
new file mode 100644
index 00000000..2739d3c6
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/docs/[[...mdxPath]]/page.tsx
@@ -0,0 +1,43 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { generateStaticParamsFor, importPage } from 'nextra/pages';
+import { useMDXComponents } from '../../../mdx-components';
+
+export const generateStaticParams = generateStaticParamsFor('mdxPath');
+
+export async function generateMetadata(props: any) {
+ const params = await props.params;
+ const { metadata } = await importPage(params.mdxPath);
+ return metadata;
+}
+
+const Wrapper = useMDXComponents().wrapper;
+
+export default async function Page(props: any) {
+ const params = await props.params;
+ const result = await importPage(params.mdxPath);
+ const { default: MDXContent, toc, metadata } = result;
+
+ return (
+ <Wrapper toc={toc} metadata={metadata}>
+ <MDXContent {...props} params={params} />
+ </Wrapper>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/global.css b/ecosystem/AI-Tooling/beacon/app/global.css
new file mode 100644
index 00000000..4c260e75
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/global.css
@@ -0,0 +1,286 @@
+@import 'nextra-theme-docs/dist/style.css' layer(nextra);
+
+/**
+ * Custom styles
+ *
+ * You can add your own custom styles here. This file will be imported after the theme styles.
+ * This is a great place to add your own utility classes or override styles.
+ *
+ * In this case we're going to fix a glitch with the text size in the code blocks.
+ */
+@layer overwrite {
+ :root {
+ /* Force Nextra base background to pure black to match landing */
+ --x-color-nextra-bg: #000;
+ --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
+ }
+
+ :where(.dark) {
+ --x-color-neutral-800: var(--mantine-color-dark-6);
+ }
+
+ .x\:text-sm {
+ font-size: var(--x-text-sm) !important;
+ line-height: var(--tw-leading, var(--x-text-sm--line-height)) !important;
+ }
+}
+
+@layer components, mantine, nextra, overwrite;
+
+/* Global sleek styles */
+@layer overwrite {
+ html {
+ scroll-behavior: smooth;
+ }
+
+ body {
+ color: var(--mantine-color-text);
+ font-family: var(--font-sans);
+ background:
+ radial-gradient(1200px 600px at 80% -10%, rgba(0, 102, 255, 0.12), transparent 100%),
+ radial-gradient(1000px 500px at -10% 10%, rgba(255, 0, 128, 0.09), transparent 100%),
+ linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.0)) !important;
+ /* background: #000000; */
+ }
+
+ :where(.dark) body {
+ color: rgba(255, 255, 255, 0.92);
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ color: inherit;
+ font-family: var(--font-sans);
+ }
+
+ a {
+ color: #1f2937;
+ }
+ :where(.dark) a {
+ color: rgba(255, 255, 255, 0.92) !important;
+ }
+
+ /* Stronger override for Nextra link styling in docs */
+ :where(.dark) .nextra-content a,
+ :where(.dark) .nextra-content a:visited {
+ color: rgba(255, 255, 255, 0.92) !important;
+ }
+ .nextra-content a,
+ .nextra-content a:visited {
+ color: #1f2937 !important;
+ }
+
+ /* Ensure Nextra containers don’t paint a gray background */
+ :where(.nextra-container,
+ .nextra-content,
+ .nextra-sidebar-container,
+ .nx-bg-neutral-900,
+ .nx-bg-gray-900,
+ .nx-bg-black,
+ .nx-bg-dark,
+ .nx-bg-zinc-900,
+ .nx-bg-slate-900) {
+ background-color: transparent !important;
+ }
+
+ /* Make sticky headers/footers transparent and remove borders/shadows */
+ :where(.nx-sticky) {
+ background: transparent !important;
+ box-shadow: none !important;
+ border: none !important;
+ }
+ :where(.nextra-sidebar) header,
+ :where(.nextra-sidebar) footer,
+ :where(.nextra-container > header) {
+ background: transparent !important;
+ border: none !important;
+ box-shadow: none !important;
+ }
+
+ /* Glassmorphism utility */
+ .glass {
+ background: rgba(255, 255, 255, 0.06);
+ -webkit-backdrop-filter: saturate(120%) blur(10px);
+ backdrop-filter: saturate(120%) blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ }
+
+ code, pre {
+ border-radius: 10px;
+ }
+
+ /* Target article tag for markdown content font styling */
+ article {
+ font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
+ font-weight: 400;
+ line-height: 1.6;
+ }
+
+ /* Specific styling for the main content article */
+ article[data-pagefind-body="true"] {
+ font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
+ font-size: 16px;
+ line-height: 1.7;
+ color: rgba(255, 255, 255, 0.9);
+ }
+
+ /* Override Nextra's default article styling */
+ article.x\:w-full {
+ font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
+ }
+
+ /* Sidebar footer background override */
+ :where(.nextra-sidebar-footer) {
+ background: transparent !important;
+ border-top: none !important;
+ box-shadow: none !important;
+ }
+
+ /* Lightweight parallax blob for landing */
+ .parallax-blob {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 55vmax;
+ height: 55vmax;
+ transform: translate(-50%, -50%) scale(0.9);
+ background: radial-gradient(closest-side, rgba(0, 102, 255, 0.18), transparent 72%);
+ pointer-events: none;
+ transition: transform 0.18s ease-out, opacity 300ms ease-out;
+ will-change: transform;
+ filter: blur(12px);
+ opacity: 0;
+ }
+
+ /* Beacon of light effect */
+ @keyframes beamPulse {
+ 0%, 100% { opacity: 0.45; transform: translateX(-50%) scaleX(1); }
+ 50% { opacity: 0.7; transform: translateX(-50%) scaleX(1.08); }
+ }
+ .beacon-beam {
+ position: absolute;
+ left: 50%;
+ top: -10vh;
+ height: 120vh;
+ width: 2px;
+ transform: translateX(-50%);
+ background: linear-gradient(180deg,
+ rgba(255,255,255,0) 0%,
+ rgba(0, 146, 255, 0.4) 40%,
+ rgba(0, 146, 255, 0.35) 60%,
+ rgba(255,255,255,0) 100%);
+ filter: blur(8px);
+ opacity: 0.55;
+ pointer-events: none;
+ animation: beamPulse 4s ease-in-out infinite;
+ }
+ .beacon-source {
+ position: absolute;
+ left: 50%;
+ bottom: 10vh;
+ width: 36vmin;
+ height: 12vmin;
+ transform: translateX(-50%);
+ background: radial-gradient(closest-side, rgba(0,146,255,0.28), rgba(0,146,255,0.08) 60%, transparent 70%);
+ filter: blur(14px);
+ opacity: 0.6;
+ pointer-events: none;
+ }
+
+ /* Distant beacon cone (perspective beam) */
+ .beacon-cone {
+ position: absolute;
+ left: 50%;
+ bottom: -10vh;
+ transform: translateX(-50%) perspective(800px) rotateX(60deg);
+ width: 4px;
+ height: 130vh;
+ background: linear-gradient(
+ 180deg,
+ rgba(0, 146, 255, 0.0) 0%,
+ rgba(0, 146, 255, 0.28) 30%,
+ rgba(0, 146, 255, 0.18) 70%,
+ rgba(0, 146, 255, 0.0) 100%
+ );
+ box-shadow: 0 0 40px 6px rgba(0,146,255,0.18), 0 0 120px 24px rgba(0,146,255,0.10);
+ filter: blur(2px);
+ pointer-events: none;
+ opacity: 0.55;
+ }
+
+ /* Beacon outline (SVG stroke glow) */
+ .beacon-outline {
+ position: absolute;
+ left: 50%;
+ bottom: 6vh;
+ transform: translateX(-50%);
+ width: clamp(120px, 22vmin, 220px);
+ height: auto;
+ opacity: 0.8;
+ pointer-events: none;
+ }
+
+ /* Custom Pagefind Search Styles */
+ .pagefind-search {
+ position: relative;
+ z-index: 10000;
+ }
+
+ .pagefind-search-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ backdrop-filter: blur(8px);
+ z-index: 10000;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding-top: 10vh;
+ }
+
+ .pagefind-search-container {
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 12px;
+ backdrop-filter: blur(10px);
+ min-width: 600px;
+ max-width: 90vw;
+ max-height: 80vh;
+ overflow: hidden;
+ }
+
+ .pagefind-search-input {
+ background: transparent;
+ border: none;
+ outline: none;
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 16px;
+ flex: 1;
+ min-width: 0;
+ }
+
+ .pagefind-search-result {
+ padding: 16px 20px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+ }
+
+ .pagefind-search-result:hover {
+ background: rgba(255, 255, 255, 0.1);
+ }
+
+ .pagefind-search-result:last-child {
+ border-bottom: none;
+ }
+
+ /* Universal stronger focus-visible ring*/
+ :focus-visible {
+ outline: none !important;
+ box-shadow: 0 0 0 2px var(--focus-inner-ring, #000), 0 0 0 4px var(--focus-outer-ring, #fff);
+ /* match the element's radius so rings follow shape */
+ border-radius: inherit;
+ }
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/layout.tsx b/ecosystem/AI-Tooling/beacon/app/layout.tsx
new file mode 100644
index 00000000..a7a63118
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/layout.tsx
@@ -0,0 +1,120 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { MantineFooter, MantineNavBar } from '@/components';
+
+import '@gfazioli/mantine-marquee/styles.layer.css';
+import '@gfazioli/mantine-text-animate/styles.layer.css';
+
+import { ColorSchemeScript, mantineHtmlProps, MantineProvider } from '@mantine/core';
+
+import { Layout } from 'nextra-theme-docs';
+import { Banner, Head } from 'nextra/components';
+import { getPageMap } from 'nextra/page-map';
+import { theme } from '../theme';
+import { Inter } from 'next/font/google';
+
+import './global.css';
+
+import { FloatingAssistant } from '@/components/FloatingAssistant';
+import { PocketFlowChatbot } from '@/components/PocketFlowChatbot';
+import { Providers } from './providers';
+
+const inter = Inter({ subsets: ['latin'], variable: '--font-sans' });
+
+export const metadata = {
+ title: {
+ default: 'Beacon - NextGen Docs for Nexres',
+ template: '%s | ResilientDB Documentation',
+ },
+ description:
+ 'Official documentation for ResilientDB, a high-performance, scalable blockchain database for mission-critical applications.',
+ metadataBase: new URL('https://resilientdb.com/'),
+ keywords: [
+ 'ResilientDB',
+ 'Blockchain',
+ 'Database',
+ 'Distributed Systems',
+ 'Consensus',
+ 'PBFT',
+ 'Next.js',
+ 'Mantine',
+ 'Nextra',
+ 'Documentation',
+ ],
+ generator: 'Next.js',
+ applicationName: 'ResilientDB',
+ appleWebApp: {
+ title: 'ResilientDB',
+ },
+ openGraph: {
+ url: 'https://resilientdb.com/',
+ siteName: 'ResilientDB',
+ locale: 'en_US',
+ type: 'website',
+ },
+ other: {
+ 'msapplication-TileColor': '#fff',
+ },
+ twitter: {
+ site: 'https://resilientdb.com/',
+ },
+ alternates: {
+ canonical: 'https://resilientdb.com/',
+ },
+};
+
+export default async function RootLayout({ children }: { children: any }) {
+ const pageMap = await getPageMap();
+
+ return (
+ <html lang="en" dir="ltr" {...mantineHtmlProps} className={`${inter.className} ${inter.variable}`}>
+ <Head>
+ <ColorSchemeScript nonce="8IBTHwOdqNKAWeKl7plt8g==" defaultColorScheme="dark" />
+ <link rel="shortcut icon" href="/favicon.svg" type="image/svg+xml" />
+ <meta
+ name="viewport"
+ content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
+ />
+ </Head>
+ <body>
+ <Providers>
+ <MantineProvider theme={theme} defaultColorScheme="dark">
+ <Layout
+ // banner={
+ // <Banner storageKey="mantine-nextjs-nextra">
+ // ✨ AI powered documentation for ResilientDB Ecosystem and Apps
+ // </Banner>
+ // }
+ navbar={<MantineNavBar />}
+ pageMap={pageMap}
+ docsRepositoryBase="https://github.com/shuding/nextra/tree/main/docs"
+ footer={<MantineFooter />}
+ sidebar={{ defaultMenuCollapseLevel: 1, toggleButton: true }}
+ >
+ {children}
+ </Layout>
+ <FloatingAssistant />
+ {/* <PocketFlowChatbot /> */}
+ </MantineProvider>
+ </Providers>
+ </body>
+ </html>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/lib/constants.ts b/ecosystem/AI-Tooling/beacon/app/lib/constants.ts
new file mode 100644
index 00000000..c46bf8f0
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/lib/constants.ts
@@ -0,0 +1,350 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+export const PYTHON_SDK_EXAMPLE_TEMPLATES = [
+ {
+ value: 'default',
+ label: 'Welcome - Start Here',
+ code: `"""
+Welcome to the ResilientDB Python Playground!
+
+This interactive environment allows you to test and experiment with ResilientDB's key-value store
+using our Python SDK. The code here matches the patterns used in the official ResilientDB Python SDK.
+
+Available Features:
+1. Basic Transaction Operations
+ - Create simple transactions
+ - Create transactions with metadata
+ - Retrieve transactions by ID
+
+Choose an example from the dropdown menu above to get started!
+
+Note: Each transaction needs a unique ID. Our examples use timestamps to ensure uniqueness.
+"""`,
+ },
+ {
+ value: 'send_transaction',
+ label: 'Send Simple Transaction',
+ code: `"""
+Example: Create and send a simple transaction (without metadata)
+"""
+from resdb_sdk import ResilientDB, Transaction
+import json
+import time
+
+# Initialize ResilientDB client
+client = ResilientDB('https://crow.resilientdb.com')
+
+# Create unique ID using timestamp
+unique_id = f"test_{int(time.time())}"
+
+# Create transaction (without metadata)
+transaction = Transaction(
+ id=unique_id,
+ value="Hello from ResilientDB!"
+)
+
+print(f"Creating transaction with ID: {unique_id}")
+print("Transaction data:")
+print(json.dumps(transaction.to_dict(), indent=2))
+
+print("\\nSending transaction...")
+result = await client.transactions.create(transaction)
+print("Response:")
+print(json.dumps(result, indent=2))`,
+ },
+ {
+ value: 'send_transaction_with_metadata',
+ label: 'Send Transaction with Metadata',
+ code: `"""
+Example: Create and send a transaction with metadata
+"""
+from resdb_sdk import ResilientDB, Transaction, TransactionMetadata
+import json
+import time
+
+# Initialize ResilientDB client
+client = ResilientDB('https://crow.resilientdb.com')
+
+# Create unique ID using timestamp
+unique_id = f"test_{int(time.time())}"
+
+# Create metadata
+metadata = TransactionMetadata(
+ timestamp=time.time(),
+ source="playground",
+ tags=["test", "example"]
+)
+
+# Create transaction with metadata
+transaction = Transaction(
+ id=unique_id,
+ value="Hello from ResilientDB!",
+ metadata=metadata
+)
+
+print(f"Creating transaction with ID: {unique_id}")
+print("Transaction data:")
+print(json.dumps(transaction.to_dict(), indent=2))
+
+print("\\nSending transaction...")
+result = await client.transactions.create(transaction)
+print("Response:")
+print(json.dumps(result, indent=2))`,
+ },
+ {
+ value: 'get_transaction',
+ label: 'Get Transaction',
+ code: `"""
+Example: Retrieve a transaction from ResilientDB
+"""
+from resdb_sdk import ResilientDB
+import json
+
+# Initialize ResilientDB client
+client = ResilientDB('https://crow.resilientdb.com')
+
+# Transaction ID to retrieve
+tx_id = "test_1234567890"
+
+print(f"Retrieving transaction with ID: {tx_id}")
+print("\\nSending GET request...")
+
+result = await client.transactions.retrieve(tx_id)
+print("\\nResponse:")
+print(json.dumps(result, indent=2))`,
+ },
+ {
+ value: 'complete_workflow',
+ label: 'Complete Workflow',
+ code: `"""
+Example: Complete workflow demonstrating transaction creation and retrieval
+"""
+from resdb_sdk import ResilientDB, Transaction, TransactionMetadata
+import json
+import time
+
+# Initialize ResilientDB client
+client = ResilientDB('https://crow.resilientdb.com')
+
+# Step 1: Create and send transaction
+print("Step 1: Creating and sending transaction...")
+
+# Generate unique ID using timestamp
+unique_id = f"test_{int(time.time())}"
+
+# Create metadata
+metadata = TransactionMetadata(
+ timestamp=time.time(),
+ source="playground",
+ workflow="complete_example"
+)
+
+# Create transaction
+transaction = Transaction(
+ id=unique_id,
+ value="Complete workflow test",
+ metadata=metadata
+)
+
+print(f"Transaction ID: {unique_id}")
+print("Transaction data:")
+print(json.dumps(transaction.to_dict(), indent=2))
+
+# Send transaction
+send_result = await client.transactions.create(transaction)
+print("\\nPOST Response:")
+print(json.dumps(send_result, indent=2))
+
+# Step 2: Retrieve the transaction
+print("\\nStep 2: Retrieving the transaction...")
+get_result = await client.transactions.retrieve(unique_id)
+print("GET Response:")
+print(json.dumps(get_result, indent=2))
+
+print("\\nWorkflow complete!")`,
+ },
+];
+
+export const RESILIENTDB_GRAPHQL_EXAMPLE_TEMPLATES = [
+ {
+ value: 'rest_api_examples',
+ label: 'Rest API Examples',
+ code: `
+from pyodide.http import pyfetch
+import json
+
+# Initialize connection
+BASE_URL = 'https://crow.resilientdb.com/v1'
+
+# Set a transaction
+async def set_transaction(key, value):
+ response = await pyfetch(
+ f'{BASE_URL}/transactions/commit',
+ method='POST',
+ headers={'Content-Type': 'application/json'},
+ body=json.dumps({
+ 'id': key,
+ 'value': value
+ })
+ )
+ return await response.json()
+
+# Get a transaction
+async def get_transaction(key):
+ response = await pyfetch(f'{BASE_URL}/transactions/{key}')
+ return await response.json()
+
+# Example usage
+async def main():
+ # Set a transaction
+ result = await set_transaction('test_key', 'test_value')
+ print('Set result:', result)
+
+ # Get a transaction
+ value = await get_transaction('test_key')
+ print('Get result:', value)
+
+# Run the example
+await main()
+`,
+ },
+ {
+ value: 'graphql_query_example',
+ label: 'Get Transaction (Query)',
+ code: `
+from pyodide.http import pyfetch
+import json
+
+# GraphQL endpoint
+url = "https://cloud.resilientdb.com/graphql"
+
+# Query and variables
+query = """
+query GetTx($id: ID!) {
+ getTransaction(id: $id) {
+ id
+ version
+ amount
+ uri
+ type
+ publicKey
+ operation
+ metadata
+ asset
+ signerPublicKey
+ }
+}
+"""
+
+variables = {
+ "id": "6358ee73fb6bf5c659676ccc8476ebb6ea738b47a101661697ef379e5ea0212b"
+}
+
+# Prepare the payload
+payload = {
+ "query": query,
+ "variables": variables
+}
+
+async def get_transaction():
+ # Make the request
+ response = await pyfetch(
+ url,
+ method="POST",
+ headers={"Content-Type": "application/json"},
+ body=json.dumps(payload)
+ )
+
+ # Get and print the response
+ result = await response.json()
+ print(json.dumps(result, indent=2))
+
+# Run the example
+await get_transaction()
+`,
+ },
+ {
+ value: 'graphql_mutation_example',
+ label: 'Create Transaction (Mutation)',
+ code: `
+from pyodide.http import pyfetch
+import json
+
+# GraphQL endpoint
+url = "https://cloud.resilientdb.com/graphql"
+
+# Drawing ID for the asset
+drawing_id = "drawing_testing_random_seed"
+
+# Mutation and variables
+mutation = """
+mutation Test($data: PrepareAsset!) {
+ postTransaction(data: $data) {
+ id
+ }
+}
+"""
+
+variables = {
+ "data": {
+ "operation": "CREATE",
+ "amount": 1,
+ "signerPublicKey": "FbUGKzKnSgh6bKRw8sxdzaCq1NMjGT6FVeAWLot5bCa1",
+ "signerPrivateKey": "5EzirRSQvWHwtekrg4TYtxBbdJbtgvG25pepGZRWJneC",
+ "recipientPublicKey": "FbUGKzKnSgh6bKRw8sxdzaCq1NMjGT6FVeAWLot5bCa1",
+ "asset": {
+ "data": {
+ "drawingId": drawing_id,
+ "color": "#000000",
+ "lineWidth": 5,
+ "pathData": [{"x": 292, "y": 126}],
+ "timestamp": 1746736411543,
+ "user": "appleseed|1746736304144",
+ "brushStyle": "round",
+ "order": 1746736411543
+ }
+ }
+ }
+}
+
+async def create_transaction():
+ # Prepare the payload
+ payload = {
+ "query": mutation,
+ "variables": variables
+ }
+
+ # Make the request
+ response = await pyfetch(
+ url,
+ method="POST",
+ headers={"Content-Type": "application/json"},
+ body=json.dumps(payload)
+ )
+
+ # Get and print the response
+ result = await response.json()
+ print(json.dumps(result, indent=2))
+
+# Run the example
+await create_transaction()
+`,
+ },
+];
diff --git a/ecosystem/AI-Tooling/beacon/app/lib/utils.ts b/ecosystem/AI-Tooling/beacon/app/lib/utils.ts
new file mode 100644
index 00000000..09569c07
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/lib/utils.ts
@@ -0,0 +1,22 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+export function cn(...classes: (string | undefined | false | null)[]) {
+ return classes.filter(Boolean).join(' ');
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/page.module.css b/ecosystem/AI-Tooling/beacon/app/page.module.css
new file mode 100644
index 00000000..ca5c4c9d
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/page.module.css
@@ -0,0 +1,361 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+.root {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #1a1b23 0%, #25262b 50%, #1a1b23 100%);
+ transition: background 0.3s ease;
+}
+
+/* Light mode for root */
+:global([data-mantine-color-scheme="light"]) .root {
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 25%, #cbd5e1 50%, #e2e8f0 75%, #f8fafc 100%);
+}
+
+ .hero {
+ position: relative;
+ overflow: hidden;
+ padding: 5rem 0;
+ background: linear-gradient(135deg, #1a1b23 0%, #25262b 50%, #1a1b23 100%);
+ transition: background 0.3s ease;
+}
+
+/* Light mode for hero */
+:global([data-mantine-color-scheme="light"]) .hero {
+ background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 25%, #cbd5e1 50%, #e2e8f0 75%, #f1f5f9 100%);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
+}
+
+ .heroGrid {
+ position: absolute;
+ inset: 0;
+ background-image: radial-gradient(circle, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
+ background-size: 50px 50px;
+ }
+
+ .heroContainer {
+ position: relative;
+ z-index: 1;
+ }
+
+ .heroTitle {
+ font-size: clamp(3rem, 10vw, 6rem);
+ font-weight: 800;
+ color: white;
+ line-height: 1.1;
+ transition: color 0.3s ease;
+ }
+
+ /* Light mode hero title */
+ :global([data-mantine-color-scheme="light"]) .heroTitle {
+ color: #0f172a !important;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
+ }
+
+ .description {
+ color: rgba(255, 255, 255, 0.8);
+ transition: color 0.3s ease;
+ }
+
+ /* Light mode description */
+ :global([data-mantine-color-scheme="light"]) .description {
+ color: #475057 !important;
+ }
+
+ .cardTitle {
+ color: white;
+ transition: color 0.3s ease;
+ }
+
+ /* Light mode card title */
+ :global([data-mantine-color-scheme="light"]) .cardTitle {
+ color: #0f172a !important;
+ }
+
+ .cardDescription {
+ color: rgba(255, 255, 255, 0.7);
+ transition: color 0.3s ease;
+ }
+
+ /* Light mode card description */
+ :global([data-mantine-color-scheme="light"]) .cardDescription {
+ color: #64748b !important;
+ }
+
+ .gradientText {
+ background: linear-gradient(45deg, var(--mantine-color-pink-4), var(--mantine-color-red-4), var(--mantine-color-orange-4));
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: pulse 2s ease-in-out infinite;
+ }
+
+ @keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.8; }
+}
+
+.animatedGradient {
+ background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
+ background-size: 400% 400%;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: gradientShift 6s ease infinite, pulse 2s ease-in-out infinite;
+}
+
+@keyframes gradientShift {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+ .link {
+ transition: color 0.2s ease;
+ }
+
+ .link:hover {
+ color: var(--mantine-color-pink-3) !important;
+ }
+
+ .primaryButton {
+ transition: all 0.3s ease;
+ border: 1px solid var(--mantine-color-gray-6);
+ }
+
+ .primaryButton:hover {
+ transform: scale(1.05);
+ }
+
+ .secondaryButton {
+ background: rgba(var(--mantine-color-dark-8-rgb), 0.5);
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+ }
+
+ .secondaryButton:hover {
+ background: var(--mantine-color-dark-7);
+ color: white;
+ border-color: var(--mantine-color-gray-5);
+ }
+
+ .terminalCard {
+ background: var(--mantine-color-dark-9);
+ border: 1px solid var(--mantine-color-dark-6);
+ max-width: 800px;
+ margin: 0 auto;
+ }
+
+ .terminalDot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ }
+
+ .terminalContent {
+ color: var(--mantine-color-teal-4);
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 0.875rem;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ height: 250px;
+ max-height: 250px;
+ overflow-y: auto;
+ margin: 0;
+ padding: 1rem;
+ scrollbar-width: thin;
+ scrollbar-color: var(--mantine-color-dark-6) var(--mantine-color-dark-9);
+ }
+
+ /* Custom scrollbar styles for webkit browsers */
+ .terminalContent::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ .terminalContent::-webkit-scrollbar-track {
+ background: var(--mantine-color-dark-9);
+ border-radius: 4px;
+ }
+
+ .terminalContent::-webkit-scrollbar-thumb {
+ background: var(--mantine-color-dark-6);
+ border-radius: 4px;
+ }
+
+ .terminalContent::-webkit-scrollbar-thumb:hover {
+ background: var(--mantine-color-dark-5);
+ }
+
+ .carouselContainer {
+ position: relative;
+ width: 100%;
+ max-width: 1400px;
+ }
+
+ .carouselArrow {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 20;
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+ }
+
+ .leftArrow {
+ left: 1rem;
+ }
+
+ .rightArrow {
+ right: 1rem;
+ }
+
+ .arrowActive {
+ background: rgba(var(--mantine-color-dark-8-rgb), 0.9);
+ border-color: var(--mantine-color-gray-6);
+ color: white;
+ }
+
+ .arrowActive:hover {
+ background: var(--mantine-color-dark-7);
+ border-color: var(--mantine-color-gray-5);
+ }
+
+ .arrowDisabled {
+ background: rgba(var(--mantine-color-dark-9-rgb), 0.5);
+ border-color: rgba(var(--mantine-color-dark-7-rgb), 0.5);
+ color: var(--mantine-color-gray-7);
+ cursor: not-allowed;
+ }
+
+ .carouselWrapper {
+ overflow: visible;
+ padding: 0 5rem;
+ }
+
+ .carouselTrack {
+ transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
+ justify-content: flex-start;
+ }
+
+ .cardWrapper {
+ flex-shrink: 0;
+ padding: 0 0.75rem;
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ .projectCard {
+ background: rgba(var(--mantine-color-dark-8-rgb), 0.3);
+ border: 1px solid rgba(var(--mantine-color-gray-6-rgb), 0.5);
+ transition: all 0.5s ease;
+ position: relative;
+ }
+
+ .projectCard:hover {
+ border-color: var(--mantine-color-gray-5);
+ background: rgba(var(--mantine-color-dark-8-rgb), 0.5);
+ }
+
+ /* Light mode project cards */
+ :global([data-mantine-color-scheme="light"]) .projectCard {
+ background: rgba(15, 23, 42, 0.1) !important;
+ border: 1px solid rgba(15, 23, 42, 0.2) !important;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
+ }
+
+ :global([data-mantine-color-scheme="light"]) .projectCard:hover {
+ background: rgba(15, 23, 42, 0.15) !important;
+ border-color: rgba(15, 23, 42, 0.3) !important;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+ }
+
+ .iconWrapper {
+ width: 48px;
+ height: 48px;
+ background: linear-gradient(135deg, var(--mantine-color-gray-6), var(--mantine-color-gray-7));
+ border-radius: var(--mantine-radius-md);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--mantine-color-gray-2);
+ transition: transform 0.3s ease;
+ }
+
+ .projectCard:hover .iconWrapper {
+ transform: rotate(12deg);
+ }
+
+ /* Light mode icon wrapper */
+ :global([data-mantine-color-scheme="light"]) .iconWrapper {
+ background: linear-gradient(135deg, #475569, #64748b) !important;
+ color: #f8fafc !important;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
+ }
+
+ .projectButton {
+ border: 1px solid rgba(var(--mantine-color-gray-5-rgb), 0.5);
+ transition: all 0.3s ease;
+ }
+
+ .projectButton:hover {
+ border-color: var(--mantine-color-gray-4);
+ }
+
+ .dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--mantine-color-gray-6);
+ border: none;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ }
+
+ .dot:hover {
+ background: var(--mantine-color-gray-5);
+ }
+
+ .dotActive {
+ background: var(--mantine-color-pink-4);
+ width: 32px;
+ border-radius: 4px;
+ }
+
+ @media (max-width: 768px) {
+ .hero {
+ padding: 3rem 0;
+ }
+
+ .heroTitle {
+ font-size: 2.5rem;
+ }
+
+ .carouselWrapper {
+ padding: 0 3rem;
+ }
+
+ .carouselArrow {
+ display: none;
+ }
+ }
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/app/page.tsx b/ecosystem/AI-Tooling/beacon/app/page.tsx
new file mode 100644
index 00000000..e0a1e865
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/page.tsx
@@ -0,0 +1,17 @@
+import { Container } from '@mantine/core';
+import ShaderBackground from '@/components/landing/ShaderBackground';
+import PulsingCircle from '@/components/landing/PulsingCircle';
+import HeroContent from '@/components/landing/HeroContent';
+import Header from '@/components/landing/Header';
+
+export default function HomePage() {
+ return (
+ <Container p={0} size="100%" style={{ maxWidth: '100%' }}>
+ <ShaderBackground>
+ <Header />
+ <HeroContent />
+ <PulsingCircle />
+ </ShaderBackground>
+ </Container>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/providers.tsx b/ecosystem/AI-Tooling/beacon/app/providers.tsx
new file mode 100644
index 00000000..5439fd46
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/providers.tsx
@@ -0,0 +1,26 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { SessionProvider } from 'next-auth/react';
+
+export function Providers({ children }: { children: React.ReactNode }) {
+ return <SessionProvider>{children}</SessionProvider>;
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/search/page.tsx b/ecosystem/AI-Tooling/beacon/app/search/page.tsx
new file mode 100644
index 00000000..6438ab14
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/search/page.tsx
@@ -0,0 +1,25 @@
+'use client'
+
+import { PagefindSearch } from '../../components/SearchBar/PagefindSearch';
+
+export default function SearchPage() {
+ return (
+ <div style={{
+ minHeight: '100vh',
+ background: 'transparent',
+ padding: '20px'
+ }}>
+ <div style={{ maxWidth: 800, margin: '0 auto' }}>
+ <h1 style={{
+ color: 'rgba(255, 255, 255, 0.9)',
+ marginBottom: '20px',
+ fontSize: '2rem',
+ fontWeight: 600
+ }}>
+ Search Documentation
+ </h1>
+ <PagefindSearch />
+ </div>
+ </div>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/app/sitemap.ts b/ecosystem/AI-Tooling/beacon/app/sitemap.ts
new file mode 100644
index 00000000..2424c800
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/app/sitemap.ts
@@ -0,0 +1,95 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { MetadataRoute } from 'next';
+
+export default function sitemap(): MetadataRoute.Sitemap {
+ const baseUrl = 'https://beacon.resilientdb.com';
+
+ const routes = [
+ {
+ url: baseUrl,
+ lastModified: new Date(),
+ changeFrequency: 'daily' as const,
+ priority: 0.5,
+ },
+ {
+ url: `${baseUrl}/docs`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/01_client_interaction`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/02_network_communication`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/03_consensus_management`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/04_message_transaction_collection`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/05_transaction_execution`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/06_storage_layer`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/07_checkpointing`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/docs/resilientdb/chapters/08_resilientdb_configuration`,
+ lastModified: new Date(),
+ changeFrequency: 'weekly' as const,
+ priority: 1,
+ },
+ ];
+
+ return routes;
+}
diff --git a/ecosystem/AI-Tooling/beacon/asf.yaml b/ecosystem/AI-Tooling/beacon/asf.yaml
new file mode 100644
index 00000000..1d251083
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/asf.yaml
@@ -0,0 +1,42 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+github:
+ description: Global-Scale Sustainable Blockchain Fabric
+ homepage: https://resilientdb.incubator.apache.org/
+ labels:
+ - crypto
+ - smart-contracts
+ - blockchain
+ - solidity
+ - distributed-database
+ - key-value-database
+ - distributed-ledger
+ - blockchain-platform
+ - utxo
+ enabled_merge_buttons:
+ squash: true
+ merge: false
+ rebase: false
+ protected_branches:
+ main:
+ required_status_checks:
+ strict: true
+
+notifications:
+ commits: commits@resilientdb.apache.org
+ issues: commits@resilientdb.apache.org
+ pullrequests: commits@resilientdb.apache.org
diff --git a/ecosystem/AI-Tooling/beacon/components/AskAIModal.tsx b/ecosystem/AI-Tooling/beacon/components/AskAIModal.tsx
new file mode 100644
index 00000000..d007f7bf
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/AskAIModal.tsx
@@ -0,0 +1,137 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client'
+
+import { Button, Modal, TextInput, Stack, Text, Divider } from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import { useEffect, useRef } from 'react';
+import { IconRobot, IconSearch } from '@tabler/icons-react';
+import { useRouter } from 'next/navigation';
+
+export function AskAIModal() {
+ const [opened, { open: openDisclosure, close: closeDisclosure }] = useDisclosure(false);
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
+ const firstInputRef = useRef<HTMLInputElement | null>(null);
+ const lastFocusedRef = useRef<HTMLElement | null>(null);
+
+ const open = (from?: HTMLElement | null) => {
+ lastFocusedRef.current = from ?? (document.activeElement as HTMLElement | null);
+ openDisclosure();
+ };
+
+ const close = () => {
+ closeDisclosure();
+ // return focus to last focused element
+ setTimeout(() => {
+ try {
+ lastFocusedRef.current?.focus?.();
+ } catch (err) {}
+ }, 0);
+ };
+
+ // Add Escape handler while modal is open
+ useEffect(() => {
+ if (!opened) return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ close();
+ }
+ };
+ document.addEventListener('keydown', onKey);
+ // focus first input when opened
+ setTimeout(() => firstInputRef.current?.focus(), 50);
+ return () => document.removeEventListener('keydown', onKey);
+ }, [opened]);
+ const router = useRouter();
+
+ const handleSearch = (query: string) => {
+ // This will navigate to the search page with the query
+ if (query) {
+ router.push(`/?q=${encodeURIComponent(query)}`);
+ close();
+ }
+ };
+
+ return (
+ <>
+ <Button
+ ref={triggerRef}
+ onClick={(e) => open(e.currentTarget)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ open(e.currentTarget as HTMLElement);
+ }
+ }}
+ leftSection={<IconRobot size={20} />}
+ variant="subtle"
+ >
+ Ask AI
+ </Button>
+
+ <Modal
+ opened={opened}
+ onClose={close}
+ title="Ask AI Assistant"
+ size="lg"
+ >
+ <Stack>
+ <Text size="sm" c="dimmed">
+ Ask me anything about ResilientDB and its ecosystem. I'm here to help!
+ </Text>
+ <TextInput
+ ref={firstInputRef}
+ placeholder="Ask the AI assistant..."
+ size="md"
+ leftSection={<IconRobot size={16} />}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleSearch((e.target as HTMLInputElement).value);
+ }
+ }}
+ />
+ <Text size="xs" c="dimmed">
+ Coming soon: Integration with ResilientDB's AI assistant for more intelligent responses.
+ </Text>
+
+ <Divider label="Or search the documentation" labelPosition="center" my="md" />
+
+ <Text size="sm" c="dimmed">
+ Search through our comprehensive documentation to find detailed information.
+ </Text>
+ <TextInput
+ placeholder="Search documentation..."
+ size="md"
+ leftSection={<IconSearch size={16} />}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleSearch((e.target as HTMLInputElement).value);
+ }
+ }}
+ />
+ <Text size="xs" c="dimmed">
+ Press Enter to search through the entire documentation.
+ </Text>
+ </Stack>
+ </Modal>
+ </>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/AuthProviders.tsx b/ecosystem/AI-Tooling/beacon/components/AuthProviders.tsx
new file mode 100644
index 00000000..b65e224d
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/AuthProviders.tsx
@@ -0,0 +1,32 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { SessionProvider } from 'next-auth/react';
+import { MantineProvider } from '@mantine/core';
+import { theme } from '../theme';
+
+export function AuthProviders({ children }: { children: React.ReactNode }) {
+ return (
+ <SessionProvider>
+ <MantineProvider theme={theme}>{children}</MantineProvider>
+ </SessionProvider>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.module.css b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.module.css
new file mode 100644
index 00000000..3a92eae4
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.module.css
@@ -0,0 +1,46 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+.icon {
+ width: 22px;
+ height: 22px;
+ transition: opacity 0.2s ease;
+}
+
+.dark {
+ display: block;
+}
+
+.light {
+ display: block;
+}
+
+/* Hide icons based on color scheme */
+:global([data-mantine-color-scheme="light"]) .dark {
+ display: none;
+}
+
+:global([data-mantine-color-scheme="dark"]) .light {
+ display: none;
+}
+
+/* Fallback for when no color scheme is set (default to dark) */
+:global(:root:not([data-mantine-color-scheme])) .light {
+ display: none;
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.story.tsx b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.story.tsx
new file mode 100644
index 00000000..01f56202
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.story.tsx
@@ -0,0 +1,26 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { ColorSchemeControl } from './ColorSchemeControl';
+
+export default {
+ title: 'ColorSchemeControl',
+};
+
+export const Usage = () => <ColorSchemeControl />;
diff --git a/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.tsx b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.tsx
new file mode 100644
index 00000000..0eca3c57
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/ColorSchemeControl.tsx
@@ -0,0 +1,49 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { IconMoon, IconSun } from '@tabler/icons-react';
+import cx from 'clsx';
+import { useTheme } from 'nextra-theme-docs';
+import { useComputedColorScheme, useMantineColorScheme } from '@mantine/core';
+import { HeaderControl } from './HeaderControl';
+import classes from './ColorSchemeControl.module.css';
+
+export function ColorSchemeControl() {
+ const { setColorScheme } = useMantineColorScheme();
+ const { setTheme } = useTheme();
+
+ const computedColorScheme = useComputedColorScheme('dark', { getInitialValueInEffect: true });
+
+ const handleColorSchemeChange = () => {
+ const newColorScheme = computedColorScheme === 'light' ? 'dark' : 'light';
+ setColorScheme(newColorScheme);
+ setTheme(newColorScheme);
+ };
+
+ return (
+ <HeaderControl
+ onClick={handleColorSchemeChange}
+ tooltip={`${computedColorScheme === 'dark' ? 'Light' : 'Dark'} mode`}
+ aria-label="Toggle color scheme"
+ >
+ <IconSun className={cx(classes.icon, classes.light)} stroke={1.5} />
+ <IconMoon className={cx(classes.icon, classes.dark)} stroke={1.5} />
+ </HeaderControl>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/HeaderControl.module.css b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/HeaderControl.module.css
new file mode 100644
index 00000000..9f0c4210
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/HeaderControl.module.css
@@ -0,0 +1,50 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+.control {
+ width: 34px;
+ height: 34px;
+ border-radius: var(--mantine-radius-md);
+ border: 1px solid var(--mantine-color-dark-4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+ cursor: pointer;
+
+ /* Default dark mode styles */
+ border-color: var(--mantine-color-dark-4);
+ color: var(--mantine-color-white);
+ background-color: var(--mantine-color-dark-6);
+}
+
+.control:hover {
+ background-color: var(--mantine-color-dark-5);
+}
+
+/* Light mode styles */
+:global([data-mantine-color-scheme="light"]) .control {
+ border-color: var(--mantine-color-gray-3);
+ color: var(--mantine-color-gray-7);
+ background-color: var(--mantine-color-white);
+}
+
+:global([data-mantine-color-scheme="light"]) .control:hover {
+ background-color: var(--mantine-color-gray-0);
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/HeaderControl.tsx b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/HeaderControl.tsx
new file mode 100644
index 00000000..ef73e9d3
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ColorSchemeControl/HeaderControl.tsx
@@ -0,0 +1,51 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import cx from 'clsx';
+import { BoxProps, createPolymorphicComponent, Tooltip, UnstyledButton } from '@mantine/core';
+import classes from './HeaderControl.module.css';
+
+export interface HeaderControlProps extends BoxProps {
+ tooltip: string;
+ 'aria-label'?: string;
+ children: React.ReactNode;
+}
+
+function _HeaderControl({
+ tooltip,
+ className,
+ 'aria-label': label,
+ ...others
+}: HeaderControlProps) {
+ return (
+ <Tooltip label={tooltip}>
+ <UnstyledButton
+ className={cx(classes.control, className)}
+ aria-label={label || tooltip}
+ {...others}
+ />
+ </Tooltip>
+ );
+}
+
+export const HeaderControl = createPolymorphicComponent<'button', HeaderControlProps>(
+ _HeaderControl
+);
diff --git a/ecosystem/AI-Tooling/beacon/components/ColorSchemeToggle/ColorSchemeToggle.tsx b/ecosystem/AI-Tooling/beacon/components/ColorSchemeToggle/ColorSchemeToggle.tsx
new file mode 100644
index 00000000..0da7fd0b
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ColorSchemeToggle/ColorSchemeToggle.tsx
@@ -0,0 +1,62 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { useTheme } from 'next-themes';
+import { Button, Group, useMantineColorScheme } from '@mantine/core';
+
+export function ColorSchemeToggle() {
+ const { setColorScheme } = useMantineColorScheme();
+ const { setTheme } = useTheme();
+
+ /**
+ * You might improve this component. Anyway, it's a good starting point.
+ * As you can see we have to handle both the Mantine and Nextra dark mode.
+ */
+
+ return (
+ <Group justify="center" mt="xl">
+ <Button
+ onClick={() => {
+ setColorScheme('light');
+ setTheme('light');
+ }}
+ >
+ Light
+ </Button>
+ <Button
+ onClick={() => {
+ setColorScheme('dark');
+ setTheme('dark');
+ }}
+ >
+ Dark
+ </Button>
+ <Button
+ onClick={() => {
+ setColorScheme('auto');
+ setTheme('system');
+ }}
+ >
+ Auto
+ </Button>
+ </Group>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/CommentSection.tsx b/ecosystem/AI-Tooling/beacon/components/CommentSection.tsx
new file mode 100644
index 00000000..df490bb9
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/CommentSection.tsx
@@ -0,0 +1,133 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { useState } from 'react';
+import { IconAlertCircle } from '@tabler/icons-react';
+import { Alert, Button, Paper, Stack, Text, Textarea } from '@mantine/core';
+import { GitHubAuth } from './GitHubAuth';
+
+interface GitHubUser {
+ login: string;
+ avatar_url: string;
+ name: string | null;
+}
+
+interface CommentSectionProps {
+ pageTitle: string;
+ pageUrl: string;
+ repoOwner: string;
+ repoName: string;
+ labels?: string[];
+ title?: string;
+}
+
+export function CommentSection({
+ pageTitle,
+ pageUrl,
+ repoOwner,
+ repoName,
+ labels = ['user-feedback', 'documentation'],
+ title = 'Leave a Comment',
+}: CommentSectionProps) {
+ const [comment, setComment] = useState('');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+ const [success, setSuccess] = useState(false);
+ const [user, setUser] = useState<GitHubUser | null>(null);
+
+ const handleSubmit = async () => {
+ if (!user) {
+ setError('Please sign in with GitHub to submit a comment');
+ return;
+ }
+
+ try {
+ setIsSubmitting(true);
+ setError(null);
+
+ const response = await fetch('/api/create-github-issue', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ title: `Feedback: ${pageTitle}`,
+ body: comment,
+ repoOwner,
+ repoName,
+ labels,
+ }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || 'Failed to create GitHub issue');
+ }
+
+ setSuccess(true);
+ setComment('');
+ } catch (err) {
+ setError(
+ err instanceof Error ? err.message : 'Failed to submit comment. Please try again later.'
+ );
+ console.error('Error creating GitHub issue:', err);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+ <Paper p="md" withBorder>
+ <Stack gap="md">
+ <Text fw={500} size="lg">
+ {title}
+ </Text>
+ <GitHubAuth onAuthStateChange={setUser} returnUrl={pageUrl} />
+ {user && (
+ <>
+ <Textarea
+ required
+ label="Comment"
+ value={comment}
+ onChange={(event) => setComment(event.currentTarget.value)}
+ placeholder="Share your thoughts, suggestions, or report issues..."
+ minRows={3}
+ disabled={isSubmitting}
+ />
+ {error && (
+ <Alert icon={<IconAlertCircle size={16} />} color="red">
+ {error}
+ </Alert>
+ )}
+ {success && (
+ <Alert color="green">
+ Thank you for your feedback! We've created an issue and our team will review it.
+ </Alert>
+ )}
+ <Button onClick={handleSubmit} loading={isSubmitting} disabled={!comment.trim()}>
+ Submit Comment
+ </Button>
+ </>
+ )}
+ </Stack>
+ </Paper>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/Content/Content.story.tsx b/ecosystem/AI-Tooling/beacon/components/Content/Content.story.tsx
new file mode 100644
index 00000000..3e7c4154
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Content/Content.story.tsx
@@ -0,0 +1,28 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import '@gfazioli/mantine-marquee/styles.layer.css';
+
+import { Content } from './Content';
+
+export default {
+ title: 'Content',
+};
+
+export const Usage = () => <Content />;
diff --git a/ecosystem/AI-Tooling/beacon/components/Content/Content.tsx b/ecosystem/AI-Tooling/beacon/components/Content/Content.tsx
new file mode 100644
index 00000000..52ea8a5e
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Content/Content.tsx
@@ -0,0 +1,74 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { Marquee } from '@gfazioli/mantine-marquee';
+import { Anchor } from 'nextra/components';
+import { Button, Divider, Stack, Title } from '@mantine/core';
+
+export const Content = () => {
+ return (
+ <>
+ <Divider my="md" />
+ <Stack align="center" my={32}>
+ <Title order={1} ta="center">
+ Explore Apps and Ecosystem
+ </Title>
+
+ <Anchor href="https://mantine-extensions.vercel.app/">
+ Visit the ResilientDB Hub for more projects
+ </Anchor>
+
+ <Marquee fadeEdges pauseOnHover>
+ <Button
+ size="xl"
+ component="a"
+ href="https://github.com/apache/incubator-resilientdb"
+ target="_blank"
+ >
+ ResilientDB
+ </Button>
+ <Button
+ size="xl"
+ component="a"
+ href="https://github.com/apache/incubator-resilientdb-graphql"
+ target="_blank"
+ >
+ ResilientDB Graphql
+ </Button>
+ <Button
+ size="xl"
+ component="a"
+ href="https://github.com/apache/incubator-resilientdb-resilient-python-cache"
+ target="_blank"
+ >
+ Resilient-Python-Cache
+ </Button>
+ <Button
+ size="xl"
+ component="a"
+ href="https://github.com/harish876/ResLens"
+ target="_blank"
+ >
+ ResLens
+ </Button>
+ </Marquee>
+ </Stack>
+ </>
+ );
+};
diff --git a/ecosystem/AI-Tooling/beacon/components/FloatingAssistant.tsx b/ecosystem/AI-Tooling/beacon/components/FloatingAssistant.tsx
new file mode 100644
index 00000000..d2e666c8
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/FloatingAssistant.tsx
@@ -0,0 +1,832 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { ActionIcon, Tooltip, Paper, Text, Group, Box, Modal, Textarea, Button, Stack, Divider, Avatar, Switch, Badge } from '@mantine/core';
+import { IconBrain, IconSend, IconRobot, IconUser, IconHighlight, IconSettings } from '@tabler/icons-react';
+import { useState, useEffect, ReactNode, useCallback, useRef } from 'react';
+import ReactMarkdown from 'react-markdown';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
+
+interface CodeProps {
+ node?: any;
+ inline?: boolean;
+ className?: string;
+ children?: ReactNode;
+ [key: string]: any;
+}
+
+interface MarkdownComponentProps {
+ children: ReactNode;
+}
+
+export function FloatingAssistant() {
+ const [isOpen, setIsOpen] = useState(false);
+ const [selectedText, setSelectedText] = useState('');
+ const [explanation, setExplanation] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [question, setQuestion] = useState('');
+ const [isAskingQuestion, setIsAskingQuestion] = useState(false);
+ const [debounceTimer, setDebounceTimer] = useState<NodeJS.Timeout | null>(null);
+ const [lastSelection, setLastSelection] = useState('');
+ const [highlightMode, setHighlightMode] = useState(false);
+ const [showSettings, setShowSettings] = useState(false);
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
+ const firstFocusRef = useRef<HTMLTextAreaElement | HTMLButtonElement | null>(null);
+ const lastFocusedRef = useRef<HTMLElement | null>(null);
+ const switchRef = useRef<HTMLInputElement | null>(null);
+
+ // Function to check if element is within a code editor
+ const isWithinCodeEditor = (element: Element | null): boolean => {
+ if (!element) return false;
+
+ // Check if element is within a code editor
+ if (element.closest('.cm-editor') ||
+ element.closest('.CodeMirror') ||
+ element.closest('pre') ||
+ element.closest('code')) {
+ return true;
+ }
+
+ // Check parent elements
+ return isWithinCodeEditor(element.parentElement);
+ };
+
+ // Debounced text selection handler
+ const handleTextSelection = useCallback(() => {
+ // Only handle text selection if highlight mode is enabled
+ if (!highlightMode) return;
+
+ // Clear any existing timer
+ if (debounceTimer) {
+ clearTimeout(debounceTimer);
+ }
+
+ // Set new timer
+ const timer = setTimeout(() => {
+ const selection = window.getSelection();
+ const selectedText = selection?.toString().trim() || '';
+
+ // Don't trigger if:
+ // 1. No text is selected
+ // 2. Selection is within a code editor
+ // 3. Selection is the same as last time
+ if (!selectedText ||
+ isWithinCodeEditor(selection?.anchorNode?.parentElement as Element | null) ||
+ selectedText === lastSelection) {
+ return;
+ }
+
+ setLastSelection(selectedText);
+ setSelectedText(selectedText);
+ setIsOpen(true);
+ getExplanation(selectedText);
+ }, 300); // 300ms debounce delay
+
+ setDebounceTimer(timer);
+ }, [debounceTimer, lastSelection, highlightMode]);
+
+ // Cleanup timer on unmount
+ useEffect(() => {
+ return () => {
+ if (debounceTimer) {
+ clearTimeout(debounceTimer);
+ }
+ };
+ }, [debounceTimer]);
+
+ // Add event listener for text selection
+ useEffect(() => {
+ document.addEventListener('mouseup', handleTextSelection);
+ return () => {
+ document.removeEventListener('mouseup', handleTextSelection);
+ };
+ }, [handleTextSelection]);
+
+ // Reset lastSelection when modal is closed
+ const handleModalClose = () => {
+ setIsOpen(false);
+ setSelectedText('');
+ setExplanation('');
+ setQuestion('');
+ setLastSelection('');
+ // return focus to trigger
+ setTimeout(() => {
+ try {
+ lastFocusedRef.current?.focus?.();
+ } catch (err) {}
+ }, 0);
+ };
+
+ const openModal = (from?: HTMLElement | null) => {
+ lastFocusedRef.current = from ?? (document.activeElement as HTMLElement | null);
+ setIsOpen(true);
+ setTimeout(() => {
+ // focus the textarea input if present
+ const textarea = document.querySelector('textarea[placeholder]') as HTMLTextAreaElement | null;
+ (firstFocusRef.current as HTMLElement | null ?? textarea)?.focus?.();
+ }, 50);
+ };
+
+ // Escape handler while modal is open
+ useEffect(() => {
+ if (!isOpen) return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ handleModalClose();
+ }
+ };
+ document.addEventListener('keydown', onKey);
+ return () => document.removeEventListener('keydown', onKey);
+ }, [isOpen]);
+
+ // Focus the highlight switch when settings panel is opened
+ useEffect(() => {
+ if (!showSettings) return;
+ setTimeout(() => {
+ try {
+ switchRef.current?.focus?.();
+ } catch (err) {}
+ }, 50);
+ }, [showSettings]);
+
+ const getExplanation = async (text: string, customQuestion?: string) => {
+ setIsLoading(true);
+ try {
+ const response = await fetch('/api/chat', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ messages: [
+ {
+ role: 'user',
+ content: customQuestion
+ ? (selectedText
+ ? `Regarding this text: "${selectedText}"\n\n${customQuestion}`
+ : customQuestion)
+ : `Explain the following text or concept in detail:
+${text}
+
+Please:
+1. Break down any complex concepts
+2. Provide examples if relevant
+3. Explain any technical terms
+4. Use markdown formatting for better readability`
+ }
+ ]
+ }),
+ });
+
+ if (!response.ok) throw new Error('Failed to get explanation');
+
+ const reader = response.body?.getReader();
+ if (!reader) throw new Error('No reader available');
+
+ let explanation = '';
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const text = new TextDecoder().decode(value);
+ explanation += text;
+ setExplanation(explanation);
+ }
+ } catch (error) {
+ console.error('Error getting explanation:', error);
+ setExplanation('Sorry, there was an error getting the explanation.');
+ } finally {
+ setIsLoading(false);
+ setIsAskingQuestion(false);
+ }
+ };
+
+ const handleAskQuestion = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!question.trim()) return;
+
+ setIsAskingQuestion(true);
+ getExplanation(selectedText || '', question);
+ setQuestion('');
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ if (question.trim()) {
+ setIsAskingQuestion(true);
+ getExplanation(selectedText || '', question);
+ setQuestion('');
+ }
+ }
+ };
+
+ return (
+ <>
+ <div
+ style={{
+ position: 'fixed',
+ bottom: '20px',
+ right: '20px',
+ zIndex: 1000,
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 12,
+ alignItems: 'flex-end',
+ }}
+ >
+ {/* Settings Toggle */}
+ <Tooltip
+ label="AI Assistant Settings"
+ styles={{
+ tooltip: {
+ background: 'rgba(0,0,0,0.8)',
+ backdropFilter: 'blur(8px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.9)',
+ fontSize: '12px',
+ fontWeight: 400,
+ }
+ }}
+ >
+ <ActionIcon
+ variant="light"
+ size="md"
+ radius="xl"
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ backdropFilter: 'blur(8px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.7)',
+ transition: 'all 200ms ease',
+ }}
+ onClick={() => setShowSettings((s) => !s)}
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ setShowSettings((s) => !s);
+ }
+ }}
+ onFocus={(e) => {
+ // stronger focus ring for keyboard users
+ e.currentTarget.style.boxShadow = '0 0 0 6px rgba(0,191,255,0.18)';
+ e.currentTarget.style.outline = '2px solid rgba(0,191,255,0.18)';
+ }}
+ onBlur={(e) => {
+ e.currentTarget.style.boxShadow = 'none';
+ e.currentTarget.style.outline = 'none';
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.1)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.3)';
+ e.currentTarget.style.color = '#00bfff';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
+ e.currentTarget.style.color = 'rgba(255,255,255,0.7)';
+ }}
+ >
+ <IconSettings size={16} />
+ </ActionIcon>
+ </Tooltip>
+
+ {/* Settings Panel */}
+ {showSettings && (
+ <div
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ backdropFilter: 'blur(12px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ padding: 16,
+ minWidth: 200,
+ position: 'relative',
+ }}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: 1,
+ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
+ borderRadius: '12px 12px 0 0',
+ }}
+ />
+ <Stack gap={12}>
+ <Group justify="space-between" align="center">
+ <Group gap={8}>
+ <IconHighlight size={16} style={{ color: '#00bfff' }} />
+ <Text size="sm" c="rgba(255,255,255,0.9)" fw={500}>
+ Highlight Mode
+ </Text>
+ </Group>
+ <Switch
+ checked={highlightMode}
+ onChange={(event) => setHighlightMode(event.currentTarget.checked)}
+ size="sm"
+ tabIndex={0}
+ // Allow Enter to toggle in addition to Space
+ onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ setHighlightMode((v) => !v);
+ }
+ }}
+ ref={switchRef as any}
+ styles={{
+ track: {
+ backgroundColor: highlightMode ? 'rgba(0, 191, 255, 0.3)' : 'rgba(255,255,255,0.1)',
+ borderColor: highlightMode ? 'rgba(0, 191, 255, 0.5)' : 'rgba(255,255,255,0.2)',
+ },
+ thumb: {
+ backgroundColor: highlightMode ? '#00bfff' : 'rgba(255,255,255,0.6)',
+ },
+ }}
+ />
+ </Group>
+ <Text size="xs" c="rgba(255,255,255,0.6)" style={{ lineHeight: 1.4 }}>
+ {highlightMode
+ ? "Select text to automatically get explanations"
+ : "Click the AI button to ask questions manually"}
+ </Text>
+ {highlightMode && (
+ <Badge
+ size="xs"
+ variant="light"
+ color="blue"
+ style={{
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ color: '#00bfff',
+ }}
+ >
+ Active
+ </Badge>
+ )}
+ </Stack>
+ </div>
+ )}
+
+ {/* Main AI Assistant Button */}
+ <Tooltip
+ label={highlightMode ? "AI Assistant - Select text to get explanations" : "AI Assistant - Click to ask questions"}
+ styles={{
+ tooltip: {
+ background: 'rgba(0,0,0,0.8)',
+ backdropFilter: 'blur(8px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.9)',
+ fontSize: '12px',
+ fontWeight: 400,
+ }
+ }}
+ >
+ <ActionIcon
+ variant="light"
+ size="xl"
+ radius="xl"
+ style={{
+ background: 'rgba(0, 191, 255, 0.1)',
+ backdropFilter: 'blur(12px)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ color: '#00bfff',
+ transition: 'all 200ms ease',
+ position: 'relative',
+ overflow: 'hidden',
+ }}
+ ref={triggerRef}
+ onClick={(e) => openModal(e.currentTarget)}
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ openModal(e.currentTarget as HTMLElement);
+ }
+ }}
+ onFocus={(e) => {
+ e.currentTarget.style.boxShadow = '0 0 0 8px rgba(0,191,255,0.16)';
+ e.currentTarget.style.outline = '2px solid rgba(0,191,255,0.16)';
+ }}
+ onBlur={(e) => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.outline = 'none'; }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.2)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.4)';
+ e.currentTarget.style.transform = 'translateY(-2px)';
+ e.currentTarget.style.boxShadow = '0 8px 20px rgba(0, 191, 255, 0.3)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.1)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.2)';
+ e.currentTarget.style.transform = 'translateY(0)';
+ e.currentTarget.style.boxShadow = 'none';
+ }}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: 1,
+ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',
+ borderRadius: '50% 50% 0 0',
+ }}
+ />
+ <IconBrain size={20} style={{ position: 'relative', zIndex: 1 }} />
+ {highlightMode && (
+ <div
+ style={{
+ position: 'absolute',
+ top: -2,
+ right: -2,
+ width: 8,
+ height: 8,
+ background: '#00ff88',
+ borderRadius: '50%',
+ border: '2px solid rgba(0,0,0,0.8)',
+ }}
+ />
+ )}
+ </ActionIcon>
+ </Tooltip>
+ </div>
+
+ <Modal
+ opened={isOpen}
+ onClose={handleModalClose}
+ size="60vw"
+ styles={{
+ content: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: '80vh',
+ padding: 0,
+ background: 'rgba(0,0,0,0.8)',
+ backdropFilter: 'blur(20px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 16,
+ },
+ body: {
+ flex: 1,
+ padding: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ header: {
+ padding: '20px',
+ borderBottom: '1px solid rgba(255,255,255,0.1)',
+ background: 'rgba(255,255,255,0.02)',
+ backdropFilter: 'blur(8px)',
+ borderRadius: '16px 16px 0 0',
+ },
+ }}
+ title={
+ <Group justify="space-between" w="100%">
+ <Group gap={12}>
+ <div
+ style={{
+ width: 40,
+ height: 40,
+ borderRadius: '50%',
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+ <IconRobot size={20} style={{ color: '#00bfff' }} />
+ </div>
+ <div>
+ <Text size="sm" fw={500} c="rgba(255,255,255,0.9)">
+ AI Assistant
+ </Text>
+ <Text size="xs" c="rgba(255,255,255,0.6)">
+ {highlightMode ? "Highlight mode enabled" : "Manual mode"}
+ </Text>
+ </div>
+ </Group>
+ {selectedText && (
+ <div
+ style={{
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ borderRadius: 8,
+ padding: '8px 12px',
+ maxWidth: '60%',
+ }}
+ >
+ <Text size="xs" c="#00bfff" fw={500}>
+ Selected: "{selectedText.length > 50 ? selectedText.substring(0, 50) + '...' : selectedText}"
+ </Text>
+ </div>
+ )}
+ </Group>
+ }
+ >
+ <Stack style={{ flex: 1, height: '100%' }} p={0}>
+ {selectedText && (
+ <div
+ style={{
+ padding: '16px 20px',
+ background: 'rgba(255,255,255,0.03)',
+ borderBottom: '1px solid rgba(255,255,255,0.1)',
+ backdropFilter: 'blur(8px)',
+ }}
+ >
+ <Group gap={12}>
+ <div
+ style={{
+ width: 32,
+ height: 32,
+ borderRadius: '50%',
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+ <IconUser size={14} style={{ color: 'rgba(255,255,255,0.7)' }} />
+ </div>
+ <Box style={{ flex: 1 }}>
+ <Text size="sm" fw={500} mb={4} c="rgba(255,255,255,0.9)">
+ Selected Text
+ </Text>
+ <Text size="sm" c="rgba(255,255,255,0.7)" style={{ lineHeight: 1.5 }}>
+ {selectedText}
+ </Text>
+ </Box>
+ </Group>
+ </div>
+ )}
+
+ <Box
+ style={{
+ flex: 1,
+ overflowY: 'auto',
+ padding: '1rem',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '1rem',
+ }}
+ >
+ {isLoading ? (
+ <Group align="flex-start" gap={12}>
+ <div
+ style={{
+ width: 32,
+ height: 32,
+ borderRadius: '50%',
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+ <IconRobot size={16} style={{ color: '#00bfff' }} />
+ </div>
+ <div
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ padding: 16,
+ maxWidth: '80%',
+ backdropFilter: 'blur(8px)',
+ }}
+ >
+ <Text size="sm" c="rgba(255,255,255,0.7)">
+ Generating response...
+ </Text>
+ </div>
+ </Group>
+ ) : explanation ? (
+ <Group align="flex-start" gap={12}>
+ <div
+ style={{
+ width: 32,
+ height: 32,
+ borderRadius: '50%',
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+ <IconRobot size={16} style={{ color: '#00bfff' }} />
+ </div>
+ <div
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ padding: 16,
+ maxWidth: '80%',
+ backdropFilter: 'blur(8px)',
+ }}
+ >
+ <ReactMarkdown
+ components={{
+ code({ node, inline, className, children, ...props }: CodeProps) {
+ const match = /language-(\w+)/.exec(className || '');
+ return !inline && match ? (
+ <SyntaxHighlighter
+ style={vscDarkPlus}
+ language={match[1]}
+ PreTag="div"
+ {...props}
+ >
+ {String(children).replace(/\n$/, '')}
+ </SyntaxHighlighter>
+ ) : (
+ <code className={className} {...props}>
+ {children}
+ </code>
+ );
+ },
+ p: ({ children }: MarkdownComponentProps) => (
+ <Text size="sm" mb="md" c="rgba(255,255,255,0.9)" style={{ whiteSpace: 'pre-wrap', lineHeight: 1.6 }}>
+ {children}
+ </Text>
+ ),
+ ul: ({ children }: MarkdownComponentProps) => (
+ <Box component="ul" mb="md" style={{ paddingLeft: '1.5rem' }}>
+ {children}
+ </Box>
+ ),
+ ol: ({ children }: MarkdownComponentProps) => (
+ <Box component="ol" mb="md" style={{ paddingLeft: '1.5rem' }}>
+ {children}
+ </Box>
+ ),
+ li: ({ children }: MarkdownComponentProps) => (
+ <Text size="sm" component="li" mb="xs" c="rgba(255,255,255,0.9)">
+ {children}
+ </Text>
+ ),
+ h1: ({ children }: MarkdownComponentProps) => (
+ <Text size="xl" fw={700} mb="md" c="rgba(255,255,255,0.9)">
+ {children}
+ </Text>
+ ),
+ h2: ({ children }: MarkdownComponentProps) => (
+ <Text size="lg" fw={600} mb="md" c="rgba(255,255,255,0.9)">
+ {children}
+ </Text>
+ ),
+ h3: ({ children }: MarkdownComponentProps) => (
+ <Text size="md" fw={600} mb="md" c="rgba(255,255,255,0.9)">
+ {children}
+ </Text>
+ ),
+ blockquote: ({ children }: MarkdownComponentProps) => (
+ <div
+ style={{
+ padding: 12,
+ marginBottom: 16,
+ background: 'rgba(0, 191, 255, 0.05)',
+ border: '1px solid rgba(0, 191, 255, 0.1)',
+ borderLeft: '4px solid #00bfff',
+ borderRadius: 8,
+ }}
+ >
+ {children}
+ </div>
+ ),
+ }}
+ >
+ {explanation}
+ </ReactMarkdown>
+ </div>
+ </Group>
+ ) : (
+ <Group align="flex-start" gap={12}>
+ <div
+ style={{
+ width: 32,
+ height: 32,
+ borderRadius: '50%',
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+ <IconRobot size={16} style={{ color: '#00bfff' }} />
+ </div>
+ <div
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ padding: 16,
+ maxWidth: '80%',
+ backdropFilter: 'blur(8px)',
+ }}
+ >
+ <Text size="sm" c="rgba(255,255,255,0.7)">
+ {selectedText
+ ? "Select text to get an explanation"
+ : "Ask any question or select text to get an explanation"}
+ </Text>
+ </div>
+ </Group>
+ )}
+ </Box>
+
+ <div
+ style={{
+ padding: '20px',
+ borderTop: '1px solid rgba(255,255,255,0.1)',
+ background: 'rgba(255,255,255,0.02)',
+ backdropFilter: 'blur(8px)',
+ }}
+ >
+ <form onSubmit={handleAskQuestion}>
+ <Group gap={12}>
+ <Textarea
+ ref={firstFocusRef as any}
+ placeholder={selectedText
+ ? "Ask a specific question about the selected text..."
+ : "Ask any question..."}
+ value={question}
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setQuestion(e.target.value)}
+ onKeyDown={handleKeyDown}
+ style={{ flex: 1 }}
+ disabled={isLoading}
+ autosize
+ minRows={1}
+ maxRows={3}
+ styles={{
+ input: {
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.9)',
+ borderRadius: 12,
+ '&:focus': {
+ borderColor: 'rgba(0, 191, 255, 0.5)',
+ background: 'rgba(255,255,255,0.08)',
+ },
+ '&::placeholder': {
+ color: 'rgba(255,255,255,0.5)',
+ }
+ }
+ }}
+ />
+ <Button
+ type="submit"
+ loading={isAskingQuestion}
+ disabled={!question.trim()}
+ leftSection={<IconSend size={14} />}
+ style={{
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.3)',
+ color: '#00bfff',
+ borderRadius: 12,
+ transition: 'all 200ms ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.2)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.5)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.1)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.3)';
+ }}
+ >
+ Send
+ </Button>
+ </Group>
+ </form>
+ </div>
+ </Stack>
+ </Modal>
+ </>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/GitHubAuth.tsx b/ecosystem/AI-Tooling/beacon/components/GitHubAuth.tsx
new file mode 100644
index 00000000..3beb28b7
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/GitHubAuth.tsx
@@ -0,0 +1,182 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Button, Text, Group, Avatar } from '@mantine/core';
+import { IconBrandGithub } from '@tabler/icons-react';
+import { signIn } from 'next-auth/react';
+
+interface GitHubUser {
+ login: string;
+ avatar_url: string;
+ name: string | null;
+}
+
+interface GitHubAuthProps {
+ onAuthStateChange: (user: GitHubUser | null) => void;
+ returnUrl?: string;
+}
+
+export function GitHubAuth({ onAuthStateChange, returnUrl }: GitHubAuthProps) {
+ const [user, setUser] = useState<GitHubUser | null>(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ // Check if user is already authenticated
+ checkAuthStatus();
+ }, []);
+
+ const checkAuthStatus = async () => {
+ try {
+ const response = await fetch('/api/auth/session');
+ const data = await response.json();
+
+ if (data.user) {
+ setUser(data.user);
+ onAuthStateChange(data.user);
+ }
+ } catch (error) {
+ console.error('Error checking auth status:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSignIn = () => {
+ signIn('github', { callbackUrl: returnUrl || window.location.href });
+ };
+
+ const handleSignOut = async () => {
+ try {
+ await fetch('/api/auth/signout', { method: 'POST' });
+ setUser(null);
+ onAuthStateChange(null);
+ } catch (error) {
+ console.error('Error signing out:', error);
+ }
+ };
+
+ if (loading) {
+ return null;
+ }
+
+ if (user) {
+ return (
+ <div
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ gap: 12,
+ padding: 12,
+ background: 'rgba(255,255,255,0.05)',
+ backdropFilter: 'blur(8px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ }}
+ >
+ <Avatar
+ src={user.avatar_url}
+ alt={user.login}
+ radius="xl"
+ style={{
+ border: '2px solid rgba(255,255,255,0.2)',
+ }}
+ />
+ <div style={{ flex: 1 }}>
+ <Text size="sm" c="rgba(255,255,255,0.9)" fw={400}>
+ Signed in as <span style={{ color: '#00bfff', fontWeight: 500 }}>{user.name || user.login}</span>
+ </Text>
+ </div>
+ <Button
+ onClick={handleSignOut}
+ size="xs"
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.7)',
+ borderRadius: 8,
+ transition: 'all 200ms ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255, 77, 77, 0.1)';
+ e.currentTarget.style.borderColor = 'rgba(255, 77, 77, 0.3)';
+ e.currentTarget.style.color = '#ff4d4d';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
+ e.currentTarget.style.color = 'rgba(255,255,255,0.7)';
+ }}
+ >
+ Sign Out
+ </Button>
+ </div>
+ );
+ }
+
+ return (
+ <Button
+ leftSection={<IconBrandGithub size={16} />}
+ onClick={handleSignIn}
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ backdropFilter: 'blur(8px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.9)',
+ borderRadius: 12,
+ padding: '12px 20px',
+ fontWeight: 400,
+ transition: 'all 200ms ease',
+ position: 'relative',
+ overflow: 'hidden',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.1)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.3)';
+ e.currentTarget.style.color = '#00bfff';
+ e.currentTarget.style.transform = 'translateY(-1px)';
+ e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 191, 255, 0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateY(0)';
+ e.currentTarget.style.boxShadow = 'none';
+ }}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: 1,
+ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
+ borderRadius: '12px 12px 0 0',
+ }}
+ />
+ <span style={{ position: 'relative', zIndex: 1 }}>
+ Sign in with GitHub to Comment
+ </span>
+ </Button>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/InstallationForm.tsx b/ecosystem/AI-Tooling/beacon/components/InstallationForm.tsx
new file mode 100644
index 00000000..72581af0
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/InstallationForm.tsx
@@ -0,0 +1,128 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+"use client"
+
+import { useState } from 'react';
+import styles from '../styles/installation.module.css';
+
+const installCommands = {
+ resilientdb: `
+# Install ResilientDB
+echo "Installing ResilientDB..."
+git clone https://github.com/apache/incubator-resilientdb.git /opt/incubator-resilientdb
+cd /opt/incubator-resilientdb
+./INSTALL.sh
+./service/tools/kv/server_tools/start_kv_service.sh
+bazel build service/tools/kv/api_tools/kv_service_tools
+`,
+ pythonsdk: `
+# Install PythonSDK
+echo "Installing PythonSDK..."
+git clone https://github.com/apache/incubator-resilientdb-python-sdk.git /opt/incubator-resilientdb-python-sdk
+cd /opt/incubator-resilientdb-python-sdk
+sh ./INSTALL.sh
+python3.10 -m venv venv
+source venv/bin/activate
+pip install -r requirements.txt
+bazel build service/http_server/crow_service_main
+bazel-bin/service/http_server/crow_service_main service/tools/config/interface/client.config service/http_server/server_config.config
+deactivate
+`,
+ resdborm: `
+# Install ResDBORM
+echo "Installing ResDBORM..."
+git clone https://github.com/ResilientEcosystem/ResDB-ORM.git /opt/ResDB-ORM
+cd /opt/ResDB-ORM
+./INSTALL.sh
+pip install resdb-orm
+`,
+ "smartcontracts-cli": `
+# Install Smart-Contracts CLI
+echo "Installing Smart-Contracts CLI..."
+npm install -g rescontract-cli
+`,
+ "smartcontracts-graphql": `
+# Install Smart-Contracts GraphQL
+echo "Installing Smart-Contracts GraphQL..."
+git clone https://github.com/ResilientEcosystem/smart-contracts-graphql.git /opt/smart-contracts-graphql
+cd /opt/smart-contracts-graphql
+npm install
+`,
+ resvault: `
+# Install ResVault
+echo "Installing ResVault..."
+git clone https://github.com/apache/incubator-resilientdb-resvault.git /opt/ResVault
+cd /opt/ResVault
+npm install
+npm run build
+`
+};
+
+export function InstallationForm() {
+ const [error, setError] = useState('');
+
+ const handleGenerateScript = () => {
+ const selectedApps = Array.from(document.querySelectorAll('input[name="app"]:checked')).map(cb => (cb as HTMLInputElement).value);
+ if (selectedApps.length === 0) {
+ setError("Please select at least one application to install.");
+ return;
+ }
+
+ let scriptContent = "#!/bin/bash\n\n# Check for root privileges\nif [ \"$EUID\" -ne 0 ]; then\n echo \"Please run as root. Try using 'sudo ./INSTALL.sh'\"\n exit\nfi\n\n";
+ scriptContent += "echo \"Updating package lists...\"\napt-get update\n";
+ scriptContent += "echo \"Installing dependencies...\"\napt-get install -y git curl build-essential python3 python3-venv python3-pip npm solc\n\n";
+
+ selectedApps.forEach(app => {
+ scriptContent += installCommands[app as keyof typeof installCommands];
+ });
+
+ scriptContent += "\necho \"Installation complete. Please refer to the documentation for usage instructions.\"\n";
+
+ const blob = new Blob([scriptContent], { type: "text/plain" });
+ const link = document.getElementById("download-link");
+ if (link) {
+ link.setAttribute('href', URL.createObjectURL(blob));
+ link.setAttribute('download', "INSTALL.sh");
+ link.style.display = "block";
+ link.textContent = "Download your custom INSTALL.sh";
+ }
+ setError('');
+ };
+
+ return (
+ <div>
+ <form id="install-form">
+ <label><input type="checkbox" name="app" value="resilientdb" /> ResilientDB</label><br/>
+ <label><input type="checkbox" name="app" value="pythonsdk" /> PythonSDK</label><br/>
+ <label><input type="checkbox" name="app" value="resdborm" /> ResDBORM</label><br/>
+ <label><input type="checkbox" name="app" value="smartcontracts-cli" /> Smart-Contracts CLI</label><br/>
+ <label><input type="checkbox" name="app" value="smartcontracts-graphql" /> Smart-Contracts GraphQL</label><br/>
+ <label><input type="checkbox" name="app" value="resvault" /> ResVault</label><br/>
+ {error && <p style={{ color: 'red' }}>{error}</p>}
+ <button type="button" className={styles.generateButton} onClick={handleGenerateScript}>
+ Generate INSTALL.sh
+ </button>
+ </form>
+ <a id="download-link" className={styles.downloadButton} style={{ display: 'none' }}>
+ Download your custom INSTALL.sh
+ </a>
+ </div>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/Logo/Logo.story.tsx b/ecosystem/AI-Tooling/beacon/components/Logo/Logo.story.tsx
new file mode 100644
index 00000000..1b3007a3
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Logo/Logo.story.tsx
@@ -0,0 +1,26 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { Logo } from './Logo';
+
+export default {
+ title: 'Logo',
+};
+
+export const Usage = () => <Logo />;
diff --git a/ecosystem/AI-Tooling/beacon/components/Logo/Logo.tsx b/ecosystem/AI-Tooling/beacon/components/Logo/Logo.tsx
new file mode 100644
index 00000000..493972a8
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Logo/Logo.tsx
@@ -0,0 +1,24 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import Image from 'next/image';
+
+export function Logo() {
+ return <Image src="/lighthouse.png" alt="Logo" width={48} height={48} style={{ marginRight: '12px' }} />;
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/MantineFooter/MantineFooter.tsx b/ecosystem/AI-Tooling/beacon/components/MantineFooter/MantineFooter.tsx
new file mode 100644
index 00000000..37b98396
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/MantineFooter/MantineFooter.tsx
@@ -0,0 +1,384 @@
+'use client';
+
+/**
+ * Sleek footer component matching the landing page design language
+ * Features glassmorphism effects, subtle animations, and modern styling
+ * Uses regular HTML/React components for full width stretching
+ *
+ * @since 1.0.0
+ */
+export const MantineFooter = () => (
+ <footer
+ style={{
+ position: 'relative',
+ marginTop: 48,
+ width: '100vw',
+ left: '50%',
+ right: '50%',
+ marginLeft: '-50vw',
+ marginRight: '-50vw',
+ padding: '32px 24px',
+ background: 'rgba(255, 255, 255, 0.02)',
+ backdropFilter: 'blur(10px)',
+ borderTop: '1px solid rgba(255, 255, 255, 0.08)',
+ borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
+ }}
+ >
+ <div style={{ maxWidth: '1200px', margin: '0 auto' }}>
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
+ {/* Main footer content */}
+ <div style={{
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start',
+ width: '100%',
+ gap: 32,
+ flexWrap: 'wrap'
+ }}>
+ {/* Left section - Brand */}
+ <div style={{ flex: 1, minWidth: 200 }}>
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+ <svg
+ fill="currentColor"
+ viewBox="0 0 120 32"
+ xmlns="http://www.w3.org/2000/svg"
+ style={{ width: 100, height: 24, color: 'rgba(255,255,255,0.9)' }}
+ >
+ <path d="M8 4 L2 18 L8 28 L14 18 Z" fill="currentColor" opacity="0.9"/>
+ <path d="M8 6 L4 18 L8 26 L12 18 Z" fill="currentColor" opacity="0.6"/>
+ <circle cx="8" cy="18" r="1.5" fill="currentColor" opacity="0.8"/>
+ <text x="20" y="22" fontSize="16" fontWeight="600" fill="currentColor" fontFamily="system-ui, -apple-system, sans-serif">
+ Beacon
+ </text>
+ </svg>
+ </div>
+ <p style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ lineHeight: 1.5,
+ maxWidth: 280,
+ margin: 0
+ }}>
+ Next generation documentation for ResilientDB ecosystem.
+ Your gateway to high-performance blockchain infrastructure.
+ </p>
+ </div>
+ </div>
+
+ {/* Center section - Links */}
+ <div style={{
+ flex: 1,
+ display: 'flex',
+ justifyContent: 'center',
+ gap: 32,
+ flexWrap: 'wrap'
+ }}>
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
+ <h4 style={{
+ fontSize: 14,
+ fontWeight: 500,
+ color: 'white',
+ margin: '0 0 4px 0'
+ }}>
+ Documentation
+ </h4>
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
+ <a
+ href="/docs"
+ style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ display: 'block',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateX(4px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ e.currentTarget.style.transform = 'translateX(0)';
+ }}
+ >
+ Getting Started
+ </a>
+ <a
+ href="/docs/installation"
+ style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ display: 'block',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateX(4px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ e.currentTarget.style.transform = 'translateX(0)';
+ }}
+ >
+ Installation
+ </a>
+ <a
+ href="/docs/resilientdb"
+ style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ display: 'block',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateX(4px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ e.currentTarget.style.transform = 'translateX(0)';
+ }}
+ >
+ ResilientDB
+ </a>
+ </div>
+ </div>
+
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
+ <h4 style={{
+ fontSize: 14,
+ fontWeight: 500,
+ color: 'white',
+ margin: '0 0 4px 0'
+ }}>
+ Community
+ </h4>
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
+ <a
+ href="https://resilientdb.incubator.apache.org/"
+ target="_blank"
+ rel="noopener noreferrer"
+ style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ display: 'block',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateX(4px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ e.currentTarget.style.transform = 'translateX(0)';
+ }}
+ >
+ Official Site
+ </a>
+ <a
+ href="https://blog.resilientdb.com"
+ target="_blank"
+ rel="noopener noreferrer"
+ style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ display: 'block',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateX(4px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ e.currentTarget.style.transform = 'translateX(0)';
+ }}
+ >
+ Blog
+ </a>
+ <a
+ href="/docs/about"
+ style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ display: 'block',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ e.currentTarget.style.transform = 'translateX(4px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ e.currentTarget.style.transform = 'translateX(0)';
+ }}
+ >
+ About
+ </a>
+ </div>
+ </div>
+ </div>
+
+ {/* Right section - Tech stack */}
+ <div style={{
+ flex: 1,
+ minWidth: 200,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'flex-end',
+ gap: 12
+ }}>
+ <h4 style={{
+ fontSize: 14,
+ fontWeight: 500,
+ color: 'white',
+ margin: 0
+ }}>
+ Built with
+ </h4>
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
+ <div
+ style={{
+ padding: '4px 8px',
+ borderRadius: 6,
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ fontSize: 11,
+ color: 'rgba(255,255,255,0.7)',
+ transition: 'all 200ms ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.1)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
+ }}
+ >
+ Next.js
+ </div>
+ <div
+ style={{
+ padding: '4px 8px',
+ borderRadius: 6,
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ fontSize: 11,
+ color: 'rgba(255,255,255,0.7)',
+ transition: 'all 200ms ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.1)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
+ }}
+ >
+ Mantine
+ </div>
+ <div
+ style={{
+ padding: '4px 8px',
+ borderRadius: 6,
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ fontSize: 11,
+ color: 'rgba(255,255,255,0.7)',
+ transition: 'all 200ms ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.1)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
+ e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
+ }}
+ >
+ Nextra
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* Divider */}
+ <div
+ style={{
+ height: 1,
+ margin: '8px 0',
+ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent)',
+ }}
+ />
+
+ {/* Bottom section - Copyright */}
+ <div style={{
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ width: '100%',
+ flexWrap: 'wrap',
+ gap: 16
+ }}>
+ <p style={{
+ fontSize: 14,
+ color: 'rgba(255,255,255,0.6)',
+ margin: 0
+ }}>
+ Apache {new Date().getFullYear()} ©{' '}
+ <a
+ href="https://resilientdb.incubator.apache.org/"
+ style={{
+ color: 'rgba(255,255,255,0.6)',
+ transition: 'all 200ms ease',
+ textDecoration: 'none',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.9)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.color = 'rgba(255,255,255,0.6)';
+ }}
+ >
+ Apache Software Foundation
+ </a>
+ </p>
+
+ <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
+ <span style={{
+ fontSize: 12,
+ color: 'rgba(255,255,255,0.6)',
+ opacity: 0.7
+ }}>
+ Licensed under Apache 2.0
+ </span>
+ <div
+ style={{
+ width: 4,
+ height: 4,
+ borderRadius: '50%',
+ background: 'rgba(255,255,255,0.3)',
+ opacity: 0.5,
+ }}
+ />
+ <span style={{
+ fontSize: 12,
+ color: 'rgba(255,255,255,0.6)',
+ opacity: 0.7
+ }}>
+ Incubating Project
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </footer>
+);
diff --git a/ecosystem/AI-Tooling/beacon/components/MantineNavBar/MantineNavBar.tsx b/ecosystem/AI-Tooling/beacon/components/MantineNavBar/MantineNavBar.tsx
new file mode 100644
index 00000000..83ebb0d3
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/MantineNavBar/MantineNavBar.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { Box } from '@mantine/core';
+import { usePathname } from 'next/navigation';
+
+import '@mantine/core/styles.css';
+import { MantineNextraThemeObserver } from '../MantineNextraThemeObserver/MantineNextraThemeObserver';
+import Header from '@/components/landing/Header';
+
+/**
+ * You can customize the Nextra NavBar component.
+ * Don't forget to use the MantineProvider and MantineNextraThemeObserver components.
+ *
+ * @since 1.0.0
+ *
+ */
+export const MantineNavBar = () => {
+ const pathname = usePathname();
+ if (pathname === '/') return null;
+ return (
+ <>
+ <MantineNextraThemeObserver />
+ <Box>
+ <Header />
+ </Box>
+ </>
+ );
+};
diff --git a/ecosystem/AI-Tooling/beacon/components/MantineNextraThemeObserver/MantineNextraThemeObserver.tsx b/ecosystem/AI-Tooling/beacon/components/MantineNextraThemeObserver/MantineNextraThemeObserver.tsx
new file mode 100644
index 00000000..d68b170d
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/MantineNextraThemeObserver/MantineNextraThemeObserver.tsx
@@ -0,0 +1,43 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { useTheme } from 'nextra-theme-docs';
+import { useMantineColorScheme } from '@mantine/core';
+import { useDidUpdate } from '@mantine/hooks';
+
+/**
+ * This component is responsible for observing the theme changes in Nextra and Mantine.
+ * By using this component, you can ensure that the Mantine theme is always in sync with the Nextra theme.
+ *
+ * This component is used in the MantineNavBar component.
+ *
+ * @since 1.0.0
+ *
+ * @see https://mantine.dev/docs/color-scheme/
+ */
+export function MantineNextraThemeObserver() {
+ const { setColorScheme } = useMantineColorScheme();
+ const { theme } = useTheme();
+
+ useDidUpdate(() => {
+ setColorScheme(theme === 'dark' ? 'dark' : theme === 'system' ? 'auto' : 'light');
+ }, [theme]);
+
+ return null;
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/PocketFlowChatbot.tsx b/ecosystem/AI-Tooling/beacon/components/PocketFlowChatbot.tsx
new file mode 100644
index 00000000..5e8944f3
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/PocketFlowChatbot.tsx
@@ -0,0 +1,130 @@
+'use client';
+
+import { useEffect, useRef } from 'react';
+
+declare global {
+ interface Window {
+ initializeChatbot: (config: any) => void;
+ }
+}
+
+interface PocketFlowChatbotProps {
+ extraUrls?: string[];
+ prefixes?: string[];
+ chatbotName?: string;
+ wsUrl?: string;
+ instruction?: string;
+ isOpen?: boolean;
+}
+
+export function PocketFlowChatbot({
+ extraUrls = ["https://beacon.resilientdb.com/docs"],
+ prefixes = [],
+ chatbotName = 'Beacon',
+ wsUrl = 'wss://askthispage.com/api/ws/chat',
+ instruction = '',
+ isOpen = false
+}: PocketFlowChatbotProps) {
+ const containerRef = useRef<HTMLDivElement>(null);
+
+ useEffect(() => {
+ // Check if the script is already loaded
+ if (document.querySelector('script[src="https://askthispage.com/embed/chatbot.js"]')) {
+ return;
+ }
+
+ const script = document.createElement("script");
+ script.src = "https://askthispage.com/embed/chatbot.js";
+ script.onload = function() {
+ if (window.initializeChatbot) {
+ window.initializeChatbot({
+ extra_urls: extraUrls,
+ prefixes: prefixes,
+ chatbotName: chatbotName,
+ wsUrl: wsUrl,
+ instruction: instruction,
+ isOpen: isOpen
+ });
+ }
+ };
+ document.head.appendChild(script);
+
+ // Cleanup function
+ return () => {
+ const existingScript = document.querySelector('script[src="https://askthispage.com/embed/chatbot.js"]');
+ if (existingScript) {
+ existingScript.remove();
+ }
+ };
+ }, [extraUrls, prefixes, chatbotName, wsUrl, instruction, isOpen]);
+
+ // Add custom styles after the chatbot loads
+ useEffect(() => {
+ const checkAndStyle = () => {
+ const chatbot = document.querySelector('.askthispage-chatbot') as HTMLElement;
+ if (chatbot) {
+ // Move to left corner
+ chatbot.style.position = 'fixed';
+ chatbot.style.left = '20px';
+ chatbot.style.right = 'auto';
+ chatbot.style.bottom = '20px';
+ chatbot.style.zIndex = '1000';
+
+ // Add experimental badge
+ const badge = document.createElement('div');
+ badge.innerHTML = '🧪 Experimental';
+ badge.style.cssText = `
+ position: absolute;
+ top: -30px;
+ left: 0;
+ background: linear-gradient(45deg, #ff6b6b, #ffa500);
+ color: white;
+ padding: 4px 8px;
+ border-radius: 12px;
+ font-size: 10px;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
+ z-index: 1001;
+ animation: pulse 2s infinite;
+ white-space: nowrap;
+ `;
+
+ // Add pulse animation
+ const style = document.createElement('style');
+ style.textContent = `
+ @keyframes pulse {
+ 0% { opacity: 1; }
+ 50% { opacity: 0.7; }
+ 100% { opacity: 1; }
+ }
+ `;
+ document.head.appendChild(style);
+
+ chatbot.appendChild(badge);
+ }
+ };
+
+ // Check immediately and then periodically
+ checkAndStyle();
+ const interval = setInterval(checkAndStyle, 1000);
+
+ return () => {
+ clearInterval(interval);
+ };
+ }, []);
+
+ return (
+ <div
+ ref={containerRef}
+ style={{
+ position: 'fixed',
+ left: '20px',
+ bottom: '20px',
+ zIndex: 1000,
+ pointerEvents: 'none'
+ }}
+ />
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/PythonIDE.tsx b/ecosystem/AI-Tooling/beacon/components/PythonIDE.tsx
new file mode 100644
index 00000000..7bd8a2a9
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/PythonIDE.tsx
@@ -0,0 +1,114 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+"use client"
+import { useEffect, useRef, useState } from "react"
+import { loadPyodideInstance } from "@/components/utils/loadPyodide"
+
+export default function PythonSplitIDE({ defaultCode }: { defaultCode: string }) {
+ const [pyodide, setPyodide] = useState<any>(null)
+ const [output, setOutput] = useState<string[]>(["Initializing Python..."])
+ const [isLoading, setIsLoading] = useState(true)
+ const [isRunning, setIsRunning] = useState(false)
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
+
+ useEffect(() => {
+ async function init() {
+ try {
+ const py = await loadPyodideInstance()
+
+ py.globals.set("js_print", (text: string) => {
+ setOutput(prev => [...prev, text])
+ })
+
+ await py.runPythonAsync(`import builtins\nbuiltins.print = js_print`)
+
+ const sdk = await fetch("/resdb_sdk.py").then(res => res.text())
+ await py.runPythonAsync(sdk)
+
+ setPyodide(py)
+ setOutput(["✅ Python environment loaded."])
+ setIsLoading(false)
+ } catch (e) {
+ console.error(e)
+ setOutput(["❌ Failed to initialize Python."])
+ setIsLoading(false)
+ }
+ }
+
+ init()
+ }, [])
+
+ const runCode = async () => {
+ if (!pyodide || !textareaRef.current) return
+ setIsRunning(true)
+ setOutput(prev => [...prev, "\n▶ Running code..."])
+
+ try {
+ const code = textareaRef.current.value
+ await pyodide.runPythonAsync(code)
+ setOutput(prev => [...prev, "✅ Code execution completed."])
+ } catch (err: any) {
+ setOutput(prev => [...prev, `❌ Error: ${err.message}`])
+ } finally {
+ setIsRunning(false)
+ }
+ }
+
+ return (
+ <div className="flex flex-col md:flex-row gap-4 w-full my-6">
+ {/* Left side: Editor */}
+ <div className="flex-1 bg-black rounded-lg p-4 border border-slate-700">
+ <textarea
+ ref={textareaRef}
+ defaultValue={defaultCode}
+ className="w-full h-[400px] bg-black text-green-200 font-mono text-sm p-4 rounded resize-none border border-slate-600"
+ placeholder="Write your Python code here..."
+ disabled={isLoading}
+ />
+ <div className="mt-4 flex gap-2">
+ <button
+ onClick={runCode}
+ disabled={isLoading || isRunning}
+ className="px-4 py-2 rounded bg-blue-600 text-white text-sm hover:bg-blue-700"
+ >
+ {isRunning ? "Running..." : "Run Code"}
+ </button>
+ <button
+ onClick={() => setOutput([])}
+ className="px-4 py-2 rounded bg-slate-700 text-white text-sm hover:bg-slate-600"
+ >
+ Clear Output
+ </button>
+ </div>
+ </div>
+
+ {/* Right side: Output */}
+ <div className="flex-1 bg-slate-900 text-green-400 font-mono text-sm p-4 border border-slate-700 rounded-lg overflow-auto h-[400px]">
+ {output.length === 0 ? (
+ <div className="text-slate-500">No output yet.</div>
+ ) : (
+ output.map((line, i) => <div key={i}>{line}</div>)
+ )}
+ {isLoading && <div className="text-slate-400">⏳ Loading Python...</div>}
+ {isRunning && <div className="text-slate-400">⚙️ Running...</div>}
+ </div>
+ </div>
+ )
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/PythonPlayground.tsx b/ecosystem/AI-Tooling/beacon/components/PythonPlayground.tsx
new file mode 100644
index 00000000..1a063f5e
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/PythonPlayground.tsx
@@ -0,0 +1,773 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { useEffect, useState, useRef } from 'react';
+import { python } from '@codemirror/lang-python';
+import { IconPlayerPlay, IconTemplate, IconTrash, IconMessage, IconPlayerStop, IconMaximize, IconMinimize } from '@tabler/icons-react';
+import { vscodeDark } from '@uiw/codemirror-theme-vscode';
+import CodeMirror from '@uiw/react-codemirror';
+import { Box, Button, Group, Paper, Select, Stack, Text, Tooltip, Textarea, ActionIcon, Modal } from '@mantine/core';
+import ReactMarkdown from 'react-markdown';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
+
+declare global {
+ interface Window {
+ loadPyodide: (options: { indexURL: string }) => Promise<any>;
+ pyodide: any;
+ }
+}
+
+interface Template {
+ value: string;
+ label: string;
+ code: string;
+}
+
+interface Message {
+ role: 'user' | 'assistant';
+ content: string;
+}
+
+interface CodeProps {
+ node?: any;
+ inline?: boolean;
+ className?: string;
+ children?: React.ReactNode;
+ [key: string]: any;
+}
+
+const EXAMPLE_TEMPLATES = [
+ {
+ value: 'default',
+ label: 'Instructions',
+ code: `
+ """
+ Instructions
+ """
+ `,
+ },
+ {
+ value: 'solution',
+ label: 'Solution',
+ code: `
+ """
+ Solution
+ """
+`,
+ },
+];
+
+export function PythonPlayground({ templates }: { templates?: Template[] }) {
+ const codeTemplates = templates && Array.isArray(templates) ? templates : EXAMPLE_TEMPLATES;
+ const [code, setCode] = useState<string>(codeTemplates[0].code);
+ const [output, setOutput] = useState<string[]>(['Initializing Python environment...']);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isRunning, setIsRunning] = useState(false);
+ const [showChat, setShowChat] = useState(false);
+ const [messages, setMessages] = useState<Message[]>([]);
+ const [input, setInput] = useState('');
+ const [isAiLoading, setIsAiLoading] = useState(false);
+ const [abortController, setAbortController] = useState<AbortController | null>(null);
+ const [isExpanded, setIsExpanded] = useState(false);
+ const expandTriggerRef = useRef<HTMLButtonElement | null>(null);
+ const lastFocusedRef = useRef<HTMLElement | null>(null);
+
+ const openExpanded = (from?: HTMLElement | null) => {
+ lastFocusedRef.current = from ?? (document.activeElement as HTMLElement | null);
+ setIsExpanded(true);
+ setTimeout(() => {
+ const ta = document.querySelector('textarea') as HTMLElement | null;
+ ta?.focus?.();
+ }, 50);
+ };
+
+ const closeExpanded = () => {
+ setIsExpanded(false);
+ setTimeout(() => {
+ try { lastFocusedRef.current?.focus?.(); } catch (err) {}
+ }, 0);
+ };
+
+ useEffect(() => {
+ if (!isExpanded) return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ closeExpanded();
+ }
+ };
+ document.addEventListener('keydown', onKey);
+ return () => document.removeEventListener('keydown', onKey);
+ }, [isExpanded]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!input.trim()) return;
+
+ const userMessage: Message = { role: 'user', content: input };
+ setMessages(prev => [...prev, userMessage]);
+ setInput('');
+ setIsAiLoading(true);
+
+ // Create new AbortController for this request
+ const controller = new AbortController();
+ setAbortController(controller);
+
+ try {
+ const response = await fetch('/api/chat', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages: [...messages, userMessage], code }),
+ signal: controller.signal
+ });
+
+ if (!response.ok) throw new Error('Failed to get response');
+
+ const reader = response.body?.getReader();
+ if (!reader) throw new Error('No reader available');
+
+ let assistantMessage = '';
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const text = new TextDecoder().decode(value);
+ assistantMessage += text;
+ setMessages(prev => {
+ const newMessages = [...prev];
+ const lastMessage = newMessages[newMessages.length - 1];
+ if (lastMessage.role === 'assistant') {
+ lastMessage.content = assistantMessage;
+ } else {
+ newMessages.push({ role: 'assistant', content: assistantMessage });
+ }
+ return newMessages;
+ });
+ }
+ } catch (error) {
+ if (error instanceof Error && error.name === 'AbortError') {
+ setMessages(prev => [...prev, { role: 'assistant', content: 'Response generation stopped.' }]);
+ } else {
+ console.error('Chat error:', error);
+ setMessages(prev => [...prev, { role: 'assistant', content: 'Sorry, there was an error processing your request.' }]);
+ }
+ } finally {
+ setIsAiLoading(false);
+ setAbortController(null);
+ }
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ if (input.trim() && !isAiLoading) {
+ handleSubmit(e as unknown as React.FormEvent);
+ }
+ }
+ };
+
+ const stopGeneration = () => {
+ if (abortController) {
+ abortController.abort();
+ setAbortController(null);
+ }
+ };
+
+ useEffect(() => {
+ const initPython = async () => {
+ try {
+ if (window.pyodide) {
+ setOutput(['✅ Python environment ready']);
+ setIsLoading(false);
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js';
+ document.head.appendChild(script);
+
+ await new Promise((resolve) => {
+ script.onload = resolve;
+ });
+
+ const pyodide = await window.loadPyodide({
+ indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/',
+ });
+
+
+ // Load and register our SDK
+ const sdk = await fetch('/resdb_sdk.py').then((res) => res.text());
+ await pyodide.runPythonAsync(sdk);
+
+ // Register the SDK as a module
+ await pyodide.runPythonAsync(`
+import sys
+import types
+resdb_sdk = types.ModuleType('resdb_sdk')
+resdb_sdk.__dict__.update(globals())
+sys.modules['resdb_sdk'] = resdb_sdk
+ `);
+
+ await pyodide.runPythonAsync(`
+ import sys
+ from io import StringIO
+ sys.stdout = StringIO()
+ sys.stderr = StringIO()
+ `);
+
+ window.pyodide = pyodide;
+ setOutput(['✅ Python environment ready']);
+ setIsLoading(false);
+ } catch (error) {
+ setOutput((prev) => [
+ ...prev,
+ `❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
+ ]);
+ setIsLoading(false);
+ }
+ };
+
+ initPython();
+ }, []);
+
+ const runCode = async () => {
+ if (isLoading || isRunning) {
+ return;
+ }
+ setIsRunning(true);
+ setOutput(['▶ Running code...']);
+
+ try {
+ // Wrap code in async function if it uses await
+ const codeToRun = /\bawait\b/.test(code)
+ ? `
+async def __run():
+${code
+ .split('\n')
+ .map((line: string) => ` ${line}`)
+ .join('\n')}
+
+import asyncio
+loop = asyncio.get_event_loop()
+loop.run_until_complete(__run())
+`
+ : code;
+
+ // Run the code and capture output
+ await window.pyodide.runPythonAsync(codeToRun);
+
+ // Get output
+ const stdout = await window.pyodide.runPythonAsync('sys.stdout.getvalue()');
+ if (stdout) {
+ setOutput((prev) => [...prev, stdout]);
+ }
+
+ setOutput((prev) => [...prev, '✅ Code execution completed']);
+ } catch (error: any) {
+ setOutput((prev) => [...prev, `❌ Error: ${error.message}`]);
+ } finally {
+ // Clear buffers
+ await window.pyodide.runPythonAsync('sys.stdout.truncate(0)\nsys.stdout.seek(0)');
+ setIsRunning(false);
+ }
+ };
+
+ const clearOutput = () => setOutput([]);
+
+ const clearConversation = () => {
+ setMessages([]);
+ };
+
+ return (
+ <div
+ style={{
+ background: 'rgba(255,255,255,0.02)',
+ backdropFilter: 'blur(12px)',
+ border: '1px solid rgba(255,255,255,0.08)',
+ borderRadius: 16,
+ padding: 24,
+ position: 'relative',
+ overflow: 'hidden',
+ }}
+ >
+ {/* Glass effect overlay */}
+ <div style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: 1,
+ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
+ borderRadius: '16px 16px 0 0'
+ }} />
+
+ <Stack gap={20}>
+ <Group justify="space-between" align="center">
+ <div
+ style={{
+ display: 'inline-flex',
+ alignItems: 'center',
+ paddingInline: 12,
+ paddingBlock: 6,
+ borderRadius: 999,
+ background: 'rgba(255,255,255,0.05)',
+ backdropFilter: 'blur(6px)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ }}
+ >
+ <span style={{ color: 'rgba(255,255,255,0.9)', fontSize: 12, fontWeight: 300 }}>
+ 🐍 Python Playground
+ </span>
+ </div>
+
+ <Group gap={8}>
+ <Tooltip label="Load example">
+ <Select
+ size="xs"
+ placeholder="Load example"
+ data={codeTemplates}
+ onChange={(value) =>
+ value && setCode(codeTemplates.find((t) => t.value === value)?.code || '')
+ }
+ leftSection={<IconTemplate size={14} />}
+ styles={{
+ input: {
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.9)',
+ '&:focus': {
+ borderColor: 'rgba(0, 191, 255, 0.5)',
+ }
+ }
+ }}
+ />
+ </Tooltip>
+ <Tooltip label="Ask AI Assistant">
+ <ActionIcon
+ variant="light"
+ onClick={() => setShowChat(!showChat)}
+ color={showChat ? "blue" : "gray"}
+ style={{
+ background: showChat ? 'rgba(0, 191, 255, 0.1)' : 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: showChat ? '#00bfff' : 'rgba(255,255,255,0.7)',
+ }}
+ >
+ <IconMessage size={14} />
+ </ActionIcon>
+ </Tooltip>
+ </Group>
+ </Group>
+
+ <div
+ style={{
+ background: 'rgba(0,0,0,0.3)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ overflow: 'hidden',
+ position: 'relative',
+ }}
+ >
+ <CodeMirror
+ value={code}
+ height="400px"
+ theme={vscodeDark}
+ extensions={[python()]}
+ onChange={setCode}
+ editable={!isLoading}
+ basicSetup={{
+ lineNumbers: true,
+ highlightActiveLineGutter: true,
+ highlightSpecialChars: true,
+ history: true,
+ foldGutter: true,
+ drawSelection: true,
+ dropCursor: true,
+ allowMultipleSelections: true,
+ indentOnInput: true,
+ syntaxHighlighting: true,
+ bracketMatching: true,
+ closeBrackets: true,
+ autocompletion: true,
+ rectangularSelection: true,
+ crosshairCursor: true,
+ highlightActiveLine: true,
+ highlightSelectionMatches: true,
+ closeBracketsKeymap: true,
+ defaultKeymap: true,
+ searchKeymap: true,
+ historyKeymap: true,
+ foldKeymap: true,
+ completionKeymap: true,
+ lintKeymap: true,
+ }}
+ />
+ </div>
+
+ {showChat && (
+ <>
+ <div
+ style={{
+ background: 'rgba(255,255,255,0.03)',
+ backdropFilter: 'blur(8px)',
+ border: '1px solid rgba(255,255,255,0.08)',
+ borderRadius: 12,
+ padding: 16,
+ position: 'relative',
+ }}
+ >
+ <Stack gap={16}>
+ <Group justify="space-between" align="center">
+ <div
+ style={{
+ display: 'inline-flex',
+ alignItems: 'center',
+ paddingInline: 8,
+ paddingBlock: 4,
+ borderRadius: 6,
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.2)',
+ }}
+ >
+ <Text size="xs" fw={500} c="#00bfff">AI Assistant</Text>
+ </div>
+ <Group gap={4}>
+ <Tooltip label={isExpanded ? "Minimize" : "Expand"}>
+ <ActionIcon
+ ref={expandTriggerRef}
+ variant="light"
+ onClick={(e) => openExpanded(e.currentTarget)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ openExpanded(e.currentTarget as HTMLElement);
+ }
+ }}
+ disabled={isExpanded}
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.7)',
+ }}
+ >
+ <IconMaximize size={14} />
+ </ActionIcon>
+ </Tooltip>
+ <Tooltip label="Clear conversation">
+ <ActionIcon
+ variant="light"
+ color="red"
+ onClick={clearConversation}
+ disabled={messages.length === 0}
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.7)',
+ }}
+ >
+ <IconTrash size={14} />
+ </ActionIcon>
+ </Tooltip>
+ </Group>
+ </Group>
+ <div style={{ maxHeight: '200px', overflowY: 'auto' }}>
+ {[...messages].reverse().map((message, i) => (
+ <div
+ key={i}
+ style={{
+ padding: 12,
+ marginBottom: 8,
+ borderRadius: 8,
+ background: message.role === 'user'
+ ? 'rgba(0, 191, 255, 0.1)'
+ : 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.08)',
+ backdropFilter: 'blur(4px)',
+ }}
+ >
+ <Text
+ size="sm"
+ c={message.role === 'user' ? '#00bfff' : 'rgba(255,255,255,0.9)'}
+ style={{ lineHeight: 1.5 }}
+ >
+ {message.role === 'user' ? (
+ message.content
+ ) : (
+ <ReactMarkdown
+ components={{
+ code({ node, inline, className, children, ...props }: CodeProps) {
+ const match = /language-(\w+)/.exec(className || '');
+ return !inline && match ? (
+ <SyntaxHighlighter
+ style={vscDarkPlus}
+ language={match[1]}
+ PreTag="div"
+ {...props}
+ >
+ {String(children).replace(/\n$/, '')}
+ </SyntaxHighlighter>
+ ) : (
+ <code className={className} {...props}>
+ {children}
+ </code>
+ );
+ },
+ }}
+ >
+ {message.content}
+ </ReactMarkdown>
+ )}
+ </Text>
+ </div>
+ ))}
+ </div>
+ <form onSubmit={handleSubmit}>
+ <Group gap={8}>
+ <Textarea
+ placeholder="Ask about your code... (Press Enter to send, Shift+Enter for new line)"
+ value={input}
+ onChange={(e) => setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ style={{ flex: 1 }}
+ disabled={isAiLoading}
+ autosize
+ minRows={1}
+ maxRows={4}
+ styles={{
+ input: {
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.9)',
+ '&:focus': {
+ borderColor: 'rgba(0, 191, 255, 0.5)',
+ },
+ '&::placeholder': {
+ color: 'rgba(255,255,255,0.5)',
+ }
+ }
+ }}
+ />
+ {isAiLoading ? (
+ <Button
+ color="red"
+ onClick={stopGeneration}
+ leftSection={<IconPlayerStop size={14} />}
+ style={{
+ background: 'rgba(255, 77, 77, 0.1)',
+ border: '1px solid rgba(255, 77, 77, 0.3)',
+ color: '#ff4d4d',
+ }}
+ >
+ Stop
+ </Button>
+ ) : (
+ <Button
+ type="submit"
+ style={{
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.3)',
+ color: '#00bfff',
+ }}
+ >
+ Send
+ </Button>
+ )}
+ </Group>
+ </form>
+ </Stack>
+ </div>
+
+ <Modal
+ opened={isExpanded}
+ onClose={() => setIsExpanded(false)}
+ size="xl"
+ title={
+ <Group justify="space-between" w="100%">
+ <Text size="sm" fw={500}>AI Assistant</Text>
+ <Group>
+ <Tooltip label="Minimize">
+ <ActionIcon
+ variant="light"
+ onClick={() => setIsExpanded(false)}
+ >
+ <IconMinimize size={14} />
+ </ActionIcon>
+ </Tooltip>
+ <Tooltip label="Clear conversation">
+ <ActionIcon
+ variant="light"
+ color="red"
+ onClick={clearConversation}
+ disabled={messages.length === 0}
+ >
+ <IconTrash size={14} />
+ </ActionIcon>
+ </Tooltip>
+ </Group>
+ </Group>
+ }
+ styles={{
+ title: {
+ width: '100%',
+ marginRight: 0,
+ },
+ body: {
+ height: 'calc(100vh - 200px)',
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ }}
+ >
+ <Stack style={{ flex: 1, height: '100%' }}>
+ <Box style={{ flex: 1, overflowY: 'auto' }}>
+ {[...messages].reverse().map((message, i) => (
+ <Paper key={i} p="xs" mb="xs" bg={message.role === 'user' ? 'dark.6' : 'dark.7'}>
+ <Text size="sm" c={message.role === 'user' ? 'blue' : 'white'}>
+ {message.role === 'user' ? (
+ message.content
+ ) : (
+ <ReactMarkdown
+ components={{
+ code({ node, inline, className, children, ...props }: CodeProps) {
+ const match = /language-(\w+)/.exec(className || '');
+ return !inline && match ? (
+ <SyntaxHighlighter
+ style={vscDarkPlus}
+ language={match[1]}
+ PreTag="div"
+ {...props}
+ >
+ {String(children).replace(/\n$/, '')}
+ </SyntaxHighlighter>
+ ) : (
+ <code className={className} {...props}>
+ {children}
+ </code>
+ );
+ },
+ }}
+ >
+ {message.content}
+ </ReactMarkdown>
+ )}
+ </Text>
+ </Paper>
+ ))}
+ </Box>
+ <form onSubmit={handleSubmit}>
+ <Group>
+ <Textarea
+ placeholder="Ask about your code... (Press Enter to send, Shift+Enter for new line)"
+ value={input}
+ onChange={(e) => setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ style={{ flex: 1 }}
+ disabled={isAiLoading}
+ autosize
+ minRows={1}
+ maxRows={4}
+ />
+ {isAiLoading ? (
+ <Button
+ color="red"
+ onClick={stopGeneration}
+ leftSection={<IconPlayerStop size={14} />}
+ >
+ Stop
+ </Button>
+ ) : (
+ <Button type="submit">
+ Send
+ </Button>
+ )}
+ </Group>
+ </form>
+ </Stack>
+ </Modal>
+ </>
+ )}
+
+ <Group justify="space-between">
+ <Button
+ onClick={runCode}
+ loading={isRunning}
+ disabled={isLoading}
+ leftSection={<IconPlayerPlay size={14} />}
+ style={{
+ background: 'rgba(0, 191, 255, 0.1)',
+ border: '1px solid rgba(0, 191, 255, 0.3)',
+ color: '#00bfff',
+ borderRadius: 8,
+ }}
+ >
+ {isRunning ? 'Running...' : 'Run'}
+ </Button>
+ <Button
+ onClick={clearOutput}
+ disabled={isLoading || output.length === 0}
+ leftSection={<IconTrash size={14} />}
+ style={{
+ background: 'rgba(255,255,255,0.05)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ color: 'rgba(255,255,255,0.7)',
+ borderRadius: 8,
+ }}
+ >
+ Clear Output
+ </Button>
+ </Group>
+
+ <div
+ style={{
+ background: 'rgba(0,0,0,0.4)',
+ border: '1px solid rgba(255,255,255,0.1)',
+ borderRadius: 12,
+ padding: 16,
+ height: '200px',
+ overflow: 'auto',
+ fontFamily: 'monospace',
+ position: 'relative',
+ }}
+ >
+ {output.map((line, i) => (
+ <Text
+ key={i}
+ style={{
+ whiteSpace: 'pre-wrap',
+ color: line.includes('✅') ? '#00ff88' :
+ line.includes('❌') ? '#ff4d4d' :
+ line.includes('▶') ? '#00bfff' :
+ 'rgba(255,255,255,0.8)',
+ fontSize: 13,
+ lineHeight: 1.4,
+ display: 'block',
+ marginBottom: 2,
+ }}
+ >
+ {line}
+ </Text>
+ ))}
+ </div>
+ </Stack>
+ </div>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/ReleaseNotes/ReleaseNotes.tsx b/ecosystem/AI-Tooling/beacon/components/ReleaseNotes/ReleaseNotes.tsx
new file mode 100644
index 00000000..53845ea4
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ReleaseNotes/ReleaseNotes.tsx
@@ -0,0 +1,148 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { useEffect, useState } from 'react';
+import { compileMdx } from 'nextra/compile';
+import { MDXRemote } from 'nextra/mdx-remote';
+import useSWR from 'swr';
+import { Alert, Badge, Group, Loader, Paper, Skeleton, Stack, Text } from '@mantine/core';
+import { useMDXComponents } from '@/mdx-components';
+
+interface Author {
+ login: string;
+ id: number;
+ node_id: string;
+ avatar_url: string;
+ gravatar_id: string;
+ url: string;
+ html_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ starred_url: string;
+ subscriptions_url: string;
+ organizations_url: string;
+ repos_url: string;
+ events_url: string;
+ received_events_url: string;
+ type: string;
+ user_view_type: string;
+ site_admin: boolean;
+}
+
+interface Release {
+ url: string;
+ assets_url: string;
+ upload_url: string;
+ html_url: string;
+ id: number;
+ author: Author;
+ node_id: string;
+ tag_name: string;
+ target_commitish: string;
+ name: string;
+ draft: boolean;
+ prerelease: boolean;
+ created_at: string;
+ published_at: string;
+ assets: any[];
+ tarball_url: string;
+ zipball_url: string;
+ body: string;
+}
+
+const fetcher = (url: string) => fetch(url).then((res) => res.json());
+
+export function ReleaseNotes() {
+ const { data, error, isLoading } = useSWR<{
+ releases: Release[];
+ }>('/api/github-releases', fetcher);
+ const [compiledReleases, setCompiledReleases] = useState<Release[]>([]);
+
+ const components = useMDXComponents();
+
+ useEffect(() => {
+ if (data) {
+ const fetchReleases = async () => {
+ const releases = await Promise.all(
+ data.releases.map(async (release) => ({
+ ...release,
+ created_at: new Date(release.created_at).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ }),
+ body: await compileMdx(release.body),
+ }))
+ );
+ setCompiledReleases(releases);
+ };
+ fetchReleases();
+ }
+ }, [data, isLoading, error]); // Add isLoading and error to the dependency array
+
+ if (isLoading || compiledReleases.length === 0) {
+ return (
+ <Stack mt={24} w="100%" align="center">
+ <Group>
+ <Text>Loading releases...</Text>
+ <Loader type="dots" />
+ </Group>
+ <Skeleton height={200} width="100%" radius={12} />
+ <Skeleton height={50} width="100%" radius={12} />
+ <Skeleton height={20} width="100%" radius={12} />
+ </Stack>
+ );
+ }
+
+ if (error) {
+ return (
+ <Alert title="Error" color="red">
+ Failed to load releases
+ </Alert>
+ );
+ }
+
+ if (!data) {
+ return null;
+ }
+
+ if (compiledReleases.length === 0) {
+ return null;
+ }
+
+ return (
+ <Stack mt={24}>
+ {compiledReleases.map((release: Release) => (
+ <Paper key={release.id} withBorder p={24} radius={12} shadow="sm">
+ <Group justify="space-between">
+ <Badge component="a" href={release.html_url} size="xl">
+ {release.name}
+ </Badge>
+ <Text>{release.created_at}</Text>
+ </Group>
+
+ <MDXRemote compiledSource={release.body} components={components} />
+ </Paper>
+ ))}
+ </Stack>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/ResContractPlayground.tsx b/ecosystem/AI-Tooling/beacon/components/ResContractPlayground.tsx
new file mode 100644
index 00000000..fa3c5b18
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/ResContractPlayground.tsx
@@ -0,0 +1,316 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { useState } from 'react';
+import { Box, Tabs, TabsList, TabsTab, TabsPanel, Alert, Button, Paper, Text, Group, Code } from '@mantine/core';
+import { IconInfoCircle, IconPlayerPlay, IconCheck, IconX, IconCopy } from '@tabler/icons-react';
+import CodeMirror from '@uiw/react-codemirror';
+import { javascript } from '@codemirror/lang-javascript';
+import { vscodeDark } from '@uiw/codemirror-theme-vscode';
+
+const DEFAULT_CONTRACT = `// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+contract SimpleToken {
+ string public name;
+ string public symbol;
+ uint256 public totalSupply;
+ mapping(address => uint256) public balanceOf;
+
+ constructor(string memory _name, string memory _symbol, uint256 _totalSupply) {
+ name = _name;
+ symbol = _symbol;
+ totalSupply = _totalSupply;
+ balanceOf[msg.sender] = _totalSupply;
+ }
+
+ function transfer(address _to, uint256 _value) public returns (bool) {
+ require(balanceOf[msg.sender] >= _value, "Insufficient balance");
+ balanceOf[msg.sender] -= _value;
+ balanceOf[_to] += _value;
+ return true;
+ }
+}`;
+
+const EXAMPLE_CONTRACTS = {
+ token: `// Basic ERC-20 Token Contract
+// Try modifying the token name, symbol, or total supply`,
+ voting: `// Voting Contract
+// Experiment with different voting mechanisms`,
+ auction: `// Auction Contract
+// Try implementing different auction types`
+};
+
+const TUTORIAL_STEPS = {
+ step1: `// Step 1: Setup
+// Make sure you have Node.js and solc installed`,
+ step2: `// Step 2: Create Contract
+// Your first smart contract
+// Follow the instructions in the comments`,
+ step3: `// Step 3: Deploy
+// We'll use a test network for this tutorial`,
+ step4: `// Step 4: Interact
+// Try calling different functions
+// Experiment with different parameters`
+};
+
+interface CompilationResult {
+ success: boolean;
+ output: string;
+ abi?: any;
+ bytecode?: string;
+}
+
+export function ResContractPlayground() {
+ const [code, setCode] = useState(DEFAULT_CONTRACT);
+ const [activeTab, setActiveTab] = useState('playground');
+ const [activeExample, setActiveExample] = useState('token');
+ const [activeStep, setActiveStep] = useState('step1');
+ const [compilationResult, setCompilationResult] = useState<CompilationResult | null>(null);
+ const [isCompiling, setIsCompiling] = useState(false);
+
+ const handleCompile = async () => {
+ setIsCompiling(true);
+ setCompilationResult(null);
+
+ try {
+ const response = await fetch('/api/compile', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ code }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to compile contract');
+ }
+
+ const result = await response.json();
+ setCompilationResult(result);
+ } catch (error) {
+ setCompilationResult({
+ success: false,
+ output: 'Failed to compile contract. Please check your code and try again.',
+ });
+ } finally {
+ setIsCompiling(false);
+ }
+ };
+
+ const copyToClipboard = (text: string) => {
+ navigator.clipboard.writeText(text);
+ };
+
+ return (
+ <Tabs defaultValue="playground" value={activeTab} onChange={(value) => setActiveTab(value as string)}>
+ <TabsList>
+ <TabsTab value="playground">Smart Contract Playground</TabsTab>
+ <TabsTab value="examples">Example Contracts</TabsTab>
+ <TabsTab value="tutorial">Step-by-Step Tutorial</TabsTab>
+ </TabsList>
+
+ <TabsPanel value="playground">
+ <Box my="md">
+ <Alert icon={<IconInfoCircle size={16} />} title="Interactive Playground" color="blue">
+ Experiment with a simple token contract in our interactive playground. Modify the code and see the results instantly.
+ </Alert>
+ <Box mt="md">
+ <CodeMirror
+ value={code}
+ height="400px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ onChange={(value) => setCode(value)}
+ />
+ </Box>
+ <Group mt="md" justify="flex-end">
+ <Button
+ leftSection={<IconPlayerPlay size={16} />}
+ onClick={handleCompile}
+ loading={isCompiling}
+ >
+ Compile Contract
+ </Button>
+ </Group>
+ {compilationResult && (
+ <Paper mt="md" p="md" withBorder>
+ <Group mb="xs">
+ {compilationResult.success ? (
+ <IconCheck size={20} color="green" />
+ ) : (
+ <IconX size={20} color="red" />
+ )}
+ <Text fw={500}>
+ {compilationResult.success ? 'Compilation Successful' : 'Compilation Failed'}
+ </Text>
+ </Group>
+ <Text size="sm" style={{ whiteSpace: 'pre-wrap' }}>
+ {compilationResult.output}
+ </Text>
+ {compilationResult.success && (
+ <>
+ <Box mt="md">
+ <Group justify="space-between" mb="xs">
+ <Text size="sm" fw={500}>Contract ABI:</Text>
+ <Button
+ variant="subtle"
+ size="xs"
+ leftSection={<IconCopy size={14} />}
+ onClick={() => copyToClipboard(JSON.stringify(compilationResult.abi, null, 2))}
+ >
+ Copy
+ </Button>
+ </Group>
+ <CodeMirror
+ value={JSON.stringify(compilationResult.abi, null, 2)}
+ height="200px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ readOnly
+ />
+ </Box>
+ <Box mt="md">
+ <Group justify="space-between" mb="xs">
+ <Text size="sm" fw={500}>Bytecode:</Text>
+ <Button
+ variant="subtle"
+ size="xs"
+ leftSection={<IconCopy size={14} />}
+ onClick={() => copyToClipboard(compilationResult.bytecode || '')}
+ >
+ Copy
+ </Button>
+ </Group>
+ <Code block>
+ {compilationResult.bytecode}
+ </Code>
+ </Box>
+ </>
+ )}
+ </Paper>
+ )}
+ </Box>
+ </TabsPanel>
+
+ <TabsPanel value="examples">
+ <Box my="md">
+ <Alert icon={<IconInfoCircle size={16} />} title="Example Contracts" color="blue">
+ Explore and learn from these example smart contracts. Each example demonstrates different concepts and patterns.
+ </Alert>
+ <Tabs defaultValue="token" value={activeExample} onChange={(value) => setActiveExample(value as string)}>
+ <TabsList>
+ <TabsTab value="token">Token Contract</TabsTab>
+ <TabsTab value="voting">Voting Contract</TabsTab>
+ <TabsTab value="auction">Auction Contract</TabsTab>
+ </TabsList>
+
+ <TabsPanel value="token">
+ <Box mt="md">
+ <CodeMirror
+ value={EXAMPLE_CONTRACTS.token}
+ height="200px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ onChange={(value) => setCode(value)}
+ />
+ </Box>
+ </TabsPanel>
+
+ <TabsPanel value="voting">
+ <Box mt="md">
+ <CodeMirror
+ value={EXAMPLE_CONTRACTS.voting}
+ height="200px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ onChange={(value) => setCode(value)}
+ />
+ </Box>
+ </TabsPanel>
+
+ <TabsPanel value="auction">
+ <Box mt="md">
+ <CodeMirror
+ value={EXAMPLE_CONTRACTS.auction}
+ height="200px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ onChange={(value) => setCode(value)}
+ />
+ </Box>
+ </TabsPanel>
+ </Tabs>
+ </Box>
+ </TabsPanel>
+
+ <TabsPanel value="tutorial">
+ <Box my="md">
+ <Alert icon={<IconInfoCircle size={16} />} title="Interactive Tutorial" color="blue">
+ Follow this step-by-step tutorial to learn how to create, deploy, and interact with smart contracts.
+ </Alert>
+ <Tabs defaultValue="step1" value={activeStep} onChange={(value) => setActiveStep(value as string)}>
+ <TabsList>
+ <TabsTab value="step1">Step 1: Setup</TabsTab>
+ <TabsTab value="step2">Step 2: Create Contract</TabsTab>
+ <TabsTab value="step3">Step 3: Deploy</TabsTab>
+ <TabsTab value="step4">Step 4: Interact</TabsTab>
+ </TabsList>
+
+ <TabsPanel value="step1">
+ <Alert icon={<IconInfoCircle size={16} />} title="Prerequisites" color="blue" mb="md">
+ Make sure you have Node.js and solc installed before proceeding.
+ </Alert>
+ </TabsPanel>
+
+ <TabsPanel value="step2">
+ <Box mt="md">
+ <CodeMirror
+ value={TUTORIAL_STEPS.step2}
+ height="200px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ onChange={(value) => setCode(value)}
+ />
+ </Box>
+ </TabsPanel>
+
+ <TabsPanel value="step3">
+ <Alert icon={<IconInfoCircle size={16} />} title="Note" color="blue" mb="md">
+ We'll use a test network for this tutorial.
+ </Alert>
+ </TabsPanel>
+
+ <TabsPanel value="step4">
+ <Box mt="md">
+ <CodeMirror
+ value={TUTORIAL_STEPS.step4}
+ height="200px"
+ theme={vscodeDark}
+ extensions={[javascript()]}
+ onChange={(value) => setCode(value)}
+ />
+ </Box>
+ </TabsPanel>
+ </Tabs>
+ </Box>
+ </TabsPanel>
+ </Tabs>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/SearchBar/PagefindSearch.tsx b/ecosystem/AI-Tooling/beacon/components/SearchBar/PagefindSearch.tsx
new file mode 100644
index 00000000..84cd565a
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/SearchBar/PagefindSearch.tsx
@@ -0,0 +1,725 @@
+'use client'
+
+import { useState, useEffect, useRef } from 'react';
+import { useRouter } from 'next/navigation';
+
+interface SearchResult {
+ id: string;
+ data: () => Promise<{
+ url: string;
+ meta: {
+ title: string;
+ description?: string;
+ };
+ excerpt: string;
+ }>;
+}
+
+interface PagefindSearchProps {
+ onClose?: () => void;
+}
+
+export function PagefindSearch({ onClose }: PagefindSearchProps) {
+ const [query, setQuery] = useState('');
+ const [results, setResults] = useState<SearchResult[]>([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isOpen, setIsOpen] = useState(false);
+ const [pagefind, setPagefind] = useState<any>(null);
+ const [mode, setMode] = useState<'search' | 'agent'>('search');
+ const [agentResults, setAgentResults] = useState<any[]>([]);
+ const [isAgentLoading, setIsAgentLoading] = useState(false);
+ const inputRef = useRef<HTMLInputElement>(null);
+ const resultsRef = useRef<HTMLDivElement>(null);
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
+ const lastFocusedRef = useRef<HTMLElement | null>(null);
+ const router = useRouter();
+
+ // Load Pagefind
+ useEffect(() => {
+ async function loadPagefind() {
+ if (typeof window !== 'undefined' && typeof (window as any).pagefind === 'undefined') {
+ try {
+ (window as any).pagefind = await import(
+ // @ts-expect-error pagefind.js generated after build
+ /* webpackIgnore: true */ '/_pagefind/pagefind.js'
+ );
+ setPagefind((window as any).pagefind);
+ } catch (e) {
+ console.log('Pagefind not available in development');
+ // Mock pagefind for development
+ (window as any).pagefind = {
+ search: () => ({ results: [] }),
+ debouncedSearch: () => ({ results: [] })
+ };
+ setPagefind((window as any).pagefind);
+ }
+ } else if (typeof window !== 'undefined') {
+ setPagefind((window as any).pagefind);
+ }
+ }
+ loadPagefind();
+ }, []);
+
+ // Highlight search terms in text
+ const highlightSearchTerms = (text: string, query: string): string => {
+ if (!query.trim()) return text;
+
+ const terms = query.toLowerCase().split(/\s+/).filter(term => term.length > 2);
+ let highlightedText = text;
+
+ terms.forEach(term => {
+ const regex = new RegExp(`(${term})`, 'gi');
+ highlightedText = highlightedText.replace(regex, '<mark style="background: rgba(0, 191, 255, 0.3); color: #00bfff; padding: 1px 2px; border-radius: 2px;">$1</mark>');
+ });
+
+ return highlightedText;
+ };
+
+ // Handle search
+ const handleSearch = async (searchQuery: string) => {
+ if (!pagefind || !searchQuery.trim()) {
+ setResults([]);
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const search = await pagefind.debouncedSearch(searchQuery);
+ const results = search.results || [];
+
+ // Add highlighting to results
+ const highlightedResults = results.map((result: any) => ({
+ ...result,
+ highlightedExcerpt: highlightSearchTerms(result.excerpt || '', searchQuery)
+ }));
+
+ setResults(highlightedResults);
+ } catch (error) {
+ console.error('Search error:', error);
+ setResults([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Handle AI Agent search
+ const handleAgentSearch = async (searchQuery: string) => {
+ if (!searchQuery.trim()) return;
+
+ setIsAgentLoading(true);
+ setAgentResults([]);
+
+ try {
+ const response = await fetch('/api/ai-search', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ query: searchQuery,
+ limit: 10
+ }),
+ });
+
+ if (!response.ok) throw new Error('Failed to perform AI agent search');
+
+ const data = await response.json();
+ const results = data.results || [];
+
+ // Add highlighting to agent results
+ const highlightedResults = results.map((result: any) => ({
+ ...result,
+ highlightedExcerpt: highlightSearchTerms(result.excerpt || '', searchQuery)
+ }));
+
+ setAgentResults(highlightedResults);
+ } catch (error) {
+ console.error('AI Agent search error:', error);
+ setAgentResults([]);
+ } finally {
+ setIsAgentLoading(false);
+ }
+ };
+
+ // Handle Enter key and search button for agent mode
+ const handleSubmit = (e?: React.FormEvent) => {
+ if (e) e.preventDefault();
+ if (query.trim() && mode === 'agent') {
+ handleAgentSearch(query);
+ }
+ };
+
+ // Handle input change with debouncing (only for search mode)
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ if (query && mode === 'search') {
+ handleSearch(query);
+ } else if (mode === 'search') {
+ setResults([]);
+ }
+ }, 300);
+
+ return () => clearTimeout(timer);
+ }, [query, pagefind, mode]);
+
+ // Handle keyboard shortcuts
+ // Global keyboard shortcuts (open/search mode toggle)
+ useEffect(() => {
+ const handleGlobalKeyDown = (e: KeyboardEvent) => {
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+ e.preventDefault();
+ // remember where focus was and open
+ lastFocusedRef.current = document.activeElement as HTMLElement | null;
+ setIsOpen(true);
+ // focus the input shortly after opening
+ setTimeout(() => inputRef.current?.focus(), 50);
+ }
+ if ((e.metaKey || e.ctrlKey) && e.key === 'j') {
+ e.preventDefault();
+ setMode((m) => (m === 'search' ? 'agent' : 'search'));
+ }
+ };
+
+ document.addEventListener('keydown', handleGlobalKeyDown);
+ return () => document.removeEventListener('keydown', handleGlobalKeyDown);
+ }, []);
+
+ // Add an Escape handler only while the modal is open so it can be removed when closed
+ useEffect(() => {
+ if (!isOpen) return;
+
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ closeModal();
+ }
+ };
+
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, [isOpen]);
+
+ // Open/close helpers that manage focus
+ const openModal = (fromTrigger?: HTMLElement | null) => {
+ lastFocusedRef.current = fromTrigger || (document.activeElement as HTMLElement | null);
+ setIsOpen(true);
+ setTimeout(() => inputRef.current?.focus(), 50);
+ };
+
+ const closeModal = () => {
+ setIsOpen(false);
+ setQuery('');
+ setResults([]);
+ setAgentResults([]);
+ onClose?.();
+ // return focus to trigger if possible
+ setTimeout(() => {
+ try {
+ (lastFocusedRef.current ?? triggerRef.current)?.focus?.();
+ } catch (err) {
+ // ignore
+ }
+ }, 0);
+ };
+
+ // Clean URL by removing .html extension and fixing paths
+ const cleanUrl = (url: string): string => {
+ // Remove .html extension
+ let cleanUrl = url.replace(/\.html$/, '');
+
+ // Handle index pages - convert /docs/index to /docs
+ if (cleanUrl.endsWith('/index')) {
+ cleanUrl = cleanUrl.replace(/\/index$/, '');
+ }
+
+ // Ensure URL starts with / if it doesn't already
+ if (!cleanUrl.startsWith('/')) {
+ cleanUrl = '/' + cleanUrl;
+ }
+
+ return cleanUrl;
+ };
+
+ // Handle result click
+ const handleResultClick = async (result: SearchResult) => {
+ try {
+ const data = await result.data();
+ const cleanPath = cleanUrl(data.url);
+ router.push(cleanPath);
+ setIsOpen(false);
+ setQuery('');
+ setResults([]);
+ onClose?.();
+ } catch (error) {
+ console.error('Error navigating to result:', error);
+ }
+ };
+
+ if (!isOpen) {
+ return (
+ <button
+ ref={triggerRef}
+ onClick={(e) => openModal(e.currentTarget)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ openModal(e.currentTarget as HTMLElement);
+ }
+ }}
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ gap: 8,
+ padding: '8px 16px',
+ background: 'rgba(255, 255, 255, 0.08)',
+ border: '1px solid rgba(255, 255, 255, 0.1)',
+ borderRadius: 24,
+ color: 'rgba(255, 255, 255, 0.7)',
+ fontSize: 14,
+ fontWeight: 400,
+ cursor: 'pointer',
+ transition: 'all 200ms ease',
+ backdropFilter: 'blur(10px)',
+ minWidth: 200,
+ justifyContent: 'flex-start',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255, 255, 255, 0.12)';
+ e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(255, 255, 255, 0.08)';
+ e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.1)';
+ }}
+ >
+ <svg
+ width="16"
+ height="16"
+ fill="currentColor"
+ viewBox="0 0 20 20"
+ style={{ opacity: 0.6, flexShrink: 0 }}
+ >
+ <path
+ fillRule="evenodd"
+ d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
+ clipRule="evenodd"
+ />
+ </svg>
+ <span style={{ flex: 1, textAlign: 'left' }}>Search...</span>
+ <div style={{ display: 'flex', alignItems: 'center', gap: 2, opacity: 0.5 }}>
+ <kbd style={{
+ background: 'rgba(255, 255, 255, 0.1)',
+ border: '1px solid rgba(255, 255, 255, 0.2)',
+ borderRadius: 4,
+ padding: '2px 6px',
+ fontSize: 11,
+ fontFamily: 'monospace',
+ color: 'rgba(255, 255, 255, 0.7)',
+ }}>
+ ⌘
+ </kbd>
+ <kbd style={{
+ background: 'rgba(255, 255, 255, 0.1)',
+ border: '1px solid rgba(255, 255, 255, 0.2)',
+ borderRadius: 4,
+ padding: '2px 6px',
+ fontSize: 11,
+ fontFamily: 'monospace',
+ color: 'rgba(255, 255, 255, 0.7)',
+ }}>
+ K
+ </kbd>
+ </div>
+ </button>
+ );
+ }
+
+ return (
+ <div
+ style={{
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ background: 'rgba(0, 0, 0, 0.8)',
+ backdropFilter: 'blur(8px)',
+ zIndex: 10000,
+ display: 'flex',
+ alignItems: 'flex-start',
+ justifyContent: 'center',
+ paddingTop: '10vh',
+ }}
+ onClick={(e) => {
+ if (e.target === e.currentTarget) {
+ setIsOpen(false);
+ setQuery('');
+ setResults([]);
+ onClose?.();
+ }
+ }}
+ >
+ <div
+ style={{
+ background: 'rgba(255, 255, 255, 0.1)',
+ border: '1px solid rgba(255, 255, 255, 0.2)',
+ borderRadius: 12,
+ backdropFilter: 'blur(10px)',
+ minWidth: 600,
+ maxWidth: '90vw',
+ height: '500px',
+ overflow: 'hidden',
+ display: 'flex',
+ flexDirection: 'column',
+ }}
+ >
+ {/* Search Input */}
+ <div style={{ padding: '20px', borderBottom: '1px solid rgba(255, 255, 255, 0.1)' }}>
+ {/* Mode Toggle */}
+ <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
+ <button
+ onClick={() => setMode('search')}
+ style={{
+ padding: '6px 12px',
+ borderRadius: 6,
+ border: 'none',
+ background: mode === 'search' ? 'rgba(0, 191, 255, 0.2)' : 'rgba(255, 255, 255, 0.1)',
+ color: mode === 'search' ? '#00bfff' : 'rgba(255, 255, 255, 0.7)',
+ fontSize: 12,
+ fontWeight: 500,
+ cursor: 'pointer',
+ transition: 'all 200ms ease',
+ }}
+ >
+ 🔍 Search
+ </button>
+ <button
+ onClick={() => setMode('agent')}
+ style={{
+ padding: '6px 12px',
+ borderRadius: 6,
+ border: 'none',
+ background: mode === 'agent' ? 'rgba(0, 191, 255, 0.2)' : 'rgba(255, 255, 255, 0.1)',
+ color: mode === 'agent' ? '#00bfff' : 'rgba(255, 255, 255, 0.7)',
+ fontSize: 12,
+ fontWeight: 500,
+ cursor: 'pointer',
+ transition: 'all 200ms ease',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 6,
+ }}
+ >
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
+ </svg>
+ Ask Our Agent
+ <span style={{
+ background: 'rgba(255, 193, 7, 0.2)',
+ color: '#ffc107',
+ fontSize: 10,
+ padding: '2px 4px',
+ borderRadius: 3,
+ fontWeight: 600,
+ textTransform: 'uppercase',
+ letterSpacing: '0.5px'
+ }}>
+ Beta
+ </span>
+ </button>
+ </div>
+
+ <form onSubmit={handleSubmit} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%' }}>
+ {mode === 'search' ? (
+ <svg
+ width="20"
+ height="20"
+ fill="currentColor"
+ viewBox="0 0 20 20"
+ style={{ opacity: 0.6 }}
+ >
+ <path
+ fillRule="evenodd"
+ d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
+ clipRule="evenodd"
+ />
+ </svg>
+ ) : (
+ <svg
+ width="20"
+ height="20"
+ fill="currentColor"
+ viewBox="0 0 24 24"
+ style={{ opacity: 0.6 }}
+ >
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
+ </svg>
+ )}
+ <input
+ ref={inputRef}
+ type="text"
+ placeholder={
+ mode === 'search'
+ ? "Search documentation..."
+ : "Ask our agent to find relevant content (e.g., 'storage optimization', 'transaction handling')..."
+ }
+ value={query}
+ onChange={(e) => setQuery(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' && mode === 'agent') {
+ e.preventDefault();
+ handleSubmit();
+ }
+ }}
+ style={{
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ color: 'rgba(255, 255, 255, 0.9)',
+ fontSize: 16,
+ flex: 1,
+ minWidth: 0,
+ }}
+ />
+ {mode === 'agent' && query.trim() && !isAgentLoading && (
+ <button
+ type="submit"
+ style={{
+ padding: '8px 16px',
+ background: 'rgba(0, 191, 255, 0.2)',
+ border: '1px solid rgba(0, 191, 255, 0.4)',
+ borderRadius: 8,
+ color: '#00bfff',
+ fontSize: 14,
+ fontWeight: 500,
+ cursor: 'pointer',
+ transition: 'all 200ms ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.3)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.6)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'rgba(0, 191, 255, 0.2)';
+ e.currentTarget.style.borderColor = 'rgba(0, 191, 255, 0.4)';
+ }}
+ >
+ Ask Our Agent
+ </button>
+ )}
+ {(isLoading || isAgentLoading) && (
+ <div style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 14 }}>
+ {mode === 'search' ? 'Searching...' : 'Our agent is searching...'}
+ </div>
+ )}
+ </form>
+
+ {/* Keyboard shortcuts hint */}
+ <div style={{
+ display: 'flex',
+ gap: 8,
+ marginTop: 8,
+ fontSize: 11,
+ color: 'rgba(255, 255, 255, 0.5)'
+ }}>
+ <span>⌘K to open</span>
+ <span>⌘J to switch mode</span>
+ {mode === 'agent' && <span>Enter to ask our agent</span>}
+ <span>Esc to close</span>
+ </div>
+ </div>
+
+ {/* Results */}
+ <div
+ ref={resultsRef}
+ style={{
+ flex: 1,
+ overflowY: 'auto',
+ overflowX: 'hidden',
+ minHeight: 0
+ }}
+ >
+ {mode === 'search' ? (
+ // Search Results
+ results.length > 0 ? (
+ results.map((result, index) => (
+ <SearchResultItem
+ key={result.id}
+ result={result}
+ onClick={() => handleResultClick(result)}
+ />
+ ))
+ ) : query && !isLoading ? (
+ <div style={{ padding: '20px', textAlign: 'center', color: 'rgba(255, 255, 255, 0.6)' }}>
+ No results found for "{query}"
+ </div>
+ ) : !query ? (
+ <div style={{ padding: '20px', textAlign: 'center', color: 'rgba(255, 255, 255, 0.6)' }}>
+ Start typing to search...
+ </div>
+ ) : null
+ ) : (
+ // Agent Results
+ agentResults.length > 0 ? (
+ agentResults.map((result, index) => (
+ <div
+ key={result.id}
+ onClick={() => {
+ router.push(result.url);
+ setIsOpen(false); // Auto-close search bar
+ setQuery('');
+ setAgentResults([]);
+ }}
+ style={{
+ padding: '16px 20px',
+ borderBottom: '1px solid rgba(255, 255, 255, 0.05)',
+ cursor: 'pointer',
+ transition: 'background-color 0.2s ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'transparent';
+ }}
+ >
+ <div style={{
+ color: 'rgba(255, 255, 255, 0.9)',
+ fontSize: 16,
+ fontWeight: 500,
+ marginBottom: 8
+ }}>
+ {result.title}
+ {result.matchedSection && (
+ <span style={{
+ color: 'rgba(0, 191, 255, 0.8)',
+ fontSize: 14,
+ fontWeight: 400,
+ marginLeft: 8
+ }}>
+ → {result.matchedSection.title}
+ </span>
+ )}
+ </div>
+ <div
+ style={{
+ color: 'rgba(255, 255, 255, 0.7)',
+ fontSize: 14,
+ lineHeight: 1.4,
+ marginBottom: 8
+ }}
+ dangerouslySetInnerHTML={{
+ __html: result.highlightedExcerpt || result.excerpt
+ }}
+ />
+ <div style={{
+ color: 'rgba(0, 191, 255, 0.8)',
+ fontSize: 12,
+ display: 'flex',
+ alignItems: 'center',
+ gap: 8
+ }}>
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
+ </svg>
+ <span>Ask Our Agent</span>
+ <span style={{
+ background: 'rgba(255, 193, 7, 0.2)',
+ color: '#ffc107',
+ fontSize: 8,
+ padding: '1px 3px',
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'uppercase',
+ letterSpacing: '0.3px'
+ }}>
+ Beta
+ </span>
+ </div>
+ <span>•</span>
+ <span>{result.url}</span>
+ <span>•</span>
+ <span>Score: {result.relevanceScore.toFixed(1)}</span>
+ {result.matchedSection && (
+ <>
+ <span>•</span>
+ <span>📍 Section match</span>
+ </>
+ )}
+ </div>
+ </div>
+ ))
+ ) : query && !isAgentLoading ? (
+ <div style={{ padding: '20px', textAlign: 'center', color: 'rgba(255, 255, 255, 0.6)' }}>
+ No results found for "{query}"
+ </div>
+ ) : !query ? (
+ <div style={{ padding: '20px', textAlign: 'center', color: 'rgba(255, 255, 255, 0.6)' }}>
+ Start typing to ask our agent...
+ </div>
+ ) : null
+ )}
+ </div>
+ </div>
+ </div>
+ );
+}
+
+function SearchResultItem({ result, onClick }: { result: SearchResult; onClick: () => void }) {
+ const [data, setData] = useState<any>(null);
+
+ useEffect(() => {
+ async function fetchData() {
+ try {
+ const resultData = await result.data();
+ setData(resultData);
+ } catch (error) {
+ console.error('Error fetching result data:', error);
+ }
+ }
+ fetchData();
+ }, [result]);
+
+ if (!data) {
+ return (
+ <div style={{ padding: '16px 20px', color: 'rgba(255, 255, 255, 0.6)' }}>
+ Loading...
+ </div>
+ );
+ }
+
+ return (
+ <div
+ onClick={onClick}
+ style={{
+ padding: '16px 20px',
+ borderBottom: '1px solid rgba(255, 255, 255, 0.05)',
+ cursor: 'pointer',
+ transition: 'background-color 0.2s ease',
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = 'transparent';
+ }}
+ >
+ <div style={{ color: 'rgba(255, 255, 255, 0.9)', fontWeight: 600, marginBottom: 4 }}>
+ {data.meta.title}
+ </div>
+ {data.meta.description && (
+ <div style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 14, marginBottom: 8 }}>
+ {data.meta.description}
+ </div>
+ )}
+ <div
+ style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: 14, lineHeight: 1.4 }}
+ dangerouslySetInnerHTML={{
+ __html: data.highlightedExcerpt || data.excerpt
+ }}
+ />
+ <div style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: 12, marginTop: 8 }}>
+ {data.url.replace(/\.html$/, '').replace(/\/index$/, '')}
+ </div>
+ </div>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/SearchBar/SearchBar.tsx b/ecosystem/AI-Tooling/beacon/components/SearchBar/SearchBar.tsx
new file mode 100644
index 00000000..ca26cbba
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/SearchBar/SearchBar.tsx
@@ -0,0 +1,7 @@
+'use client'
+
+import { PagefindSearch } from './PagefindSearch';
+
+export function SearchBar() {
+ return <PagefindSearch />;
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/TypeScriptPlayground.tsx b/ecosystem/AI-Tooling/beacon/components/TypeScriptPlayground.tsx
new file mode 100644
index 00000000..7adefc35
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/TypeScriptPlayground.tsx
@@ -0,0 +1,800 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+/* TODO BUILD THIS OUT */
+
+import { useEffect, useState, useRef } from 'react';
+import { javascript } from '@codemirror/lang-javascript';
+import { IconPlayerPlay, IconTemplate, IconTrash, IconMessage, IconPlayerStop, IconMaximize, IconMinimize } from '@tabler/icons-react';
+import { vscodeDark } from '@uiw/codemirror-theme-vscode';
+import CodeMirror from '@uiw/react-codemirror';
+import { Box, Button, Group, Paper, Select, Stack, Text, Tooltip, Textarea, ActionIcon, Modal } from '@mantine/core';
+import ReactMarkdown from 'react-markdown';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
+
+interface Template {
+ value: string;
+ label: string;
+ code: string;
+}
+
+interface Message {
+ role: 'user' | 'assistant';
+ content: string;
+}
+
+interface CodeProps {
+ node?: any;
+ inline?: boolean;
+ className?: string;
+ children?: React.ReactNode;
+ [key: string]: any;
+}
+
+const EXAMPLE_TEMPLATES = [
+ {
+ value: 'default',
+ label: 'Welcome - Start Here',
+ code: `/**
+ * Welcome to the TypeScript Playground!
+ *
+ * This interactive environment allows you to test and experiment with TypeScript code.
+ * The code here runs in a sandboxed environment using the TypeScript compiler.
+ *
+ * Available Features:
+ * 1. TypeScript Type Checking
+ * 2. Code Execution
+ * 3. Error Reporting
+ * 4. AI Assistant Support
+ *
+ * Choose an example from the dropdown menu above to get started!
+ */
+
+// Example: Basic TypeScript Interface
+interface User {
+ id: number;
+ name: string;
+ email: string;
+ isActive: boolean;
+}
+
+// Example: Function with Type Safety
+function greetUser(user: User): string {
+ return \`Hello, \${user.name}! Your email is \${user.email}\`;
+}
+
+// Example: Using the Interface
+const user: User = {
+ id: 1,
+ name: "John Doe",
+ email: "john@example.com",
+ isActive: true
+};
+
+console.log(greetUser(user));`,
+ },
+ {
+ value: 'async_await',
+ label: 'Async/Await Example',
+ code: `/**
+ * Example: Using async/await with TypeScript
+ */
+
+// Define types for our API response
+interface Todo {
+ id: number;
+ title: string;
+ completed: boolean;
+}
+
+// Async function to fetch todos
+async function fetchTodos(): Promise<Todo[]> {
+ try {
+ const response = await fetch('https://jsonplaceholder.typicode.com/todos');
+ const data = await response.json();
+ return data as Todo[];
+ } catch (error) {
+ console.error('Error fetching todos:', error);
+ return [];
+ }
+}
+
+// Main function to demonstrate async/await
+async function main() {
+ console.log('Fetching todos...');
+ const todos = await fetchTodos();
+ console.log('First 3 todos:', todos.slice(0, 3));
+}
+
+// Run the example
+main();`,
+ },
+ {
+ value: 'generics',
+ label: 'Generics Example',
+ code: `/**
+ * Example: Using TypeScript Generics
+ */
+
+// Generic function to create an array of a specific type
+function createArray<T>(length: number, value: T): T[] {
+ return Array(length).fill(value);
+}
+
+// Generic class for a simple stack
+class Stack<T> {
+ private items: T[] = [];
+
+ push(item: T): void {
+ this.items.push(item);
+ }
+
+ pop(): T | undefined {
+ return this.items.pop();
+ }
+
+ peek(): T | undefined {
+ return this.items[this.items.length - 1];
+ }
+
+ isEmpty(): boolean {
+ return this.items.length === 0;
+ }
+}
+
+// Example usage
+const numberStack = new Stack<number>();
+numberStack.push(1);
+numberStack.push(2);
+numberStack.push(3);
+
+console.log('Stack operations:');
+console.log('Pop:', numberStack.pop());
+console.log('Peek:', numberStack.peek());
+console.log('Is empty:', numberStack.isEmpty());
+
+// Using the generic array creator
+const stringArray = createArray<string>(3, 'hello');
+console.log('String array:', stringArray);`,
+ },
+ {
+ value: 'decorators',
+ label: 'Decorators Example',
+ code: `/**
+ * Example: Using TypeScript Decorators
+ * Note: Decorators are an experimental feature
+ */
+
+// Simple method decorator
+function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
+ const originalMethod = descriptor.value;
+
+ descriptor.value = function (...args: any[]) {
+ console.log(\`Calling \${propertyKey} with args:\`, args);
+ const result = originalMethod.apply(this, args);
+ console.log(\`\${propertyKey} returned:\`, result);
+ return result;
+ };
+
+ return descriptor;
+}
+
+// Class using the decorator
+class Calculator {
+ @log
+ add(a: number, b: number): number {
+ return a + b;
+ }
+
+ @log
+ multiply(a: number, b: number): number {
+ return a * b;
+ }
+}
+
+// Example usage
+const calc = new Calculator();
+console.log('Calculator operations:');
+calc.add(5, 3);
+calc.multiply(4, 2);`,
+ },
+];
+
+export function TypeScriptPlayground({ templates }: { templates?: Template[] }) {
+ const codeTemplates = templates && Array.isArray(templates) ? templates : EXAMPLE_TEMPLATES;
+ const [code, setCode] = useState<string>(codeTemplates[0].code);
+ const [output, setOutput] = useState<string[]>(['Initializing TypeScript environment...']);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isRunning, setIsRunning] = useState(false);
+ const [showChat, setShowChat] = useState(false);
+ const [messages, setMessages] = useState<Message[]>([]);
+ const [input, setInput] = useState('');
+ const [isAiLoading, setIsAiLoading] = useState(false);
+ const [abortController, setAbortController] = useState<AbortController | null>(null);
+ const [isExpanded, setIsExpanded] = useState(false);
+ const expandTriggerRef = useRef<HTMLButtonElement | null>(null);
+ const lastFocusedRef = useRef<HTMLElement | null>(null);
+
+ const openExpanded = (from?: HTMLElement | null) => {
+ lastFocusedRef.current = from ?? (document.activeElement as HTMLElement | null);
+ setIsExpanded(true);
+ // focus textarea inside modal after open
+ setTimeout(() => {
+ const ta = document.querySelector('.cm-theme') as HTMLElement | null;
+ ta?.focus?.();
+ }, 50);
+ };
+
+ const closeExpanded = () => {
+ setIsExpanded(false);
+ setTimeout(() => {
+ try { lastFocusedRef.current?.focus?.(); } catch (err) {}
+ }, 0);
+ };
+
+ useEffect(() => {
+ if (!isExpanded) return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ closeExpanded();
+ }
+ };
+ document.addEventListener('keydown', onKey);
+ return () => document.removeEventListener('keydown', onKey);
+ }, [isExpanded]);
+
+ useEffect(() => {
+ // Initialize TypeScript environment
+ const initTypeScript = async () => {
+ try {
+ // Load TypeScript compiler
+ const script = document.createElement('script');
+ script.src = 'https://unpkg.com/typescript@latest/lib/typescript.js';
+ document.head.appendChild(script);
+
+ await new Promise((resolve) => {
+ script.onload = resolve;
+ });
+
+ setOutput(['✅ TypeScript environment ready']);
+ setIsLoading(false);
+ } catch (error) {
+ setOutput((prev) => [
+ ...prev,
+ `❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
+ ]);
+ setIsLoading(false);
+ }
+ };
+
+ initTypeScript();
+ }, []);
+
+ const runCode = async () => {
+ if (isLoading || isRunning) {
+ return;
+ }
+ setIsRunning(true);
+ setOutput(['▶ Running code...']);
+
+ try {
+ // Compile TypeScript to JavaScript
+ const jsCode = (window as any).ts.transpile(code, {
+ target: 'ES2020',
+ module: 'ES2020',
+ strict: true,
+ esModuleInterop: true,
+ skipLibCheck: true,
+ forceConsistentCasingInFileNames: true,
+ });
+
+ // Create a sandboxed environment with better console handling
+ const sandbox = `
+ const originalConsole = console;
+ const console = {
+ log: (...args) => {
+ window.parent.postMessage({ type: 'log', args: args.map(arg =>
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+ )}, '*');
+ originalConsole.log(...args);
+ },
+ error: (...args) => {
+ window.parent.postMessage({ type: 'error', args: args.map(arg =>
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+ )}, '*');
+ originalConsole.error(...args);
+ },
+ warn: (...args) => {
+ window.parent.postMessage({ type: 'log', args: args.map(arg =>
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+ )}, '*');
+ originalConsole.warn(...args);
+ },
+ info: (...args) => {
+ window.parent.postMessage({ type: 'log', args: args.map(arg =>
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+ )}, '*');
+ originalConsole.info(...args);
+ }
+ };
+
+ // Add error handling for uncaught errors
+ window.onerror = (message, source, lineno, colno, error) => {
+ console.error(\`Error: \${message}\nLine: \${lineno}, Column: \${colno}\`);
+ return true;
+ };
+
+ // Add promise rejection handling
+ window.onunhandledrejection = (event) => {
+ console.error(\`Unhandled Promise Rejection: \${event.reason}\`);
+ };
+
+ try {
+ ${jsCode}
+ } catch (error) {
+ console.error(error.message);
+ }
+ `;
+
+ // Create an iframe for sandboxed execution
+ const iframe = document.createElement('iframe');
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ // Handle messages from the sandbox
+ const messageHandler = (event: MessageEvent) => {
+ if (event.data.type === 'log') {
+ setOutput(prev => [...prev, ...event.data.args]);
+ } else if (event.data.type === 'error') {
+ setOutput(prev => [...prev, `❌ Error: ${event.data.args.join(' ')}`]);
+ }
+ };
+
+ window.addEventListener('message', messageHandler);
+
+ // Write the sandboxed code to the iframe
+ if (iframe.contentWindow) {
+ iframe.contentWindow.document.open();
+ iframe.contentWindow.document.write(`
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <script>
+ ${sandbox}
+ </script>
+ </head>
+ <body></body>
+ </html>
+ `);
+ iframe.contentWindow.document.close();
+ }
+
+ // Wait for any pending console output before cleanup
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Cleanup
+ window.removeEventListener('message', messageHandler);
+ document.body.removeChild(iframe);
+ setOutput(prev => [...prev, '✅ Code execution completed']);
+ setIsRunning(false);
+ } catch (error: any) {
+ setOutput((prev) => [...prev, `❌ Error: ${error.message}`]);
+ setIsRunning(false);
+ }
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!input.trim()) return;
+
+ const userMessage: Message = { role: 'user', content: input };
+ setMessages(prev => [...prev, userMessage]);
+ setInput('');
+ setIsAiLoading(true);
+
+ const controller = new AbortController();
+ setAbortController(controller);
+
+ try {
+ const response = await fetch('/api/chat', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages: [...messages, userMessage], code }),
+ signal: controller.signal
+ });
+
+ if (!response.ok) throw new Error('Failed to get response');
+
+ const reader = response.body?.getReader();
+ if (!reader) throw new Error('No reader available');
+
+ let assistantMessage = '';
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const text = new TextDecoder().decode(value);
+ assistantMessage += text;
+ setMessages(prev => {
+ const newMessages = [...prev];
+ const lastMessage = newMessages[newMessages.length - 1];
+ if (lastMessage.role === 'assistant') {
+ lastMessage.content = assistantMessage;
+ } else {
+ newMessages.push({ role: 'assistant', content: assistantMessage });
+ }
+ return newMessages;
+ });
+ }
+ } catch (error) {
+ if (error instanceof Error && error.name === 'AbortError') {
+ setMessages(prev => [...prev, { role: 'assistant', content: 'Response generation stopped.' }]);
+ } else {
+ console.error('Chat error:', error);
+ setMessages(prev => [...prev, { role: 'assistant', content: 'Sorry, there was an error processing your request.' }]);
+ }
+ } finally {
+ setIsAiLoading(false);
+ setAbortController(null);
+ }
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ if (input.trim() && !isAiLoading) {
+ handleSubmit(e as unknown as React.FormEvent);
+ }
+ }
+ };
+
+ const stopGeneration = () => {
+ if (abortController) {
+ abortController.abort();
+ setAbortController(null);
+ }
+ };
+
+ const clearOutput = () => setOutput([]);
+
+ const clearConversation = () => {
+ setMessages([]);
+ };
+
+ return (
+ <Paper withBorder p="md" radius="md">
+ <Stack>
+ <Group justify="space-between" align="center">
+ <Group>
+ <Tooltip label="Load example">
+ <Select
+ size="xs"
+ placeholder="Load example"
+ data={codeTemplates}
+ onChange={(value) =>
+ value && setCode(codeTemplates.find((t) => t.value === value)?.code || '')
+ }
+ leftSection={<IconTemplate size={14} />}
+ />
+ </Tooltip>
+ <Tooltip label="Ask AI Assistant">
+ <ActionIcon
+ variant="light"
+ onClick={() => setShowChat(!showChat)}
+ color={showChat ? "blue" : "gray"}
+ >
+ <IconMessage size={14} />
+ </ActionIcon>
+ </Tooltip>
+ </Group>
+ </Group>
+
+ <Box>
+ <CodeMirror
+ value={code}
+ height="400px"
+ theme={vscodeDark}
+ extensions={[javascript({ typescript: true })]}
+ onChange={setCode}
+ editable={!isLoading}
+ basicSetup={{
+ lineNumbers: true,
+ highlightActiveLineGutter: true,
+ highlightSpecialChars: true,
+ history: true,
+ foldGutter: true,
+ drawSelection: true,
+ dropCursor: true,
+ allowMultipleSelections: true,
+ indentOnInput: true,
+ syntaxHighlighting: true,
+ bracketMatching: true,
+ closeBrackets: true,
+ autocompletion: true,
+ rectangularSelection: true,
+ crosshairCursor: true,
+ highlightActiveLine: true,
+ highlightSelectionMatches: true,
+ closeBracketsKeymap: true,
+ defaultKeymap: true,
+ searchKeymap: true,
+ historyKeymap: true,
+ foldKeymap: true,
+ completionKeymap: true,
+ lintKeymap: true,
+ }}
+ />
+ </Box>
+
+ {showChat && (
+ <>
+ <Paper withBorder p="md" radius="md">
+ <Stack>
+ <Group justify="space-between" align="center">
+ <Text size="sm" fw={500}>AI Assistant</Text>
+ <Group>
+ <Tooltip label={isExpanded ? "Minimize" : "Expand"}>
+ <ActionIcon
+ ref={expandTriggerRef}
+ variant="light"
+ onClick={(e) => openExpanded(e.currentTarget)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ openExpanded(e.currentTarget as HTMLElement);
+ }
+ }}
+ disabled={isExpanded}
+ >
+ <IconMaximize size={14} />
+ </ActionIcon>
+ </Tooltip>
+ <Tooltip label="Clear conversation">
+ <ActionIcon
+ variant="light"
+ color="red"
+ onClick={clearConversation}
+ disabled={messages.length === 0}
+ >
+ <IconTrash size={14} />
+ </ActionIcon>
+ </Tooltip>
+ </Group>
+ </Group>
+ <Box style={{ maxHeight: '200px', overflowY: 'auto' }}>
+ {[...messages].reverse().map((message, i) => (
+ <Paper key={i} p="xs" mb="xs" bg={message.role === 'user' ? 'dark.6' : 'dark.7'}>
+ <Text size="sm" c={message.role === 'user' ? 'blue' : 'white'}>
+ {message.role === 'user' ? (
+ message.content
+ ) : (
+ <ReactMarkdown
+ components={{
+ code({ node, inline, className, children, ...props }: CodeProps) {
+ const match = /language-(\w+)/.exec(className || '');
+ return !inline && match ? (
+ <SyntaxHighlighter
+ style={vscDarkPlus}
+ language={match[1]}
+ PreTag="div"
+ {...props}
+ >
+ {String(children).replace(/\n$/, '')}
+ </SyntaxHighlighter>
+ ) : (
+ <code className={className} {...props}>
+ {children}
+ </code>
+ );
+ },
+ }}
+ >
+ {message.content}
+ </ReactMarkdown>
+ )}
+ </Text>
+ </Paper>
+ ))}
+ </Box>
+ <form onSubmit={handleSubmit}>
+ <Group>
+ <Textarea
+ placeholder="Ask about your code... (Press Enter to send, Shift+Enter for new line)"
+ value={input}
+ onChange={(e) => setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ style={{ flex: 1 }}
+ disabled={isAiLoading}
+ autosize
+ minRows={1}
+ maxRows={4}
+ />
+ {isAiLoading ? (
+ <Button
+ color="red"
+ onClick={stopGeneration}
+ leftSection={<IconPlayerStop size={14} />}
+ >
+ Stop
+ </Button>
+ ) : (
+ <Button type="submit">
+ Send
+ </Button>
+ )}
+ </Group>
+ </form>
+ </Stack>
+ </Paper>
+
+ <Modal
+ opened={isExpanded}
+ onClose={() => closeExpanded()}
+ size="xl"
+ title={
+ <Group justify="space-between" w="100%">
+ <Text size="sm" fw={500}>AI Assistant</Text>
+ <Group>
+ <Tooltip label="Minimize">
+ <ActionIcon
+ variant="light"
+ onClick={() => closeExpanded()}
+ >
+ <IconMinimize size={14} />
+ </ActionIcon>
+ </Tooltip>
+ <Tooltip label="Clear conversation">
+ <ActionIcon
+ variant="light"
+ color="red"
+ onClick={clearConversation}
+ disabled={messages.length === 0}
+ >
+ <IconTrash size={14} />
+ </ActionIcon>
+ </Tooltip>
+ </Group>
+ </Group>
+ }
+ styles={{
+ title: {
+ width: '100%',
+ marginRight: 0,
+ },
+ body: {
+ height: 'calc(100vh - 200px)',
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ }}
+ >
+ <Stack style={{ flex: 1, height: '100%' }}>
+ <Box style={{ flex: 1, overflowY: 'auto' }}>
+ {[...messages].reverse().map((message, i) => (
+ <Paper key={i} p="xs" mb="xs" bg={message.role === 'user' ? 'dark.6' : 'dark.7'}>
+ <Text size="sm" c={message.role === 'user' ? 'blue' : 'white'}>
+ {message.role === 'user' ? (
+ message.content
+ ) : (
+ <ReactMarkdown
+ components={{
+ code({ node, inline, className, children, ...props }: CodeProps) {
+ const match = /language-(\w+)/.exec(className || '');
+ return !inline && match ? (
+ <SyntaxHighlighter
+ style={vscDarkPlus}
+ language={match[1]}
+ PreTag="div"
+ {...props}
+ >
+ {String(children).replace(/\n$/, '')}
+ </SyntaxHighlighter>
+ ) : (
+ <code className={className} {...props}>
+ {children}
+ </code>
+ );
+ },
+ }}
+ >
+ {message.content}
+ </ReactMarkdown>
+ )}
+ </Text>
+ </Paper>
+ ))}
+ </Box>
+ <form onSubmit={handleSubmit}>
+ <Group>
+ <Textarea
+ placeholder="Ask about your code... (Press Enter to send, Shift+Enter for new line)"
+ value={input}
+ onChange={(e) => setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ style={{ flex: 1 }}
+ disabled={isAiLoading}
+ autosize
+ minRows={1}
+ maxRows={4}
+ />
+ {isAiLoading ? (
+ <Button
+ color="red"
+ onClick={stopGeneration}
+ leftSection={<IconPlayerStop size={14} />}
+ >
+ Stop
+ </Button>
+ ) : (
+ <Button type="submit">
+ Send
+ </Button>
+ )}
+ </Group>
+ </form>
+ </Stack>
+ </Modal>
+ </>
+ )}
+
+ <Group justify="space-between">
+ <Button
+ onClick={runCode}
+ loading={isRunning}
+ disabled={isLoading}
+ color="blue"
+ leftSection={<IconPlayerPlay size={14} />}
+ >
+ {isRunning ? 'Running...' : 'Run'}
+ </Button>
+ <Button
+ onClick={clearOutput}
+ variant="light"
+ disabled={isLoading || output.length === 0}
+ leftSection={<IconTrash size={14} />}
+ >
+ Clear Output
+ </Button>
+ </Group>
+
+ <Paper
+ withBorder
+ p="xs"
+ style={{
+ backgroundColor: '#1e1e1e',
+ color: '#33ff00',
+ fontFamily: 'monospace',
+ height: '200px',
+ overflow: 'auto',
+ }}
+ >
+ {output.map((line, i) => (
+ <Text key={i} style={{ whiteSpace: 'pre-wrap', color: 'inherit' }}>
+ {line}
+ </Text>
+ ))}
+ </Paper>
+ </Stack>
+ </Paper>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.module.css b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.module.css
new file mode 100644
index 00000000..e35538ce
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.module.css
@@ -0,0 +1,32 @@
+.title {
+ color: white;
+ font-size: 72px;
+ line-height: 1.05;
+ letter-spacing: -0.02em;
+
+ @media (max-width: 768px) {
+ font-size: 44px;
+ }
+}
+
+.subtitle {
+ font-size: 80px;
+ line-height: 1.05;
+ letter-spacing: -0.03em;
+}
+
+.hero {
+ position: relative;
+ padding-top: 96px;
+}
+
+.halo {
+ position: absolute;
+ inset: 0;
+ background:
+ radial-gradient(600px 200px at 50% 0%, rgba(0, 102, 255, 0.15), transparent 70%),
+ radial-gradient(300px 150px at 10% 30%, rgba(255, 0, 128, 0.12), transparent 70%),
+ radial-gradient(300px 150px at 90% 30%, rgba(255, 196, 0, 0.12), transparent 70%);
+ pointer-events: none;
+ mask-image: radial-gradient(closest-side, rgba(0,0,0,0.6), transparent 70%);
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.story.tsx b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.story.tsx
new file mode 100644
index 00000000..0dfa9cc7
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.story.tsx
@@ -0,0 +1,26 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { Welcome } from './Welcome';
+
+export default {
+ title: 'Welcome',
+};
+
+export const Usage = () => <Welcome />;
diff --git a/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.test.tsx b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.test.tsx
new file mode 100644
index 00000000..4a83aa2a
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.test.tsx
@@ -0,0 +1,31 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { render, screen } from '@/test-utils';
+import { Welcome } from './Welcome';
+
+describe('Welcome component', () => {
+ it('has correct Next.js theming section link', () => {
+ render(<Welcome />);
+ expect(screen.getByText('this guide')).toHaveAttribute(
+ 'href',
+ 'https://mantine.dev/guides/next/'
+ );
+ });
+});
diff --git a/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.tsx b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.tsx
new file mode 100644
index 00000000..79c63444
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/Welcome/Welcome.tsx
@@ -0,0 +1,91 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import { TextAnimate } from '@gfazioli/mantine-text-animate';
+import { IconBrandGithub, IconExternalLink } from '@tabler/icons-react';
+import { Anchor, Button, Center, Paper, Text, Title, Stack } from '@mantine/core';
+import pack from '../../package.json';
+import classes from './Welcome.module.css';
+
+export function Welcome() {
+ return (
+ <>
+ <div className={classes.hero}>
+ <div className={classes.halo} />
+ <Stack gap="sm" align="center" mt={64}>
+ <Title maw="900px" mx="auto" ta="center">
+ <span className={classes.title}>Next‑gen docs for </span>
+ <Text component="span" variant="gradient" gradient={{ from: 'pink', to: 'yellow' }} className={classes.subtitle}>
+ NexRes
+ </Text>
+ </Title>
+
+ <Text c="dimmed" ta="center" size="lg" maw={720} mx="auto">
+ Find all documentation related to ResilientDB, its applications, and ecosystem tools
+ supported by the ResilientDB team. This site is your gateway to high-performance
+ blockchain infrastructure, developer guides, and integration resources. To learn more
+ about ResilientDB, visit <Anchor href="https://resilientdb.com/">the official website</Anchor>.
+ </Text>
+
+ <Center>
+ <Button
+ href="https://resilientdb.incubator.apache.org/"
+ component="a"
+ rightSection={<IconExternalLink />}
+ leftSection={<IconBrandGithub />}
+ variant="gradient"
+ gradient={{ from: 'blue', to: 'grape' }}
+ px={28}
+ radius={999}
+ size="md"
+ mx="auto"
+ mt="sm"
+ >
+ Latest Release v{pack.version}
+ </Button>
+ </Center>
+ </Stack>
+ </div>
+
+ <Paper shadow="xl" p={8} mih={300} my={32} bg="dark.9" mx="auto" radius={12}>
+ <TextAnimate.Typewriter
+ inherit
+ fz={11}
+ c="green.5"
+ ff="monospace"
+ multiline
+ delay={100}
+ loop={false}
+ value={[
+ 'Dependencies:',
+ ...Object.keys(pack.dependencies).map(
+ (key: string) =>
+ `• ${key} : ${pack.dependencies[key as keyof typeof pack.dependencies].toString()}`
+ ),
+ '',
+ 'Each dependency is a block in your development chain.',
+ 'Keep your stack resilient!',
+ ]}
+ />
+ </Paper>
+ </>
+ );
+}
diff --git a/ecosystem/AI-Tooling/beacon/components/cache/NodeConfigGenerator.tsx b/ecosystem/AI-Tooling/beacon/components/cache/NodeConfigGenerator.tsx
new file mode 100644
index 00000000..8d93b5b2
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/cache/NodeConfigGenerator.tsx
@@ -0,0 +1,155 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import React, { useEffect, useState } from 'react';
+import { Box, TextInput, Switch, NumberInput, Code, Paper } from '@mantine/core';
+
+export function NodeConfigGenerator() {
+ const [config, setConfig] = useState({
+ mongoUri: 'mongodb://localhost:27017',
+ dbName: 'resilientdb_cache',
+ collectionName: 'blocks',
+ baseUrl: 'resilientdb://crow.resilientdb.com',
+ httpSecure: true,
+ wsSecure: true,
+ reconnectInterval: 5000,
+ fetchInterval: 30000,
+ });
+
+ const [generatedCode, setGeneratedCode] = useState('');
+
+ const handleNumberChange = (
+ field: 'reconnectInterval' | 'fetchInterval',
+ value: number | string | undefined
+ ) => {
+ const numValue = typeof value === 'string' ? parseInt(value, 10) : value;
+ if (numValue) {
+ setConfig({ ...config, [field]: numValue });
+ }
+ };
+
+ const generateCode = () => {
+ const code = `import { WebSocketMongoSync } from 'resilient-node-cache';
+
+// MongoDB Configuration
+const mongoConfig = {
+ uri: "${config.mongoUri}",
+ dbName: "${config.dbName}",
+ collectionName: "${config.collectionName}"
+};
+
+// ResilientDB Configuration
+const resilientDBConfig = {
+ baseUrl: "${config.baseUrl}",
+ httpSecure: ${config.httpSecure},
+ wsSecure: ${config.wsSecure},
+ reconnectInterval: ${config.reconnectInterval},
+ fetchInterval: ${config.fetchInterval}
+};
+
+// Initialize sync
+const sync = new WebSocketMongoSync(mongoConfig, resilientDBConfig);`;
+
+ setGeneratedCode(code);
+ };
+
+ useEffect(() => {
+ generateCode();
+ }, [config]);
+
+ return (
+ <Box>
+ <Box mb="md">
+ <TextInput
+ label="MongoDB URI"
+ description="Required: Connection string for MongoDB instance"
+ value={config.mongoUri}
+ onChange={(e) => setConfig({ ...config, mongoUri: e.target.value })}
+ placeholder="mongodb://localhost:27017"
+ required
+ />
+ <TextInput
+ label="Database Name"
+ description="Required: Name of the database to store blocks"
+ value={config.dbName}
+ onChange={(e) => setConfig({ ...config, dbName: e.target.value })}
+ placeholder="resilientdb_cache"
+ required
+ />
+ <TextInput
+ label="Collection Name"
+ description="Required: Name of the collection to store blocks"
+ value={config.collectionName}
+ onChange={(e) => setConfig({ ...config, collectionName: e.target.value })}
+ placeholder="blocks"
+ required
+ />
+ <TextInput
+ label="ResilientDB Base URL"
+ description="Required: URL of your ResilientDB node"
+ value={config.baseUrl}
+ onChange={(e) => setConfig({ ...config, baseUrl: e.target.value })}
+ placeholder="resilientdb://crow.resilientdb.com"
+ required
+ />
+ <Switch
+ label="Use HTTPS"
+ description="Required: Use HTTPS for REST API calls"
+ checked={config.httpSecure}
+ onChange={(e) => setConfig({ ...config, httpSecure: e.target.checked })}
+ mt="md"
+ />
+ <Switch
+ label="Use WSS"
+ description="Required: Use WSS for WebSocket connections"
+ checked={config.wsSecure}
+ onChange={(e) => setConfig({ ...config, wsSecure: e.target.checked })}
+ mt="xs"
+ />
+ <NumberInput
+ label="Reconnect Interval (ms)"
+ description="Optional: How often to attempt reconnection if connection is lost"
+ value={config.reconnectInterval}
+ onChange={(val) => handleNumberChange('reconnectInterval', val)}
+ min={1000}
+ max={60000}
+ step={1000}
+ mt="md"
+ defaultValue={5000}
+ />
+ <NumberInput
+ label="Fetch Interval (ms)"
+ description="Optional: How often to sync data from ResilientDB"
+ value={config.fetchInterval}
+ onChange={(val) => handleNumberChange('fetchInterval', val)}
+ min={5000}
+ max={300000}
+ step={5000}
+ defaultValue={30000}
+ />
+ </Box>
+
+ <Paper withBorder p="xs">
+ <Code block>{generatedCode}</Code>
+ </Paper>
+ </Box>
+ );
+}
\ No newline at end of file
diff --git a/ecosystem/AI-Tooling/beacon/components/cache/NodeQueryBuilder.tsx b/ecosystem/AI-Tooling/beacon/components/cache/NodeQueryBuilder.tsx
new file mode 100644
index 00000000..7bea1e07
--- /dev/null
+++ b/ecosystem/AI-Tooling/beacon/components/cache/NodeQueryBuilder.tsx
@@ -0,0 +1,145 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+'use client';
+
+import React, { useState } from 'react';
+import { Box, Button, Code, Paper, Select, Text, TextInput } from '@mantine/core';
+
+const SAMPLE_DATA = {
+ id: 123, // Block number
+ transactions: [
+ {
+ key: "test_key",
+ value: {
+ id: "tx_123",
+ data: "Sample transaction data",
+ timestamp: 1746736411543
+ }
+ },
+ {
+ key: "config_key",
+ value: {
+ setting: "value",
+ enabled: true
+ }
+ }
+ ]
+};
+
+export function NodeQueryBuilder() {
+ const [field, setField] = useState('id');
+ const [operator, setOperator] = useState('eq');
+ const [value, setValue] = useState('');
+ const [query, setQuery] = useState('');
+ const [result, setResult] = useState('');
+
+ const buildQuery = () => {
+ let mongoQuery = '{\n';
+ if (operator === 'eq') {
+ // Check if the value is a number
+ const numValue = Number(value);
+ if (!isNaN(numValue) && field === 'id') {
+ mongoQuery += ` "${field}": ${numValue}\n`;
+ } else {
+ mongoQuery += ` "${field}": "${value}"\n`;
+ }
+ } else if (operator === 'gt' || operator === 'lt') {
+ const numValue = Number(value);
+ if (!isNaN(numValue)) {
+ mongoQuery += ` "${field}": { "$${operator}": ${numValue} }\n`;
+ } else {
+ mongoQuery += ` "${field}": { "$${operator}": "${value}" }\n`;
+ }
+ } else if (operator === 'in') {
+ mongoQuery += ` "${field}": { "$in": ${value.split(',').map((v) => {
+ const numValue = Number(v.trim());
+ return !isNaN(numValue) ? numValue : `"${v.trim()}"`;
+ })} }\n`;
+ } else if (operator === 'regex') {
+ mongoQuery += ` "${field}": { "$regex": "${value}", "$options": "i" }\n`;
+ } else if (operator === 'exists') {
+ mongoQuery += ` "${field}": { "$exists": ${value === 'true'} }\n`;
+ }
+ mongoQuery += '}';
+
+ setQuery(mongoQuery);
+ setResult(JSON.stringify(SAMPLE_DATA, null, 2));
+ };
+
+ return (
+ <Box>
+ <Box mb="md">
+ <Select
+ label="Field"
+ value={field}
+ onChange={(val) => setField(val || 'id')}
+ data={[
+ { value: 'id', label: 'Block ID' },
+ { value: 'transactions.key', label: 'Transaction Key' },
+ { value: 'transactions.value.id', label: 'Transaction ID' },
+ { value: 'transactions.value.timestamp', label: 'Transaction Timestamp' },
+ { value: 'transactions.value.data', label: 'Transaction Data' }
+ ]}
+ />
+ <Select
+ label="Operator"
+ value={operator}
+ onChange={(val) => setOperator(val || 'eq')}
+ data={[
+ { value: 'eq', label: 'Equals' },
+ { value: 'gt', label: 'Greater Than' },
+ { value: 'lt', label: 'Less Than' },
+ { value: 'in', label: 'In Array' },
... 57256 lines suppressed ...