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
+78 -12
View File
@@ -53,7 +53,7 @@ const columns = computed<TableColumnsType<AIPointUsageLog>>(() => [
title: t('aiPoints.table.points'),
dataIndex: 'points',
key: 'points',
width: 110,
width: 188,
align: 'right',
},
{
@@ -89,7 +89,7 @@ function handleTableChange(nextPage: number, nextPageSize: number): void {
function getAIPointUsageTypeLabel(usageType: string): string {
const key = `shell.aiUsageTypes.${usageType}`
const label = t(key)
return label === key ? usageType : label
return label === key ? t('aiPoints.usageTypeFallback') : label
}
function getAIPointStatusMeta(status: string): { label: string; color: string } {
@@ -108,8 +108,48 @@ function getAIPointStatusMeta(status: string): { label: string; color: string }
}
}
function formatAIPointDelta(item: AIPointUsageLog): string {
return item.status === 'refunded' ? `+${item.points}` : `-${item.points}`
function getAIPointStatusHelp(status: string): string | null {
switch (status) {
case 'pending':
return t('shell.aiUsagePendingHelp')
case 'refunded':
return t('aiPoints.refundedHelp')
default:
return null
}
}
function getAIPointAmountMeta(item: AIPointUsageLog): {
primary: string
detail: string
tone: 'charge' | 'refund' | 'neutral'
} {
switch (item.status) {
case 'refunded':
return {
primary: t('aiPoints.amount.netZero'),
detail: t('aiPoints.amount.refundedFlow', { points: item.points }),
tone: 'refund',
}
case 'pending':
return {
primary: `-${item.points}`,
detail: t('aiPoints.amount.pendingFlow', { points: item.points }),
tone: 'charge',
}
case 'completed':
return {
primary: `-${item.points}`,
detail: t('aiPoints.amount.completedFlow', { points: item.points }),
tone: 'charge',
}
default:
return {
primary: t('aiPoints.amount.zero'),
detail: t('aiPoints.amount.failedFlow'),
tone: 'neutral',
}
}
}
</script>
@@ -208,12 +248,13 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
</template>
<template v-else-if="column.key === 'points'">
<span
class="points-delta"
:class="{ 'points-delta--refund': record.status === 'refunded' }"
<div
class="points-cell"
:class="`points-cell--${getAIPointAmountMeta(record).tone}`"
>
{{ formatAIPointDelta(record) }}
</span>
<strong>{{ getAIPointAmountMeta(record).primary }}</strong>
<span>{{ getAIPointAmountMeta(record).detail }}</span>
</div>
</template>
<template v-else-if="column.key === 'request_chars'">
@@ -229,7 +270,10 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
:bordered="false"
>
{{ getAIPointStatusMeta(record.status).label }}
<a-tooltip v-if="record.status === 'pending'" :title="t('shell.aiUsagePendingHelp')">
<a-tooltip
v-if="getAIPointStatusHelp(record.status)"
:title="getAIPointStatusHelp(record.status)"
>
<QuestionCircleOutlined class="status-help-icon" />
</a-tooltip>
</a-tag>
@@ -397,15 +441,37 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
color: #cf1322 !important;
}
.points-delta {
.points-cell {
display: grid;
gap: 2px;
justify-items: end;
min-width: 132px;
line-height: 1.35;
}
.points-cell strong {
color: #cf1322;
font-weight: 600;
}
.points-delta--refund {
.points-cell span {
color: #8c8c8c;
font-size: 12px;
white-space: nowrap;
}
.points-cell--refund strong {
color: #595959;
}
.points-cell--refund span {
color: #389e0d;
}
.points-cell--neutral strong {
color: #595959;
}
.status-tag {
display: inline-flex;
align-items: center;