diff --git a/backend/internal/svc/servicecontext.go b/backend/internal/svc/servicecontext.go index 2e1cdbc..a4f32ab 100644 --- a/backend/internal/svc/servicecontext.go +++ b/backend/internal/svc/servicecontext.go @@ -91,6 +91,10 @@ func NewServiceContext(c config.Config) *ServiceContext { seedMenus(db) seedRoleMenus(db) + // 种子 AI 供应商和模型 + seedAIProviders(db) + seedAIModels(db) + // 初始化存储 store, err := storage.NewStorage(c.Storage) if err != nil { @@ -261,6 +265,16 @@ func seedCasbinPolicies(enforcer *casbin.Enforcer) { // super_admin: 机构删除 {"super_admin", "/api/v1/organization/:id", "DELETE"}, + + // AI: all authenticated users + {"user", "/api/v1/ai/chat/completions", "POST"}, + {"user", "/api/v1/ai/conversations", "GET"}, + {"user", "/api/v1/ai/conversation", "POST"}, + {"user", "/api/v1/ai/conversation/:id", "GET"}, + {"user", "/api/v1/ai/conversation/:id", "PUT"}, + {"user", "/api/v1/ai/conversation/:id", "DELETE"}, + {"user", "/api/v1/ai/models", "GET"}, + {"user", "/api/v1/ai/quota/me", "GET"}, } for _, p := range policies { @@ -298,10 +312,11 @@ func seedMenus(db *gorm.DB) { {Name: "仪表盘", Path: "/dashboard", Icon: "LayoutDashboard", Type: "config", SortOrder: 2, Visible: true, Status: 1}, {Name: "用户管理", Path: "/users", Icon: "Users", Type: "config", SortOrder: 3, Visible: true, Status: 1}, {Name: "文件管理", Path: "/files", Icon: "FolderOpen", Type: "config", SortOrder: 4, Visible: true, Status: 1}, - {Name: "角色管理", Path: "/roles", Icon: "Shield", Type: "config", SortOrder: 5, Visible: true, Status: 1}, - {Name: "菜单管理", Path: "/menus", Icon: "Menu", Type: "config", SortOrder: 6, Visible: true, Status: 1}, - {Name: "机构管理", Path: "/organizations", Icon: "Building2", Type: "config", SortOrder: 7, Visible: true, Status: 1}, - {Name: "设置", Path: "/settings", Icon: "Settings", Type: "default", SortOrder: 8, Visible: true, Status: 1}, + {Name: "AI 对话", Path: "/ai/chat", Icon: "Bot", Type: "config", SortOrder: 5, Visible: true, Status: 1}, + {Name: "角色管理", Path: "/roles", Icon: "Shield", Type: "config", SortOrder: 6, Visible: true, Status: 1}, + {Name: "菜单管理", Path: "/menus", Icon: "Menu", Type: "config", SortOrder: 7, Visible: true, Status: 1}, + {Name: "机构管理", Path: "/organizations", Icon: "Building2", Type: "config", SortOrder: 8, Visible: true, Status: 1}, + {Name: "设置", Path: "/settings", Icon: "Settings", Type: "default", SortOrder: 9, Visible: true, Status: 1}, } for _, m := range menus { @@ -363,3 +378,51 @@ func seedRoleMenus(db *gorm.DB) { } log.Println("[Seed] RoleMenus seeded successfully") } + +// seedAIProviders 种子 AI 供应商(幂等) +func seedAIProviders(db *gorm.DB) { + providers := []model.AIProvider{ + {Name: "openai", DisplayName: "OpenAI", BaseUrl: "https://api.openai.com/v1", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 1}, + {Name: "claude", DisplayName: "Anthropic Claude", BaseUrl: "https://api.anthropic.com", SdkType: "anthropic", Protocol: "anthropic", IsActive: true, SortOrder: 2}, + {Name: "qwen", DisplayName: "阿里千问", BaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 3}, + {Name: "zhipu", DisplayName: "智谱 GLM", BaseUrl: "https://open.bigmodel.cn/api/paas/v4", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 4}, + {Name: "deepseek", DisplayName: "DeepSeek", BaseUrl: "https://api.deepseek.com/v1", SdkType: "openai_compat", Protocol: "openai", IsActive: true, SortOrder: 5}, + } + for _, p := range providers { + var existing model.AIProvider + if err := db.Where("name = ?", p.Name).First(&existing).Error; err != nil { + db.Create(&p) + } + } + log.Println("[Seed] AI providers seeded successfully") +} + +// seedAIModels 种子 AI 模型(幂等) +func seedAIModels(db *gorm.DB) { + // First, fetch providers by name to get their IDs + providerIds := map[string]int64{} + var providers []model.AIProvider + db.Find(&providers) + for _, p := range providers { + providerIds[p.Name] = p.Id + } + + models := []model.AIModel{ + {ProviderId: providerIds["openai"], ModelId: "gpt-4o", DisplayName: "GPT-4o", InputPrice: 0.0025, OutputPrice: 0.01, MaxTokens: 16384, ContextWindow: 128000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 1}, + {ProviderId: providerIds["openai"], ModelId: "gpt-4o-mini", DisplayName: "GPT-4o Mini", InputPrice: 0.00015, OutputPrice: 0.0006, MaxTokens: 16384, ContextWindow: 128000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 2}, + {ProviderId: providerIds["claude"], ModelId: "claude-sonnet-4-5-20250514", DisplayName: "Claude Sonnet 4.5", InputPrice: 0.003, OutputPrice: 0.015, MaxTokens: 8192, ContextWindow: 200000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 3}, + {ProviderId: providerIds["claude"], ModelId: "claude-haiku-3-5-20241022", DisplayName: "Claude Haiku 3.5", InputPrice: 0.0008, OutputPrice: 0.004, MaxTokens: 8192, ContextWindow: 200000, SupportsStream: true, SupportsVision: true, IsActive: true, SortOrder: 4}, + {ProviderId: providerIds["qwen"], ModelId: "qwen-plus", DisplayName: "通义千问 Plus", InputPrice: 0.0008, OutputPrice: 0.002, MaxTokens: 8192, ContextWindow: 131072, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 5}, + {ProviderId: providerIds["qwen"], ModelId: "qwen-turbo", DisplayName: "通义千问 Turbo", InputPrice: 0.0003, OutputPrice: 0.0006, MaxTokens: 8192, ContextWindow: 131072, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 6}, + {ProviderId: providerIds["zhipu"], ModelId: "glm-4-flash", DisplayName: "GLM-4 Flash", InputPrice: 0.0001, OutputPrice: 0.0001, MaxTokens: 4096, ContextWindow: 128000, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 7}, + {ProviderId: providerIds["deepseek"], ModelId: "deepseek-chat", DisplayName: "DeepSeek Chat", InputPrice: 0.00014, OutputPrice: 0.00028, MaxTokens: 8192, ContextWindow: 64000, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 8}, + {ProviderId: providerIds["deepseek"], ModelId: "deepseek-reasoner", DisplayName: "DeepSeek Reasoner", InputPrice: 0.00055, OutputPrice: 0.00219, MaxTokens: 8192, ContextWindow: 64000, SupportsStream: true, SupportsVision: false, IsActive: true, SortOrder: 9}, + } + for _, m := range models { + var existing model.AIModel + if err := db.Where("model_id = ?", m.ModelId).First(&existing).Error; err != nil { + db.Create(&m) + } + } + log.Println("[Seed] AI models seeded successfully") +}