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 个修改的文件
包含
346 行增加
和
509 行删除
+346
-509
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
+0
-0
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
+0
-0
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 }) {
...
@@ -92,11 +92,7 @@ export function ChatList({ show }: { show?: boolean }) {
const
handleDeleteChat
=
async
(
chatId
:
number
)
=>
{
const
handleDeleteChat
=
async
(
chatId
:
number
)
=>
{
try
{
try
{
const
result
=
await
IpcClient
.
getInstance
().
deleteChat
(
chatId
);
await
IpcClient
.
getInstance
().
deleteChat
(
chatId
);
if
(
!
result
.
success
)
{
showError
(
"Failed to delete chat"
);
return
;
}
showSuccess
(
"Chat deleted successfully"
);
showSuccess
(
"Chat deleted successfully"
);
// If the deleted chat was selected, navigate to home
// If the deleted chat was selected, navigate to home
...
...
src/components/GitHubConnector.tsx
浏览文件 @
c71638a5
...
@@ -153,18 +153,14 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
...
@@ -153,18 +153,14 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
setIsCreatingRepo
(
true
);
setIsCreatingRepo
(
true
);
setCreateRepoSuccess
(
false
);
setCreateRepoSuccess
(
false
);
try
{
try
{
const
result
=
await
IpcClient
.
getInstance
().
createGithubRepo
(
await
IpcClient
.
getInstance
().
createGithubRepo
(
githubOrg
,
githubOrg
,
repoName
,
repoName
,
appId
!
,
appId
!
,
);
);
if
(
result
.
success
)
{
setCreateRepoSuccess
(
true
);
setCreateRepoSuccess
(
true
);
setRepoCheckError
(
null
);
setRepoCheckError
(
null
);
refreshApp
();
refreshApp
();
}
else
{
setCreateRepoError
(
result
.
error
||
"Failed to create repository."
);
}
}
catch
(
err
:
any
)
{
}
catch
(
err
:
any
)
{
setCreateRepoError
(
err
.
message
||
"Failed to create repository."
);
setCreateRepoError
(
err
.
message
||
"Failed to create repository."
);
}
finally
{
}
finally
{
...
@@ -180,12 +176,8 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
...
@@ -180,12 +176,8 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
setIsDisconnecting
(
true
);
setIsDisconnecting
(
true
);
setDisconnectError
(
null
);
setDisconnectError
(
null
);
try
{
try
{
const
result
=
await
IpcClient
.
getInstance
().
disconnectGithubRepo
(
appId
);
await
IpcClient
.
getInstance
().
disconnectGithubRepo
(
appId
);
if
(
result
.
success
)
{
refreshApp
();
refreshApp
();
}
else
{
setDisconnectError
(
result
.
error
||
"Failed to disconnect repository."
);
}
}
catch
(
err
:
any
)
{
}
catch
(
err
:
any
)
{
setDisconnectError
(
err
.
message
||
"Failed to disconnect repository."
);
setDisconnectError
(
err
.
message
||
"Failed to disconnect repository."
);
}
finally
{
}
finally
{
...
...
src/components/HelpDialog.tsx
浏览文件 @
c71638a5
...
@@ -171,17 +171,12 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
...
@@ -171,17 +171,12 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
const
{
uploadUrl
,
filename
}
=
await
response
.
json
();
const
{
uploadUrl
,
filename
}
=
await
response
.
json
();
// Upload to the signed URL using IPC
await
IpcClient
.
getInstance
().
uploadToSignedUrl
(
const
uploadResult
=
await
IpcClient
.
getInstance
().
uploadToSignedUrl
(
uploadUrl
,
uploadUrl
,
"application/json"
,
"application/json"
,
chatLogsJson
,
chatLogsJson
,
);
);
if
(
!
uploadResult
.
success
)
{
throw
new
Error
(
`Failed to upload logs:
${
uploadResult
.
error
}
`
);
}
// Extract session ID (filename without extension)
// Extract session ID (filename without extension)
const
sessionId
=
filename
.
replace
(
".json"
,
""
);
const
sessionId
=
filename
.
replace
(
".json"
,
""
);
setSessionId
(
sessionId
);
setSessionId
(
sessionId
);
...
...
src/components/chat/ChatInput.tsx
浏览文件 @
c71638a5
...
@@ -58,7 +58,7 @@ import { useVersions } from "@/hooks/useVersions";
...
@@ -58,7 +58,7 @@ import { useVersions } from "@/hooks/useVersions";
import
{
useAttachments
}
from
"@/hooks/useAttachments"
;
import
{
useAttachments
}
from
"@/hooks/useAttachments"
;
import
{
AttachmentsList
}
from
"./AttachmentsList"
;
import
{
AttachmentsList
}
from
"./AttachmentsList"
;
import
{
DragDropOverlay
}
from
"./DragDropOverlay"
;
import
{
DragDropOverlay
}
from
"./DragDropOverlay"
;
import
{
showUncommittedFilesWarning
}
from
"@/lib/toast"
;
import
{
show
Error
,
show
UncommittedFilesWarning
}
from
"@/lib/toast"
;
const
showTokenBarAtom
=
atom
(
false
);
const
showTokenBarAtom
=
atom
(
false
);
export
function
ChatInput
({
chatId
}:
{
chatId
?:
number
})
{
export
function
ChatInput
({
chatId
}:
{
chatId
?:
number
})
{
...
@@ -182,13 +182,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
...
@@ -182,13 +182,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
chatId
,
chatId
,
messageId
,
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
)
{
if
(
result
.
uncommittedFiles
)
{
showUncommittedFilesWarning
(
result
.
uncommittedFiles
);
showUncommittedFilesWarning
(
result
.
uncommittedFiles
);
}
}
...
@@ -215,17 +208,10 @@ export function ChatInput({ chatId }: { chatId?: number }) {
...
@@ -215,17 +208,10 @@ export function ChatInput({ chatId }: { chatId?: number }) {
setIsRejecting
(
true
);
setIsRejecting
(
true
);
posthog
.
capture
(
"chat:reject"
);
posthog
.
capture
(
"chat:reject"
);
try
{
try
{
const
result
=
await
IpcClient
.
getInstance
().
rejectProposal
({
await
IpcClient
.
getInstance
().
rejectProposal
({
chatId
,
chatId
,
messageId
,
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
)
{
}
catch
(
err
)
{
console
.
error
(
"Error rejecting proposal:"
,
err
);
console
.
error
(
"Error rejecting proposal:"
,
err
);
setError
((
err
as
Error
)?.
message
||
"An error occurred while rejecting"
);
setError
((
err
as
Error
)?.
message
||
"An error occurred while rejecting"
);
...
@@ -389,13 +375,17 @@ function SummarizeInNewChatButton() {
...
@@ -389,13 +375,17 @@ function SummarizeInNewChatButton() {
console
.
error
(
"No app id found"
);
console
.
error
(
"No app id found"
);
return
;
return
;
}
}
const
newChatId
=
await
IpcClient
.
getInstance
().
createChat
(
appId
);
try
{
// navigate to new chat
const
newChatId
=
await
IpcClient
.
getInstance
().
createChat
(
appId
);
await
navigate
({
to
:
"/chat"
,
search
:
{
id
:
newChatId
}
});
// navigate to new chat
await
streamMessage
({
await
navigate
({
to
:
"/chat"
,
search
:
{
id
:
newChatId
}
});
prompt
:
"Summarize from chat-id="
+
chatId
,
await
streamMessage
({
chatId
:
newChatId
,
prompt
:
"Summarize from chat-id="
+
chatId
,
});
chatId
:
newChatId
,
});
}
catch
(
err
)
{
showError
(
err
);
}
};
};
return
(
return
(
<
TooltipProvider
>
<
TooltipProvider
>
...
...
src/components/chat/MessagesList.tsx
浏览文件 @
c71638a5
...
@@ -89,14 +89,13 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
...
@@ -89,14 +89,13 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
await
revertVersion
({
await
revertVersion
({
versionId
:
chat
.
initialCommitHash
,
versionId
:
chat
.
initialCommitHash
,
});
});
const
result
=
try
{
await
IpcClient
.
getInstance
().
deleteMessages
(
await
IpcClient
.
getInstance
().
deleteMessages
(
selectedChatId
,
selectedChatId
,
);
);
if
(
result
.
success
)
{
setMessages
([]);
setMessages
([]);
}
else
{
}
catch
(
err
)
{
showError
(
result
.
erro
r
);
showError
(
er
r
);
}
}
}
else
{
}
else
{
showWarning
(
showWarning
(
...
...
src/components/preview_panel/FileEditor.tsx
浏览文件 @
c71638a5
...
@@ -6,6 +6,7 @@ import { ChevronRight, Circle } from "lucide-react";
...
@@ -6,6 +6,7 @@ import { ChevronRight, Circle } from "lucide-react";
import
"@/components/chat/monaco"
;
import
"@/components/chat/monaco"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
showError
}
from
"@/lib/toast"
;
interface
FileEditorProps
{
interface
FileEditorProps
{
appId
:
number
|
null
;
appId
:
number
|
null
;
...
@@ -132,8 +133,7 @@ export const FileEditor = ({ appId, filePath }: FileEditorProps) => {
...
@@ -132,8 +133,7 @@ export const FileEditor = ({ appId, filePath }: FileEditorProps) => {
needsSaveRef
.
current
=
false
;
needsSaveRef
.
current
=
false
;
setDisplayUnsavedChanges
(
false
);
setDisplayUnsavedChanges
(
false
);
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"Error saving file:"
,
error
);
showError
(
error
);
// Could add error notification here
}
finally
{
}
finally
{
isSavingRef
.
current
=
false
;
isSavingRef
.
current
=
false
;
}
}
...
...
src/hooks/useSupabase.ts
浏览文件 @
c71638a5
...
@@ -42,14 +42,8 @@ export function useSupabase() {
...
@@ -42,14 +42,8 @@ export function useSupabase() {
async
(
projectId
:
string
,
appId
:
number
)
=>
{
async
(
projectId
:
string
,
appId
:
number
)
=>
{
setLoading
(
true
);
setLoading
(
true
);
try
{
try
{
const
result
=
await
ipcClient
.
setSupabaseAppProject
(
projectId
,
appId
);
await
ipcClient
.
setSupabaseAppProject
(
projectId
,
appId
);
setError
(
null
);
if
(
result
.
success
)
{
setError
(
null
);
return
result
;
}
else
{
throw
new
Error
(
"Failed to set project for app"
);
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"Error setting Supabase project for app:"
,
error
);
console
.
error
(
"Error setting Supabase project for app:"
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
...
@@ -68,14 +62,8 @@ export function useSupabase() {
...
@@ -68,14 +62,8 @@ export function useSupabase() {
async
(
appId
:
number
)
=>
{
async
(
appId
:
number
)
=>
{
setLoading
(
true
);
setLoading
(
true
);
try
{
try
{
const
result
=
await
ipcClient
.
unsetSupabaseAppProject
(
appId
);
await
ipcClient
.
unsetSupabaseAppProject
(
appId
);
setError
(
null
);
if
(
result
.
success
)
{
setError
(
null
);
return
result
;
}
else
{
throw
new
Error
(
"Failed to unset project for app"
);
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"Error unsetting Supabase project for app:"
,
error
);
console
.
error
(
"Error unsetting Supabase project for app:"
,
error
);
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
setError
(
error
instanceof
Error
?
error
:
new
Error
(
String
(
error
)));
...
...
src/ipc/handlers/app_handlers.ts
浏览文件 @
c71638a5
差异被折叠。
点击展开。
src/ipc/handlers/chat_handlers.ts
浏览文件 @
c71638a5
...
@@ -5,14 +5,16 @@ import { desc, eq } from "drizzle-orm";
...
@@ -5,14 +5,16 @@ import { desc, eq } from "drizzle-orm";
import
type
{
ChatSummary
}
from
"../../lib/schemas"
;
import
type
{
ChatSummary
}
from
"../../lib/schemas"
;
import
*
as
git
from
"isomorphic-git"
;
import
*
as
git
from
"isomorphic-git"
;
import
*
as
fs
from
"fs"
;
import
*
as
fs
from
"fs"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
import
log
from
"electron-log"
;
import
log
from
"electron-log"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
const
logger
=
log
.
scope
(
"chat_handlers"
);
const
logger
=
log
.
scope
(
"chat_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerChatHandlers
()
{
export
function
registerChatHandlers
()
{
ipcMain
.
handle
(
"create-chat"
,
async
(
_
,
appId
:
number
)
=>
{
handle
(
"create-chat"
,
async
(
_
,
appId
:
number
):
Promise
<
number
>
=>
{
// Get the app's path first
// Get the app's path first
const
app
=
await
db
.
query
.
apps
.
findFirst
({
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
where
:
eq
(
apps
.
id
,
appId
),
...
@@ -74,54 +76,38 @@ export function registerChatHandlers() {
...
@@ -74,54 +76,38 @@ export function registerChatHandlers() {
return
chat
;
return
chat
;
});
});
ipcMain
.
handle
(
handle
(
"get-chats"
,
async
(
_
,
appId
?:
number
):
Promise
<
ChatSummary
[]
>
=>
{
"get-chats"
,
// If appId is provided, filter chats for that app
async
(
_
,
appId
?:
number
):
Promise
<
ChatSummary
[]
>
=>
{
const
query
=
appId
// If appId is provided, filter chats for that app
?
db
.
query
.
chats
.
findMany
({
const
query
=
appId
where
:
eq
(
chats
.
appId
,
appId
),
?
db
.
query
.
chats
.
findMany
({
columns
:
{
where
:
eq
(
chats
.
appId
,
appId
),
id
:
true
,
columns
:
{
title
:
true
,
id
:
true
,
createdAt
:
true
,
title
:
true
,
appId
:
true
,
createdAt
:
true
,
},
appId
:
true
,
orderBy
:
[
desc
(
chats
.
createdAt
)],
},
})
orderBy
:
[
desc
(
chats
.
createdAt
)],
:
db
.
query
.
chats
.
findMany
({
})
columns
:
{
:
db
.
query
.
chats
.
findMany
({
id
:
true
,
columns
:
{
title
:
true
,
id
:
true
,
createdAt
:
true
,
title
:
true
,
appId
:
true
,
createdAt
:
true
,
},
appId
:
true
,
orderBy
:
[
desc
(
chats
.
createdAt
)],
},
});
orderBy
:
[
desc
(
chats
.
createdAt
)],
});
const
allChats
=
await
query
;
const
allChats
=
await
query
;
return
allChats
;
return
allChats
;
},
});
);
ipcMain
.
handle
(
"delete-chat"
,
async
(
_
,
chatId
:
number
)
=>
{
handle
(
"delete-chat"
,
async
(
_
,
chatId
:
number
):
Promise
<
void
>
=>
{
try
{
await
db
.
delete
(
chats
).
where
(
eq
(
chats
.
id
,
chatId
));
// 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
};
}
});
});
ipcMain
.
handle
(
"delete-messages"
,
async
(
_
,
chatId
:
number
)
=>
{
handle
(
"delete-messages"
,
async
(
_
,
chatId
:
number
):
Promise
<
void
>
=>
{
try
{
await
db
.
delete
(
messages
).
where
(
eq
(
messages
.
chatId
,
chatId
));
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
};
}
});
});
}
}
src/ipc/handlers/dependency_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
{
db
}
from
"../../db"
;
import
{
db
}
from
"../../db"
;
import
{
messages
,
apps
,
chats
}
from
"../../db/schema"
;
import
{
messages
,
apps
,
chats
}
from
"../../db/schema"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
executeAddDependency
}
from
"../processors/executeAddDependency"
;
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
()
{
export
function
registerDependencyHandlers
()
{
ipcMain
.
handle
(
handle
(
"chat:add-dep"
,
"chat:add-dep"
,
async
(
async
(
_event
,
_event
,
{
chatId
,
packages
}:
{
chatId
:
number
;
packages
:
string
[]
},
{
chatId
,
packages
}:
{
chatId
:
number
;
packages
:
string
[]
},
)
=>
{
)
:
Promise
<
void
>
=>
{
// Find the message from the database
// Find the message from the database
const
foundMessages
=
await
db
.
query
.
messages
.
findMany
({
const
foundMessages
=
await
db
.
query
.
messages
.
findMany
({
where
:
eq
(
messages
.
chatId
,
chatId
),
where
:
eq
(
messages
.
chatId
,
chatId
),
...
...
src/ipc/handlers/github_handlers.ts
浏览文件 @
c71638a5
...
@@ -285,7 +285,7 @@ function handleStartGithubFlow(
...
@@ -285,7 +285,7 @@ function handleStartGithubFlow(
async
function
handleIsRepoAvailable
(
async
function
handleIsRepoAvailable
(
event
:
IpcMainInvokeEvent
,
event
:
IpcMainInvokeEvent
,
{
org
,
repo
}:
{
org
:
string
;
repo
:
string
},
{
org
,
repo
}:
{
org
:
string
;
repo
:
string
},
)
{
)
:
Promise
<
{
available
:
boolean
;
error
?:
string
}
>
{
try
{
try
{
// Get access token from settings
// Get access token from settings
const
settings
=
readSettings
();
const
settings
=
readSettings
();
...
@@ -323,49 +323,44 @@ async function handleIsRepoAvailable(
...
@@ -323,49 +323,44 @@ async function handleIsRepoAvailable(
async
function
handleCreateRepo
(
async
function
handleCreateRepo
(
event
:
IpcMainInvokeEvent
,
event
:
IpcMainInvokeEvent
,
{
org
,
repo
,
appId
}:
{
org
:
string
;
repo
:
string
;
appId
:
number
},
{
org
,
repo
,
appId
}:
{
org
:
string
;
repo
:
string
;
appId
:
number
},
)
{
):
Promise
<
void
>
{
try
{
// Get access token from settings
// Get access token from settings
const
settings
=
readSettings
();
const
settings
=
readSettings
();
const
accessToken
=
settings
.
githubAccessToken
?.
value
;
const
accessToken
=
settings
.
githubAccessToken
?.
value
;
if
(
!
accessToken
)
{
if
(
!
accessToken
)
{
throw
new
Error
(
"Not authenticated with GitHub."
);
return
{
success
:
false
,
error
:
"Not authenticated with GitHub."
};
}
}
// If org is empty, create for the authenticated user
// If org is empty, create for the authenticated user
let
owner
=
org
;
let
owner
=
org
;
if
(
!
owner
)
{
if
(
!
owner
)
{
const
userRes
=
await
fetch
(
"https://api.github.com/user"
,
{
const
userRes
=
await
fetch
(
"https://api.github.com/user"
,
{
headers
:
{
Authorization
:
`Bearer
${
accessToken
}
`
},
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
,
}),
});
});
if
(
!
res
.
ok
)
{
const
user
=
await
userRes
.
json
();
const
data
=
await
res
.
json
();
owner
=
user
.
login
;
return
{
success
:
false
,
error
:
data
.
message
||
"Failed to create repo"
};
}
}
// Create repo
// Store org and repo in the app's DB row (apps table)
const
createUrl
=
org
await
updateAppGithubRepo
(
appId
,
owner
,
repo
);
?
`https://api.github.com/orgs/
${
owner
}
/repos`
return
{
success
:
true
};
:
`https://api.github.com/user/repos`
;
}
catch
(
err
:
any
)
{
const
res
=
await
fetch
(
createUrl
,
{
return
{
success
:
false
,
error
:
err
.
message
||
"Unknown error"
};
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 ---
// --- GitHub Push Handler ---
...
@@ -420,36 +415,26 @@ async function handlePushToGithub(
...
@@ -420,36 +415,26 @@ async function handlePushToGithub(
async
function
handleDisconnectGithubRepo
(
async
function
handleDisconnectGithubRepo
(
event
:
IpcMainInvokeEvent
,
event
:
IpcMainInvokeEvent
,
{
appId
}:
{
appId
:
number
},
{
appId
}:
{
appId
:
number
},
)
{
):
Promise
<
void
>
{
try
{
logger
.
log
(
`Disconnecting GitHub repo for appId:
${
appId
}
`
);
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"
};
}
// Update app in database to remove GitHub repo and org
// Get the app from the database
await
db
const
app
=
await
db
.
query
.
apps
.
findFirst
({
.
update
(
apps
)
where
:
eq
(
apps
.
id
,
appId
),
.
set
({
});
githubRepo
:
null
,
githubOrg
:
null
,
})
.
where
(
eq
(
apps
.
id
,
appId
));
return
{
success
:
true
};
if
(
!
app
)
{
}
catch
(
error
)
{
throw
new
Error
(
"App not found"
);
logger
.
error
(
`Error disconnecting GitHub repo:
${
error
}
`
);
return
{
success
:
false
,
error
:
error
instanceof
Error
?
error
.
message
:
String
(
error
),
};
}
}
// 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 ---
// --- Registration ---
...
...
src/ipc/handlers/local_model_lmstudio_handler.ts
浏览文件 @
c71638a5
...
@@ -18,28 +18,24 @@ export interface LMStudioModel {
...
@@ -18,28 +18,24 @@ export interface LMStudioModel {
}
}
export
async
function
fetchLMStudioModels
():
Promise
<
LocalModelListResponse
>
{
export
async
function
fetchLMStudioModels
():
Promise
<
LocalModelListResponse
>
{
try
{
const
modelsResponse
:
Response
=
await
fetch
(
const
modelsResponse
:
Response
=
await
fetch
(
"http://localhost:1234/api/v0/models"
,
"http://localhost:1234/api/v0/models"
,
);
);
if
(
!
modelsResponse
.
ok
)
{
if
(
!
modelsResponse
.
ok
)
{
throw
new
Error
(
"Failed to fetch models from LM Studio"
);
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
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
()
{
export
function
registerLMStudioHandlers
()
{
...
...
src/ipc/handlers/local_model_ollama_handler.ts
浏览文件 @
c71638a5
...
@@ -47,20 +47,17 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
...
@@ -47,20 +47,17 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
};
};
});
});
logger
.
info
(
`Successfully fetched
${
models
.
length
}
models from Ollama`
);
logger
.
info
(
`Successfully fetched
${
models
.
length
}
models from Ollama`
);
return
{
models
,
error
:
null
};
return
{
models
};
}
catch
(
error
)
{
}
catch
(
error
)
{
if
(
if
(
error
instanceof
TypeError
&&
error
instanceof
TypeError
&&
(
error
as
Error
).
message
.
includes
(
"fetch failed"
)
(
error
as
Error
).
message
.
includes
(
"fetch failed"
)
)
{
)
{
logger
.
error
(
"Could not connect to Ollama"
);
throw
new
Error
(
return
{
"Could not connect to Ollama. Make sure it's running at http://localhost:11434"
,
models
:
[],
);
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
{
import
type
{
CodeProposal
,
CodeProposal
,
ProposalResult
,
ProposalResult
,
...
@@ -29,9 +29,9 @@ import {
...
@@ -29,9 +29,9 @@ import {
import
{
extractCodebase
}
from
"../../utils/codebase"
;
import
{
extractCodebase
}
from
"../../utils/codebase"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
withLock
}
from
"../utils/lock_utils"
;
import
{
withLock
}
from
"../utils/lock_utils"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"proposal_handlers"
);
const
logger
=
log
.
scope
(
"proposal_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
// Cache for codebase token counts
// Cache for codebase token counts
interface
CodebaseTokenCache
{
interface
CodebaseTokenCache
{
chatId
:
number
;
chatId
:
number
;
...
@@ -317,115 +317,83 @@ const approveProposalHandler = async (
...
@@ -317,115 +317,83 @@ const approveProposalHandler = async (
_event
:
IpcMainInvokeEvent
,
_event
:
IpcMainInvokeEvent
,
{
chatId
,
messageId
}:
{
chatId
:
number
;
messageId
:
number
},
{
chatId
,
messageId
}:
{
chatId
:
number
;
messageId
:
number
},
):
Promise
<
{
):
Promise
<
{
success
:
boolean
;
error
?:
string
;
uncommittedFiles
?:
string
[];
uncommittedFiles
?:
string
[];
}
>
=>
{
}
>
=>
{
logger
.
log
(
// 1. Fetch the specific assistant message
`IPC: approve-proposal called for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
const
messageToApprove
=
await
db
.
query
.
messages
.
findFirst
({
);
where
:
and
(
eq
(
messages
.
id
,
messageId
),
try
{
eq
(
messages
.
chatId
,
chatId
),
// 1. Fetch the specific assistant message
eq
(
messages
.
role
,
"assistant"
),
const
messageToApprove
=
await
db
.
query
.
messages
.
findFirst
({
),
where
:
and
(
columns
:
{
eq
(
messages
.
id
,
messageId
),
content
:
true
,
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."
};
}
// 2. Process the actions defined in the message content
if
(
!
messageToApprove
?.
content
)
{
const
chatSummary
=
getDyadChatSummaryTag
(
messageToApprove
.
content
);
throw
new
Error
(
const
processResult
=
await
processFullResponseActions
(
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
messageToApprove
.
content
,
chatId
,
{
chatSummary
:
chatSummary
??
undefined
,
messageId
,
},
// Pass summary if found
);
);
}
if
(
processResult
.
error
)
{
// 2. Process the actions defined in the message content
logger
.
error
(
const
chatSummary
=
getDyadChatSummaryTag
(
messageToApprove
.
content
);
`Error processing actions for message
${
messageId
}
:`
,
const
processResult
=
await
processFullResponseActions
(
processResult
.
error
,
messageToApprove
.
content
,
);
chatId
,
// Optionally: Update message state to 'error' or similar?
{
// For now, just return error to frontend
chatSummary
:
chatSummary
??
undefined
,
return
{
messageId
,
success
:
false
,
},
// Pass summary if found
error
:
`Action processing failed:
${
processResult
.
error
}
`
,
);
};
}
return
{
success
:
true
,
uncommittedFiles
:
processResult
.
uncommittedFiles
};
if
(
processResult
.
error
)
{
}
catch
(
error
)
{
throw
new
Error
(
logger
.
error
(
`Error approving proposal for messageId
${
messageId
}
:`
,
error
);
`Error processing actions for message
${
messageId
}
:
${
processResult
.
error
}
`
,
return
{
);
success
:
false
,
error
:
(
error
as
Error
)?.
message
||
"Unknown error"
,
};
}
}
return
{
uncommittedFiles
:
processResult
.
uncommittedFiles
};
};
};
// Handler to reject a proposal (just update message state)
// Handler to reject a proposal (just update message state)
const
rejectProposalHandler
=
async
(
const
rejectProposalHandler
=
async
(
_event
:
IpcMainInvokeEvent
,
_event
:
IpcMainInvokeEvent
,
{
chatId
,
messageId
}:
{
chatId
:
number
;
messageId
:
number
},
{
chatId
,
messageId
}:
{
chatId
:
number
;
messageId
:
number
},
):
Promise
<
{
success
:
boolean
;
error
?:
string
}
>
=>
{
):
Promise
<
void
>
=>
{
logger
.
log
(
logger
.
log
(
`IPC: reject-proposal called for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
`IPC: reject-proposal called for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
);
try
{
// 1. Verify the message exists and is an assistant message
// 1. Verify the message exists and is an assistant message
const
messageToReject
=
await
db
.
query
.
messages
.
findFirst
({
const
messageToReject
=
await
db
.
query
.
messages
.
findFirst
({
where
:
and
(
where
:
and
(
eq
(
messages
.
id
,
messageId
),
eq
(
messages
.
id
,
messageId
),
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
role
,
"assistant"
),
eq
(
messages
.
role
,
"assistant"
),
),
),
columns
:
{
id
:
true
},
columns
:
{
id
:
true
},
});
});
if
(
!
messageToReject
)
{
logger
.
error
(
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
);
return
{
success
:
false
,
error
:
"Assistant message not found."
};
}
// 2. Update the message's approval state to 'rejected'
if
(
!
messageToReject
)
{
await
db
throw
new
Error
(
.
update
(
messages
)
`Assistant message not found for chatId:
${
chatId
}
, messageId:
${
messageId
}
`
,
.
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"
,
};
}
}
// 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
// Function to register proposal-related handlers
export
function
registerProposalHandlers
()
{
export
function
registerProposalHandlers
()
{
ipcMain
.
handle
(
"get-proposal"
,
getProposalHandler
);
handle
(
"get-proposal"
,
getProposalHandler
);
ipcMain
.
handle
(
"approve-proposal"
,
approveProposalHandler
);
handle
(
"approve-proposal"
,
approveProposalHandler
);
ipcMain
.
handle
(
"reject-proposal"
,
rejectProposalHandler
);
handle
(
"reject-proposal"
,
rejectProposalHandler
);
}
}
src/ipc/handlers/safe_handle.ts
浏览文件 @
c71638a5
import
{
ipcMain
,
IpcMainInvokeEvent
}
from
"electron"
;
import
{
ipcMain
,
IpcMainInvokeEvent
}
from
"electron"
;
import
log
from
"electron-log"
;
import
log
from
"electron-log"
;
export
function
create
Safe
Handler
(
logger
:
log
.
LogFunctions
)
{
export
function
create
Logged
Handler
(
logger
:
log
.
LogFunctions
)
{
return
(
return
(
channel
:
string
,
channel
:
string
,
fn
:
(
event
:
IpcMainInvokeEvent
,
...
args
:
any
[])
=>
Promise
<
any
>
,
fn
:
(
event
:
IpcMainInvokeEvent
,
...
args
:
any
[])
=>
Promise
<
any
>
,
...
@@ -9,8 +9,11 @@ export function createSafeHandler(logger: log.LogFunctions) {
...
@@ -9,8 +9,11 @@ export function createSafeHandler(logger: log.LogFunctions) {
ipcMain
.
handle
(
ipcMain
.
handle
(
channel
,
channel
,
async
(
event
:
IpcMainInvokeEvent
,
...
args
:
any
[])
=>
{
async
(
event
:
IpcMainInvokeEvent
,
...
args
:
any
[])
=>
{
logger
.
log
(
`IPC:
${
channel
}
called with args:
${
JSON
.
stringify
(
args
)}
`
);
try
{
try
{
return
await
fn
(
event
,
...
args
);
const
result
=
await
fn
(
event
,
...
args
);
logger
.
log
(
`IPC:
${
channel
}
returned:
${
JSON
.
stringify
(
result
)}
`
);
return
result
;
}
catch
(
error
)
{
}
catch
(
error
)
{
logger
.
error
(
logger
.
error
(
`Error in
${
fn
.
name
}
: args:
${
JSON
.
stringify
(
args
)}
`
,
`Error in
${
fn
.
name
}
: args:
${
JSON
.
stringify
(
args
)}
`
,
...
...
src/ipc/handlers/settings_handlers.ts
浏览文件 @
c71638a5
...
@@ -4,11 +4,13 @@ import { writeSettings } from "../../main/settings";
...
@@ -4,11 +4,13 @@ import { writeSettings } from "../../main/settings";
import
{
readSettings
}
from
"../../main/settings"
;
import
{
readSettings
}
from
"../../main/settings"
;
export
function
registerSettingsHandlers
()
{
export
function
registerSettingsHandlers
()
{
// Intentionally do NOT use handle because it could log sensitive data from the return value.
ipcMain
.
handle
(
"get-user-settings"
,
async
()
=>
{
ipcMain
.
handle
(
"get-user-settings"
,
async
()
=>
{
const
settings
=
readSettings
();
const
settings
=
readSettings
();
return
settings
;
return
settings
;
});
});
// Intentionally do NOT use handle because it could log sensitive data from the args.
ipcMain
.
handle
(
ipcMain
.
handle
(
"set-user-settings"
,
"set-user-settings"
,
async
(
_
,
settings
:
Partial
<
UserSettings
>
)
=>
{
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
log
from
"electron-log"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"shell_handlers"
);
const
logger
=
log
.
scope
(
"shell_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerShellHandlers
()
{
export
function
registerShellHandlers
()
{
ipcMain
.
handle
(
"open-external-url"
,
async
(
_event
,
url
:
string
)
=>
{
handle
(
"open-external-url"
,
async
(
_event
,
url
:
string
)
=>
{
try
{
if
(
!
url
)
{
// Basic validation to ensure it's a http/https url
throw
new
Error
(
"No URL provided."
);
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
};
}
}
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
)
=>
{
handle
(
"show-item-in-folder"
,
async
(
_event
,
fullPath
:
string
)
=>
{
try
{
// Validate that a path was provided
// Validate that a path was provided
if
(
!
fullPath
)
{
if
(
!
fullPath
)
{
throw
new
Error
(
"No file path provided."
);
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
};
}
}
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
log
from
"electron-log"
;
import
{
db
}
from
"../../db"
;
import
{
db
}
from
"../../db"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
apps
}
from
"../../db/schema"
;
import
{
apps
}
from
"../../db/schema"
;
import
{
getSupabaseClient
}
from
"../../supabase_admin/supabase_management_client"
;
import
{
getSupabaseClient
}
from
"../../supabase_admin/supabase_management_client"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"supabase_handlers"
);
const
logger
=
log
.
scope
(
"supabase_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerSupabaseHandlers
()
{
export
function
registerSupabaseHandlers
()
{
// List all Supabase projects
handle
(
"supabase:list-projects"
,
async
()
=>
{
ipcMain
.
handle
(
"supabase:list-projects"
,
async
()
=>
{
const
supabase
=
await
getSupabaseClient
();
try
{
return
supabase
.
getProjects
();
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
;
}
});
});
// Set app project - links a Dyad app to a Supabase project
// Set app project - links a Dyad app to a Supabase project
ipcMain
.
handle
(
handle
(
"supabase:set-app-project"
,
"supabase:set-app-project"
,
async
(
_
,
{
project
,
app
}:
{
project
:
string
;
app
:
number
})
=>
{
async
(
_
,
{
project
,
app
}:
{
project
:
string
;
app
:
number
})
=>
{
try
{
await
db
// Here you could store the project-app association in your database
.
update
(
apps
)
// For example:
.
set
({
supabaseProjectId
:
project
})
await
db
.
where
(
eq
(
apps
.
id
,
app
));
.
update
(
apps
)
.
set
({
supabaseProjectId
:
project
})
.
where
(
eq
(
apps
.
id
,
app
));
logger
.
info
(
`Associated app
${
app
}
with Supabase project
${
project
}
`
);
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
;
}
},
},
);
);
// Unset app project - removes the link between a Dyad app and a Supabase project
// Unset app project - removes the link between a Dyad app and a Supabase project
ipcMain
.
handle
(
handle
(
"supabase:unset-app-project"
,
async
(
_
,
{
app
}:
{
app
:
number
})
=>
{
"supabase:unset-app-project"
,
await
db
async
(
_
,
{
app
}:
{
app
:
number
})
=>
{
.
update
(
apps
)
try
{
.
set
({
supabaseProjectId
:
null
})
await
db
.
where
(
eq
(
apps
.
id
,
app
));
.
update
(
apps
)
.
set
({
supabaseProjectId
:
null
})
.
where
(
eq
(
apps
.
id
,
app
));
logger
.
info
(
`Removed Supabase project association for app
${
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
;
}
},
);
}
}
src/ipc/handlers/token_count_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
{
db
}
from
"../../db"
;
import
{
db
}
from
"../../db"
;
import
{
chats
}
from
"../../db/schema"
;
import
{
chats
}
from
"../../db/schema"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
eq
}
from
"drizzle-orm"
;
...
@@ -15,91 +14,82 @@ import { getSupabaseContext } from "../../supabase_admin/supabase_context";
...
@@ -15,91 +14,82 @@ import { getSupabaseContext } from "../../supabase_admin/supabase_context";
import
{
TokenCountParams
}
from
"../ipc_types"
;
import
{
TokenCountParams
}
from
"../ipc_types"
;
import
{
TokenCountResult
}
from
"../ipc_types"
;
import
{
TokenCountResult
}
from
"../ipc_types"
;
import
{
estimateTokens
,
getContextWindow
}
from
"../utils/token_utils"
;
import
{
estimateTokens
,
getContextWindow
}
from
"../utils/token_utils"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"token_count_handlers"
);
const
logger
=
log
.
scope
(
"token_count_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
export
function
registerTokenCountHandlers
()
{
export
function
registerTokenCountHandlers
()
{
ipcMain
.
handle
(
handle
(
"chat:count-tokens"
,
"chat:count-tokens"
,
async
(
event
,
req
:
TokenCountParams
):
Promise
<
TokenCountResult
>
=>
{
async
(
event
,
req
:
TokenCountParams
):
Promise
<
TokenCountResult
>
=>
{
try
{
const
chat
=
await
db
.
query
.
chats
.
findFirst
({
// Get the chat with messages
where
:
eq
(
chats
.
id
,
req
.
chatId
),
const
chat
=
await
db
.
query
.
chats
.
findFirst
({
with
:
{
where
:
eq
(
chats
.
id
,
req
.
chatId
),
messages
:
{
with
:
{
orderBy
:
(
messages
,
{
asc
})
=>
[
asc
(
messages
.
createdAt
)],
messages
:
{
orderBy
:
(
messages
,
{
asc
})
=>
[
asc
(
messages
.
createdAt
)],
},
app
:
true
,
},
},
});
app
:
true
,
},
});
if
(
!
chat
)
{
if
(
!
chat
)
{
throw
new
Error
(
`Chat not found:
${
req
.
chatId
}
`
);
throw
new
Error
(
`Chat not found:
${
req
.
chatId
}
`
);
}
}
// Prepare message history for token counting
// Prepare message history for token counting
const
messageHistory
=
chat
.
messages
const
messageHistory
=
chat
.
messages
.
map
((
message
)
=>
message
.
content
)
.
map
((
message
)
=>
message
.
content
)
.
join
(
""
);
.
join
(
""
);
const
messageHistoryTokens
=
estimateTokens
(
messageHistory
);
const
messageHistoryTokens
=
estimateTokens
(
messageHistory
);
// Count input tokens
// Count input tokens
const
inputTokens
=
estimateTokens
(
req
.
input
);
const
inputTokens
=
estimateTokens
(
req
.
input
);
// Count system prompt tokens
// Count system prompt tokens
let
systemPrompt
=
SYSTEM_PROMPT
;
let
systemPrompt
=
SYSTEM_PROMPT
;
let
supabaseContext
=
""
;
let
supabaseContext
=
""
;
if
(
chat
.
app
?.
supabaseProjectId
)
{
if
(
chat
.
app
?.
supabaseProjectId
)
{
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_AVAILABLE_SYSTEM_PROMPT
;
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_AVAILABLE_SYSTEM_PROMPT
;
supabaseContext
=
await
getSupabaseContext
({
supabaseContext
=
await
getSupabaseContext
({
supabaseProjectId
:
chat
.
app
.
supabaseProjectId
,
supabaseProjectId
:
chat
.
app
.
supabaseProjectId
,
});
});
}
else
{
}
else
{
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT
;
systemPrompt
+=
"
\
n
\
n"
+
SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT
;
}
}
const
systemPromptTokens
=
estimateTokens
(
const
systemPromptTokens
=
estimateTokens
(
systemPrompt
+
supabaseContext
);
systemPrompt
+
supabaseContext
,
);
// Extract codebase information if app is associated with the chat
// Extract codebase information if app is associated with the chat
let
codebaseInfo
=
""
;
let
codebaseInfo
=
""
;
let
codebaseTokens
=
0
;
let
codebaseTokens
=
0
;
if
(
chat
.
app
)
{
if
(
chat
.
app
)
{
const
appPath
=
getDyadAppPath
(
chat
.
app
.
path
);
const
appPath
=
getDyadAppPath
(
chat
.
app
.
path
);
try
{
codebaseInfo
=
await
extractCodebase
(
appPath
);
codebaseInfo
=
await
extractCodebase
(
appPath
);
codebaseTokens
=
estimateTokens
(
codebaseInfo
);
codebaseTokens
=
estimateTokens
(
codebaseInfo
);
logger
.
log
(
logger
.
log
(
`Extracted codebase information from
${
appPath
}
, tokens:
${
codebaseTokens
}
`
,
`Extracted codebase information from
${
appPath
}
, tokens:
${
codebaseTokens
}
`
,
);
);
}
}
catch
(
error
)
{
logger
.
error
(
"Error extracting codebase:"
,
error
);
}
}
// Calculate total tokens
// Calculate total tokens
const
totalTokens
=
const
totalTokens
=
messageHistoryTokens
+
messageHistoryTokens
+
inputTokens
+
inputTokens
+
systemPromptTokens
+
systemPromptTokens
+
codebaseTokens
;
codebaseTokens
;
return
{
return
{
totalTokens
,
totalTokens
,
messageHistoryTokens
,
messageHistoryTokens
,
codebaseTokens
,
codebaseTokens
,
inputTokens
,
inputTokens
,
systemPromptTokens
,
systemPromptTokens
,
contextWindow
:
getContextWindow
(),
contextWindow
:
getContextWindow
(),
};
};
}
catch
(
error
)
{
logger
.
error
(
"Error counting tokens:"
,
error
);
throw
error
;
}
},
},
);
);
}
}
src/ipc/handlers/upload_handlers.ts
浏览文件 @
c71638a5
import
{
ipcMain
}
from
"electron"
;
import
log
from
"electron-log"
;
import
log
from
"electron-log"
;
import
fetch
from
"node-fetch"
;
import
fetch
from
"node-fetch"
;
import
{
createLoggedHandler
}
from
"./safe_handle"
;
const
logger
=
log
.
scope
(
"upload_handlers"
);
const
logger
=
log
.
scope
(
"upload_handlers"
);
const
handle
=
createLoggedHandler
(
logger
);
interface
UploadToSignedUrlParams
{
interface
UploadToSignedUrlParams
{
url
:
string
;
url
:
string
;
contentType
:
string
;
contentType
:
string
;
...
@@ -11,49 +13,37 @@ interface UploadToSignedUrlParams {
...
@@ -11,49 +13,37 @@ interface UploadToSignedUrlParams {
}
}
export
function
registerUploadHandlers
()
{
export
function
registerUploadHandlers
()
{
ipcMain
.
handle
(
handle
(
"upload-to-signed-url"
,
async
(
_
,
params
:
UploadToSignedUrlParams
)
=>
{
"upload-to-signed-url"
,
const
{
url
,
contentType
,
data
}
=
params
;
async
(
_
,
params
:
UploadToSignedUrlParams
)
=>
{
logger
.
debug
(
"IPC: upload-to-signed-url called"
);
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://"
))
{
try
{
throw
new
Error
(
"Invalid signed URL provided"
);
// 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"
);
// 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"
,
// Perform the upload to the signed URL
headers
:
{
const
response
=
await
fetch
(
url
,
{
"Content-Type"
:
contentType
,
method
:
"PUT"
,
},
headers
:
{
body
:
JSON
.
stringify
(
data
),
"Content-Type"
:
contentType
,
});
},
body
:
JSON
.
stringify
(
data
),
if
(
!
response
.
ok
)
{
});
throw
new
Error
(
`Upload failed with status
${
response
.
status
}
:
${
response
.
statusText
}
`
,
if
(
!
response
.
ok
)
{
);
throw
new
Error
(
}
`Upload failed with status
${
response
.
status
}
:
${
response
.
statusText
}
`
,
);
logger
.
debug
(
"Successfully uploaded data to signed URL"
);
}
});
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
),
};
}
},
);
logger
.
debug
(
"Registered upload IPC handlers"
);
logger
.
debug
(
"Registered upload IPC handlers"
);
}
}
src/ipc/handlers/version_handlers.ts
浏览文件 @
c71638a5
...
@@ -10,11 +10,11 @@ import { promises as fsPromises } from "node:fs";
...
@@ -10,11 +10,11 @@ import { promises as fsPromises } from "node:fs";
import
{
withLock
}
from
"../utils/lock_utils"
;
import
{
withLock
}
from
"../utils/lock_utils"
;
import
{
getGitAuthor
}
from
"../utils/git_author"
;
import
{
getGitAuthor
}
from
"../utils/git_author"
;
import
log
from
"electron-log"
;
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
logger
=
log
.
scope
(
"version_handlers"
);
const
handle
=
create
Safe
Handler
(
logger
);
const
handle
=
create
Logged
Handler
(
logger
);
export
function
registerVersionHandlers
()
{
export
function
registerVersionHandlers
()
{
handle
(
"list-versions"
,
async
(
_
,
{
appId
}:
{
appId
:
number
})
=>
{
handle
(
"list-versions"
,
async
(
_
,
{
appId
}:
{
appId
:
number
})
=>
{
...
...
src/ipc/ipc_client.ts
浏览文件 @
c71638a5
差异被折叠。
点击展开。
src/ipc/ipc_types.ts
浏览文件 @
c71638a5
...
@@ -111,7 +111,6 @@ export interface LocalModel {
...
@@ -111,7 +111,6 @@ export interface LocalModel {
export
type
LocalModelListResponse
=
{
export
type
LocalModelListResponse
=
{
models
:
LocalModel
[];
models
:
LocalModel
[];
error
:
string
|
null
;
};
};
export
interface
TokenCountParams
{
export
interface
TokenCountParams
{
...
...
src/pages/app-details.tsx
浏览文件 @
c71638a5
...
@@ -28,6 +28,7 @@ import {
...
@@ -28,6 +28,7 @@ import {
}
from
"@/components/ui/dialog"
;
}
from
"@/components/ui/dialog"
;
import
{
GitHubConnector
}
from
"@/components/GitHubConnector"
;
import
{
GitHubConnector
}
from
"@/components/GitHubConnector"
;
import
{
SupabaseConnector
}
from
"@/components/SupabaseConnector"
;
import
{
SupabaseConnector
}
from
"@/components/SupabaseConnector"
;
import
{
showError
}
from
"@/lib/toast"
;
export
default
function
AppDetailsPage
()
{
export
default
function
AppDetailsPage
()
{
const
navigate
=
useNavigate
();
const
navigate
=
useNavigate
();
...
@@ -62,7 +63,7 @@ export default function AppDetailsPage() {
...
@@ -62,7 +63,7 @@ export default function AppDetailsPage() {
await
refreshApps
();
await
refreshApps
();
navigate
({
to
:
"/"
,
search
:
{}
});
navigate
({
to
:
"/"
,
search
:
{}
});
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"Failed to delete app:"
,
error
);
showError
(
error
);
}
finally
{
}
finally
{
setIsDeleting
(
false
);
setIsDeleting
(
false
);
}
}
...
...
src/pages/settings.tsx
浏览文件 @
c71638a5
...
@@ -27,12 +27,8 @@ export default function SettingsPage() {
...
@@ -27,12 +27,8 @@ export default function SettingsPage() {
setIsResetting
(
true
);
setIsResetting
(
true
);
try
{
try
{
const
ipcClient
=
IpcClient
.
getInstance
();
const
ipcClient
=
IpcClient
.
getInstance
();
const
result
=
await
ipcClient
.
resetAll
();
await
ipcClient
.
resetAll
();
if
(
result
.
success
)
{
showSuccess
(
"Successfully reset everything. Restart the application."
);
showSuccess
(
"Successfully reset everything. Restart the application."
);
}
else
{
showError
(
result
.
message
||
"Failed to reset everything."
);
}
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
"Error resetting:"
,
error
);
console
.
error
(
"Error resetting:"
,
error
);
showError
(
showError
(
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论