Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
4a158417
提交
4a158417
authored
4月 18, 2025
作者:
Will Chen
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Parse proposal from latest chat message (w/o security parts)
上级
4e0f93d2
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
356 行增加
和
68 行删除
+356
-68
0001_hesitant_roland_deschain.sql
drizzle/0001_hesitant_roland_deschain.sql
+2
-0
0001_snapshot.json
drizzle/meta/0001_snapshot.json
+200
-0
_journal.json
drizzle/meta/_journal.json
+8
-0
ChatInput.tsx
src/components/chat/ChatInput.tsx
+27
-25
schema.ts
src/db/schema.ts
+1
-1
proposal_handlers.ts
src/ipc/handlers/proposal_handlers.ts
+75
-32
response_processor.ts
src/ipc/processors/response_processor.ts
+43
-10
没有找到文件。
drizzle/0001_hesitant_roland_deschain.sql
0 → 100644
浏览文件 @
4a158417
ALTER
TABLE
`messages`
ADD
`approval_state`
text
;
\ No newline at end of file
drizzle/meta/0001_snapshot.json
0 → 100644
浏览文件 @
4a158417
{
"version"
:
"6"
,
"dialect"
:
"sqlite"
,
"id"
:
"0803dac6-46b8-4e22-8397-4840e614d6c9"
,
"prevId"
:
"1a0ffcb3-606d-4b03-81b7-7c585555a548"
,
"tables"
:
{
"apps"
:
{
"name"
:
"apps"
,
"columns"
:
{
"id"
:
{
"name"
:
"id"
,
"type"
:
"integer"
,
"primaryKey"
:
true
,
"notNull"
:
true
,
"autoincrement"
:
true
},
"name"
:
{
"name"
:
"name"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
},
"path"
:
{
"name"
:
"path"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
},
"created_at"
:
{
"name"
:
"created_at"
,
"type"
:
"integer"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
,
"default"
:
"(unixepoch())"
},
"updated_at"
:
{
"name"
:
"updated_at"
,
"type"
:
"integer"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
,
"default"
:
"(unixepoch())"
},
"github_org"
:
{
"name"
:
"github_org"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
false
,
"autoincrement"
:
false
},
"github_repo"
:
{
"name"
:
"github_repo"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
false
,
"autoincrement"
:
false
}
},
"indexes"
:
{},
"foreignKeys"
:
{},
"compositePrimaryKeys"
:
{},
"uniqueConstraints"
:
{},
"checkConstraints"
:
{}
},
"chats"
:
{
"name"
:
"chats"
,
"columns"
:
{
"id"
:
{
"name"
:
"id"
,
"type"
:
"integer"
,
"primaryKey"
:
true
,
"notNull"
:
true
,
"autoincrement"
:
true
},
"app_id"
:
{
"name"
:
"app_id"
,
"type"
:
"integer"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
},
"title"
:
{
"name"
:
"title"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
false
,
"autoincrement"
:
false
},
"created_at"
:
{
"name"
:
"created_at"
,
"type"
:
"integer"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
,
"default"
:
"(unixepoch())"
}
},
"indexes"
:
{},
"foreignKeys"
:
{
"chats_app_id_apps_id_fk"
:
{
"name"
:
"chats_app_id_apps_id_fk"
,
"tableFrom"
:
"chats"
,
"tableTo"
:
"apps"
,
"columnsFrom"
:
[
"app_id"
],
"columnsTo"
:
[
"id"
],
"onDelete"
:
"cascade"
,
"onUpdate"
:
"no action"
}
},
"compositePrimaryKeys"
:
{},
"uniqueConstraints"
:
{},
"checkConstraints"
:
{}
},
"messages"
:
{
"name"
:
"messages"
,
"columns"
:
{
"id"
:
{
"name"
:
"id"
,
"type"
:
"integer"
,
"primaryKey"
:
true
,
"notNull"
:
true
,
"autoincrement"
:
true
},
"chat_id"
:
{
"name"
:
"chat_id"
,
"type"
:
"integer"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
},
"role"
:
{
"name"
:
"role"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
},
"content"
:
{
"name"
:
"content"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
},
"approval_state"
:
{
"name"
:
"approval_state"
,
"type"
:
"text"
,
"primaryKey"
:
false
,
"notNull"
:
false
,
"autoincrement"
:
false
},
"created_at"
:
{
"name"
:
"created_at"
,
"type"
:
"integer"
,
"primaryKey"
:
false
,
"notNull"
:
true
,
"autoincrement"
:
false
,
"default"
:
"(unixepoch())"
}
},
"indexes"
:
{},
"foreignKeys"
:
{
"messages_chat_id_chats_id_fk"
:
{
"name"
:
"messages_chat_id_chats_id_fk"
,
"tableFrom"
:
"messages"
,
"tableTo"
:
"chats"
,
"columnsFrom"
:
[
"chat_id"
],
"columnsTo"
:
[
"id"
],
"onDelete"
:
"cascade"
,
"onUpdate"
:
"no action"
}
},
"compositePrimaryKeys"
:
{},
"uniqueConstraints"
:
{},
"checkConstraints"
:
{}
}
},
"views"
:
{},
"enums"
:
{},
"_meta"
:
{
"schemas"
:
{},
"tables"
:
{},
"columns"
:
{}
},
"internal"
:
{
"indexes"
:
{}
}
}
\ No newline at end of file
drizzle/meta/_journal.json
浏览文件 @
4a158417
...
@@ -8,6 +8,13 @@
...
@@ -8,6 +8,13 @@
"when"
:
1744692127560
,
"when"
:
1744692127560
,
"tag"
:
"0000_nebulous_proemial_gods"
,
"tag"
:
"0000_nebulous_proemial_gods"
,
"breakpoints"
:
true
"breakpoints"
:
true
},
{
"idx"
:
1
,
"version"
:
"6"
,
"when"
:
1744999922420
,
"tag"
:
"0001_hesitant_roland_deschain"
,
"breakpoints"
:
true
}
}
]
]
}
}
\ No newline at end of file
src/components/chat/ChatInput.tsx
浏览文件 @
4a158417
...
@@ -220,7 +220,7 @@ function ChatInputActions({
...
@@ -220,7 +220,7 @@ function ChatInputActions({
)
:
(
)
:
(
<
ChevronDown
size=
{
16
}
className=
"mr-1"
/>
<
ChevronDown
size=
{
16
}
className=
"mr-1"
/>
)
}
)
}
Review:
{
proposal
.
title
}
{
proposal
.
title
}
</
button
>
</
button
>
{
proposal
.
securityRisks
.
length
>
0
&&
(
{
proposal
.
securityRisks
.
length
>
0
&&
(
<
span
className=
"bg-red-100 text-red-700 text-xs font-medium px-2 py-0.5 rounded-full"
>
<
span
className=
"bg-red-100 text-red-700 text-xs font-medium px-2 py-0.5 rounded-full"
>
...
@@ -269,30 +269,32 @@ function ChatInputActions({
...
@@ -269,30 +269,32 @@ function ChatInputActions({
{
isDetailsVisible
&&
(
{
isDetailsVisible
&&
(
<
div
className=
"p-3 border-t border-border bg-muted/50 text-sm"
>
<
div
className=
"p-3 border-t border-border bg-muted/50 text-sm"
>
<
div
className=
"mb-3"
>
{
!!
proposal
.
securityRisks
.
length
&&
(
<
h4
className=
"font-semibold mb-1"
>
Security Risks
</
h4
>
<
div
className=
"mb-3"
>
<
ul
className=
"space-y-1"
>
<
h4
className=
"font-semibold mb-1"
>
Security Risks
</
h4
>
{
proposal
.
securityRisks
.
map
((
risk
,
index
)
=>
(
<
ul
className=
"space-y-1"
>
<
li
key=
{
index
}
className=
"flex items-start space-x-2"
>
{
proposal
.
securityRisks
.
map
((
risk
,
index
)
=>
(
{
risk
.
type
===
"warning"
?
(
<
li
key=
{
index
}
className=
"flex items-start space-x-2"
>
<
AlertTriangle
{
risk
.
type
===
"warning"
?
(
size=
{
16
}
<
AlertTriangle
className=
"text-yellow-500 mt-0.5 flex-shrink-0"
size=
{
16
}
/>
className=
"text-yellow-500 mt-0.5 flex-shrink-0"
)
:
(
/>
<
AlertOctagon
)
:
(
size=
{
16
}
<
AlertOctagon
className=
"text-red-500 mt-0.5 flex-shrink-0"
size=
{
16
}
/>
className=
"text-red-500 mt-0.5 flex-shrink-0"
)
}
/>
<
div
>
)
}
<
span
className=
"font-medium"
>
{
risk
.
title
}
:
</
span
>
{
" "
}
<
div
>
<
span
>
{
risk
.
description
}
</
span
>
<
span
className=
"font-medium"
>
{
risk
.
title
}
:
</
span
>
{
" "
}
</
div
>
<
span
>
{
risk
.
description
}
</
span
>
</
li
>
</
div
>
))
}
</
li
>
</
ul
>
))
}
</
div
>
</
ul
>
</
div
>
)
}
<
div
>
<
div
>
<
h4
className=
"font-semibold mb-1"
>
Files Changed
</
h4
>
<
h4
className=
"font-semibold mb-1"
>
Files Changed
</
h4
>
...
...
src/db/schema.ts
浏览文件 @
4a158417
...
@@ -35,7 +35,7 @@ export const messages = sqliteTable("messages", {
...
@@ -35,7 +35,7 @@ export const messages = sqliteTable("messages", {
role
:
text
(
"role"
,
{
enum
:
[
"user"
,
"assistant"
]
}).
notNull
(),
role
:
text
(
"role"
,
{
enum
:
[
"user"
,
"assistant"
]
}).
notNull
(),
content
:
text
(
"content"
).
notNull
(),
content
:
text
(
"content"
).
notNull
(),
approvalState
:
text
(
"approval_state"
,
{
approvalState
:
text
(
"approval_state"
,
{
enum
:
[
"approved"
,
"rejected"
,
"pending"
],
enum
:
[
"approved"
,
"rejected"
],
}),
}),
createdAt
:
integer
(
"created_at"
,
{
mode
:
"timestamp"
})
createdAt
:
integer
(
"created_at"
,
{
mode
:
"timestamp"
})
.
notNull
()
.
notNull
()
...
...
src/ipc/handlers/proposal_handlers.ts
浏览文件 @
4a158417
import
{
ipcMain
,
type
IpcMainInvokeEvent
}
from
"electron"
;
import
{
ipcMain
,
type
IpcMainInvokeEvent
}
from
"electron"
;
import
type
{
Proposal
}
from
"@/lib/schemas"
;
import
type
{
Proposal
}
from
"@/lib/schemas"
;
import
{
db
}
from
"../../db"
;
import
{
messages
}
from
"../../db/schema"
;
import
{
desc
,
eq
,
and
}
from
"drizzle-orm"
;
import
path
from
"node:path"
;
// Import path for basename
// Import tag parsers
import
{
getDyadChatSummaryTag
,
getDyadWriteTags
,
}
from
"../processors/response_processor"
;
// Placeholder Proposal data
// Placeholder Proposal data (can be removed or kept for reference)
const
placeholderProposal
:
Proposal
=
{
// const placeholderProposal: Proposal = { ... };
title
:
"Review: Example Refactoring (from IPC)"
,
securityRisks
:
[
// Type guard for the parsed proposal structure
{
interface
ParsedProposal
{
type
:
"warning"
,
title
:
string
;
title
:
"Potential XSS Vulnerability"
,
files
:
string
[];
description
:
"User input is directly rendered without sanitization."
,
}
},
{
function
isParsedProposal
(
obj
:
any
):
obj
is
ParsedProposal
{
type
:
"danger"
,
return
(
title
:
"Hardcoded API Key"
,
obj
&&
description
:
"API key found in plain text in configuration file."
,
typeof
obj
===
"object"
&&
},
typeof
obj
.
title
===
"string"
&&
],
Array
.
isArray
(
obj
.
files
)
&&
filesChanged
:
[
obj
.
files
.
every
((
file
:
any
)
=>
typeof
file
===
"string"
)
{
);
name
:
"ChatInput.tsx"
,
}
path
:
"src/components/chat/ChatInput.tsx"
,
summary
:
"Added review actions and details section."
,
},
{
name
:
"api.ts"
,
path
:
"src/lib/api.ts"
,
summary
:
"Refactored API call structure."
,
},
],
};
const
getProposalHandler
=
async
(
const
getProposalHandler
=
async
(
_event
:
IpcMainInvokeEvent
,
_event
:
IpcMainInvokeEvent
,
{
chatId
}:
{
chatId
:
number
}
{
chatId
}:
{
chatId
:
number
}
):
Promise
<
Proposal
>
=>
{
):
Promise
<
Proposal
|
null
>
=>
{
console
.
log
(
`IPC: get-proposal called for chatId:
${
chatId
}
`
);
console
.
log
(
`IPC: get-proposal called for chatId:
${
chatId
}
`
);
// Simulate async operation
await
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
500
));
// 500ms delay
try
{
return
placeholderProposal
;
// Find the latest ASSISTANT message for the chat
const
latestAssistantMessage
=
await
db
.
query
.
messages
.
findFirst
({
where
:
and
(
eq
(
messages
.
chatId
,
chatId
),
eq
(
messages
.
role
,
"assistant"
)),
orderBy
:
[
desc
(
messages
.
createdAt
)],
columns
:
{
content
:
true
,
// Fetch the content to parse
},
});
if
(
latestAssistantMessage
?.
content
)
{
console
.
log
(
"Found latest assistant message, parsing content..."
);
const
messageContent
=
latestAssistantMessage
.
content
;
// Parse tags directly from message content
const
proposalTitle
=
getDyadChatSummaryTag
(
messageContent
);
const
proposalFiles
=
getDyadWriteTags
(
messageContent
);
// Gets { path: string, content: string }[]
// Check if we have enough information to create a proposal
if
(
proposalTitle
||
proposalFiles
.
length
>
0
)
{
const
proposal
:
Proposal
=
{
// Use parsed title or a default title if summary tag is missing but write tags exist
title
:
proposalTitle
??
"Proposed File Changes"
,
securityRisks
:
[],
// Keep empty
filesChanged
:
proposalFiles
.
map
((
tag
)
=>
({
name
:
path
.
basename
(
tag
.
path
),
path
:
tag
.
path
,
summary
:
tag
.
description
??
"(no change summary found)"
,
// Generic summary
})),
};
console
.
log
(
"Generated proposal on the fly:"
,
proposal
);
return
proposal
;
}
else
{
console
.
log
(
"No relevant tags found in the latest assistant message content."
);
return
null
;
// No proposal could be generated
}
}
else
{
console
.
log
(
`No assistant message found for chatId:
${
chatId
}
`
);
return
null
;
// No message found
}
}
catch
(
error
)
{
console
.
error
(
`Error processing proposal for chatId
${
chatId
}
:`
,
error
);
return
null
;
// Indicate DB or processing error
}
};
};
// Function to register proposal-related handlers
// Function to register proposal-related handlers
...
...
src/ipc/processors/response_processor.ts
浏览文件 @
4a158417
...
@@ -11,20 +11,42 @@ import { getGitAuthor } from "../utils/git_author";
...
@@ -11,20 +11,42 @@ import { getGitAuthor } from "../utils/git_author";
export
function
getDyadWriteTags
(
fullResponse
:
string
):
{
export
function
getDyadWriteTags
(
fullResponse
:
string
):
{
path
:
string
;
path
:
string
;
content
:
string
;
content
:
string
;
description
?:
string
;
}[]
{
}[]
{
const
dyadWriteRegex
=
const
dyadWriteRegex
=
/<dyad-write
([^
>
]
*
)
>
([\s\S]
*
?)
<
\/
dyad-write>/gi
;
/<dyad-write path="
([^
"
]
+
)
"
[^
>
]
*>
([\s\S]
*
?)
<
\/
dyad-write>/g
;
const
pathRegex
=
/path="
([^
"
]
+
)
"/
;
const
descriptionRegex
=
/description="
([^
"
]
+
)
"/
;
let
match
;
let
match
;
const
tags
:
{
path
:
string
;
content
:
string
}[]
=
[];
const
tags
:
{
path
:
string
;
content
:
string
;
description
?:
string
}[]
=
[];
while
((
match
=
dyadWriteRegex
.
exec
(
fullResponse
))
!==
null
)
{
while
((
match
=
dyadWriteRegex
.
exec
(
fullResponse
))
!==
null
)
{
const
content
=
match
[
2
].
trim
().
split
(
"
\
n"
);
const
attributesString
=
match
[
1
];
if
(
content
[
0
].
startsWith
(
"```"
))
{
let
content
=
match
[
2
].
trim
();
content
.
shift
();
}
const
pathMatch
=
pathRegex
.
exec
(
attributesString
);
if
(
content
[
content
.
length
-
1
].
startsWith
(
"```"
))
{
const
descriptionMatch
=
descriptionRegex
.
exec
(
attributesString
);
content
.
pop
();
if
(
pathMatch
&&
pathMatch
[
1
])
{
const
path
=
pathMatch
[
1
];
const
description
=
descriptionMatch
?.[
1
];
const
contentLines
=
content
.
split
(
"
\
n"
);
if
(
contentLines
[
0
]?.
startsWith
(
"```"
))
{
contentLines
.
shift
();
}
if
(
contentLines
[
contentLines
.
length
-
1
]?.
startsWith
(
"```"
))
{
contentLines
.
pop
();
}
content
=
contentLines
.
join
(
"
\
n"
);
tags
.
push
({
path
,
content
,
description
});
}
else
{
console
.
warn
(
"Found <dyad-write> tag without a valid 'path' attribute:"
,
match
[
0
]
);
}
}
tags
.
push
({
path
:
match
[
1
],
content
:
content
.
join
(
"
\
n"
)
});
}
}
return
tags
;
return
tags
;
}
}
...
@@ -65,6 +87,16 @@ export function getDyadAddDependencyTags(fullResponse: string): string[] {
...
@@ -65,6 +87,16 @@ export function getDyadAddDependencyTags(fullResponse: string): string[] {
return
packages
;
return
packages
;
}
}
export
function
getDyadChatSummaryTag
(
fullResponse
:
string
):
string
|
null
{
const
dyadChatSummaryRegex
=
/<dyad-chat-summary>
([\s\S]
*
?)
<
\/
dyad-chat-summary>/g
;
const
match
=
dyadChatSummaryRegex
.
exec
(
fullResponse
);
if
(
match
&&
match
[
1
])
{
return
match
[
1
].
trim
();
}
return
null
;
}
export
async
function
processFullResponseActions
(
export
async
function
processFullResponseActions
(
fullResponse
:
string
,
fullResponse
:
string
,
chatId
:
number
,
chatId
:
number
,
...
@@ -206,6 +238,7 @@ export async function processFullResponseActions(
...
@@ -206,6 +238,7 @@ export async function processFullResponseActions(
if
(
deletedFiles
.
length
>
0
)
if
(
deletedFiles
.
length
>
0
)
changes
.
push
(
`deleted
${
deletedFiles
.
length
}
file(s)`
);
changes
.
push
(
`deleted
${
deletedFiles
.
length
}
file(s)`
);
// Use chat summary, if provided, or default for commit message
await
git
.
commit
({
await
git
.
commit
({
fs
,
fs
,
dir
:
appPath
,
dir
:
appPath
,
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论