Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@ docker:
# Code provider configuration
code_provider: claude # Options: claude, gemini
use_docker: true # Whether to use Docker, false means use local CLI

# AI mention configuration
mention:
# List of mention triggers that will activate AI processing
# Supports multiple triggers for different use cases
triggers:
- "@qiniu-ci" # Default company AI assistant
- "@qiniu-ai" # another company AI assistant
# Default trigger (used when no specific trigger list is provided)
default_trigger: "@qiniu-ci"
17 changes: 17 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Config struct {

// v0.6 Configuration
Commands CommandsConfig `yaml:"commands"`
// AI Mention Configuration
Mention MentionConfig `yaml:"mention"`
}

type GeminiConfig struct {
Expand Down Expand Up @@ -73,6 +75,13 @@ type CommandsConfig struct {
GlobalPath string `yaml:"global_path"`
}

type MentionConfig struct {
// 可配置的mention目标,支持多个
Triggers []string `yaml:"triggers"`
// 默认的mention目标(向后兼容)
DefaultTrigger string `yaml:"default_trigger"`
}

func Load(configPath string) (*Config, error) {
// 首先尝试从文件加载
if _, err := os.Stat(configPath); err == nil {
Expand Down Expand Up @@ -168,6 +177,10 @@ func (c *Config) loadFromEnv() {
if globalPath := os.Getenv("GLOBAL_COMMANDS_PATH"); globalPath != "" {
c.Commands.GlobalPath = globalPath
}
// Mention configuration from environment
if mentionTrigger := os.Getenv("MENTION_TRIGGER"); mentionTrigger != "" {
c.Mention.DefaultTrigger = mentionTrigger
}
}

func loadFromEnv() *Config {
Expand Down Expand Up @@ -213,6 +226,10 @@ func loadFromEnv() *Config {
Commands: CommandsConfig{
GlobalPath: os.Getenv("GLOBAL_COMMANDS_PATH"),
},
Mention: MentionConfig{
Triggers: []string{getEnvOrDefault("MENTION_TRIGGER", "@qiniu-ci")},
DefaultTrigger: getEnvOrDefault("MENTION_TRIGGER", "@qiniu-ci"),
},
CodeProvider: getEnvOrDefault("CODE_PROVIDER", "claude"),
UseDocker: getEnvBoolOrDefault("USE_DOCKER", true),
}
Expand Down
91 changes: 68 additions & 23 deletions pkg/models/events.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package models

import (
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -156,8 +157,33 @@ const (
AIModelGemini = "gemini"
)

// HasCommand 检查上下文是否包含命令
// MentionConfig 提及配置接口
type MentionConfig interface {
GetTriggers() []string
GetDefaultTrigger() string
}

// ConfigMentionAdapter 从内部config包适配到models包
type ConfigMentionAdapter struct {
Triggers []string
DefaultTrigger string
}

func (c *ConfigMentionAdapter) GetTriggers() []string {
return c.Triggers
}

func (c *ConfigMentionAdapter) GetDefaultTrigger() string {
return c.DefaultTrigger
}

// HasCommand 检查上下文是否包含命令(使用默认mention配置)
func HasCommand(ctx GitHubContext) (*CommandInfo, bool) {
return HasCommandWithConfig(ctx, nil)
}

// HasCommandWithConfig 检查上下文是否包含命令(支持自定义mention配置)
func HasCommandWithConfig(ctx GitHubContext, mentionConfig MentionConfig) (*CommandInfo, bool) {
var content string

switch c := ctx.(type) {
Expand Down Expand Up @@ -186,7 +212,11 @@ func HasCommand(ctx GitHubContext) (*CommandInfo, bool) {
return cmdInfo, true
}

// Then try to parse as @claude mention
// Then try to parse as mention with config
if mentionConfig != nil {
return parseMentionWithConfig(content, mentionConfig)
}
// Fallback to default mention parsing
return parseMention(content)
}

Expand Down Expand Up @@ -231,43 +261,58 @@ func parseCommand(content string) (*CommandInfo, bool) {
}, true
}

// parseMention 解析@qiniu-ci提及
// parseMention 解析@qiniu-ci提及(默认触发词,向后兼容)
func parseMention(content string) (*CommandInfo, bool) {
content = strings.TrimSpace(content)
return parseMentionWithTrigger(content, CommandClaude)
}

// 检查是否包含@qiniu-ci
if !strings.Contains(content, CommandClaude) {
return nil, false
// parseMentionWithConfig 使用配置解析mention
func parseMentionWithConfig(content string, config MentionConfig) (*CommandInfo, bool) {
triggers := config.GetTriggers()
if len(triggers) == 0 {
triggers = []string{config.GetDefaultTrigger()}
}

// 找到@qiniu-ci的位置
mentionIndex := strings.Index(content, CommandClaude)
if mentionIndex == -1 {
// 尝试所有配置的触发词
for _, trigger := range triggers {
if trigger == "" {
continue
}
if cmdInfo, found := parseMentionWithTrigger(content, trigger); found {
return cmdInfo, true
}
}

return nil, false
}

// parseMentionWithTrigger 使用指定触发词解析mention,传递完整评论内容
func parseMentionWithTrigger(content string, trigger string) (*CommandInfo, bool) {
content = strings.TrimSpace(content)
pattern := `(^|\s)` + regexp.QuoteMeta(trigger) + `([\s.,!?;:]|$)`
re := regexp.MustCompile(pattern)

match := re.FindStringSubmatch(content)
if match == nil {
return nil, false
}

// 提取@qiniu-ci之后的内容作为参数
afterMention := strings.TrimSpace(content[mentionIndex+len(CommandClaude):])
// NOTE(CarlJin): mention 模式传递暂时完整评论内容
fullContent := content

// 解析AI模型和参数(类似于parseCommand的逻辑)
var aiModel string
var args string

if strings.HasPrefix(afterMention, "-claude") {
// 查找模型指定标志
if strings.Contains(fullContent, "-claude") {
aiModel = AIModelClaude
args = strings.TrimSpace(strings.TrimPrefix(afterMention, "-claude"))
} else if strings.HasPrefix(afterMention, "-gemini") {
} else if strings.Contains(fullContent, "-gemini") {
aiModel = AIModelGemini
args = strings.TrimSpace(strings.TrimPrefix(afterMention, "-gemini"))
} else {
aiModel = ""
args = afterMention
}

return &CommandInfo{
Command: CommandClaude,
Command: CommandClaude, // 总是使用CommandClaude作为mention的标识
AIModel: aiModel,
Args: args,
Args: fullContent, // 传递完整评论内容
RawText: content,
}, true
}
Expand Down
Loading
Loading