lint using oxlint (#106)

This commit is contained in:
Will Chen
2025-05-08 17:21:35 -07:00
committed by GitHub
parent 0e8cc26fb5
commit 2537fbb342
63 changed files with 251 additions and 292 deletions

5
.oxlintrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"rules": {
"eslint/no-unused-vars": "error"
}
}

View File

@@ -7,8 +7,6 @@ import { VitePlugin } from "@electron-forge/plugin-vite";
import { FusesPlugin } from "@electron-forge/plugin-fuses"; import { FusesPlugin } from "@electron-forge/plugin-fuses";
import { FuseV1Options, FuseVersion } from "@electron/fuses"; import { FuseV1Options, FuseVersion } from "@electron/fuses";
import { AutoUnpackNativesPlugin } from "@electron-forge/plugin-auto-unpack-natives"; import { AutoUnpackNativesPlugin } from "@electron-forge/plugin-auto-unpack-natives";
import path from "path";
import fs from "fs";
// Based on https://github.com/electron/forge/blob/6b2d547a7216c30fde1e1fddd1118eee5d872945/packages/plugin/vite/src/VitePlugin.ts#L124 // Based on https://github.com/electron/forge/blob/6b2d547a7216c30fde1e1fddd1118eee5d872945/packages/plugin/vite/src/VitePlugin.ts#L124
const ignore = (file: string) => { const ignore = (file: string) => {

140
package-lock.json generated
View File

@@ -99,6 +99,7 @@
"happy-dom": "^17.4.4", "happy-dom": "^17.4.4",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.5.2", "lint-staged": "^15.5.2",
"oxlint": "^0.16.9",
"prettier": "3.5.3", "prettier": "3.5.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^5.4.17", "vite": "^5.4.17",
@@ -3604,6 +3605,118 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/@oxlint/darwin-arm64": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-0.16.9.tgz",
"integrity": "sha512-s8gPacumFNuDQcl0dCRLI0+efbiQrOF984brGrW1i2buJtaMzjYiunaU5TSrbHgnb/omvpFnG4l9g+YHOK/s0A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxlint/darwin-x64": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-0.16.9.tgz",
"integrity": "sha512-QFue9yhfRU+fkbsOmDzCCwDafPR6fnaP/M+Rocp/MMDQ7qvjbB2sj0/xxp/CcvL7aLtFklMeXPR0hCfW3LyrSw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxlint/linux-arm64-gnu": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-0.16.9.tgz",
"integrity": "sha512-qP/wdlgqLuiW9WDkAsyMN85wQ3nqAQynjRD+1II1QO0yI9N1ZHD6LF9P5fXAqY0eJwcf3emluQMoaeveewtiCg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint/linux-arm64-musl": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-0.16.9.tgz",
"integrity": "sha512-486wn1MIqP4hkHTnuWedTb16X6Zs3ImmmMxqzfYlcemf9kODM6yNlxal6wGvkm7SGRPYrsB/P9S5wgpzmLzKrw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint/linux-x64-gnu": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-0.16.9.tgz",
"integrity": "sha512-d20zqy4Mimz9CxjIEJdGd6jtyyhpSryff95gNJSTvh/EA4NqvjjlbjxuHt3clNjglRwJWE3kgmUCw9U9oWFcWA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint/linux-x64-musl": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-0.16.9.tgz",
"integrity": "sha512-mZLshz99773RMa77YhtsgWbqE7JY4xnYSDecDy+ZkafRb0acqz1Ujiq2l4cE+HnJOGVOMaOpzG0UoQ3ZNkXNAA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxlint/win32-arm64": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-0.16.9.tgz",
"integrity": "sha512-Zp9+0CfTb7ebgvRwDO2F6NVgRtRmxWMdBnrbMRdVbKY6CCT2vjLAIILwBf5AsNLdLQC7FbXAEivSKbRX0UPyJA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxlint/win32-x64": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-0.16.9.tgz",
"integrity": "sha512-6a507bALmDNFdvEbJzu9ybajryBHo+6nMWPNyu/mBguCqmconoBbQXftELd2VG/0ecxBmBcMKAQW6aONGUVc3w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@petamoriken/float16": { "node_modules/@petamoriken/float16": {
"version": "3.9.2", "version": "3.9.2",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.2.tgz", "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.2.tgz",
@@ -15062,6 +15175,33 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/oxlint": {
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-0.16.9.tgz",
"integrity": "sha512-YMGu177AURJxdCq45/Yw6Q+uDh9ZfU++GuLYhUz+DfIGdHpAqVlBI9lCqm2HkLc6qO8ySYZ+8ljsWHLQA8F+EQ==",
"dev": true,
"license": "MIT",
"bin": {
"oxc_language_server": "bin/oxc_language_server",
"oxlint": "bin/oxlint"
},
"engines": {
"node": ">=8.*"
},
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
"@oxlint/darwin-arm64": "0.16.9",
"@oxlint/darwin-x64": "0.16.9",
"@oxlint/linux-arm64-gnu": "0.16.9",
"@oxlint/linux-arm64-musl": "0.16.9",
"@oxlint/linux-x64-gnu": "0.16.9",
"@oxlint/linux-x64-musl": "0.16.9",
"@oxlint/win32-arm64": "0.16.9",
"@oxlint/win32-x64": "0.16.9"
}
},
"node_modules/p-cancelable": { "node_modules/p-cancelable": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",

View File

@@ -18,7 +18,8 @@
"make": "npm run clean && electron-forge make", "make": "npm run clean && electron-forge make",
"publish": "npm run clean && electron-forge publish", "publish": "npm run clean && electron-forge publish",
"ts": "npx tsc -p tsconfig.app.json --noEmit", "ts": "npx tsc -p tsconfig.app.json --noEmit",
"lint": "npx biome lint src --fix --unsafe", "lint": "npx oxlint --fix",
"lint:fix": "npx oxlint --fix --fix-suggestions --fix-dangerously",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push", "db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio", "db:studio": "drizzle-kit studio",
@@ -61,6 +62,7 @@
"happy-dom": "^17.4.4", "happy-dom": "^17.4.4",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.5.2", "lint-staged": "^15.5.2",
"oxlint": "^0.16.9",
"prettier": "3.5.3", "prettier": "3.5.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^5.4.17", "vite": "^5.4.17",
@@ -131,6 +133,7 @@
"uuid": "^11.1.0" "uuid": "^11.1.0"
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint",
"*.{js,css,md,ts,tsx,jsx,json}": "prettier --write" "*.{js,css,md,ts,tsx,jsx,json}": "prettier --write"
} }
} }

View File

@@ -17,6 +17,7 @@
? new URL(url, window.location.href).href ? new URL(url, window.location.href).href
: window.location.href; : window.location.href;
} catch (e) { } catch (e) {
console.error("Could not parse URL", e);
newUrl = window.location.href; newUrl = window.location.href;
} }
@@ -64,7 +65,7 @@
}; };
// --- Listener for Back/Forward Navigation (popstate event) --- // --- Listener for Back/Forward Navigation (popstate event) ---
window.addEventListener("popstate", (event) => { window.addEventListener("popstate", () => {
const currentUrl = window.location.href; const currentUrl = window.location.href;
previousUrl = currentUrl; previousUrl = currentUrl;
}); });

View File

@@ -12,7 +12,7 @@ type ToasterToast = ToastProps & {
action?: ToastActionElement; action?: ToastActionElement;
}; };
const actionTypes = { const _actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: "DISMISS_TOAST",
@@ -26,7 +26,7 @@ function genId() {
return count.toString(); return count.toString();
} }
type ActionType = typeof actionTypes; type ActionType = typeof _actionTypes;
type Action = type Action =
| { | {

View File

@@ -87,7 +87,7 @@ export function devErrorAndNavigationPlugin(): Plugin {
}; };
} }
export default defineConfig(({ mode }) => ({ export default defineConfig(() => ({
server: { server: {
host: "::", host: "::",
port: 8080, port: 8080,

View File

@@ -99,8 +99,8 @@ console.log("TodoItem");
expect(result).toEqual([ expect(result).toEqual([
{ {
path: "src/components/TodoItem.tsx", path: "src/components/TodoItem.tsx",
content: `import React from \"react\"; content: `import React from "react";
console.log(\"TodoItem\");`, console.log("TodoItem");`,
}, },
]); ]);
}); });
@@ -117,8 +117,8 @@ console.log("TodoItem");
expect(result).toEqual([ expect(result).toEqual([
{ {
path: "src/components/TodoItem.tsx", path: "src/components/TodoItem.tsx",
content: `import React from \"react\"; content: `import React from "react";
console.log(\"TodoItem\");`, console.log("TodoItem");`,
}, },
]); ]);
}); });

View File

@@ -1,4 +1,4 @@
import { atom } from "jotai"; import { atom } from "jotai";
import type { CodeProposal, ProposalResult } from "@/lib/schemas"; import type { ProposalResult } from "@/lib/schemas";
export const proposalResultAtom = atom<ProposalResult | null>(null); export const proposalResultAtom = atom<ProposalResult | null>(null);

View File

@@ -1,6 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate, useRouterState } from "@tanstack/react-router"; import { useNavigate, useRouterState } from "@tanstack/react-router";
import type { ChatSummary } from "@/lib/schemas";
import { formatDistanceToNow } from "date-fns"; import { formatDistanceToNow } from "date-fns";
import { PlusCircle, MoreVertical, Trash2 } from "lucide-react"; import { PlusCircle, MoreVertical, Trash2 } from "lucide-react";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
@@ -29,7 +29,7 @@ export function ChatList({ show }: { show?: boolean }) {
const navigate = useNavigate(); const navigate = useNavigate();
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom); const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom);
const [selectedAppId, setSelectedAppId] = useAtom(selectedAppIdAtom); const [selectedAppId, setSelectedAppId] = useAtom(selectedAppIdAtom);
const [isDropdownOpen, setIsDropdownOpen] = useAtom(dropdownOpenAtom); const [, setIsDropdownOpen] = useAtom(dropdownOpenAtom);
const { chats, loading, refreshChats } = useChats(selectedAppId); const { chats, loading, refreshChats } = useChats(selectedAppId);
const routerState = useRouterState(); const routerState = useRouterState();
const isChatRoute = routerState.location.pathname === "/chat"; const isChatRoute = routerState.location.pathname === "/chat";

View File

@@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useCallback } from "react";
import { useAtom, useAtomValue } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import { chatMessagesAtom, chatStreamCountAtom } from "../atoms/chatAtoms"; import { chatMessagesAtom, chatStreamCountAtom } from "../atoms/chatAtoms";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { ChatHeader } from "./chat/ChatHeader"; import { ChatHeader } from "./chat/ChatHeader";
import { MessagesList } from "./chat/MessagesList"; import { MessagesList } from "./chat/MessagesList";
import { ChatInput } from "./chat/ChatInput"; import { ChatInput } from "./chat/ChatInput";
@@ -20,14 +20,11 @@ export function ChatPanel({
isPreviewOpen, isPreviewOpen,
onTogglePreview, onTogglePreview,
}: ChatPanelProps) { }: ChatPanelProps) {
const appId = useAtomValue(selectedAppIdAtom);
const [messages, setMessages] = useAtom(chatMessagesAtom); const [messages, setMessages] = useAtom(chatMessagesAtom);
const [appName, setAppName] = useState<string>("Chat");
const [isVersionPaneOpen, setIsVersionPaneOpen] = useState(false); const [isVersionPaneOpen, setIsVersionPaneOpen] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const streamCount = useAtomValue(chatStreamCountAtom); const streamCount = useAtomValue(chatStreamCountAtom);
// Reference to store the processed prompt so we don't submit it twice // Reference to store the processed prompt so we don't submit it twice
const processedPromptRef = useRef<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement | null>(null); const messagesEndRef = useRef<HTMLDivElement | null>(null);
const messagesContainerRef = useRef<HTMLDivElement | null>(null); const messagesContainerRef = useRef<HTMLDivElement | null>(null);
@@ -83,23 +80,6 @@ export function ChatPanel({
}; };
}, []); }, []);
useEffect(() => {
const fetchAppName = async () => {
if (!appId) return;
try {
const app = await IpcClient.getInstance().getApp(appId);
if (app?.name) {
setAppName(app.name);
}
} catch (error) {
console.error("Failed to fetch app name:", error);
}
};
fetchAppName();
}, [appId]);
const fetchChatMessages = useCallback(async () => { const fetchChatMessages = useCallback(async () => {
if (!chatId) { if (!chatId) {
setMessages([]); setMessages([]);

View File

@@ -1,10 +1,4 @@
import React, { import React, { useState, useEffect } from "react";
Component,
ErrorInfo,
ReactNode,
useState,
useEffect,
} from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { LightbulbIcon } from "lucide-react"; import { LightbulbIcon } from "lucide-react";
import { ErrorComponentProps } from "@tanstack/react-router"; import { ErrorComponentProps } from "@tanstack/react-router";

View File

@@ -292,7 +292,7 @@ Session ID: ${sessionId}
<div className="border rounded-md p-3"> <div className="border rounded-md p-3">
<h3 className="font-medium mb-2">Chat Messages</h3> <h3 className="font-medium mb-2">Chat Messages</h3>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto"> <div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto">
{chatLogsData.chat.messages.map((msg, index) => ( {chatLogsData.chat.messages.map((msg) => (
<div key={msg.id} className="mb-2"> <div key={msg.id} className="mb-2">
<span className="font-semibold"> <span className="font-semibold">
{msg.role === "user" ? "You" : "Assistant"}:{" "} {msg.role === "user" ? "You" : "Assistant"}:{" "}

View File

@@ -10,13 +10,8 @@ import { providerSettingsRoute } from "@/routes/settings/providers/$provider";
import type { ModelProvider } from "@/lib/schemas"; import type { ModelProvider } from "@/lib/schemas";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { GiftIcon } from "lucide-react"; import { GiftIcon } from "lucide-react";
interface ProviderSettingsProps {
configuredProviders?: ModelProvider[];
}
export function ProviderSettingsGrid({ export function ProviderSettingsGrid() {
configuredProviders = [],
}: ProviderSettingsProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const handleProviderClick = (provider: ModelProvider) => { const handleProviderClick = (provider: ModelProvider) => {
@@ -33,10 +28,6 @@ export function ProviderSettingsGrid({
<h2 className="text-2xl font-bold mb-6">AI Providers</h2> <h2 className="text-2xl font-bold mb-6">AI Providers</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(PROVIDERS).map(([key, provider]) => { {Object.entries(PROVIDERS).map(([key, provider]) => {
const isConfigured = configuredProviders.includes(
key as ModelProvider,
);
return ( return (
<Card <Card
key={key} key={key}

View File

@@ -1,8 +1,8 @@
import { useState, useEffect } from "react"; import { useEffect } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { SupabaseSchema } from "@/lib/schemas";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { toast } from "sonner"; import { toast } from "sonner";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
@@ -24,7 +24,7 @@ import {
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { useLoadApp } from "@/hooks/useLoadApp"; import { useLoadApp } from "@/hooks/useLoadApp";
import { useDeepLink } from "@/contexts/DeepLinkContext"; import { useDeepLink } from "@/contexts/DeepLinkContext";
const OAUTH_CLIENT_ID = "bf747de7-60bb-48a2-9015-6494e0b04983";
// @ts-ignore // @ts-ignore
import supabaseLogoLight from "../../assets/supabase/supabase-logo-wordmark--light.svg"; import supabaseLogoLight from "../../assets/supabase/supabase-logo-wordmark--light.svg";
// @ts-ignore // @ts-ignore
@@ -74,7 +74,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
toast.success("Project connected to app successfully"); toast.success("Project connected to app successfully");
await refreshApp(); await refreshApp();
} catch (error) { } catch (error) {
toast.error("Failed to connect project to app"); toast.error("Failed to connect project to app: " + error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import React, { useState } from "react"; import React from "react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";

View File

@@ -1,7 +1,6 @@
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { showInfo } from "@/lib/toast";
export function TelemetrySwitch() { export function TelemetrySwitch() {
const { settings, updateSettings } = useSettings(); const { settings, updateSettings } = useSettings();

View File

@@ -1,5 +1,4 @@
import { FileText, X } from "lucide-react"; import { FileText, X } from "lucide-react";
import { useEffect } from "react";
interface AttachmentsListProps { interface AttachmentsListProps {
attachments: File[]; attachments: File[];

View File

@@ -3,11 +3,10 @@ import {
History, History,
PlusCircle, PlusCircle,
GitBranch, GitBranch,
AlertCircle,
Info, Info,
} from "lucide-react"; } from "lucide-react";
import { PanelRightClose } from "lucide-react"; import { PanelRightClose } from "lucide-react";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useVersions } from "@/hooks/useVersions"; import { useVersions } from "@/hooks/useVersions";
import { Button } from "../ui/button"; import { Button } from "../ui/button";

View File

@@ -58,17 +58,14 @@ import { useVersions } from "@/hooks/useVersions";
import { useAttachments } from "@/hooks/useAttachments"; import { useAttachments } from "@/hooks/useAttachments";
import { AttachmentsList } from "./AttachmentsList"; import { AttachmentsList } from "./AttachmentsList";
import { DragDropOverlay } from "./DragDropOverlay"; import { DragDropOverlay } from "./DragDropOverlay";
import { import { showUncommittedFilesWarning } from "@/lib/toast";
showError as showErrorToast,
showUncommittedFilesWarning,
} from "@/lib/toast";
const showTokenBarAtom = atom(false); const showTokenBarAtom = atom(false);
export function ChatInput({ chatId }: { chatId?: number }) { export function ChatInput({ chatId }: { chatId?: number }) {
const posthog = usePostHog(); const posthog = usePostHog();
const [inputValue, setInputValue] = useAtom(chatInputValueAtom); const [inputValue, setInputValue] = useAtom(chatInputValueAtom);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { settings, updateSettings, isAnyProviderSetup } = useSettings(); const { settings, updateSettings } = useSettings();
const appId = useAtomValue(selectedAppIdAtom); const appId = useAtomValue(selectedAppIdAtom);
const { refreshVersions } = useVersions(appId); const { refreshVersions } = useVersions(appId);
const { streamMessage, isStreaming, setIsStreaming, error, setError } = const { streamMessage, isStreaming, setIsStreaming, error, setError } =
@@ -76,7 +73,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
const [showError, setShowError] = useState(true); const [showError, setShowError] = useState(true);
const [isApproving, setIsApproving] = useState(false); // State for approving const [isApproving, setIsApproving] = useState(false); // State for approving
const [isRejecting, setIsRejecting] = useState(false); // State for rejecting const [isRejecting, setIsRejecting] = useState(false); // State for rejecting
const [messages, setMessages] = useAtom<Message[]>(chatMessagesAtom); const [, setMessages] = useAtom<Message[]>(chatMessagesAtom);
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom); const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [showTokenBar, setShowTokenBar] = useAtom(showTokenBarAtom); const [showTokenBar, setShowTokenBar] = useAtom(showTokenBarAtom);
@@ -101,7 +98,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
error: proposalError, error: proposalError,
refreshProposal, refreshProposal,
} = useProposal(chatId); } = useProposal(chatId);
const { proposal, chatId: proposalChatId, messageId } = proposalResult ?? {}; const { proposal, messageId } = proposalResult ?? {};
const adjustHeight = () => { const adjustHeight = () => {
const textarea = textareaRef.current; const textarea = textareaRef.current;
@@ -620,7 +617,6 @@ function ChatInputActions({
isApproving, isApproving,
isRejecting, isRejecting,
}: ChatInputActionsProps) { }: ChatInputActionsProps) {
const [autoApprove, setAutoApprove] = useState(false);
const [isDetailsVisible, setIsDetailsVisible] = useState(false); const [isDetailsVisible, setIsDetailsVisible] = useState(false);
if (proposal.type === "tip-proposal") { if (proposal.type === "tip-proposal") {

View File

@@ -1,4 +1,3 @@
import { memo } from "react";
import type { Message } from "@/ipc/ipc_types"; import type { Message } from "@/ipc/ipc_types";
import { import {
DyadMarkdownParser, DyadMarkdownParser,

View File

@@ -1,19 +1,10 @@
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useState } from "react"; import { useState } from "react";
import { Button } from "../ui/button";
import { IpcClient } from "../../ipc/ipc_client"; import { IpcClient } from "../../ipc/ipc_client";
import { useAtom, useAtomValue } from "jotai";
import { chatMessagesAtom, selectedChatIdAtom } from "../../atoms/chatAtoms"; import { Package, ChevronsUpDown, ChevronsDownUp } from "lucide-react";
import { useStreamChat } from "@/hooks/useStreamChat";
import {
Package,
ChevronsUpDown,
ChevronsDownUp,
Loader,
ExternalLink,
Download,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
interface DyadAddDependencyProps { interface DyadAddDependencyProps {
@@ -28,15 +19,14 @@ export const DyadAddDependency: React.FC<DyadAddDependencyProps> = ({
}) => { }) => {
// Extract package attribute from the node if available // Extract package attribute from the node if available
const packages = node?.properties?.packages?.split(" ") || ""; const packages = node?.properties?.packages?.split(" ") || "";
const [isInstalling, setIsInstalling] = useState(false);
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
const hasChildren = !!children; const hasChildren = !!children;
return ( return (
<div <div
className={`bg-(--background-lightest) dark:bg-gray-900 hover:bg-(--background-lighter) rounded-lg px-4 py-3 border my-2 ${ 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" : "" hasChildren ? "cursor-pointer" : ""
} ${isInstalling ? "border-amber-500" : "border-border"}`} }`}
onClick={ onClick={
hasChildren ? () => setIsContentVisible(!isContentVisible) : undefined hasChildren ? () => setIsContentVisible(!isContentVisible) : undefined
} }
@@ -66,12 +56,6 @@ export const DyadAddDependency: React.FC<DyadAddDependencyProps> = ({
</div> </div>
</div> </div>
)} )}
{isInstalling && (
<div className="flex items-center text-amber-600 text-xs ml-2">
<Loader size={14} className="mr-1 animate-spin" />
<span>Installing...</span>
</div>
)}
</div> </div>
{hasChildren && ( {hasChildren && (
<div className="flex items-center"> <div className="flex items-center">

View File

@@ -30,7 +30,13 @@ type ContentPiece =
| { type: "markdown"; content: string } | { type: "markdown"; content: string }
| { type: "custom-tag"; tagInfo: CustomTagInfo }; | { type: "custom-tag"; tagInfo: CustomTagInfo };
const customLink = ({ node, ...props }: { node?: any; [key: string]: any }) => ( const customLink = ({
node: _node,
...props
}: {
node?: any;
[key: string]: any;
}) => (
<a <a
{...props} {...props}
onClick={(e) => { onClick={(e) => {
@@ -358,11 +364,3 @@ function renderCustomTag(
return null; return null;
} }
} }
/**
* Extract attribute values from className string
*/
function extractAttribute(className: string, attrName: string): string {
const match = new RegExp(`${attrName}="([^"]*)"`, "g").exec(className);
return match ? match[1] : "";
}

View File

@@ -6,8 +6,8 @@ import {
XCircle, XCircle,
Sparkles, Sparkles,
} from "lucide-react"; } from "lucide-react";
import { useAtom, useSetAtom } from "jotai"; import { useAtomValue } from "jotai";
import { chatInputValueAtom, selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { useStreamChat } from "@/hooks/useStreamChat"; import { useStreamChat } from "@/hooks/useStreamChat";
interface DyadOutputProps { interface DyadOutputProps {
type: "error" | "warning"; type: "error" | "warning";
@@ -21,7 +21,7 @@ export const DyadOutput: React.FC<DyadOutputProps> = ({
children, children,
}) => { }) => {
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom); const selectedChatId = useAtomValue(selectedChatIdAtom);
const { streamMessage } = useStreamChat(); const { streamMessage } = useStreamChat();
// If the type is not warning, it is an error (in case LLM gives a weird "type") // If the type is not warning, it is an error (in case LLM gives a weird "type")

View File

@@ -1,6 +1,6 @@
import { SendIcon, StopCircleIcon, X, Paperclip, Loader2 } from "lucide-react"; import { SendIcon, StopCircleIcon, Paperclip } from "lucide-react";
import type React from "react"; import type React from "react";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef } from "react";
import { ModelPicker } from "@/components/ModelPicker"; import { ModelPicker } from "@/components/ModelPicker";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { homeChatInputValueAtom } from "@/atoms/chatAtoms"; // Use a different atom for home input import { homeChatInputValueAtom } from "@/atoms/chatAtoms"; // Use a different atom for home input
@@ -21,7 +21,7 @@ export function HomeChatInput({
const [inputValue, setInputValue] = useAtom(homeChatInputValueAtom); const [inputValue, setInputValue] = useAtom(homeChatInputValueAtom);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { settings, updateSettings, isAnyProviderSetup } = useSettings(); const { settings, updateSettings, isAnyProviderSetup } = useSettings();
const { streamMessage, isStreaming, setIsStreaming } = useStreamChat({ const { isStreaming } = useStreamChat({
hasChatId: false, hasChatId: false,
}); // eslint-disable-line @typescript-eslint/no-unused-vars }); // eslint-disable-line @typescript-eslint/no-unused-vars

View File

@@ -6,12 +6,12 @@ import { SetupBanner } from "../SetupBanner";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useStreamChat } from "@/hooks/useStreamChat"; import { useStreamChat } from "@/hooks/useStreamChat";
import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from "jotai";
import { Loader2, RefreshCw, Undo } from "lucide-react"; import { Loader2, RefreshCw, Undo } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useVersions } from "@/hooks/useVersions"; import { useVersions } from "@/hooks/useVersions";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { showError, showSuccess, showWarning } from "@/lib/toast"; import { showError, showWarning } from "@/lib/toast";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { chatMessagesAtom } from "@/atoms/chatAtoms"; import { chatMessagesAtom } from "@/atoms/chatAtoms";
@@ -24,13 +24,13 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
function MessagesList({ messages, messagesEndRef }, ref) { function MessagesList({ messages, messagesEndRef }, ref) {
const appId = useAtomValue(selectedAppIdAtom); const appId = useAtomValue(selectedAppIdAtom);
const { versions, revertVersion } = useVersions(appId); const { versions, revertVersion } = useVersions(appId);
const { streamMessage, isStreaming, error, setError } = useStreamChat(); const { streamMessage, isStreaming } = useStreamChat();
const { isAnyProviderSetup } = useSettings(); const { isAnyProviderSetup } = useSettings();
const setMessages = useSetAtom(chatMessagesAtom); const setMessages = useSetAtom(chatMessagesAtom);
const [isUndoLoading, setIsUndoLoading] = useState(false); const [isUndoLoading, setIsUndoLoading] = useState(false);
const [isRetryLoading, setIsRetryLoading] = useState(false); const [isRetryLoading, setIsRetryLoading] = useState(false);
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom); const selectedChatId = useAtomValue(selectedChatIdAtom);
return ( return (
<div className="flex-1 overflow-y-auto p-4" ref={ref}> <div className="flex-1 overflow-y-auto p-4" ref={ref}>

View File

@@ -1,4 +1,3 @@
import { useState } from "react";
import { FileEditor } from "./FileEditor"; import { FileEditor } from "./FileEditor";
import { FileTree } from "./FileTree"; import { FileTree } from "./FileTree";
import { RefreshCw } from "lucide-react"; import { RefreshCw } from "lucide-react";

View File

@@ -90,7 +90,7 @@ export const FileEditor = ({ appId, filePath }: FileEditorProps) => {
const editorTheme = isDarkMode ? "dyad-dark" : "dyad-light"; const editorTheme = isDarkMode ? "dyad-dark" : "dyad-light";
// Handle editor mount // Handle editor mount
const handleEditorDidMount: OnMount = (editor, monaco) => { const handleEditorDidMount: OnMount = (editor) => {
editorRef.current = editor; editorRef.current = editor;
// Listen for model content change events // Listen for model content change events

View File

@@ -20,7 +20,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { useLoadApp } from "@/hooks/useLoadApp";
import { useLoadAppFile } from "@/hooks/useLoadAppFile"; import { useLoadAppFile } from "@/hooks/useLoadAppFile";
import { import {
DropdownMenu, DropdownMenu,
@@ -28,8 +28,6 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useSettings } from "@/hooks/useSettings";
import { useRunApp } from "@/hooks/useRunApp";
import { useStreamChat } from "@/hooks/useStreamChat"; import { useStreamChat } from "@/hooks/useStreamChat";
interface ErrorBannerProps { interface ErrorBannerProps {
@@ -113,7 +111,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
// State to trigger iframe reload // State to trigger iframe reload
const [reloadKey, setReloadKey] = useState(0); const [reloadKey, setReloadKey] = useState(0);
const [errorMessage, setErrorMessage] = useAtom(previewErrorMessageAtom); const [errorMessage, setErrorMessage] = useAtom(previewErrorMessageAtom);
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom); const selectedChatId = useAtomValue(selectedChatIdAtom);
const { streamMessage } = useStreamChat(); const { streamMessage } = useStreamChat();
const [availableRoutes, setAvailableRoutes] = useState< const [availableRoutes, setAvailableRoutes] = useState<
Array<{ path: string; label: string }> Array<{ path: string; label: string }>
@@ -168,7 +166,6 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
const [navigationHistory, setNavigationHistory] = useState<string[]>([]); const [navigationHistory, setNavigationHistory] = useState<string[]>([]);
const [currentHistoryPosition, setCurrentHistoryPosition] = useState(0); const [currentHistoryPosition, setCurrentHistoryPosition] = useState(0);
const iframeRef = useRef<HTMLIFrameElement>(null); const iframeRef = useRef<HTMLIFrameElement>(null);
const { settings } = useSettings();
// Add message listener for iframe errors and navigation events // Add message listener for iframe errors and navigation events
useEffect(() => { useEffect(() => {

View File

@@ -5,7 +5,7 @@ import {
previewPanelKeyAtom, previewPanelKeyAtom,
selectedAppIdAtom, selectedAppIdAtom,
} from "../../atoms/appAtoms"; } from "../../atoms/appAtoms";
import { useLoadApp } from "@/hooks/useLoadApp";
import { CodeView } from "./CodeView"; import { CodeView } from "./CodeView";
import { PreviewIframe } from "./PreviewIframe"; import { PreviewIframe } from "./PreviewIframe";
import { import {
@@ -14,11 +14,8 @@ import {
ChevronDown, ChevronDown,
ChevronUp, ChevronUp,
Logs, Logs,
RefreshCw,
MoreVertical, MoreVertical,
Trash2,
Cog, Cog,
CirclePower,
Power, Power,
} from "lucide-react"; } from "lucide-react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";

View File

@@ -104,7 +104,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
providerSettings: { providerSettings: {
...settings?.providerSettings, ...settings?.providerSettings,
[provider]: { [provider]: {
...(settings?.providerSettings?.[provider] || {}), ...settings?.providerSettings?.[provider],
apiKey: { apiKey: {
value: apiKeyInput, value: apiKeyInput,
}, },
@@ -134,7 +134,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
providerSettings: { providerSettings: {
...settings?.providerSettings, ...settings?.providerSettings,
[provider]: { [provider]: {
...(settings?.providerSettings?.[provider] || {}), ...settings?.providerSettings?.[provider],
apiKey: undefined, apiKey: undefined,
}, },
}, },

View File

@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority"; import { type VariantProps, cva } from "class-variance-authority";
import { Menu, PanelLeftIcon } from "lucide-react"; import { Menu } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -224,7 +224,6 @@ function Sidebar({
} }
function SidebarTrigger({ function SidebarTrigger({
className,
onClick, onClick,
...props ...props
}: React.ComponentProps<typeof Button>) { }: React.ComponentProps<typeof Button>) {

View File

@@ -9,7 +9,7 @@ export function useAppVersion() {
try { try {
const version = await IpcClient.getInstance().getAppVersion(); const version = await IpcClient.getInstance().getAppVersion();
setAppVersion(version); setAppVersion(version);
} catch (error) { } catch {
setAppVersion(null); setAppVersion(null);
} }
}; };

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import type { App } from "@/ipc/ipc_types";
import { atom, useAtom } from "jotai"; import { useAtom } from "jotai";
import { currentAppAtom } from "@/atoms/appAtoms"; import { currentAppAtom } from "@/atoms/appAtoms";
export function useLoadApp(appId: number | null) { export function useLoadApp(appId: number | null) {

View File

@@ -5,7 +5,7 @@ import { IpcClient } from "@/ipc/ipc_client";
export function useLoadApps() { export function useLoadApps() {
const [apps, setApps] = useAtom(appsListAtom); const [apps, setApps] = useAtom(appsListAtom);
const [appBasePath, setAppBasePath] = useAtom(appBasePathAtom); const [, setAppBasePath] = useAtom(appBasePathAtom);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import type { CodeProposal, ProposalResult } from "@/lib/schemas"; // Import Proposal type import type { ProposalResult } from "@/lib/schemas"; // Import Proposal type
import { proposalResultAtom } from "@/atoms/proposalAtoms"; import { proposalResultAtom } from "@/atoms/proposalAtoms";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
export function useProposal(chatId?: number | undefined) { export function useProposal(chatId?: number | undefined) {

View File

@@ -9,7 +9,6 @@ import {
selectedAppIdAtom, selectedAppIdAtom,
} from "@/atoms/appAtoms"; } from "@/atoms/appAtoms";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { App } from "@/ipc/ipc_types";
export function useRunApp() { export function useRunApp() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { atom, useAtom } from "jotai"; import { useAtom } from "jotai";
import { userSettingsAtom, envVarsAtom } from "@/atoms/appAtoms"; import { userSettingsAtom, envVarsAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { cloudProviders, type UserSettings } from "@/lib/schemas"; import { cloudProviders, type UserSettings } from "@/lib/schemas";
@@ -11,9 +11,6 @@ const PROVIDER_TO_ENV_VAR: Record<string, string> = {
google: "GEMINI_API_KEY", google: "GEMINI_API_KEY",
}; };
// Define a type for the environment variables we expect
type EnvVars = Record<string, string | undefined>;
const TELEMETRY_CONSENT_KEY = "dyadTelemetryConsent"; const TELEMETRY_CONSENT_KEY = "dyadTelemetryConsent";
const TELEMETRY_USER_ID_KEY = "dyadTelemetryUserId"; const TELEMETRY_USER_ID_KEY = "dyadTelemetryUserId";

View File

@@ -1,4 +1,4 @@
import { useCallback, useState } from "react"; import { useCallback } from "react";
import type { Message } from "@/ipc/ipc_types"; import type { Message } from "@/ipc/ipc_types";
import { useAtom, useSetAtom } from "jotai"; import { useAtom, useSetAtom } from "jotai";
import { import {
@@ -14,7 +14,7 @@ import { useChats } from "./useChats";
import { useLoadApp } from "./useLoadApp"; import { useLoadApp } from "./useLoadApp";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useVersions } from "./useVersions"; import { useVersions } from "./useVersions";
import { showError, showUncommittedFilesWarning } from "@/lib/toast"; import { showUncommittedFilesWarning } from "@/lib/toast";
import { useProposal } from "./useProposal"; import { useProposal } from "./useProposal";
import { useSearch } from "@tanstack/react-router"; import { useSearch } from "@tanstack/react-router";
import { useRunApp } from "./useRunApp"; import { useRunApp } from "./useRunApp";
@@ -27,7 +27,7 @@ export function getRandomNumberId() {
export function useStreamChat({ export function useStreamChat({
hasChatId = true, hasChatId = true,
}: { hasChatId?: boolean } = {}) { }: { hasChatId?: boolean } = {}) {
const [messages, setMessages] = useAtom(chatMessagesAtom); const [, setMessages] = useAtom(chatMessagesAtom);
const [isStreaming, setIsStreaming] = useAtom(isStreamingAtom); const [isStreaming, setIsStreaming] = useAtom(isStreamingAtom);
const [error, setError] = useAtom(chatErrorAtom); const [error, setError] = useAtom(chatErrorAtom);
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom); const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);

View File

@@ -10,7 +10,7 @@ export function useVersions(appId: number | null) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
const selectedChatId = useAtomValue(selectedChatIdAtom); const selectedChatId = useAtomValue(selectedChatIdAtom);
const [messages, setMessages] = useAtom(chatMessagesAtom); const [, setMessages] = useAtom(chatMessagesAtom);
useEffect(() => { useEffect(() => {
const loadVersions = async () => { const loadVersions = async () => {
// If no app is selected, clear versions and return // If no app is selected, clear versions and return

View File

@@ -1,20 +1,15 @@
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import { db, getDatabasePath } from "../../db"; import { db, getDatabasePath } from "../../db";
import { apps, chats, messages } from "../../db/schema"; import { apps, chats } from "../../db/schema";
import { desc, eq, and, gte, sql, gt } from "drizzle-orm"; import { desc, eq } from "drizzle-orm";
import type { import type { App, CreateAppParams } from "../ipc_types";
App,
CreateAppParams,
SandboxConfig,
Version,
} from "../ipc_types";
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { getDyadAppPath, getUserDataPath } from "../../paths/paths"; import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import git from "isomorphic-git"; import git from "isomorphic-git";
import { promises as fsPromises } from "node:fs"; import { promises as fsPromises } from "node:fs";
import { extractCodebase } from "../../utils/codebase";
// Import our utility modules // Import our utility modules
import { withLock } from "../utils/lock_utils"; import { withLock } from "../utils/lock_utils";
import { import {
@@ -26,19 +21,17 @@ import {
processCounter, processCounter,
killProcess, killProcess,
removeAppIfCurrentProcess, removeAppIfCurrentProcess,
RunningAppInfo,
} from "../utils/process_manager"; } from "../utils/process_manager";
import { ALLOWED_ENV_VARS } from "../../constants/models"; import { ALLOWED_ENV_VARS } from "../../constants/models";
import { getEnvVar } from "../utils/read_env"; import { getEnvVar } from "../utils/read_env";
import { readSettings } from "../../main/settings"; import { readSettings } from "../../main/settings";
import { Worker } from "worker_threads";
import fixPath from "fix-path"; import fixPath from "fix-path";
import { getGitAuthor } from "../utils/git_author"; import { getGitAuthor } from "../utils/git_author";
import killPort from "kill-port"; import killPort from "kill-port";
import util from "util"; import util from "util";
import log from "electron-log"; import log from "electron-log";
import { getSupabaseProjectName } from "../../supabase_admin/supabase_management_client"; import { getSupabaseProjectName } from "../../supabase_admin/supabase_management_client";
import { settings } from "happy-dom/lib/PropertySymbol.js";
const logger = log.scope("app_handlers"); const logger = log.scope("app_handlers");
@@ -138,7 +131,7 @@ async function executeAppLocalNode({
async function killProcessOnPort(port: number): Promise<void> { async function killProcessOnPort(port: number): Promise<void> {
try { try {
await killPort(port, "tcp"); await killPort(port, "tcp");
} catch (err) { } catch {
// Ignore if nothing was running on that port // Ignore if nothing was running on that port
} }
} }

View File

@@ -5,7 +5,7 @@ import { desc, eq } from "drizzle-orm";
import type { ChatSummary } from "../../lib/schemas"; import type { ChatSummary } from "../../lib/schemas";
import * as git from "isomorphic-git"; import * as git from "isomorphic-git";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path";
import log from "electron-log"; import log from "electron-log";
import { getDyadAppPath } from "../../paths/paths"; import { getDyadAppPath } from "../../paths/paths";

View File

@@ -26,7 +26,7 @@ import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import * as os from "os"; import * as os from "os";
import * as crypto from "crypto"; import * as crypto from "crypto";
import { stat, readFile, writeFile, mkdir, unlink } from "fs/promises"; import { readFile, writeFile, unlink } from "fs/promises";
const logger = log.scope("chat_stream_handlers"); const logger = log.scope("chat_stream_handlers");
@@ -61,19 +61,6 @@ if (!fs.existsSync(TEMP_DIR)) {
fs.mkdirSync(TEMP_DIR, { recursive: true }); fs.mkdirSync(TEMP_DIR, { recursive: true });
} }
// First, define the proper content types to match ai SDK
type TextContent = {
type: "text";
text: string;
};
type ImageContent = {
type: "image";
image: Buffer;
};
type MessageContent = TextContent | ImageContent;
export function registerChatStreamHandlers() { export function registerChatStreamHandlers() {
ipcMain.handle("chat:stream", async (event, req: ChatStreamParams) => { ipcMain.handle("chat:stream", async (event, req: ChatStreamParams) => {
try { try {

View File

@@ -1,8 +1,8 @@
import { ipcMain, app } from "electron"; import { ipcMain } from "electron";
import { platform, arch } from "os"; import { platform, arch } from "os";
import { SystemDebugInfo, ChatLogsData } from "../ipc_types"; import { SystemDebugInfo, ChatLogsData } from "../ipc_types";
import { readSettings } from "../../main/settings"; import { readSettings } from "../../main/settings";
import { execSync } from "child_process";
import log from "electron-log"; import log from "electron-log";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";

View File

@@ -1,15 +1,10 @@
import { import { ipcMain, BrowserWindow, IpcMainInvokeEvent } from "electron";
ipcMain,
IpcMainEvent,
BrowserWindow,
IpcMainInvokeEvent,
} from "electron";
import fetch from "node-fetch"; // Use node-fetch for making HTTP requests in main process import fetch from "node-fetch"; // Use node-fetch for making HTTP requests in main process
import { writeSettings, readSettings } from "../../main/settings"; import { writeSettings, readSettings } from "../../main/settings";
import { updateAppGithubRepo } from "../../db/index"; import { updateAppGithubRepo } from "../../db/index";
import git from "isomorphic-git"; import git from "isomorphic-git";
import http from "isomorphic-git/http/node"; import http from "isomorphic-git/http/node";
import path from "node:path";
import fs from "node:fs"; import fs from "node:fs";
import { getDyadAppPath } from "../../paths/paths"; import { getDyadAppPath } from "../../paths/paths";
import { db } from "../../db"; import { db } from "../../db";

View File

@@ -37,7 +37,7 @@ export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
logger.info(`Successfully fetched ${models.length} models from LM Studio`); logger.info(`Successfully fetched ${models.length} models from LM Studio`);
return { models, error: null }; return { models, error: null };
} catch (error) { } catch {
return { models: [], error: "Failed to fetch models from LM Studio" }; return { models: [], error: "Failed to fetch models from LM Studio" };
} }
} }

View File

@@ -1,5 +1,5 @@
import { ipcMain, app } from "electron"; import { ipcMain } from "electron";
import { exec, execSync } from "child_process"; import { execSync } from "child_process";
import { platform, arch } from "os"; import { platform, arch } from "os";
import { NodeSystemInfo } from "../ipc_types"; import { NodeSystemInfo } from "../ipc_types";
import fixPath from "fix-path"; import fixPath from "fix-path";

View File

@@ -1,14 +1,12 @@
import { ipcMain, type IpcMainInvokeEvent } from "electron"; import { ipcMain, type IpcMainInvokeEvent } from "electron";
import type { import type {
CodeProposal, CodeProposal,
FileChange,
ProposalResult, ProposalResult,
SqlQuery,
ActionProposal, ActionProposal,
} from "../../lib/schemas"; } from "../../lib/schemas";
import { db } from "../../db"; import { db } from "../../db";
import { messages, chats } from "../../db/schema"; import { messages, chats } from "../../db/schema";
import { desc, eq, and, Update } from "drizzle-orm"; import { desc, eq, and } from "drizzle-orm";
import path from "node:path"; // Import path for basename import path from "node:path"; // Import path for basename
// Import tag parsers // Import tag parsers
import { import {
@@ -31,26 +29,9 @@ import {
import { extractCodebase } from "../../utils/codebase"; import { extractCodebase } from "../../utils/codebase";
import { getDyadAppPath } from "../../paths/paths"; import { getDyadAppPath } from "../../paths/paths";
import { withLock } from "../utils/lock_utils"; import { withLock } from "../utils/lock_utils";
const logger = log.scope("proposal_handlers"); const logger = log.scope("proposal_handlers");
// Placeholder Proposal data (can be removed or kept for reference)
// const placeholderProposal: Proposal = { ... };
// Type guard for the parsed proposal structure
interface ParsedProposal {
title: string;
files: string[];
}
function isParsedProposal(obj: any): obj is ParsedProposal {
return (
obj &&
typeof obj === "object" &&
typeof obj.title === "string" &&
Array.isArray(obj.files) &&
obj.files.every((file: any) => typeof file === "string")
);
}
// Cache for codebase token counts // Cache for codebase token counts
interface CodebaseTokenCache { interface CodebaseTokenCache {
chatId: number; chatId: number;

View File

@@ -5,28 +5,7 @@ import { readSettings } from "../../main/settings";
export function registerSettingsHandlers() { export function registerSettingsHandlers() {
ipcMain.handle("get-user-settings", async () => { ipcMain.handle("get-user-settings", async () => {
const settings = await readSettings(); const settings = readSettings();
// Mask API keys before sending to renderer
if (settings?.providerSettings) {
// Use optional chaining
for (const providerKey in settings.providerSettings) {
// Ensure the key is own property and providerSetting exists
if (
Object.prototype.hasOwnProperty.call(
settings.providerSettings,
providerKey,
)
) {
const providerSetting = settings.providerSettings[providerKey];
// Check if apiKey exists and is a non-empty string before masking
if (providerSetting?.apiKey?.value) {
providerSetting.apiKey = providerSetting.apiKey;
}
}
}
}
return settings; return settings;
}); });

View File

@@ -1,6 +1,6 @@
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import { db } from "../../db"; import { db } from "../../db";
import { chats, messages } from "../../db/schema"; import { chats } from "../../db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { SYSTEM_PROMPT } from "../../prompts/system_prompt"; import { SYSTEM_PROMPT } from "../../prompts/system_prompt";
import { import {
@@ -11,8 +11,7 @@ import { getDyadAppPath } from "../../paths/paths";
import log from "electron-log"; import log from "electron-log";
import { extractCodebase } from "../../utils/codebase"; import { extractCodebase } from "../../utils/codebase";
import { getSupabaseContext } from "../../supabase_admin/supabase_context"; import { getSupabaseContext } from "../../supabase_admin/supabase_context";
import { readSettings } from "../../main/settings";
import { MODEL_OPTIONS } from "../../constants/models";
import { TokenCountParams } from "../ipc_types"; import { TokenCountParams } from "../ipc_types";
import { TokenCountResult } from "../ipc_types"; import { TokenCountResult } from "../ipc_types";
import { estimateTokens, getContextWindow } from "../utils/token_utils"; import { estimateTokens, getContextWindow } from "../utils/token_utils";

View File

@@ -133,12 +133,7 @@ export function registerVersionHandlers() {
}); });
// Process each file to revert to the state in previousVersionId // Process each file to revert to the state in previousVersionId
for (const [ for (const [filepath, headStatus, workdirStatus] of matrix) {
filepath,
headStatus,
workdirStatus,
stageStatus,
] of matrix) {
const fullPath = path.join(appPath, filepath); const fullPath = path.join(appPath, filepath);
// If file exists in HEAD (previous version) // If file exists in HEAD (previous version)

View File

@@ -9,7 +9,6 @@ import type {
AppOutput, AppOutput,
Chat, Chat,
ChatResponseEnd, ChatResponseEnd,
ChatStreamParams,
CreateAppParams, CreateAppParams,
CreateAppResult, CreateAppResult,
ListAppsResponse, ListAppsResponse,
@@ -18,13 +17,12 @@ import type {
Version, Version,
SystemDebugInfo, SystemDebugInfo,
LocalModel, LocalModel,
LocalModelListResponse,
TokenCountParams, TokenCountParams,
TokenCountResult, TokenCountResult,
ChatLogsData, ChatLogsData,
BranchResult, BranchResult,
} from "./ipc_types"; } from "./ipc_types";
import type { CodeProposal, ProposalResult } from "@/lib/schemas"; import type { ProposalResult } from "@/lib/schemas";
import { showError } from "@/lib/toast"; import { showError } from "@/lib/toast";
export interface ChatStreamCallbacks { export interface ChatStreamCallbacks {

View File

@@ -5,7 +5,7 @@ import fs from "node:fs";
import { getDyadAppPath } from "../../paths/paths"; import { getDyadAppPath } from "../../paths/paths";
import path from "node:path"; import path from "node:path";
import git from "isomorphic-git"; import git from "isomorphic-git";
import { getGithubUser } from "../handlers/github_handlers";
import { getGitAuthor } from "../utils/git_author"; import { getGitAuthor } from "../utils/git_author";
import log from "electron-log"; import log from "electron-log";
import { executeAddDependency } from "./executeAddDependency"; import { executeAddDependency } from "./executeAddDependency";
@@ -229,7 +229,7 @@ export async function processFullResponseActions(
if (dyadExecuteSqlQueries.length > 0) { if (dyadExecuteSqlQueries.length > 0) {
for (const query of dyadExecuteSqlQueries) { for (const query of dyadExecuteSqlQueries) {
try { try {
const result = await executeSupabaseSql({ await executeSupabaseSql({
supabaseProjectId: chatWithApp.app.supabaseProjectId!, supabaseProjectId: chatWithApp.app.supabaseProjectId!,
query: query.content, query: query.content,
}); });

View File

@@ -1,4 +1,4 @@
import { createOpenAI, OpenAIProvider } from "@ai-sdk/openai"; import { createOpenAI } from "@ai-sdk/openai";
import { createGoogleGenerativeAI as createGoogle } from "@ai-sdk/google"; import { createGoogleGenerativeAI as createGoogle } from "@ai-sdk/google";
import { createAnthropic } from "@ai-sdk/anthropic"; import { createAnthropic } from "@ai-sdk/anthropic";
import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { createOpenRouter } from "@openrouter/ai-sdk-provider";

View File

@@ -39,7 +39,7 @@ export async function withLock<T>(
} }
// Acquire a new lock // Acquire a new lock
const { release, promise } = acquireLock(lockId); const { release } = acquireLock(lockId);
try { try {
const result = await fn(); const result = await fn();

View File

@@ -1,4 +1,3 @@
import type { Message } from "ai";
import { IpcClient } from "../ipc/ipc_client"; import { IpcClient } from "../ipc/ipc_client";
import type { ChatSummary } from "./schemas"; import type { ChatSummary } from "./schemas";
import type { CreateAppParams, CreateAppResult } from "../ipc/ipc_types"; import type { CreateAppParams, CreateAppResult } from "../ipc/ipc_types";

View File

@@ -53,7 +53,7 @@ export const showLoading = <T>(
) => { ) => {
return toast.promise(promise, { return toast.promise(promise, {
loading: loadingMessage, loading: loadingMessage,
success: (data) => successMessage || "Operation completed successfully", success: () => successMessage || "Operation completed successfully",
error: (err) => errorMessage || `Error: ${err.message || "Unknown error"}`, error: (err) => errorMessage || `Error: ${err.message || "Unknown error"}`,
}); });
}; };

View File

@@ -129,7 +129,7 @@ const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) { if (!gotTheLock) {
app.quit(); app.quit();
} else { } else {
app.on("second-instance", (event, commandLine, workingDirectory) => { app.on("second-instance", (_event, commandLine, _workingDirectory) => {
// Someone tried to run a second instance, we should focus our window. // Someone tried to run a second instance, we should focus our window.
if (mainWindow) { if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore(); if (mainWindow.isMinimized()) mainWindow.restore();

View File

@@ -3,15 +3,13 @@ import { useAtom, useAtomValue } from "jotai";
import { appBasePathAtom, appsListAtom } from "@/atoms/appAtoms"; import { appBasePathAtom, appsListAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { useLoadApps } from "@/hooks/useLoadApps"; import { useLoadApps } from "@/hooks/useLoadApps";
import { useState, useEffect } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
ArrowLeft, ArrowLeft,
MoreVertical, MoreVertical,
ArrowRight,
MessageCircle, MessageCircle,
Pencil, Pencil,
Github,
Folder, Folder,
} from "lucide-react"; } from "lucide-react";
import { import {
@@ -30,7 +28,6 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { GitHubConnector } from "@/components/GitHubConnector"; import { GitHubConnector } from "@/components/GitHubConnector";
import { SupabaseConnector } from "@/components/SupabaseConnector"; import { SupabaseConnector } from "@/components/SupabaseConnector";
import { useSettings } from "@/hooks/useSettings";
export default function AppDetailsPage() { export default function AppDetailsPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -50,7 +47,7 @@ export default function AppDetailsPage() {
const [newFolderName, setNewFolderName] = useState(""); const [newFolderName, setNewFolderName] = useState("");
const [isRenamingFolder, setIsRenamingFolder] = useState(false); const [isRenamingFolder, setIsRenamingFolder] = useState(false);
const appBasePath = useAtomValue(appBasePathAtom); const appBasePath = useAtomValue(appBasePathAtom);
const { settings } = useSettings();
// Get the appId from search params and find the corresponding app // Get the appId from search params and find the corresponding app
const appId = search.appId ? Number(search.appId) : null; const appId = search.appId ? Number(search.appId) : null;
const selectedApp = appId ? appsList.find((app) => app.id === appId) : null; const selectedApp = appId ? appsList.find((app) => app.id === appId) : null;

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { useTheme } from "../contexts/ThemeContext"; import { useTheme } from "../contexts/ThemeContext";
import { ProviderSettingsGrid } from "@/components/ProviderSettings"; import { ProviderSettingsGrid } from "@/components/ProviderSettings";
import ConfirmationDialog from "@/components/ConfirmationDialog"; import ConfirmationDialog from "@/components/ConfirmationDialog";
@@ -113,7 +113,7 @@ export default function SettingsPage() {
</div> </div>
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm"> <div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
<ProviderSettingsGrid configuredProviders={[]} /> <ProviderSettingsGrid />
</div> </div>
<div className="space-y-6"> <div className="space-y-6">

View File

@@ -33,7 +33,7 @@ export function getElectron(): typeof import("electron") | undefined {
if (process.versions.electron) { if (process.versions.electron) {
electron = require("electron"); electron = require("electron");
} }
} catch (e) { } catch {
// Not in Electron environment // Not in Electron environment
} }
return electron; return electron;

View File

@@ -226,7 +226,7 @@ async function safeParseErrorResponseBody(
) { ) {
return { message: body.message }; return { message: body.message };
} }
} catch (error) { } catch {
return; return;
} }
} }

View File

@@ -70,7 +70,7 @@ async function isGitIgnored(
gitIgnoreMtimes.set(rootGitIgnorePath, stats.mtimeMs); gitIgnoreMtimes.set(rootGitIgnorePath, stats.mtimeMs);
shouldClearCache = true; shouldClearCache = true;
} }
} catch (error) { } catch {
// Root .gitignore might not exist, which is fine // Root .gitignore might not exist, which is fine
} }
@@ -86,7 +86,7 @@ async function isGitIgnored(
gitIgnoreMtimes.set(gitIgnorePath, stats.mtimeMs); gitIgnoreMtimes.set(gitIgnorePath, stats.mtimeMs);
shouldClearCache = true; shouldClearCache = true;
} }
} catch (error) { } catch {
// This directory might not have a .gitignore, which is fine // This directory might not have a .gitignore, which is fine
} }
} }
@@ -324,41 +324,3 @@ async function sortFilesByModificationTime(files: string[]): Promise<string[]> {
// Sort by modification time (oldest first) // Sort by modification time (oldest first)
return fileStats.sort((a, b) => a.mtime - b.mtime).map((item) => item.file); return fileStats.sort((a, b) => a.mtime - b.mtime).map((item) => item.file);
} }
/**
* Sort files by their importance for context
*/
function sortFilesByImportance(files: string[], baseDir: string): string[] {
// Define patterns for important files
const highPriorityPatterns = [
new RegExp(`(^|/)${ALWAYS_INCLUDE_FILES[0]}$`),
/tsconfig\.json$/,
/README\.md$/,
/index\.(ts|js)x?$/,
/main\.(ts|js)x?$/,
/app\.(ts|js)x?$/,
];
// Custom sorting function
return [...files].sort((a, b) => {
const relativeA = path.relative(baseDir, a);
const relativeB = path.relative(baseDir, b);
// Check if file A matches any high priority pattern
const aIsHighPriority = highPriorityPatterns.some((pattern) =>
pattern.test(relativeA),
);
// Check if file B matches any high priority pattern
const bIsHighPriority = highPriorityPatterns.some((pattern) =>
pattern.test(relativeB),
);
// Sort by priority first
if (aIsHighPriority && !bIsHighPriority) return -1;
if (!aIsHighPriority && bIsHighPriority) return 1;
// If both are same priority, sort alphabetically
return relativeA.localeCompare(relativeB);
});
}