Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
e947eede
Unverified
提交
e947eede
authored
7月 23, 2025
作者:
Will Chen
提交者:
GitHub
7月 23, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
community templates (#691)
上级
9edd0fa8
显示空白字符变更
内嵌
并排
正在显示
37 个修改的文件
包含
532 行增加
和
120 行删除
+532
-120
test_helper.ts
e2e-tests/helpers/test_helper.ts
+2
-2
problems.spec.ts
e2e-tests/problems.spec.ts
+1
-1
select_component.spec.ts
e2e-tests/select_component.spec.ts
+1
-1
auto_update.spec.ts_auto-update---disable-and-enable-1.txt
...uto_update.spec.ts_auto-update---disable-and-enable-1.txt
+1
-0
auto_update.spec.ts_auto-update---disable-and-enable-2.txt
...uto_update.spec.ts_auto-update---disable-and-enable-2.txt
+1
-0
context_window.spec.ts_context-window-4.txt
...sts/snapshots/context_window.spec.ts_context-window-4.txt
+1
-0
release_channel.spec.ts_release-channel---change-from-stable-to-beta-and-back-1.txt
...lease-channel---change-from-stable-to-beta-and-back-1.txt
+1
-0
release_channel.spec.ts_release-channel---change-from-stable-to-beta-and-back-2.txt
...lease-channel---change-from-stable-to-beta-and-back-2.txt
+1
-0
telemetry.spec.ts_telemetry---accept-1.txt
...ests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt
+1
-0
telemetry.spec.ts_telemetry---accept-2.txt
...ests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt
+1
-0
telemetry.spec.ts_telemetry---later-1.txt
...tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt
+1
-0
telemetry.spec.ts_telemetry---later-2.txt
...tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt
+1
-0
telemetry.spec.ts_telemetry---reject-1.txt
...ests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt
+1
-0
telemetry.spec.ts_telemetry---reject-2.txt
...ests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt
+1
-0
template-community.spec.ts_template---community-1.txt
...ots/template-community.spec.ts_template---community-1.txt
+21
-0
template-community.spec.ts_template---community-2.txt
...ots/template-community.spec.ts_template---community-2.txt
+22
-0
template-create-nextjs.spec.ts_create-next-js-app-1.txt
...s/template-create-nextjs.spec.ts_create-next-js-app-1.txt
+22
-0
thinking_budget.spec.ts_thinking-budget-1.txt
...s/snapshots/thinking_budget.spec.ts_thinking-budget-1.txt
+1
-0
thinking_budget.spec.ts_thinking-budget-3.txt
...s/snapshots/thinking_budget.spec.ts_thinking-budget-3.txt
+1
-0
thinking_budget.spec.ts_thinking-budget-5.txt
...s/snapshots/thinking_budget.spec.ts_thinking-budget-5.txt
+1
-0
template-community.spec.ts
e2e-tests/template-community.spec.ts
+19
-0
template-create-nextjs.spec.ts
e2e-tests/template-create-nextjs.spec.ts
+2
-1
readSettings.test.ts
src/__tests__/readSettings.test.ts
+46
-32
CommunityCodeConsentDialog.tsx
src/components/CommunityCodeConsentDialog.tsx
+51
-0
TemplateCard.tsx
src/components/TemplateCard.tsx
+134
-0
useTemplates.ts
src/hooks/useTemplates.ts
+24
-0
createFromTemplate.ts
src/ipc/handlers/createFromTemplate.ts
+3
-3
template_handlers.ts
src/ipc/handlers/template_handlers.ts
+19
-0
ipc_client.ts
src/ipc/ipc_client.ts
+6
-0
ipc_host.ts
src/ipc/ipc_host.ts
+2
-0
template_utils.ts
src/ipc/utils/template_utils.ts
+82
-0
schemas.ts
src/lib/schemas.ts
+2
-1
toast.tsx
src/lib/toast.tsx
+1
-1
settings.ts
src/main/settings.ts
+2
-0
hub.tsx
src/pages/hub.tsx
+41
-66
preload.ts
src/preload.ts
+1
-0
templates.ts
src/shared/templates.ts
+14
-12
没有找到文件。
e2e-tests/helpers/test_helper.ts
浏览文件 @
e947eede
...
@@ -876,11 +876,11 @@ export class PageObject {
...
@@ -876,11 +876,11 @@ export class PageObject {
await
this
.
page
.
getByRole
(
"link"
,
{
name
:
"Hub"
}).
click
();
await
this
.
page
.
getByRole
(
"link"
,
{
name
:
"Hub"
}).
click
();
}
}
private
async
selectTemplate
(
templateName
:
string
)
{
async
selectTemplate
(
templateName
:
string
)
{
await
this
.
page
.
getByRole
(
"img"
,
{
name
:
templateName
}).
click
();
await
this
.
page
.
getByRole
(
"img"
,
{
name
:
templateName
}).
click
();
}
}
async
selectHub
Template
(
templateName
:
"Next.js Template"
)
{
async
goToHubAndSelect
Template
(
templateName
:
"Next.js Template"
)
{
await
this
.
goToHubTab
();
await
this
.
goToHubTab
();
await
this
.
selectTemplate
(
templateName
);
await
this
.
selectTemplate
(
templateName
);
await
this
.
goToAppsTab
();
await
this
.
goToAppsTab
();
...
...
e2e-tests/problems.spec.ts
浏览文件 @
e947eede
...
@@ -112,7 +112,7 @@ export default App;
...
@@ -112,7 +112,7 @@ export default App;
test
(
"problems - manual edit (next.js)"
,
async
({
po
})
=>
{
test
(
"problems - manual edit (next.js)"
,
async
({
po
})
=>
{
await
po
.
setUp
({
enableAutoFixProblems
:
true
});
await
po
.
setUp
({
enableAutoFixProblems
:
true
});
await
po
.
selectHub
Template
(
"Next.js Template"
);
await
po
.
goToHubAndSelect
Template
(
"Next.js Template"
);
await
po
.
sendPrompt
(
"tc=1"
);
await
po
.
sendPrompt
(
"tc=1"
);
const
appPath
=
await
po
.
getCurrentAppPath
();
const
appPath
=
await
po
.
getCurrentAppPath
();
...
...
e2e-tests/select_component.spec.ts
浏览文件 @
e947eede
...
@@ -81,7 +81,7 @@ testSkipIfWindows("upgrade app to select component", async ({ po }) => {
...
@@ -81,7 +81,7 @@ testSkipIfWindows("upgrade app to select component", async ({ po }) => {
testSkipIfWindows
(
"select component next.js"
,
async
({
po
})
=>
{
testSkipIfWindows
(
"select component next.js"
,
async
({
po
})
=>
{
await
po
.
setUp
();
await
po
.
setUp
();
await
po
.
selectHub
Template
(
"Next.js Template"
);
await
po
.
goToHubAndSelect
Template
(
"Next.js Template"
);
await
po
.
sendPrompt
(
"tc=basic"
);
await
po
.
sendPrompt
(
"tc=basic"
);
await
po
.
clickTogglePreviewPanel
();
await
po
.
clickTogglePreviewPanel
();
...
...
e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-1.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": false,
"enableAutoUpdate": false,
...
...
e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-2.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt
浏览文件 @
e947eede
...
@@ -13,6 +13,7 @@
...
@@ -13,6 +13,7 @@
"maxChatTurnsInContext": 5,
"maxChatTurnsInContext": 5,
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/release_channel.spec.ts_release-channel---change-from-stable-to-beta-and-back-1.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/release_channel.spec.ts_release-channel---change-from-stable-to-beta-and-back-2.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt
浏览文件 @
e947eede
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
"experiments": {},
"experiments": {},
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt
浏览文件 @
e947eede
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
"experiments": {},
"experiments": {},
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt
浏览文件 @
e947eede
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
"experiments": {},
"experiments": {},
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt
浏览文件 @
e947eede
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/template-community.spec.ts_template---community-1.txt
0 → 100644
浏览文件 @
e947eede
{
"selectedModel": {
"name": "auto",
"provider": "auto"
},
"providerSettings": {},
"telemetryConsent": "unset",
"telemetryUserId": "[UUID]",
"hasRunBefore": true,
"experiments": {},
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"releaseChannel": "stable",
"isTestMode": true
}
\ No newline at end of file
e2e-tests/snapshots/template-community.spec.ts_template---community-2.txt
0 → 100644
浏览文件 @
e947eede
{
"selectedModel": {
"name": "auto",
"provider": "auto"
},
"providerSettings": {},
"telemetryConsent": "unset",
"telemetryUserId": "[UUID]",
"hasRunBefore": true,
"experiments": {},
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "jeff-kazzee/dyad-template-angular",
"selectedChatMode": "build",
"acceptedCommunityCode": true,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"releaseChannel": "stable",
"isTestMode": true
}
\ No newline at end of file
e2e-tests/snapshots/template-create-nextjs.spec.ts_create-next-js-app-1.txt
0 → 100644
浏览文件 @
e947eede
{
"selectedModel": {
"name": "test-model",
"provider": "custom::testing",
"customModelId": 1
},
"providerSettings": {},
"telemetryConsent": "unset",
"telemetryUserId": "[UUID]",
"hasRunBefore": true,
"experiments": {},
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "next",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"releaseChannel": "stable",
"isTestMode": true
}
\ No newline at end of file
e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-1.txt
浏览文件 @
e947eede
...
@@ -20,6 +20,7 @@
...
@@ -20,6 +20,7 @@
"thinkingBudget": "low",
"thinkingBudget": "low",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-3.txt
浏览文件 @
e947eede
...
@@ -20,6 +20,7 @@
...
@@ -20,6 +20,7 @@
"thinkingBudget": "medium",
"thinkingBudget": "medium",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-5.txt
浏览文件 @
e947eede
...
@@ -20,6 +20,7 @@
...
@@ -20,6 +20,7 @@
"thinkingBudget": "high",
"thinkingBudget": "high",
"enableProLazyEditsMode": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"enableProSmartFilesContextMode": true,
"selectedTemplateId": "react",
"selectedChatMode": "build",
"selectedChatMode": "build",
"enableAutoFixProblems": false,
"enableAutoFixProblems": false,
"enableAutoUpdate": true,
"enableAutoUpdate": true,
...
...
e2e-tests/template-community.spec.ts
0 → 100644
浏览文件 @
e947eede
import
{
test
}
from
"./helpers/test_helper"
;
test
(
"template - community"
,
async
({
po
})
=>
{
await
po
.
goToHubTab
();
// This is a community template, so we should see the consent dialog
await
po
.
selectTemplate
(
"Angular"
);
await
po
.
page
.
getByRole
(
"button"
,
{
name
:
"Cancel"
}).
click
();
await
po
.
snapshotSettings
();
await
po
.
selectTemplate
(
"Angular"
);
await
po
.
page
.
getByRole
(
"button"
,
{
name
:
"Accept"
}).
click
();
await
po
.
page
.
locator
(
"section"
)
.
filter
({
hasText
:
"Community"
})
.
locator
(
"div"
)
.
first
()
.
click
();
await
po
.
snapshotSettings
();
});
e2e-tests/template-create-nextjs.spec.ts
浏览文件 @
e947eede
...
@@ -3,7 +3,8 @@ import { expect } from "@playwright/test";
...
@@ -3,7 +3,8 @@ import { expect } from "@playwright/test";
test
(
"create next.js app"
,
async
({
po
})
=>
{
test
(
"create next.js app"
,
async
({
po
})
=>
{
await
po
.
setUp
();
await
po
.
setUp
();
await
po
.
selectHubTemplate
(
"Next.js Template"
);
await
po
.
goToHubAndSelectTemplate
(
"Next.js Template"
);
await
po
.
snapshotSettings
();
// Create an app
// Create an app
await
po
.
sendPrompt
(
"tc=edit-made-with-dyad"
);
await
po
.
sendPrompt
(
"tc=edit-made-with-dyad"
);
...
...
src/__tests__/readSettings.test.ts
浏览文件 @
e947eede
...
@@ -4,6 +4,7 @@ import path from "node:path";
...
@@ -4,6 +4,7 @@ import path from "node:path";
import
{
safeStorage
}
from
"electron"
;
import
{
safeStorage
}
from
"electron"
;
import
{
readSettings
,
getSettingsFilePath
}
from
"@/main/settings"
;
import
{
readSettings
,
getSettingsFilePath
}
from
"@/main/settings"
;
import
{
getUserDataPath
}
from
"@/paths/paths"
;
import
{
getUserDataPath
}
from
"@/paths/paths"
;
import
{
UserSettings
}
from
"@/lib/schemas"
;
// Mock dependencies
// Mock dependencies
vi
.
mock
(
"node:fs"
);
vi
.
mock
(
"node:fs"
);
...
@@ -50,23 +51,26 @@ describe("readSettings", () => {
...
@@ -50,23 +51,26 @@ describe("readSettings", () => {
mockSettingsPath
,
mockSettingsPath
,
expect
.
stringContaining
(
'"selectedModel"'
),
expect
.
stringContaining
(
'"selectedModel"'
),
);
);
expect
(
result
).
toEqual
({
expect
(
scrubSettings
(
result
)).
toMatchInlineSnapshot
(
`
selectedModel
:
{
{
name
:
"auto"
,
"enableAutoFixProblems": false,
provider
:
"auto"
,
"enableAutoUpdate": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"experiments": {},
"hasRunBefore": false,
"providerSettings": {},
"releaseChannel": "stable",
"selectedChatMode": "build",
"selectedModel": {
"name": "auto",
"provider": "auto",
},
},
providerSettings
:
{},
"selectedTemplateId": "react",
telemetryConsent
:
"unset"
,
"telemetryConsent": "unset",
telemetryUserId
:
expect
.
any
(
String
),
"telemetryUserId": "[scrubbed]",
hasRunBefore
:
false
,
}
experiments
:
{},
`
);
enableProLazyEditsMode
:
true
,
enableProSmartFilesContextMode
:
true
,
selectedChatMode
:
"build"
,
enableAutoFixProblems
:
false
,
enableAutoUpdate
:
true
,
releaseChannel
:
"stable"
,
});
});
});
});
});
...
@@ -293,23 +297,26 @@ describe("readSettings", () => {
...
@@ -293,23 +297,26 @@ describe("readSettings", () => {
const
result
=
readSettings
();
const
result
=
readSettings
();
expect
(
result
).
toEqual
({
expect
(
scrubSettings
(
result
)).
toMatchInlineSnapshot
(
`
selectedModel
:
{
{
name
:
"auto"
,
"enableAutoFixProblems": false,
provider
:
"auto"
,
"enableAutoUpdate": true,
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"experiments": {},
"hasRunBefore": false,
"providerSettings": {},
"releaseChannel": "stable",
"selectedChatMode": "build",
"selectedModel": {
"name": "auto",
"provider": "auto",
},
},
providerSettings
:
{},
"selectedTemplateId": "react",
telemetryConsent
:
"unset"
,
"telemetryConsent": "unset",
telemetryUserId
:
expect
.
any
(
String
),
"telemetryUserId": "[scrubbed]",
hasRunBefore
:
false
,
}
experiments
:
{},
`
);
enableProLazyEditsMode
:
true
,
enableProSmartFilesContextMode
:
true
,
selectedChatMode
:
"build"
,
enableAutoFixProblems
:
false
,
enableAutoUpdate
:
true
,
releaseChannel
:
"stable"
,
});
});
});
it
(
"should return default settings when JSON parsing fails"
,
()
=>
{
it
(
"should return default settings when JSON parsing fails"
,
()
=>
{
...
@@ -389,3 +396,10 @@ describe("readSettings", () => {
...
@@ -389,3 +396,10 @@ describe("readSettings", () => {
});
});
});
});
});
});
function
scrubSettings
(
result
:
UserSettings
)
{
return
{
...
result
,
telemetryUserId
:
"[scrubbed]"
,
};
}
src/components/CommunityCodeConsentDialog.tsx
0 → 100644
浏览文件 @
e947eede
import
React
from
"react"
;
import
{
AlertDialog
,
AlertDialogAction
,
AlertDialogCancel
,
AlertDialogContent
,
AlertDialogDescription
,
AlertDialogFooter
,
AlertDialogHeader
,
AlertDialogTitle
,
}
from
"@/components/ui/alert-dialog"
;
interface
CommunityCodeConsentDialogProps
{
isOpen
:
boolean
;
onAccept
:
()
=>
void
;
onCancel
:
()
=>
void
;
}
export
const
CommunityCodeConsentDialog
:
React
.
FC
<
CommunityCodeConsentDialogProps
>
=
({
isOpen
,
onAccept
,
onCancel
})
=>
{
return
(
<
AlertDialog
open=
{
isOpen
}
onOpenChange=
{
(
open
)
=>
!
open
&&
onCancel
()
}
>
<
AlertDialogContent
>
<
AlertDialogHeader
>
<
AlertDialogTitle
>
Community Code Notice
</
AlertDialogTitle
>
<
AlertDialogDescription
className=
"space-y-3"
>
<
p
>
This code was created by a Dyad community member, not our core
team.
</
p
>
<
p
>
Community code can be very helpful, but since it's built
independently, it may have bugs, security risks, or could cause
issues with your system. We can't provide official support if
problems occur.
</
p
>
<
p
>
We recommend reviewing the code on GitHub first. Only proceed if
you're comfortable with these risks.
</
p
>
</
AlertDialogDescription
>
</
AlertDialogHeader
>
<
AlertDialogFooter
>
<
AlertDialogCancel
onClick=
{
onCancel
}
>
Cancel
</
AlertDialogCancel
>
<
AlertDialogAction
onClick=
{
onAccept
}
>
Accept
</
AlertDialogAction
>
</
AlertDialogFooter
>
</
AlertDialogContent
>
</
AlertDialog
>
);
};
src/components/TemplateCard.tsx
0 → 100644
浏览文件 @
e947eede
import
React
,
{
useState
}
from
"react"
;
import
{
ArrowLeft
}
from
"lucide-react"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
CommunityCodeConsentDialog
}
from
"./CommunityCodeConsentDialog"
;
import
type
{
Template
}
from
"@/shared/templates"
;
interface
TemplateCardProps
{
template
:
Template
;
isSelected
:
boolean
;
onSelect
:
(
templateId
:
string
)
=>
void
;
}
export
const
TemplateCard
:
React
.
FC
<
TemplateCardProps
>
=
({
template
,
isSelected
,
onSelect
,
})
=>
{
const
{
settings
,
updateSettings
}
=
useSettings
();
const
[
showConsentDialog
,
setShowConsentDialog
]
=
useState
(
false
);
const
handleCardClick
=
()
=>
{
// If it's a community template and user hasn't accepted community code yet, show dialog
if
(
!
template
.
isOfficial
&&
!
settings
?.
acceptedCommunityCode
)
{
setShowConsentDialog
(
true
);
return
;
}
// Otherwise, proceed with selection
onSelect
(
template
.
id
);
};
const
handleConsentAccept
=
()
=>
{
// Update settings to accept community code
updateSettings
({
acceptedCommunityCode
:
true
});
// Select the template
onSelect
(
template
.
id
);
// Close dialog
setShowConsentDialog
(
false
);
};
const
handleConsentCancel
=
()
=>
{
// Just close dialog, don't update settings or select template
setShowConsentDialog
(
false
);
};
const
handleGithubClick
=
(
e
:
React
.
MouseEvent
)
=>
{
e
.
stopPropagation
();
if
(
template
.
githubUrl
)
{
IpcClient
.
getInstance
().
openExternalUrl
(
template
.
githubUrl
);
}
};
return
(
<>
<
div
onClick=
{
handleCardClick
}
className=
{
`
bg-white dark:bg-gray-800 rounded-xl shadow-sm overflow-hidden
transform transition-all duration-300 ease-in-out
cursor-pointer group relative
${
isSelected
? "ring-2 ring-blue-500 dark:ring-blue-400 shadow-xl"
: "hover:shadow-lg hover:-translate-y-1"
}
`
}
>
<
div
className=
"relative"
>
<
img
src=
{
template
.
imageUrl
}
alt=
{
template
.
title
}
className=
{
`w-full h-52 object-cover transition-opacity duration-300 group-hover:opacity-80 ${
isSelected ? "opacity-75" : ""
}`
}
/>
{
isSelected
&&
(
<
span
className=
"absolute top-3 right-3 bg-blue-600 text-white text-xs font-bold px-3 py-1.5 rounded-md shadow-lg"
>
Selected
</
span
>
)
}
</
div
>
<
div
className=
"p-4"
>
<
div
className=
"flex justify-between items-center mb-1.5"
>
<
h2
className=
{
`text-lg font-semibold ${
isSelected
? "text-blue-600 dark:text-blue-400"
: "text-gray-900 dark:text-white"
}`
}
>
{
template
.
title
}
</
h2
>
{
template
.
isOfficial
&&
(
<
span
className=
{
`text-xs font-semibold px-2 py-0.5 rounded-full ${
isSelected
? "bg-blue-100 text-blue-700 dark:bg-blue-600 dark:text-blue-100"
: "bg-green-100 text-green-800 dark:bg-green-700 dark:text-green-200"
}`
}
>
Official
</
span
>
)
}
</
div
>
<
p
className=
"text-sm text-gray-500 dark:text-gray-400 mb-3 h-8 overflow-y-auto"
>
{
template
.
description
}
</
p
>
{
template
.
githubUrl
&&
(
<
a
className=
{
`inline-flex items-center text-sm font-medium transition-colors duration-200 ${
isSelected
? "text-blue-500 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200"
: "text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
}`
}
onClick=
{
handleGithubClick
}
>
View on GitHub
{
" "
}
<
ArrowLeft
className=
"w-4 h-4 ml-1 transform rotate-180"
/>
</
a
>
)
}
</
div
>
</
div
>
<
CommunityCodeConsentDialog
isOpen=
{
showConsentDialog
}
onAccept=
{
handleConsentAccept
}
onCancel=
{
handleConsentCancel
}
/>
</>
);
};
src/hooks/useTemplates.ts
0 → 100644
浏览文件 @
e947eede
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
localTemplatesData
,
type
Template
}
from
"@/shared/templates"
;
export
function
useTemplates
()
{
const
query
=
useQuery
({
queryKey
:
[
"templates"
],
queryFn
:
async
():
Promise
<
Template
[]
>
=>
{
const
ipcClient
=
IpcClient
.
getInstance
();
return
ipcClient
.
getTemplates
();
},
initialData
:
localTemplatesData
,
meta
:
{
showErrorToast
:
true
,
},
});
return
{
templates
:
query
.
data
,
isLoading
:
query
.
isLoading
,
error
:
query
.
error
,
refetch
:
query
.
refetch
,
};
}
src/ipc/handlers/createFromTemplate.ts
浏览文件 @
e947eede
...
@@ -5,7 +5,7 @@ import http from "isomorphic-git/http/node";
...
@@ -5,7 +5,7 @@ import http from "isomorphic-git/http/node";
import
{
app
}
from
"electron"
;
import
{
app
}
from
"electron"
;
import
{
copyDirectoryRecursive
}
from
"../utils/file_utils"
;
import
{
copyDirectoryRecursive
}
from
"../utils/file_utils"
;
import
{
readSettings
}
from
"@/main/settings"
;
import
{
readSettings
}
from
"@/main/settings"
;
import
{
DEFAULT_TEMPLATE_ID
,
getTemplateOrThrow
}
from
"@/shared/template
s"
;
import
{
getTemplateOrThrow
}
from
"../utils/template_util
s"
;
import
log
from
"electron-log"
;
import
log
from
"electron-log"
;
const
logger
=
log
.
scope
(
"createFromTemplate"
);
const
logger
=
log
.
scope
(
"createFromTemplate"
);
...
@@ -16,7 +16,7 @@ export async function createFromTemplate({
...
@@ -16,7 +16,7 @@ export async function createFromTemplate({
fullAppPath
:
string
;
fullAppPath
:
string
;
})
{
})
{
const
settings
=
readSettings
();
const
settings
=
readSettings
();
const
templateId
=
settings
.
selectedTemplateId
??
DEFAULT_TEMPLATE_ID
;
const
templateId
=
settings
.
selectedTemplateId
;
if
(
templateId
===
"react"
)
{
if
(
templateId
===
"react"
)
{
await
copyDirectoryRecursive
(
await
copyDirectoryRecursive
(
...
@@ -26,7 +26,7 @@ export async function createFromTemplate({
...
@@ -26,7 +26,7 @@ export async function createFromTemplate({
return
;
return
;
}
}
const
template
=
getTemplateOrThrow
(
templateId
);
const
template
=
await
getTemplateOrThrow
(
templateId
);
if
(
!
template
.
githubUrl
)
{
if
(
!
template
.
githubUrl
)
{
throw
new
Error
(
`Template
${
templateId
}
has no GitHub URL`
);
throw
new
Error
(
`Template
${
templateId
}
has no GitHub URL`
);
}
}
...
...
src/ipc/handlers/template_handlers.ts
0 → 100644
浏览文件 @
e947eede
import
{
createLoggedHandler
}
from
"./safe_handle"
;
import
log
from
"electron-log"
;
import
{
getAllTemplates
}
from
"../utils/template_utils"
;
import
{
localTemplatesData
,
type
Template
}
from
"../../shared/templates"
;
const
logger
=
log
.
scope
(
"template_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerTemplateHandlers
()
{
handle
(
"get-templates"
,
async
():
Promise
<
Template
[]
>
=>
{
try
{
const
templates
=
await
getAllTemplates
();
return
templates
;
}
catch
(
error
)
{
logger
.
error
(
"Error fetching templates:"
,
error
);
return
localTemplatesData
;
}
});
}
src/ipc/ipc_client.ts
浏览文件 @
e947eede
...
@@ -52,6 +52,7 @@ import type {
...
@@ -52,6 +52,7 @@ import type {
UpdateChatParams
,
UpdateChatParams
,
FileAttachment
,
FileAttachment
,
}
from
"./ipc_types"
;
}
from
"./ipc_types"
;
import
type
{
Template
}
from
"../shared/templates"
;
import
type
{
AppChatContext
,
ProposalResult
}
from
"@/lib/schemas"
;
import
type
{
AppChatContext
,
ProposalResult
}
from
"@/lib/schemas"
;
import
{
showError
}
from
"@/lib/toast"
;
import
{
showError
}
from
"@/lib/toast"
;
...
@@ -1024,4 +1025,9 @@ export class IpcClient {
...
@@ -1024,4 +1025,9 @@ export class IpcClient {
}):
Promise
<
ProblemReport
>
{
}):
Promise
<
ProblemReport
>
{
return
this
.
ipcRenderer
.
invoke
(
"check-problems"
,
params
);
return
this
.
ipcRenderer
.
invoke
(
"check-problems"
,
params
);
}
}
// Template methods
public
async
getTemplates
():
Promise
<
Template
[]
>
{
return
this
.
ipcRenderer
.
invoke
(
"get-templates"
);
}
}
}
src/ipc/ipc_host.ts
浏览文件 @
e947eede
...
@@ -25,6 +25,7 @@ import { registerAppUpgradeHandlers } from "./handlers/app_upgrade_handlers";
...
@@ -25,6 +25,7 @@ import { registerAppUpgradeHandlers } from "./handlers/app_upgrade_handlers";
import
{
registerCapacitorHandlers
}
from
"./handlers/capacitor_handlers"
;
import
{
registerCapacitorHandlers
}
from
"./handlers/capacitor_handlers"
;
import
{
registerProblemsHandlers
}
from
"./handlers/problems_handlers"
;
import
{
registerProblemsHandlers
}
from
"./handlers/problems_handlers"
;
import
{
registerAppEnvVarsHandlers
}
from
"./handlers/app_env_vars_handlers"
;
import
{
registerAppEnvVarsHandlers
}
from
"./handlers/app_env_vars_handlers"
;
import
{
registerTemplateHandlers
}
from
"./handlers/template_handlers"
;
export
function
registerIpcHandlers
()
{
export
function
registerIpcHandlers
()
{
// Register all IPC handlers by category
// Register all IPC handlers by category
...
@@ -55,4 +56,5 @@ export function registerIpcHandlers() {
...
@@ -55,4 +56,5 @@ export function registerIpcHandlers() {
registerAppUpgradeHandlers
();
registerAppUpgradeHandlers
();
registerCapacitorHandlers
();
registerCapacitorHandlers
();
registerAppEnvVarsHandlers
();
registerAppEnvVarsHandlers
();
registerTemplateHandlers
();
}
}
src/ipc/utils/template_utils.ts
0 → 100644
浏览文件 @
e947eede
import
{
type
Template
,
type
ApiTemplate
,
localTemplatesData
,
}
from
"../../shared/templates"
;
import
log
from
"electron-log"
;
const
logger
=
log
.
scope
(
"template_utils"
);
// In-memory cache for API templates
let
apiTemplatesCache
:
Template
[]
|
null
=
null
;
let
apiTemplatesFetchPromise
:
Promise
<
Template
[]
>
|
null
=
null
;
// Convert API template to our Template interface
function
convertApiTemplate
(
apiTemplate
:
ApiTemplate
):
Template
{
return
{
id
:
`
${
apiTemplate
.
githubOrg
}
/
${
apiTemplate
.
githubRepo
}
`
,
title
:
apiTemplate
.
title
,
description
:
apiTemplate
.
description
,
imageUrl
:
apiTemplate
.
imageUrl
,
githubUrl
:
`https://github.com/
${
apiTemplate
.
githubOrg
}
/
${
apiTemplate
.
githubRepo
}
`
,
isOfficial
:
false
,
};
}
// Fetch templates from API with caching
export
async
function
fetchApiTemplates
():
Promise
<
Template
[]
>
{
// Return cached data if available
if
(
apiTemplatesCache
)
{
return
apiTemplatesCache
;
}
// Return existing promise if fetch is already in progress
if
(
apiTemplatesFetchPromise
)
{
return
apiTemplatesFetchPromise
;
}
// Start new fetch
apiTemplatesFetchPromise
=
(
async
():
Promise
<
Template
[]
>
=>
{
try
{
const
response
=
await
fetch
(
"https://api.dyad.sh/v1/templates"
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
`Failed to fetch templates:
${
response
.
status
}
${
response
.
statusText
}
`
,
);
}
const
apiTemplates
:
ApiTemplate
[]
=
await
response
.
json
();
const
convertedTemplates
=
apiTemplates
.
map
(
convertApiTemplate
);
// Cache the result
apiTemplatesCache
=
convertedTemplates
;
return
convertedTemplates
;
}
catch
(
error
)
{
logger
.
error
(
"Failed to fetch API templates:"
,
error
);
// Reset the promise so we can retry later
apiTemplatesFetchPromise
=
null
;
return
[];
// Return empty array on error
}
})();
return
apiTemplatesFetchPromise
;
}
// Get all templates (local + API)
export
async
function
getAllTemplates
():
Promise
<
Template
[]
>
{
const
apiTemplates
=
await
fetchApiTemplates
();
return
[...
localTemplatesData
,
...
apiTemplates
];
}
export
async
function
getTemplateOrThrow
(
templateId
:
string
,
):
Promise
<
Template
>
{
const
allTemplates
=
await
getAllTemplates
();
const
template
=
allTemplates
.
find
((
template
)
=>
template
.
id
===
templateId
);
if
(
!
template
)
{
throw
new
Error
(
`Template
${
templateId
}
not found. Please select a different template.`
,
);
}
return
template
;
}
src/lib/schemas.ts
浏览文件 @
e947eede
...
@@ -149,9 +149,10 @@ export const UserSettingsSchema = z.object({
...
@@ -149,9 +149,10 @@ export const UserSettingsSchema = z.object({
thinkingBudget
:
z
.
enum
([
"low"
,
"medium"
,
"high"
]).
optional
(),
thinkingBudget
:
z
.
enum
([
"low"
,
"medium"
,
"high"
]).
optional
(),
enableProLazyEditsMode
:
z
.
boolean
().
optional
(),
enableProLazyEditsMode
:
z
.
boolean
().
optional
(),
enableProSmartFilesContextMode
:
z
.
boolean
().
optional
(),
enableProSmartFilesContextMode
:
z
.
boolean
().
optional
(),
selectedTemplateId
:
z
.
string
()
.
optional
()
,
selectedTemplateId
:
z
.
string
(),
enableSupabaseWriteSqlMigration
:
z
.
boolean
().
optional
(),
enableSupabaseWriteSqlMigration
:
z
.
boolean
().
optional
(),
selectedChatMode
:
ChatModeSchema
.
optional
(),
selectedChatMode
:
ChatModeSchema
.
optional
(),
acceptedCommunityCode
:
z
.
boolean
().
optional
(),
enableAutoFixProblems
:
z
.
boolean
().
optional
(),
enableAutoFixProblems
:
z
.
boolean
().
optional
(),
enableNativeGit
:
z
.
boolean
().
optional
(),
enableNativeGit
:
z
.
boolean
().
optional
(),
...
...
src/lib/toast.tsx
浏览文件 @
e947eede
...
@@ -64,7 +64,7 @@ export const showError = (message: any) => {
...
@@ -64,7 +64,7 @@ export const showError = (message: any) => {
onCopy=
{
()
=>
onCopy
(
t
)
}
onCopy=
{
()
=>
onCopy
(
t
)
}
/>
/>
),
),
{
duration
:
4
000
},
{
duration
:
8
_
000
},
);
);
return
toastId
;
return
toastId
;
...
...
src/main/settings.ts
浏览文件 @
e947eede
...
@@ -5,6 +5,7 @@ import { UserSettingsSchema, type UserSettings, Secret } from "../lib/schemas";
...
@@ -5,6 +5,7 @@ import { UserSettingsSchema, type UserSettings, Secret } from "../lib/schemas";
import
{
safeStorage
}
from
"electron"
;
import
{
safeStorage
}
from
"electron"
;
import
{
v4
as
uuidv4
}
from
"uuid"
;
import
{
v4
as
uuidv4
}
from
"uuid"
;
import
log
from
"electron-log"
;
import
log
from
"electron-log"
;
import
{
DEFAULT_TEMPLATE_ID
}
from
"@/shared/templates"
;
const
logger
=
log
.
scope
(
"settings"
);
const
logger
=
log
.
scope
(
"settings"
);
...
@@ -26,6 +27,7 @@ const DEFAULT_SETTINGS: UserSettings = {
...
@@ -26,6 +27,7 @@ const DEFAULT_SETTINGS: UserSettings = {
enableAutoFixProblems
:
false
,
enableAutoFixProblems
:
false
,
enableAutoUpdate
:
true
,
enableAutoUpdate
:
true
,
releaseChannel
:
"stable"
,
releaseChannel
:
"stable"
,
selectedTemplateId
:
DEFAULT_TEMPLATE_ID
,
};
};
const
SETTINGS_FILE
=
"user-settings.json"
;
const
SETTINGS_FILE
=
"user-settings.json"
;
...
...
src/pages/hub.tsx
浏览文件 @
e947eede
...
@@ -3,20 +3,26 @@ import { Button } from "@/components/ui/button";
...
@@ -3,20 +3,26 @@ import { Button } from "@/components/ui/button";
import
{
ArrowLeft
}
from
"lucide-react"
;
import
{
ArrowLeft
}
from
"lucide-react"
;
import
{
useRouter
}
from
"@tanstack/react-router"
;
import
{
useRouter
}
from
"@tanstack/react-router"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client
"
;
import
{
useTemplates
}
from
"@/hooks/useTemplates
"
;
import
{
DEFAULT_TEMPLATE_ID
,
templatesData
}
from
"@/shared/templates
"
;
import
{
TemplateCard
}
from
"@/components/TemplateCard
"
;
const
HubPage
:
React
.
FC
=
()
=>
{
const
HubPage
:
React
.
FC
=
()
=>
{
const
{
settings
,
updateSettings
}
=
useSettings
();
const
{
settings
,
updateSettings
}
=
useSettings
();
const
router
=
useRouter
();
const
router
=
useRouter
();
const
{
templates
,
isLoading
}
=
useTemplates
();
const
selectedTemplateId
=
const
selectedTemplateId
=
settings
?.
selectedTemplateId
;
settings
?.
selectedTemplateId
||
DEFAULT_TEMPLATE_ID
;
const
handleTemplateSelect
=
(
templateId
:
string
)
=>
{
const
handleTemplateSelect
=
(
templateId
:
string
)
=>
{
updateSettings
({
selectedTemplateId
:
templateId
});
updateSettings
({
selectedTemplateId
:
templateId
});
};
};
// Separate templates into official and community
const
officialTemplates
=
templates
?.
filter
((
template
)
=>
template
.
isOfficial
)
||
[];
const
communityTemplates
=
templates
?.
filter
((
template
)
=>
!
template
.
isOfficial
)
||
[];
return
(
return
(
<
div
className=
"min-h-screen px-8 py-4"
>
<
div
className=
"min-h-screen px-8 py-4"
>
<
div
className=
"max-w-5xl mx-auto"
>
<
div
className=
"max-w-5xl mx-auto"
>
...
@@ -35,81 +41,50 @@ const HubPage: React.FC = () => {
...
@@ -35,81 +41,50 @@ const HubPage: React.FC = () => {
</
h1
>
</
h1
>
<
p
className=
"text-md text-gray-600 dark:text-gray-400"
>
<
p
className=
"text-md text-gray-600 dark:text-gray-400"
>
Choose a starting point for your new project.
Choose a starting point for your new project.
{
isLoading
&&
" Loading additional templates..."
}
</
p
>
</
p
>
</
header
>
</
header
>
{
/* Official Templates Section */
}
{
officialTemplates
.
length
>
0
&&
(
<
section
className=
"mb-12"
>
<
h2
className=
"text-2xl font-bold text-gray-900 dark:text-white mb-6"
>
Official templates
</
h2
>
<
div
className=
"grid grid-cols-1 md:grid-cols-2 gap-6"
>
<
div
className=
"grid grid-cols-1 md:grid-cols-2 gap-6"
>
{
templatesData
.
map
((
template
)
=>
{
{
officialTemplates
.
map
((
template
)
=>
(
const
isSelected
=
template
.
id
===
selectedTemplateId
;
<
TemplateCard
return
(
<
div
key=
{
template
.
id
}
key=
{
template
.
id
}
onClick=
{
()
=>
handleTemplateSelect
(
template
.
id
)
}
template=
{
template
}
className=
{
`
isSelected=
{
template
.
id
===
selectedTemplateId
}
bg-white dark:bg-gray-800 rounded-xl shadow-sm overflow-hidden
onSelect=
{
handleTemplateSelect
}
transform transition-all duration-300 ease-in-out
cursor-pointer group relative
${
isSelected
? "ring-2 ring-blue-500 dark:ring-blue-400 shadow-xl"
: "hover:shadow-lg hover:-translate-y-1"
}
`
}
>
<
div
className=
"relative"
>
<
img
src=
{
template
.
imageUrl
}
alt=
{
template
.
title
}
className=
{
`w-full h-52 object-cover transition-opacity duration-300 group-hover:opacity-80 ${isSelected ? "opacity-75" : ""}`
}
/>
/>
{
isSelected
&&
(
))
}
<
span
className=
"absolute top-3 right-3 bg-blue-600 text-white text-xs font-bold px-3 py-1.5 rounded-md shadow-lg"
>
Selected
</
span
>
)
}
</
div
>
</
div
>
<
div
className=
"p-4"
>
</
section
>
<
div
className=
"flex justify-between items-center mb-1.5"
>
<
h2
className=
{
`text-lg font-semibold ${isSelected ? "text-blue-600 dark:text-blue-400" : "text-gray-900 dark:text-white"}`
}
>
{
template
.
title
}
</
h2
>
{
template
.
isOfficial
&&
(
<
span
className=
{
`text-xs font-semibold px-2 py-0.5 rounded-full ${isSelected ? "bg-blue-100 text-blue-700 dark:bg-blue-600 dark:text-blue-100" : "bg-green-100 text-green-800 dark:bg-green-700 dark:text-green-200"}`
}
>
Official
</
span
>
)
}
)
}
{
/* Community Templates Section */
}
{
communityTemplates
.
length
>
0
&&
(
<
section
className=
"mb-12"
>
<
h2
className=
"text-2xl font-bold text-gray-900 dark:text-white mb-6"
>
Community templates
</
h2
>
<
div
className=
"grid grid-cols-1 md:grid-cols-2 gap-6"
>
{
communityTemplates
.
map
((
template
)
=>
(
<
TemplateCard
key=
{
template
.
id
}
template=
{
template
}
isSelected=
{
template
.
id
===
selectedTemplateId
}
onSelect=
{
handleTemplateSelect
}
/>
))
}
</
div
>
</
div
>
<
p
className=
"text-sm text-gray-500 dark:text-gray-400 mb-3 h-8 overflow-y-auto"
>
</
section
>
{
template
.
description
}
</
p
>
{
template
.
githubUrl
&&
(
<
a
className=
{
`inline-flex items-center text-sm font-medium transition-colors duration-200 ${isSelected ? "text-blue-500 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200" : "text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"}`
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
if
(
template
.
githubUrl
)
{
IpcClient
.
getInstance
().
openExternalUrl
(
template
.
githubUrl
,
);
}
}
}
>
View on GitHub
{
" "
}
<
ArrowLeft
className=
"w-4 h-4 ml-1 transform rotate-180"
/>
</
a
>
)
}
)
}
</
div
>
</
div
>
</
div
>
</
div
>
);
);
})
}
</
div
>
</
div
>
</
div
>
);
};
};
export
default
HubPage
;
export
default
HubPage
;
src/preload.ts
浏览文件 @
e947eede
...
@@ -100,6 +100,7 @@ const validInvokeChannels = [
...
@@ -100,6 +100,7 @@ const validInvokeChannels = [
"open-android"
,
"open-android"
,
"check-problems"
,
"check-problems"
,
"restart-dyad"
,
"restart-dyad"
,
"get-templates"
,
// Test-only channels
// Test-only channels
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
// We can't detect with IS_TEST_BUILD in the preload script because
// We can't detect with IS_TEST_BUILD in the preload script because
...
...
src/shared/templates.ts
浏览文件 @
e947eede
...
@@ -7,17 +7,27 @@ export interface Template {
...
@@ -7,17 +7,27 @@ export interface Template {
isOfficial
:
boolean
;
isOfficial
:
boolean
;
}
}
export
const
DEFAULT_TEMPLATE_ID
=
"react"
;
// API Template interface from the external API
export
interface
ApiTemplate
{
githubOrg
:
string
;
githubRepo
:
string
;
title
:
string
;
description
:
string
;
imageUrl
:
string
;
}
export
const
templatesData
:
Template
[]
=
[
export
const
DEFAULT_TEMPLATE_ID
=
"react"
;
{
export
const
DEFAULT_TEMPLATE
=
{
id
:
"react"
,
id
:
"react"
,
title
:
"React.js Template"
,
title
:
"React.js Template"
,
description
:
"Uses React.js, Vite, Shadcn, Tailwind and TypeScript."
,
description
:
"Uses React.js, Vite, Shadcn, Tailwind and TypeScript."
,
imageUrl
:
imageUrl
:
"https://github.com/user-attachments/assets/5b700eab-b28c-498e-96de-8649b14c16d9"
,
"https://github.com/user-attachments/assets/5b700eab-b28c-498e-96de-8649b14c16d9"
,
isOfficial
:
true
,
isOfficial
:
true
,
},
};
export
const
localTemplatesData
:
Template
[]
=
[
DEFAULT_TEMPLATE
,
{
{
id
:
"next"
,
id
:
"next"
,
title
:
"Next.js Template"
,
title
:
"Next.js Template"
,
...
@@ -28,11 +38,3 @@ export const templatesData: Template[] = [
...
@@ -28,11 +38,3 @@ export const templatesData: Template[] = [
isOfficial
:
true
,
isOfficial
:
true
,
},
},
];
];
export
function
getTemplateOrThrow
(
templateId
:
string
):
Template
{
const
template
=
templatesData
.
find
((
template
)
=>
template
.
id
===
templateId
);
if
(
!
template
)
{
throw
new
Error
(
`Template
${
templateId
}
not found`
);
}
return
template
;
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论