Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
43ec6a45
Unverified
提交
43ec6a45
authored
5月 06, 2025
作者:
Will Chen
提交者:
GitHub
5月 06, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add warning banner if user is not on main branch (#91)
上级
1fcb58a1
显示空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
186 行增加
和
4 行删除
+186
-4
ChatHeader.tsx
src/components/chat/ChatHeader.tsx
+116
-3
version_handlers.ts
src/ipc/handlers/version_handlers.ts
+48
-1
ipc_client.ts
src/ipc/ipc_client.ts
+9
-0
ipc_types.ts
src/ipc/ipc_types.ts
+12
-0
preload.ts
src/preload.ts
+1
-0
没有找到文件。
src/components/chat/ChatHeader.tsx
浏览文件 @
43ec6a45
import
{
PanelRightOpen
,
History
,
PlusCircle
}
from
"lucide-react"
;
import
{
PanelRightOpen
,
History
,
PlusCircle
,
GitBranch
,
AlertCircle
,
Info
,
}
from
"lucide-react"
;
import
{
PanelRightClose
}
from
"lucide-react"
;
import
{
useAtomValue
,
useSetAtom
}
from
"jotai"
;
import
{
useAtom
,
useAtom
Value
,
useSetAtom
}
from
"jotai"
;
import
{
selectedAppIdAtom
}
from
"@/atoms/appAtoms"
;
import
{
useVersions
}
from
"@/hooks/useVersions"
;
import
{
Button
}
from
"../ui/button"
;
import
{
Tooltip
,
TooltipContent
,
TooltipProvider
,
TooltipTrigger
,
}
from
"../ui/tooltip"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
useRouter
}
from
"@tanstack/react-router"
;
import
{
selectedChatIdAtom
}
from
"@/atoms/chatAtoms"
;
import
{
useChats
}
from
"@/hooks/useChats"
;
import
{
showError
}
from
"@/lib/toast"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
BranchResult
}
from
"@/ipc/ipc_types"
;
import
{
useStreamChat
}
from
"@/hooks/useStreamChat"
;
interface
ChatHeaderProps
{
isPreviewOpen
:
boolean
;
...
...
@@ -24,8 +40,59 @@ export function ChatHeader({
const
appId
=
useAtomValue
(
selectedAppIdAtom
);
const
{
versions
,
loading
}
=
useVersions
(
appId
);
const
{
navigate
}
=
useRouter
();
const
setSelectedChatId
=
useSet
Atom
(
selectedChatIdAtom
);
const
[
selectedChatId
,
setSelectedChatId
]
=
use
Atom
(
selectedChatIdAtom
);
const
{
refreshChats
}
=
useChats
(
appId
);
const
[
branchInfo
,
setBranchInfo
]
=
useState
<
BranchResult
|
null
>
(
null
);
const
[
checkingOutMain
,
setCheckingOutMain
]
=
useState
(
false
);
const
{
isStreaming
}
=
useStreamChat
();
// Fetch the current branch when appId changes
useEffect
(()
=>
{
if
(
!
appId
)
return
;
const
fetchBranch
=
async
()
=>
{
try
{
const
result
=
await
IpcClient
.
getInstance
().
getCurrentBranch
(
appId
);
if
(
result
.
success
)
{
setBranchInfo
(
result
);
}
else
{
showError
(
"Failed to get current branch: "
+
result
.
errorMessage
);
}
}
catch
(
error
)
{
showError
(
`Failed to get current branch:
${
error
}
`
);
}
};
fetchBranch
();
// The use of selectedChatId and isStreaming is a hack to ensure that
// the branch info is relatively up to date.
},
[
appId
,
selectedChatId
,
isStreaming
]);
const
handleCheckoutMainBranch
=
async
()
=>
{
if
(
!
appId
)
return
;
try
{
setCheckingOutMain
(
true
);
// Find the latest commit on main branch
// For simplicity, we'll just checkout to "main" directly
await
IpcClient
.
getInstance
().
checkoutVersion
({
appId
,
versionId
:
"main"
,
});
// Refresh branch info
const
result
=
await
IpcClient
.
getInstance
().
getCurrentBranch
(
appId
);
if
(
result
.
success
)
{
setBranchInfo
(
result
);
}
else
{
showError
(
result
.
errorMessage
);
}
}
catch
(
error
)
{
showError
(
`Failed to checkout main branch:
${
error
}
`
);
}
finally
{
setCheckingOutMain
(
false
);
}
};
const
handleNewChat
=
async
()
=>
{
// Only create a new chat if an app is selected
...
...
@@ -54,7 +121,52 @@ export function ChatHeader({
};
// TODO: KEEP UP TO DATE WITH app_handlers.ts
const
versionPostfix
=
versions
.
length
===
10
_000
?
`+`
:
""
;
// Check if we're not on the main branch
const
isNotMainBranch
=
branchInfo
?.
success
&&
branchInfo
.
data
.
branch
!==
"main"
;
return
(
<
div
className=
"flex flex-col w-full @container"
>
{
isNotMainBranch
&&
(
<
div
className=
"flex flex-col @sm:flex-row items-center justify-between px-4 py-2 bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200"
>
<
div
className=
"flex items-center gap-2 text-sm"
>
<
GitBranch
size=
{
16
}
/>
<
span
>
{
branchInfo
?.
data
.
branch
===
"<no-branch>"
&&
(
<>
<
TooltipProvider
>
<
Tooltip
>
<
TooltipTrigger
asChild
>
<
span
className=
"flex items-center gap-1"
>
<
strong
>
Warning:
</
strong
>
<
span
>
You are not on a branch
</
span
>
<
Info
size=
{
14
}
/>
</
span
>
</
TooltipTrigger
>
<
TooltipContent
>
<
p
>
Checkout main branch, otherwise changes will not be
saved properly
</
p
>
</
TooltipContent
>
</
Tooltip
>
</
TooltipProvider
>
</>
)
}
</
span
>
</
div
>
<
Button
variant=
"outline"
size=
"sm"
onClick=
{
handleCheckoutMainBranch
}
disabled=
{
checkingOutMain
}
>
{
checkingOutMain
?
"Checking out..."
:
"Switch to main branch"
}
</
Button
>
</
div
>
)
}
<
div
className=
"@container flex items-center justify-between py-1.5"
>
<
div
className=
"flex items-center space-x-2"
>
<
Button
...
...
@@ -86,5 +198,6 @@ export function ChatHeader({
)
}
</
button
>
</
div
>
</
div
>
);
}
src/ipc/handlers/version_handlers.ts
浏览文件 @
43ec6a45
...
...
@@ -2,7 +2,7 @@ import { ipcMain } from "electron";
import
{
db
}
from
"../../db"
;
import
{
apps
,
messages
}
from
"../../db/schema"
;
import
{
desc
,
eq
,
and
,
gt
}
from
"drizzle-orm"
;
import
type
{
Version
}
from
"../ipc_types"
;
import
type
{
Version
,
BranchResult
}
from
"../ipc_types"
;
import
fs
from
"node:fs"
;
import
path
from
"node:path"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
...
...
@@ -51,6 +51,53 @@ export function registerVersionHandlers() {
}
});
ipcMain
.
handle
(
"get-current-branch"
,
async
(
_
,
{
appId
}:
{
appId
:
number
}):
Promise
<
BranchResult
>
=>
{
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
if
(
!
app
)
{
return
{
success
:
false
,
errorMessage
:
"App not found"
,
};
}
const
appPath
=
getDyadAppPath
(
app
.
path
);
// Return appropriate result if the app is not a git repo
if
(
!
fs
.
existsSync
(
path
.
join
(
appPath
,
".git"
)))
{
return
{
success
:
false
,
errorMessage
:
"Not a git repository"
,
};
}
try
{
const
currentBranch
=
await
git
.
currentBranch
({
fs
,
dir
:
appPath
,
fullname
:
false
,
});
return
{
success
:
true
,
data
:
{
branch
:
currentBranch
||
"<no-branch>"
,
},
};
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error getting current branch for app
${
appId
}
:`
,
error
);
return
{
success
:
false
,
errorMessage
:
`Failed to get current branch:
${
error
.
message
}
`
,
};
}
}
);
ipcMain
.
handle
(
"revert-version"
,
async
(
...
...
src/ipc/ipc_client.ts
浏览文件 @
43ec6a45
...
...
@@ -22,6 +22,7 @@ import type {
TokenCountParams
,
TokenCountResult
,
ChatLogsData
,
BranchResult
,
}
from
"./ipc_types"
;
import
type
{
CodeProposal
,
ProposalResult
}
from
"@/lib/schemas"
;
import
{
showError
}
from
"@/lib/toast"
;
...
...
@@ -478,6 +479,14 @@ export class IpcClient {
}
}
// Get the current branch of an app
public
async
getCurrentBranch
(
appId
:
number
):
Promise
<
BranchResult
>
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"get-current-branch"
,
{
appId
,
});
return
result
;
}
// Get user settings
public
async
getUserSettings
():
Promise
<
UserSettings
>
{
try
{
...
...
src/ipc/ipc_types.ts
浏览文件 @
43ec6a45
...
...
@@ -74,6 +74,18 @@ export interface Version {
timestamp
:
number
;
}
export
type
Result
<
T
>
=
|
{
success
:
true
;
data
:
T
;
}
|
{
success
:
false
;
errorMessage
:
string
;
};
export
type
BranchResult
=
Result
<
{
branch
:
string
}
>
;
export
interface
SandboxConfig
{
files
:
Record
<
string
,
string
>
;
dependencies
:
Record
<
string
,
string
>
;
...
...
src/preload.ts
浏览文件 @
43ec6a45
...
...
@@ -25,6 +25,7 @@ const validInvokeChannels = [
"list-versions"
,
"revert-version"
,
"checkout-version"
,
"get-current-branch"
,
"delete-app"
,
"rename-app"
,
"get-user-settings"
,
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论