feat: 将成长档案调整为近期实修回顾

This commit is contained in:
2026-08-03 12:25:51 +08:00
parent d2e08ab36c
commit 7747334ea4
29 changed files with 566 additions and 344 deletions

View File

@@ -9,7 +9,7 @@ import MessageList, { type DisplayMessage } from "./components/MessageList.vue";
import SessionDrawer from "./components/SessionDrawer.vue";
import SessionQuota from "./components/SessionQuota.vue";
import { ApiError, api, clearToken, getToken, streamChat } from "./services/api";
import type { ChatMessage as ApiMessage, ChatSession, GrowthProfileResult, PeriodicReport, ShareDraft, TeacherHelpCard, UserProfile } from "./types/api";
import type { ChatMessage as ApiMessage, ChatSession, PeriodicReport, PracticeReviewResult, ShareDraft, TeacherHelpCard, UserProfile } from "./types/api";
const user = ref<UserProfile | null>(null);
const sessions = ref<ChatSession[]>([]);
@@ -25,7 +25,7 @@ const logoutDialogOpen = ref(false);
const profileDialogOpen = ref(false);
const helpCardDialogOpen = ref(false);
const shareDraftDialogOpen = ref(false);
const growthProfile = ref<GrowthProfileResult | null>(null);
const practiceReview = ref<PracticeReviewResult | null>(null);
const helpCard = ref<TeacherHelpCard | null>(null);
const helpCardContent = ref("");
const shareDraft = ref<ShareDraft | null>(null);
@@ -218,7 +218,7 @@ async function finishCurrentTopic() {
try {
const result = await api.finishTopic(activeSessionId.value);
if (result.settlementStatus === "success" || result.settlementStatus === "fallback") {
showToast(result.growthProfileEnabled ? "本主题已沉淀,并更新了你的成长档案" : "本主题已沉淀");
showToast(result.growthProfileEnabled ? "本主题已沉淀,并更新了近期实修回顾" : "本主题已沉淀");
} else {
showToast("主题已结束,实修记录正在后台沉淀,可以继续使用其他功能");
void watchTopicSettlement(result.summary.id);
@@ -240,7 +240,7 @@ async function watchTopicSettlement(summaryId: number) {
try {
const result = await api.topicSettlement(summaryId);
if (result.settlementStatus === "success" || result.settlementStatus === "fallback") {
showToast(result.growthProfileEnabled ? "实修记录已沉淀,并更新了成长档案" : "实修记录已沉淀完成");
showToast(result.growthProfileEnabled ? "实修记录已沉淀,并更新了近期实修回顾" : "实修记录已沉淀完成");
await refreshProfile();
return;
}
@@ -330,22 +330,22 @@ async function copyShareDraft() {
}
}
async function openGrowthProfile() {
async function openPracticeReview() {
profileDialogOpen.value = true;
profileLoading.value = true;
try {
const [profileResult, helpCards, shareDrafts, reports] = await Promise.all([
api.growthProfile(),
api.practiceReview(),
api.helpCards(10),
api.shareDrafts(10),
api.periodicReports(10),
]);
growthProfile.value = profileResult;
practiceReview.value = profileResult;
helpCardHistory.value = helpCards;
shareDraftHistory.value = shareDrafts;
reportHistory.value = reports;
} catch (error) {
handleError(error, "成长档案加载失败");
handleError(error, "近期实修回顾加载失败");
} finally {
profileLoading.value = false;
}
@@ -484,7 +484,7 @@ async function copyText(text: string) {
:generating-help-card="generatingHelpCard"
:generating-share-draft="generatingShareDraft"
@finish-topic="finishCurrentTopic"
@open-profile="openGrowthProfile"
@open-profile="openPracticeReview"
@generate-help-card="generateHelpCard"
@generate-share-draft="generateShareDraft"
/>
@@ -512,23 +512,19 @@ async function copyText(text: string) {
<div v-if="toastText" class="chat-toast" role="status">{{ toastText }}</div>
<AppDialog v-if="profileDialogOpen" title="我的实修档案" labelled-by="growth-profile-title" @close="profileDialogOpen = false">
<AppDialog v-if="profileDialogOpen" title="近期实修回顾" labelled-by="growth-profile-title" @close="profileDialogOpen = false">
<section class="growth-profile-dialog" :aria-busy="profileLoading">
<p v-if="profileLoading" class="profile-empty">正在加载成长档案...</p>
<template v-else-if="growthProfile?.profile">
<pre>{{ growthProfile.profile.profileText }}</pre>
<div class="growth-profile-fields">
<p v-if="growthProfile.profile.recurringTopics"><strong>反复议题</strong>{{ growthProfile.profile.recurringTopics }}</p>
<p v-if="growthProfile.profile.commonEmotions"><strong>常见情绪</strong>{{ growthProfile.profile.commonEmotions }}</p>
<p v-if="growthProfile.profile.bodyPatterns"><strong>身体感受</strong>{{ growthProfile.profile.bodyPatterns }}</p>
<p v-if="growthProfile.profile.homeworkDone"><strong>做过的功课</strong>{{ growthProfile.profile.homeworkDone }}</p>
<p v-if="growthProfile.profile.recentProgress"><strong>最近进展</strong>{{ growthProfile.profile.recentProgress }}</p>
<p v-if="profileLoading" class="profile-empty">正在加载近期回顾...</p>
<template v-else-if="practiceReview?.profile">
<pre>{{ practiceReview.profile.reviewText }}</pre>
<div v-if="practiceReview.profile.currentFocus" class="growth-profile-fields">
<p><strong>近期关注</strong>{{ practiceReview.profile.currentFocus }}</p>
</div>
</template>
<p v-else class="profile-empty">还没有成长档案你可以在完成一次主题对话后点击沉淀本主题</p>
<div v-if="growthProfile?.recentSummaries.length" class="recent-topic-summaries">
<p v-else class="profile-empty">还没有近期回顾你可以在完成一次主题对话后点击沉淀本主题</p>
<div v-if="practiceReview?.recentSummaries.length" class="recent-topic-summaries">
<h3>最近主题沉淀</h3>
<article v-for="item in growthProfile.recentSummaries" :key="item.id">
<article v-for="item in practiceReview.recentSummaries" :key="item.id">
<time>{{ item.generatedAt }} · {{ settlementStatusLabel(item.status) }}</time>
<p v-if="item.status === 'success' || item.status === 'fallback'">{{ item.summary }}</p>
<p v-else-if="item.status === 'failed'">沉淀暂时失败对话内容仍已保留</p>
@@ -536,7 +532,7 @@ async function copyText(text: string) {
</article>
</div>
<div v-if="reportHistory.length" class="recent-topic-summaries periodic-report-list">
<h3>周期实修报告</h3>
<h3>周期实修回顾</h3>
<article v-for="item in reportHistory" :key="`report-${item.id}`">
<time>{{ item.reportTypeLabel }} · {{ item.periodStart }} {{ item.periodEnd }} · {{ item.generatedAt }}</time>
<p class="report-title">{{ item.title }}</p>

View File

@@ -52,7 +52,7 @@ const guidanceText = computed(() => {
class="quota-link"
@click="$emit('openProfile')"
>
我的档案
近期回顾
</button>
<button type="button" class="quota-link" :disabled="finishing" @click="$emit('finishTopic')">
{{ finishing ? '沉淀中' : '沉淀本主题' }}

View File

@@ -4,7 +4,7 @@ import type {
ChatMessage,
ChatSession,
FinishTopicResult,
GrowthProfileResult,
PracticeReviewResult,
LoginResult,
PeriodicReport,
ShareDraft,
@@ -96,7 +96,7 @@ export const api = {
generateShareDraft: (sessionId: number) => request<ShareDraft>(`/chat/session/${sessionId}/share-draft`, { method: "POST", body: JSON.stringify({}) }),
markShareDraftCopied: (draftId: number) => request<ShareDraft>(`/chat/share-draft/${draftId}/copied`, { method: "POST", body: JSON.stringify({}) }),
shareDrafts: (limit = 20) => request<ShareDraft[]>(`/chat/share-draft/list?limit=${limit}`),
growthProfile: () => request<GrowthProfileResult>("/user/growth-profile"),
practiceReview: () => request<PracticeReviewResult>("/user/growth-profile"),
periodicReports: (limit = 10) => request<PeriodicReport[]>(`/user/periodic-report/list?limit=${limit}`),
stop: (sessionId: number) => request<null>("/chat/stop", { method: "POST", body: JSON.stringify({ sessionId }) }),
};

View File

@@ -35,8 +35,9 @@ export interface UserEntitlementSummary {
export interface TopicSummary {
id: number;
topicSessionId: number;
schemaVersion: number;
summary: string;
recommendedHomework?: string | null;
currentFocus?: string | null;
nextObservation?: string | null;
status: "pending" | "running" | "success" | "fallback" | "failed" | string;
errorMessage?: string | null;
@@ -48,27 +49,24 @@ export interface TopicSummary {
generatedAt: string;
}
export interface GrowthProfile {
export interface PracticeReview {
id: number;
userId: number;
profileText: string;
recurringTopics?: string | null;
commonEmotions?: string | null;
bodyPatterns?: string | null;
relationPatterns?: string | null;
homeworkDone?: string | null;
effectiveHomework?: string | null;
recentProgress?: string | null;
schemaVersion: number;
reviewText: string;
currentFocus?: string | null;
sourceSummaryIds: number[];
updatedAt: string;
}
export interface GrowthProfileResult {
profile: GrowthProfile | null;
export interface PracticeReviewResult {
profile: PracticeReview | null;
recentSummaries: TopicSummary[];
}
export interface PeriodicReport {
id: number;
schemaVersion: number;
reportType: "weekly" | "monthly" | "stage" | string;
reportTypeLabel: string;
periodStart: string;
@@ -82,7 +80,7 @@ export interface PeriodicReport {
export interface FinishTopicResult {
topic: Record<string, unknown>;
summary: TopicSummary & Record<string, unknown>;
profile: GrowthProfile | null;
profile: PracticeReview | null;
growthProfileEnabled: boolean;
settlementStatus: string;
}