feat: 提交详情能看 diff,也能丢给 AI 审这一次改动。文件检查可单独排除路径,TODO 只认大写,避免把 todo 表和模块当成待办标记。

热力图按容器宽度铺满一年,不再横向滚动。托盘右键换成可换皮肤的弹层;更新下载显示进度,退出后再由脚本拉起安装器,避免还占着 exe。
This commit is contained in:
李琦
2026-08-19 15:46:03 +08:00
parent 1694397245
commit 7c4109f687
38 changed files with 2618 additions and 129 deletions

62
ai.go
View File

@@ -11,6 +11,7 @@ import (
"strings"
"time"
"view/service"
"view/service/ai"
)
@@ -145,6 +146,49 @@ type aiStreamEvent struct {
// SendAIMessage 发送一条用户消息并启动流式回复。
// conversationID 为 0 时自动创建会话scenario: chat | project | git | todo | ticket。
func (a *App) SendAIMessage(conversationID, projectID int64, scenario, content string) (AIConversation, error) {
return a.startAIChat(conversationID, projectID, scenario, content, "", "")
}
// SendAIDiffMessage 针对某次提交的 diff 发起 AI 分析;后续同会话追问仍带上该 diff。
func (a *App) SendAIDiffMessage(conversationID, projectID int64, hash, content string) (AIConversation, error) {
hash = strings.TrimSpace(hash)
if hash == "" {
return AIConversation{}, errors.New("GIT_REF_NOT_FOUND")
}
if e := a.ready(); e != nil {
return AIConversation{}, e
}
if projectID <= 0 && conversationID > 0 {
convs, _ := a.store.ListAIConversations(0)
for _, c := range convs {
if c.ID == conversationID {
projectID = c.ProjectID
break
}
}
}
p, e := a.store.GetProject(projectID)
if e != nil {
return AIConversation{}, e
}
d, e := (GitAnalyzer{}).CommitDetail(a.ctx, p.Path, hash)
if e != nil {
return AIConversation{}, e
}
title := strings.TrimSpace(d.Message)
if title == "" {
title = content
}
if r := []rune(title); len(r) > 36 {
title = string(r[:36]) + "…"
}
if !strings.HasPrefix(strings.ToLower(title), "diff") {
title = "Diff: " + title
}
return a.startAIChat(conversationID, projectID, "diff", content, service.FormatCommitDiffForAI(d, 0), title)
}
func (a *App) startAIChat(conversationID, projectID int64, scenario, content, extraSystem, title string) (AIConversation, error) {
if e := a.ready(); e != nil {
return AIConversation{}, e
}
@@ -166,7 +210,9 @@ func (a *App) SendAIMessage(conversationID, projectID int64, scenario, content s
var conv AIConversation
if conversationID == 0 {
title := content
if strings.TrimSpace(title) == "" {
title = content
}
if r := []rune(title); len(r) > 40 {
title = string(r[:40]) + "…"
}
@@ -197,9 +243,19 @@ func (a *App) SendAIMessage(conversationID, projectID int64, scenario, content s
a.aiStreams[conv.ID] = cancel
a.mu.Unlock()
// 组装消息:场景系统提示词 + 最近历史 + 本条用户消息。
sys := a.buildAIContext(projectID, scenario, st.Locale)
if extraSystem != "" {
sys = strings.TrimRight(sys, "\n") + "\n\n" + extraSystem
if scenario == "diff" {
if st.Locale == "en" {
sys += "\nTask: Review this commit's diff. Cover intent, risks, missing tests, and brief improvements. Use only the diff above; do not invent changes.\n"
} else {
sys += "\n任务审查这次提交的 diff说明改动意图、潜在风险、遗漏测试并给出简要改进建议。只依据上面的 diff不要编造未出现的改动。\n"
}
}
}
history, _ := a.store.ListAIMessages(conv.ID)
msgs := []ai.Message{{Role: "system", Content: a.buildAIContext(projectID, scenario, st.Locale)}}
msgs := []ai.Message{{Role: "system", Content: sys}}
if len(history) > 20 {
history = history[len(history)-20:]
}