feat: add topic summaries and growth profiles
This commit is contained in:
@@ -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, UserProfile } from "./types/api";
|
||||
import type { ChatMessage as ApiMessage, ChatSession, GrowthProfileResult, UserProfile } from "./types/api";
|
||||
|
||||
const user = ref<UserProfile | null>(null);
|
||||
const sessions = ref<ChatSession[]>([]);
|
||||
@@ -22,6 +22,10 @@ const loadingSession = ref(false);
|
||||
const loadingSessions = ref(false);
|
||||
const sessionOperationPending = ref(false);
|
||||
const logoutDialogOpen = ref(false);
|
||||
const profileDialogOpen = ref(false);
|
||||
const growthProfile = ref<GrowthProfileResult | null>(null);
|
||||
const profileLoading = ref(false);
|
||||
const finishingTopic = ref(false);
|
||||
const statusText = ref("连接后端中");
|
||||
const toastText = ref("");
|
||||
const followingOutput = ref(true);
|
||||
@@ -195,6 +199,33 @@ async function stop() {
|
||||
}
|
||||
}
|
||||
|
||||
async function finishCurrentTopic() {
|
||||
if (!activeSessionId.value || sending.value || finishingTopic.value) return;
|
||||
finishingTopic.value = true;
|
||||
try {
|
||||
const result = await api.finishTopic(activeSessionId.value);
|
||||
showToast(result.growthProfileEnabled ? "本主题已沉淀,并更新了你的成长档案" : "本主题已沉淀");
|
||||
await refreshSessionList();
|
||||
await refreshProfile();
|
||||
} catch (error) {
|
||||
handleError(error, "主题沉淀失败");
|
||||
} finally {
|
||||
finishingTopic.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openGrowthProfile() {
|
||||
profileDialogOpen.value = true;
|
||||
profileLoading.value = true;
|
||||
try {
|
||||
growthProfile.value = await api.growthProfile();
|
||||
} catch (error) {
|
||||
handleError(error, "成长档案加载失败");
|
||||
} finally {
|
||||
profileLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function renameSession(sessionId: number, title: string, done: (success: boolean) => void) {
|
||||
if (sessionOperationPending.value) return;
|
||||
sessionOperationPending.value = true;
|
||||
@@ -304,7 +335,14 @@ function showToast(message: string) {
|
||||
<LoginPanel v-else-if="!user" @logged-in="onLoggedIn" />
|
||||
<template v-else>
|
||||
<ChatHeader :user="user" :status-text="statusText" @open-history="drawerOpen = true" @logout="logoutDialogOpen = true" />
|
||||
<SessionQuota :used="user.todayUsed" :limit="user.dailyLimit" :entitlement="user.entitlement" />
|
||||
<SessionQuota
|
||||
:used="user.todayUsed"
|
||||
:limit="user.dailyLimit"
|
||||
:entitlement="user.entitlement"
|
||||
:finishing="finishingTopic"
|
||||
@finish-topic="finishCurrentTopic"
|
||||
@open-profile="openGrowthProfile"
|
||||
/>
|
||||
<MessageList
|
||||
ref="messageList"
|
||||
:messages="messages"
|
||||
@@ -329,6 +367,30 @@ function showToast(message: 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">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="profile-empty">还没有成长档案。你可以在完成一次主题对话后点击“沉淀本主题”。</p>
|
||||
<div v-if="growthProfile?.recentSummaries.length" class="recent-topic-summaries">
|
||||
<h3>最近主题沉淀</h3>
|
||||
<article v-for="item in growthProfile.recentSummaries" :key="item.id">
|
||||
<time>{{ item.generatedAt }}</time>
|
||||
<p>{{ item.summary }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog v-if="logoutDialogOpen" title="退出登录" labelled-by="logout-dialog-title" @close="logoutDialogOpen = false">
|
||||
<p class="confirm-copy">确定退出当前账号吗?</p>
|
||||
<template #footer>
|
||||
|
||||
Reference in New Issue
Block a user