Run prettier on everything (#104)
This commit is contained in:
@@ -27,13 +27,13 @@ Add these commands to your `package.json`:
|
||||
When mocking the `node:fs` module, use a default export in the mock:
|
||||
|
||||
```typescript
|
||||
vi.mock('node:fs', async () => {
|
||||
vi.mock("node:fs", async () => {
|
||||
return {
|
||||
default: {
|
||||
mkdirSync: vi.fn(),
|
||||
writeFileSync: vi.fn(),
|
||||
// Add other fs methods as needed
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
```
|
||||
@@ -43,12 +43,12 @@ vi.mock('node:fs', async () => {
|
||||
When mocking isomorphic-git, provide a default export:
|
||||
|
||||
```typescript
|
||||
vi.mock('isomorphic-git', () => ({
|
||||
vi.mock("isomorphic-git", () => ({
|
||||
default: {
|
||||
add: vi.fn().mockResolvedValue(undefined),
|
||||
commit: vi.fn().mockResolvedValue(undefined),
|
||||
// Add other git methods as needed
|
||||
}
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
@@ -57,7 +57,7 @@ vi.mock('isomorphic-git', () => ({
|
||||
When testing IPC handlers, mock the Electron IPC system:
|
||||
|
||||
```typescript
|
||||
vi.mock('electron', () => ({
|
||||
vi.mock("electron", () => ({
|
||||
ipcMain: {
|
||||
handle: vi.fn(),
|
||||
on: vi.fn(),
|
||||
@@ -74,4 +74,4 @@ vi.mock('electron', () => ({
|
||||
|
||||
## Example
|
||||
|
||||
See `chat_stream_handlers.test.ts` for an example of testing IPC handlers with proper mocking.
|
||||
See `chat_stream_handlers.test.ts` for an example of testing IPC handlers with proper mocking.
|
||||
|
||||
@@ -58,28 +58,28 @@ describe("getDyadAddDependencyTags", () => {
|
||||
|
||||
it("should return an array of dyad-add-dependency tags", () => {
|
||||
const result = getDyadAddDependencyTags(
|
||||
`<dyad-add-dependency packages="uuid"></dyad-add-dependency>`
|
||||
`<dyad-add-dependency packages="uuid"></dyad-add-dependency>`,
|
||||
);
|
||||
expect(result).toEqual(["uuid"]);
|
||||
});
|
||||
|
||||
it("should return all the packages in the dyad-add-dependency tags", () => {
|
||||
const result = getDyadAddDependencyTags(
|
||||
`<dyad-add-dependency packages="pkg1 pkg2"></dyad-add-dependency>`
|
||||
`<dyad-add-dependency packages="pkg1 pkg2"></dyad-add-dependency>`,
|
||||
);
|
||||
expect(result).toEqual(["pkg1", "pkg2"]);
|
||||
});
|
||||
|
||||
it("should return all the packages in the dyad-add-dependency tags", () => {
|
||||
const result = getDyadAddDependencyTags(
|
||||
`txt before<dyad-add-dependency packages="pkg1 pkg2"></dyad-add-dependency>text after`
|
||||
`txt before<dyad-add-dependency packages="pkg1 pkg2"></dyad-add-dependency>text after`,
|
||||
);
|
||||
expect(result).toEqual(["pkg1", "pkg2"]);
|
||||
});
|
||||
|
||||
it("should return all the packages in multiple dyad-add-dependency tags", () => {
|
||||
const result = getDyadAddDependencyTags(
|
||||
`txt before<dyad-add-dependency packages="pkg1 pkg2"></dyad-add-dependency>txt between<dyad-add-dependency packages="pkg3"></dyad-add-dependency>text after`
|
||||
`txt before<dyad-add-dependency packages="pkg1 pkg2"></dyad-add-dependency>txt between<dyad-add-dependency packages="pkg3"></dyad-add-dependency>text after`,
|
||||
);
|
||||
expect(result).toEqual(["pkg1", "pkg2", "pkg3"]);
|
||||
});
|
||||
@@ -450,7 +450,7 @@ declare module 'uuid' {
|
||||
}
|
||||
</dyad-write>
|
||||
|
||||
I've created a complete todo list application with the ability to add, complete, and delete tasks. The app includes statistics and uses local storage to persist data.`
|
||||
I've created a complete todo list application with the ability to add, complete, and delete tasks. The app includes statistics and uses local storage to persist data.`,
|
||||
);
|
||||
expect(result.length).toEqual(7);
|
||||
});
|
||||
@@ -465,7 +465,7 @@ describe("getDyadRenameTags", () => {
|
||||
it("should return an array of dyad-rename tags", () => {
|
||||
const result = getDyadRenameTags(
|
||||
`<dyad-rename from="src/components/UserProfile.jsx" to="src/components/ProfileCard.jsx"></dyad-rename>
|
||||
<dyad-rename from="src/utils/helpers.js" to="src/utils/utils.js"></dyad-rename>`
|
||||
<dyad-rename from="src/utils/helpers.js" to="src/utils/utils.js"></dyad-rename>`,
|
||||
);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
@@ -486,7 +486,7 @@ describe("getDyadDeleteTags", () => {
|
||||
it("should return an array of dyad-delete paths", () => {
|
||||
const result = getDyadDeleteTags(
|
||||
`<dyad-delete path="src/components/Analytics.jsx"></dyad-delete>
|
||||
<dyad-delete path="src/utils/unused.js"></dyad-delete>`
|
||||
<dyad-delete path="src/utils/unused.js"></dyad-delete>`,
|
||||
);
|
||||
expect(result).toEqual([
|
||||
"src/components/Analytics.jsx",
|
||||
@@ -526,7 +526,7 @@ describe("processFullResponse", () => {
|
||||
{
|
||||
chatSummary: undefined,
|
||||
messageId: 1,
|
||||
}
|
||||
},
|
||||
);
|
||||
expect(result).toEqual({});
|
||||
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
||||
@@ -547,16 +547,16 @@ describe("processFullResponse", () => {
|
||||
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src",
|
||||
{ recursive: true }
|
||||
{ recursive: true },
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/file1.js",
|
||||
"console.log('Hello');"
|
||||
"console.log('Hello');",
|
||||
);
|
||||
expect(git.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/file1.js",
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(git.commit).toHaveBeenCalled();
|
||||
expect(result).toEqual({ updatedFiles: true });
|
||||
@@ -604,49 +604,49 @@ describe("processFullResponse", () => {
|
||||
// Check that directories were created for each file path
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src",
|
||||
{ recursive: true }
|
||||
{ recursive: true },
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/utils",
|
||||
{ recursive: true }
|
||||
{ recursive: true },
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components",
|
||||
{ recursive: true }
|
||||
{ recursive: true },
|
||||
);
|
||||
|
||||
// Using toHaveBeenNthCalledWith to check each specific call
|
||||
expect(fs.writeFileSync).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"/mock/user/data/path/mock-app-path/src/file1.js",
|
||||
"console.log('First file');"
|
||||
"console.log('First file');",
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/mock/user/data/path/mock-app-path/src/utils/file2.js",
|
||||
"export const add = (a, b) => a + b;"
|
||||
"export const add = (a, b) => a + b;",
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
"/mock/user/data/path/mock-app-path/src/components/Button.tsx",
|
||||
"import React from 'react';\n export const Button = ({ children }) => <button>{children}</button>;"
|
||||
"import React from 'react';\n export const Button = ({ children }) => <button>{children}</button>;",
|
||||
);
|
||||
|
||||
// Verify git operations were called for each file
|
||||
expect(git.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/file1.js",
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(git.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/utils/file2.js",
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(git.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/components/Button.tsx",
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify commit was called once after all files were added
|
||||
@@ -669,21 +669,21 @@ describe("processFullResponse", () => {
|
||||
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components",
|
||||
{ recursive: true }
|
||||
{ recursive: true },
|
||||
);
|
||||
expect(fs.renameSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components/OldComponent.jsx",
|
||||
"/mock/user/data/path/mock-app-path/src/components/NewComponent.jsx"
|
||||
"/mock/user/data/path/mock-app-path/src/components/NewComponent.jsx",
|
||||
);
|
||||
expect(git.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/components/NewComponent.jsx",
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(git.remove).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/components/OldComponent.jsx",
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(git.commit).toHaveBeenCalled();
|
||||
expect(result).toEqual({ updatedFiles: true });
|
||||
@@ -719,12 +719,12 @@ describe("processFullResponse", () => {
|
||||
});
|
||||
|
||||
expect(fs.unlinkSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components/Unused.jsx"
|
||||
"/mock/user/data/path/mock-app-path/src/components/Unused.jsx",
|
||||
);
|
||||
expect(git.remove).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filepath: "src/components/Unused.jsx",
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(git.commit).toHaveBeenCalled();
|
||||
expect(result).toEqual({ updatedFiles: true });
|
||||
@@ -769,18 +769,18 @@ describe("processFullResponse", () => {
|
||||
// Check write operation happened
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components/NewComponent.jsx",
|
||||
"import React from 'react'; export default () => <div>New</div>;"
|
||||
"import React from 'react'; export default () => <div>New</div>;",
|
||||
);
|
||||
|
||||
// Check rename operation happened
|
||||
expect(fs.renameSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components/OldComponent.jsx",
|
||||
"/mock/user/data/path/mock-app-path/src/components/RenamedComponent.jsx"
|
||||
"/mock/user/data/path/mock-app-path/src/components/RenamedComponent.jsx",
|
||||
);
|
||||
|
||||
// Check delete operation happened
|
||||
expect(fs.unlinkSync).toHaveBeenCalledWith(
|
||||
"/mock/user/data/path/mock-app-path/src/components/Unused.jsx"
|
||||
"/mock/user/data/path/mock-app-path/src/components/Unused.jsx",
|
||||
);
|
||||
|
||||
// Check git operations
|
||||
@@ -791,9 +791,9 @@ describe("processFullResponse", () => {
|
||||
expect(git.commit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining(
|
||||
"wrote 1 file(s), renamed 1 file(s), deleted 1 file(s)"
|
||||
"wrote 1 file(s), renamed 1 file(s), deleted 1 file(s)",
|
||||
),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result).toEqual({ updatedFiles: true });
|
||||
|
||||
@@ -92,7 +92,7 @@ export const TitleBar = () => {
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"ml-4 no-app-region-drag h-7 bg-indigo-600 text-white dark:bg-indigo-600 dark:text-white",
|
||||
!isDyadProEnabled && "bg-zinc-600 dark:bg-zinc-600"
|
||||
!isDyadProEnabled && "bg-zinc-600 dark:bg-zinc-600",
|
||||
)}
|
||||
size="sm"
|
||||
>
|
||||
|
||||
@@ -63,7 +63,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
// Create the GitHub issue URL with the pre-filled body
|
||||
const encodedBody = encodeURIComponent(issueBody);
|
||||
const encodedTitle = encodeURIComponent(
|
||||
"[bug] Error in Dyad application"
|
||||
"[bug] Error in Dyad application",
|
||||
);
|
||||
const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=bug,filed-from-app,client-error&body=${encodedBody}`;
|
||||
|
||||
@@ -73,7 +73,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
console.error("Failed to prepare bug report:", err);
|
||||
// Fallback to opening the regular GitHub issue page
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://github.com/dyad-sh/dyad/issues/new"
|
||||
"https://github.com/dyad-sh/dyad/issues/new",
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -21,7 +21,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
const [githubError, setGithubError] = useState<string | null>(null);
|
||||
const [isConnectingToGithub, setIsConnectingToGithub] = useState(false);
|
||||
const [githubStatusMessage, setGithubStatusMessage] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [codeCopied, setCodeCopied] = useState(false);
|
||||
// --- ---
|
||||
@@ -91,7 +91,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
setGithubUserCode(null);
|
||||
setGithubVerificationUri(null);
|
||||
setIsConnectingToGithub(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
cleanupFunctions.push(removeErrorListener);
|
||||
|
||||
@@ -134,7 +134,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
try {
|
||||
const result = await IpcClient.getInstance().checkGithubRepoAvailable(
|
||||
githubOrg,
|
||||
repoName
|
||||
repoName,
|
||||
);
|
||||
setRepoAvailable(result.available);
|
||||
if (!result.available) {
|
||||
@@ -156,7 +156,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
const result = await IpcClient.getInstance().createGithubRepo(
|
||||
githubOrg,
|
||||
repoName,
|
||||
appId!
|
||||
appId!,
|
||||
);
|
||||
if (result.success) {
|
||||
setCreateRepoSuccess(true);
|
||||
@@ -247,7 +247,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
githubVerificationUri
|
||||
githubVerificationUri,
|
||||
);
|
||||
}}
|
||||
target="_blank"
|
||||
@@ -273,7 +273,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
setTimeout(() => setCodeCopied(false), 2000);
|
||||
})
|
||||
.catch((err) =>
|
||||
console.error("Failed to copy code:", err)
|
||||
console.error("Failed to copy code:", err),
|
||||
);
|
||||
}
|
||||
}}
|
||||
@@ -325,7 +325,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
`https://github.com/${app.githubOrg}/${app.githubRepo}`
|
||||
`https://github.com/${app.githubOrg}/${app.githubRepo}`,
|
||||
);
|
||||
}}
|
||||
className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400"
|
||||
|
||||
@@ -21,7 +21,7 @@ export function GitHubIntegration() {
|
||||
}
|
||||
} catch (err: any) {
|
||||
showError(
|
||||
err.message || "An error occurred while disconnecting from GitHub"
|
||||
err.message || "An error occurred while disconnecting from GitHub",
|
||||
);
|
||||
} finally {
|
||||
setIsDisconnecting(false);
|
||||
|
||||
@@ -105,7 +105,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
console.error("Failed to prepare bug report:", error);
|
||||
// Fallback to opening the regular GitHub issue page
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://github.com/dyad-sh/dyad/issues/new"
|
||||
"https://github.com/dyad-sh/dyad/issues/new",
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -121,9 +121,8 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
setIsUploading(true);
|
||||
try {
|
||||
// Get chat logs (includes debug info, chat data, and codebase)
|
||||
const chatLogs = await IpcClient.getInstance().getChatLogs(
|
||||
selectedChatId
|
||||
);
|
||||
const chatLogs =
|
||||
await IpcClient.getInstance().getChatLogs(selectedChatId);
|
||||
|
||||
// Store data for review and switch to review mode
|
||||
setChatLogsData(chatLogs);
|
||||
@@ -131,7 +130,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
} catch (error) {
|
||||
console.error("Failed to upload chat session:", error);
|
||||
alert(
|
||||
"Failed to upload chat session. Please try again or report manually."
|
||||
"Failed to upload chat session. Please try again or report manually.",
|
||||
);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
@@ -162,7 +161,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
extension: "json",
|
||||
contentType: "application/json",
|
||||
}),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -176,7 +175,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||
const uploadResult = await IpcClient.getInstance().uploadToSignedUrl(
|
||||
uploadUrl,
|
||||
"application/json",
|
||||
chatLogsJson
|
||||
chatLogsJson,
|
||||
);
|
||||
|
||||
if (!uploadResult.success) {
|
||||
@@ -374,7 +373,7 @@ Session ID: ${sessionId}
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://www.dyad.sh/docs"
|
||||
"https://www.dyad.sh/docs",
|
||||
);
|
||||
}}
|
||||
className="w-full py-6 bg-(--background-lightest)"
|
||||
|
||||
@@ -62,14 +62,14 @@ export function ModelPicker({
|
||||
if (selectedModel.provider === "ollama") {
|
||||
return (
|
||||
ollamaModels.find(
|
||||
(model: LocalModel) => model.modelName === selectedModel.name
|
||||
(model: LocalModel) => model.modelName === selectedModel.name,
|
||||
)?.displayName || selectedModel.name
|
||||
);
|
||||
}
|
||||
if (selectedModel.provider === "lmstudio") {
|
||||
return (
|
||||
lmStudioModels.find(
|
||||
(model: LocalModel) => model.modelName === selectedModel.name
|
||||
(model: LocalModel) => model.modelName === selectedModel.name,
|
||||
)?.displayName || selectedModel.name // Fallback to path if not found
|
||||
);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export function ModelPicker({
|
||||
// Fallback for cloud models
|
||||
return (
|
||||
MODEL_OPTIONS[selectedModel.provider]?.find(
|
||||
(model) => model.name === selectedModel.name
|
||||
(model) => model.name === selectedModel.name,
|
||||
)?.displayName || selectedModel.name
|
||||
);
|
||||
};
|
||||
@@ -90,7 +90,7 @@ export function ModelPicker({
|
||||
models.map((model) => ({
|
||||
...model,
|
||||
provider: provider as ModelProvider,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
|
||||
// Determine availability of local models
|
||||
|
||||
@@ -34,7 +34,7 @@ export function ProviderSettingsGrid({
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(PROVIDERS).map(([key, provider]) => {
|
||||
const isConfigured = configuredProviders.includes(
|
||||
key as ModelProvider
|
||||
key as ModelProvider,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,7 +35,7 @@ export function SetupBanner() {
|
||||
const navigate = useNavigate();
|
||||
const { isAnyProviderSetup, loading } = useSettings();
|
||||
const [nodeSystemInfo, setNodeSystemInfo] = useState<NodeSystemInfo | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [nodeCheckError, setNodeCheckError] = useState<boolean>(false);
|
||||
const [nodeInstallStep, setNodeInstallStep] =
|
||||
@@ -106,7 +106,7 @@ export function SetupBanner() {
|
||||
|
||||
const bannerClasses = cn(
|
||||
"w-full mb-6 border rounded-xl shadow-sm overflow-hidden",
|
||||
"border-zinc-200 dark:border-zinc-700"
|
||||
"border-zinc-200 dark:border-zinc-700",
|
||||
);
|
||||
|
||||
const getStatusIcon = (isComplete: boolean, hasError: boolean = false) => {
|
||||
@@ -137,8 +137,8 @@ export function SetupBanner() {
|
||||
nodeCheckError
|
||||
? "bg-red-50 dark:bg-red-900/30"
|
||||
: isNodeSetupComplete
|
||||
? "bg-green-50 dark:bg-green-900/30"
|
||||
: "bg-yellow-50 dark:bg-yellow-900/30"
|
||||
? "bg-green-50 dark:bg-green-900/30"
|
||||
: "bg-yellow-50 dark:bg-yellow-900/30",
|
||||
)}
|
||||
>
|
||||
<AccordionTrigger className="px-4 py-3 transition-colors w-full hover:no-underline">
|
||||
@@ -178,7 +178,7 @@ export function SetupBanner() {
|
||||
className="text-blue-500 dark:text-blue-400 hover:underline"
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://nodejs.org/en/download"
|
||||
"https://nodejs.org/en/download",
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -203,12 +203,12 @@ export function SetupBanner() {
|
||||
className={cn(
|
||||
isAnyProviderSetup()
|
||||
? "bg-green-50 dark:bg-green-900/30"
|
||||
: "bg-yellow-50 dark:bg-yellow-900/30"
|
||||
: "bg-yellow-50 dark:bg-yellow-900/30",
|
||||
)}
|
||||
>
|
||||
<AccordionTrigger
|
||||
className={cn(
|
||||
"px-4 py-3 transition-colors w-full hover:no-underline"
|
||||
"px-4 py-3 transition-colors w-full hover:no-underline",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
@@ -288,7 +288,7 @@ function NodeJsHelpCallout() {
|
||||
<a
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://www.dyad.sh/docs/help/nodejs"
|
||||
"https://www.dyad.sh/docs/help/nodejs",
|
||||
);
|
||||
}}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
|
||||
|
||||
@@ -100,7 +100,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
`https://supabase.com/dashboard/project/${app.supabaseProjectId}`
|
||||
`https://supabase.com/dashboard/project/${app.supabaseProjectId}`,
|
||||
);
|
||||
}}
|
||||
className="ml-2 px-2 py-1"
|
||||
@@ -204,7 +204,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
||||
<img
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://supabase-oauth.dyad.sh/api/connect-supabase/login"
|
||||
"https://supabase-oauth.dyad.sh/api/connect-supabase/login",
|
||||
);
|
||||
}}
|
||||
src={isDarkMode ? connectSupabaseDark : connectSupabaseLight}
|
||||
|
||||
@@ -24,7 +24,7 @@ export function SupabaseIntegration() {
|
||||
}
|
||||
} catch (err: any) {
|
||||
showError(
|
||||
err.message || "An error occurred while disconnecting from Supabase"
|
||||
err.message || "An error occurred while disconnecting from Supabase",
|
||||
);
|
||||
} finally {
|
||||
setIsDisconnecting(false);
|
||||
|
||||
@@ -33,7 +33,7 @@ export function PrivacyBanner() {
|
||||
<a
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://dyad.sh/docs/telemetry"
|
||||
"https://dyad.sh/docs/telemetry",
|
||||
);
|
||||
}}
|
||||
className="cursor-pointer text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { XCircle, AlertTriangle } from "lucide-react"; // Assuming lucide-react is used
|
||||
|
||||
interface ChatErrorProps {
|
||||
|
||||
@@ -176,7 +176,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
||||
if (!chatId || !messageId || isApproving || isRejecting || isStreaming)
|
||||
return;
|
||||
console.log(
|
||||
`Approving proposal for chatId: ${chatId}, messageId: ${messageId}`
|
||||
`Approving proposal for chatId: ${chatId}, messageId: ${messageId}`,
|
||||
);
|
||||
setIsApproving(true);
|
||||
posthog.capture("chat:approve");
|
||||
@@ -213,7 +213,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
||||
if (!chatId || !messageId || isApproving || isRejecting || isStreaming)
|
||||
return;
|
||||
console.log(
|
||||
`Rejecting proposal for chatId: ${chatId}, messageId: ${messageId}`
|
||||
`Rejecting proposal for chatId: ${chatId}, messageId: ${messageId}`,
|
||||
);
|
||||
setIsRejecting(true);
|
||||
posthog.capture("chat:reject");
|
||||
@@ -771,7 +771,7 @@ function ChatInputActions({
|
||||
className="flex items-center space-x-2"
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
`https://www.npmjs.com/package/${pkg}`
|
||||
`https://www.npmjs.com/package/${pkg}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -884,7 +884,7 @@ function ProposalSummary({
|
||||
parts.push(
|
||||
`${sqlQueries.length} SQL ${
|
||||
sqlQueries.length === 1 ? "query" : "queries"
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -892,7 +892,7 @@ function ProposalSummary({
|
||||
parts.push(
|
||||
`${serverFunctions.length} Server ${
|
||||
serverFunctions.length === 1 ? "Function" : "Functions"
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -900,13 +900,13 @@ function ProposalSummary({
|
||||
parts.push(
|
||||
`${packagesAdded.length} ${
|
||||
packagesAdded.length === 1 ? "package" : "packages"
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (filesChanged.length) {
|
||||
parts.push(
|
||||
`${filesChanged.length} ${filesChanged.length === 1 ? "file" : "files"}`
|
||||
`${filesChanged.length} ${filesChanged.length === 1 ? "file" : "files"}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export const CodeHighlight = memo(
|
||||
isDarkMode ? githubDark : github,
|
||||
{
|
||||
delay: 150,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Update the cache whenever we get a new highlighted code
|
||||
@@ -64,5 +64,5 @@ export const CodeHighlight = memo(
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
return prevProps.children === nextProps.children;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ export const DyadAddDependency: React.FC<DyadAddDependencyProps> = ({
|
||||
key={p}
|
||||
onClick={() => {
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
`https://www.npmjs.com/package/${p}`
|
||||
`https://www.npmjs.com/package/${p}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -34,8 +34,8 @@ export const DyadExecuteSql: React.FC<DyadExecuteSqlProps> = ({
|
||||
inProgress
|
||||
? "border-amber-500"
|
||||
: aborted
|
||||
? "border-red-500"
|
||||
: "border-border"
|
||||
? "border-red-500"
|
||||
: "border-border"
|
||||
}`}
|
||||
onClick={() => setIsContentVisible(!isContentVisible)}
|
||||
>
|
||||
|
||||
@@ -177,7 +177,7 @@ function parseCustomTags(content: string): ContentPiece[] {
|
||||
|
||||
const tagPattern = new RegExp(
|
||||
`<(${customTagNames.join("|")})\\s*([^>]*)>(.*?)<\\/\\1>`,
|
||||
"gs"
|
||||
"gs",
|
||||
);
|
||||
|
||||
const contentPieces: ContentPiece[] = [];
|
||||
@@ -253,7 +253,7 @@ function getState({
|
||||
*/
|
||||
function renderCustomTag(
|
||||
tagInfo: CustomTagInfo,
|
||||
{ isStreaming }: { isStreaming: boolean }
|
||||
{ isStreaming }: { isStreaming: boolean },
|
||||
): React.ReactNode {
|
||||
const { tag, attributes, content, inProgress } = tagInfo;
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({
|
||||
inProgress
|
||||
? "border-amber-500"
|
||||
: aborted
|
||||
? "border-red-500"
|
||||
: "border-border"
|
||||
? "border-red-500"
|
||||
: "border-border"
|
||||
}`}
|
||||
onClick={() => setIsContentVisible(!isContentVisible)}
|
||||
>
|
||||
|
||||
@@ -71,27 +71,27 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
|
||||
previousAssistantMessage?.commitHash
|
||||
) {
|
||||
console.debug(
|
||||
"Reverting to previous assistant version"
|
||||
"Reverting to previous assistant version",
|
||||
);
|
||||
await revertVersion({
|
||||
versionId: previousAssistantMessage.commitHash,
|
||||
});
|
||||
const chat = await IpcClient.getInstance().getChat(
|
||||
selectedChatId
|
||||
);
|
||||
const chat =
|
||||
await IpcClient.getInstance().getChat(
|
||||
selectedChatId,
|
||||
);
|
||||
setMessages(chat.messages);
|
||||
}
|
||||
} else {
|
||||
const chat = await IpcClient.getInstance().getChat(
|
||||
selectedChatId
|
||||
);
|
||||
const chat =
|
||||
await IpcClient.getInstance().getChat(selectedChatId);
|
||||
if (chat.initialCommitHash) {
|
||||
await revertVersion({
|
||||
versionId: chat.initialCommitHash,
|
||||
});
|
||||
const result =
|
||||
await IpcClient.getInstance().deleteMessages(
|
||||
selectedChatId
|
||||
selectedChatId,
|
||||
);
|
||||
if (result.success) {
|
||||
setMessages([]);
|
||||
@@ -100,7 +100,7 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
|
||||
}
|
||||
} else {
|
||||
showWarning(
|
||||
"No initial commit hash found for chat. Need to manually undo code changes"
|
||||
"No initial commit hash found for chat. Need to manually undo code changes",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -148,27 +148,26 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
|
||||
previousAssistantMessage?.commitHash
|
||||
) {
|
||||
console.debug(
|
||||
"Reverting to previous assistant version"
|
||||
"Reverting to previous assistant version",
|
||||
);
|
||||
await revertVersion({
|
||||
versionId: previousAssistantMessage.commitHash,
|
||||
});
|
||||
shouldRedo = false;
|
||||
} else {
|
||||
const chat = await IpcClient.getInstance().getChat(
|
||||
selectedChatId
|
||||
);
|
||||
const chat =
|
||||
await IpcClient.getInstance().getChat(selectedChatId);
|
||||
if (chat.initialCommitHash) {
|
||||
console.debug(
|
||||
"Reverting to initial commit hash",
|
||||
chat.initialCommitHash
|
||||
chat.initialCommitHash,
|
||||
);
|
||||
await revertVersion({
|
||||
versionId: chat.initialCommitHash,
|
||||
});
|
||||
} else {
|
||||
showWarning(
|
||||
"No initial commit hash found for chat. Need to manually undo code changes"
|
||||
"No initial commit hash found for chat. Need to manually undo code changes",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -212,5 +211,5 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
||||
const { versions, loading, refreshVersions, revertVersion } =
|
||||
useVersions(appId);
|
||||
const [selectedVersionId, setSelectedVersionId] = useAtom(
|
||||
selectedVersionIdAtom
|
||||
selectedVersionIdAtom,
|
||||
);
|
||||
useEffect(() => {
|
||||
// Refresh versions in case the user updated versions outside of the app
|
||||
@@ -86,20 +86,20 @@ export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
||||
{version.message && (
|
||||
<p className="mt-1 text-sm">
|
||||
{version.message.startsWith(
|
||||
"Reverted all changes back to version "
|
||||
"Reverted all changes back to version ",
|
||||
)
|
||||
? version.message.replace(
|
||||
/Reverted all changes back to version ([a-f0-9]+)/,
|
||||
(_, hash) => {
|
||||
const targetIndex = versions.findIndex(
|
||||
(v) => v.oid === hash
|
||||
(v) => v.oid === hash,
|
||||
);
|
||||
return targetIndex !== -1
|
||||
? `Reverted all changes back to version ${
|
||||
versions.length - targetIndex
|
||||
}`
|
||||
: version.message;
|
||||
}
|
||||
},
|
||||
)
|
||||
: version.message}
|
||||
</p>
|
||||
@@ -115,7 +115,7 @@ export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
||||
}}
|
||||
className={cn(
|
||||
"invisible mt-1 flex items-center gap-1 px-2 py-0.5 text-sm font-medium bg-(--primary) text-(--primary-foreground) hover:bg-background-lightest rounded-md transition-colors",
|
||||
selectedVersionId === version.oid && "visible"
|
||||
selectedVersionId === version.oid && "visible",
|
||||
)}
|
||||
aria-label="Undo to latest version"
|
||||
>
|
||||
|
||||
@@ -122,7 +122,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
|
||||
// Load router related files to extract routes
|
||||
const { content: routerContent } = useLoadAppFile(
|
||||
selectedAppId,
|
||||
"src/App.tsx"
|
||||
"src/App.tsx",
|
||||
);
|
||||
|
||||
// Effect to parse routes from the router file
|
||||
@@ -287,7 +287,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
|
||||
type: "navigate",
|
||||
payload: { direction: "backward" },
|
||||
},
|
||||
"*"
|
||||
"*",
|
||||
);
|
||||
|
||||
// Update our local state
|
||||
@@ -305,14 +305,14 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
|
||||
type: "navigate",
|
||||
payload: { direction: "forward" },
|
||||
},
|
||||
"*"
|
||||
"*",
|
||||
);
|
||||
|
||||
// Update our local state
|
||||
setCurrentHistoryPosition((prev) => prev + 1);
|
||||
setCanGoBack(true);
|
||||
setCanGoForward(
|
||||
currentHistoryPosition + 1 < navigationHistory.length - 1
|
||||
currentHistoryPosition + 1 < navigationHistory.length - 1,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ export function PreviewPanel() {
|
||||
if (currentRunningApp !== null) {
|
||||
console.debug(
|
||||
"Component unmounting or selectedAppId changing, stopping app",
|
||||
currentRunningApp
|
||||
currentRunningApp,
|
||||
);
|
||||
stopApp(currentRunningApp);
|
||||
runningAppIdRef.current = null; // Clear ref on stop
|
||||
|
||||
@@ -78,8 +78,8 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
|
||||
const activeKeySource = isValidUserKey
|
||||
? "settings"
|
||||
: hasEnvKey
|
||||
? "env"
|
||||
: "none";
|
||||
? "env"
|
||||
: "none";
|
||||
|
||||
// --- Accordion Logic ---
|
||||
const defaultAccordionValue = [];
|
||||
@@ -202,8 +202,8 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
|
||||
{settingsLoading
|
||||
? "Loading..."
|
||||
: isConfigured
|
||||
? "Setup Complete"
|
||||
: "Not Setup"}
|
||||
? "Setup Complete"
|
||||
: "Not Setup"}
|
||||
</span>
|
||||
</div>
|
||||
{!settingsLoading && hasFreeTier && (
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
import type * as React from "react";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Accordion({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
||||
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
||||
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
||||
}
|
||||
|
||||
function AccordionItem({
|
||||
@@ -20,7 +20,7 @@ function AccordionItem({
|
||||
className={cn("border-b last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AccordionTrigger({
|
||||
@@ -34,7 +34,7 @@ function AccordionTrigger({
|
||||
data-slot="accordion-trigger"
|
||||
className={cn(
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -42,7 +42,7 @@ function AccordionTrigger({
|
||||
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AccordionContent({
|
||||
@@ -58,7 +58,7 @@ function AccordionContent({
|
||||
>
|
||||
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import type * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const alertVariants = cva(
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
@@ -16,8 +16,8 @@ const alertVariants = cva(
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
function Alert({
|
||||
className,
|
||||
@@ -31,7 +31,7 @@ function Alert({
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
data-slot="alert-title"
|
||||
className={cn(
|
||||
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AlertDescription({
|
||||
@@ -56,11 +56,11 @@ function AlertDescription({
|
||||
data-slot="alert-description"
|
||||
className={cn(
|
||||
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription }
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
|
||||
@@ -33,7 +33,7 @@ const buttonVariants = cva(
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function Button({
|
||||
|
||||
@@ -9,7 +9,7 @@ const Card = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
@@ -37,11 +37,11 @@ function DialogOverlay({
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
@@ -56,7 +56,7 @@ function DialogContent({
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -67,7 +67,7 @@ function DialogContent({
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -77,7 +77,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -86,11 +86,11 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
@@ -103,7 +103,7 @@ function DialogTitle({
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
@@ -116,7 +116,7 @@ function DialogDescription({
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -130,4 +130,4 @@ export {
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ function DropdownMenuContent({
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -73,7 +73,7 @@ function DropdownMenuItem({
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-1 rounded-sm px-2 py-1 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -91,7 +91,7 @@ function DropdownMenuCheckboxItem({
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
@@ -127,7 +127,7 @@ function DropdownMenuRadioItem({
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -154,7 +154,7 @@ function DropdownMenuLabel({
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -183,7 +183,7 @@ function DropdownMenuShortcut({
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -210,7 +210,7 @@ function DropdownMenuSubTrigger({
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -229,7 +229,7 @@ function DropdownMenuSubContent({
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type * as React from "react"
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
@@ -11,11 +11,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Input }
|
||||
export { Input };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Label({
|
||||
className,
|
||||
@@ -12,11 +12,11 @@ function Label({
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Label }
|
||||
export { Label };
|
||||
|
||||
@@ -18,7 +18,7 @@ const PopoverContent = React.forwardRef<
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
@@ -28,7 +28,7 @@ function SelectTrigger({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default"
|
||||
size?: "sm" | "default";
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
@@ -36,7 +36,7 @@ function SelectTrigger({
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -45,7 +45,7 @@ function SelectTrigger({
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
@@ -62,7 +62,7 @@ function SelectContent({
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
@@ -72,7 +72,7 @@ function SelectContent({
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -80,7 +80,7 @@ function SelectContent({
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
@@ -93,7 +93,7 @@ function SelectLabel({
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
@@ -106,7 +106,7 @@ function SelectItem({
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -117,7 +117,7 @@ function SelectItem({
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
@@ -130,7 +130,7 @@ function SelectSeparator({
|
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
@@ -142,13 +142,13 @@ function SelectScrollUpButton({
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
@@ -160,13 +160,13 @@ function SelectScrollDownButton({
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -180,4 +180,4 @@ export {
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import type * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
import type * as React from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Separator({
|
||||
className,
|
||||
@@ -18,11 +18,11 @@ function Separator({
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Separator }
|
||||
export { Separator };
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import type * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import type * as React from "react";
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
||||
}
|
||||
|
||||
function SheetTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function SheetClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
||||
}
|
||||
|
||||
function SheetPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
||||
}
|
||||
|
||||
function SheetOverlay({
|
||||
@@ -35,11 +35,11 @@ function SheetOverlay({
|
||||
data-slot="sheet-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetContent({
|
||||
@@ -48,7 +48,7 @@ function SheetContent({
|
||||
side = "right",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
}) {
|
||||
return (
|
||||
<SheetPortal>
|
||||
@@ -65,7 +65,7 @@ function SheetContent({
|
||||
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||
side === "bottom" &&
|
||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -76,7 +76,7 @@ function SheetContent({
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -86,7 +86,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -96,7 +96,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetTitle({
|
||||
@@ -109,7 +109,7 @@ function SheetTitle({
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetDescription({
|
||||
@@ -122,7 +122,7 @@ function SheetDescription({
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -134,4 +134,4 @@ export {
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ function SidebarProvider({
|
||||
// This sets the cookie to keep the sidebar state.
|
||||
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
||||
},
|
||||
[setOpenProp, open]
|
||||
[setOpenProp, open],
|
||||
);
|
||||
|
||||
// Helper to toggle the sidebar.
|
||||
@@ -117,7 +117,7 @@ function SidebarProvider({
|
||||
setOpen,
|
||||
toggleSidebar,
|
||||
}),
|
||||
[state, open, setOpen, toggleSidebar]
|
||||
[state, open, setOpen, toggleSidebar],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -135,7 +135,7 @@ function SidebarProvider({
|
||||
className={cn(
|
||||
"bg-sidebar",
|
||||
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -166,7 +166,7 @@ function Sidebar({
|
||||
data-slot="sidebar"
|
||||
className={cn(
|
||||
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -193,7 +193,7 @@ function Sidebar({
|
||||
"group-data-[side=right]:rotate-180",
|
||||
variant === "floating" || variant === "inset"
|
||||
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
||||
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
|
||||
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
@@ -207,7 +207,7 @@ function Sidebar({
|
||||
variant === "floating" || variant === "inset"
|
||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
||||
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -274,7 +274,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
||||
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
||||
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -288,7 +288,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
||||
className={cn(
|
||||
"bg-background relative flex w-full flex-1 flex-col",
|
||||
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -352,7 +352,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
data-sidebar="content"
|
||||
className={cn(
|
||||
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -384,7 +384,7 @@ function SidebarGroupLabel({
|
||||
className={cn(
|
||||
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -407,7 +407,7 @@ function SidebarGroupAction({
|
||||
// Increases the hit area of the button on mobile.
|
||||
"after:absolute after:-inset-2 md:after:hidden",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -474,7 +474,7 @@ const sidebarMenuButtonVariants = cva(
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function SidebarMenuButton({
|
||||
@@ -552,7 +552,7 @@ function SidebarMenuAction({
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
showOnHover &&
|
||||
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -574,7 +574,7 @@ function SidebarMenuBadge({
|
||||
"peer-data-[size=default]/menu-button:top-1.5",
|
||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -627,7 +627,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
||||
className={cn(
|
||||
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -673,7 +673,7 @@ function SidebarMenuSubButton({
|
||||
size === "sm" && "text-xs",
|
||||
size === "md" && "text-sm",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
@@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("bg-accent animate-pulse rounded-md", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
export { Skeleton };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||
import * as React from "react";
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
@@ -12,18 +12,18 @@ function Switch({
|
||||
data-slot="switch"
|
||||
className={cn(
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Switch }
|
||||
export { Switch };
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import * as React from "react"
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||
import { type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import { type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { toggleVariants } from "@/components/ui/toggle"
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toggleVariants } from "@/components/ui/toggle";
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
})
|
||||
});
|
||||
|
||||
function ToggleGroup({
|
||||
className,
|
||||
@@ -27,7 +27,7 @@ function ToggleGroup({
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -35,7 +35,7 @@ function ToggleGroup({
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleGroupItem({
|
||||
@@ -46,7 +46,7 @@ function ToggleGroupItem({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>) {
|
||||
const context = React.useContext(ToggleGroupContext)
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
@@ -59,13 +59,13 @@ function ToggleGroupItem({
|
||||
size: context.size || size,
|
||||
}),
|
||||
"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem }
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
||||
@@ -25,8 +25,8 @@ const toggleVariants = cva(
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
function Toggle({
|
||||
className,
|
||||
@@ -41,7 +41,7 @@ function Toggle({
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Toggle, toggleVariants }
|
||||
export { Toggle, toggleVariants };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import type * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
import type * as React from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
@@ -15,7 +15,7 @@ function TooltipProvider({
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
@@ -25,13 +25,13 @@ function Tooltip({
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
@@ -47,7 +47,7 @@ function TooltipContent({
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -55,7 +55,7 @@ function TooltipContent({
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
|
||||
@@ -135,7 +135,7 @@ export const PROVIDER_TO_ENV_VAR: Record<string, string> = {
|
||||
};
|
||||
|
||||
export const ALLOWED_ENV_VARS = Object.keys(PROVIDER_TO_ENV_VAR).map(
|
||||
(provider) => PROVIDER_TO_ENV_VAR[provider]
|
||||
(provider) => PROVIDER_TO_ENV_VAR[provider],
|
||||
);
|
||||
|
||||
export const AUTO_MODELS = [
|
||||
|
||||
@@ -61,7 +61,7 @@ export function useTheme() {
|
||||
const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const updateTheme = () => {
|
||||
setIsDarkMode(
|
||||
theme === "dark" || (theme === "system" && darkModeQuery.matches)
|
||||
theme === "dark" || (theme === "system" && darkModeQuery.matches),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export const db = _db as any as BetterSQLite3Database<typeof schema> & {
|
||||
export async function updateAppGithubRepo(
|
||||
appId: number,
|
||||
org: string,
|
||||
repo: string
|
||||
repo: string,
|
||||
): Promise<void> {
|
||||
await db
|
||||
.update(schema.apps)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
export function useIsMobile() {
|
||||
// Always return false to force desktop behavior
|
||||
return false;
|
||||
|
||||
@@ -31,7 +31,7 @@ export function useCountTokens() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[setLoading, setError, setResult]
|
||||
[setLoading, setError, setResult],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useLoadAppFile(appId: number | null, filePath: string | null) {
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error loading file ${filePath} for app ${appId}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
setError(error instanceof Error ? error : new Error(String(error)));
|
||||
setContent(null);
|
||||
@@ -50,7 +50,7 @@ export function useLoadAppFile(appId: number | null, filePath: string | null) {
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error refreshing file ${filePath} for app ${appId}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
setError(error instanceof Error ? error : new Error(String(error)));
|
||||
} finally {
|
||||
|
||||
@@ -21,7 +21,7 @@ export function useProposal(chatId?: number | undefined) {
|
||||
try {
|
||||
// Type assertion might be needed depending on how IpcClient is typed
|
||||
const result = (await IpcClient.getInstance().getProposal(
|
||||
chatId
|
||||
chatId,
|
||||
)) as ProposalResult | null;
|
||||
|
||||
if (result) {
|
||||
@@ -37,7 +37,7 @@ export function useProposal(chatId?: number | undefined) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[chatId] // Only depend on chatId, setProposalResult is stable
|
||||
[chatId], // Only depend on chatId, setProposalResult is stable
|
||||
); // Depend on chatId
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -52,7 +52,7 @@ export function useRunApp() {
|
||||
} catch (error) {
|
||||
console.error(`Error running app ${appId}:`, error);
|
||||
setPreviewErrorMessage(
|
||||
error instanceof Error ? error.message : error?.toString()
|
||||
error instanceof Error ? error.message : error?.toString(),
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -73,7 +73,7 @@ export function useRunApp() {
|
||||
} catch (error) {
|
||||
console.error(`Error stopping app ${appId}:`, error);
|
||||
setPreviewErrorMessage(
|
||||
error instanceof Error ? error.message : error?.toString()
|
||||
error instanceof Error ? error.message : error?.toString(),
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -97,7 +97,7 @@ export function useRunApp() {
|
||||
console.debug(
|
||||
"Restarting app",
|
||||
appId,
|
||||
removeNodeModules ? "with node_modules cleanup" : ""
|
||||
removeNodeModules ? "with node_modules cleanup" : "",
|
||||
);
|
||||
|
||||
// Clear the URL and add restart message
|
||||
@@ -127,25 +127,25 @@ export function useRunApp() {
|
||||
}
|
||||
// Check if the output contains a localhost URL
|
||||
const urlMatch = output.message.match(
|
||||
/(https?:\/\/localhost:\d+\/?)/
|
||||
/(https?:\/\/localhost:\d+\/?)/,
|
||||
);
|
||||
if (urlMatch) {
|
||||
setAppUrlObj({ appUrl: urlMatch[1], appId });
|
||||
}
|
||||
},
|
||||
removeNodeModules
|
||||
removeNodeModules,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error restarting app ${appId}:`, error);
|
||||
setPreviewErrorMessage(
|
||||
error instanceof Error ? error.message : error?.toString()
|
||||
error instanceof Error ? error.message : error?.toString(),
|
||||
);
|
||||
} finally {
|
||||
setPreviewPanelKey((prevKey) => prevKey + 1);
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[appId, setApp, setAppOutput, setAppUrlObj, setPreviewPanelKey]
|
||||
[appId, setApp, setAppOutput, setAppUrlObj, setPreviewPanelKey],
|
||||
);
|
||||
|
||||
const refreshAppIframe = useCallback(async () => {
|
||||
|
||||
@@ -119,7 +119,7 @@ function processSettingsForTelemetry(settings: UserSettings) {
|
||||
if (settings.telemetryConsent) {
|
||||
window.localStorage.setItem(
|
||||
TELEMETRY_CONSENT_KEY,
|
||||
settings.telemetryConsent
|
||||
settings.telemetryConsent,
|
||||
);
|
||||
} else {
|
||||
window.localStorage.removeItem(TELEMETRY_CONSENT_KEY);
|
||||
@@ -127,7 +127,7 @@ function processSettingsForTelemetry(settings: UserSettings) {
|
||||
if (settings.telemetryUserId) {
|
||||
window.localStorage.setItem(
|
||||
TELEMETRY_USER_ID_KEY,
|
||||
settings.telemetryUserId
|
||||
settings.telemetryUserId,
|
||||
);
|
||||
} else {
|
||||
window.localStorage.removeItem(TELEMETRY_USER_ID_KEY);
|
||||
|
||||
@@ -117,7 +117,7 @@ export function useStreamChat({
|
||||
setError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
},
|
||||
[setMessages, setIsStreaming, setIsPreviewOpen]
|
||||
[setMessages, setIsStreaming, setIsPreviewOpen],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,7 +13,7 @@ export function useSupabase() {
|
||||
const [loading, setLoading] = useAtom(supabaseLoadingAtom);
|
||||
const [error, setError] = useAtom(supabaseErrorAtom);
|
||||
const [selectedProject, setSelectedProject] = useAtom(
|
||||
selectedSupabaseProjectAtom
|
||||
selectedSupabaseProjectAtom,
|
||||
);
|
||||
|
||||
const ipcClient = IpcClient.getInstance();
|
||||
@@ -58,7 +58,7 @@ export function useSupabase() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[ipcClient, setError, setLoading]
|
||||
[ipcClient, setError, setLoading],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,7 @@ export function useSupabase() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[ipcClient, setError, setLoading]
|
||||
[ipcClient, setError, setLoading],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -94,7 +94,7 @@ export function useSupabase() {
|
||||
(projectId: string | null) => {
|
||||
setSelectedProject(projectId);
|
||||
},
|
||||
[setSelectedProject]
|
||||
[setSelectedProject],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -71,7 +71,7 @@ export function useVersions(appId: number | null) {
|
||||
showError(error);
|
||||
}
|
||||
},
|
||||
[appId, setVersions, setError, selectedChatId, setMessages]
|
||||
[appId, setVersions, setError, selectedChatId, setMessages],
|
||||
);
|
||||
|
||||
return { versions, loading, error, refreshVersions, revertVersion };
|
||||
|
||||
@@ -74,7 +74,7 @@ async function executeAppLocalNode({
|
||||
shell: true,
|
||||
stdio: "pipe", // Ensure stdio is piped so we can capture output/errors and detect close
|
||||
detached: false, // Ensure child process is attached to the main process lifecycle unless explicitly backgrounded
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Check if process spawned correctly
|
||||
@@ -86,7 +86,7 @@ async function executeAppLocalNode({
|
||||
throw new Error(
|
||||
`Failed to spawn process for app ${appId}. Error: ${
|
||||
errorOutput || "Unknown spawn error"
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ async function executeAppLocalNode({
|
||||
// Handle process exit/close
|
||||
process.on("close", (code, signal) => {
|
||||
logger.log(
|
||||
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
|
||||
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`,
|
||||
);
|
||||
removeAppIfCurrentProcess(appId, process);
|
||||
});
|
||||
@@ -126,7 +126,7 @@ async function executeAppLocalNode({
|
||||
// Handle errors during process lifecycle (e.g., command not found)
|
||||
process.on("error", (err) => {
|
||||
logger.error(
|
||||
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
|
||||
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`,
|
||||
);
|
||||
removeAppIfCurrentProcess(appId, process);
|
||||
// Note: We don't throw here as the error is asynchronous. The caller got a success response already.
|
||||
@@ -173,7 +173,7 @@ export function registerAppHandlers() {
|
||||
// Copy scaffold asynchronously
|
||||
await copyDirectoryRecursive(
|
||||
path.join(__dirname, "..", "..", "scaffold"),
|
||||
fullAppPath
|
||||
fullAppPath,
|
||||
);
|
||||
// Initialize git repo and create first commit
|
||||
await git.init({
|
||||
@@ -285,7 +285,7 @@ export function registerAppHandlers() {
|
||||
logger.error(`Error reading file ${filePath} for app ${appId}:`, error);
|
||||
throw new Error("Failed to read file");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("get-env-vars", async () => {
|
||||
@@ -300,7 +300,7 @@ export function registerAppHandlers() {
|
||||
"run-app",
|
||||
async (
|
||||
event: Electron.IpcMainInvokeEvent,
|
||||
{ appId }: { appId: number }
|
||||
{ appId }: { appId: number },
|
||||
) => {
|
||||
return withLock(appId, async () => {
|
||||
// Check if app is already running
|
||||
@@ -337,19 +337,19 @@ export function registerAppHandlers() {
|
||||
throw new Error(`Failed to run app ${appId}: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("stop-app", async (_, { appId }: { appId: number }) => {
|
||||
logger.log(
|
||||
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`
|
||||
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`,
|
||||
);
|
||||
return withLock(appId, async () => {
|
||||
const appInfo = runningApps.get(appId);
|
||||
|
||||
if (!appInfo) {
|
||||
logger.log(
|
||||
`App ${appId} not found in running apps map. Assuming already stopped.`
|
||||
`App ${appId} not found in running apps map. Assuming already stopped.`,
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
@@ -359,13 +359,13 @@ export function registerAppHandlers() {
|
||||
|
||||
const { process, processId } = appInfo;
|
||||
logger.log(
|
||||
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`
|
||||
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`,
|
||||
);
|
||||
|
||||
// Check if the process is already exited or closed
|
||||
if (process.exitCode !== null || process.signalCode !== null) {
|
||||
logger.log(
|
||||
`Process for app ${appId} (PID: ${process.pid}) already exited (code: ${process.exitCode}, signal: ${process.signalCode}). Cleaning up map.`
|
||||
`Process for app ${appId} (PID: ${process.pid}) already exited (code: ${process.exitCode}, signal: ${process.signalCode}). Cleaning up map.`,
|
||||
);
|
||||
runningApps.delete(appId); // Ensure cleanup if somehow missed
|
||||
return { success: true, message: "Process already exited." };
|
||||
@@ -382,7 +382,7 @@ export function registerAppHandlers() {
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
// Attempt cleanup even if an error occurred during the stop process
|
||||
removeAppIfCurrentProcess(appId, process);
|
||||
@@ -398,7 +398,7 @@ export function registerAppHandlers() {
|
||||
{
|
||||
appId,
|
||||
removeNodeModules,
|
||||
}: { appId: number; removeNodeModules?: boolean }
|
||||
}: { appId: number; removeNodeModules?: boolean },
|
||||
) => {
|
||||
logger.log(`Restarting app ${appId}`);
|
||||
return withLock(appId, async () => {
|
||||
@@ -408,7 +408,7 @@ export function registerAppHandlers() {
|
||||
if (appInfo) {
|
||||
const { process, processId } = appInfo;
|
||||
logger.log(
|
||||
`Stopping app ${appId} (processId ${processId}) before restart`
|
||||
`Stopping app ${appId} (processId ${processId}) before restart`,
|
||||
);
|
||||
|
||||
await killProcess(process);
|
||||
@@ -435,7 +435,7 @@ export function registerAppHandlers() {
|
||||
if (removeNodeModules) {
|
||||
const nodeModulesPath = path.join(appPath, "node_modules");
|
||||
logger.log(
|
||||
`Removing node_modules for app ${appId} at ${nodeModulesPath}`
|
||||
`Removing node_modules for app ${appId} at ${nodeModulesPath}`,
|
||||
);
|
||||
if (fs.existsSync(nodeModulesPath)) {
|
||||
await fsPromises.rm(nodeModulesPath, {
|
||||
@@ -449,7 +449,7 @@ export function registerAppHandlers() {
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing app ${appId} in path ${app.path} after restart request`
|
||||
`Executing app ${appId} in path ${app.path} after restart request`,
|
||||
); // Adjusted log
|
||||
|
||||
await executeApp({ appPath, appId, event }); // This will handle starting either mode
|
||||
@@ -460,7 +460,7 @@ export function registerAppHandlers() {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
@@ -471,7 +471,7 @@ export function registerAppHandlers() {
|
||||
appId,
|
||||
filePath,
|
||||
content,
|
||||
}: { appId: number; filePath: string; content: string }
|
||||
}: { appId: number; filePath: string; content: string },
|
||||
) => {
|
||||
const app = await db.query.apps.findFirst({
|
||||
where: eq(apps.id, appId),
|
||||
@@ -517,7 +517,7 @@ export function registerAppHandlers() {
|
||||
logger.error(`Error writing file ${filePath} for app ${appId}:`, error);
|
||||
throw new Error(`Failed to write file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("delete-app", async (_, { appId }: { appId: number }) => {
|
||||
@@ -575,7 +575,7 @@ export function registerAppHandlers() {
|
||||
appId,
|
||||
appName,
|
||||
appPath,
|
||||
}: { appId: number; appName: string; appPath: string }
|
||||
}: { appId: number; appName: string; appPath: string },
|
||||
) => {
|
||||
return withLock(appId, async () => {
|
||||
// Check if app exists
|
||||
@@ -613,7 +613,7 @@ export function registerAppHandlers() {
|
||||
} catch (error: any) {
|
||||
logger.error(`Error stopping app ${appId} before renaming:`, error);
|
||||
throw new Error(
|
||||
`Failed to stop app before renaming: ${error.message}`
|
||||
`Failed to stop app before renaming: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -627,7 +627,7 @@ export function registerAppHandlers() {
|
||||
// Check if destination directory already exists
|
||||
if (fs.existsSync(newAppPath)) {
|
||||
throw new Error(
|
||||
`Destination path '${newAppPath}' already exists`
|
||||
`Destination path '${newAppPath}' already exists`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ export function registerAppHandlers() {
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`Error moving app files from ${oldAppPath} to ${newAppPath}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to move app files: ${error.message}`);
|
||||
}
|
||||
@@ -667,7 +667,7 @@ export function registerAppHandlers() {
|
||||
} catch (rollbackError) {
|
||||
logger.error(
|
||||
`Failed to rollback file move during rename error:`,
|
||||
rollbackError
|
||||
rollbackError,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -676,7 +676,7 @@ export function registerAppHandlers() {
|
||||
throw new Error(`Failed to update app in database: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("reset-all", async () => {
|
||||
|
||||
@@ -52,7 +52,7 @@ export function registerChatHandlers() {
|
||||
"for app:",
|
||||
appId,
|
||||
"with initial commit hash:",
|
||||
initialCommitHash
|
||||
initialCommitHash,
|
||||
);
|
||||
return chat.id;
|
||||
});
|
||||
@@ -101,7 +101,7 @@ export function registerChatHandlers() {
|
||||
|
||||
const allChats = await query;
|
||||
return allChats;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle("delete-chat", async (_, chatId: number) => {
|
||||
|
||||
@@ -124,7 +124,7 @@ export function registerChatStreamHandlers() {
|
||||
await db
|
||||
.delete(messages)
|
||||
.where(
|
||||
eq(messages.id, chatMessages[lastUserMessageIndex + 1].id)
|
||||
eq(messages.id, chatMessages[lastUserMessageIndex + 1].id),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -220,7 +220,7 @@ export function registerChatStreamHandlers() {
|
||||
req.chatId,
|
||||
testResponse,
|
||||
abortController,
|
||||
updatedChat
|
||||
updatedChat,
|
||||
);
|
||||
} else {
|
||||
// Normal AI processing for non-test prompts
|
||||
@@ -242,7 +242,7 @@ export function registerChatStreamHandlers() {
|
||||
"codebaseInfo: length",
|
||||
codebaseInfo.length,
|
||||
"estimated tokens",
|
||||
codebaseInfo.length / 4
|
||||
codebaseInfo.length / 4,
|
||||
);
|
||||
|
||||
// Prepare message history for the AI
|
||||
@@ -266,7 +266,7 @@ export function registerChatStreamHandlers() {
|
||||
systemPrompt += "\n\n" + SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT;
|
||||
}
|
||||
const isSummarizeIntent = req.prompt.startsWith(
|
||||
"Summarize from chat-id="
|
||||
"Summarize from chat-id=",
|
||||
);
|
||||
if (isSummarizeIntent) {
|
||||
systemPrompt = SUMMARIZE_CHAT_SYSTEM_PROMPT;
|
||||
@@ -276,7 +276,7 @@ export function registerChatStreamHandlers() {
|
||||
const hasImageAttachments =
|
||||
req.attachments &&
|
||||
req.attachments.some((attachment) =>
|
||||
attachment.type.startsWith("image/")
|
||||
attachment.type.startsWith("image/"),
|
||||
);
|
||||
|
||||
if (hasImageAttachments) {
|
||||
@@ -316,7 +316,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
// Replace the last message with one that includes attachments
|
||||
chatMessages[lastUserIndex] = await prepareMessageWithAttachments(
|
||||
lastUserMessage,
|
||||
attachmentPaths
|
||||
attachmentPaths,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -353,7 +353,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
(error as any)?.error?.message || JSON.stringify(error);
|
||||
event.sender.send(
|
||||
"chat:response:error",
|
||||
`Sorry, there was an error from the AI: ${message}`
|
||||
`Sorry, there was an error from the AI: ${message}`,
|
||||
);
|
||||
// Clean up the abort controller
|
||||
activeStreams.delete(req.chatId);
|
||||
@@ -374,7 +374,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
});
|
||||
fullResponse = fullResponse.replace(
|
||||
"$$SUPABASE_CLIENT_CODE$$",
|
||||
supabaseClientCode
|
||||
supabaseClientCode,
|
||||
);
|
||||
}
|
||||
// Store the current partial response
|
||||
@@ -421,13 +421,13 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
.where(eq(messages.id, placeholderAssistantMessage.id));
|
||||
|
||||
logger.log(
|
||||
`Updated cancelled response for placeholder message ${placeholderAssistantMessage.id} in chat ${chatId}`
|
||||
`Updated cancelled response for placeholder message ${placeholderAssistantMessage.id} in chat ${chatId}`,
|
||||
);
|
||||
partialResponses.delete(req.chatId);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error saving partial response for chat ${chatId}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -441,7 +441,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
if (!abortController.signal.aborted && fullResponse) {
|
||||
// Scrape from: <dyad-chat-summary>Renaming profile file</dyad-chat-title>
|
||||
const chatTitle = fullResponse.match(
|
||||
/<dyad-chat-summary>(.*?)<\/dyad-chat-summary>/
|
||||
/<dyad-chat-summary>(.*?)<\/dyad-chat-summary>/,
|
||||
);
|
||||
if (chatTitle) {
|
||||
await db
|
||||
@@ -461,7 +461,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
const status = await processFullResponseActions(
|
||||
fullResponse,
|
||||
req.chatId,
|
||||
{ chatSummary, messageId: placeholderAssistantMessage.id } // Use placeholder ID
|
||||
{ chatSummary, messageId: placeholderAssistantMessage.id }, // Use placeholder ID
|
||||
);
|
||||
|
||||
const chat = await db.query.chats.findFirst({
|
||||
@@ -481,7 +481,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
if (status.error) {
|
||||
event.sender.send(
|
||||
"chat:response:error",
|
||||
`Sorry, there was an error applying the AI's changes: ${status.error}`
|
||||
`Sorry, there was an error applying the AI's changes: ${status.error}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -505,12 +505,15 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
try {
|
||||
// We don't immediately delete files because they might be needed for reference
|
||||
// Instead, schedule them for deletion after some time
|
||||
setTimeout(async () => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
await unlink(filePath);
|
||||
logger.log(`Deleted temporary file: ${filePath}`);
|
||||
}
|
||||
}, 30 * 60 * 1000); // Delete after 30 minutes
|
||||
setTimeout(
|
||||
async () => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
await unlink(filePath);
|
||||
logger.log(`Deleted temporary file: ${filePath}`);
|
||||
}
|
||||
},
|
||||
30 * 60 * 1000,
|
||||
); // Delete after 30 minutes
|
||||
} catch (error) {
|
||||
logger.error(`Error scheduling file deletion: ${error}`);
|
||||
}
|
||||
@@ -523,7 +526,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
logger.error("Error calling LLM:", error);
|
||||
event.sender.send(
|
||||
"chat:response:error",
|
||||
`Sorry, there was an error processing your request: ${error}`
|
||||
`Sorry, there was an error processing your request: ${error}`,
|
||||
);
|
||||
// Clean up the abort controller
|
||||
activeStreams.delete(req.chatId);
|
||||
@@ -555,7 +558,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
}
|
||||
|
||||
export function formatMessages(
|
||||
messages: { role: string; content: string | undefined }[]
|
||||
messages: { role: string; content: string | undefined }[],
|
||||
) {
|
||||
return messages
|
||||
.map((m) => `<message role="${m.role}">${m.content}</message>`)
|
||||
@@ -566,7 +569,7 @@ export function formatMessages(
|
||||
async function replaceTextAttachmentWithContent(
|
||||
text: string,
|
||||
filePath: string,
|
||||
fileName: string
|
||||
fileName: string,
|
||||
): Promise<string> {
|
||||
try {
|
||||
if (await isTextFile(filePath)) {
|
||||
@@ -577,16 +580,16 @@ async function replaceTextAttachmentWithContent(
|
||||
const escapedPath = filePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const tagPattern = new RegExp(
|
||||
`<dyad-text-attachment filename="[^"]*" type="[^"]*" path="${escapedPath}">\\s*<\\/dyad-text-attachment>`,
|
||||
"g"
|
||||
"g",
|
||||
);
|
||||
|
||||
const replacedText = text.replace(
|
||||
tagPattern,
|
||||
`Full content of ${fileName}:\n\`\`\`\n${fullContent}\n\`\`\``
|
||||
`Full content of ${fileName}:\n\`\`\`\n${fullContent}\n\`\`\``,
|
||||
);
|
||||
|
||||
logger.log(
|
||||
`Replaced text attachment content for: ${fileName} - length before: ${text.length} - length after: ${replacedText.length}`
|
||||
`Replaced text attachment content for: ${fileName} - length before: ${text.length} - length after: ${replacedText.length}`,
|
||||
);
|
||||
return replacedText;
|
||||
}
|
||||
@@ -600,13 +603,13 @@ async function replaceTextAttachmentWithContent(
|
||||
// Helper function to convert traditional message to one with proper image attachments
|
||||
async function prepareMessageWithAttachments(
|
||||
message: CoreMessage,
|
||||
attachmentPaths: string[]
|
||||
attachmentPaths: string[],
|
||||
): Promise<CoreMessage> {
|
||||
let textContent = message.content;
|
||||
// Get the original text content
|
||||
if (typeof textContent !== "string") {
|
||||
logger.warn(
|
||||
"Message content is not a string - shouldn't happen but using message as-is"
|
||||
"Message content is not a string - shouldn't happen but using message as-is",
|
||||
);
|
||||
return message;
|
||||
}
|
||||
@@ -617,7 +620,7 @@ async function prepareMessageWithAttachments(
|
||||
textContent = await replaceTextAttachmentWithContent(
|
||||
textContent,
|
||||
filePath,
|
||||
fileName
|
||||
fileName,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export function registerDebugHandlers() {
|
||||
async (): Promise<SystemDebugInfo> => {
|
||||
console.log("IPC: get-system-debug-info called");
|
||||
return getSystemDebugInfo();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
@@ -151,7 +151,7 @@ export function registerDebugHandlers() {
|
||||
console.error(`Error in get-chat-logs:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
console.log("Registered debug IPC handlers");
|
||||
|
||||
@@ -10,7 +10,7 @@ export function registerDependencyHandlers() {
|
||||
"chat:add-dep",
|
||||
async (
|
||||
_event,
|
||||
{ chatId, packages }: { chatId: number; packages: string[] }
|
||||
{ chatId, packages }: { chatId: number; packages: string[] },
|
||||
) => {
|
||||
// Find the message from the database
|
||||
const foundMessages = await db.query.messages.findMany({
|
||||
@@ -39,13 +39,13 @@ export function registerDependencyHandlers() {
|
||||
.reverse()
|
||||
.find((m) =>
|
||||
m.content.includes(
|
||||
`<dyad-add-dependency packages="${packages.join(" ")}">`
|
||||
)
|
||||
`<dyad-add-dependency packages="${packages.join(" ")}">`,
|
||||
),
|
||||
);
|
||||
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`Message with packages ${packages.join(", ")} not found`
|
||||
`Message with packages ${packages.join(", ")} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,6 @@ export function registerDependencyHandlers() {
|
||||
message,
|
||||
appPath: getDyadAppPath(app.path),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
// Schedule next poll
|
||||
currentFlowState.timeoutId = setTimeout(
|
||||
() => pollForAccessToken(event),
|
||||
interval * 1000
|
||||
interval * 1000,
|
||||
);
|
||||
break;
|
||||
case "slow_down":
|
||||
@@ -143,7 +143,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
});
|
||||
currentFlowState.timeoutId = setTimeout(
|
||||
() => pollForAccessToken(event),
|
||||
newInterval * 1000
|
||||
newInterval * 1000,
|
||||
);
|
||||
break;
|
||||
case "expired_token":
|
||||
@@ -162,7 +162,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
break;
|
||||
default:
|
||||
logger.error(
|
||||
`Unknown GitHub error: ${data.error_description || data.error}`
|
||||
`Unknown GitHub error: ${data.error_description || data.error}`,
|
||||
);
|
||||
event.sender.send("github:flow-error", {
|
||||
error: `GitHub authorization error: ${
|
||||
@@ -202,7 +202,7 @@ function stopPolling() {
|
||||
|
||||
function handleStartGithubFlow(
|
||||
event: IpcMainInvokeEvent,
|
||||
args: { appId: number | null }
|
||||
args: { appId: number | null },
|
||||
) {
|
||||
logger.debug(`Received github:start-flow for appId: ${args.appId}`);
|
||||
|
||||
@@ -249,7 +249,7 @@ function handleStartGithubFlow(
|
||||
if (!res.ok) {
|
||||
return res.json().then((errData) => {
|
||||
throw new Error(
|
||||
`GitHub API Error: ${errData.error_description || res.statusText}`
|
||||
`GitHub API Error: ${errData.error_description || res.statusText}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -273,7 +273,7 @@ function handleStartGithubFlow(
|
||||
// Start polling after the initial interval
|
||||
currentFlowState.timeoutId = setTimeout(
|
||||
() => pollForAccessToken(event),
|
||||
currentFlowState.interval * 1000
|
||||
currentFlowState.interval * 1000,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -289,7 +289,7 @@ function handleStartGithubFlow(
|
||||
// --- GitHub Repo Availability Handler ---
|
||||
async function handleIsRepoAvailable(
|
||||
event: IpcMainInvokeEvent,
|
||||
{ org, repo }: { org: string; repo: string }
|
||||
{ org, repo }: { org: string; repo: string },
|
||||
) {
|
||||
try {
|
||||
// Get access token from settings
|
||||
@@ -327,7 +327,7 @@ async function handleIsRepoAvailable(
|
||||
// --- GitHub Create Repo Handler ---
|
||||
async function handleCreateRepo(
|
||||
event: IpcMainInvokeEvent,
|
||||
{ org, repo, appId }: { org: string; repo: string; appId: number }
|
||||
{ org, repo, appId }: { org: string; repo: string; appId: number },
|
||||
) {
|
||||
try {
|
||||
// Get access token from settings
|
||||
@@ -376,7 +376,7 @@ async function handleCreateRepo(
|
||||
// --- GitHub Push Handler ---
|
||||
async function handlePushToGithub(
|
||||
event: IpcMainInvokeEvent,
|
||||
{ appId }: { appId: number }
|
||||
{ appId }: { appId: number },
|
||||
) {
|
||||
try {
|
||||
// Get access token from settings
|
||||
@@ -424,7 +424,7 @@ async function handlePushToGithub(
|
||||
|
||||
async function handleDisconnectGithubRepo(
|
||||
event: IpcMainInvokeEvent,
|
||||
{ appId }: { appId: number }
|
||||
{ appId }: { appId: number },
|
||||
) {
|
||||
try {
|
||||
logger.log(`Disconnecting GitHub repo for appId: ${appId}`);
|
||||
@@ -464,6 +464,6 @@ export function registerGithubHandlers() {
|
||||
ipcMain.handle("github:create-repo", handleCreateRepo);
|
||||
ipcMain.handle("github:push", handlePushToGithub);
|
||||
ipcMain.handle("github:disconnect", (event, args: { appId: number }) =>
|
||||
handleDisconnectGithubRepo(event, args)
|
||||
handleDisconnectGithubRepo(event, args),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,15 +11,17 @@ export interface LMStudioModel {
|
||||
publisher: string;
|
||||
state: "loaded" | "not-loaded";
|
||||
max_context_length: number;
|
||||
quantization: string
|
||||
compatibility_type: string
|
||||
quantization: string;
|
||||
compatibility_type: string;
|
||||
arch: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
|
||||
try {
|
||||
const modelsResponse: Response = await fetch("http://localhost:1234/api/v0/models");
|
||||
const modelsResponse: Response = await fetch(
|
||||
"http://localhost:1234/api/v0/models",
|
||||
);
|
||||
if (!modelsResponse.ok) {
|
||||
throw new Error("Failed to fetch models from LM Studio");
|
||||
}
|
||||
@@ -30,7 +32,7 @@ export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
|
||||
.map((model: any) => ({
|
||||
modelName: model.id,
|
||||
displayName: model.id,
|
||||
provider: "lmstudio"
|
||||
provider: "lmstudio",
|
||||
}));
|
||||
|
||||
logger.info(`Successfully fetched ${models.length} models from LM Studio`);
|
||||
@@ -41,7 +43,10 @@ export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
|
||||
}
|
||||
|
||||
export function registerLMStudioHandlers() {
|
||||
ipcMain.handle('local-models:list-lmstudio', async (): Promise<LocalModelListResponse> => {
|
||||
return fetchLMStudioModels();
|
||||
});
|
||||
}
|
||||
ipcMain.handle(
|
||||
"local-models:list-lmstudio",
|
||||
async (): Promise<LocalModelListResponse> => {
|
||||
return fetchLMStudioModels();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,12 +31,13 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
|
||||
const ollamaModels: OllamaModel[] = data.models || [];
|
||||
|
||||
const models: LocalModel[] = ollamaModels.map((model: OllamaModel) => {
|
||||
const displayName = model.name.split(':')[0]
|
||||
.replace(/-/g, ' ')
|
||||
.replace(/(\d+)/, ' $1 ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
const displayName = model.name
|
||||
.split(":")[0]
|
||||
.replace(/-/g, " ")
|
||||
.replace(/(\d+)/, " $1 ")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
return {
|
||||
@@ -45,14 +46,18 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
|
||||
provider: "ollama",
|
||||
};
|
||||
});
|
||||
logger.info(`Successfully fetched ${models.length} models from Ollama`);
|
||||
return { models, error: null };
|
||||
logger.info(`Successfully fetched ${models.length} models from Ollama`);
|
||||
return { models, error: null };
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && (error as Error).message.includes('fetch failed')) {
|
||||
if (
|
||||
error instanceof TypeError &&
|
||||
(error as Error).message.includes("fetch failed")
|
||||
) {
|
||||
logger.error("Could not connect to Ollama");
|
||||
return {
|
||||
models: [],
|
||||
error: "Could not connect to Ollama. Make sure it's running at http://localhost:11434"
|
||||
error:
|
||||
"Could not connect to Ollama. Make sure it's running at http://localhost:11434",
|
||||
};
|
||||
}
|
||||
return { models: [], error: "Failed to fetch models from Ollama" };
|
||||
@@ -60,7 +65,10 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
|
||||
}
|
||||
|
||||
export function registerOllamaHandlers() {
|
||||
ipcMain.handle('local-models:list-ollama', async (): Promise<LocalModelListResponse> => {
|
||||
return fetchOllamaModels();
|
||||
});
|
||||
}
|
||||
ipcMain.handle(
|
||||
"local-models:list-ollama",
|
||||
async (): Promise<LocalModelListResponse> => {
|
||||
return fetchOllamaModels();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function registerNodeHandlers() {
|
||||
"handling ipc: nodejs-status for platform:",
|
||||
platform(),
|
||||
"and arch:",
|
||||
arch()
|
||||
arch(),
|
||||
);
|
||||
// Run checks in parallel
|
||||
const [nodeVersion, pnpmVersion] = await Promise.all([
|
||||
@@ -23,7 +23,7 @@ export function registerNodeHandlers() {
|
||||
// If not, try to install it using corepack.
|
||||
// If both fail, then pnpm is not available.
|
||||
runShellCommand(
|
||||
"pnpm --version || (corepack enable pnpm && pnpm --version) || (npm install -g pnpm@latest-10 && pnpm --version)"
|
||||
"pnpm --version || (corepack enable pnpm && pnpm --version) || (npm install -g pnpm@latest-10 && pnpm --version)",
|
||||
),
|
||||
]);
|
||||
// Default to mac download url.
|
||||
|
||||
@@ -80,7 +80,7 @@ function cleanupExpiredCacheEntries() {
|
||||
|
||||
if (expiredCount > 0) {
|
||||
logger.log(
|
||||
`Cleaned up ${expiredCount} expired codebase token cache entries`
|
||||
`Cleaned up ${expiredCount} expired codebase token cache entries`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ async function getCodebaseTokenCount(
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
messageContent: string,
|
||||
appPath: string
|
||||
appPath: string,
|
||||
): Promise<number> {
|
||||
// Clean up expired cache entries first
|
||||
cleanupExpiredCacheEntries();
|
||||
@@ -128,7 +128,7 @@ async function getCodebaseTokenCount(
|
||||
|
||||
const getProposalHandler = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
{ chatId }: { chatId: number }
|
||||
{ chatId }: { chatId: number },
|
||||
): Promise<ProposalResult | null> => {
|
||||
return withLock("get-proposal:" + chatId, async () => {
|
||||
logger.log(`IPC: get-proposal called for chatId: ${chatId}`);
|
||||
@@ -152,7 +152,7 @@ const getProposalHandler = async (
|
||||
) {
|
||||
const messageId = latestAssistantMessage.id; // Get the message ID
|
||||
logger.log(
|
||||
`Found latest assistant message (ID: ${messageId}), parsing content...`
|
||||
`Found latest assistant message (ID: ${messageId}), parsing content...`,
|
||||
);
|
||||
const messageContent = latestAssistantMessage.content;
|
||||
|
||||
@@ -211,7 +211,7 @@ const getProposalHandler = async (
|
||||
"files=",
|
||||
proposal.filesChanged.length,
|
||||
"packages=",
|
||||
proposal.packagesAdded.length
|
||||
proposal.packagesAdded.length,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -221,19 +221,23 @@ const getProposalHandler = async (
|
||||
};
|
||||
} else {
|
||||
logger.log(
|
||||
"No relevant tags found in the latest assistant message content."
|
||||
"No relevant tags found in the latest assistant message content.",
|
||||
);
|
||||
}
|
||||
}
|
||||
const actions: ActionProposal["actions"] = [];
|
||||
if (latestAssistantMessage?.content) {
|
||||
const writeTags = getDyadWriteTags(latestAssistantMessage.content);
|
||||
const refactorTarget = writeTags.reduce((largest, tag) => {
|
||||
const lineCount = tag.content.split("\n").length;
|
||||
return lineCount > 500 && (!largest || lineCount > largest.lineCount)
|
||||
? { path: tag.path, lineCount }
|
||||
: largest;
|
||||
}, null as { path: string; lineCount: number } | null);
|
||||
const refactorTarget = writeTags.reduce(
|
||||
(largest, tag) => {
|
||||
const lineCount = tag.content.split("\n").length;
|
||||
return lineCount > 500 &&
|
||||
(!largest || lineCount > largest.lineCount)
|
||||
? { path: tag.path, lineCount }
|
||||
: largest;
|
||||
},
|
||||
null as { path: string; lineCount: number } | null,
|
||||
);
|
||||
if (refactorTarget) {
|
||||
actions.push({
|
||||
id: "refactor-file",
|
||||
@@ -288,7 +292,7 @@ const getProposalHandler = async (
|
||||
chatId,
|
||||
latestAssistantMessage.id,
|
||||
latestAssistantMessage.content || "",
|
||||
chat.app.path
|
||||
chat.app.path,
|
||||
);
|
||||
|
||||
const totalTokens = messagesTokenCount + codebaseTokenCount;
|
||||
@@ -296,13 +300,13 @@ const getProposalHandler = async (
|
||||
logger.log(
|
||||
`Token usage: ${totalTokens}/${contextWindow} (${
|
||||
(totalTokens / contextWindow) * 100
|
||||
}%)`
|
||||
}%)`,
|
||||
);
|
||||
|
||||
// If we're using more than 80% of the context window, suggest summarizing
|
||||
if (totalTokens > contextWindow * 0.8) {
|
||||
logger.log(
|
||||
`Token usage high (${totalTokens}/${contextWindow}), suggesting summarize action`
|
||||
`Token usage high (${totalTokens}/${contextWindow}), suggesting summarize action`,
|
||||
);
|
||||
actions.push({
|
||||
id: "summarize-in-new-chat",
|
||||
@@ -330,14 +334,14 @@ const getProposalHandler = async (
|
||||
// Handler to approve a proposal (process actions and update message)
|
||||
const approveProposalHandler = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
{ chatId, messageId }: { chatId: number; messageId: number }
|
||||
{ chatId, messageId }: { chatId: number; messageId: number },
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
uncommittedFiles?: string[];
|
||||
}> => {
|
||||
logger.log(
|
||||
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
||||
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -346,7 +350,7 @@ const approveProposalHandler = async (
|
||||
where: and(
|
||||
eq(messages.id, messageId),
|
||||
eq(messages.chatId, chatId),
|
||||
eq(messages.role, "assistant")
|
||||
eq(messages.role, "assistant"),
|
||||
),
|
||||
columns: {
|
||||
content: true,
|
||||
@@ -355,7 +359,7 @@ const approveProposalHandler = async (
|
||||
|
||||
if (!messageToApprove?.content) {
|
||||
logger.error(
|
||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`,
|
||||
);
|
||||
return { success: false, error: "Assistant message not found." };
|
||||
}
|
||||
@@ -368,13 +372,13 @@ const approveProposalHandler = async (
|
||||
{
|
||||
chatSummary: chatSummary ?? undefined,
|
||||
messageId,
|
||||
} // Pass summary if found
|
||||
}, // Pass summary if found
|
||||
);
|
||||
|
||||
if (processResult.error) {
|
||||
logger.error(
|
||||
`Error processing actions for message ${messageId}:`,
|
||||
processResult.error
|
||||
processResult.error,
|
||||
);
|
||||
// Optionally: Update message state to 'error' or similar?
|
||||
// For now, just return error to frontend
|
||||
@@ -397,10 +401,10 @@ const approveProposalHandler = async (
|
||||
// Handler to reject a proposal (just update message state)
|
||||
const rejectProposalHandler = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
{ chatId, messageId }: { chatId: number; messageId: number }
|
||||
{ chatId, messageId }: { chatId: number; messageId: number },
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
logger.log(
|
||||
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
||||
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -409,14 +413,14 @@ const rejectProposalHandler = async (
|
||||
where: and(
|
||||
eq(messages.id, messageId),
|
||||
eq(messages.chatId, chatId),
|
||||
eq(messages.role, "assistant")
|
||||
eq(messages.role, "assistant"),
|
||||
),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (!messageToReject) {
|
||||
logger.error(
|
||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`,
|
||||
);
|
||||
return { success: false, error: "Assistant message not found." };
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function registerSettingsHandlers() {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
settings.providerSettings,
|
||||
providerKey
|
||||
providerKey,
|
||||
)
|
||||
) {
|
||||
const providerSetting = settings.providerSettings[providerKey];
|
||||
@@ -35,6 +35,6 @@ export function registerSettingsHandlers() {
|
||||
async (_, settings: Partial<UserSettings>) => {
|
||||
writeSettings(settings);
|
||||
return readSettings();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function registerSupabaseHandlers() {
|
||||
logger.error("Error setting Supabase project for app:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Unset app project - removes the link between a Dyad app and a Supabase project
|
||||
@@ -58,6 +58,6 @@ export function registerSupabaseHandlers() {
|
||||
logger.error("Error unsetting Supabase project for app:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function streamTestResponse(
|
||||
chatId: number,
|
||||
testResponse: string,
|
||||
abortController: AbortController,
|
||||
updatedChat: any
|
||||
updatedChat: any,
|
||||
): Promise<string> {
|
||||
console.log(`Using canned response for test prompt`);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export function registerTokenCountHandlers() {
|
||||
}
|
||||
|
||||
const systemPromptTokens = estimateTokens(
|
||||
systemPrompt + supabaseContext
|
||||
systemPrompt + supabaseContext,
|
||||
);
|
||||
|
||||
// Extract codebase information if app is associated with the chat
|
||||
@@ -75,7 +75,7 @@ export function registerTokenCountHandlers() {
|
||||
codebaseInfo = await extractCodebase(appPath);
|
||||
codebaseTokens = estimateTokens(codebaseInfo);
|
||||
logger.log(
|
||||
`Extracted codebase information from ${appPath}, tokens: ${codebaseTokens}`
|
||||
`Extracted codebase information from ${appPath}, tokens: ${codebaseTokens}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error extracting codebase:", error);
|
||||
@@ -101,6 +101,6 @@ export function registerTokenCountHandlers() {
|
||||
logger.error("Error counting tokens:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function registerUploadHandlers() {
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Upload failed with status ${response.status}: ${response.statusText}`
|
||||
`Upload failed with status ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export function registerUploadHandlers() {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
logger.debug("Registered upload IPC handlers");
|
||||
|
||||
@@ -95,14 +95,17 @@ export function registerVersionHandlers() {
|
||||
errorMessage: `Failed to get current branch: ${error.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"revert-version",
|
||||
async (
|
||||
_,
|
||||
{ appId, previousVersionId }: { appId: number; previousVersionId: string }
|
||||
{
|
||||
appId,
|
||||
previousVersionId,
|
||||
}: { appId: number; previousVersionId: string },
|
||||
) => {
|
||||
return withLock(appId, async () => {
|
||||
const app = await db.query.apps.findFirst({
|
||||
@@ -198,13 +201,13 @@ export function registerVersionHandlers() {
|
||||
const messagesToDelete = await db.query.messages.findMany({
|
||||
where: and(
|
||||
eq(messages.chatId, chatId),
|
||||
gt(messages.id, messageWithCommit.id)
|
||||
gt(messages.id, messageWithCommit.id),
|
||||
),
|
||||
orderBy: desc(messages.id),
|
||||
});
|
||||
|
||||
logger.log(
|
||||
`Deleting ${messagesToDelete.length} messages after commit ${previousVersionId} from chat ${chatId}`
|
||||
`Deleting ${messagesToDelete.length} messages after commit ${previousVersionId} from chat ${chatId}`,
|
||||
);
|
||||
|
||||
// Delete the messages
|
||||
@@ -214,8 +217,8 @@ export function registerVersionHandlers() {
|
||||
.where(
|
||||
and(
|
||||
eq(messages.chatId, chatId),
|
||||
gt(messages.id, messageWithCommit.id)
|
||||
)
|
||||
gt(messages.id, messageWithCommit.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -224,12 +227,12 @@ export function registerVersionHandlers() {
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`Error reverting to version ${previousVersionId} for app ${appId}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to revert version: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
@@ -259,11 +262,11 @@ export function registerVersionHandlers() {
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`Error checking out version ${versionId} for app ${appId}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to checkout version: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export class IpcClient {
|
||||
} else {
|
||||
console.warn(
|
||||
`[IPC] No callbacks found for chat ${chatId}`,
|
||||
this.chatStreams
|
||||
this.chatStreams,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -119,7 +119,9 @@ export class IpcClient {
|
||||
this.chatStreams.delete(chatId);
|
||||
} else {
|
||||
console.error(
|
||||
new Error(`[IPC] No callbacks found for chat ${chatId} on stream end`)
|
||||
new Error(
|
||||
`[IPC] No callbacks found for chat ${chatId} on stream end`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -220,7 +222,7 @@ export class IpcClient {
|
||||
public async editAppFile(
|
||||
appId: number,
|
||||
filePath: string,
|
||||
content: string
|
||||
content: string,
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("edit-app-file", {
|
||||
@@ -245,7 +247,7 @@ export class IpcClient {
|
||||
onUpdate: (messages: Message[]) => void;
|
||||
onEnd: (response: ChatResponseEnd) => void;
|
||||
onError: (error: string) => void;
|
||||
}
|
||||
},
|
||||
): void {
|
||||
const { chatId, redo, attachments, onUpdate, onEnd, onError } = options;
|
||||
this.chatStreams.set(chatId, { onUpdate, onEnd, onError });
|
||||
@@ -268,9 +270,9 @@ export class IpcClient {
|
||||
reader.onerror = () =>
|
||||
reject(new Error(`Failed to read file: ${file.name}`));
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.then((fileDataArray) => {
|
||||
// Use invoke to start the stream and pass the chatId and attachments
|
||||
@@ -331,7 +333,7 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public async deleteChat(
|
||||
chatId: number
|
||||
chatId: number,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("delete-chat", chatId);
|
||||
@@ -343,7 +345,7 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public async deleteMessages(
|
||||
chatId: number
|
||||
chatId: number,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("delete-messages", chatId);
|
||||
@@ -356,7 +358,7 @@ export class IpcClient {
|
||||
|
||||
// Open an external URL using the default browser
|
||||
public async openExternalUrl(
|
||||
url: string
|
||||
url: string,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("open-external-url", url);
|
||||
@@ -368,12 +370,12 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public async showItemInFolder(
|
||||
fullPath: string
|
||||
fullPath: string,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke(
|
||||
"show-item-in-folder",
|
||||
fullPath
|
||||
fullPath,
|
||||
);
|
||||
return result as { success: boolean; error?: string };
|
||||
} catch (error) {
|
||||
@@ -385,7 +387,7 @@ export class IpcClient {
|
||||
// Run an app
|
||||
public async runApp(
|
||||
appId: number,
|
||||
onOutput: (output: AppOutput) => void
|
||||
onOutput: (output: AppOutput) => void,
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("run-app", { appId });
|
||||
@@ -412,7 +414,7 @@ export class IpcClient {
|
||||
public async restartApp(
|
||||
appId: number,
|
||||
onOutput: (output: AppOutput) => void,
|
||||
removeNodeModules?: boolean
|
||||
removeNodeModules?: boolean,
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("restart-app", {
|
||||
@@ -512,12 +514,12 @@ export class IpcClient {
|
||||
|
||||
// Update user settings
|
||||
public async setUserSettings(
|
||||
settings: Partial<UserSettings>
|
||||
settings: Partial<UserSettings>,
|
||||
): Promise<UserSettings> {
|
||||
try {
|
||||
const updatedSettings = await this.ipcRenderer.invoke(
|
||||
"set-user-settings",
|
||||
settings
|
||||
settings,
|
||||
);
|
||||
return updatedSettings;
|
||||
} catch (error) {
|
||||
@@ -606,7 +608,7 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public onGithubDeviceFlowUpdate(
|
||||
callback: (data: GitHubDeviceFlowUpdateData) => void
|
||||
callback: (data: GitHubDeviceFlowUpdateData) => void,
|
||||
): () => void {
|
||||
const listener = (data: any) => {
|
||||
console.log("github:flow-update", data);
|
||||
@@ -620,7 +622,7 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public onGithubDeviceFlowSuccess(
|
||||
callback: (data: GitHubDeviceFlowSuccessData) => void
|
||||
callback: (data: GitHubDeviceFlowSuccessData) => void,
|
||||
): () => void {
|
||||
const listener = (data: any) => {
|
||||
console.log("github:flow-success", data);
|
||||
@@ -633,7 +635,7 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public onGithubDeviceFlowError(
|
||||
callback: (data: GitHubDeviceFlowErrorData) => void
|
||||
callback: (data: GitHubDeviceFlowErrorData) => void,
|
||||
): () => void {
|
||||
const listener = (data: any) => {
|
||||
console.log("github:flow-error", data);
|
||||
@@ -654,7 +656,7 @@ export class IpcClient {
|
||||
// --- GitHub Repo Management ---
|
||||
public async checkGithubRepoAvailable(
|
||||
org: string,
|
||||
repo: string
|
||||
repo: string,
|
||||
): Promise<{ available: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("github:is-repo-available", {
|
||||
@@ -670,7 +672,7 @@ export class IpcClient {
|
||||
public async createGithubRepo(
|
||||
org: string,
|
||||
repo: string,
|
||||
appId: number
|
||||
appId: number,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("github:create-repo", {
|
||||
@@ -686,7 +688,7 @@ export class IpcClient {
|
||||
|
||||
// Sync (push) local repo to GitHub
|
||||
public async syncGithubRepo(
|
||||
appId: number
|
||||
appId: number,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("github:push", { appId });
|
||||
@@ -698,7 +700,7 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public async disconnectGithubRepo(
|
||||
appId: number
|
||||
appId: number,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("github:disconnect", {
|
||||
@@ -800,7 +802,7 @@ export class IpcClient {
|
||||
|
||||
public async setSupabaseAppProject(
|
||||
project: string,
|
||||
app: number
|
||||
app: number,
|
||||
): Promise<{ success: boolean; appId: number; projectId: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("supabase:set-app-project", {
|
||||
@@ -815,14 +817,14 @@ export class IpcClient {
|
||||
}
|
||||
|
||||
public async unsetSupabaseAppProject(
|
||||
app: number
|
||||
app: number,
|
||||
): Promise<{ success: boolean; appId: number }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke(
|
||||
"supabase:unset-app-project",
|
||||
{
|
||||
app,
|
||||
}
|
||||
},
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -856,7 +858,7 @@ export class IpcClient {
|
||||
public async uploadToSignedUrl(
|
||||
url: string,
|
||||
contentType: string,
|
||||
data: any
|
||||
data: any,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("upload-to-signed-url", {
|
||||
@@ -874,7 +876,7 @@ export class IpcClient {
|
||||
public async listLocalOllamaModels(): Promise<LocalModel[]> {
|
||||
try {
|
||||
const response = await this.ipcRenderer.invoke(
|
||||
"local-models:list-ollama"
|
||||
"local-models:list-ollama",
|
||||
);
|
||||
return response?.models || [];
|
||||
} catch (error) {
|
||||
@@ -888,7 +890,7 @@ export class IpcClient {
|
||||
public async listLocalLMStudioModels(): Promise<LocalModel[]> {
|
||||
try {
|
||||
const response = await this.ipcRenderer.invoke(
|
||||
"local-models:list-lmstudio"
|
||||
"local-models:list-lmstudio",
|
||||
);
|
||||
return response?.models || [];
|
||||
} catch (error) {
|
||||
@@ -896,14 +898,14 @@ export class IpcClient {
|
||||
throw new Error(`Failed to fetch LM Studio models: ${error.message}`);
|
||||
}
|
||||
throw new Error(
|
||||
"Failed to fetch LM Studio models: Unknown error occurred"
|
||||
"Failed to fetch LM Studio models: Unknown error occurred",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for deep link events
|
||||
public onDeepLinkReceived(
|
||||
callback: (data: DeepLinkData) => void
|
||||
callback: (data: DeepLinkData) => void,
|
||||
): () => void {
|
||||
const listener = (data: any) => {
|
||||
callback(data as DeepLinkData);
|
||||
@@ -916,7 +918,7 @@ export class IpcClient {
|
||||
|
||||
// Count tokens for a chat and input
|
||||
public async countTokens(
|
||||
params: TokenCountParams
|
||||
params: TokenCountParams,
|
||||
): Promise<TokenCountResult> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("chat:count-tokens", params);
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function executeAddDependency({
|
||||
`(pnpm add ${packageStr}) || (npm install ${packageStr})`,
|
||||
{
|
||||
cwd: appPath,
|
||||
}
|
||||
},
|
||||
);
|
||||
const installResults = stdout + (stderr ? `\n${stderr}` : "");
|
||||
|
||||
@@ -30,13 +30,13 @@ export async function executeAddDependency({
|
||||
const updatedContent = message.content.replace(
|
||||
new RegExp(
|
||||
`<dyad-add-dependency packages="${packages.join(
|
||||
" "
|
||||
" ",
|
||||
)}">[^<]*</dyad-add-dependency>`,
|
||||
"g"
|
||||
"g",
|
||||
),
|
||||
`<dyad-add-dependency packages="${packages.join(
|
||||
" "
|
||||
)}">${installResults}</dyad-add-dependency>`
|
||||
" ",
|
||||
)}">${installResults}</dyad-add-dependency>`,
|
||||
);
|
||||
|
||||
// Save the updated message back to the database
|
||||
|
||||
@@ -56,7 +56,7 @@ export function getDyadWriteTags(fullResponse: string): {
|
||||
} else {
|
||||
logger.warn(
|
||||
"Found <dyad-write> tag without a valid 'path' attribute:",
|
||||
match[0]
|
||||
match[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ export async function processFullResponseActions(
|
||||
{
|
||||
chatSummary,
|
||||
messageId,
|
||||
}: { chatSummary: string | undefined; messageId: number }
|
||||
}: { chatSummary: string | undefined; messageId: number },
|
||||
): Promise<{
|
||||
updatedFiles?: boolean;
|
||||
error?: string;
|
||||
@@ -216,7 +216,7 @@ export async function processFullResponseActions(
|
||||
where: and(
|
||||
eq(messages.id, messageId),
|
||||
eq(messages.role, "assistant"),
|
||||
eq(messages.chatId, chatId)
|
||||
eq(messages.chatId, chatId),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -254,7 +254,7 @@ export async function processFullResponseActions(
|
||||
} catch (error) {
|
||||
errors.push({
|
||||
message: `Failed to add dependencies: ${dyadAddDependencyPackages.join(
|
||||
", "
|
||||
", ",
|
||||
)}`,
|
||||
error: error,
|
||||
});
|
||||
@@ -433,7 +433,7 @@ export async function processFullResponseActions(
|
||||
changes.push(`deleted ${deletedFiles.length} file(s)`);
|
||||
if (dyadAddDependencyPackages.length > 0)
|
||||
changes.push(
|
||||
`added ${dyadAddDependencyPackages.join(", ")} package(s)`
|
||||
`added ${dyadAddDependencyPackages.join(", ")} package(s)`,
|
||||
);
|
||||
if (dyadExecuteSqlQueries.length > 0)
|
||||
changes.push(`executed ${dyadExecuteSqlQueries.length} SQL queries`);
|
||||
@@ -486,13 +486,13 @@ export async function processFullResponseActions(
|
||||
${warnings
|
||||
.map(
|
||||
(warning) =>
|
||||
`<dyad-output type="warning" message="${warning.message}">${warning.error}</dyad-output>`
|
||||
`<dyad-output type="warning" message="${warning.message}">${warning.error}</dyad-output>`,
|
||||
)
|
||||
.join("\n")}
|
||||
${errors
|
||||
.map(
|
||||
(error) =>
|
||||
`<dyad-output type="error" message="${error.message}">${error.error}</dyad-output>`
|
||||
`<dyad-output type="error" message="${error.message}">${error.error}</dyad-output>`,
|
||||
)
|
||||
.join("\n")}
|
||||
`;
|
||||
|
||||
@@ -35,7 +35,7 @@ export function getFilesRecursively(dir: string, baseDir: string): string[] {
|
||||
|
||||
export async function copyDirectoryRecursive(
|
||||
source: string,
|
||||
destination: string
|
||||
destination: string,
|
||||
) {
|
||||
await fsPromises.mkdir(destination, { recursive: true });
|
||||
const entries = await fsPromises.readdir(source, { withFileTypes: true });
|
||||
|
||||
@@ -17,7 +17,7 @@ import log from "electron-log";
|
||||
const logger = log.scope("getModelClient");
|
||||
export function getModelClient(
|
||||
model: LargeLanguageModel,
|
||||
settings: UserSettings
|
||||
settings: UserSettings,
|
||||
) {
|
||||
const dyadApiKey = settings.providerSettings?.auto?.apiKey?.value;
|
||||
// Handle 'auto' provider by trying each model in AUTO_MODELS until one works
|
||||
@@ -31,7 +31,7 @@ export function getModelClient(
|
||||
|
||||
if (apiKey) {
|
||||
logger.log(
|
||||
`Using provider: ${autoModel.provider} model: ${autoModel.name}`
|
||||
`Using provider: ${autoModel.provider} model: ${autoModel.name}`,
|
||||
);
|
||||
// Use the first model that has an API key
|
||||
return getModelClient(
|
||||
@@ -39,7 +39,7 @@ export function getModelClient(
|
||||
provider: autoModel.provider,
|
||||
name: autoModel.name,
|
||||
} as LargeLanguageModel,
|
||||
settings
|
||||
settings,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ const DEFAULT_MAX_TOKENS = 8_000;
|
||||
export function getMaxTokens(model: LargeLanguageModel) {
|
||||
if (!MODEL_OPTIONS[model.provider as keyof typeof MODEL_OPTIONS]) {
|
||||
logger.warn(
|
||||
`Model provider ${model.provider} not found in MODEL_OPTIONS. Using default max tokens.`
|
||||
`Model provider ${model.provider} not found in MODEL_OPTIONS. Using default max tokens.`,
|
||||
);
|
||||
return DEFAULT_MAX_TOKENS;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function acquireLock(lockId: number | string): {
|
||||
*/
|
||||
export async function withLock<T>(
|
||||
lockId: number | string,
|
||||
fn: () => Promise<T>
|
||||
fn: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
// Wait for any existing operation to complete
|
||||
const existingLock = locks.get(lockId);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function killProcess(process: ChildProcess): Promise<void> {
|
||||
// Add timeout to prevent hanging
|
||||
const timeout = setTimeout(() => {
|
||||
console.warn(
|
||||
`Timeout waiting for process (PID: ${process.pid}) to close. Force killing may be needed.`
|
||||
`Timeout waiting for process (PID: ${process.pid}) to close. Force killing may be needed.`,
|
||||
);
|
||||
resolve();
|
||||
}, 5000); // 5-second timeout
|
||||
@@ -44,7 +44,7 @@ export function killProcess(process: ChildProcess): Promise<void> {
|
||||
process.on("close", (code, signal) => {
|
||||
clearTimeout(timeout);
|
||||
console.log(
|
||||
`Received 'close' event for process (PID: ${process.pid}) with code ${code}, signal ${signal}.`
|
||||
`Received 'close' event for process (PID: ${process.pid}) with code ${code}, signal ${signal}.`,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
@@ -53,7 +53,7 @@ export function killProcess(process: ChildProcess): Promise<void> {
|
||||
process.on("error", (err) => {
|
||||
clearTimeout(timeout);
|
||||
console.error(
|
||||
`Error during stop sequence for process (PID: ${process.pid}): ${err.message}`
|
||||
`Error during stop sequence for process (PID: ${process.pid}): ${err.message}`,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
@@ -62,16 +62,16 @@ export function killProcess(process: ChildProcess): Promise<void> {
|
||||
if (process.pid) {
|
||||
// Use tree-kill to terminate the entire process tree
|
||||
console.log(
|
||||
`Attempting to tree-kill process tree starting at PID ${process.pid}.`
|
||||
`Attempting to tree-kill process tree starting at PID ${process.pid}.`,
|
||||
);
|
||||
treeKill(process.pid, "SIGTERM", (err: Error | undefined) => {
|
||||
if (err) {
|
||||
console.warn(
|
||||
`tree-kill error for PID ${process.pid}: ${err.message}`
|
||||
`tree-kill error for PID ${process.pid}: ${err.message}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`tree-kill signal sent successfully to PID ${process.pid}.`
|
||||
`tree-kill signal sent successfully to PID ${process.pid}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -88,17 +88,17 @@ export function killProcess(process: ChildProcess): Promise<void> {
|
||||
*/
|
||||
export function removeAppIfCurrentProcess(
|
||||
appId: number,
|
||||
process: ChildProcess
|
||||
process: ChildProcess,
|
||||
): void {
|
||||
const currentAppInfo = runningApps.get(appId);
|
||||
if (currentAppInfo && currentAppInfo.process === process) {
|
||||
runningApps.delete(appId);
|
||||
console.log(
|
||||
`Removed app ${appId} (processId ${currentAppInfo.processId}) from running map. Current size: ${runningApps.size}`
|
||||
`Removed app ${appId} (processId ${currentAppInfo.processId}) from running map. Current size: ${runningApps.size}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`App ${appId} process was already removed or replaced in running map. Ignoring.`
|
||||
`App ${appId} process was already removed or replaced in running map. Ignoring.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function runShellCommand(command: string): Promise<string | null> {
|
||||
process.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
logger.debug(
|
||||
`Command "${command}" succeeded with code ${code}: ${output.trim()}`
|
||||
`Command "${command}" succeeded with code ${code}: ${output.trim()}`,
|
||||
);
|
||||
resolve(output.trim()); // Command succeeded, return trimmed output
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const estimateTokens = (text: string): number => {
|
||||
export const estimateMessagesTokens = (messages: Message[]): number => {
|
||||
return messages.reduce(
|
||||
(acc, message) => acc + estimateTokens(message.content),
|
||||
0
|
||||
0,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export function getContextWindow() {
|
||||
const model = settings.selectedModel;
|
||||
if (!MODEL_OPTIONS[model.provider as keyof typeof MODEL_OPTIONS]) {
|
||||
logger.warn(
|
||||
`Model provider ${model.provider} not found in MODEL_OPTIONS. Using default max tokens.`
|
||||
`Model provider ${model.provider} not found in MODEL_OPTIONS. Using default max tokens.`,
|
||||
);
|
||||
return DEFAULT_CONTEXT_WINDOW;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { CreateAppParams, CreateAppResult } from "../ipc/ipc_types";
|
||||
* @returns The created app and chatId
|
||||
*/
|
||||
export async function createApp(
|
||||
params: CreateAppParams
|
||||
params: CreateAppParams,
|
||||
): Promise<CreateAppResult> {
|
||||
try {
|
||||
return await IpcClient.getInstance().createApp(params);
|
||||
|
||||
@@ -41,7 +41,7 @@ const providers = [
|
||||
export const ModelProviderSchema = z.enum(providers);
|
||||
|
||||
export const cloudProviders = providers.filter(
|
||||
(provider) => provider !== "ollama" && provider !== "lmstudio"
|
||||
(provider) => provider !== "ollama" && provider !== "lmstudio",
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ export const showLoading = <T>(
|
||||
loadingMessage: string,
|
||||
promise: Promise<T>,
|
||||
successMessage?: string,
|
||||
errorMessage?: string
|
||||
errorMessage?: string,
|
||||
) => {
|
||||
return toast.promise(promise, {
|
||||
loading: loadingMessage,
|
||||
@@ -61,7 +61,7 @@ export const showLoading = <T>(
|
||||
export const showUncommittedFilesWarning = (files: string[]) => {
|
||||
showWarning(
|
||||
`Some changed files were not committed. Please use git to manually commit them.
|
||||
\n\n${files.join("\n")}`
|
||||
\n\n${files.join("\n")}`,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
10
src/main.ts
10
src/main.ts
@@ -115,7 +115,7 @@ const createWindow = () => {
|
||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
||||
} else {
|
||||
mainWindow.loadFile(
|
||||
path.join(__dirname, "../renderer/main_window/index.html")
|
||||
path.join(__dirname, "../renderer/main_window/index.html"),
|
||||
);
|
||||
}
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
@@ -158,12 +158,12 @@ function handleDeepLinkReturn(url: string) {
|
||||
"Handling deep link: protocol",
|
||||
parsed.protocol,
|
||||
"hostname",
|
||||
parsed.hostname
|
||||
parsed.hostname,
|
||||
);
|
||||
if (parsed.protocol !== "dyad:") {
|
||||
dialog.showErrorBox(
|
||||
"Invalid Protocol",
|
||||
`Expected dyad://, got ${parsed.protocol}. Full URL: ${url}`
|
||||
`Expected dyad://, got ${parsed.protocol}. Full URL: ${url}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ function handleDeepLinkReturn(url: string) {
|
||||
if (!token || !refreshToken || !expiresIn) {
|
||||
dialog.showErrorBox(
|
||||
"Invalid URL",
|
||||
"Expected token, refreshToken, and expiresIn"
|
||||
"Expected token, refreshToken, and expiresIn",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -196,7 +196,7 @@ function handleDeepLinkReturn(url: string) {
|
||||
if (!apiKey) {
|
||||
dialog.showErrorBox(
|
||||
"Invalid URL",
|
||||
"Expected key, budget_reset_at, and max_budget"
|
||||
"Expected key, budget_reset_at, and max_budget",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,25 +93,25 @@ export function writeSettings(settings: Partial<UserSettings>): void {
|
||||
const newSettings = { ...currentSettings, ...settings };
|
||||
if (newSettings.githubAccessToken) {
|
||||
newSettings.githubAccessToken = encrypt(
|
||||
newSettings.githubAccessToken.value
|
||||
newSettings.githubAccessToken.value,
|
||||
);
|
||||
}
|
||||
if (newSettings.supabase) {
|
||||
if (newSettings.supabase.accessToken) {
|
||||
newSettings.supabase.accessToken = encrypt(
|
||||
newSettings.supabase.accessToken.value
|
||||
newSettings.supabase.accessToken.value,
|
||||
);
|
||||
}
|
||||
if (newSettings.supabase.refreshToken) {
|
||||
newSettings.supabase.refreshToken = encrypt(
|
||||
newSettings.supabase.refreshToken.value
|
||||
newSettings.supabase.refreshToken.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const provider in newSettings.providerSettings) {
|
||||
if (newSettings.providerSettings[provider].apiKey) {
|
||||
newSettings.providerSettings[provider].apiKey = encrypt(
|
||||
newSettings.providerSettings[provider].apiKey.value
|
||||
newSettings.providerSettings[provider].apiKey.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function AppDetailsPage() {
|
||||
alert(
|
||||
`Error renaming app: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
} finally {
|
||||
setIsRenaming(false);
|
||||
@@ -134,7 +134,7 @@ export default function AppDetailsPage() {
|
||||
alert(
|
||||
`Error renaming folder: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
} finally {
|
||||
setIsRenamingFolder(false);
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function ChatPage() {
|
||||
id="preview-panel"
|
||||
minSize={20}
|
||||
className={cn(
|
||||
!isResizing && "transition-all duration-100 ease-in-out"
|
||||
!isResizing && "transition-all duration-100 ease-in-out",
|
||||
)}
|
||||
>
|
||||
<PreviewPanel />
|
||||
|
||||
@@ -228,7 +228,7 @@ export default function HomePage() {
|
||||
onClick={() =>
|
||||
window.open(
|
||||
releaseUrl.replace("?hideHeader=true&theme=" + theme, ""),
|
||||
"_blank"
|
||||
"_blank",
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function SettingsPage() {
|
||||
} catch (error) {
|
||||
console.error("Error resetting:", error);
|
||||
showError(
|
||||
error instanceof Error ? error.message : "An unknown error occurred"
|
||||
error instanceof Error ? error.message : "An unknown error occurred",
|
||||
);
|
||||
} finally {
|
||||
setIsResetting(false);
|
||||
|
||||
@@ -89,7 +89,7 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
},
|
||||
on: (
|
||||
channel: ValidReceiveChannel,
|
||||
listener: (...args: unknown[]) => void
|
||||
listener: (...args: unknown[]) => void,
|
||||
) => {
|
||||
if (validReceiveChannels.includes(channel)) {
|
||||
const subscription = (
|
||||
@@ -110,7 +110,7 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
},
|
||||
removeListener: (
|
||||
channel: ValidReceiveChannel,
|
||||
listener: (...args: unknown[]) => void
|
||||
listener: (...args: unknown[]) => void,
|
||||
) => {
|
||||
if (validReceiveChannels.includes(channel)) {
|
||||
ipcRenderer.removeListener(channel, listener);
|
||||
|
||||
@@ -36,12 +36,12 @@ const posthogClient = posthog.init(
|
||||
"Telemetry opted in - UUID:",
|
||||
telemetryUserId,
|
||||
"sending event",
|
||||
event
|
||||
event,
|
||||
);
|
||||
return event;
|
||||
},
|
||||
persistence: "localStorage",
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function App() {
|
||||
@@ -74,5 +74,5 @@ createRoot(document.getElementById("root")!).render(
|
||||
<PostHogProvider client={posthogClient}>
|
||||
<App />
|
||||
</PostHogProvider>
|
||||
</StrictMode>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -5,48 +5,54 @@
|
||||
/* Load Geist Font */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
src: url('/node_modules/geist/dist/fonts/geist-mono/GeistMono-Regular.ttf') format('truetype');
|
||||
font-family: "Geist Mono";
|
||||
src: url("/node_modules/geist/dist/fonts/geist-mono/GeistMono-Regular.ttf")
|
||||
format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
src: url('/node_modules/geist/dist/fonts/geist-mono/GeistMono-Medium.ttf') format('truetype');
|
||||
font-family: "Geist Mono";
|
||||
src: url("/node_modules/geist/dist/fonts/geist-mono/GeistMono-Medium.ttf")
|
||||
format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
src: url('/node_modules/geist/dist/fonts/geist-mono/GeistMono-Bold.ttf') format('truetype');
|
||||
font-family: "Geist Mono";
|
||||
src: url("/node_modules/geist/dist/fonts/geist-mono/GeistMono-Bold.ttf")
|
||||
format("truetype");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
src: url('/node_modules/geist/dist/fonts/geist-sans/Geist-Regular.ttf') format('truetype');
|
||||
font-family: "Geist";
|
||||
src: url("/node_modules/geist/dist/fonts/geist-sans/Geist-Regular.ttf")
|
||||
format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
src: url('/node_modules/geist/dist/fonts/geist-sans/Geist-Medium.ttf') format('truetype');
|
||||
font-family: "Geist";
|
||||
src: url("/node_modules/geist/dist/fonts/geist-sans/Geist-Medium.ttf")
|
||||
format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
src: url('/node_modules/geist/dist/fonts/geist-sans/Geist-Bold.ttf') format('truetype');
|
||||
font-family: "Geist";
|
||||
src: url("/node_modules/geist/dist/fonts/geist-sans/Geist-Bold.ttf")
|
||||
format("truetype");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -68,8 +74,8 @@
|
||||
:root {
|
||||
--docs-bg: #f5f5f5;
|
||||
|
||||
--default-font-family: 'Geist', sans-serif;
|
||||
--default-mono-font-family: 'Geist Mono', monospace;
|
||||
--default-font-family: "Geist", sans-serif;
|
||||
--default-mono-font-family: "Geist Mono", monospace;
|
||||
/* --background: oklch(1 0 0); */
|
||||
--background-lightest: #fff;
|
||||
--background-lighter: oklch(0.995 0.0188 292.61);
|
||||
@@ -122,7 +128,6 @@
|
||||
.dark {
|
||||
--docs-bg: #121212;
|
||||
|
||||
|
||||
--background-lightest: oklch(0.285 0 0);
|
||||
--background-lighter: oklch(0.185 0 0);
|
||||
--background: oklch(0.145 0 0);
|
||||
@@ -214,65 +219,64 @@
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
:root {
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (pointer: fine) {
|
||||
@supports not (selector(::-webkit-scrollbar)) {
|
||||
* {
|
||||
scrollbar-color: #dadce0 transparent;
|
||||
scrollbar-gutter: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
}
|
||||
|
||||
@media (pointer: fine) {
|
||||
@supports not (selector(::-webkit-scrollbar)) {
|
||||
* {
|
||||
scrollbar-color: #dadce0 transparent;
|
||||
scrollbar-gutter: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: content-box currentColor;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
color: #dadce0;
|
||||
min-height: 48px;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
:hover::-webkit-scrollbar-thumb {
|
||||
color: #80868b;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
color: #5f6368;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: content-box currentColor;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
color: #dadce0;
|
||||
min-height: 48px;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
:hover::-webkit-scrollbar-thumb {
|
||||
color: #80868b;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
color: #5f6368;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ async function getPublishableKey({ projectId }: { projectId: string }) {
|
||||
}
|
||||
const publishableKey = keys.find(
|
||||
(key) =>
|
||||
(key as any)["name"] === "anon" || (key as any)["type"] === "publishable"
|
||||
(key as any)["name"] === "anon" || (key as any)["type"] === "publishable",
|
||||
);
|
||||
|
||||
if (!publishableKey) {
|
||||
@@ -47,7 +47,7 @@ export async function getSupabaseContext({
|
||||
});
|
||||
const schema = await supabase.runQuery(
|
||||
supabaseProjectId,
|
||||
SUPABASE_SCHEMA_QUERY
|
||||
SUPABASE_SCHEMA_QUERY,
|
||||
);
|
||||
|
||||
const secrets = await supabase.getSecrets(supabaseProjectId);
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function refreshSupabaseToken(): Promise<void> {
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new Error(
|
||||
"Supabase refresh token not found. Please authenticate first."
|
||||
"Supabase refresh token not found. Please authenticate first.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function refreshSupabaseToken(): Promise<void> {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -94,7 +94,7 @@ export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
|
||||
|
||||
if (!supabaseAccessToken) {
|
||||
throw new Error(
|
||||
"Supabase access token not found. Please authenticate first."
|
||||
"Supabase access token not found. Please authenticate first.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
|
||||
}
|
||||
|
||||
export async function getSupabaseProjectName(
|
||||
projectId: string
|
||||
projectId: string,
|
||||
): Promise<string> {
|
||||
const supabase = await getSupabaseClient();
|
||||
const projects = await supabase.getProjects();
|
||||
@@ -148,12 +148,12 @@ export async function deleteSupabaseFunction({
|
||||
functionName: string;
|
||||
}): Promise<void> {
|
||||
logger.info(
|
||||
`Deleting Supabase function: ${functionName} from project: ${supabaseProjectId}`
|
||||
`Deleting Supabase function: ${functionName} from project: ${supabaseProjectId}`,
|
||||
);
|
||||
const supabase = await getSupabaseClient();
|
||||
await supabase.deleteFunction(supabaseProjectId, functionName);
|
||||
logger.info(
|
||||
`Deleted Supabase function: ${functionName} from project: ${supabaseProjectId}`
|
||||
`Deleted Supabase function: ${functionName} from project: ${supabaseProjectId}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ export async function deploySupabaseFunctions({
|
||||
content: string;
|
||||
}): Promise<void> {
|
||||
logger.info(
|
||||
`Deploying Supabase function: ${functionName} to project: ${supabaseProjectId}`
|
||||
`Deploying Supabase function: ${functionName} to project: ${supabaseProjectId}`,
|
||||
);
|
||||
const supabase = await getSupabaseClient();
|
||||
const formData = new FormData();
|
||||
@@ -176,7 +176,7 @@ export async function deploySupabaseFunctions({
|
||||
JSON.stringify({
|
||||
entrypoint_path: "index.ts",
|
||||
name: functionName,
|
||||
})
|
||||
}),
|
||||
);
|
||||
formData.append("file", new Blob([content]), "index.ts");
|
||||
|
||||
@@ -188,7 +188,7 @@ export async function deploySupabaseFunctions({
|
||||
Authorization: `Bearer ${(supabase as any).options.accessToken}`,
|
||||
},
|
||||
body: formData,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status !== 201) {
|
||||
@@ -196,7 +196,7 @@ export async function deploySupabaseFunctions({
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Deployed Supabase function: ${functionName} to project: ${supabaseProjectId}`
|
||||
`Deployed Supabase function: ${functionName} to project: ${supabaseProjectId}`,
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
@@ -208,12 +208,12 @@ async function createResponseError(response: Response, action: string) {
|
||||
`Failed to ${action}: ${response.statusText} (${response.status})${
|
||||
errorBody ? `: ${errorBody.message}` : ""
|
||||
}`,
|
||||
response
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
||||
async function safeParseErrorResponseBody(
|
||||
response: Response
|
||||
response: Response,
|
||||
): Promise<{ message: string } | undefined> {
|
||||
try {
|
||||
const body = await response.json();
|
||||
|
||||
@@ -52,7 +52,7 @@ const gitIgnoreMtimes = new Map<string, number>();
|
||||
*/
|
||||
async function isGitIgnored(
|
||||
filePath: string,
|
||||
baseDir: string
|
||||
baseDir: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Check if any relevant .gitignore has been modified
|
||||
@@ -318,7 +318,7 @@ async function sortFilesByModificationTime(files: string[]): Promise<string[]> {
|
||||
logger.error(`Error getting file stats for ${file}:`, error);
|
||||
return { file, mtime: Date.now() };
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Sort by modification time (oldest first)
|
||||
@@ -346,12 +346,12 @@ function sortFilesByImportance(files: string[], baseDir: string): string[] {
|
||||
|
||||
// Check if file A matches any high priority pattern
|
||||
const aIsHighPriority = highPriorityPatterns.some((pattern) =>
|
||||
pattern.test(relativeA)
|
||||
pattern.test(relativeA),
|
||||
);
|
||||
|
||||
// Check if file B matches any high priority pattern
|
||||
const bIsHighPriority = highPriorityPatterns.some((pattern) =>
|
||||
pattern.test(relativeB)
|
||||
pattern.test(relativeB),
|
||||
);
|
||||
|
||||
// Sort by priority first
|
||||
|
||||
Reference in New Issue
Block a user