Lastest change
This commit is contained in:
@@ -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"
|
||||
<div className="flex items-center no-app-region-drag">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 hover:bg-zinc-200 dark:hover:bg-zinc-700"
|
||||
onClick={handleMinimize}
|
||||
>
|
||||
<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"
|
||||
<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}
|
||||
>
|
||||
<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"
|
||||
<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}
|
||||
>
|
||||
<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>
|
||||
<span className="text-xs">×</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DyadProButton({
|
||||
isDyadProEnabled,
|
||||
}: {
|
||||
isDyadProEnabled: boolean;
|
||||
}) {
|
||||
const { navigate } = useRouter();
|
||||
const { userBudget } = useUserBudgetInfo();
|
||||
return (
|
||||
<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",
|
||||
)}
|
||||
size="sm"
|
||||
>
|
||||
{isDyadProEnabled ? "Pro" : "Pro (off)"}
|
||||
{userBudget && isDyadProEnabled && (
|
||||
<AICreditStatus userBudget={userBudget} />
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 && (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -250,7 +250,7 @@ export function LexicalChatInput({
|
||||
onSubmit,
|
||||
onPaste,
|
||||
excludeCurrentApp,
|
||||
placeholder = "Ask Dyad to build...",
|
||||
placeholder = "Ask MoreMinimore to build...",
|
||||
disabled = false,
|
||||
disableSendButton,
|
||||
}: LexicalChatInputProps) {
|
||||
|
||||
93
src/components/chat/MoreMinimoreAddDependency.tsx
Normal file
93
src/components/chat/MoreMinimoreAddDependency.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
89
src/components/chat/MoreMinimoreAddIntegration.tsx
Normal file
89
src/components/chat/MoreMinimoreAddIntegration.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
src/components/chat/MoreMinimoreCodeSearch.tsx
Normal file
31
src/components/chat/MoreMinimoreCodeSearch.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
123
src/components/chat/MoreMinimoreCodeSearchResult.tsx
Normal file
123
src/components/chat/MoreMinimoreCodeSearchResult.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
117
src/components/chat/MoreMinimoreCodebaseContext.tsx
Normal file
117
src/components/chat/MoreMinimoreCodebaseContext.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
45
src/components/chat/MoreMinimoreDelete.tsx
Normal file
45
src/components/chat/MoreMinimoreDelete.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
113
src/components/chat/MoreMinimoreEdit.tsx
Normal file
113
src/components/chat/MoreMinimoreEdit.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
85
src/components/chat/MoreMinimoreExecuteSql.tsx
Normal file
85
src/components/chat/MoreMinimoreExecuteSql.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
610
src/components/chat/MoreMinimoreMarkdownParser.tsx
Normal file
610
src/components/chat/MoreMinimoreMarkdownParser.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
73
src/components/chat/MoreMinimoreMcpToolCall.tsx
Normal file
73
src/components/chat/MoreMinimoreMcpToolCall.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
73
src/components/chat/MoreMinimoreMcpToolResult.tsx
Normal file
73
src/components/chat/MoreMinimoreMcpToolResult.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
112
src/components/chat/MoreMinimoreOutput.tsx
Normal file
112
src/components/chat/MoreMinimoreOutput.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
156
src/components/chat/MoreMinimoreProblemSummary.tsx
Normal file
156
src/components/chat/MoreMinimoreProblemSummary.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
44
src/components/chat/MoreMinimoreRead.tsx
Normal file
44
src/components/chat/MoreMinimoreRead.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
61
src/components/chat/MoreMinimoreRename.tsx
Normal file
61
src/components/chat/MoreMinimoreRename.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
151
src/components/chat/MoreMinimoreSearchReplace.tsx
Normal file
151
src/components/chat/MoreMinimoreSearchReplace.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
96
src/components/chat/MoreMinimoreThink.tsx
Normal file
96
src/components/chat/MoreMinimoreThink.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
36
src/components/chat/MoreMinimoreTokenSavings.tsx
Normal file
36
src/components/chat/MoreMinimoreTokenSavings.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
27
src/components/chat/MoreMinimoreWebCrawl.tsx
Normal file
27
src/components/chat/MoreMinimoreWebCrawl.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
src/components/chat/MoreMinimoreWebSearch.tsx
Normal file
31
src/components/chat/MoreMinimoreWebSearch.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
78
src/components/chat/MoreMinimoreWebSearchResult.tsx
Normal file
78
src/components/chat/MoreMinimoreWebSearchResult.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
159
src/components/chat/MoreMinimoreWrite.tsx
Normal file
159
src/components/chat/MoreMinimoreWrite.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
<Annotator
|
||||
screenshotUrl={screenshotDataUrl}
|
||||
onSubmit={addAttachments}
|
||||
handleAnnotatorClick={handleAnnotatorClick}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
1081
src/components/preview_panel/PreviewIframe.tsx.bak
Normal file
1081
src/components/preview_panel/PreviewIframe.tsx.bak
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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";
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user