Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
1327e64e
Unverified
提交
1327e64e
authored
5月 02, 2025
作者:
Will Chen
提交者:
GitHub
5月 02, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Clear stale preview iframe errors (#65)
上级
79b7f865
显示空白字符变更
内嵌
并排
正在显示
8 个修改的文件
包含
75 行增加
和
57 行删除
+75
-57
appAtoms.ts
src/atoms/appAtoms.ts
+2
-0
ChatInput.tsx
src/components/chat/ChatInput.tsx
+0
-10
CodeView.tsx
src/components/preview_panel/CodeView.tsx
+1
-10
PreviewIframe.tsx
src/components/preview_panel/PreviewIframe.tsx
+18
-15
PreviewPanel.tsx
src/components/preview_panel/PreviewPanel.tsx
+3
-7
useRunApp.ts
src/hooks/useRunApp.ts
+48
-13
ipc_client.ts
src/ipc/ipc_client.ts
+2
-2
ipc_types.ts
src/ipc/ipc_types.ts
+1
-0
没有找到文件。
src/atoms/appAtoms.ts
浏览文件 @
1327e64e
...
...
@@ -19,3 +19,5 @@ export const userSettingsAtom = atom<UserSettings | null>(null);
export
const
envVarsAtom
=
atom
<
Record
<
string
,
string
|
undefined
>>
({});
export
const
previewPanelKeyAtom
=
atom
<
number
>
(
0
);
export
const
previewErrorMessageAtom
=
atom
<
string
|
undefined
>
(
undefined
);
src/components/chat/ChatInput.tsx
浏览文件 @
1327e64e
...
...
@@ -29,19 +29,13 @@ import {
}
from
"@/atoms/chatAtoms"
;
import
{
atom
,
useAtom
,
useSetAtom
,
useAtomValue
}
from
"jotai"
;
import
{
useStreamChat
}
from
"@/hooks/useStreamChat"
;
import
{
useChats
}
from
"@/hooks/useChats"
;
import
{
selectedAppIdAtom
}
from
"@/atoms/appAtoms"
;
import
{
useLoadApp
}
from
"@/hooks/useLoadApp"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
Switch
}
from
"@/components/ui/switch"
;
import
{
useProposal
}
from
"@/hooks/useProposal"
;
import
{
CodeProposal
,
ActionProposal
,
Proposal
,
SuggestedAction
,
ProposalResult
,
FileChange
,
SqlQuery
,
}
from
"@/lib/schemas"
;
...
...
@@ -69,7 +63,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
const
{
settings
,
updateSettings
,
isAnyProviderSetup
}
=
useSettings
();
const
{
streamMessage
,
isStreaming
,
setIsStreaming
,
error
,
setError
}
=
useStreamChat
();
const
[
selectedAppId
]
=
useAtom
(
selectedAppIdAtom
);
const
[
showError
,
setShowError
]
=
useState
(
true
);
const
[
isApproving
,
setIsApproving
]
=
useState
(
false
);
// State for approving
const
[
isRejecting
,
setIsRejecting
]
=
useState
(
false
);
// State for rejecting
...
...
@@ -77,8 +70,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
const
setIsPreviewOpen
=
useSetAtom
(
isPreviewOpenAtom
);
const
[
showTokenBar
,
setShowTokenBar
]
=
useAtom
(
showTokenBarAtom
);
const
{
refreshAppIframe
}
=
useRunApp
();
// Use the hook to fetch the proposal
const
{
proposalResult
,
...
...
@@ -171,7 +162,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
}
finally
{
setIsApproving
(
false
);
setIsPreviewOpen
(
true
);
refreshAppIframe
();
// Keep same as handleReject
refreshProposal
();
...
...
src/components/preview_panel/CodeView.tsx
浏览文件 @
1327e64e
...
...
@@ -13,12 +13,11 @@ interface App {
export
interface
CodeViewProps
{
loading
:
boolean
;
error
:
Error
|
null
;
app
:
App
|
null
;
}
// Code view component that displays app files or status messages
export
const
CodeView
=
({
loading
,
error
,
app
}:
CodeViewProps
)
=>
{
export
const
CodeView
=
({
loading
,
app
}:
CodeViewProps
)
=>
{
const
selectedFile
=
useAtomValue
(
selectedFileAtom
);
const
{
refreshApp
}
=
useLoadApp
(
app
?.
id
??
null
);
...
...
@@ -26,14 +25,6 @@ export const CodeView = ({ loading, error, app }: CodeViewProps) => {
return
<
div
className=
"text-center py-4"
>
Loading files...
</
div
>;
}
if
(
error
)
{
return
(
<
div
className=
"text-center py-4 text-red-500"
>
Error loading files:
{
error
.
message
}
</
div
>
);
}
if
(
!
app
)
{
return
(
<
div
className=
"text-center py-4 text-gray-500"
>
No app selected
</
div
>
...
...
src/components/preview_panel/PreviewIframe.tsx
浏览文件 @
1327e64e
import
{
selectedAppIdAtom
,
appUrlAtom
,
appOutputAtom
}
from
"@/atoms/appAtoms"
;
import
{
useAtomValue
,
useSetAtom
}
from
"jotai"
;
import
{
selectedAppIdAtom
,
appUrlAtom
,
appOutputAtom
,
previewErrorMessageAtom
,
}
from
"@/atoms/appAtoms"
;
import
{
useAtomValue
,
useSetAtom
,
useAtom
}
from
"jotai"
;
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
ArrowLeft
,
...
...
@@ -23,6 +28,7 @@ import {
DropdownMenuTrigger
,
}
from
"@/components/ui/dropdown-menu"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
useRunApp
}
from
"@/hooks/useRunApp"
;
interface
ErrorBannerProps
{
error
:
string
|
undefined
;
...
...
@@ -78,23 +84,13 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
};
// Preview iframe component
export
const
PreviewIframe
=
({
loading
,
loadingErrorMessage
,
}:
{
loading
:
boolean
;
loadingErrorMessage
:
string
|
undefined
;
})
=>
{
export
const
PreviewIframe
=
({
loading
}:
{
loading
:
boolean
})
=>
{
const
selectedAppId
=
useAtomValue
(
selectedAppIdAtom
);
const
{
appUrl
}
=
useAtomValue
(
appUrlAtom
);
const
setAppOutput
=
useSetAtom
(
appOutputAtom
);
const
{
app
}
=
useLoadApp
(
selectedAppId
);
// State to trigger iframe reload
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[
errorMessage
,
setErrorMessage
]
=
useState
<
string
|
undefined
>
(
loadingErrorMessage
);
const
[
errorMessage
,
setErrorMessage
]
=
useAtom
(
previewErrorMessageAtom
);
const
setInputValue
=
useSetAtom
(
chatInputValueAtom
);
const
[
availableRoutes
,
setAvailableRoutes
]
=
useState
<
Array
<
{
path
:
string
;
label
:
string
}
>
...
...
@@ -179,6 +175,7 @@ export const PreviewIframe = ({
message
:
`Iframe error:
${
errorMessage
}
`
,
type
:
"client-error"
,
appId
:
selectedAppId
!
,
timestamp
:
Date
.
now
(),
},
]);
}
else
if
(
type
===
"pushState"
||
type
===
"replaceState"
)
{
...
...
@@ -204,7 +201,13 @@ export const PreviewIframe = ({
window
.
addEventListener
(
"message"
,
handleMessage
);
return
()
=>
window
.
removeEventListener
(
"message"
,
handleMessage
);
},
[
navigationHistory
,
currentHistoryPosition
,
selectedAppId
]);
},
[
navigationHistory
,
currentHistoryPosition
,
selectedAppId
,
errorMessage
,
setErrorMessage
,
]);
useEffect
(()
=>
{
// Update navigation buttons state
...
...
src/components/preview_panel/PreviewPanel.tsx
浏览文件 @
1327e64e
...
...
@@ -149,7 +149,7 @@ export function PreviewPanel() {
const
[
previewMode
,
setPreviewMode
]
=
useAtom
(
previewModeAtom
);
const
selectedAppId
=
useAtomValue
(
selectedAppIdAtom
);
const
[
isConsoleOpen
,
setIsConsoleOpen
]
=
useState
(
false
);
const
{
runApp
,
stopApp
,
restartApp
,
error
,
loading
,
app
}
=
useRunApp
();
const
{
runApp
,
stopApp
,
restartApp
,
loading
,
app
}
=
useRunApp
();
const
runningAppIdRef
=
useRef
<
number
|
null
>
(
null
);
const
key
=
useAtomValue
(
previewPanelKeyAtom
);
const
appOutput
=
useAtomValue
(
appOutputAtom
);
...
...
@@ -223,13 +223,9 @@ export function PreviewPanel() {
<
Panel
id=
"content"
minSize=
{
30
}
>
<
div
className=
"h-full overflow-y-auto"
>
{
previewMode
===
"preview"
?
(
<
PreviewIframe
key=
{
key
}
loading=
{
loading
}
loadingErrorMessage=
{
error
?.
message
}
/>
<
PreviewIframe
key=
{
key
}
loading=
{
loading
}
/>
)
:
(
<
CodeView
loading=
{
loading
}
error=
{
error
}
app=
{
app
}
/>
<
CodeView
loading=
{
loading
}
app=
{
app
}
/>
)
}
</
div
>
</
Panel
>
...
...
src/hooks/useRunApp.ts
浏览文件 @
1327e64e
...
...
@@ -5,6 +5,7 @@ import {
appUrlAtom
,
currentAppAtom
,
previewPanelKeyAtom
,
previewErrorMessageAtom
,
selectedAppIdAtom
,
}
from
"@/atoms/appAtoms"
;
import
{
useAtom
,
useAtomValue
,
useSetAtom
}
from
"jotai"
;
...
...
@@ -12,12 +13,12 @@ import { App } from "@/ipc/ipc_types";
export
function
useRunApp
()
{
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
<
Error
|
null
>
(
null
);
const
[
app
,
setApp
]
=
useAtom
(
currentAppAtom
);
const
setAppOutput
=
useSetAtom
(
appOutputAtom
);
const
[
appUrlObj
,
setAppUrlObj
]
=
useAtom
(
appUrlAtom
);
const
setPreviewPanelKey
=
useSetAtom
(
previewPanelKeyAtom
);
const
appId
=
useAtomValue
(
selectedAppIdAtom
);
const
setPreviewErrorMessage
=
useSetAtom
(
previewErrorMessageAtom
);
const
runApp
=
useCallback
(
async
(
appId
:
number
)
=>
{
setLoading
(
true
);
try
{
...
...
@@ -30,7 +31,12 @@ export function useRunApp() {
}
setAppOutput
((
prev
)
=>
[
...
prev
,
{
message
:
"Trying to restart app..."
,
type
:
"stdout"
,
appId
},
{
message
:
"Trying to restart app..."
,
type
:
"stdout"
,
appId
,
timestamp
:
Date
.
now
(),
},
]);
const
app
=
await
ipcClient
.
getApp
(
appId
);
setApp
(
app
);
...
...
@@ -42,10 +48,12 @@ export function useRunApp() {
setAppUrlObj
({
appUrl
:
urlMatch
[
1
],
appId
});
}
});
set
Error
(
null
);
set
PreviewErrorMessage
(
undefined
);
}
catch
(
error
)
{
console
.
error
(
`Error running app
${
appId
}
:`
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
setPreviewErrorMessage
(
error
instanceof
Error
?
error
.
message
:
error
?.
toString
()
);
}
finally
{
setLoading
(
false
);
}
...
...
@@ -61,15 +69,22 @@ export function useRunApp() {
const
ipcClient
=
IpcClient
.
getInstance
();
await
ipcClient
.
stopApp
(
appId
);
set
Error
(
null
);
set
PreviewErrorMessage
(
undefined
);
}
catch
(
error
)
{
console
.
error
(
`Error stopping app
${
appId
}
:`
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
setPreviewErrorMessage
(
error
instanceof
Error
?
error
.
message
:
error
?.
toString
()
);
}
finally
{
setLoading
(
false
);
}
},
[]);
const
onHotModuleReload
=
useCallback
(()
=>
{
setPreviewPanelKey
((
prevKey
)
=>
prevKey
+
1
);
setPreviewErrorMessage
(
undefined
);
},
[
setPreviewPanelKey
,
setPreviewErrorMessage
]);
const
restartApp
=
useCallback
(
async
({
removeNodeModules
=
false
,
...
...
@@ -90,7 +105,12 @@ export function useRunApp() {
setAppUrlObj
({
appUrl
:
null
,
appId
:
null
});
setAppOutput
((
prev
)
=>
[
...
prev
,
{
message
:
"Restarting app..."
,
type
:
"stdout"
,
appId
},
{
message
:
"Restarting app..."
,
type
:
"stdout"
,
appId
,
timestamp
:
Date
.
now
(),
},
]);
const
app
=
await
ipcClient
.
getApp
(
appId
);
...
...
@@ -99,6 +119,13 @@ export function useRunApp() {
appId
,
(
output
)
=>
{
setAppOutput
((
prev
)
=>
[...
prev
,
output
]);
if
(
output
.
message
.
includes
(
"hmr update"
)
&&
output
.
message
.
includes
(
"[vite]"
)
)
{
onHotModuleReload
();
return
;
}
// Check if the output contains a localhost URL
const
urlMatch
=
output
.
message
.
match
(
/
(
https
?
:
\/\/
localhost:
\d
+
\/?)
/
...
...
@@ -109,22 +136,30 @@ export function useRunApp() {
},
removeNodeModules
);
setError
(
null
);
}
catch
(
error
)
{
console
.
error
(
`Error restarting app
${
appId
}
:`
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
setPreviewErrorMessage
(
error
instanceof
Error
?
error
.
message
:
error
?.
toString
()
);
}
finally
{
setPreviewPanelKey
((
prevKey
)
=>
prevKey
+
1
);
setLoading
(
false
);
}
},
[
appId
,
setApp
,
setAppOutput
,
setAppUrlObj
,
set
Error
,
set
PreviewPanelKey
]
[
appId
,
setApp
,
setAppOutput
,
setAppUrlObj
,
setPreviewPanelKey
]
);
const
refreshAppIframe
=
useCallback
(
async
()
=>
{
setPreviewPanelKey
((
prevKey
)
=>
prevKey
+
1
);
set
Error
(
null
);
},
[
setPreviewPanelKey
,
set
Error
]);
set
PreviewErrorMessage
(
undefined
);
},
[
setPreviewPanelKey
,
set
PreviewErrorMessage
]);
return
{
loading
,
error
,
runApp
,
stopApp
,
restartApp
,
app
,
refreshAppIframe
};
return
{
loading
,
runApp
,
stopApp
,
restartApp
,
app
,
refreshAppIframe
,
};
}
src/ipc/ipc_client.ts
浏览文件 @
1327e64e
...
...
@@ -99,10 +99,10 @@ export class IpcClient {
"message"
in
data
&&
"appId"
in
data
)
{
const
{
type
,
message
,
appId
}
=
data
as
AppOutput
;
const
{
type
,
message
,
appId
}
=
data
as
unknown
as
AppOutput
;
const
callbacks
=
this
.
appStreams
.
get
(
appId
);
if
(
callbacks
)
{
callbacks
.
onOutput
({
type
,
message
,
appId
});
callbacks
.
onOutput
({
type
,
message
,
appId
,
timestamp
:
Date
.
now
()
});
}
}
else
{
showError
(
new
Error
(
`[IPC] Invalid app output data received:
${
data
}
`
));
...
...
src/ipc/ipc_types.ts
浏览文件 @
1327e64e
export
interface
AppOutput
{
type
:
"stdout"
|
"stderr"
|
"info"
|
"client-error"
;
message
:
string
;
timestamp
:
number
;
appId
:
number
;
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论