feat: add teacher help cards

This commit is contained in:
2026-07-31 15:53:47 +08:00
parent 79094d0832
commit d8fde93b69
15 changed files with 467 additions and 4 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, UserProfile } from "./types/api";
import type { ChatMessage as ApiMessage, ChatSession, GrowthProfileResult, TeacherHelpCard, UserProfile } from "./types/api";
const user = ref<UserProfile | null>(null);
const sessions = ref<ChatSession[]>([]);
@@ -23,9 +23,13 @@ const loadingSessions = ref(false);
const sessionOperationPending = ref(false);
const logoutDialogOpen = ref(false);
const profileDialogOpen = ref(false);
const helpCardDialogOpen = ref(false);
const growthProfile = ref<GrowthProfileResult | null>(null);
const helpCard = ref<TeacherHelpCard | null>(null);
const helpCardContent = ref("");
const profileLoading = ref(false);
const finishingTopic = ref(false);
const generatingHelpCard = ref(false);
const statusText = ref("连接后端中");
const toastText = ref("");
const followingOutput = ref(true);
@@ -214,6 +218,39 @@ async function finishCurrentTopic() {
}
}
async function generateHelpCard() {
if (!activeSessionId.value || sending.value || generatingHelpCard.value) return;
generatingHelpCard.value = true;
try {
const result = await api.generateHelpCard(activeSessionId.value);
helpCard.value = result;
helpCardContent.value = result.content;
helpCardDialogOpen.value = true;
showToast("求助卡已生成,可编辑后复制给老师");
await refreshProfile();
} catch (error) {
handleError(error, "求助卡生成失败");
} finally {
generatingHelpCard.value = false;
}
}
async function copyHelpCard() {
if (!helpCardContent.value.trim()) {
showToast("求助卡内容为空");
return;
}
try {
await copyText(helpCardContent.value);
if (helpCard.value) {
helpCard.value = await api.markHelpCardCopied(helpCard.value.id);
}
showToast("已复制,可粘贴给老师或班级群");
} catch (error) {
handleError(error, "复制失败,请手动选择文本复制");
}
}
async function openGrowthProfile() {
profileDialogOpen.value = true;
profileLoading.value = true;
@@ -326,6 +363,22 @@ function showToast(message: string) {
toastTimer = null;
}, 3200);
}
async function copyText(text: string) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "true");
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}
</script>
<template>
@@ -340,8 +393,10 @@ function showToast(message: string) {
:limit="user.dailyLimit"
:entitlement="user.entitlement"
:finishing="finishingTopic"
:generating-help-card="generatingHelpCard"
@finish-topic="finishCurrentTopic"
@open-profile="openGrowthProfile"
@generate-help-card="generateHelpCard"
/>
<MessageList
ref="messageList"
@@ -391,6 +446,19 @@ function showToast(message: string) {
</section>
</AppDialog>
<AppDialog v-if="helpCardDialogOpen" title="老师求助卡" labelled-by="help-card-title" @close="helpCardDialogOpen = false">
<section class="help-card-dialog">
<p>这不是转人工工单系统不会自动发送给老师你可以按真实情况删改后自行复制给老师或班级群确认</p>
<textarea v-model="helpCardContent" aria-label="求助卡内容" />
</section>
<template #footer>
<div class="dialog-actions">
<button type="button" class="dialog-secondary" @click="helpCardDialogOpen = false">关闭</button>
<button type="button" class="dialog-primary" @click="copyHelpCard">复制求助卡</button>
</div>
</template>
</AppDialog>
<AppDialog v-if="logoutDialogOpen" title="退出登录" labelled-by="logout-dialog-title" @close="logoutDialogOpen = false">
<p class="confirm-copy">确定退出当前账号吗</p>
<template #footer>