2026-07-20 13:02:26 +08:00
package seed
import (
"strings"
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"senlinai-agent/backend/internal/models"
)
type DemoOptions struct {
Email string
DisplayName string
Password string
}
type DemoResult struct {
User models . SenlinAgentUser
Projects [ ] models . SenlinAgentProject
}
func Demo ( database * gorm . DB , options DemoOptions ) ( DemoResult , error ) {
email := strings . ToLower ( strings . TrimSpace ( options . Email ) )
if email == "" {
email = "demo@senlin.ai"
}
displayName := strings . TrimSpace ( options . DisplayName )
if displayName == "" {
displayName = "演示用户"
}
password := options . Password
if password == "" {
password = "password123"
}
var result DemoResult
err := database . Transaction ( func ( tx * gorm . DB ) error {
user , err := upsertUser ( tx , email , displayName , password )
if err != nil {
return err
}
result . User = user
projects , err := seedProjects ( tx , user . ID )
if err != nil {
return err
}
result . Projects = projects
return nil
} )
return result , err
}
func upsertUser ( tx * gorm . DB , email string , displayName string , password string ) ( models . SenlinAgentUser , error ) {
var user models . SenlinAgentUser
err := tx . Where ( "email = ?" , email ) . First ( & user ) . Error
if err == nil {
return user , nil
}
if err != gorm . ErrRecordNotFound {
return user , err
}
hash , err := bcrypt . GenerateFromPassword ( [ ] byte ( password ) , bcrypt . DefaultCost )
if err != nil {
return user , err
}
user = models . SenlinAgentUser {
Email : email ,
DisplayName : displayName ,
PasswordHash : string ( hash ) ,
Role : "user" ,
}
return user , tx . Create ( & user ) . Error
}
func seedProjects ( tx * gorm . DB , ownerID uint ) ( [ ] models . SenlinAgentProject , error ) {
definitions := [ ] struct {
Name string
2026-07-21 14:41:30 +08:00
Identifier string
2026-07-20 13:02:26 +08:00
Description string
} {
2026-07-21 14:41:30 +08:00
{ Name : "项目 A1" , Identifier : "A1" , Description : "企业智能协作平台的核心工作空间" } ,
{ Name : "项目 A2" , Identifier : "A2" , Description : "客户成功与交付跟踪" } ,
{ Name : "数据中台" , Identifier : "DATA" , Description : "内部数据治理与指标分析" } ,
{ Name : "运营自动化" , Identifier : "AUTO" , Description : "定时任务和业务流程自动化" } ,
2026-07-20 13:02:26 +08:00
}
projects := make ( [ ] models . SenlinAgentProject , 0 , len ( definitions ) )
for _ , definition := range definitions {
2026-07-21 14:41:30 +08:00
project , err := firstOrCreateProject ( tx , ownerID , definition . Name , definition . Identifier , definition . Description )
2026-07-20 13:02:26 +08:00
if err != nil {
return nil , err
}
projects = append ( projects , project )
}
if len ( projects ) == 0 {
return projects , nil
}
if err := seedProjectA1 ( tx , ownerID , projects [ 0 ] . ID ) ; err != nil {
return nil , err
}
for _ , project := range projects [ 1 : ] {
if err := seedCompactProject ( tx , ownerID , project . ID ) ; err != nil {
return nil , err
}
}
return projects , nil
}
2026-07-21 14:41:30 +08:00
func firstOrCreateProject ( tx * gorm . DB , ownerID uint , name string , identifier string , description string ) ( models . SenlinAgentProject , error ) {
2026-07-20 13:02:26 +08:00
var project models . SenlinAgentProject
err := tx . Where ( "owner_id = ? AND name = ?" , ownerID , name ) . First ( & project ) . Error
if err == nil {
return project , nil
}
if err != gorm . ErrRecordNotFound {
return project , err
}
2026-07-21 14:41:30 +08:00
project = models . SenlinAgentProject { OwnerID : ownerID , Name : name , Identifier : identifier , Description : description }
2026-07-20 13:02:26 +08:00
return project , tx . Create ( & project ) . Error
}
func seedProjectA1 ( tx * gorm . DB , userID uint , projectID uint ) error {
now := time . Now ( ) . UTC ( )
nextRun := now . Add ( 6 * time . Hour )
dueSoon := now . Add ( 72 * time . Hour )
dueLater := now . Add ( 7 * 24 * time . Hour )
for _ , tag := range [ ] string { "UI" , "知识碎片" , "客户反馈" , "自动化" } {
if err := firstOrCreateTag ( tx , projectID , tag ) ; err != nil {
return err
}
}
2026-07-21 00:46:09 +08:00
uiTagID , err := tagID ( tx , projectID , "UI" )
if err != nil {
return err
}
feedbackTagID , err := tagID ( tx , projectID , "客户反馈" )
if err != nil {
return err
}
automationTagID , err := tagID ( tx , projectID , "自动化" )
if err != nil {
return err
}
2026-07-20 13:02:26 +08:00
for _ , item := range [ ] models . SenlinAgentInboxItem {
{ ProjectID : projectID , CreatedBy : userID , SourceType : "产品研究" , Title : "竞争对手定价更新监控" , Body : "跟进企业版价格调整和功能组合变化。" , Status : "open" } ,
{ ProjectID : projectID , CreatedBy : userID , SourceType : "客户支持" , Title : "客户反馈:导出功能报错" , Body : "客户在导出智能报表时遇到权限校验异常。" , Status : "open" } ,
{ ProjectID : projectID , CreatedBy : userID , SourceType : "架构讨论" , Title : "API 调用策略评估建议" , Body : "评估批量任务调度和 AI 限流策略。" , Status : "open" } ,
{ ProjectID : projectID , CreatedBy : userID , SourceType : "需求文档" , Title : "更新需求文档 v1.3.2" , Body : "补充项目频道和属性弹层的交互说明。" , Status : "open" } ,
} {
if err := firstOrCreateInbox ( tx , item ) ; err != nil {
return err
}
}
for _ , task := range [ ] models . SenlinAgentTask {
2026-07-21 00:46:09 +08:00
{ ProjectID : projectID , CreatedBy : userID , TagID : & uiTagID , Title : "智能报表导出功能" , Description : "修复权限校验并补充导出失败提示。" , Status : "open" , DueAt : & dueSoon , SortOrder : 1 } ,
{ ProjectID : projectID , CreatedBy : userID , TagID : & automationTagID , Title : "企业版权限体系梳理" , Description : "整理角色、项目和任务分享边界。" , Status : "open" , DueAt : & dueLater , SortOrder : 2 } ,
{ ProjectID : projectID , CreatedBy : userID , TagID : & feedbackTagID , Title : "Q3 营销策略制定" , Description : "汇总竞品情报和客户反馈,生成策略草案。" , Status : "open" , DueAt : & dueLater , SortOrder : 3 } ,
2026-07-20 13:02:26 +08:00
} {
if err := firstOrCreateTask ( tx , task ) ; err != nil {
return err
}
}
for _ , session := range [ ] models . SenlinAgentAISession {
{ ProjectID : projectID , CreatedBy : userID , Title : "生成 Q3 营销策略初稿" , Context : "基于市场分析和竞品数据,输出 Q3 营销策略重点。" } ,
{ ProjectID : projectID , CreatedBy : userID , Title : "产品定价模型讨论" , Context : "对企业版与标准版定价模型进行对比分析。" } ,
{ ProjectID : projectID , CreatedBy : userID , Title : "客户流失原因分析" , Context : "分析近三个月客户流失数据与反馈原因。" } ,
{ ProjectID : projectID , CreatedBy : userID , Title : "行业趋势与机会洞察" , Context : "围绕 AI 在企业协同领域的趋势与机会。" } ,
} {
if err := firstOrCreateAISession ( tx , session ) ; err != nil {
return err
}
}
for _ , note := range [ ] models . SenlinAgentNote {
{ ProjectID : projectID , CreatedBy : userID , Title : "用户权限体系设计文档 v2.1" , Markdown : "# 权限体系\n\n围绕项目、任务和资料分享做保守授权。" } ,
{ ProjectID : projectID , CreatedBy : userID , Title : "导出功能技术方案评审" , Markdown : "# 导出功能\n\n记录权限校验和任务队列方案。" } ,
} {
if err := firstOrCreateNote ( tx , note ) ; err != nil {
return err
}
}
for _ , source := range [ ] models . SenlinAgentSource {
{ ProjectID : projectID , CreatedBy : userID , Kind : "file" , Title : "2026 Q3 竞品功能对比表" , FilePath : "projects/a1/competitors.xlsx" , ContentText : "竞品功能矩阵和价格摘要" } ,
{ ProjectID : projectID , CreatedBy : userID , Kind : "link" , Title : "客户访谈记录索引" , URL : "https://example.com/interviews" , ContentText : "客户访谈和问题归类" } ,
} {
if err := firstOrCreateSource ( tx , source ) ; err != nil {
return err
}
}
for _ , plan := range [ ] models . SenlinAgentCronPlan {
{ ProjectID : projectID , CreatedBy : userID , Title : "每日 Inbox 摘要" , Schedule : "0 9 * * *" , NextRunAt : & nextRun , Enabled : true , LastResult : "上次执行成功" } ,
{ ProjectID : projectID , CreatedBy : userID , Title : "每周任务风险扫描" , Schedule : "0 10 * * 1" , NextRunAt : & dueSoon , Enabled : true , LastResult : "等待下次执行" } ,
{ ProjectID : projectID , CreatedBy : userID , Title : "月底知识归档提醒" , Schedule : "0 18 28 * *" , NextRunAt : & dueLater , Enabled : false , LastResult : "已暂停" } ,
} {
if err := firstOrCreateCronPlan ( tx , plan ) ; err != nil {
return err
}
}
for _ , channel := range [ ] models . SenlinAgentProjectChannel {
{ ProjectID : projectID , Title : "客户研究频道" , Icon : "user" , URL : "https://example.com/research" , SortOrder : 7 } ,
{ ProjectID : projectID , Title : "产品设计频道" , Icon : "link" , URL : "https://example.com/design" , SortOrder : 8 } ,
} {
if err := firstOrCreateChannel ( tx , channel ) ; err != nil {
return err
}
}
return nil
}
func seedCompactProject ( tx * gorm . DB , userID uint , projectID uint ) error {
item := models . SenlinAgentInboxItem { ProjectID : projectID , CreatedBy : userID , SourceType : "手动收集" , Title : "待处理事项" , Body : "项目启动资料待整理。" , Status : "open" }
if err := firstOrCreateInbox ( tx , item ) ; err != nil {
return err
}
task := models . SenlinAgentTask { ProjectID : projectID , CreatedBy : userID , Title : "项目例会纪要整理" , Description : "归档本周同步内容。" , Status : "open" }
return firstOrCreateTask ( tx , task )
}
func firstOrCreateTag ( tx * gorm . DB , projectID uint , name string ) error {
return firstOrCreate ( tx , & models . SenlinAgentTag { } , "project_id = ? AND name = ?" , [ ] any { projectID , name } , models . SenlinAgentTag { ProjectID : projectID , Name : name } )
}
2026-07-21 00:46:09 +08:00
func tagID ( tx * gorm . DB , projectID uint , name string ) ( uint , error ) {
var tag models . SenlinAgentTag
if err := tx . Where ( "project_id = ? AND name = ?" , projectID , name ) . First ( & tag ) . Error ; err != nil {
return 0 , err
}
return tag . ID , nil
}
2026-07-20 13:02:26 +08:00
func firstOrCreateInbox ( tx * gorm . DB , item models . SenlinAgentInboxItem ) error {
return firstOrCreate ( tx , & models . SenlinAgentInboxItem { } , "project_id = ? AND title = ?" , [ ] any { item . ProjectID , item . Title } , item )
}
func firstOrCreateTask ( tx * gorm . DB , task models . SenlinAgentTask ) error {
2026-07-21 00:46:09 +08:00
var existing models . SenlinAgentTask
err := tx . Where ( "project_id = ? AND title = ?" , task . ProjectID , task . Title ) . First ( & existing ) . Error
if err == nil {
if task . TagID != nil && existing . TagID == nil {
return tx . Model ( & existing ) . Update ( "tag_id" , * task . TagID ) . Error
}
return nil
}
if err != gorm . ErrRecordNotFound {
return err
}
return tx . Create ( & task ) . Error
2026-07-20 13:02:26 +08:00
}
func firstOrCreateAISession ( tx * gorm . DB , session models . SenlinAgentAISession ) error {
return firstOrCreate ( tx , & models . SenlinAgentAISession { } , "project_id = ? AND title = ?" , [ ] any { session . ProjectID , session . Title } , session )
}
func firstOrCreateNote ( tx * gorm . DB , note models . SenlinAgentNote ) error {
return firstOrCreate ( tx , & models . SenlinAgentNote { } , "project_id = ? AND title = ?" , [ ] any { note . ProjectID , note . Title } , note )
}
func firstOrCreateSource ( tx * gorm . DB , source models . SenlinAgentSource ) error {
return firstOrCreate ( tx , & models . SenlinAgentSource { } , "project_id = ? AND title = ?" , [ ] any { source . ProjectID , source . Title } , source )
}
func firstOrCreateCronPlan ( tx * gorm . DB , plan models . SenlinAgentCronPlan ) error {
return firstOrCreate ( tx , & models . SenlinAgentCronPlan { } , "project_id = ? AND title = ?" , [ ] any { plan . ProjectID , plan . Title } , plan )
}
func firstOrCreateChannel ( tx * gorm . DB , channel models . SenlinAgentProjectChannel ) error {
return firstOrCreate ( tx , & models . SenlinAgentProjectChannel { } , "project_id = ? AND title = ?" , [ ] any { channel . ProjectID , channel . Title } , channel )
}
func firstOrCreate [ T any ] ( tx * gorm . DB , model * T , where string , args [ ] any , create T ) error {
err := tx . Where ( where , args ... ) . First ( model ) . Error
if err == nil {
return nil
}
if err != gorm . ErrRecordNotFound {
return err
}
return tx . Create ( & create ) . Error
}