Disable auto-update setting & settings page has scroll shortcuts (#590)
Fixes https://github.com/dyad-sh/dyad/issues/561
This commit is contained in:
15
e2e-tests/auto_update.spec.ts
Normal file
15
e2e-tests/auto_update.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { expect } from "@playwright/test";
|
||||||
|
import { test } from "./helpers/test_helper";
|
||||||
|
|
||||||
|
test("auto update - disable and enable", async ({ po }) => {
|
||||||
|
await po.goToSettingsTab();
|
||||||
|
|
||||||
|
await po.toggleAutoUpdate();
|
||||||
|
await expect(
|
||||||
|
po.page.getByRole("button", { name: "Restart Dyad" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await po.snapshotSettings();
|
||||||
|
|
||||||
|
await po.toggleAutoUpdate();
|
||||||
|
await po.snapshotSettings();
|
||||||
|
});
|
||||||
@@ -833,6 +833,10 @@ export class PageObject {
|
|||||||
expect(sanitizedSettingsContent).toMatchSnapshot();
|
expect(sanitizedSettingsContent).toMatchSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleAutoUpdate() {
|
||||||
|
await this.page.getByRole("switch", { name: "Auto-update" }).click();
|
||||||
|
}
|
||||||
|
|
||||||
async clickTelemetryAccept() {
|
async clickTelemetryAccept() {
|
||||||
await this.page.getByTestId("telemetry-accept-button").click();
|
await this.page.getByTestId("telemetry-accept-button").click();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"selectedModel": {
|
||||||
|
"name": "auto",
|
||||||
|
"provider": "auto"
|
||||||
|
},
|
||||||
|
"providerSettings": {},
|
||||||
|
"telemetryConsent": "unset",
|
||||||
|
"telemetryUserId": "[UUID]",
|
||||||
|
"hasRunBefore": true,
|
||||||
|
"experiments": {},
|
||||||
|
"lastShownReleaseNotesVersion": "[scrubbed]",
|
||||||
|
"enableProLazyEditsMode": true,
|
||||||
|
"enableProSmartFilesContextMode": true,
|
||||||
|
"selectedChatMode": "build",
|
||||||
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": false,
|
||||||
|
"isTestMode": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"selectedModel": {
|
||||||
|
"name": "auto",
|
||||||
|
"provider": "auto"
|
||||||
|
},
|
||||||
|
"providerSettings": {},
|
||||||
|
"telemetryConsent": "unset",
|
||||||
|
"telemetryUserId": "[UUID]",
|
||||||
|
"hasRunBefore": true,
|
||||||
|
"experiments": {},
|
||||||
|
"lastShownReleaseNotesVersion": "[scrubbed]",
|
||||||
|
"enableProLazyEditsMode": true,
|
||||||
|
"enableProSmartFilesContextMode": true,
|
||||||
|
"selectedChatMode": "build",
|
||||||
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
|
"isTestMode": true
|
||||||
|
}
|
||||||
@@ -15,5 +15,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -13,5 +13,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -13,5 +13,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -13,5 +13,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,6 @@
|
|||||||
"enableProSmartFilesContextMode": true,
|
"enableProSmartFilesContextMode": true,
|
||||||
"selectedChatMode": "build",
|
"selectedChatMode": "build",
|
||||||
"enableAutoFixProblems": false,
|
"enableAutoFixProblems": false,
|
||||||
|
"enableAutoUpdate": true,
|
||||||
"isTestMode": true
|
"isTestMode": true
|
||||||
}
|
}
|
||||||
77
package-lock.json
generated
77
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "dyad",
|
"name": "dyad",
|
||||||
"version": "0.8.0",
|
"version": "0.11.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dyad",
|
"name": "dyad",
|
||||||
"version": "0.8.0",
|
"version": "0.11.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "^1.2.8",
|
"@ai-sdk/anthropic": "^1.2.8",
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||||
"@radix-ui/react-label": "^2.1.4",
|
"@radix-ui/react-label": "^2.1.4",
|
||||||
"@radix-ui/react-popover": "^1.1.7",
|
"@radix-ui/react-popover": "^1.1.7",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||||
"@radix-ui/react-select": "^2.2.2",
|
"@radix-ui/react-select": "^2.2.2",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.2",
|
||||||
@@ -4680,6 +4681,78 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area": {
|
||||||
|
"version": "1.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz",
|
||||||
|
"integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/number": "1.1.1",
|
||||||
|
"@radix-ui/primitive": "1.1.2",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-direction": "1.1.1",
|
||||||
|
"@radix-ui/react-presence": "1.1.4",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-select": {
|
"node_modules/@radix-ui/react-select": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.2.tgz",
|
||||||
|
|||||||
@@ -97,6 +97,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||||
"@radix-ui/react-label": "^2.1.4",
|
"@radix-ui/react-label": "^2.1.4",
|
||||||
"@radix-ui/react-popover": "^1.1.7",
|
"@radix-ui/react-popover": "^1.1.7",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||||
"@radix-ui/react-select": "^2.2.2",
|
"@radix-ui/react-select": "^2.2.2",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.2",
|
||||||
|
|||||||
36
src/components/AutoUpdateSwitch.tsx
Normal file
36
src/components/AutoUpdateSwitch.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useSettings } from "@/hooks/useSettings";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
|
|
||||||
|
export function AutoUpdateSwitch() {
|
||||||
|
const { settings, updateSettings } = useSettings();
|
||||||
|
|
||||||
|
if (!settings) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch
|
||||||
|
id="enable-auto-update"
|
||||||
|
checked={settings.enableAutoUpdate}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
updateSettings({ enableAutoUpdate: checked });
|
||||||
|
toast("Auto-update settings changed", {
|
||||||
|
description:
|
||||||
|
"You will need to restart Dyad for your settings to take effect.",
|
||||||
|
action: {
|
||||||
|
label: "Restart Dyad",
|
||||||
|
onClick: () => {
|
||||||
|
IpcClient.getInstance().restartDyad();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="enable-auto-update">Auto-update</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -68,7 +68,7 @@ export function ProviderSettingsGrid() {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h2 className="text-2xl font-bold mb-6">AI Providers</h2>
|
<h2 className="text-lg font-medium 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">
|
||||||
{[1, 2, 3, 4, 5].map((i) => (
|
{[1, 2, 3, 4, 5].map((i) => (
|
||||||
<Card key={i} className="border-border">
|
<Card key={i} className="border-border">
|
||||||
@@ -86,7 +86,7 @@ export function ProviderSettingsGrid() {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h2 className="text-2xl font-bold mb-6">AI Providers</h2>
|
<h2 className="text-lg font-medium mb-6">AI Providers</h2>
|
||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertTriangle className="h-4 w-4" />
|
<AlertTriangle className="h-4 w-4" />
|
||||||
<AlertTitle>Error</AlertTitle>
|
<AlertTitle>Error</AlertTitle>
|
||||||
@@ -100,7 +100,7 @@ export function ProviderSettingsGrid() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h2 className="text-2xl font-bold mb-6">AI Providers</h2>
|
<h2 className="text-lg font-medium 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">
|
||||||
{providers
|
{providers
|
||||||
?.filter((p) => p.type !== "local")
|
?.filter((p) => p.type !== "local")
|
||||||
@@ -116,7 +116,7 @@ export function ProviderSettingsGrid() {
|
|||||||
className="p-4 cursor-pointer"
|
className="p-4 cursor-pointer"
|
||||||
onClick={() => handleProviderClick(provider.id)}
|
onClick={() => handleProviderClick(provider.id)}
|
||||||
>
|
>
|
||||||
<CardTitle className="text-xl flex items-center justify-between">
|
<CardTitle className="text-lg font-medium flex items-center justify-between">
|
||||||
{provider.name}
|
{provider.name}
|
||||||
{isProviderSetup(provider.id) ? (
|
{isProviderSetup(provider.id) ? (
|
||||||
<span className="ml-3 text-sm font-medium text-green-500 bg-green-50 dark:bg-green-900/30 border border-green-500/50 dark:border-green-500/50 px-2 py-1 rounded-full">
|
<span className="ml-3 text-sm font-medium text-green-500 bg-green-50 dark:bg-green-900/30 border border-green-500/50 dark:border-green-500/50 px-2 py-1 rounded-full">
|
||||||
@@ -178,8 +178,8 @@ export function ProviderSettingsGrid() {
|
|||||||
onClick={() => setIsDialogOpen(true)}
|
onClick={() => setIsDialogOpen(true)}
|
||||||
>
|
>
|
||||||
<CardHeader className="p-4 flex flex-col items-center justify-center h-full">
|
<CardHeader className="p-4 flex flex-col items-center justify-center h-full">
|
||||||
<PlusIcon className="h-10 w-10 text-muted-foreground mb-2" />
|
<PlusIcon className="h-8 w-8 text-muted-foreground mb-2" />
|
||||||
<CardTitle className="text-xl text-center">
|
<CardTitle className="text-lg font-medium text-center">
|
||||||
Add custom provider
|
Add custom provider
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-center">
|
<CardDescription className="text-center">
|
||||||
|
|||||||
88
src/components/SettingsList.tsx
Normal file
88
src/components/SettingsList.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const SETTINGS_SECTIONS = [
|
||||||
|
{ id: "general-settings", label: "General" },
|
||||||
|
{ id: "workflow-settings", label: "Workflow" },
|
||||||
|
{ id: "ai-settings", label: "AI" },
|
||||||
|
{ id: "provider-settings", label: "Model Providers" },
|
||||||
|
{ id: "telemetry", label: "Telemetry" },
|
||||||
|
{ id: "integrations", label: "Integrations" },
|
||||||
|
{ id: "experiments", label: "Experiments" },
|
||||||
|
{ id: "danger-zone", label: "Danger Zone" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SettingsList({ show }: { show: boolean }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [activeSection, setActiveSection] = useState<string | null>(
|
||||||
|
"general-settings",
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setActiveSection(entry.target.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ rootMargin: "-20% 0px -80% 0px", threshold: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const section of SETTINGS_SECTIONS) {
|
||||||
|
const el = document.getElementById(section.id);
|
||||||
|
if (el) {
|
||||||
|
observer.observe(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScrollAndNavigateTo = async (id: string) => {
|
||||||
|
await navigate({
|
||||||
|
to: "/settings",
|
||||||
|
});
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
setActiveSection(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<div className="flex-shrink-0 p-4">
|
||||||
|
<h2 className="text-lg font-semibold tracking-tight">Settings</h2>
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="flex-grow">
|
||||||
|
<div className="space-y-1 p-4 pt-0">
|
||||||
|
{SETTINGS_SECTIONS.map((section) => (
|
||||||
|
<button
|
||||||
|
key={section.id}
|
||||||
|
onClick={() => handleScrollAndNavigateTo(section.id)}
|
||||||
|
className={cn(
|
||||||
|
"w-full text-left px-3 py-2 rounded-md text-sm transition-colors",
|
||||||
|
activeSection === section.id
|
||||||
|
? "bg-sidebar-accent text-sidebar-accent-foreground font-semibold"
|
||||||
|
: "hover:bg-sidebar-accent",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{section.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { ChatList } from "./ChatList";
|
import { ChatList } from "./ChatList";
|
||||||
import { AppList } from "./AppList";
|
import { AppList } from "./AppList";
|
||||||
import { HelpDialog } from "./HelpDialog"; // Import the new dialog
|
import { HelpDialog } from "./HelpDialog"; // Import the new dialog
|
||||||
|
import { SettingsList } from "./SettingsList";
|
||||||
|
|
||||||
// Menu items.
|
// Menu items.
|
||||||
const items = [
|
const items = [
|
||||||
@@ -49,6 +50,7 @@ const items = [
|
|||||||
type HoverState =
|
type HoverState =
|
||||||
| "start-hover:app"
|
| "start-hover:app"
|
||||||
| "start-hover:chat"
|
| "start-hover:chat"
|
||||||
|
| "start-hover:settings"
|
||||||
| "clear-hover"
|
| "clear-hover"
|
||||||
| "no-hover";
|
| "no-hover";
|
||||||
|
|
||||||
@@ -60,10 +62,7 @@ export function AppSidebar() {
|
|||||||
const [isDropdownOpen] = useAtom(dropdownOpenAtom);
|
const [isDropdownOpen] = useAtom(dropdownOpenAtom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (hoverState.startsWith("start-hover") && state === "collapsed") {
|
||||||
(hoverState === "start-hover:app" || hoverState === "start-hover:chat") &&
|
|
||||||
state === "collapsed"
|
|
||||||
) {
|
|
||||||
expandedByHover.current = true;
|
expandedByHover.current = true;
|
||||||
toggleSidebar();
|
toggleSidebar();
|
||||||
}
|
}
|
||||||
@@ -84,17 +83,22 @@ export function AppSidebar() {
|
|||||||
routerState.location.pathname === "/" ||
|
routerState.location.pathname === "/" ||
|
||||||
routerState.location.pathname.startsWith("/app-details");
|
routerState.location.pathname.startsWith("/app-details");
|
||||||
const isChatRoute = routerState.location.pathname === "/chat";
|
const isChatRoute = routerState.location.pathname === "/chat";
|
||||||
|
const isSettingsRoute = routerState.location.pathname.startsWith("/settings");
|
||||||
|
|
||||||
let selectedItem: string | null = null;
|
let selectedItem: string | null = null;
|
||||||
if (hoverState === "start-hover:app") {
|
if (hoverState === "start-hover:app") {
|
||||||
selectedItem = "Apps";
|
selectedItem = "Apps";
|
||||||
} else if (hoverState === "start-hover:chat") {
|
} else if (hoverState === "start-hover:chat") {
|
||||||
selectedItem = "Chat";
|
selectedItem = "Chat";
|
||||||
|
} else if (hoverState === "start-hover:settings") {
|
||||||
|
selectedItem = "Settings";
|
||||||
} else if (state === "expanded") {
|
} else if (state === "expanded") {
|
||||||
if (isAppRoute) {
|
if (isAppRoute) {
|
||||||
selectedItem = "Apps";
|
selectedItem = "Apps";
|
||||||
} else if (isChatRoute) {
|
} else if (isChatRoute) {
|
||||||
selectedItem = "Chat";
|
selectedItem = "Chat";
|
||||||
|
} else if (isSettingsRoute) {
|
||||||
|
selectedItem = "Settings";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +126,7 @@ export function AppSidebar() {
|
|||||||
<div className="w-[240px]">
|
<div className="w-[240px]">
|
||||||
<AppList show={selectedItem === "Apps"} />
|
<AppList show={selectedItem === "Apps"} />
|
||||||
<ChatList show={selectedItem === "Chat"} />
|
<ChatList show={selectedItem === "Chat"} />
|
||||||
|
<SettingsList show={selectedItem === "Settings"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
@@ -188,6 +193,8 @@ function AppIcons({
|
|||||||
onHoverChange("start-hover:app");
|
onHoverChange("start-hover:app");
|
||||||
} else if (item.title === "Chat") {
|
} else if (item.title === "Chat") {
|
||||||
onHoverChange("start-hover:chat");
|
onHoverChange("start-hover:chat");
|
||||||
|
} else if (item.title === "Settings") {
|
||||||
|
onHoverChange("start-hover:settings");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
46
src/components/ui/scroll-area.tsx
Normal file
46
src/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const ScrollArea = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn("relative overflow-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
));
|
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
const ScrollBar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
ref={ref}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"flex touch-none select-none transition-colors",
|
||||||
|
orientation === "vertical" &&
|
||||||
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
|
orientation === "horizontal" &&
|
||||||
|
"h-2.5 border-t border-t-transparent p-[1px]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
));
|
||||||
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ipcMain } from "electron";
|
import { ipcMain, app } from "electron";
|
||||||
import { db, getDatabasePath } from "../../db";
|
import { db, getDatabasePath } from "../../db";
|
||||||
import { apps, chats } from "../../db/schema";
|
import { apps, chats } from "../../db/schema";
|
||||||
import { desc, eq } from "drizzle-orm";
|
import { desc, eq } from "drizzle-orm";
|
||||||
@@ -186,6 +186,11 @@ async function killProcessOnPort(port: number): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function registerAppHandlers() {
|
export function registerAppHandlers() {
|
||||||
|
handle("restart-dyad", async () => {
|
||||||
|
app.relaunch();
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
handle(
|
handle(
|
||||||
"create-app",
|
"create-app",
|
||||||
async (
|
async (
|
||||||
|
|||||||
@@ -165,6 +165,10 @@ export class IpcClient {
|
|||||||
return IpcClient.instance;
|
return IpcClient.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async restartDyad(): Promise<void> {
|
||||||
|
await this.ipcRenderer.invoke("restart-dyad");
|
||||||
|
}
|
||||||
|
|
||||||
public async reloadEnvPath(): Promise<void> {
|
public async reloadEnvPath(): Promise<void> {
|
||||||
await this.ipcRenderer.invoke("reload-env-path");
|
await this.ipcRenderer.invoke("reload-env-path");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ export const UserSettingsSchema = z.object({
|
|||||||
|
|
||||||
enableAutoFixProblems: z.boolean().optional(),
|
enableAutoFixProblems: z.boolean().optional(),
|
||||||
enableNativeGit: z.boolean().optional(),
|
enableNativeGit: z.boolean().optional(),
|
||||||
|
enableAutoUpdate: z.boolean(),
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// E2E TESTING ONLY.
|
// E2E TESTING ONLY.
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ log.scope.labelPadding = false;
|
|||||||
|
|
||||||
const logger = log.scope("main");
|
const logger = log.scope("main");
|
||||||
|
|
||||||
updateElectronApp(); // additional configuration options available
|
// Check settings before enabling auto-update
|
||||||
|
const settings = readSettings();
|
||||||
|
if (settings.enableAutoUpdate) {
|
||||||
|
updateElectronApp({ logger }); // additional configuration options available
|
||||||
|
}
|
||||||
|
|
||||||
// Load environment variables from .env file
|
// Load environment variables from .env file
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const DEFAULT_SETTINGS: UserSettings = {
|
|||||||
enableProSmartFilesContextMode: true,
|
enableProSmartFilesContextMode: true,
|
||||||
selectedChatMode: "build",
|
selectedChatMode: "build",
|
||||||
enableAutoFixProblems: false,
|
enableAutoFixProblems: false,
|
||||||
|
enableAutoUpdate: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SETTINGS_FILE = "user-settings.json";
|
const SETTINGS_FILE = "user-settings.json";
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import { SupabaseIntegration } from "@/components/SupabaseIntegration";
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
|
import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
|
||||||
|
import { AutoUpdateSwitch } from "@/components/AutoUpdateSwitch";
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const { theme, setTheme } = useTheme();
|
|
||||||
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
|
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
|
||||||
const [isResetting, setIsResetting] = useState(false);
|
const [isResetting, setIsResetting] = useState(false);
|
||||||
const appVersion = useAppVersion();
|
const appVersion = useAppVersion();
|
||||||
@@ -60,108 +60,25 @@ export default function SettingsPage() {
|
|||||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
Settings
|
Settings
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* App Version Section */}
|
|
||||||
<div className="flex items-center text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
<span className="mr-2 font-medium">App Version:</span>
|
|
||||||
<span className="bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded text-gray-800 dark:text-gray-200 font-mono">
|
|
||||||
{appVersion ? appVersion : "-"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6">
|
<GeneralSettings appVersion={appVersion} />
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
<WorkflowSettings />
|
||||||
General Settings
|
<AISettings />
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="space-y-4 mb-4">
|
<div
|
||||||
<div className="flex items-center gap-4">
|
id="provider-settings"
|
||||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm"
|
||||||
Theme
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div className="relative bg-gray-100 dark:bg-gray-700 rounded-lg p-1 flex">
|
|
||||||
{(["system", "light", "dark"] as const).map((option) => (
|
|
||||||
<button
|
|
||||||
key={option}
|
|
||||||
onClick={() => setTheme(option)}
|
|
||||||
className={`
|
|
||||||
px-4 py-1.5 text-sm font-medium rounded-md
|
|
||||||
transition-all duration-200
|
|
||||||
${
|
|
||||||
theme === option
|
|
||||||
? "bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm"
|
|
||||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
{option.charAt(0).toUpperCase() + option.slice(1)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<AutoApproveSwitch showToast={false} />
|
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
This will automatically approve code changes and run them.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1 mt-4">
|
|
||||||
<AutoFixProblemsSwitch />
|
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
This will automatically fix TypeScript errors.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1 mt-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Switch
|
|
||||||
id="enable-native-git"
|
|
||||||
checked={!!settings?.enableNativeGit}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
updateSettings({
|
|
||||||
enableNativeGit: checked,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="enable-native-git">Enable Native Git</Label>
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
(Experimental) Native Git offers faster performance but requires{" "}
|
|
||||||
<a
|
|
||||||
onClick={() => {
|
|
||||||
IpcClient.getInstance().openExternalUrl(
|
|
||||||
"https://git-scm.com/downloads",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className="text-blue-600 hover:underline dark:text-blue-400"
|
|
||||||
>
|
|
||||||
installing Git
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4">
|
|
||||||
<ThinkingBudgetSelector />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4">
|
|
||||||
<MaxChatTurnsSelector />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
|
|
||||||
<ProviderSettingsGrid />
|
<ProviderSettingsGrid />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6">
|
<div
|
||||||
|
id="telemetry"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6"
|
||||||
|
>
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
Telemetry
|
Telemetry
|
||||||
</h2>
|
</h2>
|
||||||
@@ -182,7 +99,10 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Integrations Section */}
|
{/* Integrations Section */}
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6">
|
<div
|
||||||
|
id="integrations"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6"
|
||||||
|
>
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
Integrations
|
Integrations
|
||||||
</h2>
|
</h2>
|
||||||
@@ -193,41 +113,74 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Experiments Section */}
|
{/* Experiments Section */}
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6">
|
<div
|
||||||
|
id="experiments"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6"
|
||||||
|
>
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
Experiments
|
Experiments
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Enable File Editing Experiment */}
|
<div className="space-y-1 mt-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center space-x-2">
|
||||||
<label
|
<Switch
|
||||||
htmlFor="enable-file-editing"
|
id="enable-native-git"
|
||||||
className="text-sm font-medium text-gray-700 dark:text-gray-300"
|
checked={!!settings?.enableNativeGit}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
updateSettings({
|
||||||
|
enableNativeGit: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="enable-native-git">Enable Native Git</Label>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Native Git offers faster performance but requires{" "}
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
IpcClient.getInstance().openExternalUrl(
|
||||||
|
"https://git-scm.com/downloads",
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="text-blue-600 hover:underline dark:text-blue-400"
|
||||||
>
|
>
|
||||||
Enable File Editing
|
installing Git
|
||||||
</label>
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Enable File Editing Experiment */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
<Switch
|
<Switch
|
||||||
id="enable-file-editing"
|
id="enable-file-editing"
|
||||||
checked={!!settings?.experiments?.enableFileEditing}
|
checked={!!settings?.experiments?.enableFileEditing}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) =>
|
||||||
updateSettings({
|
updateSettings({
|
||||||
experiments: {
|
experiments: {
|
||||||
...settings?.experiments,
|
...settings?.experiments,
|
||||||
enableFileEditing: checked,
|
enableFileEditing: checked,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Label htmlFor="enable-file-editing">
|
||||||
|
Enable File Editing
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
File editing is not reliable and requires you to manually
|
||||||
|
commit changes and update Supabase edge functions.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
||||||
File editing is not reliable and requires you to manually commit
|
|
||||||
changes and update Supabase edge functions.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Danger Zone */}
|
{/* Danger Zone */}
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-red-200 dark:border-red-800">
|
<div
|
||||||
|
id="danger-zone"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-red-200 dark:border-red-800"
|
||||||
|
>
|
||||||
<h2 className="text-lg font-medium text-red-600 dark:text-red-400 mb-4">
|
<h2 className="text-lg font-medium text-red-600 dark:text-red-400 mb-4">
|
||||||
Danger Zone
|
Danger Zone
|
||||||
</h2>
|
</h2>
|
||||||
@@ -268,3 +221,108 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GeneralSettings({ appVersion }: { appVersion: string | null }) {
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="general-settings"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6"
|
||||||
|
>
|
||||||
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
General Settings
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4 mb-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Theme
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="relative bg-gray-100 dark:bg-gray-700 rounded-lg p-1 flex">
|
||||||
|
{(["system", "light", "dark"] as const).map((option) => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
onClick={() => setTheme(option)}
|
||||||
|
className={`
|
||||||
|
px-4 py-1.5 text-sm font-medium rounded-md
|
||||||
|
transition-all duration-200
|
||||||
|
${
|
||||||
|
theme === option
|
||||||
|
? "bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm"
|
||||||
|
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{option.charAt(0).toUpperCase() + option.slice(1)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 mt-4">
|
||||||
|
<AutoUpdateSwitch />
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
This will automatically update the app when new versions are
|
||||||
|
available.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center text-sm text-gray-500 dark:text-gray-400 mt-4">
|
||||||
|
<span className="mr-2 font-medium">App Version:</span>
|
||||||
|
<span className="bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded text-gray-800 dark:text-gray-200 font-mono">
|
||||||
|
{appVersion ? appVersion : "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorkflowSettings() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="workflow-settings"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6"
|
||||||
|
>
|
||||||
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
Workflow Settings
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<AutoApproveSwitch showToast={false} />
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
This will automatically approve code changes and run them.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 mt-4">
|
||||||
|
<AutoFixProblemsSwitch />
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
This will automatically fix TypeScript errors.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function AISettings() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="ai-settings"
|
||||||
|
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6"
|
||||||
|
>
|
||||||
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
AI Settings
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<ThinkingBudgetSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<MaxChatTurnsSelector />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ const validInvokeChannels = [
|
|||||||
"open-ios",
|
"open-ios",
|
||||||
"open-android",
|
"open-android",
|
||||||
"check-problems",
|
"check-problems",
|
||||||
|
"restart-dyad",
|
||||||
// Test-only channels
|
// Test-only channels
|
||||||
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
|
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
|
||||||
// We can't detect with IS_TEST_BUILD in the preload script because
|
// We can't detect with IS_TEST_BUILD in the preload script because
|
||||||
|
|||||||
Reference in New Issue
Block a user