feat(questions): make brand questions the primary monitoring & template axis
Deployment Config CI / Deployment Config (push) Successful in 29s
Frontend CI / Frontend (push) Successful in 4m4s
Backend CI / Backend (push) Failing after 7m12s

- add /questions/combination-fill endpoint with AI-driven, IP-region-aware matrix fill
- extract ip2region resolver from ops/app into shared/ipregion for cross-service use
- thread question_id filter through dashboard composite, citation summary, and collect-now
- switch template wizard from keyword inputs to brand-question selection (primary + supplemental)
- pass brand_question and supplemental_questions through assist/title/outline prompts
- add AiWaitingModal + 45s client timeout and request_timeout error mapping for long AI flows

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 15:59:39 +08:00
parent 37b0b32327
commit 1eae6fb6d4
36 changed files with 2119 additions and 412 deletions
+18 -2
View File
@@ -92,6 +92,8 @@ import type {
PublishRecord,
Question,
QuestionCandidateResult,
QuestionCombinationFillRequest,
QuestionCombinationFillResult,
QuestionCombinationRequest,
QuestionDistillRequest,
QuestionRequest,
@@ -150,6 +152,7 @@ const rawBaseURL = import.meta.env.VITE_API_BASE_URL ?? ''
const baseURL = normalizeApiBaseURL(rawBaseURL)
const generationStreamEnabled = import.meta.env.VITE_GENERATION_STREAM_ENABLED === 'true'
const accessRefreshSkewMs = 2 * 60 * 1000
const aiDistillRequestTimeoutMs = 45_000
const publicClient = createApiClient({ baseURL })
let storedRefreshPromise: Promise<AuthTokens> | null = null
@@ -1049,10 +1052,18 @@ export const brandsApi = {
payload,
)
},
fillQuestionCombination(brandId: number, payload: QuestionCombinationFillRequest) {
return apiClient.post<QuestionCombinationFillResult, QuestionCombinationFillRequest>(
`/api/tenant/brands/${brandId}/questions/combination-fill`,
payload,
{ timeout: aiDistillRequestTimeoutMs },
)
},
distillQuestions(brandId: number, payload: QuestionDistillRequest) {
return apiClient.post<QuestionCandidateResult, QuestionDistillRequest>(
`/api/tenant/brands/${brandId}/questions/ai-distill`,
payload,
{ timeout: aiDistillRequestTimeoutMs },
)
},
classifyQuestions(brandId: number, payload: ClassifyQuestionsRequest) {
@@ -1219,6 +1230,7 @@ export const monitoringApi = {
dashboardComposite(params: {
brand_id?: number
keyword_id?: number | null
question_id?: number | null
days?: number
business_date?: string
ai_platform_id?: string
@@ -1234,6 +1246,7 @@ export const monitoringApi = {
days?: number
brand_id?: number
keyword_id?: number | null
question_id?: number | null
business_date?: string
ai_platform_id?: string
}) {
@@ -1259,10 +1272,13 @@ export const monitoringApi = {
{ params },
)
},
collectNow(brandId: number, payload?: { keyword_id?: number | null; platform_ids?: string[] }) {
collectNow(
brandId: number,
payload?: { keyword_id?: number | null; question_id?: number | null; platform_ids?: string[] },
) {
return apiClient.post<
MonitoringCollectNowResponse,
{ keyword_id?: number | null; platform_ids?: string[] }
{ keyword_id?: number | null; question_id?: number | null; platform_ids?: string[] }
>(`/api/tenant/monitoring/brands/${brandId}/collect-now`, payload ?? {})
},
}