Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
c71638a5
Unverified
提交
c71638a5
authored
5月 09, 2025
作者:
Will Chen
提交者:
GitHub
5月 09, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Simplify handlers & IPC client: move from Result pattern to throwing errors (#120)
上级
26305ee0
隐藏空白字符变更
内嵌
并排
正在显示
25 个修改的文件
包含
608 行增加
和
980 行删除
+608
-980
ChatList.tsx
src/components/ChatList.tsx
+1
-5
GitHubConnector.tsx
src/components/GitHubConnector.tsx
+6
-14
HelpDialog.tsx
src/components/HelpDialog.tsx
+1
-6
ChatInput.tsx
src/components/chat/ChatInput.tsx
+13
-23
MessagesList.tsx
src/components/chat/MessagesList.tsx
+3
-4
FileEditor.tsx
src/components/preview_panel/FileEditor.tsx
+2
-2
useSupabase.ts
src/hooks/useSupabase.ts
+4
-16
app_handlers.ts
src/ipc/handlers/app_handlers.ts
+171
-160
chat_handlers.ts
src/ipc/handlers/chat_handlers.ts
+32
-46
dependency_handlers.ts
src/ipc/handlers/dependency_handlers.ts
+7
-3
github_handlers.ts
src/ipc/handlers/github_handlers.ts
+54
-69
local_model_lmstudio_handler.ts
src/ipc/handlers/local_model_lmstudio_handler.ts
+17
-21
local_model_ollama_handler.ts
src/ipc/handlers/local_model_ollama_handler.ts
+5
-8
proposal_handlers.ts
src/ipc/handlers/proposal_handlers.ts
+59
-91
safe_handle.ts
src/ipc/handlers/safe_handle.ts
+5
-2
settings_handlers.ts
src/ipc/handlers/settings_handlers.ts
+2
-0
shell_handler.ts
src/ipc/handlers/shell_handler.ts
+18
-34
supabase_handlers.ts
src/ipc/handlers/supabase_handlers.ts
+18
-42
token_count_handlers.ts
src/ipc/handlers/token_count_handlers.ts
+59
-69
upload_handlers.ts
src/ipc/handlers/upload_handlers.ts
+34
-44
version_handlers.ts
src/ipc/handlers/version_handlers.ts
+2
-2
ipc_client.ts
src/ipc/ipc_client.ts
+91
-311
ipc_types.ts
src/ipc/ipc_types.ts
+0
-1
app-details.tsx
src/pages/app-details.tsx
+2
-1
settings.tsx
src/pages/settings.tsx
+2
-6
没有找到文件。
src/components/ChatList.tsx
浏览文件 @
c71638a5
...
...
@@ -92,11 +92,7 @@ export function ChatList({ show }: { show?: boolean }) {
const
handleDeleteChat
=
async
(
chatId
:
number
)
=>
{
try
{
const
result
=
await
IpcClient
.
getInstance
().
deleteChat
(
chatId
);
if
(
!
result
.
success
)
{
showError
(
"Failed to delete chat"
);
return
;
}
await
IpcClient
.
getInstance
().
deleteChat
(
chatId
);
showSuccess
(
"Chat deleted successfully"
);
// If the deleted chat was selected, navigate to home
...
...
src/components/GitHubConnector.tsx
浏览文件 @
c71638a5
...
...
@@ -153,18 +153,14 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
setIsCreatingRepo
(
true
);
setCreateRepoSuccess
(
false
);
try
{
const
result
=
await
IpcClient
.
getInstance
().
createGithubRepo
(
await
IpcClient
.
getInstance
().
createGithubRepo
(
githubOrg
,
repoName
,
appId
!
,
);
if
(
result
.
success
)
{
setCreateRepoSuccess
(
true
);
setRepoCheckError
(
null
);
refreshApp
();
}
else
{
setCreateRepoError
(
result
.
error
||
"Failed to create repository."
);
}
setCreateRepoSuccess
(
true
);
setRepoCheckError
(
null
);
refreshApp
();
}
catch
(
err
:
any
)
{
setCreateRepoError
(
err
.
message
||
"Failed to create repository."
);
}
finally
{
...
...
@@ -180,12 +176,8 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
setIsDisconnecting
(
true
);
setDisconnectError
(
null
);
try
{
const
result
=
await
IpcClient
.
getInstance
().
disconnectGithubRepo
(
appId
);
if
(
result
.
success
)
{
refreshApp
();
}
else
{
setDisconnectError
(
result
.
error
||
"Failed to disconnect repository."
);
}
await
IpcClient
.
getInstance
().
disconnectGithubRepo
(
appId
);
refreshApp
();
}
catch
(
err
:
any
)
{
setDisconnectError
(
err
.
message
||
"Failed to disconnect repository."
);
}
finally
{
...
...
src/components/HelpDialog.tsx
浏览文件 @
c71638a5
...
...
@@ -171,17 +171,12 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
const
{
uploadUrl
,
filename
}
=
await
response
.
json
();
// Upload to the signed URL using IPC
const
uploadResult
=
await
IpcClient
.
getInstance
().
uploadToSignedUrl
(
await
IpcClient
.
getInstance
().
uploadToSignedUrl
(
uploadUrl
,
"application/json"
,
chatLogsJson
,
);
if
(
!
uploadResult
.
success
)
{
throw
new
Error
(
`Failed to upload logs:
${
uploadResult
.
error
}
`
);
}
// Extract session ID (filename without extension)
const
sessionId
=
filename
.
replace
(
".json"
,
""
);
setSessionId
(
sessionId
);
...
...
src/components/chat/ChatInput.tsx
浏览文件 @
c71638a5
...
...
@@ -58,7 +58,7 @@ import { useVersions } from "@/hooks/useVersions";
import
{
useAttachments
}
from
"@/hooks/useAttachments"
;
import
{
AttachmentsList
}
from
"./AttachmentsList"
;
import
{
DragDropOverlay
}
from
"./DragDropOverlay"
;
import
{
showUncommittedFilesWarning
}
from
"@/lib/toast"
;
import
{
show
Error
,
show
UncommittedFilesWarning
}
from
"@/lib/toast"
;
const
showTokenBarAtom
=
atom
(
false
);
export
function
ChatInput
({
chatId
}:
{
chatId
?:
number
})
{
...
...
@@ -182,13 +182,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
chatId
,
messageId
,
});
if
(
result
.
success
)
{
console
.
log
(
"Proposal approved successfully"
);
// TODO: Maybe refresh proposal state or show confirmation?
}
else
{
console
.
error
(
"Failed to approve proposal:"
,
result
.
error
);
setError
(
result
.
error
||
"Failed to approve proposal"
);
}
if
(
result
.
uncommittedFiles
)
{
showUncommittedFilesWarning
(
result
.
uncommittedFiles
);
}
...
...
@@ -215,17 +208,10 @@ export function ChatInput({ chatId }: { chatId?: number }) {
setIsRejecting
(
true
);
posthog
.
capture
(
"chat:reject"
);
try
{
const
result
=
await
IpcClient
.
getInstance
().
rejectProposal
({
await
IpcClient
.
getInstance
().
rejectProposal
({
chatId
,
messageId
,
});
if
(
result
.
success
)
{
console
.
log
(
"Proposal rejected successfully"
);
// TODO: Maybe refresh proposal state or show confirmation?
}
else
{
console
.
error
(
"Failed to reject proposal:"
,
result
.
error
);
setError
(
result
.
error
||
"Failed to reject proposal"
);
}
}
catch
(
err
)
{
console
.
error
(
"Error rejecting proposal:"
,
err
);
setError
((
err
as
Error
)?.
message
||
"An error occurred while rejecting"
);
...
...
@@ -389,13 +375,17 @@ function SummarizeInNewChatButton() {
console
.
error
(
"No app id found"
);
return
;
}
const
newChatId
=
await
IpcClient
.
getInstance
().
createChat
(
appId
);
// navigate to new chat
await
navigate
({
to
:
"/chat"
,
search
:
{
id
:
newChatId
}
});
await
streamMessage
({
prompt
:
"Summarize from chat-id="
+
chatId
,
chatId
:
newChatId
,
});
try
{
const
newChatId
=
await
IpcClient
.
getInstance
().
createChat
(
appId
);
// navigate to new chat
await
navigate
({
to
:
"/chat"
,
search
:
{
id
:
newChatId
}
});
await
streamMessage
({
prompt
:
"Summarize from chat-id="
+
chatId
,
chatId
:
newChatId
,
});
}
catch
(
err
)
{
showError
(
err
);
}
};
return
(
<
TooltipProvider
>
...
...
src/components/chat/MessagesList.tsx
浏览文件 @
c71638a5
...
...
@@ -89,14 +89,13 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
await
revertVersion
({
versionId
:
chat
.
initialCommitHash
,
});
const
result
=
try
{
await
IpcClient
.
getInstance
().
deleteMessages
(
selectedChatId
,
);
if
(
result
.
success
)
{
setMessages
([]);
}
else
{
showError
(
result
.
erro
r
);
}
catch
(
err
)
{
showError
(
er
r
);
}
}
else
{
showWarning
(
...
...
src/components/preview_panel/FileEditor.tsx
浏览文件 @
c71638a5
...
...
@@ -6,6 +6,7 @@ import { ChevronRight, Circle } from "lucide-react";
import
"@/components/chat/monaco"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
showError
}
from
"@/lib/toast"
;
interface
FileEditorProps
{
appId
:
number
|
null
;
...
...
@@ -132,8 +133,7 @@ export const FileEditor = ({ appId, filePath }: FileEditorProps) => {
needsSaveRef
.
current
=
false
;
setDisplayUnsavedChanges
(
false
);
}
catch
(
error
)
{
console
.
error
(
"Error saving file:"
,
error
);
// Could add error notification here
showError
(
error
);
}
finally
{
isSavingRef
.
current
=
false
;
}
...
...
src/hooks/useSupabase.ts
浏览文件 @
c71638a5
...
...
@@ -42,14 +42,8 @@ export function useSupabase() {
async
(
projectId
:
string
,
appId
:
number
)
=>
{
setLoading
(
true
);
try
{
const
result
=
await
ipcClient
.
setSupabaseAppProject
(
projectId
,
appId
);
if
(
result
.
success
)
{
setError
(
null
);
return
result
;
}
else
{
throw
new
Error
(
"Failed to set project for app"
);
}
await
ipcClient
.
setSupabaseAppProject
(
projectId
,
appId
);
setError
(
null
);
}
catch
(
error
)
{
console
.
error
(
"Error setting Supabase project for app:"
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
...
...
@@ -68,14 +62,8 @@ export function useSupabase() {
async
(
appId
:
number
)
=>
{
setLoading
(
true
);
try
{
const
result
=
await
ipcClient
.
unsetSupabaseAppProject
(
appId
);
if
(
result
.
success
)
{
setError
(
null
);
return
result
;
}
else
{
throw
new
Error
(
"Failed to unset project for app"
);
}
await
ipcClient
.
unsetSupabaseAppProject
(
appId
);
setError
(
null
);
}
catch
(
error
)
{
console
.
error
(
"Error unsetting Supabase project for app:"
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
...
...
src/ipc/handlers/app_handlers.ts
浏览文件 @
c71638a5
...
...
@@ -32,8 +32,10 @@ import killPort from "kill-port";
import
util
from
"util"
;
import
log
from
"electron-log"
;
import
{
getSupabaseProjectName
}
from
"../../supabase_admin/supabase_management_client"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"app_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
// Needed, otherwise electron in MacOS/Linux will not be able
// to find node/pnpm.
...
...
@@ -137,75 +139,80 @@ async function killProcessOnPort(port: number): Promise<void> {
}
export
function
registerAppHandlers
()
{
ipcMain
.
handle
(
"create-app"
,
async
(
_
,
params
:
CreateAppParams
)
=>
{
const
appPath
=
params
.
name
;
const
fullAppPath
=
getDyadAppPath
(
appPath
);
if
(
fs
.
existsSync
(
fullAppPath
))
{
throw
new
Error
(
`App already exists at:
${
fullAppPath
}
`
);
}
// Create a new app
const
[
app
]
=
await
db
.
insert
(
apps
)
.
values
({
name
:
params
.
name
,
// Use the name as the path for now
path
:
appPath
,
})
.
returning
();
// Create an initial chat for this app
const
[
chat
]
=
await
db
.
insert
(
chats
)
.
values
({
appId
:
app
.
id
,
})
.
returning
();
// Start async operations in background
try
{
// Copy scaffold asynchronously
await
copyDirectoryRecursive
(
path
.
join
(
__dirname
,
".."
,
".."
,
"scaffold"
),
fullAppPath
,
);
// Initialize git repo and create first commit
await
git
.
init
({
fs
:
fs
,
dir
:
fullAppPath
,
defaultBranch
:
"main"
,
});
handle
(
"create-app"
,
async
(
_
,
params
:
CreateAppParams
,
):
Promise
<
{
app
:
any
;
chatId
:
number
}
>
=>
{
const
appPath
=
params
.
name
;
const
fullAppPath
=
getDyadAppPath
(
appPath
);
if
(
fs
.
existsSync
(
fullAppPath
))
{
throw
new
Error
(
`App already exists at:
${
fullAppPath
}
`
);
}
// Create a new app
const
[
app
]
=
await
db
.
insert
(
apps
)
.
values
({
name
:
params
.
name
,
// Use the name as the path for now
path
:
appPath
,
})
.
returning
();
// Stage all files
await
git
.
add
({
fs
:
fs
,
dir
:
fullAppPath
,
filepath
:
"."
,
});
// Create an initial chat for this app
const
[
chat
]
=
await
db
.
insert
(
chats
)
.
values
({
appId
:
app
.
id
,
})
.
returning
();
// Create initial commit
const
commitHash
=
await
git
.
commit
({
fs
:
fs
,
dir
:
fullAppPath
,
message
:
"Init from react vite template"
,
author
:
await
getGitAuthor
(),
});
// Start async operations in background
try
{
// Copy scaffold asynchronously
await
copyDirectoryRecursive
(
path
.
join
(
__dirname
,
".."
,
".."
,
"scaffold"
),
fullAppPath
,
);
// Initialize git repo and create first commit
await
git
.
init
({
fs
:
fs
,
dir
:
fullAppPath
,
defaultBranch
:
"main"
,
});
// Update chat with initial commit hash
await
db
.
update
(
chats
)
.
set
({
initialCommitHash
:
commitHash
,
})
.
where
(
eq
(
chats
.
id
,
chat
.
id
));
}
catch
(
error
)
{
logger
.
error
(
"Error in background app initialization:"
,
error
);
}
// })();
// Stage all files
await
git
.
add
({
fs
:
fs
,
dir
:
fullAppPath
,
filepath
:
"."
,
});
return
{
app
,
chatId
:
chat
.
id
};
});
// Create initial commit
const
commitHash
=
await
git
.
commit
({
fs
:
fs
,
dir
:
fullAppPath
,
message
:
"Init from react vite template"
,
author
:
await
getGitAuthor
(),
});
// Update chat with initial commit hash
await
db
.
update
(
chats
)
.
set
({
initialCommitHash
:
commitHash
,
})
.
where
(
eq
(
chats
.
id
,
chat
.
id
));
}
catch
(
error
)
{
logger
.
error
(
"Error in background app initialization:"
,
error
);
}
return
{
app
,
chatId
:
chat
.
id
};
},
);
ipcMain
.
handle
(
"get-app"
,
async
(
_
,
appId
:
number
):
Promise
<
App
>
=>
{
handle
(
"get-app"
,
async
(
_
,
appId
:
number
):
Promise
<
App
>
=>
{
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
...
...
@@ -281,6 +288,7 @@ export function registerAppHandlers() {
},
);
// Do NOT use handle for this, it contains sensitive information.
ipcMain
.
handle
(
"get-env-vars"
,
async
()
=>
{
const
envVars
:
Record
<
string
,
string
|
undefined
>
=
{};
for
(
const
key
of
ALLOWED_ENV_VARS
)
{
...
...
@@ -294,13 +302,12 @@ export function registerAppHandlers() {
async
(
event
:
Electron
.
IpcMainInvokeEvent
,
{
appId
}:
{
appId
:
number
},
)
=>
{
)
:
Promise
<
void
>
=>
{
return
withLock
(
appId
,
async
()
=>
{
// Check if app is already running
if
(
runningApps
.
has
(
appId
))
{
logger
.
debug
(
`App
${
appId
}
is already running.`
);
// Potentially return the existing process info or confirm status
return
{
success
:
true
,
message
:
"App already running."
};
return
;
}
const
app
=
await
db
.
query
.
apps
.
findFirst
({
...
...
@@ -315,9 +322,9 @@ export function registerAppHandlers() {
const
appPath
=
getDyadAppPath
(
app
.
path
);
try
{
const
currentProcessId
=
await
executeApp
({
appPath
,
appId
,
event
});
await
executeApp
({
appPath
,
appId
,
event
});
return
{
success
:
true
,
processId
:
currentProcessId
}
;
return
;
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error running app
${
appId
}
:`
,
error
);
// Ensure cleanup if error happens during setup but before process events are handled
...
...
@@ -333,56 +340,56 @@ export function registerAppHandlers() {
},
);
ipcMain
.
handle
(
"stop-app"
,
async
(
_
,
{
appId
}:
{
appId
:
number
})
=>
{
logger
.
log
(
`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.`
,
);
return
{
success
:
true
,
message
:
"App not running."
,
};
}
const
{
process
,
processId
}
=
appInfo
;
ipcMain
.
handle
(
"stop-app"
,
async
(
_
,
{
appId
}:
{
appId
:
number
}):
Promise
<
void
>
=>
{
logger
.
log
(
`
Found running app
${
appId
}
with processId
${
processId
}
(PID:
${
process
.
pid
}
). Attempting to stop.
`
,
`
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.`
,
);
return
;
}
// Check if the process is already exited or closed
if
(
process
.
exitCode
!==
null
||
process
.
signalCode
!==
null
)
{
const
{
process
,
processId
}
=
appInfo
;
logger
.
log
(
`
Process for app
${
appId
}
(PID:
${
process
.
pid
}
) already exited (code:
${
process
.
exitCode
}
, signal:
${
process
.
signalCode
}
). Cleaning up ma
p.`
,
`
Found running app
${
appId
}
with processId
${
processId
}
(PID:
${
process
.
pid
}
). Attempting to sto
p.`
,
);
runningApps
.
delete
(
appId
);
// Ensure cleanup if somehow missed
return
{
success
:
true
,
message
:
"Process already exited."
};
}
try
{
// Use the killProcess utility to stop the process
await
killProcess
(
process
);
// 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.`
,
);
runningApps
.
delete
(
appId
);
// Ensure cleanup if somehow missed
return
;
}
// Now, safely remove the app from the map *after* confirming closure
removeAppIfCurrentProcess
(
appId
,
process
);
try
{
// Use the killProcess utility to stop the process
await
killProcess
(
process
);
return
{
success
:
true
};
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error stopping app
${
appId
}
(PID:
${
process
.
pid
}
, processId:
${
processId
}
):`
,
error
,
);
// Attempt cleanup even if an error occurred during the stop process
removeAppIfCurrentProcess
(
appId
,
process
);
throw
new
Error
(
`Failed to stop app
${
appId
}
:
${
error
.
message
}
`
);
}
});
});
// Now, safely remove the app from the map *after* confirming closure
removeAppIfCurrentProcess
(
appId
,
process
);
return
;
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error stopping app
${
appId
}
(PID:
${
process
.
pid
}
, processId:
${
processId
}
):`
,
error
,
);
// Attempt cleanup even if an error occurred during the stop process
removeAppIfCurrentProcess
(
appId
,
process
);
throw
new
Error
(
`Failed to stop app
${
appId
}
:
${
error
.
message
}
`
);
}
});
},
);
ipcMain
.
handle
(
"restart-app"
,
...
...
@@ -392,7 +399,7 @@ export function registerAppHandlers() {
appId
,
removeNodeModules
,
}:
{
appId
:
number
;
removeNodeModules
?:
boolean
},
)
=>
{
)
:
Promise
<
void
>
=>
{
logger
.
log
(
`Restarting app
${
appId
}
`
);
return
withLock
(
appId
,
async
()
=>
{
try
{
...
...
@@ -447,7 +454,7 @@ export function registerAppHandlers() {
await
executeApp
({
appPath
,
appId
,
event
});
// This will handle starting either mode
return
{
success
:
true
}
;
return
;
}
catch
(
error
)
{
logger
.
error
(
`Error restarting app
${
appId
}
:`
,
error
);
throw
error
;
...
...
@@ -465,7 +472,7 @@ export function registerAppHandlers() {
filePath
,
content
,
}:
{
appId
:
number
;
filePath
:
string
;
content
:
string
},
)
=>
{
)
:
Promise
<
void
>
=>
{
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
...
...
@@ -505,7 +512,7 @@ export function registerAppHandlers() {
});
}
return
{
success
:
true
}
;
return
;
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error writing file
${
filePath
}
for app
${
appId
}
:`
,
error
);
throw
new
Error
(
`Failed to write file:
${
error
.
message
}
`
);
...
...
@@ -513,52 +520,57 @@ export function registerAppHandlers() {
},
);
ipcMain
.
handle
(
"delete-app"
,
async
(
_
,
{
appId
}:
{
appId
:
number
})
=>
{
// Static server worker is NOT terminated here anymore
ipcMain
.
handle
(
"delete-app"
,
async
(
_
,
{
appId
}:
{
appId
:
number
}):
Promise
<
void
>
=>
{
// Static server worker is NOT terminated here anymore
return
withLock
(
appId
,
async
()
=>
{
// Check if app exists
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
return
withLock
(
appId
,
async
()
=>
{
// Check if app exists
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
if
(
!
app
)
{
throw
new
Error
(
"App not found"
);
}
if
(
!
app
)
{
throw
new
Error
(
"App not found"
);
}
// Stop the app if it's running
if
(
runningApps
.
has
(
appId
))
{
const
appInfo
=
runningApps
.
get
(
appId
)
!
;
// Stop the app if it's running
if
(
runningApps
.
has
(
appId
))
{
const
appInfo
=
runningApps
.
get
(
appId
)
!
;
try
{
logger
.
log
(
`Stopping app
${
appId
}
before deletion.`
);
// Adjusted log
await
killProcess
(
appInfo
.
process
);
runningApps
.
delete
(
appId
);
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error stopping app
${
appId
}
before deletion:`
,
error
);
// Adjusted log
// Continue with deletion even if stopping fails
}
}
// Delete app files
const
appPath
=
getDyadAppPath
(
app
.
path
);
try
{
logger
.
log
(
`Stopping app
${
appId
}
before deletion.`
);
// Adjusted log
await
killProcess
(
appInfo
.
process
);
runningApps
.
delete
(
appId
);
await
fsPromises
.
rm
(
appPath
,
{
recursive
:
true
,
force
:
true
});
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error
stopping app
${
appId
}
before deletion:`
,
error
);
// Adjusted log
// Continue with deletion even if stopping fails
logger
.
error
(
`Error
deleting app files for app
${
appId
}
:`
,
error
);
throw
new
Error
(
`Failed to delete app files:
${
error
.
message
}
`
);
}
}
// Delete app files
const
appPath
=
getDyadAppPath
(
app
.
path
);
try
{
await
fsPromises
.
rm
(
appPath
,
{
recursive
:
true
,
force
:
true
});
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error deleting app files for app
${
appId
}
:`
,
error
);
throw
new
Error
(
`Failed to delete app files:
${
error
.
message
}
`
);
}
// Delete app from database
try
{
await
db
.
delete
(
apps
).
where
(
eq
(
apps
.
id
,
appId
));
// Note: Associated chats will cascade delete if that's set up in the schema
return
{
success
:
true
};
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error deleting app
${
appId
}
from database:`
,
error
);
throw
new
Error
(
`Failed to delete app from database:
${
error
.
message
}
`
);
}
});
});
// Delete app from database
try
{
await
db
.
delete
(
apps
).
where
(
eq
(
apps
.
id
,
appId
));
// Note: Associated chats will cascade delete if that's set up in the schema
return
;
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error deleting app
${
appId
}
from database:`
,
error
);
throw
new
Error
(
`Failed to delete app from database:
${
error
.
message
}
`
,
);
}
});
},
);
ipcMain
.
handle
(
"rename-app"
,
...
...
@@ -569,7 +581,7 @@ export function registerAppHandlers() {
appName
,
appPath
,
}:
{
appId
:
number
;
appName
:
string
;
appPath
:
string
},
)
=>
{
)
:
Promise
<
void
>
=>
{
return
withLock
(
appId
,
async
()
=>
{
// Check if app exists
const
app
=
await
db
.
query
.
apps
.
findFirst
({
...
...
@@ -642,7 +654,7 @@ export function registerAppHandlers() {
// Update app in database
try
{
const
[
updatedApp
]
=
await
db
await
db
.
update
(
apps
)
.
set
({
name
:
appName
,
...
...
@@ -651,7 +663,7 @@ export function registerAppHandlers() {
.
where
(
eq
(
apps
.
id
,
appId
))
.
returning
();
return
{
success
:
true
,
app
:
updatedApp
}
;
return
;
}
catch
(
error
:
any
)
{
// Attempt to rollback the file move
if
(
newAppPath
!==
oldAppPath
)
{
...
...
@@ -672,7 +684,7 @@ export function registerAppHandlers() {
},
);
ipcMain
.
handle
(
"reset-all"
,
async
()
=>
{
ipcMain
.
handle
(
"reset-all"
,
async
()
:
Promise
<
void
>
=>
{
logger
.
log
(
"start: resetting all apps and settings."
);
// Stop all running apps first
logger
.
log
(
"stopping all running apps..."
);
...
...
@@ -722,10 +734,9 @@ export function registerAppHandlers() {
}
logger
.
log
(
"all app files removed."
);
logger
.
log
(
"reset all complete."
);
return
{
success
:
true
,
message
:
"Successfully reset everything"
};
});
ipcMain
.
handle
(
"get-app-version"
,
async
()
=>
{
ipcMain
.
handle
(
"get-app-version"
,
async
()
:
Promise
<
{
version
:
string
}
>
=>
{
// Read version from package.json at project root
const
packageJsonPath
=
path
.
resolve
(
__dirname
,
".."
,
".."
,
"package.json"
);
const
packageJson
=
JSON
.
parse
(
fs
.
readFileSync
(
packageJsonPath
,
"utf-8"
));
...
...
src/ipc/handlers/chat_handlers.ts
浏览文件 @
c71638a5
...
...
@@ -5,14 +5,16 @@ import { desc, eq } from "drizzle-orm";
import
type
{
ChatSummary
}
from
"../../lib/schemas"
;
import
*
as
git
from
"isomorphic-git"
;
import
*
as
fs
from
"fs"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
import
log
from
"electron-log"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
const
logger
=
log
.
scope
(
"chat_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerChatHandlers
()
{
ipcMain
.
handle
(
"create-chat"
,
async
(
_
,
appId
:
number
)
=>
{
handle
(
"create-chat"
,
async
(
_
,
appId
:
number
):
Promise
<
number
>
=>
{
// Get the app's path first
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
...
...
@@ -74,54 +76,38 @@ export function registerChatHandlers() {
return
chat
;
});
ipcMain
.
handle
(
"get-chats"
,
async
(
_
,
appId
?:
number
):
Promise
<
ChatSummary
[]
>
=>
{
// If appId is provided, filter chats for that app
const
query
=
appId
?
db
.
query
.
chats
.
findMany
({
where
:
eq
(
chats
.
appId
,
appId
),
columns
:
{
id
:
true
,
title
:
true
,
createdAt
:
true
,
appId
:
true
,
},
orderBy
:
[
desc
(
chats
.
createdAt
)],
})
:
db
.
query
.
chats
.
findMany
({
columns
:
{
id
:
true
,
title
:
true
,
createdAt
:
true
,
appId
:
true
,
},
orderBy
:
[
desc
(
chats
.
createdAt
)],
});
handle
(
"get-chats"
,
async
(
_
,
appId
?:
number
):
Promise
<
ChatSummary
[]
>
=>
{
// If appId is provided, filter chats for that app
const
query
=
appId
?
db
.
query
.
chats
.
findMany
({
where
:
eq
(
chats
.
appId
,
appId
),
columns
:
{
id
:
true
,
title
:
true
,
createdAt
:
true
,
appId
:
true
,
},
orderBy
:
[
desc
(
chats
.
createdAt
)],
})
:
db
.
query
.
chats
.
findMany
({
columns
:
{
id
:
true
,
title
:
true
,
createdAt
:
true
,
appId
:
true
,
},
orderBy
:
[
desc
(
chats
.
createdAt
)],
});
const
allChats
=
await
query
;
return
allChats
;
},
);
const
allChats
=
await
query
;
return
allChats
;
});
ipcMain
.
handle
(
"delete-chat"
,
async
(
_
,
chatId
:
number
)
=>
{
try
{
// Delete the chat and its associated messages
await
db
.
delete
(
chats
).
where
(
eq
(
chats
.
id
,
chatId
));
return
{
success
:
true
};
}
catch
(
error
)
{
logger
.
error
(
"Error deleting chat:"
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
}
handle
(
"delete-chat"
,
async
(
_
,
chatId
:
number
):
Promise
<
void
>
=>
{
await
db
.
delete
(
chats
).
where
(
eq
(
chats
.
id
,
chatId
));
});
ipcMain
.
handle
(
"delete-messages"
,
async
(
_
,
chatId
:
number
)
=>
{
try
{
await
db
.
delete
(
messages
).
where
(
eq
(
messages
.
chatId
,
chatId
));
return
{
success
:
true
};
}
catch
(
error
)
{
logger
.
error
(
"Error deleting messages:"
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
}
handle
(
"delete-messages"
,
async
(
_
,
chatId
:
number
):
Promise
<
void
>
=>
{
await
db
.
delete
(
messages
).
where
(
eq
(
messages
.
chatId
,
chatId
));
});
}
src/ipc/handlers/dependency_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
{
db
}
from
"../../db"
;
import
{
messages
,
apps
,
chats
}
from
"../../db/schema"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
executeAddDependency
}
from
"../processors/executeAddDependency"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
import
log
from
"electron-log"
;
const
logger
=
log
.
scope
(
"dependency_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerDependencyHandlers
()
{
ipcMain
.
handle
(
handle
(
"chat:add-dep"
,
async
(
_event
,
{
chatId
,
packages
}:
{
chatId
:
number
;
packages
:
string
[]
},
)
=>
{
)
:
Promise
<
void
>
=>
{
// Find the message from the database
const
foundMessages
=
await
db
.
query
.
messages
.
findMany
({
where
:
eq
(
messages
.
chatId
,
chatId
),
...
...
src/ipc/handlers/github_handlers.ts
浏览文件 @
c71638a5
...
...
@@ -285,7 +285,7 @@ function handleStartGithubFlow(
async
function
handleIsRepoAvailable
(
event
:
IpcMainInvokeEvent
,
{
org
,
repo
}:
{
org
:
string
;
repo
:
string
},
)
{
)
:
Promise
<
{
available
:
boolean
;
error
?:
string
}
>
{
try
{
// Get access token from settings
const
settings
=
readSettings
();
...
...
@@ -323,49 +323,44 @@ async function handleIsRepoAvailable(
async
function
handleCreateRepo
(
event
:
IpcMainInvokeEvent
,
{
org
,
repo
,
appId
}:
{
org
:
string
;
repo
:
string
;
appId
:
number
},
)
{
try
{
// Get access token from settings
const
settings
=
readSettings
();
const
accessToken
=
settings
.
githubAccessToken
?.
value
;
if
(
!
accessToken
)
{
return
{
success
:
false
,
error
:
"Not authenticated with GitHub."
};
}
// If org is empty, create for the authenticated user
let
owner
=
org
;
if
(
!
owner
)
{
const
userRes
=
await
fetch
(
"https://api.github.com/user"
,
{
headers
:
{
Authorization
:
`Bearer
${
accessToken
}
`
},
});
const
user
=
await
userRes
.
json
();
owner
=
user
.
login
;
}
// Create repo
const
createUrl
=
org
?
`https://api.github.com/orgs/
${
owner
}
/repos`
:
`https://api.github.com/user/repos`
;
const
res
=
await
fetch
(
createUrl
,
{
method
:
"POST"
,
headers
:
{
Authorization
:
`Bearer
${
accessToken
}
`
,
"Content-Type"
:
"application/json"
,
Accept
:
"application/vnd.github+json"
,
},
body
:
JSON
.
stringify
({
name
:
repo
,
private
:
true
,
}),
):
Promise
<
void
>
{
// Get access token from settings
const
settings
=
readSettings
();
const
accessToken
=
settings
.
githubAccessToken
?.
value
;
if
(
!
accessToken
)
{
throw
new
Error
(
"Not authenticated with GitHub."
);
}
// If org is empty, create for the authenticated user
let
owner
=
org
;
if
(
!
owner
)
{
const
userRes
=
await
fetch
(
"https://api.github.com/user"
,
{
headers
:
{
Authorization
:
`Bearer
${
accessToken
}
`
},
});
if
(
!
res
.
ok
)
{
const
data
=
await
res
.
json
();
return
{
success
:
false
,
error
:
data
.
message
||
"Failed to create repo"
};
}
// Store org and repo in the app's DB row (apps table)
await
updateAppGithubRepo
(
appId
,
owner
,
repo
);
return
{
success
:
true
};
}
catch
(
err
:
any
)
{
return
{
success
:
false
,
error
:
err
.
message
||
"Unknown error"
};
const
user
=
await
userRes
.
json
();
owner
=
user
.
login
;
}
// Create repo
const
createUrl
=
org
?
`https://api.github.com/orgs/
${
owner
}
/repos`
:
`https://api.github.com/user/repos`
;
const
res
=
await
fetch
(
createUrl
,
{
method
:
"POST"
,
headers
:
{
Authorization
:
`Bearer
${
accessToken
}
`
,
"Content-Type"
:
"application/json"
,
Accept
:
"application/vnd.github+json"
,
},
body
:
JSON
.
stringify
({
name
:
repo
,
private
:
true
,
}),
});
if
(
!
res
.
ok
)
{
const
data
=
await
res
.
json
();
throw
new
Error
(
data
.
message
||
"Failed to create repo"
);
}
// Store org and repo in the app's DB row (apps table)
await
updateAppGithubRepo
(
appId
,
owner
,
repo
);
}
// --- GitHub Push Handler ---
...
...
@@ -420,36 +415,26 @@ async function handlePushToGithub(
async
function
handleDisconnectGithubRepo
(
event
:
IpcMainInvokeEvent
,
{
appId
}:
{
appId
:
number
},
)
{
try
{
logger
.
log
(
`Disconnecting GitHub repo for appId:
${
appId
}
`
);
// Get the app from the database
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
if
(
!
app
)
{
return
{
success
:
false
,
error
:
"App not found"
};
}
):
Promise
<
void
>
{
logger
.
log
(
`Disconnecting GitHub repo for appId:
${
appId
}
`
);
// Update app in database to remove GitHub repo and org
await
db
.
update
(
apps
)
.
set
({
githubRepo
:
null
,
githubOrg
:
null
,
})
.
where
(
eq
(
apps
.
id
,
appId
));
// Get the app from the database
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
return
{
success
:
true
};
}
catch
(
error
)
{
logger
.
error
(
`Error disconnecting GitHub repo:
${
error
}
`
);
return
{
success
:
false
,
error
:
error
instanceof
Error
?
error
.
message
:
String
(
error
),
};
if
(
!
app
)
{
throw
new
Error
(
"App not found"
);
}
// Update app in database to remove GitHub repo and org
await
db
.
update
(
apps
)
.
set
({
githubRepo
:
null
,
githubOrg
:
null
,
})
.
where
(
eq
(
apps
.
id
,
appId
));
}
// --- Registration ---
...
...
src/ipc/handlers/local_model_lmstudio_handler.ts
浏览文件 @
c71638a5
...
...
@@ -18,28 +18,24 @@ export interface LMStudioModel {
}
export
async
function
fetchLMStudioModels
():
Promise
<
LocalModelListResponse
>
{
try
{
const
modelsResponse
:
Response
=
await
fetch
(
"http://localhost:1234/api/v0/models"
,
);
if
(
!
modelsResponse
.
ok
)
{
throw
new
Error
(
"Failed to fetch models from LM Studio"
);
}
const
modelsJson
=
await
modelsResponse
.
json
();
const
downloadedModels
=
modelsJson
.
data
as
LMStudioModel
[];
const
models
:
LocalModel
[]
=
downloadedModels
.
filter
((
model
:
any
)
=>
model
.
type
===
"llm"
)
.
map
((
model
:
any
)
=>
({
modelName
:
model
.
id
,
displayName
:
model
.
id
,
provider
:
"lmstudio"
,
}));
logger
.
info
(
`Successfully fetched
${
models
.
length
}
models from LM Studio`
);
return
{
models
,
error
:
null
};
}
catch
{
return
{
models
:
[],
error
:
"Failed to fetch models from LM Studio"
};
const
modelsResponse
:
Response
=
await
fetch
(
"http://localhost:1234/api/v0/models"
,
);
if
(
!
modelsResponse
.
ok
)
{
throw
new
Error
(
"Failed to fetch models from LM Studio"
);
}
const
modelsJson
=
await
modelsResponse
.
json
();
const
downloadedModels
=
modelsJson
.
data
as
LMStudioModel
[];
const
models
:
LocalModel
[]
=
downloadedModels
.
filter
((
model
:
any
)
=>
model
.
type
===
"llm"
)
.
map
((
model
:
any
)
=>
({
modelName
:
model
.
id
,
displayName
:
model
.
id
,
provider
:
"lmstudio"
,
}));
logger
.
info
(
`Successfully fetched
${
models
.
length
}
models from LM Studio`
);
return
{
models
};
}
export
function
registerLMStudioHandlers
()
{
...
...
src/ipc/handlers/local_model_ollama_handler.ts
浏览文件 @
c71638a5
...
...
@@ -47,20 +47,17 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
};
});
logger
.
info
(
`Successfully fetched
${
models
.
length
}
models from Ollama`
);
return
{
models
,
error
:
null
};
return
{
models
};
}
catch
(
error
)
{
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"
,
};
throw
new
Error
(
"Could not connect to Ollama. Make sure it's running at http://localhost:11434"
,
);
}
return
{
models
:
[],
error
:
"Failed to fetch models from Ollama"
}
;
throw
new
Error
(
"Failed to fetch models from Ollama"
)
;
}
}
...
...
src/ipc/handlers/proposal_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
,
type
IpcMainInvokeEvent
}
from
"electron"
;
import
{
type
IpcMainInvokeEvent
}
from
"electron"
;
import
type
{
CodeProposal
,
ProposalResult
,
...
...
@@ -29,9 +29,9 @@ import {
import
{
extractCodebase
}
from
"../../utils/codebase"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
withLock
}
from
"../utils/lock_utils"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"proposal_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
// Cache for codebase token counts
interface
CodebaseTokenCache
{
chatId
:
number
;
...
...
@@ -317,115 +317,83 @@ const approveProposalHandler = async (
_event
:
IpcMainInvokeEvent
,
{
chatId
,
messageId
}:
{
chatId
:
number
;
messageId
:
number
},
):
Promise
<
{
success
:
boolean
;
error
?:
string
;
uncommittedFiles
?:
string
[];
}
>
=>
{
logger
.
log
(
`IPC: approve-proposal called for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
try
{
// 1. Fetch the specific assistant message
const
messageToApprove
=
await
db
.
query
.
messages
.
findFirst
({
where
:
and
(
eq
(
messages
.
id
,
messageId
),
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
role
,
"assistant"
),
),
columns
:
{
content
:
true
,
},
});
if
(
!
messageToApprove
?.
content
)
{
logger
.
error
(
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
return
{
success
:
false
,
error
:
"Assistant message not found."
};
}
// 1. Fetch the specific assistant message
const
messageToApprove
=
await
db
.
query
.
messages
.
findFirst
({
where
:
and
(
eq
(
messages
.
id
,
messageId
),
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
role
,
"assistant"
),
),
columns
:
{
content
:
true
,
},
});
// 2. Process the actions defined in the message content
const
chatSummary
=
getDyadChatSummaryTag
(
messageToApprove
.
content
);
const
processResult
=
await
processFullResponseActions
(
messageToApprove
.
content
,
chatId
,
{
chatSummary
:
chatSummary
??
undefined
,
messageId
,
},
// Pass summary if found
if
(
!
messageToApprove
?.
content
)
{
throw
new
Error
(
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
}
if
(
processResult
.
error
)
{
logger
.
error
(
`Error processing actions for message
${
messageId
}
:`
,
processResult
.
error
,
);
// Optionally: Update message state to 'error' or similar?
// For now, just return error to frontend
return
{
success
:
false
,
error
:
`Action processing failed:
${
processResult
.
error
}
`
,
};
}
// 2. Process the actions defined in the message content
const
chatSummary
=
getDyadChatSummaryTag
(
messageToApprove
.
content
);
const
processResult
=
await
processFullResponseActions
(
messageToApprove
.
content
,
chatId
,
{
chatSummary
:
chatSummary
??
undefined
,
messageId
,
},
// Pass summary if found
);
return
{
success
:
true
,
uncommittedFiles
:
processResult
.
uncommittedFiles
};
}
catch
(
error
)
{
logger
.
error
(
`Error approving proposal for messageId
${
messageId
}
:`
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
)?.
message
||
"Unknown error"
,
};
if
(
processResult
.
error
)
{
throw
new
Error
(
`Error processing actions for message
${
messageId
}
:
${
processResult
.
error
}
`
,
);
}
return
{
uncommittedFiles
:
processResult
.
uncommittedFiles
};
};
// Handler to reject a proposal (just update message state)
const
rejectProposalHandler
=
async
(
_event
:
IpcMainInvokeEvent
,
{
chatId
,
messageId
}:
{
chatId
:
number
;
messageId
:
number
},
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
=>
{
):
Promise
<
void
>
=>
{
logger
.
log
(
`IPC: reject-proposal called for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
try
{
// 1. Verify the message exists and is an assistant message
const
messageToReject
=
await
db
.
query
.
messages
.
findFirst
({
where
:
and
(
eq
(
messages
.
id
,
messageId
),
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
role
,
"assistant"
),
),
columns
:
{
id
:
true
},
});
if
(
!
messageToReject
)
{
logger
.
error
(
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
return
{
success
:
false
,
error
:
"Assistant message not found."
};
}
// 1. Verify the message exists and is an assistant message
const
messageToReject
=
await
db
.
query
.
messages
.
findFirst
({
where
:
and
(
eq
(
messages
.
id
,
messageId
),
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
role
,
"assistant"
),
),
columns
:
{
id
:
true
},
});
// 2. Update the message's approval state to 'rejected'
await
db
.
update
(
messages
)
.
set
({
approvalState
:
"rejected"
})
.
where
(
eq
(
messages
.
id
,
messageId
));
logger
.
log
(
`Message
${
messageId
}
marked as rejected.`
);
return
{
success
:
true
};
}
catch
(
error
)
{
logger
.
error
(
`Error rejecting proposal for messageId
${
messageId
}
:`
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
)?.
message
||
"Unknown error"
,
};
if
(
!
messageToReject
)
{
throw
new
Error
(
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
}
// 2. Update the message's approval state to 'rejected'
await
db
.
update
(
messages
)
.
set
({
approvalState
:
"rejected"
})
.
where
(
eq
(
messages
.
id
,
messageId
));
logger
.
log
(
`Message
${
messageId
}
marked as rejected.`
);
};
// Function to register proposal-related handlers
export
function
registerProposalHandlers
()
{
ipcMain
.
handle
(
"get-proposal"
,
getProposalHandler
);
ipcMain
.
handle
(
"approve-proposal"
,
approveProposalHandler
);
ipcMain
.
handle
(
"reject-proposal"
,
rejectProposalHandler
);
handle
(
"get-proposal"
,
getProposalHandler
);
handle
(
"approve-proposal"
,
approveProposalHandler
);
handle
(
"reject-proposal"
,
rejectProposalHandler
);
}
src/ipc/handlers/safe_handle.ts
浏览文件 @
c71638a5
import
{
ipcMain
,
IpcMainInvokeEvent
}
from
"electron"
;
import
log
from
"electron-log"
;
export
function
create
Safe
Handler
(
logger
:
log
.
LogFunctions
)
{
export
function
create
Logged
Handler
(
logger
:
log
.
LogFunctions
)
{
return
(
channel
:
string
,
fn
:
(
event
:
IpcMainInvokeEvent
,
...
args
:
any
[])
=>
Promise
<
any
>
,
...
...
@@ -9,8 +9,11 @@ export function createSafeHandler(logger: log.LogFunctions) {
ipcMain
.
handle
(
channel
,
async
(
event
:
IpcMainInvokeEvent
,
...
args
:
any
[])
=>
{
logger
.
log
(
`IPC:
${
channel
}
called with args:
${
JSON
.
stringify
(
args
)}
`
);
try
{
return
await
fn
(
event
,
...
args
);
const
result
=
await
fn
(
event
,
...
args
);
logger
.
log
(
`IPC:
${
channel
}
returned:
${
JSON
.
stringify
(
result
)}
`
);
return
result
;
}
catch
(
error
)
{
logger
.
error
(
`Error in
${
fn
.
name
}
: args:
${
JSON
.
stringify
(
args
)}
`
,
...
...
src/ipc/handlers/settings_handlers.ts
浏览文件 @
c71638a5
...
...
@@ -4,11 +4,13 @@ import { writeSettings } from "../../main/settings";
import
{
readSettings
}
from
"../../main/settings"
;
export
function
registerSettingsHandlers
()
{
// Intentionally do NOT use handle because it could log sensitive data from the return value.
ipcMain
.
handle
(
"get-user-settings"
,
async
()
=>
{
const
settings
=
readSettings
();
return
settings
;
});
// Intentionally do NOT use handle because it could log sensitive data from the args.
ipcMain
.
handle
(
"set-user-settings"
,
async
(
_
,
settings
:
Partial
<
UserSettings
>
)
=>
{
...
...
src/ipc/handlers/shell_handler.ts
浏览文件 @
c71638a5
import
{
ipcMain
,
shell
}
from
"electron"
;
import
{
shell
}
from
"electron"
;
import
log
from
"electron-log"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"shell_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerShellHandlers
()
{
ipcMain
.
handle
(
"open-external-url"
,
async
(
_event
,
url
:
string
)
=>
{
try
{
// Basic validation to ensure it's a http/https url
if
(
url
&&
(
url
.
startsWith
(
"http://"
)
||
url
.
startsWith
(
"https://"
)))
{
await
shell
.
openExternal
(
url
);
logger
.
debug
(
"Opened external URL:"
,
url
);
return
{
success
:
true
};
}
logger
.
error
(
"Attempted to open invalid or non-http URL:"
,
url
);
return
{
success
:
false
,
error
:
"Invalid URL provided. Only http/https URLs are allowed."
,
};
}
catch
(
error
)
{
logger
.
error
(
`Failed to open external URL
${
url
}
:`
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
handle
(
"open-external-url"
,
async
(
_event
,
url
:
string
)
=>
{
if
(
!
url
)
{
throw
new
Error
(
"No URL provided."
);
}
if
(
!
url
.
startsWith
(
"http://"
)
&&
!
url
.
startsWith
(
"https://"
))
{
throw
new
Error
(
"Attempted to open invalid or non-http URL: "
+
url
);
}
await
shell
.
openExternal
(
url
);
logger
.
debug
(
"Opened external URL:"
,
url
);
});
ipcMain
.
handle
(
"show-item-in-folder"
,
async
(
_event
,
fullPath
:
string
)
=>
{
try
{
// Validate that a path was provided
if
(
!
fullPath
)
{
logger
.
error
(
"Attempted to show item with empty path"
);
return
{
success
:
false
,
error
:
"No file path provided."
,
};
}
shell
.
showItemInFolder
(
fullPath
);
logger
.
debug
(
"Showed item in folder:"
,
fullPath
);
return
{
success
:
true
};
}
catch
(
error
)
{
logger
.
error
(
`Failed to show item in folder
${
fullPath
}
:`
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
handle
(
"show-item-in-folder"
,
async
(
_event
,
fullPath
:
string
)
=>
{
// Validate that a path was provided
if
(
!
fullPath
)
{
throw
new
Error
(
"No file path provided."
);
}
shell
.
showItemInFolder
(
fullPath
);
logger
.
debug
(
"Showed item in folder:"
,
fullPath
);
});
}
src/ipc/handlers/supabase_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
log
from
"electron-log"
;
import
{
db
}
from
"../../db"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
apps
}
from
"../../db/schema"
;
import
{
getSupabaseClient
}
from
"../../supabase_admin/supabase_management_client"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"supabase_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerSupabaseHandlers
()
{
// List all Supabase projects
ipcMain
.
handle
(
"supabase:list-projects"
,
async
()
=>
{
try
{
const
supabase
=
await
getSupabaseClient
();
// Call the API according to supabase-management-js structure
const
projects
=
await
supabase
.
getProjects
();
return
projects
;
}
catch
(
error
)
{
logger
.
error
(
"Error listing Supabase projects:"
,
error
);
throw
error
;
}
handle
(
"supabase:list-projects"
,
async
()
=>
{
const
supabase
=
await
getSupabaseClient
();
return
supabase
.
getProjects
();
});
// Set app project - links a Dyad app to a Supabase project
ipcMain
.
handle
(
handle
(
"supabase:set-app-project"
,
async
(
_
,
{
project
,
app
}:
{
project
:
string
;
app
:
number
})
=>
{
try
{
// Here you could store the project-app association in your database
// For example:
await
db
.
update
(
apps
)
.
set
({
supabaseProjectId
:
project
})
.
where
(
eq
(
apps
.
id
,
app
));
await
db
.
update
(
apps
)
.
set
({
supabaseProjectId
:
project
})
.
where
(
eq
(
apps
.
id
,
app
));
logger
.
info
(
`Associated app
${
app
}
with Supabase project
${
project
}
`
);
return
{
success
:
true
,
appId
:
app
,
projectId
:
project
};
}
catch
(
error
)
{
logger
.
error
(
"Error setting Supabase project for app:"
,
error
);
throw
error
;
}
logger
.
info
(
`Associated app
${
app
}
with Supabase project
${
project
}
`
);
},
);
// Unset app project - removes the link between a Dyad app and a Supabase project
ipcMain
.
handle
(
"supabase:unset-app-project"
,
async
(
_
,
{
app
}:
{
app
:
number
})
=>
{
try
{
await
db
.
update
(
apps
)
.
set
({
supabaseProjectId
:
null
})
.
where
(
eq
(
apps
.
id
,
app
));
handle
(
"supabase:unset-app-project"
,
async
(
_
,
{
app
}:
{
app
:
number
})
=>
{
await
db
.
update
(
apps
)
.
set
({
supabaseProjectId
:
null
})
.
where
(
eq
(
apps
.
id
,
app
));
logger
.
info
(
`Removed Supabase project association for app
${
app
}
`
);
return
{
success
:
true
,
appId
:
app
};
}
catch
(
error
)
{
logger
.
error
(
"Error unsetting Supabase project for app:"
,
error
);
throw
error
;
}
},
);
logger
.
info
(
`Removed Supabase project association for app
${
app
}
`
);
});
}
src/ipc/handlers/token_count_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
{
db
}
from
"../../db"
;
import
{
chats
}
from
"../../db/schema"
;
import
{
eq
}
from
"drizzle-orm"
;
...
...
@@ -15,91 +14,82 @@ import { getSupabaseContext } from "../../supabase_admin/supabase_context";
import
{
TokenCountParams
}
from
"../ipc_types"
;
import
{
TokenCountResult
}
from
"../ipc_types"
;
import
{
estimateTokens
,
getContextWindow
}
from
"../utils/token_utils"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"token_count_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerTokenCountHandlers
()
{
ipcMain
.
handle
(
handle
(
"chat:count-tokens"
,
async
(
event
,
req
:
TokenCountParams
):
Promise
<
TokenCountResult
>
=>
{
try
{
// Get the chat with messages
const
chat
=
await
db
.
query
.
chats
.
findFirst
({
where
:
eq
(
chats
.
id
,
req
.
chatId
),
with
:
{
messages
:
{
orderBy
:
(
messages
,
{
asc
})
=>
[
asc
(
messages
.
createdAt
)],
},
app
:
true
,
const
chat
=
await
db
.
query
.
chats
.
findFirst
({
where
:
eq
(
chats
.
id
,
req
.
chatId
),
with
:
{
messages
:
{
orderBy
:
(
messages
,
{
asc
})
=>
[
asc
(
messages
.
createdAt
)],
},
});
app
:
true
,
},
});
if
(
!
chat
)
{
throw
new
Error
(
`Chat not found:
${
req
.
chatId
}
`
);
}
if
(
!
chat
)
{
throw
new
Error
(
`Chat not found:
${
req
.
chatId
}
`
);
}
// Prepare message history for token counting
const
messageHistory
=
chat
.
messages
.
map
((
message
)
=>
message
.
content
)
.
join
(
""
);
const
messageHistoryTokens
=
estimateTokens
(
messageHistory
);
// Prepare message history for token counting
const
messageHistory
=
chat
.
messages
.
map
((
message
)
=>
message
.
content
)
.
join
(
""
);
const
messageHistoryTokens
=
estimateTokens
(
messageHistory
);
// Count input tokens
const
inputTokens
=
estimateTokens
(
req
.
input
);
// Count input tokens
const
inputTokens
=
estimateTokens
(
req
.
input
);
// Count system prompt tokens
let
systemPrompt
=
SYSTEM_PROMPT
;
let
supabaseContext
=
""
;
// Count system prompt tokens
let
systemPrompt
=
SYSTEM_PROMPT
;
let
supabaseContext
=
""
;
if
(
chat
.
app
?.
supabaseProjectId
)
{
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_AVAILABLE_SYSTEM_PROMPT
;
supabaseContext
=
await
getSupabaseContext
({
supabaseProjectId
:
chat
.
app
.
supabaseProjectId
,
});
}
else
{
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT
;
}
if
(
chat
.
app
?.
supabaseProjectId
)
{
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_AVAILABLE_SYSTEM_PROMPT
;
supabaseContext
=
await
getSupabaseContext
({
supabaseProjectId
:
chat
.
app
.
supabaseProjectId
,
});
}
else
{
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT
;
}
const
systemPromptTokens
=
estimateTokens
(
systemPrompt
+
supabaseContext
,
);
const
systemPromptTokens
=
estimateTokens
(
systemPrompt
+
supabaseContext
);
// Extract codebase information if app is associated with the chat
let
codebaseInfo
=
""
;
let
codebaseTokens
=
0
;
// Extract codebase information if app is associated with the chat
let
codebaseInfo
=
""
;
let
codebaseTokens
=
0
;
if
(
chat
.
app
)
{
const
appPath
=
getDyadAppPath
(
chat
.
app
.
path
);
try
{
codebaseInfo
=
await
extractCodebase
(
appPath
);
codebaseTokens
=
estimateTokens
(
codebaseInfo
);
logger
.
log
(
`Extracted codebase information from
${
appPath
}
, tokens:
${
codebaseTokens
}
`
,
);
}
catch
(
error
)
{
logger
.
error
(
"Error extracting codebase:"
,
error
);
}
}
if
(
chat
.
app
)
{
const
appPath
=
getDyadAppPath
(
chat
.
app
.
path
);
codebaseInfo
=
await
extractCodebase
(
appPath
);
codebaseTokens
=
estimateTokens
(
codebaseInfo
);
logger
.
log
(
`Extracted codebase information from
${
appPath
}
, tokens:
${
codebaseTokens
}
`
,
);
}
// Calculate total tokens
const
totalTokens
=
messageHistoryTokens
+
inputTokens
+
systemPromptTokens
+
codebaseTokens
;
// Calculate total tokens
const
totalTokens
=
messageHistoryTokens
+
inputTokens
+
systemPromptTokens
+
codebaseTokens
;
return
{
totalTokens
,
messageHistoryTokens
,
codebaseTokens
,
inputTokens
,
systemPromptTokens
,
contextWindow
:
getContextWindow
(),
};
}
catch
(
error
)
{
logger
.
error
(
"Error counting tokens:"
,
error
);
throw
error
;
}
return
{
totalTokens
,
messageHistoryTokens
,
codebaseTokens
,
inputTokens
,
systemPromptTokens
,
contextWindow
:
getContextWindow
(),
};
},
);
}
src/ipc/handlers/upload_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
log
from
"electron-log"
;
import
fetch
from
"node-fetch"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"upload_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
interface
UploadToSignedUrlParams
{
url
:
string
;
contentType
:
string
;
...
...
@@ -11,49 +13,37 @@ interface UploadToSignedUrlParams {
}
export
function
registerUploadHandlers
()
{
ipcMain
.
handle
(
"upload-to-signed-url"
,
async
(
_
,
params
:
UploadToSignedUrlParams
)
=>
{
const
{
url
,
contentType
,
data
}
=
params
;
logger
.
debug
(
"IPC: upload-to-signed-url called"
);
try
{
// Validate the signed URL
if
(
!
url
||
typeof
url
!==
"string"
||
!
url
.
startsWith
(
"https://"
))
{
throw
new
Error
(
"Invalid signed URL provided"
);
}
// Validate content type
if
(
!
contentType
||
typeof
contentType
!==
"string"
)
{
throw
new
Error
(
"Invalid content type provided"
);
}
// Perform the upload to the signed URL
const
response
=
await
fetch
(
url
,
{
method
:
"PUT"
,
headers
:
{
"Content-Type"
:
contentType
,
},
body
:
JSON
.
stringify
(
data
),
});
if
(
!
response
.
ok
)
{
throw
new
Error
(
`Upload failed with status
${
response
.
status
}
:
${
response
.
statusText
}
`
,
);
}
logger
.
debug
(
"Successfully uploaded data to signed URL"
);
return
{
success
:
true
};
}
catch
(
error
)
{
logger
.
error
(
"Failed to upload to signed URL:"
,
error
);
return
{
success
:
false
,
error
:
error
instanceof
Error
?
error
.
message
:
String
(
error
),
};
}
},
);
handle
(
"upload-to-signed-url"
,
async
(
_
,
params
:
UploadToSignedUrlParams
)
=>
{
const
{
url
,
contentType
,
data
}
=
params
;
logger
.
debug
(
"IPC: upload-to-signed-url called"
);
// Validate the signed URL
if
(
!
url
||
typeof
url
!==
"string"
||
!
url
.
startsWith
(
"https://"
))
{
throw
new
Error
(
"Invalid signed URL provided"
);
}
// Validate content type
if
(
!
contentType
||
typeof
contentType
!==
"string"
)
{
throw
new
Error
(
"Invalid content type provided"
);
}
// Perform the upload to the signed URL
const
response
=
await
fetch
(
url
,
{
method
:
"PUT"
,
headers
:
{
"Content-Type"
:
contentType
,
},
body
:
JSON
.
stringify
(
data
),
});
if
(
!
response
.
ok
)
{
throw
new
Error
(
`Upload failed with status
${
response
.
status
}
:
${
response
.
statusText
}
`
,
);
}
logger
.
debug
(
"Successfully uploaded data to signed URL"
);
});
logger
.
debug
(
"Registered upload IPC handlers"
);
}
src/ipc/handlers/version_handlers.ts
浏览文件 @
c71638a5
...
...
@@ -10,11 +10,11 @@ import { promises as fsPromises } from "node:fs";
import
{
withLock
}
from
"../utils/lock_utils"
;
import
{
getGitAuthor
}
from
"../utils/git_author"
;
import
log
from
"electron-log"
;
import
{
create
Safe
Handler
}
from
"./safe_handle"
;
import
{
create
Logged
Handler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"version_handlers"
);
const
handle
=
create
Safe
Handler
(
logger
);
const
handle
=
create
Logged
Handler
(
logger
);
export
function
registerVersionHandlers
()
{
handle
(
"list-versions"
,
async
(
_
,
{
appId
}:
{
appId
:
number
})
=>
{
...
...
src/ipc/ipc_client.ts
浏览文件 @
c71638a5
...
...
@@ -150,23 +150,11 @@ export class IpcClient {
// Create a new app with an initial chat
public
async
createApp
(
params
:
CreateAppParams
):
Promise
<
CreateAppResult
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"create-app"
,
params
);
return
result
as
CreateAppResult
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"create-app"
,
params
);
}
public
async
getApp
(
appId
:
number
):
Promise
<
App
>
{
try
{
const
data
=
await
this
.
ipcRenderer
.
invoke
(
"get-app"
,
appId
);
return
data
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"get-app"
,
appId
);
}
public
async
getChat
(
chatId
:
number
):
Promise
<
Chat
>
{
...
...
@@ -192,28 +180,14 @@ export class IpcClient {
// Get all apps
public
async
listApps
():
Promise
<
ListAppsResponse
>
{
try
{
const
data
=
await
this
.
ipcRenderer
.
invoke
(
"list-apps"
);
return
data
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"list-apps"
);
}
// Read a file from an app directory
public
async
readAppFile
(
appId
:
number
,
filePath
:
string
):
Promise
<
string
>
{
try
{
const
content
=
await
this
.
ipcRenderer
.
invoke
(
"read-app-file"
,
{
appId
,
filePath
,
});
return
content
as
string
;
}
catch
(
error
)
{
// No toast because sometimes the file will disappear.
console
.
error
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"read-app-file"
,
{
appId
,
filePath
,
});
}
// Edit a file in an app directory
...
...
@@ -221,18 +195,12 @@ export class IpcClient {
appId
:
number
,
filePath
:
string
,
content
:
string
,
):
Promise
<
{
success
:
boolean
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"edit-app-file"
,
{
appId
,
filePath
,
content
,
});
return
result
as
{
success
:
boolean
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"edit-app-file"
,
{
appId
,
filePath
,
content
,
});
}
// New method for streaming responses
...
...
@@ -321,91 +289,38 @@ export class IpcClient {
// Create a new chat for an app
public
async
createChat
(
appId
:
number
):
Promise
<
number
>
{
try
{
const
chatId
=
await
this
.
ipcRenderer
.
invoke
(
"create-chat"
,
appId
);
return
chatId
as
number
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"create-chat"
,
appId
);
}
public
async
deleteChat
(
chatId
:
number
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"delete-chat"
,
chatId
);
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
deleteChat
(
chatId
:
number
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"delete-chat"
,
chatId
);
}
public
async
deleteMessages
(
chatId
:
number
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"delete-messages"
,
chatId
);
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
deleteMessages
(
chatId
:
number
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"delete-messages"
,
chatId
);
}
// Open an external URL using the default browser
public
async
openExternalUrl
(
url
:
string
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"open-external-url"
,
url
);
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
openExternalUrl
(
url
:
string
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"open-external-url"
,
url
);
}
public
async
showItemInFolder
(
fullPath
:
string
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"show-item-in-folder"
,
fullPath
,
);
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
showItemInFolder
(
fullPath
:
string
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"show-item-in-folder"
,
fullPath
);
}
// Run an app
public
async
runApp
(
appId
:
number
,
onOutput
:
(
output
:
AppOutput
)
=>
void
,
):
Promise
<
{
success
:
boolean
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"run-app"
,
{
appId
});
this
.
appStreams
.
set
(
appId
,
{
onOutput
});
return
result
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"run-app"
,
{
appId
});
this
.
appStreams
.
set
(
appId
,
{
onOutput
});
}
// Stop a running app
public
async
stopApp
(
appId
:
number
):
Promise
<
{
success
:
boolean
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"stop-app"
,
{
appId
});
return
result
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
stopApp
(
appId
:
number
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"stop-app"
,
{
appId
});
}
// Restart a running app
...
...
@@ -514,14 +429,8 @@ export class IpcClient {
}
// Delete an app and all its files
public
async
deleteApp
(
appId
:
number
):
Promise
<
{
success
:
boolean
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"delete-app"
,
{
appId
});
return
result
as
{
success
:
boolean
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
deleteApp
(
appId
:
number
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"delete-app"
,
{
appId
});
}
// Rename an app (update name and path)
...
...
@@ -533,29 +442,17 @@ export class IpcClient {
appId
:
number
;
appName
:
string
;
appPath
:
string
;
}):
Promise
<
{
success
:
boolean
;
app
:
App
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"rename-app"
,
{
appId
,
appName
,
appPath
,
});
return
result
as
{
success
:
boolean
;
app
:
App
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
}):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"rename-app"
,
{
appId
,
appName
,
appPath
,
});
}
// Reset all - removes all app files, settings, and drops the database
public
async
resetAll
():
Promise
<
{
success
:
boolean
;
message
:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"reset-all"
);
return
result
as
{
success
:
boolean
;
message
:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
resetAll
():
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"reset-all"
);
}
public
async
addDependency
({
...
...
@@ -565,26 +462,15 @@ export class IpcClient {
chatId
:
number
;
packages
:
string
[];
}):
Promise
<
void
>
{
try
{
await
this
.
ipcRenderer
.
invoke
(
"chat:add-dep"
,
{
chatId
,
packages
,
});
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
await
this
.
ipcRenderer
.
invoke
(
"chat:add-dep"
,
{
chatId
,
packages
,
});
}
// Check Node.js and npm status
public
async
getNodejsStatus
():
Promise
<
NodeSystemInfo
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"nodejs-status"
);
return
result
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"nodejs-status"
);
}
// --- GitHub Device Flow ---
...
...
@@ -631,11 +517,6 @@ export class IpcClient {
this
.
ipcRenderer
.
removeListener
(
"github:flow-error"
,
listener
);
};
}
// TODO: Implement cancel method if needed
// public cancelGithubDeviceFlow(): void {
// this.ipcRenderer.sendMessage("github:cancel-flow");
// }
// --- End GitHub Device Flow ---
// --- GitHub Repo Management ---
...
...
@@ -643,32 +524,22 @@ export class IpcClient {
org
:
string
,
repo
:
string
,
):
Promise
<
{
available
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"github:is-repo-available"
,
{
org
,
repo
,
});
return
result
;
}
catch
(
error
:
any
)
{
return
{
available
:
false
,
error
:
error
.
message
||
"Unknown error"
};
}
return
this
.
ipcRenderer
.
invoke
(
"github:is-repo-available"
,
{
org
,
repo
,
});
}
public
async
createGithubRepo
(
org
:
string
,
repo
:
string
,
appId
:
number
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"github:create-repo"
,
{
org
,
repo
,
appId
,
});
return
result
;
}
catch
(
error
:
any
)
{
return
{
success
:
false
,
error
:
error
.
message
||
"Unknown error"
};
}
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"github:create-repo"
,
{
org
,
repo
,
appId
,
});
}
// Sync (push) local repo to GitHub
...
...
@@ -684,30 +555,17 @@ export class IpcClient {
}
}
public
async
disconnectGithubRepo
(
appId
:
number
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"github:disconnect"
,
{
appId
,
});
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
disconnectGithubRepo
(
appId
:
number
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"github:disconnect"
,
{
appId
,
});
}
// --- End GitHub Repo Management ---
// Get the main app version
public
async
getAppVersion
():
Promise
<
string
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"get-app-version"
);
return
result
.
version
as
string
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"get-app-version"
);
return
result
.
version
as
string
;
}
// Get proposal details
...
...
@@ -734,24 +592,12 @@ export class IpcClient {
chatId
:
number
;
messageId
:
number
;
}):
Promise
<
{
success
:
boolean
;
error
?:
string
;
uncommittedFiles
?:
string
[];
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"approve-proposal"
,
{
chatId
,
messageId
,
});
return
result
as
{
success
:
boolean
;
error
?:
string
;
uncommittedFiles
?:
string
[];
};
}
catch
(
error
)
{
showError
(
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
}
return
this
.
ipcRenderer
.
invoke
(
"approve-proposal"
,
{
chatId
,
messageId
,
});
}
public
async
rejectProposal
({
...
...
@@ -760,132 +606,66 @@ export class IpcClient {
}:
{
chatId
:
number
;
messageId
:
number
;
}):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"reject-proposal"
,
{
chatId
,
messageId
,
});
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
}
}):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"reject-proposal"
,
{
chatId
,
messageId
,
});
}
// --- End Proposal Management ---
// --- Supabase Management ---
public
async
listSupabaseProjects
():
Promise
<
any
[]
>
{
try
{
const
projects
=
await
this
.
ipcRenderer
.
invoke
(
"supabase:list-projects"
);
return
projects
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"supabase:list-projects"
);
}
public
async
setSupabaseAppProject
(
project
:
string
,
app
:
number
,
):
Promise
<
{
success
:
boolean
;
appId
:
number
;
projectId
:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"supabase:set-app-project"
,
{
project
,
app
,
});
return
result
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"supabase:set-app-project"
,
{
project
,
app
,
});
}
public
async
unsetSupabaseAppProject
(
app
:
number
,
):
Promise
<
{
success
:
boolean
;
appId
:
number
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"supabase:unset-app-project"
,
{
app
,
},
);
return
result
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
public
async
unsetSupabaseAppProject
(
app
:
number
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"supabase:unset-app-project"
,
{
app
,
});
}
// --- End Supabase Management ---
// Get system debug information
public
async
getSystemDebugInfo
():
Promise
<
SystemDebugInfo
>
{
try
{
const
data
=
await
this
.
ipcRenderer
.
invoke
(
"get-system-debug-info"
);
return
data
as
SystemDebugInfo
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"get-system-debug-info"
);
}
public
async
getChatLogs
(
chatId
:
number
):
Promise
<
ChatLogsData
>
{
try
{
const
data
=
await
this
.
ipcRenderer
.
invoke
(
"get-chat-logs"
,
chatId
);
return
data
as
ChatLogsData
;
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
return
this
.
ipcRenderer
.
invoke
(
"get-chat-logs"
,
chatId
);
}
public
async
uploadToSignedUrl
(
url
:
string
,
contentType
:
string
,
data
:
any
,
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
{
try
{
const
result
=
await
this
.
ipcRenderer
.
invoke
(
"upload-to-signed-url"
,
{
url
,
contentType
,
data
,
});
return
result
as
{
success
:
boolean
;
error
?:
string
};
}
catch
(
error
)
{
showError
(
error
);
throw
error
;
}
):
Promise
<
void
>
{
await
this
.
ipcRenderer
.
invoke
(
"upload-to-signed-url"
,
{
url
,
contentType
,
data
,
});
}
public
async
listLocalOllamaModels
():
Promise
<
LocalModel
[]
>
{
try
{
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-ollama"
,
);
return
response
?.
models
||
[];
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
throw
new
Error
(
`Failed to fetch Ollama models:
${
error
.
message
}
`
);
}
throw
new
Error
(
"Failed to fetch Ollama models: Unknown error occurred"
);
}
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-ollama"
);
return
response
?.
models
||
[];
}
public
async
listLocalLMStudioModels
():
Promise
<
LocalModel
[]
>
{
try
{
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-lmstudio"
,
);
return
response
?.
models
||
[];
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
throw
new
Error
(
`Failed to fetch LM Studio models:
${
error
.
message
}
`
);
}
throw
new
Error
(
"Failed to fetch LM Studio models: Unknown error occurred"
,
);
}
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-lmstudio"
,
);
return
response
?.
models
||
[];
}
// Listen for deep link events
...
...
src/ipc/ipc_types.ts
浏览文件 @
c71638a5
...
...
@@ -111,7 +111,6 @@ export interface LocalModel {
export
type
LocalModelListResponse
=
{
models
:
LocalModel
[];
error
:
string
|
null
;
};
export
interface
TokenCountParams
{
...
...
src/pages/app-details.tsx
浏览文件 @
c71638a5
...
...
@@ -28,6 +28,7 @@ import {
}
from
"@/components/ui/dialog"
;
import
{
GitHubConnector
}
from
"@/components/GitHubConnector"
;
import
{
SupabaseConnector
}
from
"@/components/SupabaseConnector"
;
import
{
showError
}
from
"@/lib/toast"
;
export
default
function
AppDetailsPage
()
{
const
navigate
=
useNavigate
();
...
...
@@ -62,7 +63,7 @@ export default function AppDetailsPage() {
await
refreshApps
();
navigate
({
to
:
"/"
,
search
:
{}
});
}
catch
(
error
)
{
console
.
error
(
"Failed to delete app:"
,
error
);
showError
(
error
);
}
finally
{
setIsDeleting
(
false
);
}
...
...
src/pages/settings.tsx
浏览文件 @
c71638a5
...
...
@@ -27,12 +27,8 @@ export default function SettingsPage() {
setIsResetting
(
true
);
try
{
const
ipcClient
=
IpcClient
.
getInstance
();
const
result
=
await
ipcClient
.
resetAll
();
if
(
result
.
success
)
{
showSuccess
(
"Successfully reset everything. Restart the application."
);
}
else
{
showError
(
result
.
message
||
"Failed to reset everything."
);
}
await
ipcClient
.
resetAll
();
showSuccess
(
"Successfully reset everything. Restart the application."
);
}
catch
(
error
)
{
console
.
error
(
"Error resetting:"
,
error
);
showError
(
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论