feat(admin-brand): add question expansion service and related functionality

- Implemented QuestionExpansionService for generating and materializing questions based on combinations and AI distillation.
- Added question metadata classification logic to infer intent and layer of questions.
- Created API handlers for question expansion operations including combination preview, AI distillation, metadata classification, and materialization.
- Introduced database migrations to support new question-related fields and constraints in brand_questions and brand_keywords tables.
- Added caching mechanism for AI distillation results to improve performance.
- Defined JSON schemas for question distillation responses to ensure data integrity.
This commit is contained in:
2026-05-12 21:53:36 +08:00
parent 77d542c282
commit 37b0b32327
27 changed files with 4619 additions and 908 deletions
+31
View File
@@ -72,6 +72,8 @@ import type {
ListDesktopPublishTasksParams,
LoginRequest,
LoginResponse,
MaterializeQuestionsRequest,
MaterializeQuestionsResult,
MediaPlatform,
MonitoringCitationSummaryResponse,
MonitoringCollectNowResponse,
@@ -89,6 +91,9 @@ import type {
PublishKolPromptRequest,
PublishRecord,
Question,
QuestionCandidateResult,
QuestionCombinationRequest,
QuestionDistillRequest,
QuestionRequest,
QuotaSummary,
RecentArticle,
@@ -122,6 +127,8 @@ import type {
UpdateQuestionRequest,
UserInfo,
WorkspaceOverview,
ClassifyQuestionsRequest,
ClassifiedQuestion,
} from '@geo/shared-types'
import {
@@ -1036,6 +1043,30 @@ export const brandsApi = {
params: keywordId ? { keyword_id: keywordId } : undefined,
})
},
previewQuestionCombination(brandId: number, payload: QuestionCombinationRequest) {
return apiClient.post<QuestionCandidateResult, QuestionCombinationRequest>(
`/api/tenant/brands/${brandId}/questions/combination-preview`,
payload,
)
},
distillQuestions(brandId: number, payload: QuestionDistillRequest) {
return apiClient.post<QuestionCandidateResult, QuestionDistillRequest>(
`/api/tenant/brands/${brandId}/questions/ai-distill`,
payload,
)
},
classifyQuestions(brandId: number, payload: ClassifyQuestionsRequest) {
return apiClient.post<ClassifiedQuestion[], ClassifyQuestionsRequest>(
`/api/tenant/brands/${brandId}/questions/classify-metadata`,
payload,
)
},
materializeQuestions(brandId: number, payload: MaterializeQuestionsRequest) {
return apiClient.post<MaterializeQuestionsResult, MaterializeQuestionsRequest>(
`/api/tenant/brands/${brandId}/questions/materialize`,
payload,
)
},
createQuestion(brandId: number, payload: QuestionRequest) {
return apiClient.post<Question, QuestionRequest>(
`/api/tenant/brands/${brandId}/questions`,