496e3ba71e
Deploy PR-Helper / deploy (push) Successful in 30s
服务端: - prompt 改为要求直接输出 Markdown,不再输出 JSON - GeneratePR 返回 string 而非 PRDescription 结构体 - 移除 JSON 解析、extractJSON、fixInlineCode 等逻辑 - ChatStream 的 content 事件直接携带 Markdown 片段流式传输 前端: - generate() 仅处理 content + done + error 三个事件 - content 事件累积 Markdown 文本,实时渲染到两栏 - 移除 parsePartialJSON、buildMarkdown 等中间层 - 代码量从 ~100 行减至 ~50 行
134 lines
3.3 KiB
Go
134 lines
3.3 KiB
Go
package services
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
goopenai "github.com/sashabaranov/go-openai"
|
|
)
|
|
|
|
// GeneratePR generates a PR description as Markdown from commit history and diff.
|
|
// It streams the LLM output via callback ("content" events) and returns the full markdown.
|
|
func GeneratePR(db *sql.DB, repoPath, base, head string, userID int64, callback StreamCallback) (string, error) {
|
|
// Read LLM config (per-user)
|
|
config, err := GetLLMConfig(db, userID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Open repo
|
|
repo, err := OpenRepo(repoPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("open repo: %w", err)
|
|
}
|
|
|
|
// Get commits between base and head
|
|
commits, err := GetCommitLog(repo, head, 100)
|
|
if err != nil {
|
|
return "", fmt.Errorf("get commits: %w", err)
|
|
}
|
|
|
|
// Filter commits to only those reachable from head but not from base
|
|
baseHash, err := repo.ResolveRevision(plumbing.Revision(base))
|
|
if err == nil {
|
|
baseCommit, bcErr := repo.CommitObject(*baseHash)
|
|
if bcErr == nil {
|
|
baseSet := make(map[string]bool)
|
|
baseQueue := []*object.Commit{baseCommit}
|
|
for len(baseQueue) > 0 {
|
|
c := baseQueue[0]
|
|
baseQueue = baseQueue[1:]
|
|
if baseSet[c.Hash.String()] {
|
|
continue
|
|
}
|
|
baseSet[c.Hash.String()] = true
|
|
for _, p := range c.ParentHashes {
|
|
pc, err := repo.CommitObject(p)
|
|
if err == nil {
|
|
baseQueue = append(baseQueue, pc)
|
|
}
|
|
}
|
|
}
|
|
var filtered []CommitInfo
|
|
for _, ci := range commits {
|
|
if !baseSet[ci.Hash] {
|
|
filtered = append(filtered, ci)
|
|
}
|
|
}
|
|
if len(filtered) > 0 {
|
|
commits = filtered
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get diff
|
|
diff, err := GetDiff(repo, base, head)
|
|
if err != nil {
|
|
return "", fmt.Errorf("get diff: %w", err)
|
|
}
|
|
|
|
// Build prompt
|
|
var commitLines []string
|
|
for _, c := range commits {
|
|
commitLines = append(commitLines, fmt.Sprintf("- %s %s", c.ShortHash, c.Message))
|
|
}
|
|
|
|
commitStr := strings.Join(commitLines, "\n")
|
|
if commitStr == "" {
|
|
commitStr = "(no commits)"
|
|
}
|
|
|
|
// Truncate diff if too large (approx 60k chars to stay within token limits)
|
|
if len(diff) > 60000 {
|
|
diff = diff[:60000] + "\n\n... [diff truncated due to size]"
|
|
}
|
|
|
|
prompt := fmt.Sprintf(`你是一个专业的技术文档撰写助手。根据以下 Git 变更信息,生成一份 PR 描述。
|
|
|
|
## Commit 记录
|
|
%s
|
|
|
|
## 代码变更 (Diff)
|
|
%s
|
|
|
|
请直接输出 Markdown 格式的 PR 描述,包含以下部分:
|
|
|
|
# 标题
|
|
|
|
**类型**: feat|fix|refactor|docs|chore|style|test|perf
|
|
|
|
## 概述
|
|
一段话概述变更内容
|
|
|
|
## 详细说明
|
|
按模块分组的详细变更说明
|
|
|
|
## 影响范围
|
|
影响范围说明
|
|
|
|
格式要求:
|
|
- 引用文件名、函数名、变量名等代码标识时,必须用反引号包裹
|
|
- 直接写出实际的代码名称,不要用任何占位符替代
|
|
- 不要输出 JSON,直接输出 Markdown`, commitStr, diff)
|
|
|
|
messages := []goopenai.ChatCompletionMessage{
|
|
{Role: goopenai.ChatMessageRoleUser, Content: prompt},
|
|
}
|
|
|
|
// Call LLM with streaming — content events carry markdown chunks in real-time
|
|
fullResponse, err := ChatStream(config, messages, callback)
|
|
if err != nil {
|
|
return "", fmt.Errorf("LLM call: %w", err)
|
|
}
|
|
|
|
// Signal completion
|
|
if callback != nil {
|
|
callback("done", map[string]interface{}{"content": ""})
|
|
}
|
|
|
|
return fullResponse, nil
|
|
}
|