feat: 异步沉淀主题摘要和成长档案
This commit is contained in:
@@ -47,6 +47,7 @@ const selectedUserDetail = ref<AdminUserDetail | null>(null);
|
||||
const userDetailOpen = ref(false);
|
||||
const userDetailLoading = ref(false);
|
||||
const userReportGenerating = ref("");
|
||||
const userSettlementRetrying = ref<number | null>(null);
|
||||
const userKeyword = ref("");
|
||||
const entitlementPlans = ref<EntitlementPlan[]>([]);
|
||||
const models = ref<ModelItem[]>([]);
|
||||
@@ -375,11 +376,26 @@ async function refreshSelectedUserDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
async function retryTopicSettlement(topicId: number) {
|
||||
if (!selectedUserDetail.value || userSettlementRetrying.value !== null) return;
|
||||
userSettlementRetrying.value = topicId;
|
||||
try {
|
||||
await api.retryTopicSettlement(selectedUserDetail.value.user.id, topicId);
|
||||
selectedUserDetail.value = await api.userDetail(selectedUserDetail.value.user.id);
|
||||
ElMessage.success("主题沉淀任务已重新进入后台队列");
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : "主题沉淀重试失败");
|
||||
} finally {
|
||||
userSettlementRetrying.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function reportStatusLabel(status: string) {
|
||||
const labels: Record<string, string> = {
|
||||
pending: "等待生成",
|
||||
running: "生成中",
|
||||
success: "已完成",
|
||||
fallback: "已降级沉淀",
|
||||
empty: "暂无沉淀",
|
||||
failed: "生成失败",
|
||||
};
|
||||
@@ -1714,13 +1730,28 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<span>更新:{{ topic.updatedAt }}</span>
|
||||
</div>
|
||||
<template v-if="topic.summary">
|
||||
<pre>{{ topic.summary.summary }}</pre>
|
||||
<div class="topic-summary-fields">
|
||||
<div class="topic-summary-meta">
|
||||
<span>沉淀状态:{{ reportStatusLabel(topic.summary.status) }}</span>
|
||||
<span>执行次数:{{ topic.summary.attemptCount }}/{{ topic.summary.maxAttempts }}</span>
|
||||
<span v-if="topic.summary.lastStartedAt">开始执行:{{ topic.summary.lastStartedAt }}</span>
|
||||
<span v-if="topic.summary.finishedAt">完成:{{ topic.summary.finishedAt }}</span>
|
||||
</div>
|
||||
<p v-if="topic.summary.errorMessage" class="report-error">最近错误:{{ topic.summary.errorMessage }}</p>
|
||||
<pre v-if="topic.summary.status === 'success' || topic.summary.status === 'fallback'">{{ topic.summary.summary }}</pre>
|
||||
<p v-else-if="topic.summary.status === 'pending' || topic.summary.status === 'running'" class="report-pending">主题已结束,摘要和成长档案正在后台生成。</p>
|
||||
<div v-if="topic.summary.status === 'success' || topic.summary.status === 'fallback'" class="topic-summary-fields">
|
||||
<p v-if="topic.summary.emotions"><strong>情绪</strong>{{ topic.summary.emotions }}</p>
|
||||
<p v-if="topic.summary.bodyFeelings"><strong>身体感受</strong>{{ topic.summary.bodyFeelings }}</p>
|
||||
<p v-if="topic.summary.recommendedHomework"><strong>推荐功课</strong>{{ topic.summary.recommendedHomework }}</p>
|
||||
<p v-if="topic.summary.nextObservation"><strong>下一步观察</strong>{{ topic.summary.nextObservation }}</p>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="topic.summary.status === 'failed'"
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="userSettlementRetrying === topic.id"
|
||||
@click="retryTopicSettlement(topic.id)"
|
||||
>重新沉淀</el-button>
|
||||
</template>
|
||||
<el-empty v-else description="该主题尚未沉淀摘要" :image-size="60" />
|
||||
</section>
|
||||
|
||||
@@ -35,6 +35,7 @@ import type {
|
||||
QuestionInsightRefreshResult,
|
||||
QuestionInsightSummary,
|
||||
TopicSessionRecord,
|
||||
TopicSummaryRecord,
|
||||
} from "../types/api";
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "/api";
|
||||
@@ -135,6 +136,8 @@ export const api = {
|
||||
userReports: (id: number, limit = 20) => request<PeriodicReportRecord[]>(`/admin/user/${id}/reports${queryString({ limit })}`),
|
||||
generateUserReport: (id: number, payload: { reportType: string; periodStart?: string | null; periodEnd?: string | null }) =>
|
||||
request<PeriodicReportRecord>(`/admin/user/${id}/reports/generate`, { method: "POST", body: JSON.stringify(payload) }),
|
||||
retryTopicSettlement: (userId: number, topicId: number) =>
|
||||
request<TopicSummaryRecord>(`/admin/user/${userId}/topic/${topicId}/settlement/retry`, { method: "POST", body: "{}" }),
|
||||
createUser: (payload: Record<string, unknown>) =>
|
||||
request<AdminUser>("/admin/user", { method: "POST", body: JSON.stringify(payload) }),
|
||||
importUsers: (students: Record<string, unknown>[]) =>
|
||||
|
||||
@@ -500,6 +500,11 @@ export interface TopicSummaryRecord {
|
||||
modelName?: string | null;
|
||||
status: string;
|
||||
errorMessage?: string | null;
|
||||
attemptCount: number;
|
||||
maxAttempts: number;
|
||||
nextRunAt?: string | null;
|
||||
lastStartedAt?: string | null;
|
||||
finishedAt?: string | null;
|
||||
generatedAt: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user