2
0

feat(pages): add AI-powered landing page content generator

Enable automatic landing page content generation using AI:
- Generate hero, features, stats, and CTAs from README
- Blog section configuration in settings UI
- Extract repository metadata for AI context
- Merge generated content while preserving existing settings
- User-friendly generation button in settings panel
This commit is contained in:
2026-03-07 12:47:28 -05:00
parent 3a8bdd936c
commit a2edcdabe7
5 changed files with 289 additions and 0 deletions

View File

@@ -9,7 +9,10 @@ import (
"strings"
repo_model "code.gitcaddy.com/server/v3/models/repo"
"code.gitcaddy.com/server/v3/modules/ai"
"code.gitcaddy.com/server/v3/modules/git"
"code.gitcaddy.com/server/v3/modules/json"
"code.gitcaddy.com/server/v3/modules/log"
pages_module "code.gitcaddy.com/server/v3/modules/pages"
"code.gitcaddy.com/server/v3/modules/templates"
"code.gitcaddy.com/server/v3/services/context"
@@ -84,6 +87,7 @@ func setCommonPagesData(ctx *context.Context) {
ctx.Data["PagesURL"] = pages_service.GetPagesURL(ctx.Repo.Repository)
ctx.Data["PagesTemplates"] = pages_module.ValidTemplates()
ctx.Data["PagesTemplateNames"] = pages_module.TemplateDisplayNames()
ctx.Data["AIEnabled"] = ai.IsEnabled()
}
// Pages shows the repository pages settings (General page)
@@ -193,6 +197,30 @@ func PagesPost(ctx *context.Context) {
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.pages.domain_verified"))
}
case "ai_generate":
readme := loadRawReadme(ctx, ctx.Repo.Repository)
generated, err := pages_service.GenerateLandingPageContent(ctx, ctx.Repo.Repository, readme)
if err != nil {
log.Error("AI landing page generation failed: %v", err)
ctx.Flash.Error(ctx.Tr("repo.settings.pages.ai_generate_failed"))
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages")
return
}
// Merge AI-generated content into existing config, preserving settings
config := getPagesLandingConfig(ctx)
config.Brand.Name = generated.Brand.Name
config.Brand.Tagline = generated.Brand.Tagline
config.Hero = generated.Hero
config.Stats = generated.Stats
config.ValueProps = generated.ValueProps
config.Features = generated.Features
config.CTASection = generated.CTASection
config.SEO = generated.SEO
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("SavePagesConfig", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.ai_generate_success"))
default:
ctx.NotFound(nil)
return
@@ -267,6 +295,12 @@ func PagesContentPost(ctx *context.Context) {
config.Navigation.ShowRepository = ctx.FormBool("nav_show_repository")
config.Navigation.ShowReleases = ctx.FormBool("nav_show_releases")
config.Navigation.ShowIssues = ctx.FormBool("nav_show_issues")
config.Blog.Enabled = ctx.FormBool("blog_enabled")
config.Blog.Headline = ctx.FormString("blog_headline")
config.Blog.Subheadline = ctx.FormString("blog_subheadline")
if maxPosts := ctx.FormInt("blog_max_posts"); maxPosts > 0 {
config.Blog.MaxPosts = maxPosts
}
config.Stats = nil
for i := range 10 {
value := ctx.FormString(fmt.Sprintf("stat_value_%d", i))
@@ -454,3 +488,38 @@ func PagesThemePost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.pages.saved"))
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/theme")
}
// loadRawReadme loads the raw README content from the repository for AI consumption
func loadRawReadme(ctx *context.Context, repo *repo_model.Repository) string {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
return ""
}
defer gitRepo.Close()
branch := repo.DefaultBranch
if branch == "" {
branch = "main"
}
commit, err := gitRepo.GetBranchCommit(branch)
if err != nil {
return ""
}
for _, name := range []string{"README.md", "readme.md", "README", "README.txt"} {
entry, err := commit.GetTreeEntryByPath(name)
if err != nil {
continue
}
reader, err := entry.Blob().DataAsync()
if err != nil {
continue
}
content := make([]byte, entry.Blob().Size())
_, _ = reader.Read(content)
reader.Close()
return string(content)
}
return ""
}