Lastest change

This commit is contained in:
Kunthawat Greethong
2025-12-19 16:13:39 +07:00
parent 24c79defff
commit 6bb756fdd7
91 changed files with 4341 additions and 422 deletions

View File

@@ -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

4
package-lock.json generated
View File

@@ -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": {

View File

@@ -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",

295
scripts/frontend-debrand.sh Executable file
View File

@@ -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 && <DyadProButton/d' src/app/TitleBar.tsx
# Update logo alt text
sed -i '' 's/Dyad Logo/MoreMinimore Logo/g' src/app/TitleBar.tsx
# Remove DyadProButton component
sed -i '' '/export function DyadProButton/,/^}/d' src/app/TitleBar.tsx
sed -i '' '/export function AICreditStatus/,/^}/d' src/app/TitleBar.tsx
# Remove deep link handling for dyad-pro-return
sed -i '' '/dyad-pro-return/d' src/app/TitleBar.tsx
rm src/app/TitleBar.tsx.bak
print_success "Fixed TitleBar component"
fi
}
# Function to remove DyadProSuccessDialog component
remove_pro_dialog() {
print_status "Removing DyadProSuccessDialog component..."
if [ -f "src/components/DyadProSuccessDialog.tsx" ]; then
mv src/components/DyadProSuccessDialog.tsx src/components/DyadProSuccessDialog.tsx.disabled
print_success "Disabled DyadProSuccessDialog component"
fi
}
# Function to fix ContextFilesPicker
fix_context_files_picker() {
print_status "Fixing ContextFilesPicker component..."
if [ -f "src/components/ContextFilesPicker.tsx" ]; then
# Create a backup
cp src/components/ContextFilesPicker.tsx src/components/ContextFilesPicker.tsx.bak
# Update tooltip text
sed -i '' 's/Codebase Context/Context Settings/g' src/components/ContextFilesPicker.tsx
# Update smart context logic to remove pro restrictions
sed -i '' 's/settings?.enableDyadPro && settings?.enableProSmartFilesContextMode/true/g' src/components/ContextFilesPicker.tsx
# Update text references
sed -i '' 's/Dyad uses/MoreMinimore uses/g' src/components/ContextFilesPicker.tsx
sed -i '' 's/With Smart Context, Dyad uses/With Smart Context, MoreMinimore uses/g' src/components/ContextFilesPicker.tsx
rm src/components/ContextFilesPicker.tsx.bak
print_success "Fixed ContextFilesPicker component"
fi
}
# Function to fix DyadTokenSavings component
fix_token_savings() {
print_status "Fixing DyadTokenSavings component..."
if [ -f "src/components/chat/DyadTokenSavings.tsx" ]; then
# Create a backup
cp src/components/chat/DyadTokenSavings.tsx src/components/chat/DyadTokenSavings.tsx.bak
# Update component name and references
sed -i '' 's/DyadTokenSavings/TokenSavings/g' src/components/chat/DyadTokenSavings.tsx
rm src/components/chat/DyadTokenSavings.tsx.bak
print_success "Fixed DyadTokenSavings component"
fi
}
# Function to fix DyadThink component
fix_dyad_think() {
print_status "Fixing DyadThink component..."
if [ -f "src/components/chat/DyadThink.tsx" ]; then
# Create a backup
cp src/components/chat/DyadThink.tsx src/components/chat/DyadThink.tsx.bak
# Update component name and references
sed -i '' 's/DyadThink/AIThink/g' src/components/chat/DyadThink.tsx
rm src/components/chat/DyadThink.tsx.bak
print_success "Fixed DyadThink component"
fi
}
# Function to update smart context settings
update_smart_context_settings() {
print_status "Updating smart context settings..."
# Update smart context store to remove pro restrictions
if [ -f "src/ipc/utils/smart_context_store.ts" ]; then
cp src/ipc/utils/smart_context_store.ts src/ipc/utils/smart_context_store.ts.bak
sed -i '' 's/settings\.enableDyadPro/true/g' src/ipc/utils/smart_context_store.ts
rm src/ipc/utils/smart_context_store.ts.bak
print_success "Updated smart context store"
fi
# Update chat stream handlers to enable smart context for all users
if [ -f "src/ipc/handlers/chat_stream_handlers.ts" ]; then
cp src/ipc/handlers/chat_stream_handlers.ts src/ipc/handlers/chat_stream_handlers.ts.bak
sed -i '' 's/isDeepContextEnabled/true/g' src/ipc/handlers/chat_stream_handlers.ts
rm src/ipc/handlers/chat_stream_handlers.ts.bak
print_success "Updated chat stream handlers"
fi
}
# Function to fix remaining Dyad references
fix_remaining_references() {
print_status "Fixing remaining Dyad references..."
# Find and replace remaining Dyad references in components
find src/components -name "*.tsx" -type f -exec sed -i '' 's/Dyad/MoreMinimore/g' {} \;
find src/app -name "*.tsx" -type f -exec sed -i '' 's/Dyad/MoreMinimore/g' {} \;
# Fix specific cases where we don't want to replace
find src/components -name "*.tsx" -type f -exec sed -i '' 's/MoreMinimorePro/DyadPro/g' {} \;
find src/app -name "*.tsx" -type f -exec sed -i '' 's/MoreMinimorePro/DyadPro/g' {} \;
print_success "Fixed remaining Dyad references"
}
# Function to add missing user budget handler
add_missing_handlers() {
print_status "Adding missing IPC handlers..."
# Create a simple user budget handler that returns default values
if [ -f "src/ipc/ipc_host.ts" ]; then
cp src/ipc/ipc_host.ts src/ipc/ipc_host.ts.bak
# Add a simple user budget handler
if ! grep -q "get-user-budget" src/ipc/ipc_host.ts; then
sed -i '' '/registerDebugHandlers();/a\
\
// Add missing user budget handler\
ipcMain.handle("get-user-budget", async () => {\
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 "$@"

View File

@@ -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 '/<ProModeSelector \/>/d' src/components/ChatInputControls.tsx
sed -i.bak '/<div className="w-1.5"><\/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 ? (/,/)} : (/,/<AnnotatorOnlyForPro/,/)}\/>/c\
<Annotator\
screenshotUrl={screenshotDataUrl}\
onSubmit={addAttachments}\
handleAnnotatorClick={handleAnnotatorClick}\
/>' 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|{<ProBanner />}|// {<ProBanner />}|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|<OnboardingBanner|{/* <OnboardingBanner|g' src/components/SetupBanner.tsx
sed -i.bak 's|setIsVisible={setIsOnboardingVisible} />|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:"

View File

@@ -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 (
<>
<div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center">
<div className={`${showWindowControls ? "pl-2" : "pl-18"}`}></div>
<img src={logo} alt="Dyad Logo" className="w-6 h-6 mr-0.5" />
<img src={logo} alt="MoreMinimore Logo" className="w-6 h-6 mr-0.5" />
<Button
data-testid="title-bar-app-name-button"
variant="outline"
@@ -93,7 +81,6 @@ export const TitleBar = () => {
>
{displayText}
</Button>
{isDyadPro && <DyadProButton isDyadProEnabled={isDyadProEnabled} />}
{/* Preview Header */}
{location.pathname === "/chat" && (
@@ -104,141 +91,52 @@ export const TitleBar = () => {
{showWindowControls && <WindowsControls />}
</div>
<DyadProSuccessDialog
isOpen={isSuccessDialogOpen}
onClose={() => 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 (
<div className="ml-auto flex no-app-region-drag">
<button
className="w-10 h-10 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
onClick={minimizeWindow}
aria-label="Minimize"
>
<svg
width="12"
height="1"
viewBox="0 0 12 1"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="12"
height="1"
fill={isDarkMode ? "#ffffff" : "#000000"}
/>
</svg>
</button>
<button
className="w-10 h-10 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
onClick={maximizeWindow}
aria-label="Maximize"
>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="0.5"
y="0.5"
width="11"
height="11"
stroke={isDarkMode ? "#ffffff" : "#000000"}
/>
</svg>
</button>
<button
className="w-10 h-10 flex items-center justify-center hover:bg-red-500 transition-colors"
onClick={closeWindow}
aria-label="Close"
>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 1L11 11M1 11L11 1"
stroke={isDarkMode ? "#ffffff" : "#000000"}
strokeWidth="1.5"
/>
</svg>
</button>
</div>
);
}
export function DyadProButton({
isDyadProEnabled,
}: {
isDyadProEnabled: boolean;
}) {
const { navigate } = useRouter();
const { userBudget } = useUserBudgetInfo();
return (
<div className="flex items-center no-app-region-drag">
<Button
data-testid="title-bar-dyad-pro-button"
onClick={() => {
navigate({
to: providerSettingsRoute.id,
params: { provider: "auto" },
});
}}
variant="outline"
className={cn(
"hidden @2xl:block ml-1 no-app-region-drag h-7 bg-indigo-600 text-white dark:bg-indigo-600 dark:text-white text-xs px-2 pt-1 pb-1",
!isDyadProEnabled && "bg-zinc-600 dark:bg-zinc-600",
)}
variant="ghost"
size="sm"
className="h-8 w-8 p-0 hover:bg-zinc-200 dark:hover:bg-zinc-700"
onClick={handleMinimize}
>
{isDyadProEnabled ? "Pro" : "Pro (off)"}
{userBudget && isDyadProEnabled && (
<AICreditStatus userBudget={userBudget} />
)}
<span className="text-xs"></span>
</Button>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 hover:bg-zinc-200 dark:hover:bg-zinc-700"
onClick={handleMaximize}
>
<span className="text-xs"></span>
</Button>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 hover:bg-red-500 hover:text-white"
onClick={handleClose}
>
<span className="text-xs">×</span>
</Button>
);
}
export function AICreditStatus({ userBudget }: { userBudget: UserBudgetInfo }) {
const remaining = Math.round(
userBudget.totalCredits - userBudget.usedCredits,
);
return (
<Tooltip>
<TooltipTrigger>
<div className="text-xs pl-1 mt-0.5">{remaining} credits</div>
</TooltipTrigger>
<TooltipContent>
<div>
<p>Note: there is a slight delay in updating the credit status.</p>
</div>
</TooltipContent>
</Tooltip>
);
}
};

View File

@@ -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
</div>
) : (
<div className="space-y-4">

View File

@@ -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();
},
},
});

View File

@@ -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({
<ChatModeSelector />
{settings?.selectedChatMode === "agent" && (
<>
<div className="w-1.5"></div>
<McpToolsPicker />
</>
)}
<div className="w-1.5"></div>
<ModelPicker />
<div className="w-1.5"></div>
<ProModeSelector />
<div className="w-1"></div>
{showContextFilesPicker && (
<>

View File

@@ -26,7 +26,7 @@ export const CommunityCodeConsentDialog: React.FC<
<AlertDialogTitle>Community Code Notice</AlertDialogTitle>
<AlertDialogDescription className="space-y-3">
<p>
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.
</p>
<p>

View File

@@ -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 (
<Popover open={isOpen} onOpenChange={setIsOpen}>
@@ -130,7 +131,7 @@ export function ContextFilesPicker() {
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Codebase Context</TooltipContent>
<TooltipContent>Context Settings</TooltipContent>
</Tooltip>
<PopoverContent
@@ -139,7 +140,7 @@ export function ContextFilesPicker() {
>
<div className="relative space-y-4">
<div>
<h3 className="font-medium">Codebase Context</h3>
<h3 className="font-medium">Context Settings</h3>
<p className="text-sm text-muted-foreground">
<TooltipProvider>
<Tooltip>
@@ -152,11 +153,11 @@ export function ContextFilesPicker() {
<TooltipContent className="max-w-[300px]">
{isSmartContextEnabled ? (
<p>
With Smart Context, Dyad uses the most relevant files as
With Smart Context, MoreMinimore uses the most relevant files as
context.
</p>
) : (
<p>By default, Dyad uses your whole codebase.</p>
<p>By default, MoreMinimore uses your whole codebase.</p>
)}
</TooltipContent>
</Tooltip>
@@ -164,6 +165,28 @@ export function ContextFilesPicker() {
</p>
</div>
{/* Smart Context Toggle */}
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="smart-context-toggle" className="text-sm font-medium">
Smart Context
</Label>
<p className="text-xs text-muted-foreground">
Automatically select the most relevant files for context
</p>
</div>
<Switch
id="smart-context-toggle"
checked={isSmartContextEnabled}
onCheckedChange={(checked) => {
updateSettings({
enableProSmartFilesContextMode: checked,
proSmartContextOption: checked ? "balanced" : undefined,
});
}}
/>
</div>
<div className="flex w-full max-w-sm items-center space-x-2">
<Input
data-testid="manual-context-files-input"
@@ -226,8 +249,8 @@ export function ContextFilesPicker() {
<div className="rounded-md border border-dashed p-4 text-center">
<p className="text-sm text-muted-foreground">
{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."}
</p>
</div>
)}

View File

@@ -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"}
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 rounded-md flex items-center gap-2">
<LightbulbIcon className="h-4 w-4 text-blue-700 dark:text-blue-400 flex-shrink-0" />
<p className="text-sm text-blue-700 dark:text-blue-400">
<strong>Tip:</strong> Try closing and re-opening Dyad as a temporary
<strong>Tip:</strong> Try closing and re-opening MoreMinimore as a temporary
workaround.
</p>
</div>

View File

@@ -160,7 +160,7 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) {
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Dyad Help Bot</DialogTitle>
<DialogTitle>MoreMinimore Help Bot</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-3 h-[480px]">
{error && (
@@ -183,7 +183,7 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) {
{messages.length === 0 ? (
<div className="space-y-3">
<div className="text-sm text-muted-foreground">
Ask a question about using Dyad.
Ask a question about using MoreMinimore.
</div>
<div className="text-xs text-muted-foreground/70 bg-muted/50 rounded-md p-3">
This conversation may be logged and used to improve the

View File

@@ -98,7 +98,7 @@ Issues that do not meet these requirements will be closed and may need to be res
<!-- Screenshot of the bug -->
## 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"}
<div className="border rounded-md p-3">
<h3 className="font-medium mb-2">System Information</h3>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-32 overflow-y-auto">
<p>Dyad Version: {chatLogsData.debugInfo.dyadVersion}</p>
<p>MoreMinimore Version: {chatLogsData.debugInfo.dyadVersion}</p>
<p>Platform: {chatLogsData.debugInfo.platform}</p>
<p>Architecture: {chatLogsData.debugInfo.architecture}</p>
<p>
@@ -390,7 +390,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Need help with Dyad?</DialogTitle>
<DialogTitle>Need help with MoreMinimore?</DialogTitle>
</DialogHeader>
<DialogDescription className="">
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"
>
<SparklesIcon className="mr-2 h-5 w-5" /> Chat with Dyad help
<SparklesIcon className="mr-2 h-5 w-5" /> Chat with MoreMinimore help
bot (Pro)
</Button>
<p className="text-sm text-muted-foreground px-2">
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.
</p>
</div>

View File

@@ -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) {
</TooltipTrigger>
<TooltipContent>
<p className="text-xs">
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
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<AlertDescription className="text-xs sm:text-sm">
No AI_RULES.md found. Dyad will automatically generate
No AI_RULES.md found. MoreMinimore will automatically generate
one after importing.
</AlertDescription>
</Alert>

View File

@@ -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 (
<DropdownMenuSub key={providerId}>

View File

@@ -56,7 +56,7 @@ export function ManageDyadProButton() {
}}
>
<KeyRound aria-hidden="true" />
Manage Dyad Pro subscription
Manage MoreMinimore Pro subscription
</Button>
);
}
@@ -74,7 +74,7 @@ export function SetupDyadProButton() {
}}
>
<KeyRound aria-hidden="true" />
Already have Dyad Pro? Add your key
Already have MoreMinimore Pro? Add your key
</Button>
);
}
@@ -104,10 +104,10 @@ export function AiAccessBanner() {
</div>
<button
type="button"
aria-label="Subscribe to Dyad Pro"
aria-label="Subscribe to MoreMinimore Pro"
className="inline-flex items-center rounded-md bg-white/90 text-indigo-800 hover:bg-white shadow px-3 py-1.5 text-xs sm:text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-white/50"
>
Get Dyad Pro
Get MoreMinimore Pro
</button>
</div>
@@ -175,10 +175,10 @@ export function SmartContextBanner() {
</div>
<button
type="button"
aria-label="Get Dyad Pro"
aria-label="Get MoreMinimore Pro"
className="inline-flex items-center rounded-md bg-white/90 text-emerald-800 hover:bg-white shadow px-3 py-1.5 text-xs sm:text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-white/50"
>
Get Dyad Pro
Get MoreMinimore Pro
</button>
</div>
</div>
@@ -216,10 +216,10 @@ export function TurboBanner() {
</div>
<button
type="button"
aria-label="Get Dyad Pro"
aria-label="Get MoreMinimore Pro"
className="inline-flex items-center rounded-md bg-white/90 text-rose-800 hover:bg-white shadow px-3 py-1.5 text-xs sm:text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-white/50"
>
Get Dyad Pro
Get MoreMinimore Pro
</button>
</div>
</div>

View File

@@ -75,14 +75,14 @@ export function ProModeSelector() {
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Configure Dyad Pro settings</TooltipContent>
<TooltipContent>Configure MoreMinimore Pro settings</TooltipContent>
</Tooltip>
<PopoverContent className="w-80 border-primary/20">
<div className="space-y-4">
<div className="space-y-1">
<h4 className="font-medium flex items-center gap-1.5">
<Sparkles className="h-4 w-4 text-primary" />
<span className="text-primary font-medium">Dyad Pro</span>
<span className="text-primary font-medium">MoreMinimore Pro</span>
</h4>
<div className="h-px bg-gradient-to-r from-primary/50 via-primary/20 to-transparent" />
</div>
@@ -110,8 +110,8 @@ export function ProModeSelector() {
<div className="flex flex-col gap-5">
<SelectorRow
id="pro-enabled"
label="Enable Dyad Pro"
tooltip="Uses Dyad Pro AI credits for the main AI model and Pro modes."
label="Enable MoreMinimore Pro"
tooltip="Uses MoreMinimore Pro AI credits for the main AI model and Pro modes."
isTogglable={hasProKey}
settingEnabled={Boolean(settings?.enableDyadPro)}
toggle={toggleProEnabled}
@@ -119,7 +119,7 @@ export function ProModeSelector() {
<SelectorRow
id="web-search"
label="Web Access"
tooltip="Allows Dyad to access the web (e.g. search for information)"
tooltip="Allows MoreMinimore to access the web (e.g. search for information)"
isTogglable={proModeTogglable}
settingEnabled={Boolean(settings?.enableProWebSearch)}
toggle={toggleWebSearch}

View File

@@ -34,11 +34,11 @@ export function ReleaseChannelSelector() {
} else {
toast("Using Beta release channel", {
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();
},
},
});

View File

@@ -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 (
<>
<p className="text-xl font-medium text-zinc-700 dark:text-zinc-300 p-4">
Setup Dyad
Setup MoreMinimore
</p>
<OnboardingBanner
{/* <OnboardingBanner
isVisible={isOnboardingVisible}
setIsVisible={setIsOnboardingVisible}
/>
/> */}
<div className={bannerClasses}>
<Accordion
type="multiple"
@@ -313,7 +307,7 @@ export function SetupBanner() {
</AccordionTrigger>
<AccordionContent className="px-4 pt-2 pb-4 bg-white dark:bg-zinc-900 border-t border-inherit">
<p className="text-[15px] mb-3">
Not sure what to do? Watch the Get Started video above
Not sure what to do? Follow the setup steps below to get started.
</p>
<div className="flex gap-2">
<SetupProviderCard
@@ -341,19 +335,6 @@ export function SetupBanner() {
/>
</div>
<SetupProviderCard
className="mt-2"
variant="dyad"
onClick={handleDyadProSetupClick}
tabIndex={isNodeSetupComplete ? 0 : -1}
leadingIcon={
<img src={logo} alt="Dyad Logo" className="w-6 h-6 mr-0.5" />
}
title="Setup Dyad Pro"
subtitle="Access all AI models with one plan"
chip={<>Recommended</>}
/>
<div
className="mt-2 p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 transition-colors"
onClick={handleOtherProvidersClick}
@@ -446,7 +427,7 @@ function NodeInstallButton({
case "finished-checking":
return (
<div className="mt-3 text-sm text-red-600 dark:text-red-400">
Node.js not detected. Closing and re-opening Dyad usually fixes this.
Node.js not detected. Closing and re-opening MoreMinimore usually fixes this.
</div>
);
default:

View File

@@ -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;
}
}

View File

@@ -26,7 +26,7 @@ export function PrivacyBanner() {
Share anonymous data?
</h4>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
Help improve Dyad with anonymous usage data.
Help improve MoreMinimore with anonymous usage data.
<em className="block italic mt-0.5">
Note: this does not log your code or messages.
</em>

View File

@@ -182,7 +182,7 @@ function AppIcons({
return (
// When collapsed: only show the main menu
<SidebarGroup className="pr-0">
{/* <SidebarGroupLabel>Dyad</SidebarGroupLabel> */}
{/* <SidebarGroupLabel>MoreMinimore</SidebarGroupLabel> */}
<SidebarGroupContent>
<SidebarMenu>

View File

@@ -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
</ExternalLink>
</span>{" "}
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
</ExternalLink>
<ExternalLink href="https://dyad.sh/docs/help/ai-rate-limit">
@@ -69,12 +69,12 @@ export function ChatErrorBox({
return (
<ChatInfoContainer onDismiss={onDismiss}>
<span>
Looks like you don't have a valid Dyad Pro key.{" "}
Looks like you don't have a valid MoreMinimore Pro key.{" "}
<ExternalLink
href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=invalid-pro-key-error"
variant="primary"
>
Upgrade to Dyad Pro
Upgrade to MoreMinimore Pro
</ExternalLink>{" "}
today.
</span>
@@ -85,7 +85,7 @@ export function ChatErrorBox({
return (
<ChatInfoContainer onDismiss={onDismiss}>
<span>
You have used all of your Dyad AI credits this month.{" "}
You have used all of your MoreMinimore AI credits this month.{" "}
<ExternalLink
href="https://academy.dyad.sh/subscription?utm_source=dyad-app&utm_medium=app&utm_campaign=exceeded-budget-error"
variant="primary"
@@ -117,7 +117,7 @@ export function ChatErrorBox({
href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=general-error"
variant="primary"
>
Upgrade to Dyad Pro
Upgrade to MoreMinimore Pro
</ExternalLink>
)}
<ExternalLink href="https://www.dyad.sh/docs/faq">

View File

@@ -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}
/>

View File

@@ -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" ? (
<>
<DyadMarkdownParser content={message.content} />
<MoreMinimoreMarkdownParser content={message.content} />
{isLastMessage && isStreaming && (
<div className="mt-4 ml-4 relative w-5 h-5 animate-spin">
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-2 h-2 bg-(--primary) dark:bg-blue-500 rounded-full"></div>

View File

@@ -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<DyadAddDependencyProps> = ({
export const MoreMinimoreAddDependency: React.FC<MoreMinimoreAddDependencyProps> = ({
children,
node,
}) => {

View File

@@ -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<DyadAddIntegrationProps> = ({
export const MoreMinimoreAddIntegration: React.FC<MoreMinimoreAddIntegrationProps> = ({
node,
children,
}) => {

View File

@@ -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<DyadCodeSearchProps> = ({
export const MoreMinimoreCodeSearch: React.FC<MoreMinimoreCodeSearchProps> = ({
children,
node: _node,
query: queryProp,

View File

@@ -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<DyadCodeSearchResultProps> = ({
export const MoreMinimoreCodeSearchResult: React.FC<MoreMinimoreCodeSearchResultProps> = ({
children,
}) => {
const [isExpanded, setIsExpanded] = useState(false);

View File

@@ -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<DyadCodebaseContextProps> = ({
export const MoreMinimoreCodebaseContext: React.FC<MoreMinimoreCodebaseContextProps> = ({
node,
}) => {
const state = node?.properties?.state as CustomTagState;

View File

@@ -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<DyadDeleteProps> = ({
export const MoreMinimoreDelete: React.FC<MoreMinimoreDeleteProps> = ({
children,
node,
path: pathProp,

View File

@@ -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<DyadEditProps> = ({
export const MoreMinimoreEdit: React.FC<MoreMinimoreEditProps> = ({
children,
node,
path: pathProp,

View File

@@ -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<DyadExecuteSqlProps> = ({
export const MoreMinimoreExecuteSql: React.FC<MoreMinimoreExecuteSqlProps> = ({
children,
node,
description,

View File

@@ -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<DyadMarkdownParserProps> = ({
export const MoreMinimoreMarkdownParser: React.FC<MoreMinimoreMarkdownParserProps> = ({
content,
}) => {
const chatId = useAtomValue(selectedChatIdAtom);
@@ -346,7 +346,7 @@ function renderCustomTag(
switch (tag) {
case "dyad-read":
return (
<DyadRead
<MoreMinimoreRead
node={{
properties: {
path: attributes.path || "",
@@ -354,51 +354,51 @@ function renderCustomTag(
}}
>
{content}
</DyadRead>
</MoreMinimoreRead>
);
case "dyad-web-search":
return (
<DyadWebSearch
<MoreMinimoreWebSearch
node={{
properties: {},
}}
>
{content}
</DyadWebSearch>
</MoreMinimoreWebSearch>
);
case "dyad-web-crawl":
return (
<DyadWebCrawl
<MoreMinimoreWebCrawl
node={{
properties: {},
}}
>
{content}
</DyadWebCrawl>
</MoreMinimoreWebCrawl>
);
case "dyad-code-search":
return (
<DyadCodeSearch
<MoreMinimoreCodeSearch
node={{
properties: {},
}}
>
{content}
</DyadCodeSearch>
</MoreMinimoreCodeSearch>
);
case "dyad-code-search-result":
return (
<DyadCodeSearchResult
<MoreMinimoreCodeSearchResult
node={{
properties: {},
}}
>
{content}
</DyadCodeSearchResult>
</MoreMinimoreCodeSearchResult>
);
case "dyad-web-search-result":
return (
<DyadWebSearchResult
<MoreMinimoreWebSearchResult
node={{
properties: {
state: getState({ isStreaming, inProgress }),
@@ -406,11 +406,11 @@ function renderCustomTag(
}}
>
{content}
</DyadWebSearchResult>
</MoreMinimoreWebSearchResult>
);
case "think":
return (
<DyadThink
<MoreMinimoreThink
node={{
properties: {
state: getState({ isStreaming, inProgress }),
@@ -418,11 +418,11 @@ function renderCustomTag(
}}
>
{content}
</DyadThink>
</MoreMinimoreThink>
);
case "dyad-write":
return (
<DyadWrite
<MoreMinimoreWrite
node={{
properties: {
path: attributes.path || "",
@@ -432,12 +432,12 @@ function renderCustomTag(
}}
>
{content}
</DyadWrite>
</MoreMinimoreWrite>
);
case "dyad-rename":
return (
<DyadRename
<MoreMinimoreRename
node={{
properties: {
from: attributes.from || "",
@@ -446,12 +446,12 @@ function renderCustomTag(
}}
>
{content}
</DyadRename>
</MoreMinimoreRename>
);
case "dyad-delete":
return (
<DyadDelete
<MoreMinimoreDelete
node={{
properties: {
path: attributes.path || "",
@@ -459,12 +459,12 @@ function renderCustomTag(
}}
>
{content}
</DyadDelete>
</MoreMinimoreDelete>
);
case "dyad-add-dependency":
return (
<DyadAddDependency
<MoreMinimoreAddDependency
node={{
properties: {
packages: attributes.packages || "",
@@ -472,12 +472,12 @@ function renderCustomTag(
}}
>
{content}
</DyadAddDependency>
</MoreMinimoreAddDependency>
);
case "dyad-execute-sql":
return (
<DyadExecuteSql
<MoreMinimoreExecuteSql
node={{
properties: {
state: getState({ isStreaming, inProgress }),
@@ -486,12 +486,12 @@ function renderCustomTag(
}}
>
{content}
</DyadExecuteSql>
</MoreMinimoreExecuteSql>
);
case "dyad-add-integration":
return (
<DyadAddIntegration
<MoreMinimoreAddIntegration
node={{
properties: {
provider: attributes.provider || "",
@@ -499,12 +499,12 @@ function renderCustomTag(
}}
>
{content}
</DyadAddIntegration>
</MoreMinimoreAddIntegration>
);
case "dyad-edit":
return (
<DyadEdit
<MoreMinimoreEdit
node={{
properties: {
path: attributes.path || "",
@@ -514,12 +514,12 @@ function renderCustomTag(
}}
>
{content}
</DyadEdit>
</MoreMinimoreEdit>
);
case "dyad-search-replace":
return (
<DyadSearchReplace
<MoreMinimoreSearchReplace
node={{
properties: {
path: attributes.path || "",
@@ -529,12 +529,12 @@ function renderCustomTag(
}}
>
{content}
</DyadSearchReplace>
</MoreMinimoreSearchReplace>
);
case "dyad-codebase-context":
return (
<DyadCodebaseContext
<MoreMinimoreCodebaseContext
node={{
properties: {
files: attributes.files || "",
@@ -543,12 +543,12 @@ function renderCustomTag(
}}
>
{content}
</DyadCodebaseContext>
</MoreMinimoreCodebaseContext>
);
case "dyad-mcp-tool-call":
return (
<DyadMcpToolCall
<MoreMinimoreMcpToolCall
node={{
properties: {
serverName: attributes.server || "",
@@ -557,12 +557,12 @@ function renderCustomTag(
}}
>
{content}
</DyadMcpToolCall>
</MoreMinimoreMcpToolCall>
);
case "dyad-mcp-tool-result":
return (
<DyadMcpToolResult
<MoreMinimoreMcpToolResult
node={{
properties: {
serverName: attributes.server || "",
@@ -571,17 +571,17 @@ function renderCustomTag(
}}
>
{content}
</DyadMcpToolResult>
</MoreMinimoreMcpToolResult>
);
case "dyad-output":
return (
<DyadOutput
<MoreMinimoreOutput
type={attributes.type as "warning" | "error"}
message={attributes.message}
>
{content}
</DyadOutput>
</MoreMinimoreOutput>
);
case "dyad-problem-report":

View File

@@ -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<DyadMcpToolCallProps> = ({
export const MoreMinimoreMcpToolCall: React.FC<MoreMinimoreMcpToolCallProps> = ({
node,
children,
}) => {

View File

@@ -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<DyadMcpToolResultProps> = ({
export const MoreMinimoreMcpToolResult: React.FC<MoreMinimoreMcpToolResultProps> = ({
node,
children,
}) => {

View File

@@ -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<DyadOutputProps> = ({
export const MoreMinimoreOutput: React.FC<MoreMinimoreOutputProps> = ({
type,
message,
children,

View File

@@ -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<DyadReadProps> = ({
export const MoreMinimoreRead: React.FC<MoreMinimoreReadProps> = ({
children,
node,
path: pathProp,

View File

@@ -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<DyadRenameProps> = ({
export const MoreMinimoreRename: React.FC<MoreMinimoreRenameProps> = ({
children,
node,
from: fromProp,

View File

@@ -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<DyadSearchReplaceProps> = ({
export const MoreMinimoreSearchReplace: React.FC<MoreMinimoreSearchReplaceProps> = ({
children,
node,
path: pathProp,

View File

@@ -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<DyadThinkProps> = ({ children, node }) => {
export const AIThink: React.FC<AIThinkProps> = ({ 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<DyadThinkProps> = ({ 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 (
<DyadTokenSavings
<MoreMinimoreTokenSavings
originalTokens={originalTokens}
smartContextTokens={smartContextTokens}
/>

View File

@@ -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<DyadTokenSavingsProps> = ({
export const TokenSavings: React.FC<TokenSavingsProps> = ({
originalTokens,
smartContextTokens,
}) => {

View File

@@ -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<DyadWebCrawlProps> = ({
export const MoreMinimoreWebCrawl: React.FC<MoreMinimoreWebCrawlProps> = ({
children,
node: _node,
}) => {

View File

@@ -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<DyadWebSearchProps> = ({
export const MoreMinimoreWebSearch: React.FC<MoreMinimoreWebSearchProps> = ({
children,
node: _node,
query: queryProp,

View File

@@ -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<DyadWebSearchResultProps> = ({
export const MoreMinimoreWebSearchResult: React.FC<MoreMinimoreWebSearchResultProps> = ({
children,
node,
}) => {

View File

@@ -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<DyadWriteProps> = ({
export const MoreMinimoreWrite: React.FC<MoreMinimoreWriteProps> = ({
children,
node,
path: pathProp,

View File

@@ -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 {

View File

@@ -250,7 +250,7 @@ export function LexicalChatInput({
onSubmit,
onPaste,
excludeCurrentApp,
placeholder = "Ask Dyad to build...",
placeholder = "Ask MoreMinimore to build...",
disabled = false,
disableSendButton,
}: LexicalChatInputProps) {

View File

@@ -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<MoreMinimoreAddDependencyProps> = ({
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 (
<div
className={`bg-(--background-lightest) dark:bg-gray-900 hover:bg-(--background-lighter) rounded-lg px-4 py-3 border my-2 border-border ${
hasChildren ? "cursor-pointer" : ""
}`}
onClick={
hasChildren ? () => setIsContentVisible(!isContentVisible) : undefined
}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Package size={18} className="text-gray-600 dark:text-gray-400" />
{packages.length > 0 && (
<div className="text-gray-800 dark:text-gray-200 font-semibold text-base">
<div className="font-normal">
Do you want to install these packages?
</div>{" "}
<div className="flex flex-wrap gap-2 mt-2">
{packages.map((p: string) => (
<span
className="cursor-pointer text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
key={p}
onClick={() => {
IpcClient.getInstance().openExternalUrl(
`https://www.npmjs.com/package/${p}`,
);
}}
>
{p}
</span>
))}
</div>
</div>
)}
</div>
{hasChildren && (
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
)}
</div>
{packages.length > 0 && (
<div className="text-sm text-gray-600 dark:text-gray-400 mb-1">
Make sure these packages are what you want.{" "}
</div>
)}
{/* Show content if it's visible and has children */}
{isContentVisible && hasChildren && (
<div className="mt-2">
<div className="text-xs">
<CodeHighlight className="language-shell">{children}</CodeHighlight>
</div>
</div>
)}
</div>
);
};

View File

@@ -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<MoreMinimoreAddIntegrationProps> = ({
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 (
<div className="flex flex-col my-2 p-3 border border-green-300 rounded-lg bg-green-50 shadow-sm">
<div className="flex items-center space-x-2">
<svg
className="w-5 h-5 text-green-600"
fill="none"
stroke="currentColor"
strokeWidth={2}
viewBox="0 0 24 24"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="2"
fill="#bbf7d0"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12l2 2 4-4"
/>
</svg>
<span className="font-semibold text-green-800">
Supabase integration complete
</span>
</div>
<div className="text-sm text-green-900">
<p>
This app is connected to Supabase project:{" "}
<span className="font-mono font-medium bg-green-100 px-1 py-0.5 rounded">
{app.supabaseProjectName}
</span>
</p>
<p>Click the chat suggestion "Keep going" to continue.</p>
</div>
</div>
);
}
return (
<div className="flex flex-col gap-2 my-2 p-3 border rounded-md bg-secondary/10">
<div className="text-sm">
<div className="font-medium">Integrate with {provider}?</div>
<div className="text-muted-foreground text-xs">{children}</div>
</div>
<Button onClick={handleSetupClick} className="self-start w-full">
Set up {provider}
</Button>
</div>
);
};

View File

@@ -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<MoreMinimoreCodeSearchProps> = ({
children,
node: _node,
query: queryProp,
}) => {
const query = queryProp || (typeof children === "string" ? children : "");
return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border my-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileCode size={16} className="text-purple-600" />
<div className="text-xs text-purple-600 font-medium">Code Search</div>
</div>
</div>
<div className="text-sm italic text-gray-600 dark:text-gray-300 mt-2">
{query || children}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreCodeSearchResultProps> = ({
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 (
<div
className="relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border border-border my-2 cursor-pointer"
onClick={() => 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 */}
<div
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-purple-600 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<FileCode size={16} className="text-purple-600" />
<span>Code Search Result</span>
</div>
{/* File count when collapsed */}
{files.length > 0 && (
<div className="absolute top-2 left-44 flex items-center">
<span className="px-1.5 py-0.5 bg-gray-100 dark:bg-zinc-800 text-xs rounded text-gray-600 dark:text-gray-300">
Found {files.length} file{files.length !== 1 ? "s" : ""}
</span>
</div>
)}
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "1000px" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px",
}}
>
{/* File list when expanded */}
{files.length > 0 && (
<div className="mb-3">
<div className="flex flex-wrap gap-2 mt-2">
{files.map((file, index) => {
const filePath = file.trim();
const fileName = filePath.split("/").pop() || filePath;
const pathPart =
filePath.substring(0, filePath.length - fileName.length) ||
"";
return (
<div
key={index}
className="px-2 py-1 bg-gray-100 dark:bg-zinc-800 rounded-lg"
>
<div className="flex items-center gap-1.5">
<FileText
size={14}
className="text-gray-500 dark:text-gray-400 flex-shrink-0"
/>
<div className="text-sm font-medium text-gray-700 dark:text-gray-300">
{fileName}
</div>
</div>
{pathPart && (
<div className="text-xs text-gray-500 dark:text-gray-400 ml-5">
{pathPart}
</div>
)}
</div>
);
})}
</div>
</div>
)}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreCodebaseContextProps> = ({
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 (
<div
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress ? "border-blue-500" : "border-border"
}`}
onClick={() => 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 */}
<div
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-blue-500 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<Code2 size={16} className="text-blue-500" />
<span>Codebase Context</span>
</div>
{/* File count when collapsed */}
{files.length > 0 && (
<div className="absolute top-2 left-40 flex items-center">
<span className="px-1.5 py-0.5 bg-gray-100 dark:bg-zinc-800 text-xs rounded text-gray-600 dark:text-gray-300">
Using {files.length} file{files.length !== 1 ? "s" : ""}
</span>
</div>
)}
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "1000px" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px", // Compensate for padding
}}
>
{/* File list when expanded */}
{files.length > 0 && (
<div className="mb-3">
<div className="flex flex-wrap gap-2 mt-2">
{files.map((file, index) => {
const filePath = file.trim();
const fileName = filePath.split("/").pop() || filePath;
const pathPart =
filePath.substring(0, filePath.length - fileName.length) ||
"";
return (
<div
key={index}
className="px-2 py-1 bg-gray-100 dark:bg-zinc-800 rounded-lg"
>
<div className="flex items-center gap-1.5">
<FileText
size={14}
className="text-gray-500 dark:text-gray-400 flex-shrink-0"
/>
<div className="text-sm font-medium text-gray-700 dark:text-gray-300">
{fileName}
</div>
</div>
{pathPart && (
<div className="text-xs text-gray-500 dark:text-gray-400 ml-5">
{pathPart}
</div>
)}
</div>
);
})}
</div>
</div>
)}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreDeleteProps> = ({
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 (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border border-red-500 my-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Trash2 size={16} className="text-red-500" />
{fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fileName}
</span>
)}
<div className="text-xs text-red-500 font-medium">Delete</div>
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
<div className="text-sm text-gray-600 dark:text-gray-300 mt-2">
{children}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreEditProps> = ({
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 (
<div
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center">
<Rabbit size={16} />
<span className="bg-blue-500 text-white text-xs px-1.5 py-0.5 rounded ml-1 font-medium">
Turbo Edit
</span>
</div>
{fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fileName}
</span>
)}
{inProgress && (
<div className="flex items-center text-amber-600 text-xs">
<Loader size={14} className="mr-1 animate-spin" />
<span>Editing...</span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{description && (
<div className="text-sm text-gray-600 dark:text-gray-300">
<span className="font-medium">Summary: </span>
{description}
</div>
)}
{isContentVisible && (
<div
className="text-xs cursor-text"
onClick={(e) => e.stopPropagation()}
>
<CodeHighlight className="language-typescript">
{children}
</CodeHighlight>
</div>
)}
</div>
);
};

View File

@@ -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<MoreMinimoreExecuteSqlProps> = ({
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 (
<div
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Database size={16} />
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
<span className="font-bold mr-2 outline-2 outline-gray-200 dark:outline-gray-700 bg-gray-100 dark:bg-gray-800 rounded-md px-1">
SQL
</span>
{queryDescription}
</span>
{inProgress && (
<div className="flex items-center text-amber-600 text-xs">
<Loader size={14} className="mr-1 animate-spin" />
<span>Executing...</span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{isContentVisible && (
<div className="text-xs">
<CodeHighlight className="language-sql">{children}</CodeHighlight>
</div>
)}
</div>
);
};

View File

@@ -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<string, string>;
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;
}) => (
<a
{...props}
onClick={(e) => {
const url = props.href;
if (url) {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(url);
}
}}
/>
);
export const VanillaMarkdownParser = ({ content }: { content: string }) => {
return (
<ReactMarkdown
components={{
code: CodeHighlight,
a: customLink,
}}
>
{content}
</ReactMarkdown>
);
};
/**
* Custom component to parse markdown content with MoreMinimore-specific tags
*/
export const MoreMinimoreMarkdownParser: React.FC<MoreMinimoreMarkdownParserProps> = ({
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) => (
<React.Fragment key={index}>
{piece.type === "markdown"
? piece.content && (
<ReactMarkdown
components={{
code: CodeHighlight,
a: customLink,
}}
>
{piece.content}
</ReactMarkdown>
)
: renderCustomTag(piece.tagInfo, { isStreaming })}
{index === lastErrorIndex &&
errorCount > 1 &&
!isStreaming &&
chatId && (
<div className="mt-3 w-full flex">
<FixAllErrorsButton
errorMessages={errorMessages}
chatId={chatId}
/>
</div>
)}
</React.Fragment>
))}
</>
);
};
/**
* 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<string, Set<number>>;
} {
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<string, Set<number>>();
// 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(`</${tagName}>`, "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(`</${tagName}>`)
.join("");
// Mark the last N tags as in progress where N is the number of missing closing tags
const inProgressIndexes = new Set<number>();
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<string, string> = {};
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 (
<MoreMinimoreRead
node={{
properties: {
path: attributes.path || "",
},
}}
>
{content}
</MoreMinimoreRead>
);
case "dyad-web-search":
return (
<MoreMinimoreWebSearch
node={{
properties: {},
}}
>
{content}
</MoreMinimoreWebSearch>
);
case "dyad-web-crawl":
return (
<MoreMinimoreWebCrawl
node={{
properties: {},
}}
>
{content}
</MoreMinimoreWebCrawl>
);
case "dyad-code-search":
return (
<MoreMinimoreCodeSearch
node={{
properties: {},
}}
>
{content}
</MoreMinimoreCodeSearch>
);
case "dyad-code-search-result":
return (
<MoreMinimoreCodeSearchResult
node={{
properties: {},
}}
>
{content}
</MoreMinimoreCodeSearchResult>
);
case "dyad-web-search-result":
return (
<MoreMinimoreWebSearchResult
node={{
properties: {
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</MoreMinimoreWebSearchResult>
);
case "think":
return (
<MoreMinimoreThink
node={{
properties: {
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</MoreMinimoreThink>
);
case "dyad-write":
return (
<MoreMinimoreWrite
node={{
properties: {
path: attributes.path || "",
description: attributes.description || "",
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</MoreMinimoreWrite>
);
case "dyad-rename":
return (
<MoreMinimoreRename
node={{
properties: {
from: attributes.from || "",
to: attributes.to || "",
},
}}
>
{content}
</MoreMinimoreRename>
);
case "dyad-delete":
return (
<MoreMinimoreDelete
node={{
properties: {
path: attributes.path || "",
},
}}
>
{content}
</MoreMinimoreDelete>
);
case "dyad-add-dependency":
return (
<MoreMinimoreAddDependency
node={{
properties: {
packages: attributes.packages || "",
},
}}
>
{content}
</MoreMinimoreAddDependency>
);
case "dyad-execute-sql":
return (
<MoreMinimoreExecuteSql
node={{
properties: {
state: getState({ isStreaming, inProgress }),
description: attributes.description || "",
},
}}
>
{content}
</MoreMinimoreExecuteSql>
);
case "dyad-add-integration":
return (
<MoreMinimoreAddIntegration
node={{
properties: {
provider: attributes.provider || "",
},
}}
>
{content}
</MoreMinimoreAddIntegration>
);
case "dyad-edit":
return (
<MoreMinimoreEdit
node={{
properties: {
path: attributes.path || "",
description: attributes.description || "",
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</MoreMinimoreEdit>
);
case "dyad-search-replace":
return (
<MoreMinimoreSearchReplace
node={{
properties: {
path: attributes.path || "",
description: attributes.description || "",
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</MoreMinimoreSearchReplace>
);
case "dyad-codebase-context":
return (
<MoreMinimoreCodebaseContext
node={{
properties: {
files: attributes.files || "",
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</MoreMinimoreCodebaseContext>
);
case "dyad-mcp-tool-call":
return (
<MoreMinimoreMcpToolCall
node={{
properties: {
serverName: attributes.server || "",
toolName: attributes.tool || "",
},
}}
>
{content}
</MoreMinimoreMcpToolCall>
);
case "dyad-mcp-tool-result":
return (
<MoreMinimoreMcpToolResult
node={{
properties: {
serverName: attributes.server || "",
toolName: attributes.tool || "",
},
}}
>
{content}
</MoreMinimoreMcpToolResult>
);
case "dyad-output":
return (
<MoreMinimoreOutput
type={attributes.type as "warning" | "error"}
message={attributes.message}
>
{content}
</MoreMinimoreOutput>
);
case "dyad-problem-report":
return (
<MoreMinimoreProblemSummary summary={attributes.summary}>
{content}
</MoreMinimoreProblemSummary>
);
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;
}
}

View File

@@ -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<MoreMinimoreMcpToolCallProps> = ({
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 (
<div
className="relative bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer"
onClick={() => setExpanded((v) => !v)}
>
{/* Top-left label badge */}
<div
className="absolute top-3 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-blue-600 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<Wrench size={16} className="text-blue-600" />
<span>Tool Call</span>
</div>
{/* Right chevron */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{expanded ? <ChevronsDownUp size={18} /> : <ChevronsUpDown size={18} />}
</div>
{/* Header content */}
<div className="flex items-start gap-2 pl-24 pr-8 py-1">
{serverName ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-blue-50 dark:bg-zinc-800 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-zinc-700">
{serverName}
</span>
) : null}
{toolName ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-gray-200 border border-border">
{toolName}
</span>
) : null}
{/* Intentionally no preview or content when collapsed */}
</div>
{/* JSON content */}
{expanded ? (
<div className="mt-2 pr-4 pb-2">
<CodeHighlight className="language-json">{prettyJson}</CodeHighlight>
</div>
) : null}
</div>
);
};

View File

@@ -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<MoreMinimoreMcpToolResultProps> = ({
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 (
<div
className="relative bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer"
onClick={() => setExpanded((v) => !v)}
>
{/* Top-left label badge */}
<div
className="absolute top-3 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-emerald-600 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<CheckCircle size={16} className="text-emerald-600" />
<span>Tool Result</span>
</div>
{/* Right chevron */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{expanded ? <ChevronsDownUp size={18} /> : <ChevronsUpDown size={18} />}
</div>
{/* Header content */}
<div className="flex items-start gap-2 pl-24 pr-8 py-1">
{serverName ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-emerald-50 dark:bg-zinc-800 text-emerald-700 dark:text-emerald-300 border border-emerald-200 dark:border-zinc-700">
{serverName}
</span>
) : null}
{toolName ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-gray-200 border border-border">
{toolName}
</span>
) : null}
{/* Intentionally no preview or content when collapsed */}
</div>
{/* JSON content */}
{expanded ? (
<div className="mt-2 pr-4 pb-2">
<CodeHighlight className="language-json">{prettyJson}</CodeHighlight>
</div>
) : null}
</div>
);
};

View File

@@ -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<MoreMinimoreOutputProps> = ({
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 ? (
<XCircle size={16} className={iconColor} />
) : (
<AlertTriangle size={16} className={iconColor} />
);
const label = isError ? "Error" : "Warning";
const handleAIFix = (e: React.MouseEvent) => {
e.stopPropagation();
if (message && selectedChatId) {
streamMessage({
prompt: `Fix the error: ${message}`,
chatId: selectedChatId,
});
}
};
return (
<div
className={`relative bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer min-h-18 ${borderColor}`}
onClick={() => setIsContentVisible(!isContentVisible)}
>
{/* Top-left label badge */}
<div
className={`absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold ${iconColor} bg-white dark:bg-gray-900`}
style={{ zIndex: 1 }}
>
{icon}
<span>{label}</span>
</div>
{/* Main content, padded to avoid label */}
<div className="flex items-center justify-between pl-24 pr-6">
<div className="flex items-center gap-2">
{message && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{message.slice(0, isContentVisible ? undefined : 100) +
(!isContentVisible ? "..." : "")}
</span>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{/* Content area */}
{isContentVisible && children && (
<div className="mt-4 pl-20 text-sm text-gray-800 dark:text-gray-200">
{children}
</div>
)}
{/* Action buttons at the bottom - always visible for errors */}
{isError && message && (
<div className="mt-3 px-6 flex justify-end gap-2">
<CopyErrorMessage
errorMessage={children ? `${message}\n${children}` : message}
/>
<button
onClick={handleAIFix}
className="cursor-pointer flex items-center justify-center bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white rounded text-xs px-2 py-1 h-6"
>
<Sparkles size={14} className="mr-1" />
<span>Fix with AI</span>
</button>
</div>
)}
</div>
);
};

View File

@@ -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<Problem, "snippet">;
interface MoreMinimoreProblemSummaryProps {
summary?: string;
children?: React.ReactNode;
}
interface ProblemItemProps {
problem: ProblemWithoutSnippet;
index: number;
}
const ProblemItem: React.FC<ProblemItemProps> = ({ problem, index }) => {
return (
<div className="flex items-start gap-3 py-2 px-3 border-b border-gray-200 dark:border-gray-700 last:border-b-0">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mt-0.5">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
{index + 1}
</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<FileText size={14} className="text-gray-500 flex-shrink-0" />
<span className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
{problem.file}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
{problem.line}:{problem.column}
</span>
<span className="text-xs bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-600 dark:text-gray-300">
TS{problem.code}
</span>
</div>
<p className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">
{problem.message}
</p>
</div>
</div>
);
};
export const MoreMinimoreProblemSummary: React.FC<MoreMinimoreProblemSummaryProps> = ({
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 <problem> tags
const problemTagRegex =
/<problem\s+file="([^"]+)"\s+line="(\d+)"\s+column="(\d+)"\s+code="(\d+)">([^<]+)<\/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 (
<div
className="bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border border-border my-2 cursor-pointer"
onClick={() => setIsContentVisible(!isContentVisible)}
data-testid="problem-summary"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<AlertTriangle
size={16}
className="text-amber-600 dark:text-amber-500"
/>
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
<span className="font-bold mr-2 outline-2 outline-amber-200 dark:outline-amber-700 bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 rounded-md px-1">
Auto-fix
</span>
{displaySummary}
</span>
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{/* Content area - show individual problems */}
{isContentVisible && totalProblems > 0 && (
<div className="mt-4">
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
{problems.map((problem, index) => (
<ProblemItem
key={`${problem.file}-${problem.line}-${problem.column}-${index}`}
problem={problem}
index={index}
/>
))}
</div>
</div>
)}
{/* Fallback content area for raw children */}
{isContentVisible && totalProblems === 0 && children && (
<div className="mt-4 text-sm text-gray-800 dark:text-gray-200">
<pre className="whitespace-pre-wrap font-mono text-xs bg-gray-100 dark:bg-gray-800 p-3 rounded">
{children}
</pre>
</div>
)}
</div>
);
};

View File

@@ -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<MoreMinimoreReadProps> = ({
children,
node,
path: pathProp,
}) => {
const path = pathProp || node?.properties?.path || "";
const fileName = path ? path.split("/").pop() : "";
return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border border-border my-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileText size={16} className="text-gray-600" />
{fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fileName}
</span>
)}
<div className="text-xs text-gray-600 font-medium">Read</div>
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{children && (
<div className="text-sm text-gray-600 dark:text-gray-300 mt-2">
{children}
</div>
)}
</div>
);
};

View File

@@ -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<MoreMinimoreRenameProps> = ({
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 (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border border-amber-500 my-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileEdit size={16} className="text-amber-500" />
{(fromFileName || toFileName) && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fromFileName && toFileName
? `${fromFileName}${toFileName}`
: fromFileName || toFileName}
</span>
)}
<div className="text-xs text-amber-500 font-medium">Rename</div>
</div>
</div>
{(from || to) && (
<div className="flex flex-col text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{from && (
<div>
<span className="text-gray-500 dark:text-gray-400">From:</span>{" "}
{from}
</div>
)}
{to && (
<div>
<span className="text-gray-500 dark:text-gray-400">To:</span> {to}
</div>
)}
</div>
)}
<div className="text-sm text-gray-600 dark:text-gray-300 mt-2">
{children}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreSearchReplaceProps> = ({
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 (
<div
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center">
<Search size={16} />
<span className="bg-purple-600 text-white text-xs px-1.5 py-0.5 rounded ml-1 font-medium">
Search & Replace
</span>
</div>
{fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fileName}
</span>
)}
{inProgress && (
<div className="flex items-center text-amber-600 text-xs">
<Loader size={14} className="mr-1 animate-spin" />
<span>Applying changes...</span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{description && (
<div className="text-sm text-gray-600 dark:text-gray-300">
<span className="font-medium">Summary: </span>
{description}
</div>
)}
{isContentVisible && (
<div
className="text-xs cursor-text"
onClick={(e) => e.stopPropagation()}
>
{blocks.length === 0 ? (
<CodeHighlight className="language-typescript">
{children}
</CodeHighlight>
) : (
<div className="space-y-3">
{blocks.map((b, i) => (
<div key={i} className="border rounded-lg">
<div className="flex items-center justify-between px-3 py-2 bg-(--background-lighter) rounded-t-lg text-[11px]">
<div className="flex items-center gap-2">
<ArrowLeftRight size={14} />
<span className="font-medium">Change {i + 1}</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-0">
<div className="p-3 border-t md:border-r">
<div className="text-[11px] mb-1 text-muted-foreground font-medium">
Search
</div>
<CodeHighlight className="language-typescript">
{b.searchContent}
</CodeHighlight>
</div>
<div className="p-3 border-t">
<div className="text-[11px] mb-1 text-muted-foreground font-medium">
Replace
</div>
<CodeHighlight className="language-typescript">
{b.replaceContent}
</CodeHighlight>
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
);
};

View File

@@ -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<AIThinkProps> = ({ 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 (
<MoreMinimoreTokenSavings
originalTokens={originalTokens}
smartContextTokens={smartContextTokens}
/>
);
}
return (
<div
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress ? "border-purple-500" : "border-border"
}`}
onClick={() => 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 */}
<div
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-purple-500 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<Brain size={16} className="text-purple-500" />
<span>Thinking</span>
{inProgress && (
<Loader size={14} className="ml-1 text-purple-500 animate-spin" />
)}
</div>
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "none" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px", // Compensate for padding
}}
>
<div className="px-0 text-sm text-gray-600 dark:text-gray-300">
{typeof children === "string" ? (
<VanillaMarkdownParser content={children} />
) : (
children
)}
</div>
</div>
</div>
);
};

View File

@@ -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<TokenSavingsProps> = ({
originalTokens,
smartContextTokens,
}) => {
const tokensSaved = originalTokens - smartContextTokens;
const percentageSaved = Math.round((tokensSaved / originalTokens) * 100);
return (
<Tooltip>
<TooltipTrigger asChild>
<div className="bg-green-50 dark:bg-green-950 hover:bg-green-100 dark:hover:bg-green-900 rounded-lg px-4 py-2 border border-green-200 dark:border-green-800 my-2 cursor-pointer">
<div className="flex items-center gap-2 text-green-700 dark:text-green-300">
<Zap size={16} className="text-green-600 dark:text-green-400" />
<span className="text-xs font-medium">
Saved {percentageSaved}% of codebase tokens with Smart Context
</span>
</div>
</div>
</TooltipTrigger>
<TooltipContent side="top" align="center">
<div className="text-left">
Saved {Math.round(tokensSaved).toLocaleString()} tokens
</div>
</TooltipContent>
</Tooltip>
);
};

View File

@@ -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<MoreMinimoreWebCrawlProps> = ({
children,
node: _node,
}) => {
return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border my-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<ScanQrCode size={16} className="text-blue-600" />
<div className="text-xs text-blue-600 font-medium">Web Crawl</div>
</div>
</div>
<div className="text-sm italic text-gray-600 dark:text-gray-300 mt-2">
{children}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreWebSearchProps> = ({
children,
node: _node,
query: queryProp,
}) => {
const query = queryProp || (typeof children === "string" ? children : "");
return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border my-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Globe size={16} className="text-blue-600" />
<div className="text-xs text-blue-600 font-medium">Web Search</div>
</div>
</div>
<div className="text-sm italic text-gray-600 dark:text-gray-300 mt-2">
{query || children}
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreWebSearchResultProps> = ({
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 (
<div
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress ? "border-blue-500" : "border-border"
}`}
onClick={() => 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 */}
<div
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-blue-600 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<Globe size={16} className="text-blue-600" />
<span>Web Search Result</span>
{inProgress && (
<Loader size={14} className="ml-1 text-blue-600 animate-spin" />
)}
</div>
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "none" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px",
}}
>
<div className="px-0 text-sm text-gray-600 dark:text-gray-300">
{typeof children === "string" ? (
<VanillaMarkdownParser content={children} />
) : (
children
)}
</div>
</div>
</div>
);
};

View File

@@ -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<MoreMinimoreWriteProps> = ({
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 (
<div
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Pencil size={16} />
{fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fileName}
</span>
)}
{inProgress && (
<div className="flex items-center text-amber-600 text-xs">
<Loader size={14} className="mr-1 animate-spin" />
<span>Writing...</span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{!inProgress && (
<>
{isEditing ? (
<>
<button
onClick={(e) => {
e.stopPropagation();
handleCancel();
}}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 px-2 py-1 rounded cursor-pointer"
>
<X size={14} />
Cancel
</button>
</>
) : (
<button
onClick={(e) => {
e.stopPropagation();
handleEdit();
}}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 px-2 py-1 rounded cursor-pointer"
>
<Edit size={14} />
Edit
</button>
)}
</>
)}
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{description && (
<div className="text-sm text-gray-600 dark:text-gray-300">
<span className="font-medium">Summary: </span>
{description}
</div>
)}
{isContentVisible && (
<div
className="text-xs cursor-text"
onClick={(e) => e.stopPropagation()}
>
{isEditing ? (
<div className="h-96 min-h-96 border border-gray-200 dark:border-gray-700 rounded overflow-hidden">
<FileEditor appId={appId ?? null} filePath={path} />
</div>
) : (
<CodeHighlight className="language-typescript">
{children}
</CodeHighlight>
)}
</div>
)}
</div>
);
};

View File

@@ -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",

View File

@@ -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
</a>
</div>
)}

View File

@@ -29,7 +29,7 @@ export const OnboardingBanner = ({
<div className="relative p-2">
<img
src="https://img.youtube.com/vi/rgdNoHLaRN4/maxresdefault.jpg"
alt="Get started with Dyad in 3 minutes"
alt="Get started with MoreMinimore in 3 minutes"
className="w-28 h-16 object-cover rounded-md"
/>
<div className="absolute inset-0 flex items-center justify-center">
@@ -41,7 +41,7 @@ export const OnboardingBanner = ({
<div className="flex-1 px-4 py-3">
<div className="text-foreground">
<p className="font-semibold text-base">
Get started with Dyad in 3 minutes
Get started with MoreMinimore in 3 minutes
</p>
<p className="text-sm text-muted-foreground">
Start building your app for free

View File

@@ -36,7 +36,7 @@ export const AnnotatorOnlyForPro = ({ onGoBack }: AnnotatorOnlyForProProps) => {
</h2>
<p className="text-muted-foreground mb-10 text-center max-w-md text-base leading-relaxed">
Unlock the ability to annotate screenshots and enhance your workflow
with Dyad Pro.
with MoreMinimore Pro.
</p>
{/* 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
</Button>
</div>
</div>

View File

@@ -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" && (
<div className="absolute top-1 right-1 p-1 bg-red-100 dark:bg-red-900 rounded-md text-xs font-medium text-red-700 dark:text-red-300">
Internal Dyad error
Internal MoreMinimore error
</div>
)}
@@ -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."}
</span>
</div>
@@ -985,17 +984,11 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
: { width: `${deviceWidthConfig[deviceMode]}px` }
}
>
{userBudget ? (
<Annotator
screenshotUrl={screenshotDataUrl}
onSubmit={addAttachments}
handleAnnotatorClick={handleAnnotatorClick}
/>
) : (
<AnnotatorOnlyForPro
onGoBack={() => setAnnotatorMode(false)}
/>
)}
</div>
) : (
<>

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import type { SecurityFinding, SecurityReviewResult } from "@/ipc/ipc_types";
import { useState, useEffect } from "react";
import { VanillaMarkdownParser } from "@/components/chat/DyadMarkdownParser";
import { VanillaMarkdownParser } from "@/components/chat/MoreMinimoreMarkdownParser";
import { showSuccess, showWarning } from "@/lib/toast";
import { useLoadAppFile } from "@/hooks/useLoadAppFile";
import { useQueryClient } from "@tanstack/react-query";

View File

@@ -33,7 +33,7 @@ interface ApiKeyConfigurationProps {
onApiKeyInputChange: (value: string) => void;
onSaveKey: (value: string) => Promise<void>;
onDeleteKey: () => Promise<void>;
isDyad: boolean;
isMoreMinimore: boolean;
updateSettings: (settings: Partial<UserSettings>) => Promise<UserSettings>;
}
@@ -49,7 +49,7 @@ export function ApiKeyConfiguration({
onApiKeyInputChange,
onSaveKey,
onDeleteKey,
isDyad,
isMoreMinimore,
updateSettings,
}: ApiKeyConfigurationProps) {
// Special handling for Azure OpenAI which requires environment variables
@@ -86,7 +86,7 @@ export function ApiKeyConfiguration({
if (isValidUserKey || !hasEnvKey) {
defaultAccordionValue.push("settings-key");
}
if (!isDyad && hasEnvKey) {
if (!isMoreMinimore && hasEnvKey) {
defaultAccordionValue.push("env-key");
}
@@ -188,7 +188,7 @@ export function ApiKeyConfiguration({
</AccordionContent>
</AccordionItem>
{!isDyad && envVarName && (
{!isMoreMinimore && envVarName && (
<AccordionItem
value="env-key"
className="border rounded-lg px-4 bg-(--background-lightest)"

View File

@@ -101,7 +101,7 @@ export function AzureConfiguration({
variant: "default" as const,
title: "Azure OpenAI Configured",
description:
"Dyad will use the credentials saved in Settings for Azure OpenAI models.",
"MoreMinimore will use the credentials saved in Settings for Azure OpenAI models.",
icon: KeyRound,
titleClassName: "",
descriptionClassName: "",
@@ -259,12 +259,12 @@ export function AzureConfiguration({
<div className="text-sm text-muted-foreground space-y-2">
<p>
You can continue to configure Azure via environment variables.
If both variables are present and no settings are saved, Dyad
If both variables are present and no settings are saved, MoreMinimore
will use them automatically.
</p>
<p>
Values saved in Settings take precedence over environment
variables. Restart Dyad after changing environment variables.
variables. Restart MoreMinimore after changing environment variables.
</p>
</div>
</AccordionContent>

View File

@@ -22,21 +22,21 @@ interface ProviderSettingsHeaderProps {
isLoading: boolean;
hasFreeTier?: boolean;
providerWebsiteUrl?: string;
isDyad: boolean;
isMoreMinimore: boolean;
onBackClick: () => void;
}
function getKeyButtonText({
isConfigured,
isDyad,
isMoreMinimore,
}: {
isConfigured: boolean;
isDyad: boolean;
isMoreMinimore: boolean;
}) {
if (isDyad) {
if (isMoreMinimore) {
return isConfigured
? "Manage Dyad Pro Subscription"
: "Setup Dyad Pro Subscription";
? "Manage MoreMinimore Pro Subscription"
: "Setup MoreMinimore Pro Subscription";
}
return isConfigured ? "Manage API Keys" : "Setup API Key";
}
@@ -47,7 +47,7 @@ export function ProviderSettingsHeader({
isLoading,
hasFreeTier,
providerWebsiteUrl,
isDyad,
isMoreMinimore,
onBackClick,
}: ProviderSettingsHeaderProps) {
const handleGetApiKeyClick = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -63,7 +63,7 @@ export function ProviderSettingsHeader({
className="mb-4 cursor-pointer py-5 w-full ring-4 ring-primary/60 shadow-lg shadow-primary/30 border-primary/60"
>
<KeyRound className="mr-2 h-4 w-4" />
{getKeyButtonText({ isConfigured, isDyad })}
{getKeyButtonText({ isConfigured, isMoreMinimore })}
<ExternalLink className="ml-2 h-4 w-4" />
</Button>
);

View File

@@ -55,22 +55,22 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
const supportsCustomModels =
providerData?.type === "custom" || providerData?.type === "cloud";
const isDyad = provider === "auto";
const isMoreMinimore = provider === "auto";
const [apiKeyInput, setApiKeyInput] = useState("");
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const router = useRouter();
// Use fetched data (or defaults for Dyad)
const providerDisplayName = isDyad
? "Dyad"
// Use fetched data (or defaults for MoreMinimore)
const providerDisplayName = isMoreMinimore
? "MoreMinimore"
: (providerData?.name ?? "Unknown Provider");
const providerWebsiteUrl = isDyad
const providerWebsiteUrl = isMoreMinimore
? "https://academy.dyad.sh/settings"
: providerData?.websiteUrl;
const hasFreeTier = isDyad ? false : providerData?.hasFreeTier;
const envVarName = isDyad ? undefined : providerData?.envVarName;
const hasFreeTier = isMoreMinimore ? false : providerData?.hasFreeTier;
const envVarName = isMoreMinimore ? undefined : providerData?.envVarName;
// Use provider ID (which is the 'provider' prop)
const userApiKey = settings?.providerSettings?.[provider]?.apiKey?.value;
@@ -137,7 +137,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
},
},
};
if (isDyad) {
if (isMoreMinimore) {
settingsUpdate.enableDyadPro = true;
}
await updateSettings(settingsUpdate);
@@ -174,7 +174,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
}
};
// --- Toggle Dyad Pro Handler ---
// --- Toggle MoreMinimore Pro Handler ---
const handleToggleDyadPro = async (enabled: boolean) => {
setIsSaving(true);
try {
@@ -182,7 +182,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
enableDyadPro: enabled,
});
} catch (error: any) {
showError(`Error toggling Dyad Pro: ${error}`);
showError(`Error toggling MoreMinimore Pro: ${error}`);
} finally {
setIsSaving(false);
}
@@ -241,7 +241,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
}
// Handle case where provider is not found (e.g., invalid ID in URL)
if (!providerData && !isDyad) {
if (!providerData && !isMoreMinimore) {
return (
<div className="min-h-screen px-8 py-4">
<div className="max-w-4xl mx-auto">
@@ -278,7 +278,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
isLoading={settingsLoading}
hasFreeTier={hasFreeTier}
providerWebsiteUrl={providerWebsiteUrl}
isDyad={isDyad}
isMoreMinimore={isMoreMinimore}
onBackClick={() => router.history.back()}
/>
@@ -306,17 +306,17 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
onApiKeyInputChange={setApiKeyInput}
onSaveKey={handleSaveKey}
onDeleteKey={handleDeleteKey}
isDyad={isDyad}
isMoreMinimore={isMoreMinimore}
updateSettings={updateSettings}
/>
)}
{isDyad && !settingsLoading && (
{isMoreMinimore && !settingsLoading && (
<div className="mt-6 flex items-center justify-between p-4 bg-(--background-lightest) rounded-lg border">
<div>
<h3 className="font-medium">Enable Dyad Pro</h3>
<h3 className="font-medium">Enable MoreMinimore Pro</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Toggle to enable Dyad Pro
Toggle to enable MoreMinimore Pro
</p>
</div>
<Switch

View File

@@ -830,7 +830,7 @@ This conversation includes one or more image attachments. When the user uploads
appPath,
});
}
const smartContextMode: SmartContextMode = isDeepContextEnabled
const smartContextMode: SmartContextMode = true
? "deep"
: "balanced";
// Build provider options with correct Google/Vertex thinking config gating

View File

@@ -280,6 +280,10 @@ export class IpcClient {
await this.ipcRenderer.invoke("restart-dyad");
}
public async restartMoreMinimore(): Promise<void> {
await this.ipcRenderer.invoke("restart-dyad");
}
public async reloadEnvPath(): Promise<void> {
await this.ipcRenderer.invoke("reload-env-path");
}

View File

@@ -1,3 +1,4 @@
import { ipcMain } from "electron";
import { registerAppHandlers } from "./handlers/app_handlers";
import { registerChatHandlers } from "./handlers/chat_handlers";
import { registerChatStreamHandlers } from "./handlers/chat_stream_handlers";
@@ -48,6 +49,15 @@ export function registerIpcHandlers() {
registerProblemsHandlers();
registerProposalHandlers();
registerDebugHandlers();
// Add missing user budget handler
ipcMain.handle("get-user-budget", async () => {
return {
totalCredits: 1000,
usedCredits: 0,
resetDate: new Date().toISOString()
};
});
registerSupabaseHandlers();
registerNeonHandlers();
registerLocalModelHandlers();

View File

@@ -530,9 +530,9 @@ export const CLOUD_PROVIDERS: Record<
gatewayPrefix: "openrouter/",
},
auto: {
displayName: "Dyad",
websiteUrl: "https://academy.dyad.sh/settings",
gatewayPrefix: "dyad/",
displayName: "MoreMinimore",
websiteUrl: "https://moreminimore.com/settings",
gatewayPrefix: "moreminimore/",
},
azure: {
displayName: "Azure OpenAI",

View File

@@ -19,7 +19,7 @@ import log from "electron-log";
import { FREE_OPENROUTER_MODEL_NAMES } from "../shared/language_model_constants";
import { getLanguageModelProviders } from "../shared/language_model_helpers";
import { LanguageModelProvider } from "../ipc_types";
// import { createDyadEngine } from "./llm_engine_provider"; // Removed - Dyad Engine dependency
// // // // // import { createDyadEngine } from "./llm_engine_provider"; // Removed - Dyad Engine dependency // Removed - Dyad Engine dependency // Removed - Dyad Engine dependency // Removed - Dyad Engine dependency // Removed - Dyad Engine dependency
import { LM_STUDIO_BASE_URL } from "./lm_studio_utils";
import { createOllamaProvider } from "./ollama_provider";

View File

@@ -66,12 +66,12 @@ if (fs.existsSync(gitDir)) {
// https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app#main-process-mainjs
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient("dyad", process.execPath, [
app.setAsDefaultProtocolClient("moreminimore", process.execPath, [
path.resolve(process.argv[1]),
]);
}
} else {
app.setAsDefaultProtocolClient("dyad");
app.setAsDefaultProtocolClient("moreminimore");
}
export async function onReady() {
@@ -306,10 +306,10 @@ function handleDeepLinkReturn(url: string) {
"hostname",
parsed.hostname,
);
if (parsed.protocol !== "dyad:") {
if (parsed.protocol !== "moreminimore:") {
dialog.showErrorBox(
"Invalid Protocol",
`Expected dyad://, got ${parsed.protocol}. Full URL: ${url}`,
`Expected moreminimore://, got ${parsed.protocol}. Full URL: ${url}`,
);
return;
}

View File

@@ -33,7 +33,7 @@ import { ForceCloseDialog } from "@/components/ForceCloseDialog";
import type { FileAttachment } from "@/ipc/ipc_types";
import { NEON_TEMPLATE_IDS } from "@/shared/templates";
import { neonTemplateHook } from "@/client_logic/template_hook";
import { ProBanner } from "@/components/ProBanner";
// import { ProBanner } from "@/components/ProBanner";
// Adding an export for attachments
export interface HomeSubmitOptions {
@@ -268,7 +268,6 @@ export default function HomePage() {
</span>
</button>
</div>
<ProBanner />
</div>
<PrivacyBanner />

View File

@@ -53,4 +53,14 @@ export const localTemplatesData: Template[] = [
isExperimental: true,
requiresNeon: true,
},
{
id: "moreminimore-custom",
title: "MoreMinimore Custom",
description: "Custom MoreMinimore template with enhanced features and debranded experience.",
imageUrl:
"https://github.com/user-attachments/assets/5b700eab-b28c-498e-96de-8649b14c16d9",
githubUrl: "https://github.com/kunthawat/moreminimore-vibe",
isOfficial: false,
isExperimental: false,
},
];