From 6bb756fdd750709c6679e5f2d56a50de10beec8e Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Fri, 19 Dec 2025 16:13:39 +0700 Subject: [PATCH] Lastest change --- README-DEBRAND.md | 86 +- package-lock.json | 4 +- package.json | 2 +- scripts/frontend-debrand.sh | 295 +++++ scripts/update-and-debrand.sh | 172 ++- src/app/TitleBar.tsx | 162 +-- src/components/AppUpgrades.tsx | 2 +- src/components/AutoUpdateSwitch.tsx | 6 +- src/components/ChatInputControls.tsx | 5 - src/components/CommunityCodeConsentDialog.tsx | 2 +- src/components/ContextFilesPicker.tsx | 41 +- ....tsx => DyadProSuccessDialog.tsx.disabled} | 0 src/components/ErrorBoundary.tsx | 6 +- src/components/HelpBotDialog.tsx | 4 +- src/components/HelpDialog.tsx | 10 +- src/components/ImportAppDialog.tsx | 6 +- src/components/ModelPicker.tsx | 6 +- src/components/ProBanner.tsx | 16 +- src/components/ProModeSelector.tsx | 10 +- src/components/ReleaseChannelSelector.tsx | 6 +- src/components/SetupBanner.tsx | 31 +- src/components/SetupProviderCard.tsx | 11 +- src/components/TelemetryBanner.tsx | 2 +- src/components/app-sidebar.tsx | 2 +- src/components/chat/ChatErrorBox.tsx | 18 +- src/components/chat/ChatInput.tsx | 2 +- src/components/chat/ChatMessage.tsx | 6 +- src/components/chat/DyadAddDependency.tsx | 4 +- src/components/chat/DyadAddIntegration.tsx | 4 +- src/components/chat/DyadCodeSearch.tsx | 4 +- src/components/chat/DyadCodeSearchResult.tsx | 4 +- src/components/chat/DyadCodebaseContext.tsx | 4 +- src/components/chat/DyadDelete.tsx | 4 +- src/components/chat/DyadEdit.tsx | 4 +- src/components/chat/DyadExecuteSql.tsx | 4 +- src/components/chat/DyadMarkdownParser.tsx | 120 +- src/components/chat/DyadMcpToolCall.tsx | 4 +- src/components/chat/DyadMcpToolResult.tsx | 4 +- src/components/chat/DyadOutput.tsx | 4 +- src/components/chat/DyadRead.tsx | 4 +- src/components/chat/DyadRename.tsx | 4 +- src/components/chat/DyadSearchReplace.tsx | 4 +- src/components/chat/DyadThink.tsx | 12 +- src/components/chat/DyadTokenSavings.tsx | 4 +- src/components/chat/DyadWebCrawl.tsx | 4 +- src/components/chat/DyadWebSearch.tsx | 4 +- src/components/chat/DyadWebSearchResult.tsx | 6 +- src/components/chat/DyadWrite.tsx | 4 +- src/components/chat/HomeChatInput.tsx | 2 +- src/components/chat/LexicalChatInput.tsx | 2 +- .../chat/MoreMinimoreAddDependency.tsx | 93 ++ .../chat/MoreMinimoreAddIntegration.tsx | 89 ++ .../chat/MoreMinimoreCodeSearch.tsx | 31 + .../chat/MoreMinimoreCodeSearchResult.tsx | 123 ++ .../chat/MoreMinimoreCodebaseContext.tsx | 117 ++ src/components/chat/MoreMinimoreDelete.tsx | 45 + src/components/chat/MoreMinimoreEdit.tsx | 113 ++ .../chat/MoreMinimoreExecuteSql.tsx | 85 ++ .../chat/MoreMinimoreMarkdownParser.tsx | 610 ++++++++++ .../chat/MoreMinimoreMcpToolCall.tsx | 73 ++ .../chat/MoreMinimoreMcpToolResult.tsx | 73 ++ src/components/chat/MoreMinimoreOutput.tsx | 112 ++ .../chat/MoreMinimoreProblemSummary.tsx | 156 +++ src/components/chat/MoreMinimoreRead.tsx | 44 + src/components/chat/MoreMinimoreRename.tsx | 61 + .../chat/MoreMinimoreSearchReplace.tsx | 151 +++ src/components/chat/MoreMinimoreThink.tsx | 96 ++ .../chat/MoreMinimoreTokenSavings.tsx | 36 + src/components/chat/MoreMinimoreWebCrawl.tsx | 27 + src/components/chat/MoreMinimoreWebSearch.tsx | 31 + .../chat/MoreMinimoreWebSearchResult.tsx | 78 ++ src/components/chat/MoreMinimoreWrite.tsx | 159 +++ src/components/chat/PromoMessage.tsx | 12 +- src/components/chat/TokenBar.tsx | 2 +- src/components/home/OnboardingBanner.tsx | 4 +- .../preview_panel/AnnotatorOnlyForPro.tsx | 4 +- .../preview_panel/PreviewIframe.tsx | 21 +- .../preview_panel/PreviewIframe.tsx.bak | 1081 +++++++++++++++++ .../preview_panel/SecurityPanel.tsx | 2 +- .../settings/ApiKeyConfiguration.tsx | 8 +- .../settings/AzureConfiguration.tsx | 6 +- .../settings/ProviderSettingsHeader.tsx | 16 +- .../settings/ProviderSettingsPage.tsx | 32 +- src/ipc/handlers/chat_stream_handlers.ts | 2 +- src/ipc/ipc_client.ts | 4 + src/ipc/ipc_host.ts | 10 + src/ipc/shared/language_model_constants.ts | 6 +- src/ipc/utils/get_model_client.ts | 2 +- src/main.ts | 8 +- src/pages/home.tsx | 3 +- src/shared/templates.ts | 10 + 91 files changed, 4341 insertions(+), 422 deletions(-) create mode 100755 scripts/frontend-debrand.sh rename src/components/{DyadProSuccessDialog.tsx => DyadProSuccessDialog.tsx.disabled} (100%) create mode 100644 src/components/chat/MoreMinimoreAddDependency.tsx create mode 100644 src/components/chat/MoreMinimoreAddIntegration.tsx create mode 100644 src/components/chat/MoreMinimoreCodeSearch.tsx create mode 100644 src/components/chat/MoreMinimoreCodeSearchResult.tsx create mode 100644 src/components/chat/MoreMinimoreCodebaseContext.tsx create mode 100644 src/components/chat/MoreMinimoreDelete.tsx create mode 100644 src/components/chat/MoreMinimoreEdit.tsx create mode 100644 src/components/chat/MoreMinimoreExecuteSql.tsx create mode 100644 src/components/chat/MoreMinimoreMarkdownParser.tsx create mode 100644 src/components/chat/MoreMinimoreMcpToolCall.tsx create mode 100644 src/components/chat/MoreMinimoreMcpToolResult.tsx create mode 100644 src/components/chat/MoreMinimoreOutput.tsx create mode 100644 src/components/chat/MoreMinimoreProblemSummary.tsx create mode 100644 src/components/chat/MoreMinimoreRead.tsx create mode 100644 src/components/chat/MoreMinimoreRename.tsx create mode 100644 src/components/chat/MoreMinimoreSearchReplace.tsx create mode 100644 src/components/chat/MoreMinimoreThink.tsx create mode 100644 src/components/chat/MoreMinimoreTokenSavings.tsx create mode 100644 src/components/chat/MoreMinimoreWebCrawl.tsx create mode 100644 src/components/chat/MoreMinimoreWebSearch.tsx create mode 100644 src/components/chat/MoreMinimoreWebSearchResult.tsx create mode 100644 src/components/chat/MoreMinimoreWrite.tsx create mode 100644 src/components/preview_panel/PreviewIframe.tsx.bak diff --git a/README-DEBRAND.md b/README-DEBRAND.md index 63c4668..e64b34c 100644 --- a/README-DEBRAND.md +++ b/README-DEBRAND.md @@ -1,3 +1,84 @@ +# MoreMinimore + +MoreMinimore is a local, open-source AI app builder. It's fast, private, and fully under your control โ€” like Lovable, v0, or Bolt, but running right on your machine. + +![Image](https://github.com/user-attachments/assets/f6c83dfc-6ffd-4d32-93dd-4b9c46d17790) + +## ๐Ÿš€ Features + +- โšก๏ธ **Local**: Fast, private and no lock-in. +- ๐Ÿ›  **Bring your own keys**: Use your own AI API keys โ€” no vendor lock-in. +- ๐Ÿ–ฅ๏ธ **Cross-platform**: Easy to run on Mac or Windows. +- โœจ **Enhanced**: Smart context and unlimited usage with custom features. + +## ๐Ÿงฐ Prerequisites + +- Node.js >= 20 +- npm (comes with Node.js) +- Git + +You can verify your versions: + +```bash +node -v +npm -v +``` + +## ๐Ÿ—๏ธ Install (from source) + +```bash +git clone https://github.com/kunthawat/moreminimore-vibe.git +cd moreminimore-vibe +npm install +``` + +## โ–ถ๏ธ Run locally (development) + +- Start the app with the default configuration: + +```bash +npm start +``` + +### Environment variables (optional) + +- `MOREMINIMORE_GATEWAY_URL`: URL of a compatible gateway if you prefer to route requests. + +Example: + +```bash +MOREMINIMORE_GATEWAY_URL=http://localhost:8080/v1 npm start +``` + +## ๐Ÿ“ฆ Build installers (make) + +Create platform-specific distributables: + +```bash +npm run make +``` + +Outputs are written to the `out/` directory. + +## ๐Ÿงช Tests and linting + +```bash +# Unit tests +npm test + +# Lint +npm run lint + +# Prettier check +npm run prettier:check +``` + +## ๐Ÿ“„ License + +MIT License โ€” see [LICENSE](./LICENSE). + +--- + # MoreMinimore Debranding and Custom Features Guide This guide explains how to remove Dyad branding and dependencies from the codebase and integrate custom features for MoreMinimore. @@ -174,12 +255,9 @@ Edit `src/preload.ts`: } ``` -Replace the dependency: +Keep the original dependency (no changes needed): ```json -// FROM: "@dyad-sh/supabase-management-js": "v1.0.1", -// TO: -"@moreminimore/supabase-management-js": "v1.0.1", ``` ### Update Protocol Handlers diff --git a/package-lock.json b/package-lock.json index b0ce051..5f3375a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "dyad", + "name": "moreminimore", "version": "0.31.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "dyad", + "name": "moreminimore", "version": "0.31.0-beta.1", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index d156168..dddaf3d 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@ai-sdk/xai": "^2.0.16", "@babel/parser": "^7.28.5", "@biomejs/biome": "^1.9.4", - "@moreminimore/supabase-management-js": "v1.0.1", + "@dyad-sh/supabase-management-js": "v1.0.1", "@lexical/react": "^0.33.1", "@modelcontextprotocol/sdk": "^1.17.5", "@monaco-editor/react": "^4.7.0-rc.0", diff --git a/scripts/frontend-debrand.sh b/scripts/frontend-debrand.sh new file mode 100755 index 0000000..351eecf --- /dev/null +++ b/scripts/frontend-debrand.sh @@ -0,0 +1,295 @@ +#!/bin/bash + +# Frontend Debranding Script for MoreMinimore +# This script handles comprehensive frontend debranding and UI fixes +# Usage: ./scripts/frontend-debrand.sh + +set -e # Exit on any error + +echo "๐ŸŽจ Starting frontend debranding process..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if we're in the right directory +if [ ! -f "package.json" ] || [ ! -d "src" ]; then + print_error "Please run this script from the project root directory" + exit 1 +fi + +# Function to fix TitleBar component +fix_titlebar() { + print_status "Fixing TitleBar component..." + + if [ -f "src/app/TitleBar.tsx" ]; then + # Create a backup + cp src/app/TitleBar.tsx src/app/TitleBar.tsx.bak + + # Remove Dyad Pro related imports and components + sed -i '' '/import.*DyadProSuccessDialog/d' src/app/TitleBar.tsx + sed -i '' '/import.*useUserBudgetInfo/d' src/app/TitleBar.tsx + sed -i '' '/import.*UserBudgetInfo/d' src/app/TitleBar.tsx + + # Remove Dyad Pro button and related logic + sed -i '' '/const isDyadPro =/d' src/app/TitleBar.tsx + sed -i '' '/const isDyadProEnabled =/d' src/app/TitleBar.tsx + sed -i '' '/showDyadProSuccessDialog/d' src/app/TitleBar.tsx + sed -i '' '/DyadProSuccessDialog/,/<\/DyadProSuccessDialog>/d' src/app/TitleBar.tsx + sed -i '' '/{isDyadPro && {\ + return {\ + totalCredits: 1000,\ + usedCredits: 0,\ + resetDate: new Date().toISOString()\ + };\ + });' src/ipc/ipc_host.ts + fi + + rm src/ipc/ipc_host.ts.bak + print_success "Added missing user budget handler" + fi +} + +# Function to update preload script +update_preload() { + print_status "Updating preload script..." + + if [ -f "src/preload.ts" ]; then + cp src/preload.ts src/preload.ts.bak + + # Add get-user-budget to preload if not present + if ! grep -q "get-user-budget" src/preload.ts; then + sed -i '' '/"get-system-debug-info":/a\ + "get-user-budget": () => ipcRenderer.invoke("get-user-budget"),' src/preload.ts + fi + + rm src/preload.ts.bak + print_success "Updated preload script" + fi +} + +# Function to update IPC client +update_ipc_client() { + print_status "Updating IPC client..." + + if [ -f "src/ipc/ipc_client.ts" ]; then + cp src/ipc/ipc_client.ts src/ipc/ipc_client.ts.bak + + # Add getUserBudget method if not present + if ! grep -q "getUserBudget" src/ipc/ipc_client.ts; then + sed -i '' '/async getSystemDebugInfo()/a\ +\ + async getUserBudget() {\ + return this.ipcRenderer.invoke("get-user-budget");\ + }' src/ipc/ipc_client.ts + fi + + rm src/ipc/ipc_client.ts.bak + print_success "Updated IPC client" + fi +} + +# Function to test compilation +test_compilation() { + print_status "Testing compilation..." + + if command -v npm &> /dev/null; then + if npm run ts 2>/dev/null; then + print_success "TypeScript compilation successful" + else + print_warning "TypeScript compilation failed. Check the errors above." + fi + fi +} + +# Main execution +main() { + print_status "Starting frontend debranding..." + + fix_titlebar + remove_pro_dialog + fix_context_files_picker + fix_token_savings + fix_dyad_think + update_smart_context_settings + fix_remaining_references + add_missing_handlers + update_preload + update_ipc_client + test_compilation + + print_success "๐ŸŽ‰ Frontend debranding completed!" + echo "" + echo "Summary of changes:" + echo "โœ… Fixed TitleBar component (removed Pro elements)" + echo "โœ… Removed DyadProSuccessDialog component" + echo "โœ… Fixed ContextFilesPicker component" + echo "โœ… Fixed DyadTokenSavings component" + echo "โœ… Fixed DyadThink component" + echo "โœ… Updated smart context settings" + echo "โœ… Fixed remaining Dyad references" + echo "โœ… Added missing IPC handlers" + echo "โœ… Updated preload script" + echo "โœ… Updated IPC client" + echo "" + echo "Next steps:" + echo "1. Test the application with 'npm start'" + echo "2. Verify all UI elements are properly debranded" + echo "3. Test smart context functionality" + echo "" + print_warning "Please test the application thoroughly before committing changes." +} + +# Run main function +main "$@" diff --git a/scripts/update-and-debrand.sh b/scripts/update-and-debrand.sh index f1d8804..fea60e3 100755 --- a/scripts/update-and-debrand.sh +++ b/scripts/update-and-debrand.sh @@ -144,18 +144,44 @@ remove_pro_features() { rm src/preload.ts.bak print_success "Removed pro IPC channels" fi + + # Remove ProModeSelector from ChatInputControls + if [ -f "src/components/ChatInputControls.tsx" ]; then + sed -i.bak '/import { ProModeSelector } from ".\/ProModeSelector";/d' src/components/ChatInputControls.tsx + sed -i.bak '//d' src/components/ChatInputControls.tsx + sed -i.bak '/
<\/div>/d' src/components/ChatInputControls.tsx + rm src/components/ChatInputControls.tsx.bak + print_success "Removed ProModeSelector from chat controls" + fi + + # Remove Pro restrictions from PreviewIframe (Annotator) + if [ -f "src/components/preview_panel/PreviewIframe.tsx" ]; then + sed -i.bak '/import { AnnotatorOnlyForPro } from ".\/AnnotatorOnlyForPro";/d' src/components/preview_panel/PreviewIframe.tsx + sed -i.bak '/{userBudget ? (/,/)} : (/,//c\ + ' src/components/preview_panel/PreviewIframe.tsx + rm src/components/preview_panel/PreviewIframe.tsx.bak + print_success "Removed Pro restrictions from Annotator" + fi + + # Comment out ProBanner in home.tsx + if [ -f "src/pages/home.tsx" ]; then + sed -i.bak 's|// import { ProBanner } from "@/components/ProBanner";|// import { ProBanner } from "@/components/ProBanner";|g' src/pages/home.tsx + sed -i.bak 's|{}|// {}|g' src/pages/home.tsx + rm src/pages/home.tsx.bak + print_success "Commented out ProBanner" + fi } # Function to update branding update_branding() { print_status "Updating branding from Dyad to MoreMinimore..." - # Update package.json - if [ -f "package.json" ]; then - sed -i.bak 's/@dyad-sh\/supabase-management-js/@moreminimore\/supabase-management-js/g' package.json - rm package.json.bak - print_success "Updated package.json branding" - fi + # Package.json already updated - keeping original @dyad-sh/supabase-management-js + print_success "Package.json configuration maintained" # Update app name in main.ts if [ -f "src/main.ts" ]; then @@ -192,6 +218,121 @@ update_ui_text() { print_success "Updated UI text" } +# Function to update component names from Dyad to MoreMinimore +update_component_names() { + print_status "Updating component names from Dyad to MoreMinimore..." + + # Update component imports and exports + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadThink/MoreMinimoreThink/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadTokenSavings/MoreMinimoreTokenSavings/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadCodebaseContext/MoreMinimoreCodebaseContext/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadMarkdownParser/MoreMinimoreMarkdownParser/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadEdit/MoreMinimoreEdit/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadWrite/MoreMinimoreWrite/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadRead/MoreMinimoreRead/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadRename/MoreMinimoreRename/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadDelete/MoreMinimoreDelete/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadSearchReplace/MoreMinimoreSearchReplace/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadCodeSearch/MoreMinimoreCodeSearch/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadCodeSearchResult/MoreMinimoreCodeSearchResult/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadWebSearch/MoreMinimoreWebSearch/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadWebSearchResult/MoreMinimoreWebSearchResult/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadWebCrawl/MoreMinimoreWebCrawl/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadExecuteSql/MoreMinimoreExecuteSql/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadOutput/MoreMinimoreOutput/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadAddDependency/MoreMinimoreAddDependency/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadAddIntegration/MoreMinimoreAddIntegration/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadMcpToolCall/MoreMinimoreMcpToolCall/g' {} \; + find src/components/chat -name "*.tsx" -type f -exec sed -i.bak 's/DyadMcpToolResult/MoreMinimoreMcpToolResult/g' {} \; + + # Clean up backup files + find src/components/chat -name "*.bak" -type f -delete + + print_success "Updated component names" +} + +# Function to update URLs and external links +update_urls() { + print_status "Updating URLs and external links..." + + # Update Dyad URLs to MoreMinimore URLs + find src -name "*.tsx" -type f -exec sed -i.bak 's|https://dyad.sh|https://moreminimore.com|g' {} \; + find src -name "*.tsx" -type f -exec sed -i.bak 's|https://www.dyad.sh|https://www.moreminimore.com|g' {} \; + find src -name "*.ts" -type f -exec sed -i.bak 's|https://dyad.sh|https://moreminimore.com|g' {} \; + find src -name "*.ts" -type f -exec sed -i.bak 's|https://www.dyad.sh|https://www.moreminimore.com|g' {} \; + + # Clean up backup files + find src -name "*.bak" -type f -delete + + print_success "Updated URLs" +} + +# Function to update branding text +update_branding_text() { + print_status "Updating branding text..." + + # Update Dyad references to MoreMinimore + find src -name "*.tsx" -type f -exec sed -i.bak 's/Dyad Pro/MoreMinimore Pro/g' {} \; + find src -name "*.tsx" -type f -exec sed -i.bak 's/Dyad/MoreMinimore/g' {} \; + find src -name "*.ts" -type f -exec sed -i.bak 's/Dyad Pro/MoreMinimore Pro/g' {} \; + find src -name "*.ts" -type f -exec sed -i.bak 's/Dyad/MoreMinimore/g' {} \; + + # Clean up backup files + find src -name "*.bak" -type f -delete + + print_success "Updated branding text" +} + +# Function to update AI provider settings +update_ai_providers() { + print_status "Updating AI provider settings..." + + # Update the auto provider in language model constants + if [ -f "src/ipc/shared/language_model_constants.ts" ]; then + sed -i.bak 's/displayName: "Dyad",/displayName: "MoreMinimore",/g' src/ipc/shared/language_model_constants.ts + sed -i.bak 's|websiteUrl: "https://academy.dyad.sh/settings"|websiteUrl: "https://moreminimore.com/settings"|g' src/ipc/shared/language_model_constants.ts + sed -i.bak 's/gatewayPrefix: "dyad\/",/gatewayPrefix: "moreminimore\/",/g' src/ipc/shared/language_model_constants.ts + sed -i.bak '/Use the same gateway prefix as Google Gemini for Dyad Pro compatibility./c\ + // Use the same gateway prefix as Google Gemini for MoreMinimore Pro compatibility.' src/ipc/shared/language_model_constants.ts + rm src/ipc/shared/language_model_constants.ts.bak + print_success "Updated AI provider settings" + fi +} + +# Function to remove YouTube video section +remove_youtube_section() { + print_status "Removing YouTube video section..." + + # Comment out OnboardingBanner import and usage in SetupBanner + if [ -f "src/components/SetupBanner.tsx" ]; then + sed -i.bak 's/import { OnboardingBanner } from ".\/home\/OnboardingBanner";/\/\/ import { OnboardingBanner } from ".\/home\/OnboardingBanner";/g' src/components/SetupBanner.tsx + sed -i.bak 's||setIsVisible={setIsOnboardingVisible} /> */}|g' src/components/SetupBanner.tsx + sed -i.bak 's/Not sure what to do? Watch the Get Started video above โ˜๏ธ/Not sure what to do? Follow the setup steps below to get started./g' src/components/SetupBanner.tsx + rm src/components/SetupBanner.tsx.bak + print_success "Removed YouTube video section" + fi +} + +# Function to update title bar and app metadata +update_app_metadata() { + print_status "Updating app metadata..." + + # Update title bar + if [ -f "src/app/TitleBar.tsx" ]; then + sed -i.bak 's/Dyad/MoreMinimore/g' src/app/TitleBar.tsx + rm src/app/TitleBar.tsx.bak + print_success "Updated title bar" + fi + + # Update package.json description (keep name as is for compatibility) + if [ -f "package.json" ]; then + sed -i.bak 's/"description": ".*"/"description": "MoreMinimore - AI-powered development environment"/g' package.json + rm package.json.bak + print_success "Updated package.json description" + fi +} + # Function to clean up imports cleanup_imports() { print_status "Cleaning up unused imports..." @@ -254,6 +395,12 @@ main() { update_branding convert_smart_context update_ui_text + update_component_names + update_urls + update_branding_text + update_ai_providers + remove_youtube_section + update_app_metadata cleanup_imports install_dependencies test_compilation @@ -264,13 +411,24 @@ main() { echo "โœ… Applied custom remove-limit feature" echo "โœ… Removed Dyad API dependencies" echo "โœ… Removed Dyad Engine dependencies" - echo "โœ… Removed pro features" + echo "โœ… Removed pro features and Pro button" echo "โœ… Updated branding to MoreMinimore" echo "โœ… Converted smart context to standard feature" echo "โœ… Updated UI text" + echo "โœ… Updated component names from Dyad to MoreMinimore" + echo "โœ… Updated URLs and external links" + echo "โœ… Updated branding text throughout app" + echo "โœ… Updated AI provider settings" + echo "โœ… Removed YouTube video section" + echo "โœ… Updated app metadata and title bar" echo "โœ… Cleaned up unused imports" echo "โœ… Installed dependencies" echo "" + echo "Key features liberated:" + echo "๐Ÿ”“ Smart Context now available to all users" + echo "๐Ÿ”“ Annotator tool now available to all users" + echo "๐Ÿ”“ Removed Pro upgrade buttons and restrictions" + echo "" echo "Backup created at: $BACKUP_DIR" echo "" echo "Next steps:" diff --git a/src/app/TitleBar.tsx b/src/app/TitleBar.tsx index 889afb2..9d3dadb 100644 --- a/src/app/TitleBar.tsx +++ b/src/app/TitleBar.tsx @@ -10,11 +10,8 @@ import { providerSettingsRoute } from "@/routes/settings/providers/$provider"; import { cn } from "@/lib/utils"; import { useDeepLink } from "@/contexts/DeepLinkContext"; import { useEffect, useState } from "react"; -import { DyadProSuccessDialog } from "@/components/DyadProSuccessDialog"; import { useTheme } from "@/contexts/ThemeContext"; import { IpcClient } from "@/ipc/ipc_client"; -import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; -import { UserBudgetInfo } from "@/ipc/ipc_types"; import { Tooltip, TooltipContent, @@ -28,7 +25,6 @@ export const TitleBar = () => { const { navigate } = useRouter(); const location = useLocation(); const { settings, refreshSettings } = useSettings(); - const [isSuccessDialogOpen, setIsSuccessDialogOpen] = useState(false); const [showWindowControls, setShowWindowControls] = useState(false); useEffect(() => { @@ -45,16 +41,11 @@ export const TitleBar = () => { checkPlatform(); }, []); - const showDyadProSuccessDialog = () => { - setIsSuccessDialogOpen(true); - }; - const { lastDeepLink, clearLastDeepLink } = useDeepLink(); useEffect(() => { const handleDeepLink = async () => { - if (lastDeepLink?.type === "dyad-pro-return") { + if (lastDeepLink) { await refreshSettings(); - showDyadProSuccessDialog(); clearLastDeepLink(); } }; @@ -73,15 +64,12 @@ export const TitleBar = () => { } }; - const isDyadPro = !!settings?.providerSettings?.auto?.apiKey?.value; - const isDyadProEnabled = Boolean(settings?.enableDyadPro); - return ( <>
- Dyad Logo + MoreMinimore Logo - {isDyadPro && } {/* Preview Header */} {location.pathname === "/chat" && ( @@ -104,141 +91,52 @@ export const TitleBar = () => { {showWindowControls && }
- - setIsSuccessDialogOpen(false)} - /> ); }; -function WindowsControls() { - const { isDarkMode } = useTheme(); +// Windows window controls component +const WindowsControls = () => { const ipcClient = IpcClient.getInstance(); - const minimizeWindow = () => { + const handleMinimize = () => { ipcClient.minimizeWindow(); }; - const maximizeWindow = () => { + const handleMaximize = () => { ipcClient.maximizeWindow(); }; - const closeWindow = () => { + const handleClose = () => { ipcClient.closeWindow(); }; return ( -
- - + - + + ร— +
); -} - -export function DyadProButton({ - isDyadProEnabled, -}: { - isDyadProEnabled: boolean; -}) { - const { navigate } = useRouter(); - const { userBudget } = useUserBudgetInfo(); - return ( - - ); -} - -export function AICreditStatus({ userBudget }: { userBudget: UserBudgetInfo }) { - const remaining = Math.round( - userBudget.totalCredits - userBudget.usedCredits, - ); - return ( - - -
{remaining} credits
-
- -
-

Note: there is a slight delay in updating the credit status.

-
-
-
- ); -} +}; diff --git a/src/components/AppUpgrades.tsx b/src/components/AppUpgrades.tsx index 8811e83..0105505 100644 --- a/src/components/AppUpgrades.tsx +++ b/src/components/AppUpgrades.tsx @@ -94,7 +94,7 @@ export function AppUpgrades({ appId }: { appId: number | null }) { data-testid="no-app-upgrades-needed" className="p-4 bg-green-50 border border-green-200 dark:bg-green-900/20 dark:border-green-800/50 rounded-lg text-sm text-green-800 dark:text-green-300" > - App is up-to-date and has all Dyad capabilities enabled + App is up-to-date and has all MoreMinimore capabilities enabled
) : (
diff --git a/src/components/AutoUpdateSwitch.tsx b/src/components/AutoUpdateSwitch.tsx index 1e60206..070d265 100644 --- a/src/components/AutoUpdateSwitch.tsx +++ b/src/components/AutoUpdateSwitch.tsx @@ -20,11 +20,11 @@ export function AutoUpdateSwitch() { updateSettings({ enableAutoUpdate: checked }); toast("Auto-update settings changed", { description: - "You will need to restart Dyad for your settings to take effect.", + "You will need to restart MoreMinimore for your settings to take effect.", action: { - label: "Restart Dyad", + label: "Restart MoreMinimore", onClick: () => { - IpcClient.getInstance().restartDyad(); + IpcClient.getInstance().restartMoreMinimore(); }, }, }); diff --git a/src/components/ChatInputControls.tsx b/src/components/ChatInputControls.tsx index 2c28731..62b7c57 100644 --- a/src/components/ChatInputControls.tsx +++ b/src/components/ChatInputControls.tsx @@ -1,6 +1,5 @@ import { ContextFilesPicker } from "./ContextFilesPicker"; import { ModelPicker } from "./ModelPicker"; -import { ProModeSelector } from "./ProModeSelector"; import { ChatModeSelector } from "./ChatModeSelector"; import { McpToolsPicker } from "@/components/McpToolsPicker"; import { useSettings } from "@/hooks/useSettings"; @@ -17,14 +16,10 @@ export function ChatInputControls({ {settings?.selectedChatMode === "agent" && ( <> -
)} -
-
-
{showContextFilesPicker && ( <> diff --git a/src/components/CommunityCodeConsentDialog.tsx b/src/components/CommunityCodeConsentDialog.tsx index a073a58..3802116 100644 --- a/src/components/CommunityCodeConsentDialog.tsx +++ b/src/components/CommunityCodeConsentDialog.tsx @@ -26,7 +26,7 @@ export const CommunityCodeConsentDialog: React.FC< Community Code Notice

- This code was created by a Dyad community member, not our core + This code was created by a MoreMinimore community member, not our core team.

diff --git a/src/components/ContextFilesPicker.tsx b/src/components/ContextFilesPicker.tsx index 1dafdd5..34a560a 100644 --- a/src/components/ContextFilesPicker.tsx +++ b/src/components/ContextFilesPicker.tsx @@ -5,6 +5,8 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; import { InfoIcon, Settings2, Trash2 } from "lucide-react"; import { useState } from "react"; @@ -19,7 +21,7 @@ import { useContextPaths } from "@/hooks/useContextPaths"; import type { ContextPathResult } from "@/lib/schemas"; export function ContextFilesPicker() { - const { settings } = useSettings(); + const { settings, updateSettings } = useSettings(); const { contextPaths, smartContextAutoIncludes, @@ -112,8 +114,7 @@ export function ContextFilesPicker() { updateExcludePaths(newPaths); }; - const isSmartContextEnabled = - settings?.enableDyadPro && settings?.enableProSmartFilesContextMode; + const isSmartContextEnabled = settings?.enableProSmartFilesContextMode ?? false; return ( @@ -130,7 +131,7 @@ export function ContextFilesPicker() { - Codebase Context + Context Settings

-

Codebase Context

+

Context Settings

@@ -152,11 +153,11 @@ export function ContextFilesPicker() { {isSmartContextEnabled ? (

- With Smart Context, Dyad uses the most relevant files as + With Smart Context, MoreMinimore uses the most relevant files as context.

) : ( -

By default, Dyad uses your whole codebase.

+

By default, MoreMinimore uses your whole codebase.

)} @@ -164,6 +165,28 @@ export function ContextFilesPicker() {

+ {/* Smart Context Toggle */} +
+
+ +

+ Automatically select the most relevant files for context +

+
+ { + updateSettings({ + enableProSmartFilesContextMode: checked, + proSmartContextOption: checked ? "balanced" : undefined, + }); + }} + /> +
+

{isSmartContextEnabled - ? "Dyad will use Smart Context to automatically find the most relevant files to use as context." - : "Dyad will use the entire codebase as context."} + ? "MoreMinimore will use Smart Context to automatically find the most relevant files to use as context." + : "MoreMinimore will use the entire codebase as context."}

)} diff --git a/src/components/DyadProSuccessDialog.tsx b/src/components/DyadProSuccessDialog.tsx.disabled similarity index 100% rename from src/components/DyadProSuccessDialog.tsx rename to src/components/DyadProSuccessDialog.tsx.disabled diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 527f7ad..5f62692 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -40,7 +40,7 @@ export function ErrorBoundary({ error }: ErrorComponentProps) { ${error?.stack ? `\n\`\`\`\n${error.stack.slice(0, 1000)}\n\`\`\`` : ""} ## System Information -- Dyad Version: ${debugInfo.dyadVersion} +- MoreMinimore Version: ${debugInfo.dyadVersion} - Platform: ${debugInfo.platform} - Architecture: ${debugInfo.architecture} - Node Version: ${debugInfo.nodeVersion || "Not available"} @@ -57,7 +57,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"} // Create the GitHub issue URL with the pre-filled body const encodedBody = encodeURIComponent(issueBody); const encodedTitle = encodeURIComponent( - "[bug] Error in Dyad application", + "[bug] Error in MoreMinimore application", ); const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=bug,filed-from-app,client-error&body=${encodedBody}`; @@ -103,7 +103,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}

- Tip: Try closing and re-opening Dyad as a temporary + Tip: Try closing and re-opening MoreMinimore as a temporary workaround.

diff --git a/src/components/HelpBotDialog.tsx b/src/components/HelpBotDialog.tsx index 13aaa01..4d97bb1 100644 --- a/src/components/HelpBotDialog.tsx +++ b/src/components/HelpBotDialog.tsx @@ -160,7 +160,7 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) { - Dyad Help Bot + MoreMinimore Help Bot
{error && ( @@ -183,7 +183,7 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) { {messages.length === 0 ? (
- Ask a question about using Dyad. + Ask a question about using MoreMinimore.
This conversation may be logged and used to improve the diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx index 70f8df9..06b2b8e 100644 --- a/src/components/HelpDialog.tsx +++ b/src/components/HelpDialog.tsx @@ -98,7 +98,7 @@ Issues that do not meet these requirements will be closed and may need to be res ## System Information -- Dyad Version: ${debugInfo.dyadVersion} +- MoreMinimore Version: ${debugInfo.dyadVersion} - Platform: ${debugInfo.platform} - Architecture: ${debugInfo.architecture} - Node Version: ${debugInfo.nodeVersion || "n/a"} @@ -348,7 +348,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}

System Information

-

Dyad Version: {chatLogsData.debugInfo.dyadVersion}

+

MoreMinimore Version: {chatLogsData.debugInfo.dyadVersion}

Platform: {chatLogsData.debugInfo.platform}

Architecture: {chatLogsData.debugInfo.architecture}

@@ -390,7 +390,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}

- Need help with Dyad? + Need help with MoreMinimore? If you need help or want to report an issue, here are some options: @@ -405,11 +405,11 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"} }} className="w-full py-6 border-primary/50 shadow-sm shadow-primary/10 transition-all hover:shadow-md hover:shadow-primary/15" > - Chat with Dyad help + Chat with MoreMinimore help bot (Pro)

- Opens an in-app help chat assistant that searches through Dyad's + Opens an in-app help chat assistant that searches through MoreMinimore's docs.

diff --git a/src/components/ImportAppDialog.tsx b/src/components/ImportAppDialog.tsx index 8ab8256..880dc1b 100644 --- a/src/components/ImportAppDialog.tsx +++ b/src/components/ImportAppDialog.tsx @@ -248,7 +248,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) { onSuccess: async (result) => { showSuccess( !hasAiRules - ? "App imported successfully. Dyad will automatically generate an AI_RULES.md now." + ? "App imported successfully. MoreMinimore will automatically generate an AI_RULES.md now." : "App imported successfully", ); onClose(); @@ -456,14 +456,14 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {

- AI_RULES.md lets Dyad know which tech stack to + AI_RULES.md lets MoreMinimore know which tech stack to use for editing the app

- No AI_RULES.md found. Dyad will automatically generate + No AI_RULES.md found. MoreMinimore will automatically generate one after importing. diff --git a/src/components/ModelPicker.tsx b/src/components/ModelPicker.tsx index b8b2149..f00d6e7 100644 --- a/src/components/ModelPicker.tsx +++ b/src/components/ModelPicker.tsx @@ -266,8 +266,8 @@ export function ModelPicker() { {/* Primary providers as submenus */} {primaryProviders.map(([providerId, models]) => { models = models.filter((model) => { - // Don't show free models if Dyad Pro is enabled because - // we will use the paid models (in Dyad Pro backend) which + // Don't show free models if MoreMinimore Pro is enabled because + // we will use the paid models (in MoreMinimore Pro backend) which // don't have the free limitations. if ( isDyadProEnabled(settings) && @@ -280,7 +280,7 @@ export function ModelPicker() { const provider = providers?.find((p) => p.id === providerId); const providerDisplayName = provider?.id === "auto" - ? "Dyad Turbo" + ? "MoreMinimore Turbo" : (provider?.name ?? providerId); return ( diff --git a/src/components/ProBanner.tsx b/src/components/ProBanner.tsx index de5c602..4714586 100644 --- a/src/components/ProBanner.tsx +++ b/src/components/ProBanner.tsx @@ -56,7 +56,7 @@ export function ManageDyadProButton() { }} >
@@ -175,10 +175,10 @@ export function SmartContextBanner() {
@@ -216,10 +216,10 @@ export function TurboBanner() {
diff --git a/src/components/ProModeSelector.tsx b/src/components/ProModeSelector.tsx index 9de6d56..f1e2c2e 100644 --- a/src/components/ProModeSelector.tsx +++ b/src/components/ProModeSelector.tsx @@ -75,14 +75,14 @@ export function ProModeSelector() { - Configure Dyad Pro settings + Configure MoreMinimore Pro settings

- Dyad Pro + MoreMinimore Pro

@@ -110,8 +110,8 @@ export function ProModeSelector() {
{ - IpcClient.getInstance().restartDyad(); + IpcClient.getInstance().restartMoreMinimore(); }, }, }); diff --git a/src/components/SetupBanner.tsx b/src/components/SetupBanner.tsx index bebb96c..0afc592 100644 --- a/src/components/SetupBanner.tsx +++ b/src/components/SetupBanner.tsx @@ -30,7 +30,7 @@ import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders"; import { useScrollAndNavigateTo } from "@/hooks/useScrollAndNavigateTo"; // @ts-ignore import logo from "../../assets/logo.svg"; -import { OnboardingBanner } from "./home/OnboardingBanner"; +// import { OnboardingBanner } from "./home/OnboardingBanner"; import { showError } from "@/lib/toast"; import { useSettings } from "@/hooks/useSettings"; @@ -114,12 +114,6 @@ export function SetupBanner() { params: { provider: "openrouter" }, }); }; - const handleDyadProSetupClick = () => { - posthog.capture("setup-flow:ai-provider-setup:dyad:click"); - IpcClient.getInstance().openExternalUrl( - "https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=setup-banner", - ); - }; const handleOtherProvidersClick = () => { posthog.capture("setup-flow:ai-provider-setup:other:click"); @@ -178,12 +172,12 @@ export function SetupBanner() { return ( <>

- Setup Dyad + Setup MoreMinimore

- + /> */}

- Not sure what to do? Watch the Get Started video above โ˜๏ธ + Not sure what to do? Follow the setup steps below to get started.

- - } - title="Setup Dyad Pro" - subtitle="Access all AI models with one plan" - chip={<>Recommended} - /> -
- Node.js not detected. Closing and re-opening Dyad usually fixes this. + Node.js not detected. Closing and re-opening MoreMinimore usually fixes this.
); default: diff --git a/src/components/SetupProviderCard.tsx b/src/components/SetupProviderCard.tsx index fba27e9..b0dbaf6 100644 --- a/src/components/SetupProviderCard.tsx +++ b/src/components/SetupProviderCard.tsx @@ -2,7 +2,7 @@ import { ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { ReactNode } from "react"; -type SetupProviderVariant = "google" | "openrouter" | "dyad"; +type SetupProviderVariant = "google" | "openrouter"; export function SetupProviderCard({ variant, @@ -94,15 +94,6 @@ function getVariantStyles(variant: SetupProviderVariant) { subtitleColor: "text-teal-600 dark:text-teal-400", chevronColor: "text-teal-600 dark:text-teal-400", } as const; - case "dyad": - return { - container: - "bg-primary/10 border-primary/50 dark:bg-violet-800/50 dark:border-violet-700 hover:bg-violet-100 dark:hover:bg-violet-900/70", - iconWrapper: "bg-primary/5 dark:bg-violet-800", - titleColor: "text-violet-800 dark:text-violet-300", - subtitleColor: "text-violet-600 dark:text-violet-400", - chevronColor: "text-violet-600 dark:text-violet-400", - } as const; } } diff --git a/src/components/TelemetryBanner.tsx b/src/components/TelemetryBanner.tsx index ccf5c53..e1d5e24 100644 --- a/src/components/TelemetryBanner.tsx +++ b/src/components/TelemetryBanner.tsx @@ -26,7 +26,7 @@ export function PrivacyBanner() { Share anonymous data?

- Help improve Dyad with anonymous usage data. + Help improve MoreMinimore with anonymous usage data. Note: this does not log your code or messages. diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 70f98e9..abb8bb0 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -182,7 +182,7 @@ function AppIcons({ return ( // When collapsed: only show the main menu - {/* Dyad */} + {/* MoreMinimore */} diff --git a/src/components/chat/ChatErrorBox.tsx b/src/components/chat/ChatErrorBox.tsx index 5a41ba2..c5e9211 100644 --- a/src/components/chat/ChatErrorBox.tsx +++ b/src/components/chat/ChatErrorBox.tsx @@ -26,7 +26,7 @@ export function ChatErrorBox({ href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=free-quota-error" variant="primary" > - Access with Dyad Pro + Access with MoreMinimore Pro {" "} or switch to another model. @@ -37,9 +37,9 @@ export function ChatErrorBox({ // Important, this needs to come after the "free quota tier" check // because it also includes this URL in the error message // - // Sometimes Dyad Pro can return rate limit errors and we do not want to - // show the upgrade to Dyad Pro link in that case because they are - // already on the Dyad Pro plan. + // Sometimes MoreMinimore Pro can return rate limit errors and we do not want to + // show the upgrade to MoreMinimore Pro link in that case because they are + // already on the MoreMinimore Pro plan. if ( !isDyadProEnabled && (error.includes("Resource has been exhausted") || @@ -54,7 +54,7 @@ export function ChatErrorBox({ href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=rate-limit-error" variant="primary" > - Upgrade to Dyad Pro + Upgrade to MoreMinimore Pro @@ -69,12 +69,12 @@ export function ChatErrorBox({ return ( - Looks like you don't have a valid Dyad Pro key.{" "} + Looks like you don't have a valid MoreMinimore Pro key.{" "} - Upgrade to Dyad Pro + Upgrade to MoreMinimore Pro {" "} today. @@ -85,7 +85,7 @@ export function ChatErrorBox({ return ( - You have used all of your Dyad AI credits this month.{" "} + You have used all of your MoreMinimore AI credits this month.{" "} - Upgrade to Dyad Pro + Upgrade to MoreMinimore Pro )} diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index 63a146d..99bbd14 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -392,7 +392,7 @@ export function ChatInput({ chatId }: { chatId?: number }) { onChange={setInputValue} onSubmit={handleSubmit} onPaste={handlePaste} - placeholder="Ask Dyad to build..." + placeholder="Ask MoreMinimore to build..." excludeCurrentApp={true} disableSendButton={disableSendButton} /> diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx index 99acc59..dd70e2f 100644 --- a/src/components/chat/ChatMessage.tsx +++ b/src/components/chat/ChatMessage.tsx @@ -1,8 +1,8 @@ import type { Message } from "@/ipc/ipc_types"; import { - DyadMarkdownParser, + MoreMinimoreMarkdownParser, VanillaMarkdownParser, -} from "./DyadMarkdownParser"; +} from "./MoreMinimoreMarkdownParser"; import { motion } from "framer-motion"; import { useStreamChat } from "@/hooks/useStreamChat"; import { @@ -140,7 +140,7 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { > {message.role === "assistant" ? ( <> - + {isLastMessage && isStreaming && (

diff --git a/src/components/chat/DyadAddDependency.tsx b/src/components/chat/DyadAddDependency.tsx index 3350c09..65a9f5a 100644 --- a/src/components/chat/DyadAddDependency.tsx +++ b/src/components/chat/DyadAddDependency.tsx @@ -7,13 +7,13 @@ import { IpcClient } from "../../ipc/ipc_client"; import { Package, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; import { CodeHighlight } from "./CodeHighlight"; -interface DyadAddDependencyProps { +interface MoreMinimoreAddDependencyProps { children?: ReactNode; node?: any; packages?: string; } -export const DyadAddDependency: React.FC = ({ +export const MoreMinimoreAddDependency: React.FC = ({ children, node, }) => { diff --git a/src/components/chat/DyadAddIntegration.tsx b/src/components/chat/DyadAddIntegration.tsx index 2545e30..c62a1b3 100644 --- a/src/components/chat/DyadAddIntegration.tsx +++ b/src/components/chat/DyadAddIntegration.tsx @@ -6,7 +6,7 @@ import { useAtomValue } from "jotai"; import { showError } from "@/lib/toast"; import { useLoadApp } from "@/hooks/useLoadApp"; -interface DyadAddIntegrationProps { +interface MoreMinimoreAddIntegrationProps { node: { properties: { provider: string; @@ -15,7 +15,7 @@ interface DyadAddIntegrationProps { children: React.ReactNode; } -export const DyadAddIntegration: React.FC = ({ +export const MoreMinimoreAddIntegration: React.FC = ({ node, children, }) => { diff --git a/src/components/chat/DyadCodeSearch.tsx b/src/components/chat/DyadCodeSearch.tsx index 4da9d0d..4db4f01 100644 --- a/src/components/chat/DyadCodeSearch.tsx +++ b/src/components/chat/DyadCodeSearch.tsx @@ -2,13 +2,13 @@ import type React from "react"; import type { ReactNode } from "react"; import { FileCode } from "lucide-react"; -interface DyadCodeSearchProps { +interface MoreMinimoreCodeSearchProps { children?: ReactNode; node?: any; query?: string; } -export const DyadCodeSearch: React.FC = ({ +export const MoreMinimoreCodeSearch: React.FC = ({ children, node: _node, query: queryProp, diff --git a/src/components/chat/DyadCodeSearchResult.tsx b/src/components/chat/DyadCodeSearchResult.tsx index ccfc470..d68844e 100644 --- a/src/components/chat/DyadCodeSearchResult.tsx +++ b/src/components/chat/DyadCodeSearchResult.tsx @@ -1,12 +1,12 @@ import React, { useState, useMemo } from "react"; import { ChevronDown, ChevronUp, FileCode, FileText } from "lucide-react"; -interface DyadCodeSearchResultProps { +interface MoreMinimoreCodeSearchResultProps { node?: any; children?: React.ReactNode; } -export const DyadCodeSearchResult: React.FC = ({ +export const MoreMinimoreCodeSearchResult: React.FC = ({ children, }) => { const [isExpanded, setIsExpanded] = useState(false); diff --git a/src/components/chat/DyadCodebaseContext.tsx b/src/components/chat/DyadCodebaseContext.tsx index eaa6a3d..393d062 100644 --- a/src/components/chat/DyadCodebaseContext.tsx +++ b/src/components/chat/DyadCodebaseContext.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import { ChevronUp, ChevronDown, Code2, FileText } from "lucide-react"; import { CustomTagState } from "./stateTypes"; -interface DyadCodebaseContextProps { +interface MoreMinimoreCodebaseContextProps { children: React.ReactNode; node?: { properties?: { @@ -12,7 +12,7 @@ interface DyadCodebaseContextProps { }; } -export const DyadCodebaseContext: React.FC = ({ +export const MoreMinimoreCodebaseContext: React.FC = ({ node, }) => { const state = node?.properties?.state as CustomTagState; diff --git a/src/components/chat/DyadDelete.tsx b/src/components/chat/DyadDelete.tsx index 8cade93..fb0f570 100644 --- a/src/components/chat/DyadDelete.tsx +++ b/src/components/chat/DyadDelete.tsx @@ -2,13 +2,13 @@ import type React from "react"; import type { ReactNode } from "react"; import { Trash2 } from "lucide-react"; -interface DyadDeleteProps { +interface MoreMinimoreDeleteProps { children?: ReactNode; node?: any; path?: string; } -export const DyadDelete: React.FC = ({ +export const MoreMinimoreDelete: React.FC = ({ children, node, path: pathProp, diff --git a/src/components/chat/DyadEdit.tsx b/src/components/chat/DyadEdit.tsx index 094102f..edd0b2b 100644 --- a/src/components/chat/DyadEdit.tsx +++ b/src/components/chat/DyadEdit.tsx @@ -11,14 +11,14 @@ import { import { CodeHighlight } from "./CodeHighlight"; import { CustomTagState } from "./stateTypes"; -interface DyadEditProps { +interface MoreMinimoreEditProps { children?: ReactNode; node?: any; path?: string; description?: string; } -export const DyadEdit: React.FC = ({ +export const MoreMinimoreEdit: React.FC = ({ children, node, path: pathProp, diff --git a/src/components/chat/DyadExecuteSql.tsx b/src/components/chat/DyadExecuteSql.tsx index 87031be..bd03df4 100644 --- a/src/components/chat/DyadExecuteSql.tsx +++ b/src/components/chat/DyadExecuteSql.tsx @@ -11,13 +11,13 @@ import { import { CodeHighlight } from "./CodeHighlight"; import { CustomTagState } from "./stateTypes"; -interface DyadExecuteSqlProps { +interface MoreMinimoreExecuteSqlProps { children?: ReactNode; node?: any; description?: string; } -export const DyadExecuteSql: React.FC = ({ +export const MoreMinimoreExecuteSql: React.FC = ({ children, node, description, diff --git a/src/components/chat/DyadMarkdownParser.tsx b/src/components/chat/DyadMarkdownParser.tsx index ad8fddb..97383af 100644 --- a/src/components/chat/DyadMarkdownParser.tsx +++ b/src/components/chat/DyadMarkdownParser.tsx @@ -1,36 +1,36 @@ import React, { useMemo } from "react"; import ReactMarkdown from "react-markdown"; -import { DyadWrite } from "./DyadWrite"; -import { DyadRename } from "./DyadRename"; -import { DyadDelete } from "./DyadDelete"; -import { DyadAddDependency } from "./DyadAddDependency"; -import { DyadExecuteSql } from "./DyadExecuteSql"; -import { DyadAddIntegration } from "./DyadAddIntegration"; -import { DyadEdit } from "./DyadEdit"; -import { DyadSearchReplace } from "./DyadSearchReplace"; -import { DyadCodebaseContext } from "./DyadCodebaseContext"; -import { DyadThink } from "./DyadThink"; +import { MoreMinimoreWrite } from "./MoreMinimoreWrite"; +import { MoreMinimoreRename } from "./MoreMinimoreRename"; +import { MoreMinimoreDelete } from "./MoreMinimoreDelete"; +import { MoreMinimoreAddDependency } from "./MoreMinimoreAddDependency"; +import { MoreMinimoreExecuteSql } from "./MoreMinimoreExecuteSql"; +import { MoreMinimoreAddIntegration } from "./MoreMinimoreAddIntegration"; +import { MoreMinimoreEdit } from "./MoreMinimoreEdit"; +import { MoreMinimoreSearchReplace } from "./MoreMinimoreSearchReplace"; +import { MoreMinimoreCodebaseContext } from "./MoreMinimoreCodebaseContext"; +import { MoreMinimoreThink } from "./MoreMinimoreThink"; import { CodeHighlight } from "./CodeHighlight"; import { useAtomValue } from "jotai"; import { isStreamingByIdAtom, selectedChatIdAtom } from "@/atoms/chatAtoms"; import { CustomTagState } from "./stateTypes"; -import { DyadOutput } from "./DyadOutput"; +import { MoreMinimoreOutput } from "./MoreMinimoreOutput"; import { DyadProblemSummary } from "./DyadProblemSummary"; import { IpcClient } from "@/ipc/ipc_client"; -import { DyadMcpToolCall } from "./DyadMcpToolCall"; -import { DyadMcpToolResult } from "./DyadMcpToolResult"; -import { DyadWebSearchResult } from "./DyadWebSearchResult"; -import { DyadWebSearch } from "./DyadWebSearch"; -import { DyadWebCrawl } from "./DyadWebCrawl"; -import { DyadCodeSearchResult } from "./DyadCodeSearchResult"; -import { DyadCodeSearch } from "./DyadCodeSearch"; -import { DyadRead } from "./DyadRead"; +import { MoreMinimoreMcpToolCall } from "./MoreMinimoreMcpToolCall"; +import { MoreMinimoreMcpToolResult } from "./MoreMinimoreMcpToolResult"; +import { MoreMinimoreWebSearchResult } from "./MoreMinimoreWebSearchResult"; +import { MoreMinimoreWebSearch } from "./MoreMinimoreWebSearch"; +import { MoreMinimoreWebCrawl } from "./MoreMinimoreWebCrawl"; +import { MoreMinimoreCodeSearchResult } from "./MoreMinimoreCodeSearchResult"; +import { MoreMinimoreCodeSearch } from "./MoreMinimoreCodeSearch"; +import { MoreMinimoreRead } from "./MoreMinimoreRead"; import { mapActionToButton } from "./ChatInput"; import { SuggestedAction } from "@/lib/schemas"; import { FixAllErrorsButton } from "./FixAllErrorsButton"; -interface DyadMarkdownParserProps { +interface MoreMinimoreMarkdownParserProps { content: string; } @@ -79,9 +79,9 @@ export const VanillaMarkdownParser = ({ content }: { content: string }) => { }; /** - * Custom component to parse markdown content with Dyad-specific tags + * Custom component to parse markdown content with MoreMinimore-specific tags */ -export const DyadMarkdownParser: React.FC = ({ +export const MoreMinimoreMarkdownParser: React.FC = ({ content, }) => { const chatId = useAtomValue(selectedChatIdAtom); @@ -346,7 +346,7 @@ function renderCustomTag( switch (tag) { case "dyad-read": return ( - {content} - + ); case "dyad-web-search": return ( - {content} - + ); case "dyad-web-crawl": return ( - {content} - + ); case "dyad-code-search": return ( - {content} - + ); case "dyad-code-search-result": return ( - {content} - + ); case "dyad-web-search-result": return ( - {content} - + ); case "think": return ( - {content} - + ); case "dyad-write": return ( - {content} - + ); case "dyad-rename": return ( - {content} - + ); case "dyad-delete": return ( - {content} - + ); case "dyad-add-dependency": return ( - {content} - + ); case "dyad-execute-sql": return ( - {content} - + ); case "dyad-add-integration": return ( - {content} - + ); case "dyad-edit": return ( - {content} - + ); case "dyad-search-replace": return ( - {content} - + ); case "dyad-codebase-context": return ( - {content} - + ); case "dyad-mcp-tool-call": return ( - {content} - + ); case "dyad-mcp-tool-result": return ( - {content} - + ); case "dyad-output": return ( - {content} - + ); case "dyad-problem-report": diff --git a/src/components/chat/DyadMcpToolCall.tsx b/src/components/chat/DyadMcpToolCall.tsx index 8a2a3ee..eaf8781 100644 --- a/src/components/chat/DyadMcpToolCall.tsx +++ b/src/components/chat/DyadMcpToolCall.tsx @@ -2,12 +2,12 @@ import React, { useMemo, useState } from "react"; import { Wrench, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; import { CodeHighlight } from "./CodeHighlight"; -interface DyadMcpToolCallProps { +interface MoreMinimoreMcpToolCallProps { node?: any; children?: React.ReactNode; } -export const DyadMcpToolCall: React.FC = ({ +export const MoreMinimoreMcpToolCall: React.FC = ({ node, children, }) => { diff --git a/src/components/chat/DyadMcpToolResult.tsx b/src/components/chat/DyadMcpToolResult.tsx index 998a0c5..55d3fd8 100644 --- a/src/components/chat/DyadMcpToolResult.tsx +++ b/src/components/chat/DyadMcpToolResult.tsx @@ -2,12 +2,12 @@ import React, { useMemo, useState } from "react"; import { CheckCircle, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; import { CodeHighlight } from "./CodeHighlight"; -interface DyadMcpToolResultProps { +interface MoreMinimoreMcpToolResultProps { node?: any; children?: React.ReactNode; } -export const DyadMcpToolResult: React.FC = ({ +export const MoreMinimoreMcpToolResult: React.FC = ({ node, children, }) => { diff --git a/src/components/chat/DyadOutput.tsx b/src/components/chat/DyadOutput.tsx index a24fbfc..9e6816a 100644 --- a/src/components/chat/DyadOutput.tsx +++ b/src/components/chat/DyadOutput.tsx @@ -10,13 +10,13 @@ import { useAtomValue } from "jotai"; import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { useStreamChat } from "@/hooks/useStreamChat"; import { CopyErrorMessage } from "@/components/CopyErrorMessage"; -interface DyadOutputProps { +interface MoreMinimoreOutputProps { type: "error" | "warning"; message?: string; children?: React.ReactNode; } -export const DyadOutput: React.FC = ({ +export const MoreMinimoreOutput: React.FC = ({ type, message, children, diff --git a/src/components/chat/DyadRead.tsx b/src/components/chat/DyadRead.tsx index 974a6fa..d1453df 100644 --- a/src/components/chat/DyadRead.tsx +++ b/src/components/chat/DyadRead.tsx @@ -2,13 +2,13 @@ import type React from "react"; import type { ReactNode } from "react"; import { FileText } from "lucide-react"; -interface DyadReadProps { +interface MoreMinimoreReadProps { children?: ReactNode; node?: any; path?: string; } -export const DyadRead: React.FC = ({ +export const MoreMinimoreRead: React.FC = ({ children, node, path: pathProp, diff --git a/src/components/chat/DyadRename.tsx b/src/components/chat/DyadRename.tsx index a464e32..4945fbf 100644 --- a/src/components/chat/DyadRename.tsx +++ b/src/components/chat/DyadRename.tsx @@ -2,14 +2,14 @@ import type React from "react"; import type { ReactNode } from "react"; import { FileEdit } from "lucide-react"; -interface DyadRenameProps { +interface MoreMinimoreRenameProps { children?: ReactNode; node?: any; from?: string; to?: string; } -export const DyadRename: React.FC = ({ +export const MoreMinimoreRename: React.FC = ({ children, node, from: fromProp, diff --git a/src/components/chat/DyadSearchReplace.tsx b/src/components/chat/DyadSearchReplace.tsx index 6396e66..8d312ce 100644 --- a/src/components/chat/DyadSearchReplace.tsx +++ b/src/components/chat/DyadSearchReplace.tsx @@ -13,14 +13,14 @@ import { CodeHighlight } from "./CodeHighlight"; import { CustomTagState } from "./stateTypes"; import { parseSearchReplaceBlocks } from "@/pro/shared/search_replace_parser"; -interface DyadSearchReplaceProps { +interface MoreMinimoreSearchReplaceProps { children?: ReactNode; node?: any; path?: string; description?: string; } -export const DyadSearchReplace: React.FC = ({ +export const MoreMinimoreSearchReplace: React.FC = ({ children, node, path: pathProp, diff --git a/src/components/chat/DyadThink.tsx b/src/components/chat/DyadThink.tsx index 4123add..27ce6d4 100644 --- a/src/components/chat/DyadThink.tsx +++ b/src/components/chat/DyadThink.tsx @@ -1,15 +1,15 @@ import React, { useState, useEffect } from "react"; import { Brain, ChevronDown, ChevronUp, Loader } from "lucide-react"; -import { VanillaMarkdownParser } from "./DyadMarkdownParser"; +import { VanillaMarkdownParser } from "./MoreMinimoreMarkdownParser"; import { CustomTagState } from "./stateTypes"; -import { DyadTokenSavings } from "./DyadTokenSavings"; +import { MoreMinimoreTokenSavings } from "./MoreMinimoreTokenSavings"; -interface DyadThinkProps { +interface AIThinkProps { node?: any; children?: React.ReactNode; } -export const DyadThink: React.FC = ({ children, node }) => { +export const AIThink: React.FC = ({ children, node }) => { const state = node?.properties?.state as CustomTagState; const inProgress = state === "pending"; const [isExpanded, setIsExpanded] = useState(inProgress); @@ -29,12 +29,12 @@ export const DyadThink: React.FC = ({ children, node }) => { } }, [inProgress]); - // If it's token savings format, render DyadTokenSavings component + // If it's token savings format, render MoreMinimoreTokenSavings component if (tokenSavingsMatch) { const originalTokens = parseFloat(tokenSavingsMatch[1]); const smartContextTokens = parseFloat(tokenSavingsMatch[2]); return ( - diff --git a/src/components/chat/DyadTokenSavings.tsx b/src/components/chat/DyadTokenSavings.tsx index 14ed4cf..63710ee 100644 --- a/src/components/chat/DyadTokenSavings.tsx +++ b/src/components/chat/DyadTokenSavings.tsx @@ -2,12 +2,12 @@ import React from "react"; import { Zap } from "lucide-react"; import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip"; -interface DyadTokenSavingsProps { +interface TokenSavingsProps { originalTokens: number; smartContextTokens: number; } -export const DyadTokenSavings: React.FC = ({ +export const TokenSavings: React.FC = ({ originalTokens, smartContextTokens, }) => { diff --git a/src/components/chat/DyadWebCrawl.tsx b/src/components/chat/DyadWebCrawl.tsx index fe2bf7d..7575fe5 100644 --- a/src/components/chat/DyadWebCrawl.tsx +++ b/src/components/chat/DyadWebCrawl.tsx @@ -2,12 +2,12 @@ import type React from "react"; import type { ReactNode } from "react"; import { ScanQrCode } from "lucide-react"; -interface DyadWebCrawlProps { +interface MoreMinimoreWebCrawlProps { children?: ReactNode; node?: any; } -export const DyadWebCrawl: React.FC = ({ +export const MoreMinimoreWebCrawl: React.FC = ({ children, node: _node, }) => { diff --git a/src/components/chat/DyadWebSearch.tsx b/src/components/chat/DyadWebSearch.tsx index 65ae008..8d737fb 100644 --- a/src/components/chat/DyadWebSearch.tsx +++ b/src/components/chat/DyadWebSearch.tsx @@ -2,13 +2,13 @@ import type React from "react"; import type { ReactNode } from "react"; import { Globe } from "lucide-react"; -interface DyadWebSearchProps { +interface MoreMinimoreWebSearchProps { children?: ReactNode; node?: any; query?: string; } -export const DyadWebSearch: React.FC = ({ +export const MoreMinimoreWebSearch: React.FC = ({ children, node: _node, query: queryProp, diff --git a/src/components/chat/DyadWebSearchResult.tsx b/src/components/chat/DyadWebSearchResult.tsx index 2e1dfce..9f15209 100644 --- a/src/components/chat/DyadWebSearchResult.tsx +++ b/src/components/chat/DyadWebSearchResult.tsx @@ -1,14 +1,14 @@ import React, { useEffect, useState } from "react"; import { ChevronDown, ChevronUp, Globe, Loader } from "lucide-react"; -import { VanillaMarkdownParser } from "./DyadMarkdownParser"; +import { VanillaMarkdownParser } from "./MoreMinimoreMarkdownParser"; import { CustomTagState } from "./stateTypes"; -interface DyadWebSearchResultProps { +interface MoreMinimoreWebSearchResultProps { node?: any; children?: React.ReactNode; } -export const DyadWebSearchResult: React.FC = ({ +export const MoreMinimoreWebSearchResult: React.FC = ({ children, node, }) => { diff --git a/src/components/chat/DyadWrite.tsx b/src/components/chat/DyadWrite.tsx index 6b45709..1a321a9 100644 --- a/src/components/chat/DyadWrite.tsx +++ b/src/components/chat/DyadWrite.tsx @@ -16,14 +16,14 @@ import { FileEditor } from "../preview_panel/FileEditor"; import { useAtomValue } from "jotai"; import { selectedAppIdAtom } from "@/atoms/appAtoms"; -interface DyadWriteProps { +interface MoreMinimoreWriteProps { children?: ReactNode; node?: any; path?: string; description?: string; } -export const DyadWrite: React.FC = ({ +export const MoreMinimoreWrite: React.FC = ({ children, node, path: pathProp, diff --git a/src/components/chat/HomeChatInput.tsx b/src/components/chat/HomeChatInput.tsx index 80eb11b..608a0ab 100644 --- a/src/components/chat/HomeChatInput.tsx +++ b/src/components/chat/HomeChatInput.tsx @@ -32,7 +32,7 @@ export function HomeChatInput({ "an information page...", "a landing page...", ]); - const placeholder = `Ask Dyad to build ${typingText ?? ""}`; + const placeholder = `Ask MoreMinimore to build ${typingText ?? ""}`; // Use the attachments hook const { diff --git a/src/components/chat/LexicalChatInput.tsx b/src/components/chat/LexicalChatInput.tsx index ed4780a..25b5abc 100644 --- a/src/components/chat/LexicalChatInput.tsx +++ b/src/components/chat/LexicalChatInput.tsx @@ -250,7 +250,7 @@ export function LexicalChatInput({ onSubmit, onPaste, excludeCurrentApp, - placeholder = "Ask Dyad to build...", + placeholder = "Ask MoreMinimore to build...", disabled = false, disableSendButton, }: LexicalChatInputProps) { diff --git a/src/components/chat/MoreMinimoreAddDependency.tsx b/src/components/chat/MoreMinimoreAddDependency.tsx new file mode 100644 index 0000000..65a9f5a --- /dev/null +++ b/src/components/chat/MoreMinimoreAddDependency.tsx @@ -0,0 +1,93 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { useState } from "react"; + +import { IpcClient } from "../../ipc/ipc_client"; + +import { Package, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; + +interface MoreMinimoreAddDependencyProps { + children?: ReactNode; + node?: any; + packages?: string; +} + +export const MoreMinimoreAddDependency: React.FC = ({ + children, + node, +}) => { + // Extract package attribute from the node if available + const packages = node?.properties?.packages?.split(" ") || ""; + const [isContentVisible, setIsContentVisible] = useState(false); + const hasChildren = !!children; + + return ( +
setIsContentVisible(!isContentVisible) : undefined + } + > +
+
+ + {packages.length > 0 && ( +
+
+ Do you want to install these packages? +
{" "} +
+ {packages.map((p: string) => ( + { + IpcClient.getInstance().openExternalUrl( + `https://www.npmjs.com/package/${p}`, + ); + }} + > + {p} + + ))} +
+
+ )} +
+ {hasChildren && ( +
+ {isContentVisible ? ( + + ) : ( + + )} +
+ )} +
+ + {packages.length > 0 && ( +
+ Make sure these packages are what you want.{" "} +
+ )} + + {/* Show content if it's visible and has children */} + {isContentVisible && hasChildren && ( +
+
+ {children} +
+
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreAddIntegration.tsx b/src/components/chat/MoreMinimoreAddIntegration.tsx new file mode 100644 index 0000000..c62a1b3 --- /dev/null +++ b/src/components/chat/MoreMinimoreAddIntegration.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import { useNavigate } from "@tanstack/react-router"; +import { Button } from "@/components/ui/button"; +import { selectedAppIdAtom } from "@/atoms/appAtoms"; +import { useAtomValue } from "jotai"; +import { showError } from "@/lib/toast"; +import { useLoadApp } from "@/hooks/useLoadApp"; + +interface MoreMinimoreAddIntegrationProps { + node: { + properties: { + provider: string; + }; + }; + children: React.ReactNode; +} + +export const MoreMinimoreAddIntegration: React.FC = ({ + node, + children, +}) => { + const navigate = useNavigate(); + + const { provider } = node.properties; + const appId = useAtomValue(selectedAppIdAtom); + const { app } = useLoadApp(appId); + + const handleSetupClick = () => { + if (!appId) { + showError("No app ID found"); + return; + } + navigate({ to: "/app-details", search: { appId } }); + }; + + if (app?.supabaseProjectName) { + return ( +
+
+ + + + + + Supabase integration complete + +
+
+

+ This app is connected to Supabase project:{" "} + + {app.supabaseProjectName} + +

+

Click the chat suggestion "Keep going" to continue.

+
+
+ ); + } + + return ( +
+
+
Integrate with {provider}?
+
{children}
+
+ +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreCodeSearch.tsx b/src/components/chat/MoreMinimoreCodeSearch.tsx new file mode 100644 index 0000000..4db4f01 --- /dev/null +++ b/src/components/chat/MoreMinimoreCodeSearch.tsx @@ -0,0 +1,31 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { FileCode } from "lucide-react"; + +interface MoreMinimoreCodeSearchProps { + children?: ReactNode; + node?: any; + query?: string; +} + +export const MoreMinimoreCodeSearch: React.FC = ({ + children, + node: _node, + query: queryProp, +}) => { + const query = queryProp || (typeof children === "string" ? children : ""); + + return ( +
+
+
+ +
Code Search
+
+
+
+ {query || children} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreCodeSearchResult.tsx b/src/components/chat/MoreMinimoreCodeSearchResult.tsx new file mode 100644 index 0000000..d68844e --- /dev/null +++ b/src/components/chat/MoreMinimoreCodeSearchResult.tsx @@ -0,0 +1,123 @@ +import React, { useState, useMemo } from "react"; +import { ChevronDown, ChevronUp, FileCode, FileText } from "lucide-react"; + +interface MoreMinimoreCodeSearchResultProps { + node?: any; + children?: React.ReactNode; +} + +export const MoreMinimoreCodeSearchResult: React.FC = ({ + children, +}) => { + const [isExpanded, setIsExpanded] = useState(false); + + // Parse file paths from children content + const files = useMemo(() => { + if (typeof children !== "string") { + return []; + } + + const filePaths: string[] = []; + const lines = children.split("\n"); + + for (const line of lines) { + const trimmedLine = line.trim(); + // Skip empty lines and lines that look like tags + if ( + trimmedLine && + !trimmedLine.startsWith("<") && + !trimmedLine.startsWith(">") + ) { + filePaths.push(trimmedLine); + } + } + + return filePaths; + }, [children]); + + return ( +
setIsExpanded(!isExpanded)} + role="button" + aria-expanded={isExpanded} + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setIsExpanded(!isExpanded); + } + }} + > + {/* Top-left label badge */} +
+ + Code Search Result +
+ + {/* File count when collapsed */} + {files.length > 0 && ( +
+ + Found {files.length} file{files.length !== 1 ? "s" : ""} + +
+ )} + + {/* Indicator icon */} +
+ {isExpanded ? : } +
+ + {/* Main content with smooth transition */} +
+ {/* File list when expanded */} + {files.length > 0 && ( +
+
+ {files.map((file, index) => { + const filePath = file.trim(); + const fileName = filePath.split("/").pop() || filePath; + const pathPart = + filePath.substring(0, filePath.length - fileName.length) || + ""; + + return ( +
+
+ +
+ {fileName} +
+
+ {pathPart && ( +
+ {pathPart} +
+ )} +
+ ); + })} +
+
+ )} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreCodebaseContext.tsx b/src/components/chat/MoreMinimoreCodebaseContext.tsx new file mode 100644 index 0000000..393d062 --- /dev/null +++ b/src/components/chat/MoreMinimoreCodebaseContext.tsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from "react"; +import { ChevronUp, ChevronDown, Code2, FileText } from "lucide-react"; +import { CustomTagState } from "./stateTypes"; + +interface MoreMinimoreCodebaseContextProps { + children: React.ReactNode; + node?: { + properties?: { + files?: string; + state?: CustomTagState; + }; + }; +} + +export const MoreMinimoreCodebaseContext: React.FC = ({ + node, +}) => { + const state = node?.properties?.state as CustomTagState; + const inProgress = state === "pending"; + const [isExpanded, setIsExpanded] = useState(inProgress); + const files = node?.properties?.files?.split(",") || []; + + // Collapse when transitioning from in-progress to not-in-progress + useEffect(() => { + if (!inProgress && isExpanded) { + setIsExpanded(false); + } + }, [inProgress]); + + return ( +
setIsExpanded(!isExpanded)} + role="button" + aria-expanded={isExpanded} + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setIsExpanded(!isExpanded); + } + }} + > + {/* Top-left label badge */} +
+ + Codebase Context +
+ + {/* File count when collapsed */} + {files.length > 0 && ( +
+ + Using {files.length} file{files.length !== 1 ? "s" : ""} + +
+ )} + + {/* Indicator icon */} +
+ {isExpanded ? : } +
+ + {/* Main content with smooth transition */} +
+ {/* File list when expanded */} + {files.length > 0 && ( +
+
+ {files.map((file, index) => { + const filePath = file.trim(); + const fileName = filePath.split("/").pop() || filePath; + const pathPart = + filePath.substring(0, filePath.length - fileName.length) || + ""; + + return ( +
+
+ +
+ {fileName} +
+
+ {pathPart && ( +
+ {pathPart} +
+ )} +
+ ); + })} +
+
+ )} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreDelete.tsx b/src/components/chat/MoreMinimoreDelete.tsx new file mode 100644 index 0000000..fb0f570 --- /dev/null +++ b/src/components/chat/MoreMinimoreDelete.tsx @@ -0,0 +1,45 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { Trash2 } from "lucide-react"; + +interface MoreMinimoreDeleteProps { + children?: ReactNode; + node?: any; + path?: string; +} + +export const MoreMinimoreDelete: React.FC = ({ + children, + node, + path: pathProp, +}) => { + // Use props directly if provided, otherwise extract from node + const path = pathProp || node?.properties?.path || ""; + + // Extract filename from path + const fileName = path ? path.split("/").pop() : ""; + + return ( +
+
+
+ + {fileName && ( + + {fileName} + + )} +
Delete
+
+
+ {path && ( +
+ {path} +
+ )} +
+ {children} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreEdit.tsx b/src/components/chat/MoreMinimoreEdit.tsx new file mode 100644 index 0000000..edd0b2b --- /dev/null +++ b/src/components/chat/MoreMinimoreEdit.tsx @@ -0,0 +1,113 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { useState } from "react"; +import { + ChevronsDownUp, + ChevronsUpDown, + Loader, + CircleX, + Rabbit, +} from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; +import { CustomTagState } from "./stateTypes"; + +interface MoreMinimoreEditProps { + children?: ReactNode; + node?: any; + path?: string; + description?: string; +} + +export const MoreMinimoreEdit: React.FC = ({ + children, + node, + path: pathProp, + description: descriptionProp, +}) => { + const [isContentVisible, setIsContentVisible] = useState(false); + + // Use props directly if provided, otherwise extract from node + const path = pathProp || node?.properties?.path || ""; + const description = descriptionProp || node?.properties?.description || ""; + const state = node?.properties?.state as CustomTagState; + const inProgress = state === "pending"; + const aborted = state === "aborted"; + + // Extract filename from path + const fileName = path ? path.split("/").pop() : ""; + + return ( +
setIsContentVisible(!isContentVisible)} + > +
+
+
+ + + Turbo Edit + +
+ {fileName && ( + + {fileName} + + )} + {inProgress && ( +
+ + Editing... +
+ )} + {aborted && ( +
+ + Did not finish +
+ )} +
+
+ {isContentVisible ? ( + + ) : ( + + )} +
+
+ {path && ( +
+ {path} +
+ )} + {description && ( +
+ Summary: + {description} +
+ )} + {isContentVisible && ( +
e.stopPropagation()} + > + + {children} + +
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreExecuteSql.tsx b/src/components/chat/MoreMinimoreExecuteSql.tsx new file mode 100644 index 0000000..bd03df4 --- /dev/null +++ b/src/components/chat/MoreMinimoreExecuteSql.tsx @@ -0,0 +1,85 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { useState } from "react"; +import { + ChevronsDownUp, + ChevronsUpDown, + Database, + Loader, + CircleX, +} from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; +import { CustomTagState } from "./stateTypes"; + +interface MoreMinimoreExecuteSqlProps { + children?: ReactNode; + node?: any; + description?: string; +} + +export const MoreMinimoreExecuteSql: React.FC = ({ + children, + node, + description, +}) => { + const [isContentVisible, setIsContentVisible] = useState(false); + const state = node?.properties?.state as CustomTagState; + const inProgress = state === "pending"; + const aborted = state === "aborted"; + const queryDescription = description || node?.properties?.description; + + return ( +
setIsContentVisible(!isContentVisible)} + > +
+
+ + + + SQL + + {queryDescription} + + {inProgress && ( +
+ + Executing... +
+ )} + {aborted && ( +
+ + Did not finish +
+ )} +
+
+ {isContentVisible ? ( + + ) : ( + + )} +
+
+ {isContentVisible && ( +
+ {children} +
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreMarkdownParser.tsx b/src/components/chat/MoreMinimoreMarkdownParser.tsx new file mode 100644 index 0000000..1a82d92 --- /dev/null +++ b/src/components/chat/MoreMinimoreMarkdownParser.tsx @@ -0,0 +1,610 @@ +import React, { useMemo } from "react"; +import ReactMarkdown from "react-markdown"; + +import { MoreMinimoreWrite } from "./MoreMinimoreWrite"; +import { MoreMinimoreRename } from "./MoreMinimoreRename"; +import { MoreMinimoreDelete } from "./MoreMinimoreDelete"; +import { MoreMinimoreAddDependency } from "./MoreMinimoreAddDependency"; +import { MoreMinimoreExecuteSql } from "./MoreMinimoreExecuteSql"; +import { MoreMinimoreAddIntegration } from "./MoreMinimoreAddIntegration"; +import { MoreMinimoreEdit } from "./MoreMinimoreEdit"; +import { MoreMinimoreSearchReplace } from "./MoreMinimoreSearchReplace"; +import { MoreMinimoreCodebaseContext } from "./MoreMinimoreCodebaseContext"; +import { MoreMinimoreThink } from "./MoreMinimoreThink"; +import { CodeHighlight } from "./CodeHighlight"; +import { useAtomValue } from "jotai"; +import { isStreamingByIdAtom, selectedChatIdAtom } from "@/atoms/chatAtoms"; +import { CustomTagState } from "./stateTypes"; +import { MoreMinimoreOutput } from "./MoreMinimoreOutput"; +import { MoreMinimoreProblemSummary } from "./MoreMinimoreProblemSummary"; +import { IpcClient } from "@/ipc/ipc_client"; +import { MoreMinimoreMcpToolCall } from "./MoreMinimoreMcpToolCall"; +import { MoreMinimoreMcpToolResult } from "./MoreMinimoreMcpToolResult"; +import { MoreMinimoreWebSearchResult } from "./MoreMinimoreWebSearchResult"; +import { MoreMinimoreWebSearch } from "./MoreMinimoreWebSearch"; +import { MoreMinimoreWebCrawl } from "./MoreMinimoreWebCrawl"; +import { MoreMinimoreCodeSearchResult } from "./MoreMinimoreCodeSearchResult"; +import { MoreMinimoreCodeSearch } from "./MoreMinimoreCodeSearch"; +import { MoreMinimoreRead } from "./MoreMinimoreRead"; +import { mapActionToButton } from "./ChatInput"; +import { SuggestedAction } from "@/lib/schemas"; +import { FixAllErrorsButton } from "./FixAllErrorsButton"; + +interface MoreMinimoreMarkdownParserProps { + content: string; +} + +type CustomTagInfo = { + tag: string; + attributes: Record; + content: string; + fullMatch: string; + inProgress?: boolean; +}; + +type ContentPiece = + | { type: "markdown"; content: string } + | { type: "custom-tag"; tagInfo: CustomTagInfo }; + +const customLink = ({ + node: _node, + ...props +}: { + node?: any; + [key: string]: any; +}) => ( + { + const url = props.href; + if (url) { + e.preventDefault(); + IpcClient.getInstance().openExternalUrl(url); + } + }} + /> +); + +export const VanillaMarkdownParser = ({ content }: { content: string }) => { + return ( + + {content} + + ); +}; + +/** + * Custom component to parse markdown content with MoreMinimore-specific tags + */ +export const MoreMinimoreMarkdownParser: React.FC = ({ + content, +}) => { + const chatId = useAtomValue(selectedChatIdAtom); + const isStreaming = useAtomValue(isStreamingByIdAtom).get(chatId!) ?? false; + // Extract content pieces (markdown and custom tags) + const contentPieces = useMemo(() => { + return parseCustomTags(content); + }, [content]); + + // Extract error messages and track positions + const { errorMessages, lastErrorIndex, errorCount } = useMemo(() => { + const errors: string[] = []; + let lastIndex = -1; + let count = 0; + + contentPieces.forEach((piece, index) => { + if ( + piece.type === "custom-tag" && + piece.tagInfo.tag === "dyad-output" && + piece.tagInfo.attributes.type === "error" + ) { + const errorMessage = piece.tagInfo.attributes.message; + if (errorMessage?.trim()) { + errors.push(errorMessage.trim()); + count++; + lastIndex = index; + } + } + }); + + return { + errorMessages: errors, + lastErrorIndex: lastIndex, + errorCount: count, + }; + }, [contentPieces]); + + return ( + <> + {contentPieces.map((piece, index) => ( + + {piece.type === "markdown" + ? piece.content && ( + + {piece.content} + + ) + : renderCustomTag(piece.tagInfo, { isStreaming })} + {index === lastErrorIndex && + errorCount > 1 && + !isStreaming && + chatId && ( +
+ +
+ )} +
+ ))} + + ); +}; + +/** + * Pre-process content to handle unclosed custom tags + * Adds closing tags at the end of the content for any unclosed custom tags + * Assumes the opening tags are complete and valid + * Returns the processed content and a map of in-progress tags + */ +function preprocessUnclosedTags(content: string): { + processedContent: string; + inProgressTags: Map>; +} { + const customTagNames = [ + "dyad-write", + "dyad-rename", + "dyad-delete", + "dyad-add-dependency", + "dyad-execute-sql", + "dyad-add-integration", + "dyad-output", + "dyad-problem-report", + "dyad-chat-summary", + "dyad-edit", + "dyad-search-replace", + "dyad-codebase-context", + "dyad-web-search-result", + "dyad-web-search", + "dyad-web-crawl", + "dyad-read", + "think", + "dyad-command", + "dyad-mcp-tool-call", + "dyad-mcp-tool-result", + ]; + + let processedContent = content; + // Map to track which tags are in progress and their positions + const inProgressTags = new Map>(); + + // For each tag type, check if there are unclosed tags + for (const tagName of customTagNames) { + // Count opening and closing tags + const openTagPattern = new RegExp(`<${tagName}(?:\\s[^>]*)?>`, "g"); + const closeTagPattern = new RegExp(``, "g"); + + // Track the positions of opening tags + const openingMatches: RegExpExecArray[] = []; + let match; + + // Reset regex lastIndex to start from the beginning + openTagPattern.lastIndex = 0; + + while ((match = openTagPattern.exec(processedContent)) !== null) { + openingMatches.push({ ...match }); + } + + const openCount = openingMatches.length; + const closeCount = (processedContent.match(closeTagPattern) || []).length; + + // If we have more opening than closing tags + const missingCloseTags = openCount - closeCount; + if (missingCloseTags > 0) { + // Add the required number of closing tags at the end + processedContent += Array(missingCloseTags) + .fill(``) + .join(""); + + // Mark the last N tags as in progress where N is the number of missing closing tags + const inProgressIndexes = new Set(); + const startIndex = openCount - missingCloseTags; + for (let i = startIndex; i < openCount; i++) { + inProgressIndexes.add(openingMatches[i].index); + } + inProgressTags.set(tagName, inProgressIndexes); + } + } + + return { processedContent, inProgressTags }; +} + +/** + * Parse the content to extract custom tags and markdown sections into a unified array + */ +function parseCustomTags(content: string): ContentPiece[] { + const { processedContent, inProgressTags } = preprocessUnclosedTags(content); + + const customTagNames = [ + "dyad-write", + "dyad-rename", + "dyad-delete", + "dyad-add-dependency", + "dyad-execute-sql", + "dyad-add-integration", + "dyad-output", + "dyad-problem-report", + "dyad-chat-summary", + "dyad-edit", + "dyad-search-replace", + "dyad-codebase-context", + "dyad-web-search-result", + "dyad-web-search", + "dyad-web-crawl", + "dyad-code-search-result", + "dyad-code-search", + "dyad-read", + "think", + "dyad-command", + "dyad-mcp-tool-call", + "dyad-mcp-tool-result", + ]; + + const tagPattern = new RegExp( + `<(${customTagNames.join("|")})\\s*([^>]*)>(.*?)<\\/\\1>`, + "gs", + ); + + const contentPieces: ContentPiece[] = []; + let lastIndex = 0; + let match; + + // Find all custom tags + while ((match = tagPattern.exec(processedContent)) !== null) { + const [fullMatch, tag, attributesStr, tagContent] = match; + const startIndex = match.index; + + // Add the markdown content before this tag + if (startIndex > lastIndex) { + contentPieces.push({ + type: "markdown", + content: processedContent.substring(lastIndex, startIndex), + }); + } + + // Parse attributes + const attributes: Record = {}; + const attrPattern = /(\w+)="([^"]*)"/g; + let attrMatch; + while ((attrMatch = attrPattern.exec(attributesStr)) !== null) { + attributes[attrMatch[1]] = attrMatch[2]; + } + + // Check if this tag was marked as in progress + const tagInProgressSet = inProgressTags.get(tag); + const isInProgress = tagInProgressSet?.has(startIndex); + + // Add the tag info + contentPieces.push({ + type: "custom-tag", + tagInfo: { + tag, + attributes, + content: tagContent, + fullMatch, + inProgress: isInProgress || false, + }, + }); + + lastIndex = startIndex + fullMatch.length; + } + + // Add the remaining markdown content + if (lastIndex < processedContent.length) { + contentPieces.push({ + type: "markdown", + content: processedContent.substring(lastIndex), + }); + } + + return contentPieces; +} + +function getState({ + isStreaming, + inProgress, +}: { + isStreaming?: boolean; + inProgress?: boolean; +}): CustomTagState { + if (!inProgress) { + return "finished"; + } + return isStreaming ? "pending" : "aborted"; +} + +/** + * Render a custom tag based on its type + */ +function renderCustomTag( + tagInfo: CustomTagInfo, + { isStreaming }: { isStreaming: boolean }, +): React.ReactNode { + const { tag, attributes, content, inProgress } = tagInfo; + + switch (tag) { + case "dyad-read": + return ( + + {content} + + ); + case "dyad-web-search": + return ( + + {content} + + ); + case "dyad-web-crawl": + return ( + + {content} + + ); + case "dyad-code-search": + return ( + + {content} + + ); + case "dyad-code-search-result": + return ( + + {content} + + ); + case "dyad-web-search-result": + return ( + + {content} + + ); + case "think": + return ( + + {content} + + ); + case "dyad-write": + return ( + + {content} + + ); + + case "dyad-rename": + return ( + + {content} + + ); + + case "dyad-delete": + return ( + + {content} + + ); + + case "dyad-add-dependency": + return ( + + {content} + + ); + + case "dyad-execute-sql": + return ( + + {content} + + ); + + case "dyad-add-integration": + return ( + + {content} + + ); + + case "dyad-edit": + return ( + + {content} + + ); + + case "dyad-search-replace": + return ( + + {content} + + ); + + case "dyad-codebase-context": + return ( + + {content} + + ); + + case "dyad-mcp-tool-call": + return ( + + {content} + + ); + + case "dyad-mcp-tool-result": + return ( + + {content} + + ); + + case "dyad-output": + return ( + + {content} + + ); + + case "dyad-problem-report": + return ( + + {content} + + ); + + case "dyad-chat-summary": + // Don't render anything for dyad-chat-summary + return null; + + case "dyad-command": + if (attributes.type) { + const action = { + id: attributes.type, + } as SuggestedAction; + return <>{mapActionToButton(action)}; + } + return null; + + default: + return null; + } +} diff --git a/src/components/chat/MoreMinimoreMcpToolCall.tsx b/src/components/chat/MoreMinimoreMcpToolCall.tsx new file mode 100644 index 0000000..eaf8781 --- /dev/null +++ b/src/components/chat/MoreMinimoreMcpToolCall.tsx @@ -0,0 +1,73 @@ +import React, { useMemo, useState } from "react"; +import { Wrench, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; + +interface MoreMinimoreMcpToolCallProps { + node?: any; + children?: React.ReactNode; +} + +export const MoreMinimoreMcpToolCall: React.FC = ({ + node, + children, +}) => { + const serverName: string = node?.properties?.serverName || ""; + const toolName: string = node?.properties?.toolName || ""; + const [expanded, setExpanded] = useState(false); + + const raw = typeof children === "string" ? children : String(children ?? ""); + + const prettyJson = useMemo(() => { + if (!expanded) return ""; + try { + const parsed = JSON.parse(raw); + return JSON.stringify(parsed, null, 2); + } catch (e) { + console.error("Error parsing JSON for dyad-mcp-tool-call", e); + return raw; + } + }, [expanded, raw]); + + return ( +
setExpanded((v) => !v)} + > + {/* Top-left label badge */} +
+ + Tool Call +
+ + {/* Right chevron */} +
+ {expanded ? : } +
+ + {/* Header content */} +
+ {serverName ? ( + + {serverName} + + ) : null} + {toolName ? ( + + {toolName} + + ) : null} + {/* Intentionally no preview or content when collapsed */} +
+ + {/* JSON content */} + {expanded ? ( +
+ {prettyJson} +
+ ) : null} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreMcpToolResult.tsx b/src/components/chat/MoreMinimoreMcpToolResult.tsx new file mode 100644 index 0000000..55d3fd8 --- /dev/null +++ b/src/components/chat/MoreMinimoreMcpToolResult.tsx @@ -0,0 +1,73 @@ +import React, { useMemo, useState } from "react"; +import { CheckCircle, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; + +interface MoreMinimoreMcpToolResultProps { + node?: any; + children?: React.ReactNode; +} + +export const MoreMinimoreMcpToolResult: React.FC = ({ + node, + children, +}) => { + const serverName: string = node?.properties?.serverName || ""; + const toolName: string = node?.properties?.toolName || ""; + const [expanded, setExpanded] = useState(false); + + const raw = typeof children === "string" ? children : String(children ?? ""); + + const prettyJson = useMemo(() => { + if (!expanded) return ""; + try { + const parsed = JSON.parse(raw); + return JSON.stringify(parsed, null, 2); + } catch (e) { + console.error("Error parsing JSON for dyad-mcp-tool-result", e); + return raw; + } + }, [expanded, raw]); + + return ( +
setExpanded((v) => !v)} + > + {/* Top-left label badge */} +
+ + Tool Result +
+ + {/* Right chevron */} +
+ {expanded ? : } +
+ + {/* Header content */} +
+ {serverName ? ( + + {serverName} + + ) : null} + {toolName ? ( + + {toolName} + + ) : null} + {/* Intentionally no preview or content when collapsed */} +
+ + {/* JSON content */} + {expanded ? ( +
+ {prettyJson} +
+ ) : null} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreOutput.tsx b/src/components/chat/MoreMinimoreOutput.tsx new file mode 100644 index 0000000..9e6816a --- /dev/null +++ b/src/components/chat/MoreMinimoreOutput.tsx @@ -0,0 +1,112 @@ +import React, { useState } from "react"; +import { + ChevronsDownUp, + ChevronsUpDown, + AlertTriangle, + XCircle, + Sparkles, +} from "lucide-react"; +import { useAtomValue } from "jotai"; +import { selectedChatIdAtom } from "@/atoms/chatAtoms"; +import { useStreamChat } from "@/hooks/useStreamChat"; +import { CopyErrorMessage } from "@/components/CopyErrorMessage"; +interface MoreMinimoreOutputProps { + type: "error" | "warning"; + message?: string; + children?: React.ReactNode; +} + +export const MoreMinimoreOutput: React.FC = ({ + type, + message, + children, +}) => { + const [isContentVisible, setIsContentVisible] = useState(false); + const selectedChatId = useAtomValue(selectedChatIdAtom); + const { streamMessage } = useStreamChat(); + + // If the type is not warning, it is an error (in case LLM gives a weird "type") + const isError = type !== "warning"; + const borderColor = isError ? "border-red-500" : "border-amber-500"; + const iconColor = isError ? "text-red-500" : "text-amber-500"; + const icon = isError ? ( + + ) : ( + + ); + const label = isError ? "Error" : "Warning"; + + const handleAIFix = (e: React.MouseEvent) => { + e.stopPropagation(); + if (message && selectedChatId) { + streamMessage({ + prompt: `Fix the error: ${message}`, + chatId: selectedChatId, + }); + } + }; + + return ( +
setIsContentVisible(!isContentVisible)} + > + {/* Top-left label badge */} +
+ {icon} + {label} +
+ + {/* Main content, padded to avoid label */} +
+
+ {message && ( + + {message.slice(0, isContentVisible ? undefined : 100) + + (!isContentVisible ? "..." : "")} + + )} +
+
+ {isContentVisible ? ( + + ) : ( + + )} +
+
+ + {/* Content area */} + {isContentVisible && children && ( +
+ {children} +
+ )} + + {/* Action buttons at the bottom - always visible for errors */} + {isError && message && ( +
+ + +
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreProblemSummary.tsx b/src/components/chat/MoreMinimoreProblemSummary.tsx new file mode 100644 index 0000000..958054f --- /dev/null +++ b/src/components/chat/MoreMinimoreProblemSummary.tsx @@ -0,0 +1,156 @@ +import React, { useState } from "react"; +import { + ChevronsDownUp, + ChevronsUpDown, + AlertTriangle, + FileText, +} from "lucide-react"; +import type { Problem } from "@/ipc/ipc_types"; + +type ProblemWithoutSnippet = Omit; + +interface MoreMinimoreProblemSummaryProps { + summary?: string; + children?: React.ReactNode; +} + +interface ProblemItemProps { + problem: ProblemWithoutSnippet; + index: number; +} + +const ProblemItem: React.FC = ({ problem, index }) => { + return ( +
+
+ + {index + 1} + +
+
+
+ + + {problem.file} + + + + {problem.line}:{problem.column} + + + TS{problem.code} + +
+

+ {problem.message} +

+
+
+ ); +}; + +export const MoreMinimoreProblemSummary: React.FC = ({ + summary, + children, +}) => { + const [isContentVisible, setIsContentVisible] = useState(false); + + // Parse problems from children content if available + const problems: ProblemWithoutSnippet[] = React.useMemo(() => { + if (!children || typeof children !== "string") return []; + + // Parse structured format with tags + const problemTagRegex = + /([^<]+)<\/problem>/g; + const problems: ProblemWithoutSnippet[] = []; + let match; + + while ((match = problemTagRegex.exec(children)) !== null) { + try { + problems.push({ + file: match[1], + line: parseInt(match[2], 10), + column: parseInt(match[3], 10), + message: match[5].trim(), + code: parseInt(match[4], 10), + }); + } catch { + return [ + { + file: "unknown", + line: 0, + column: 0, + message: children, + code: 0, + }, + ]; + } + } + + return problems; + }, [children]); + + const totalProblems = problems.length; + const displaySummary = + summary || `${totalProblems} problems found (TypeScript errors)`; + + return ( +
setIsContentVisible(!isContentVisible)} + data-testid="problem-summary" + > +
+
+ + + + Auto-fix + + {displaySummary} + +
+
+ {isContentVisible ? ( + + ) : ( + + )} +
+
+ + {/* Content area - show individual problems */} + {isContentVisible && totalProblems > 0 && ( +
+
+ {problems.map((problem, index) => ( + + ))} +
+
+ )} + + {/* Fallback content area for raw children */} + {isContentVisible && totalProblems === 0 && children && ( +
+
+            {children}
+          
+
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreRead.tsx b/src/components/chat/MoreMinimoreRead.tsx new file mode 100644 index 0000000..d1453df --- /dev/null +++ b/src/components/chat/MoreMinimoreRead.tsx @@ -0,0 +1,44 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { FileText } from "lucide-react"; + +interface MoreMinimoreReadProps { + children?: ReactNode; + node?: any; + path?: string; +} + +export const MoreMinimoreRead: React.FC = ({ + children, + node, + path: pathProp, +}) => { + const path = pathProp || node?.properties?.path || ""; + const fileName = path ? path.split("/").pop() : ""; + + return ( +
+
+
+ + {fileName && ( + + {fileName} + + )} +
Read
+
+
+ {path && ( +
+ {path} +
+ )} + {children && ( +
+ {children} +
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreRename.tsx b/src/components/chat/MoreMinimoreRename.tsx new file mode 100644 index 0000000..4945fbf --- /dev/null +++ b/src/components/chat/MoreMinimoreRename.tsx @@ -0,0 +1,61 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { FileEdit } from "lucide-react"; + +interface MoreMinimoreRenameProps { + children?: ReactNode; + node?: any; + from?: string; + to?: string; +} + +export const MoreMinimoreRename: React.FC = ({ + children, + node, + from: fromProp, + to: toProp, +}) => { + // Use props directly if provided, otherwise extract from node + const from = fromProp || node?.properties?.from || ""; + const to = toProp || node?.properties?.to || ""; + + // Extract filenames from paths + const fromFileName = from ? from.split("/").pop() : ""; + const toFileName = to ? to.split("/").pop() : ""; + + return ( +
+
+
+ + {(fromFileName || toFileName) && ( + + {fromFileName && toFileName + ? `${fromFileName} โ†’ ${toFileName}` + : fromFileName || toFileName} + + )} +
Rename
+
+
+ {(from || to) && ( +
+ {from && ( +
+ From:{" "} + {from} +
+ )} + {to && ( +
+ To: {to} +
+ )} +
+ )} +
+ {children} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreSearchReplace.tsx b/src/components/chat/MoreMinimoreSearchReplace.tsx new file mode 100644 index 0000000..8d312ce --- /dev/null +++ b/src/components/chat/MoreMinimoreSearchReplace.tsx @@ -0,0 +1,151 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { useMemo, useState } from "react"; +import { + ChevronsDownUp, + ChevronsUpDown, + Loader, + CircleX, + Search, + ArrowLeftRight, +} from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; +import { CustomTagState } from "./stateTypes"; +import { parseSearchReplaceBlocks } from "@/pro/shared/search_replace_parser"; + +interface MoreMinimoreSearchReplaceProps { + children?: ReactNode; + node?: any; + path?: string; + description?: string; +} + +export const MoreMinimoreSearchReplace: React.FC = ({ + children, + node, + path: pathProp, + description: descriptionProp, +}) => { + const [isContentVisible, setIsContentVisible] = useState(false); + + const path = pathProp || node?.properties?.path || ""; + const description = descriptionProp || node?.properties?.description || ""; + const state = node?.properties?.state as CustomTagState; + const inProgress = state === "pending"; + const aborted = state === "aborted"; + + const blocks = useMemo( + () => parseSearchReplaceBlocks(String(children ?? "")), + [children], + ); + + const fileName = path ? path.split("/").pop() : ""; + + return ( +
setIsContentVisible(!isContentVisible)} + > +
+
+
+ + + Search & Replace + +
+ {fileName && ( + + {fileName} + + )} + {inProgress && ( +
+ + Applying changes... +
+ )} + {aborted && ( +
+ + Did not finish +
+ )} +
+
+ {isContentVisible ? ( + + ) : ( + + )} +
+
+ {path && ( +
+ {path} +
+ )} + {description && ( +
+ Summary: + {description} +
+ )} + {isContentVisible && ( +
e.stopPropagation()} + > + {blocks.length === 0 ? ( + + {children} + + ) : ( +
+ {blocks.map((b, i) => ( +
+
+
+ + Change {i + 1} +
+
+
+
+
+ Search +
+ + {b.searchContent} + +
+
+
+ Replace +
+ + {b.replaceContent} + +
+
+
+ ))} +
+ )} +
+ )} +
+ ); +}; diff --git a/src/components/chat/MoreMinimoreThink.tsx b/src/components/chat/MoreMinimoreThink.tsx new file mode 100644 index 0000000..559d5e9 --- /dev/null +++ b/src/components/chat/MoreMinimoreThink.tsx @@ -0,0 +1,96 @@ +import React, { useState, useEffect } from "react"; +import { Brain, ChevronDown, ChevronUp, Loader } from "lucide-react"; +import { VanillaMarkdownParser } from "./MoreMinimoreMarkdownParser"; +import { CustomTagState } from "./stateTypes"; +import { MoreMinimoreTokenSavings } from "./MoreMinimoreTokenSavings"; + +interface AIThinkProps { + node?: any; + children?: React.ReactNode; +} + +export const MoreMinimoreThink: React.FC = ({ children, node }) => { + const state = node?.properties?.state as CustomTagState; + const inProgress = state === "pending"; + const [isExpanded, setIsExpanded] = useState(inProgress); + + // Check if content matches token savings format + const tokenSavingsMatch = + typeof children === "string" + ? children.match( + /^dyad-token-savings\?original-tokens=([0-9.]+)&smart-context-tokens=([0-9.]+)$/, + ) + : null; + + // Collapse when transitioning from in-progress to not-in-progress + useEffect(() => { + if (!inProgress && isExpanded) { + setIsExpanded(false); + } + }, [inProgress]); + + // If it's token savings format, render MoreMinimoreTokenSavings component + if (tokenSavingsMatch) { + const originalTokens = parseFloat(tokenSavingsMatch[1]); + const smartContextTokens = parseFloat(tokenSavingsMatch[2]); + return ( + + ); + } + + return ( +
setIsExpanded(!isExpanded)} + role="button" + aria-expanded={isExpanded} + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setIsExpanded(!isExpanded); + } + }} + > + {/* Top-left label badge */} +
+ + Thinking + {inProgress && ( + + )} +
+ + {/* Indicator icon */} +
+ {isExpanded ? : } +
+ + {/* Main content with smooth transition */} +
+
+ {typeof children === "string" ? ( + + ) : ( + children + )} +
+
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreTokenSavings.tsx b/src/components/chat/MoreMinimoreTokenSavings.tsx new file mode 100644 index 0000000..30a521d --- /dev/null +++ b/src/components/chat/MoreMinimoreTokenSavings.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Zap } from "lucide-react"; +import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip"; + +interface TokenSavingsProps { + originalTokens: number; + smartContextTokens: number; +} + +export const MoreMinimoreTokenSavings: React.FC = ({ + originalTokens, + smartContextTokens, +}) => { + const tokensSaved = originalTokens - smartContextTokens; + const percentageSaved = Math.round((tokensSaved / originalTokens) * 100); + + return ( + + +
+
+ + + Saved {percentageSaved}% of codebase tokens with Smart Context + +
+
+
+ +
+ Saved {Math.round(tokensSaved).toLocaleString()} tokens +
+
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreWebCrawl.tsx b/src/components/chat/MoreMinimoreWebCrawl.tsx new file mode 100644 index 0000000..7575fe5 --- /dev/null +++ b/src/components/chat/MoreMinimoreWebCrawl.tsx @@ -0,0 +1,27 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { ScanQrCode } from "lucide-react"; + +interface MoreMinimoreWebCrawlProps { + children?: ReactNode; + node?: any; +} + +export const MoreMinimoreWebCrawl: React.FC = ({ + children, + node: _node, +}) => { + return ( +
+
+
+ +
Web Crawl
+
+
+
+ {children} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreWebSearch.tsx b/src/components/chat/MoreMinimoreWebSearch.tsx new file mode 100644 index 0000000..8d737fb --- /dev/null +++ b/src/components/chat/MoreMinimoreWebSearch.tsx @@ -0,0 +1,31 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { Globe } from "lucide-react"; + +interface MoreMinimoreWebSearchProps { + children?: ReactNode; + node?: any; + query?: string; +} + +export const MoreMinimoreWebSearch: React.FC = ({ + children, + node: _node, + query: queryProp, +}) => { + const query = queryProp || (typeof children === "string" ? children : ""); + + return ( +
+
+
+ +
Web Search
+
+
+
+ {query || children} +
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreWebSearchResult.tsx b/src/components/chat/MoreMinimoreWebSearchResult.tsx new file mode 100644 index 0000000..9f15209 --- /dev/null +++ b/src/components/chat/MoreMinimoreWebSearchResult.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from "react"; +import { ChevronDown, ChevronUp, Globe, Loader } from "lucide-react"; +import { VanillaMarkdownParser } from "./MoreMinimoreMarkdownParser"; +import { CustomTagState } from "./stateTypes"; + +interface MoreMinimoreWebSearchResultProps { + node?: any; + children?: React.ReactNode; +} + +export const MoreMinimoreWebSearchResult: React.FC = ({ + children, + node, +}) => { + const state = node?.properties?.state as CustomTagState; + const inProgress = state === "pending"; + const [isExpanded, setIsExpanded] = useState(inProgress); + + // Collapse when transitioning from in-progress to not-in-progress + useEffect(() => { + if (!inProgress && isExpanded) { + setIsExpanded(false); + } + }, [inProgress]); + + return ( +
setIsExpanded(!isExpanded)} + role="button" + aria-expanded={isExpanded} + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setIsExpanded(!isExpanded); + } + }} + > + {/* Top-left label badge */} +
+ + Web Search Result + {inProgress && ( + + )} +
+ + {/* Indicator icon */} +
+ {isExpanded ? : } +
+ + {/* Main content with smooth transition */} +
+
+ {typeof children === "string" ? ( + + ) : ( + children + )} +
+
+
+ ); +}; diff --git a/src/components/chat/MoreMinimoreWrite.tsx b/src/components/chat/MoreMinimoreWrite.tsx new file mode 100644 index 0000000..1a321a9 --- /dev/null +++ b/src/components/chat/MoreMinimoreWrite.tsx @@ -0,0 +1,159 @@ +import type React from "react"; +import type { ReactNode } from "react"; +import { useState } from "react"; +import { + ChevronsDownUp, + ChevronsUpDown, + Pencil, + Loader, + CircleX, + Edit, + X, +} from "lucide-react"; +import { CodeHighlight } from "./CodeHighlight"; +import { CustomTagState } from "./stateTypes"; +import { FileEditor } from "../preview_panel/FileEditor"; +import { useAtomValue } from "jotai"; +import { selectedAppIdAtom } from "@/atoms/appAtoms"; + +interface MoreMinimoreWriteProps { + children?: ReactNode; + node?: any; + path?: string; + description?: string; +} + +export const MoreMinimoreWrite: React.FC = ({ + children, + node, + path: pathProp, + description: descriptionProp, +}) => { + const [isContentVisible, setIsContentVisible] = useState(false); + + // Use props directly if provided, otherwise extract from node + const path = pathProp || node?.properties?.path || ""; + const description = descriptionProp || node?.properties?.description || ""; + const state = node?.properties?.state as CustomTagState; + + const aborted = state === "aborted"; + const appId = useAtomValue(selectedAppIdAtom); + const [isEditing, setIsEditing] = useState(false); + const inProgress = state === "pending"; + + const handleCancel = () => { + setIsEditing(false); + }; + + const handleEdit = () => { + setIsEditing(true); + setIsContentVisible(true); + }; + // Extract filename from path + const fileName = path ? path.split("/").pop() : ""; + + return ( +
setIsContentVisible(!isContentVisible)} + > +
+
+ + {fileName && ( + + {fileName} + + )} + {inProgress && ( +
+ + Writing... +
+ )} + {aborted && ( +
+ + Did not finish +
+ )} +
+
+ {!inProgress && ( + <> + {isEditing ? ( + <> + + + ) : ( + + )} + + )} + {isContentVisible ? ( + + ) : ( + + )} +
+
+ {path && ( +
+ {path} +
+ )} + {description && ( +
+ Summary: + {description} +
+ )} + {isContentVisible && ( +
e.stopPropagation()} + > + {isEditing ? ( +
+ +
+ ) : ( + + {children} + + )} +
+ )} +
+ ); +}; diff --git a/src/components/chat/PromoMessage.tsx b/src/components/chat/PromoMessage.tsx index 57de11b..98a2cb2 100644 --- a/src/components/chat/PromoMessage.tsx +++ b/src/components/chat/PromoMessage.tsx @@ -56,7 +56,7 @@ export function Message({ spans }: MessageConfig) { export const TURBO_EDITS_PROMO_MESSAGE: MessageConfig = { spans: [ { type: "text", content: "Tired of waiting on AI?" }, - { type: "link", content: " Get Dyad Pro", url: "https://dyad.sh/pro#ai" }, + { type: "link", content: " Get MoreMinimore Pro", url: "https://dyad.sh/pro#ai" }, { type: "text", content: " for faster edits with Turbo Edits." }, ], }; @@ -66,7 +66,7 @@ export const SMART_CONTEXT_PROMO_MESSAGE: MessageConfig = { { type: "text", content: "Save up to 5x on AI costs with " }, { type: "link", - content: "Dyad Pro's Smart Context", + content: "MoreMinimore Pro's Smart Context", url: "https://dyad.sh/pro#ai", }, ], @@ -90,7 +90,7 @@ export const REDDIT_TIP: MessageConfig = { }, { type: "link", - content: "Dyad subreddit", + content: "MoreMinimore subreddit", url: "https://www.reddit.com/r/dyadbuilders/", }, ], @@ -124,7 +124,7 @@ export const BUILD_A_BIBLE_APP_TIP: MessageConfig = { }, { type: "text", - content: " the creator of Dyad build a Bible app step-by-step", + content: " the creator of MoreMinimore build a Bible app step-by-step", }, ], }; @@ -182,12 +182,12 @@ export const ROADMAP_TIP: MessageConfig = { ], }; -// Like Dyad? Star it on GitHub https://github.com/dyad-sh/dyad/ +// Like MoreMinimore? Star it on GitHub https://github.com/dyad-sh/dyad/ export const GITHUB_TIP: MessageConfig = { spans: [ { type: "text", - content: "Like Dyad? Star it on ", + content: "Like MoreMinimore? Star it on ", }, { type: "link", diff --git a/src/components/chat/TokenBar.tsx b/src/components/chat/TokenBar.tsx index 58f5919..f906a0f 100644 --- a/src/components/chat/TokenBar.tsx +++ b/src/components/chat/TokenBar.tsx @@ -145,7 +145,7 @@ export function TokenBar({ chatId }: TokenBarProps) { } className="text-blue-500 dark:text-blue-400 cursor-pointer hover:underline" > - Dyad Pro's Smart Context + MoreMinimore Pro's Smart Context
)} diff --git a/src/components/home/OnboardingBanner.tsx b/src/components/home/OnboardingBanner.tsx index 0036d69..5f03e45 100644 --- a/src/components/home/OnboardingBanner.tsx +++ b/src/components/home/OnboardingBanner.tsx @@ -29,7 +29,7 @@ export const OnboardingBanner = ({
Get started with Dyad in 3 minutes
@@ -41,7 +41,7 @@ export const OnboardingBanner = ({

- Get started with Dyad in 3 minutes + Get started with MoreMinimore in 3 minutes

Start building your app for free diff --git a/src/components/preview_panel/AnnotatorOnlyForPro.tsx b/src/components/preview_panel/AnnotatorOnlyForPro.tsx index 08f307c..9925cf4 100644 --- a/src/components/preview_panel/AnnotatorOnlyForPro.tsx +++ b/src/components/preview_panel/AnnotatorOnlyForPro.tsx @@ -36,7 +36,7 @@ export const AnnotatorOnlyForPro = ({ onGoBack }: AnnotatorOnlyForProProps) => {

Unlock the ability to annotate screenshots and enhance your workflow - with Dyad Pro. + with MoreMinimore Pro.

{/* Get Pro Button */} @@ -45,7 +45,7 @@ export const AnnotatorOnlyForPro = ({ onGoBack }: AnnotatorOnlyForProProps) => { size="lg" className="px-8 shadow-md hover:shadow-lg transition-all" > - Get Dyad Pro + Get MoreMinimore Pro
diff --git a/src/components/preview_panel/PreviewIframe.tsx b/src/components/preview_panel/PreviewIframe.tsx index 6d0485d..a2bc990 100644 --- a/src/components/preview_panel/PreviewIframe.tsx +++ b/src/components/preview_panel/PreviewIframe.tsx @@ -64,7 +64,6 @@ import { useShortcut } from "@/hooks/useShortcut"; import { cn } from "@/lib/utils"; import { normalizePath } from "../../../shared/normalizePath"; import { showError } from "@/lib/toast"; -import { AnnotatorOnlyForPro } from "./AnnotatorOnlyForPro"; import { useAttachments } from "@/hooks/useAttachments"; import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; import { Annotator } from "@/pro/ui/components/Annotator/Annotator"; @@ -107,7 +106,7 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => { {/* Add a little chip that says "Internal error" if source is "dyad-app" */} {error.source === "dyad-app" && (
- Internal Dyad error + Internal MoreMinimore error
)} @@ -143,7 +142,7 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => { {isDockerError ? "Make sure Docker Desktop is running and try restarting the app." : error.source === "dyad-app" - ? "Try restarting the Dyad app or restarting your computer to see if that fixes the error." + ? "Try restarting the MoreMinimore app or restarting your computer to see if that fixes the error." : "Check if restarting the app fixes the error."}
@@ -985,17 +984,11 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { : { width: `${deviceWidthConfig[deviceMode]}px` } } > - {userBudget ? ( - - ) : ( - setAnnotatorMode(false)} - /> - )} +
) : ( <> diff --git a/src/components/preview_panel/PreviewIframe.tsx.bak b/src/components/preview_panel/PreviewIframe.tsx.bak new file mode 100644 index 0000000..a2bc990 --- /dev/null +++ b/src/components/preview_panel/PreviewIframe.tsx.bak @@ -0,0 +1,1081 @@ +import { + selectedAppIdAtom, + appUrlAtom, + appOutputAtom, + previewErrorMessageAtom, +} from "@/atoms/appAtoms"; +import { useAtomValue, useSetAtom, useAtom } from "jotai"; +import { useEffect, useRef, useState } from "react"; +import { + ArrowLeft, + ArrowRight, + RefreshCw, + ExternalLink, + Loader2, + X, + Sparkles, + ChevronDown, + Lightbulb, + ChevronRight, + MousePointerClick, + Power, + MonitorSmartphone, + Monitor, + Tablet, + Smartphone, + Pen, +} from "lucide-react"; +import { selectedChatIdAtom } from "@/atoms/chatAtoms"; +import { CopyErrorMessage } from "@/components/CopyErrorMessage"; +import { IpcClient } from "@/ipc/ipc_client"; + +import { useParseRouter } from "@/hooks/useParseRouter"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { useStreamChat } from "@/hooks/useStreamChat"; +import { + selectedComponentsPreviewAtom, + visualEditingSelectedComponentAtom, + currentComponentCoordinatesAtom, + previewIframeRefAtom, + annotatorModeAtom, + screenshotDataUrlAtom, + pendingVisualChangesAtom, +} from "@/atoms/previewAtoms"; +import { ComponentSelection } from "@/ipc/ipc_types"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { useRunApp } from "@/hooks/useRunApp"; +import { useShortcut } from "@/hooks/useShortcut"; +import { cn } from "@/lib/utils"; +import { normalizePath } from "../../../shared/normalizePath"; +import { showError } from "@/lib/toast"; +import { useAttachments } from "@/hooks/useAttachments"; +import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; +import { Annotator } from "@/pro/ui/components/Annotator/Annotator"; +import { VisualEditingToolbar } from "./VisualEditingToolbar"; + +interface ErrorBannerProps { + error: { message: string; source: "preview-app" | "dyad-app" } | undefined; + onDismiss: () => void; + onAIFix: () => void; +} + +const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => { + const [isCollapsed, setIsCollapsed] = useState(true); + const { isStreaming } = useStreamChat(); + if (!error) return null; + const isDockerError = error.message.includes("Cannot connect to the Docker"); + + const getTruncatedError = () => { + const firstLine = error.message.split("\n")[0]; + const snippetLength = 250; + const snippet = error.message.substring(0, snippetLength); + return firstLine.length < snippet.length + ? firstLine + : snippet + (snippet.length === snippetLength ? "..." : ""); + }; + + return ( +
+ {/* Close button in top left */} + + + {/* Add a little chip that says "Internal error" if source is "dyad-app" */} + {error.source === "dyad-app" && ( +
+ Internal MoreMinimore error +
+ )} + + {/* Error message in the middle */} +
+
setIsCollapsed(!isCollapsed)} + > + + {isCollapsed ? getTruncatedError() : error.message} +
+
+ + {/* Tip message */} +
+
+
+ +
+ + Tip: + {isDockerError + ? "Make sure Docker Desktop is running and try restarting the app." + : error.source === "dyad-app" + ? "Try restarting the MoreMinimore app or restarting your computer to see if that fixes the error." + : "Check if restarting the app fixes the error."} + +
+
+ + {/* Action buttons at the bottom */} + {!isDockerError && error.source === "preview-app" && ( +
+ + +
+ )} +
+ ); +}; + +// Preview iframe component +export const PreviewIframe = ({ loading }: { loading: boolean }) => { + const selectedAppId = useAtomValue(selectedAppIdAtom); + const { appUrl, originalUrl } = useAtomValue(appUrlAtom); + const setAppOutput = useSetAtom(appOutputAtom); + // State to trigger iframe reload + const [reloadKey, setReloadKey] = useState(0); + const [errorMessage, setErrorMessage] = useAtom(previewErrorMessageAtom); + const selectedChatId = useAtomValue(selectedChatIdAtom); + const { streamMessage } = useStreamChat(); + const { routes: availableRoutes } = useParseRouter(selectedAppId); + const { restartApp } = useRunApp(); + const { userBudget } = useUserBudgetInfo(); + const isProMode = !!userBudget; + + // Navigation state + const [isComponentSelectorInitialized, setIsComponentSelectorInitialized] = + useState(false); + const [canGoBack, setCanGoBack] = useState(false); + const [canGoForward, setCanGoForward] = useState(false); + const [navigationHistory, setNavigationHistory] = useState([]); + const [currentHistoryPosition, setCurrentHistoryPosition] = useState(0); + const setSelectedComponentsPreview = useSetAtom( + selectedComponentsPreviewAtom, + ); + const [visualEditingSelectedComponent, setVisualEditingSelectedComponent] = + useAtom(visualEditingSelectedComponentAtom); + const setCurrentComponentCoordinates = useSetAtom( + currentComponentCoordinatesAtom, + ); + const setPreviewIframeRef = useSetAtom(previewIframeRefAtom); + const iframeRef = useRef(null); + const [isPicking, setIsPicking] = useState(false); + const [annotatorMode, setAnnotatorMode] = useAtom(annotatorModeAtom); + const [screenshotDataUrl, setScreenshotDataUrl] = useAtom( + screenshotDataUrlAtom, + ); + + const { addAttachments } = useAttachments(); + const setPendingChanges = useSetAtom(pendingVisualChangesAtom); + + // AST Analysis State + const [isDynamicComponent, setIsDynamicComponent] = useState(false); + const [hasStaticText, setHasStaticText] = useState(false); + + // Device mode state + type DeviceMode = "desktop" | "tablet" | "mobile"; + const [deviceMode, setDeviceMode] = useState("desktop"); + const [isDevicePopoverOpen, setIsDevicePopoverOpen] = useState(false); + + // Device configurations + const deviceWidthConfig = { + tablet: 768, + mobile: 375, + }; + + //detect if the user is using Mac + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + + const analyzeComponent = async (componentId: string) => { + if (!componentId || !selectedAppId) return; + + try { + const result = await IpcClient.getInstance().analyzeComponent({ + appId: selectedAppId, + componentId, + }); + setIsDynamicComponent(result.isDynamic); + setHasStaticText(result.hasStaticText); + + // Automatically enable text editing if component has static text + if (result.hasStaticText && iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage( + { + type: "enable-dyad-text-editing", + data: { + componentId: componentId, + runtimeId: visualEditingSelectedComponent?.runtimeId, + }, + }, + "*", + ); + } + } catch (err) { + console.error("Failed to analyze component", err); + setIsDynamicComponent(false); + setHasStaticText(false); + } + }; + + const handleTextUpdated = async (data: any) => { + const { componentId, text } = data; + if (!componentId || !selectedAppId) return; + + // Parse componentId to extract file path and line number + const [filePath, lineStr] = componentId.split(":"); + const lineNumber = parseInt(lineStr, 10); + + if (!filePath || isNaN(lineNumber)) { + console.error("Invalid componentId format:", componentId); + return; + } + + // Store text change in pending changes + setPendingChanges((prev) => { + const updated = new Map(prev); + const existing = updated.get(componentId); + + updated.set(componentId, { + componentId: componentId, + componentName: + existing?.componentName || visualEditingSelectedComponent?.name || "", + relativePath: filePath, + lineNumber: lineNumber, + styles: existing?.styles || {}, + textContent: text, + }); + + return updated; + }); + }; + + // Function to get current styles from selected element + const getCurrentElementStyles = () => { + if (!iframeRef.current?.contentWindow || !visualEditingSelectedComponent) + return; + + try { + // Send message to iframe to get current styles + iframeRef.current.contentWindow.postMessage( + { + type: "get-dyad-component-styles", + data: { + elementId: visualEditingSelectedComponent.id, + runtimeId: visualEditingSelectedComponent.runtimeId, + }, + }, + "*", + ); + } catch (error) { + console.error("Failed to get element styles:", error); + } + }; + useEffect(() => { + setAnnotatorMode(false); + }, []); + // Reset visual editing state when app changes or component unmounts + useEffect(() => { + return () => { + // Cleanup on unmount or when app changes + setVisualEditingSelectedComponent(null); + setPendingChanges(new Map()); + setCurrentComponentCoordinates(null); + }; + }, [selectedAppId]); + + // Update iframe ref atom + useEffect(() => { + setPreviewIframeRef(iframeRef.current); + }, [iframeRef.current, setPreviewIframeRef]); + + // Send pro mode status to iframe + useEffect(() => { + if (iframeRef.current?.contentWindow && isComponentSelectorInitialized) { + iframeRef.current.contentWindow.postMessage( + { type: "dyad-pro-mode", enabled: isProMode }, + "*", + ); + } + }, [isProMode, isComponentSelectorInitialized]); + + // Add message listener for iframe errors and navigation events + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + // Only handle messages from our iframe + if (event.source !== iframeRef.current?.contentWindow) { + return; + } + + if (event.data?.type === "dyad-component-selector-initialized") { + setIsComponentSelectorInitialized(true); + iframeRef.current?.contentWindow?.postMessage( + { type: "dyad-pro-mode", enabled: isProMode }, + "*", + ); + return; + } + + if (event.data?.type === "dyad-text-updated") { + handleTextUpdated(event.data); + return; + } + + if (event.data?.type === "dyad-text-finalized") { + handleTextUpdated(event.data); + return; + } + + if (event.data?.type === "dyad-component-selected") { + console.log("Component picked:", event.data); + + const component = parseComponentSelection(event.data); + + if (!component) return; + + // Store the coordinates + if (event.data.coordinates && isProMode) { + setCurrentComponentCoordinates(event.data.coordinates); + } + + // Add to selected components if not already there + setSelectedComponentsPreview((prev) => { + const exists = prev.some((c) => { + // Check by runtimeId if available otherwise by id + // Stored components may have lost their runtimeId after re-renders or reloading the page + if (component.runtimeId && c.runtimeId) { + return c.runtimeId === component.runtimeId; + } + return c.id === component.id; + }); + if (exists) { + return prev; + } + return [...prev, component]; + }); + + if (isProMode) { + // Set as the highlighted component for visual editing + setVisualEditingSelectedComponent(component); + // Trigger AST analysis + analyzeComponent(component.id); + } + + return; + } + + if (event.data?.type === "dyad-component-deselected") { + const componentId = event.data.componentId; + if (componentId) { + // Disable text editing for the deselected component + if (iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage( + { + type: "disable-dyad-text-editing", + data: { componentId }, + }, + "*", + ); + } + + setSelectedComponentsPreview((prev) => + prev.filter((c) => c.id !== componentId), + ); + setVisualEditingSelectedComponent((prev) => { + const shouldClear = prev?.id === componentId; + if (shouldClear) { + setCurrentComponentCoordinates(null); + } + return shouldClear ? null : prev; + }); + } + return; + } + + if (event.data?.type === "dyad-component-coordinates-updated") { + if (event.data.coordinates) { + setCurrentComponentCoordinates(event.data.coordinates); + } + return; + } + + if (event.data?.type === "dyad-screenshot-response") { + if (event.data.success && event.data.dataUrl) { + setScreenshotDataUrl(event.data.dataUrl); + setAnnotatorMode(true); + } else { + showError(event.data.error); + } + return; + } + + const { type, payload } = event.data as { + type: + | "window-error" + | "unhandled-rejection" + | "iframe-sourcemapped-error" + | "build-error-report" + | "pushState" + | "replaceState"; + payload?: { + message?: string; + stack?: string; + reason?: string; + newUrl?: string; + file?: string; + frame?: string; + }; + }; + + if ( + type === "window-error" || + type === "unhandled-rejection" || + type === "iframe-sourcemapped-error" + ) { + const stack = + type === "iframe-sourcemapped-error" + ? payload?.stack?.split("\n").slice(0, 1).join("\n") + : payload?.stack; + const errorMessage = `Error ${ + payload?.message || payload?.reason + }\nStack trace: ${stack}`; + console.error("Iframe error:", errorMessage); + setErrorMessage({ message: errorMessage, source: "preview-app" }); + setAppOutput((prev) => [ + ...prev, + { + message: `Iframe error: ${errorMessage}`, + type: "client-error", + appId: selectedAppId!, + timestamp: Date.now(), + }, + ]); + } else if (type === "build-error-report") { + console.debug(`Build error report: ${payload}`); + const errorMessage = `${payload?.message} from file ${payload?.file}.\n\nSource code:\n${payload?.frame}`; + setErrorMessage({ message: errorMessage, source: "preview-app" }); + setAppOutput((prev) => [ + ...prev, + { + message: `Build error report: ${JSON.stringify(payload)}`, + type: "client-error", + appId: selectedAppId!, + timestamp: Date.now(), + }, + ]); + } else if (type === "pushState" || type === "replaceState") { + console.debug(`Navigation event: ${type}`, payload); + + // Update navigation history based on the type of state change + if (type === "pushState" && payload?.newUrl) { + // For pushState, we trim any forward history and add the new URL + const newHistory = [ + ...navigationHistory.slice(0, currentHistoryPosition + 1), + payload.newUrl, + ]; + setNavigationHistory(newHistory); + setCurrentHistoryPosition(newHistory.length - 1); + } else if (type === "replaceState" && payload?.newUrl) { + // For replaceState, we replace the current URL + const newHistory = [...navigationHistory]; + newHistory[currentHistoryPosition] = payload.newUrl; + setNavigationHistory(newHistory); + } + } + }; + + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, [ + navigationHistory, + currentHistoryPosition, + selectedAppId, + errorMessage, + setErrorMessage, + setIsComponentSelectorInitialized, + setSelectedComponentsPreview, + setVisualEditingSelectedComponent, + ]); + + useEffect(() => { + // Update navigation buttons state + setCanGoBack(currentHistoryPosition > 0); + setCanGoForward(currentHistoryPosition < navigationHistory.length - 1); + }, [navigationHistory, currentHistoryPosition]); + + // Initialize navigation history when iframe loads + useEffect(() => { + if (appUrl) { + setNavigationHistory([appUrl]); + setCurrentHistoryPosition(0); + setCanGoBack(false); + setCanGoForward(false); + } + }, [appUrl]); + + // Get current styles when component is selected for visual editing + useEffect(() => { + if (visualEditingSelectedComponent) { + getCurrentElementStyles(); + } + }, [visualEditingSelectedComponent]); + + // Function to activate component selector in the iframe + const handleActivateComponentSelector = () => { + if (iframeRef.current?.contentWindow) { + const newIsPicking = !isPicking; + if (!newIsPicking) { + // Clean up any text editing states when deactivating + iframeRef.current.contentWindow.postMessage( + { type: "cleanup-all-text-editing" }, + "*", + ); + } + setIsPicking(newIsPicking); + setVisualEditingSelectedComponent(null); + iframeRef.current.contentWindow.postMessage( + { + type: newIsPicking + ? "activate-dyad-component-selector" + : "deactivate-dyad-component-selector", + }, + "*", + ); + } + }; + + // Function to handle annotator button click + const handleAnnotatorClick = () => { + if (annotatorMode) { + setAnnotatorMode(false); + return; + } + if (iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage( + { + type: "dyad-take-screenshot", + }, + "*", + ); + } + }; + + // Activate component selector using a shortcut + useShortcut( + "c", + { shift: true, ctrl: !isMac, meta: isMac }, + handleActivateComponentSelector, + isComponentSelectorInitialized, + iframeRef, + ); + + // Function to navigate back + const handleNavigateBack = () => { + if (canGoBack && iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage( + { + type: "navigate", + payload: { direction: "backward" }, + }, + "*", + ); + + // Update our local state + setCurrentHistoryPosition((prev) => prev - 1); + setCanGoBack(currentHistoryPosition - 1 > 0); + setCanGoForward(true); + } + }; + + // Function to navigate forward + const handleNavigateForward = () => { + if (canGoForward && iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.postMessage( + { + type: "navigate", + payload: { direction: "forward" }, + }, + "*", + ); + + // Update our local state + setCurrentHistoryPosition((prev) => prev + 1); + setCanGoBack(true); + setCanGoForward( + currentHistoryPosition + 1 < navigationHistory.length - 1, + ); + } + }; + + // Function to handle reload + const handleReload = () => { + setReloadKey((prevKey) => prevKey + 1); + setErrorMessage(undefined); + // Reset visual editing state + setVisualEditingSelectedComponent(null); + setPendingChanges(new Map()); + setCurrentComponentCoordinates(null); + // Optionally, add logic here if you need to explicitly stop/start the app again + // For now, just changing the key should remount the iframe + console.debug("Reloading iframe preview for app", selectedAppId); + }; + + // Function to navigate to a specific route + const navigateToRoute = (path: string) => { + if (iframeRef.current?.contentWindow && appUrl) { + // Create the full URL by combining the base URL with the path + const baseUrl = new URL(appUrl).origin; + const newUrl = `${baseUrl}${path}`; + + // Navigate to the URL + iframeRef.current.contentWindow.location.href = newUrl; + + // iframeRef.current.src = newUrl; + + // Update navigation history + const newHistory = [ + ...navigationHistory.slice(0, currentHistoryPosition + 1), + newUrl, + ]; + setNavigationHistory(newHistory); + setCurrentHistoryPosition(newHistory.length - 1); + setCanGoBack(true); + setCanGoForward(false); + } + }; + + // Display loading state + if (loading) { + return ( +
+
+
+
+
+
+
+

+ Preparing app preview... +

+
+
+ ); + } + + // Display message if no app is selected + if (selectedAppId === null) { + return ( +
+ Select an app to see the preview. +
+ ); + } + + const onRestart = () => { + restartApp(); + }; + + return ( +
+ {/* Browser-style header - hide when annotator is active */} + {!annotatorMode && ( +
+ {/* Navigation Buttons */} +
+ + + + + + +

+ {isPicking + ? "Deactivate component selector" + : "Select component"} +

+

{isMac ? "โŒ˜ + โ‡ง + C" : "Ctrl + โ‡ง + C"}

+
+
+
+ + + + + + +

+ {annotatorMode + ? "Annotator mode active" + : "Activate annotator"} +

+
+
+
+ + + +
+ + {/* Address Bar with Routes Dropdown - using shadcn/ui dropdown-menu */} +
+ + +
+ + {navigationHistory[currentHistoryPosition] + ? new URL(navigationHistory[currentHistoryPosition]) + .pathname + : "/"} + + +
+
+ + {availableRoutes.length > 0 ? ( + availableRoutes.map((route) => ( + navigateToRoute(route.path)} + className="flex justify-between" + > + {route.label} + + {route.path} + + + )) + ) : ( + + Loading routes... + + )} + +
+
+ + {/* Action Buttons */} +
+ + + + {/* Device Mode Button */} + + + + + e.preventDefault()} + onInteractOutside={(e) => e.preventDefault()} + > + + { + if (value) { + setDeviceMode(value as DeviceMode); + setIsDevicePopoverOpen(false); + } + }} + variant="outline" + > + {/* Tooltips placed inside items instead of wrapping + to avoid asChild prop merging that breaks highlighting */} + + + + + + + + +

Desktop

+
+
+
+ + + + + + + + +

Tablet

+
+
+
+ + + + + + + + +

Mobile

+
+
+
+
+
+
+
+
+
+ )} + +
+ setErrorMessage(undefined)} + onAIFix={() => { + if (selectedChatId) { + streamMessage({ + prompt: `Fix error: ${errorMessage?.message}`, + chatId: selectedChatId, + }); + } + }} + /> + + {!appUrl ? ( +
+ +

+ Starting your app server... +

+
+ ) : ( +
+ {annotatorMode && screenshotDataUrl ? ( +
+ +
+ ) : ( + <> +