Refactor brand service and related components

- Removed ListQuestionVersions method and associated types from BrandService and Queries.
- Updated PromptRuleGenerationService to change task naming and handling.
- Enhanced ScheduleTaskService to support filtering by created date range and keyword.
- Introduced InstantTaskService for managing instant tasks with appropriate filtering and response structures.
- Added InstantTaskHandler for handling API requests related to instant tasks.
- Created InstantTaskTab component for the admin web interface to display and manage instant tasks.
- Updated database migrations to rename source types for articles and generation tasks.
- Adjusted models and repository queries to reflect the removal of question version handling.
This commit is contained in:
2026-04-02 21:16:12 +08:00
parent 111498a65f
commit 8958cb44c0
36 changed files with 1378 additions and 358 deletions
@@ -62,6 +62,8 @@ type promptRuleGenerationRecord struct {
Status string
}
const promptRuleGeneratingTitlePlaceholder = "标题生成中"
func NewPromptRuleGenerationService(
pool *pgxpool.Pool,
llmClient llm.Client,
@@ -121,12 +123,12 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
return nil, response.ErrBadRequest(40018, "generate_count_not_supported", "prompt rule instant generation currently supports generate_count = 1 only")
}
initialTitle := strings.TrimSpace(extractString(extra, "task_name"))
if initialTitle == "" {
initialTitle = strings.TrimSpace(rule.Name)
taskName := strings.TrimSpace(extractString(extra, "task_name"))
if taskName == "" {
taskName = strings.TrimSpace(rule.Name)
}
if initialTitle == "" {
initialTitle = "Generated Article"
if taskName == "" {
taskName = "即时任务"
}
quotaRepo := repository.NewQuotaRepository(s.pool)
@@ -136,11 +138,14 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
}
inputParams := map[string]interface{}{
"title": initialTitle,
"prompt_rule_id": req.PromptRuleID,
"task_name": taskName,
"target_platform": strings.TrimSpace(derefString(req.TargetPlatform)),
"enable_web_search": extractBool(extra, "enable_web_search"),
}
if generationMode := strings.TrimSpace(extractString(extra, "generation_mode")); generationMode != "" {
inputParams["generation_mode"] = generationMode
}
platformIDs := parsePlatformIDs(derefString(req.TargetPlatform))
if len(platformIDs) > 0 {
inputParams["target_platforms"] = platformIDs
@@ -150,7 +155,7 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
}
inputJSON, _ := json.Marshal(inputParams)
wizardStateJSON, _ := mergeArticleWizardState(nil, initialTitle, platformIDs)
wizardStateJSON, _ := mergeArticleWizardState(nil, promptRuleGeneratingTitlePlaceholder, platformIDs)
tx, err := s.pool.Begin(ctx)
if err != nil {
@@ -195,7 +200,7 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
var articleID int64
err = tx.QueryRow(ctx, `
INSERT INTO articles (tenant_id, source_type, prompt_rule_id, generate_status, publish_status, wizard_state_json)
VALUES ($1, 'prompt_rule', $2, 'generating', 'unpublished', $3)
VALUES ($1, 'custom_generation', $2, 'generating', 'unpublished', $3)
RETURNING id
`, actor.TenantID, req.PromptRuleID, wizardStateJSON).Scan(&articleID)
if err != nil {
@@ -211,7 +216,7 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
TenantID: actor.TenantID,
ArticleID: &articleID,
QuotaReservationID: &reservationID,
TaskType: "prompt_rule",
TaskType: "custom_generation",
InputParamsJSON: inputJSON,
})
if err != nil {
@@ -230,7 +235,7 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
ReservationID: reservationID,
Prompt: buildPromptRuleGenerationPrompt(rule, inputParams),
InputParams: inputParams,
InitialTitle: initialTitle,
InitialTitle: promptRuleGeneratingTitlePlaceholder,
WebSearch: llm.WebSearchOptions{
Enabled: extractBool(extra, "enable_web_search"),
},
@@ -242,7 +247,7 @@ func (s *PromptRuleGenerationService) GenerateFromRule(ctx context.Context, req
}
if s.streamEnabled {
s.streams.Start(articleID, taskID, initialTitle)
s.streams.Start(articleID, taskID, promptRuleGeneratingTitlePlaceholder)
}
return &GenerateFromRuleResponse{ArticleID: articleID, TaskID: taskID}, nil
@@ -392,6 +397,10 @@ func (s *PromptRuleGenerationService) executeGeneration(ctx context.Context, job
func (s *PromptRuleGenerationService) failGeneration(ctx context.Context, job promptRuleGenerationJob, title, stage string, genErr error) {
errMsg := formatGenerationFailure(stage, genErr)
now := time.Now()
failureTitle := strings.TrimSpace(extractString(job.InputParams, "task_name"))
if failureTitle == "" {
failureTitle = title
}
auditRepo := repository.NewAuditRepository(s.pool)
articleRepo := repository.NewArticleRepository(s.pool)
@@ -405,6 +414,14 @@ func (s *PromptRuleGenerationService) failGeneration(ctx context.Context, job pr
CompletedAt: &now,
})
_ = articleRepo.UpdateArticleGenerateStatus(ctx, job.ArticleID, job.TenantID, "failed")
if failureTitle != "" {
_, _ = s.pool.Exec(ctx, `
UPDATE articles
SET wizard_state_json = jsonb_set(COALESCE(wizard_state_json, '{}'::jsonb), '{title}', to_jsonb($1::text), true),
updated_at = NOW()
WHERE id = $2 AND tenant_id = $3
`, failureTitle, job.ArticleID, job.TenantID)
}
balance, balanceErr := quotaRepo.GetCurrentBalance(ctx, job.TenantID, "article_generation")
if balanceErr == nil {
@@ -426,7 +443,7 @@ func (s *PromptRuleGenerationService) failGeneration(ctx context.Context, job pr
_ = quotaRepo.RefundReservation(ctx, job.ReservationID, job.TenantID)
if s.streamEnabled {
s.streams.Fail(job.ArticleID, job.TaskID, title, errMsg)
s.streams.Fail(job.ArticleID, job.TaskID, failureTitle, errMsg)
}
}
@@ -434,8 +451,8 @@ func buildPromptRuleGenerationPrompt(rule *promptRuleGenerationRecord, params ma
sections := []string{strings.TrimSpace(rule.PromptContent)}
var supplements []string
if title := strings.TrimSpace(extractString(params, "title")); title != "" {
supplements = append(supplements, "任务名称:"+title)
if taskName := strings.TrimSpace(extractString(params, "task_name")); taskName != "" {
supplements = append(supplements, "任务名称:"+taskName)
}
if rule.Scene != nil && strings.TrimSpace(*rule.Scene) != "" {
supplements = append(supplements, "适用场景:"+strings.TrimSpace(*rule.Scene))
@@ -454,7 +471,7 @@ func buildPromptRuleGenerationPrompt(rule *promptRuleGenerationRecord, params ma
sections = append(sections, "补充要求:\n- "+strings.Join(supplements, "\n- "))
}
sections = append(sections, "输出要求:\n1. 直接输出完整可发布的中文 Markdown 文章。\n2. 标题使用一级标题。\n3. 不要输出你的思考过程、解释或致歉。")
sections = append(sections, "输出要求:\n1. 直接输出完整可发布的中文 Markdown 文章。\n2. 标题使用一级标题,并由 AI 根据正文内容自行生成,不要直接复用任务名称。\n3. 不要输出你的思考过程、解释或致歉。")
return strings.Join(sections, "\n\n")
}