Files
OCS_service/app/stores/answer.ts
T

262 lines
7.7 KiB
TypeScript

// app/stores/answer.ts - 答题页和 Dashboard 的统一状态管理
import { defineStore } from "pinia";
import type {
IHealthResponse,
IQaRecord,
ISearchSuccessResponse,
IStatsResponse,
QuestionType
} from "@/interfaces";
import { AnswerService } from "@/services";
/** Dashboard 最近记录表格每页数量,组件直接复用,避免多个地方写死 */
export const DASHBOARD_RECORD_PAGE_SIZE = 10;
/** 分页大小可选列表,与后端白名单保持一致 */
export const DASHBOARD_PAGE_SIZE_OPTIONS = [10, 50, 100] as const;
/** 题型下拉选项,value 保持 OCS / 旧 Python 服务使用的英文值 */
export const QUESTION_TYPE_OPTIONS: Array<{
label: string;
value: QuestionType;
}> = [
{ label: "自动判断", value: "" },
{ label: "单选题", value: "single" },
{ label: "多选题", value: "multiple" },
{ label: "判断题", value: "judgement" },
{ label: "填空题", value: "completion" }
];
/** 题型展示文案,Dashboard 表格用它把英文值转成本地业务表达 */
export const QUESTION_TYPE_LABELS: Record<string, string> = {
"": "自动判断",
single: "单选题",
multiple: "多选题",
judgement: "判断题",
completion: "填空题"
};
/** 从 `$fetch` 错误里提取安全文案;服务端已经保证 message/msg 不含敏感上游细节 */
const getClientErrorMessage = (error: unknown, fallback: string) => {
if (typeof error !== "object" || error === null) return fallback;
const data = "data" in error ? (error as { data?: unknown }).data : undefined;
if (typeof data === "object" && data !== null) {
const message = (data as { message?: unknown; msg?: unknown }).message;
const msg = (data as { message?: unknown; msg?: unknown }).msg;
if (typeof message === "string" && message) return message;
if (typeof msg === "string" && msg) return msg;
}
return fallback;
};
/** 把运行秒数格式化为 dashboard 更容易扫读的时长 */
const formatUptime = (seconds: number) => {
const safeSeconds = Math.max(0, Math.floor(seconds));
const days = Math.floor(safeSeconds / 86_400);
const hours = Math.floor((safeSeconds % 86_400) / 3_600);
const minutes = Math.floor((safeSeconds % 3_600) / 60);
if (days > 0) return `${days}${hours} 小时`;
if (hours > 0) return `${hours} 小时 ${minutes} 分钟`;
return `${Math.max(1, minutes)} 分钟`;
};
/** OCS AI 答题服务前端状态管理 */
export const useAnswerStore = defineStore("answer", () => {
// ---- 答题表单状态 ----
const question = ref("");
const questionType = ref<QuestionType>("");
const options = ref("");
const searchLoading = ref(false);
const answerResult = ref<ISearchSuccessResponse>();
const searchErrorMessage = ref("");
// ---- Dashboard 状态 ----
const health = ref<IHealthResponse>();
const stats = ref<IStatsResponse>();
const records = ref<IQaRecord[]>([]);
const dashboardErrorMessage = ref("");
const statsLoading = ref(false);
const recordsLoading = ref(false);
const cacheClearing = ref(false);
const recordsPage = ref(1);
const recordsPageSize = ref(DASHBOARD_RECORD_PAGE_SIZE);
const recordCount = ref(0);
const selectedRecord = ref<IQaRecord>();
/** Dashboard 表格当前页数据,页码切换会重新请求服务端分页接口 */
const pagedRecords = computed(() => records.value);
/** Dashboard 顶部运行时长展示 */
const uptimeText = computed(() => {
return stats.value ? formatUptime(stats.value.uptime) : "-";
});
/** 选择表格记录,交给详情弹窗展示完整题目、选项和答案 */
const selectRecord = (record?: IQaRecord) => {
selectedRecord.value = record;
};
/** 切换最近记录表格页码,并请求对应页数据 */
const setRecordsPage = async (page: number) => {
if (recordsLoading.value || page === recordsPage.value) return;
await getRecords(page);
};
/** 切换分页大小,重置到第 1 页后重新请求 */
const setRecordsPageSize = async (size: number) => {
if (recordsLoading.value || size === recordsPageSize.value) return;
recordsPageSize.value = size;
await getRecords(1);
};
/** 调用答题 API,返回 OCS 兼容结构但只在 store 中维护 UI 状态 */
const searchAnswer = async () => {
const title = question.value.trim();
const requestOptions = options.value.trim();
answerResult.value = undefined;
searchErrorMessage.value = "";
if (!title) {
searchErrorMessage.value = "未提供问题内容";
return;
}
searchLoading.value = true;
try {
const res = await AnswerService.search({
title,
type: questionType.value,
options: requestOptions
});
if (res.data.code === 1) {
answerResult.value = res.data;
recordsPage.value = 1;
await Promise.allSettled([getStats(), getRecords()]);
return;
}
searchErrorMessage.value = res.data.msg || "答题失败";
} catch (error) {
searchErrorMessage.value = getClientErrorMessage(error, "服务器内部错误");
} finally {
searchLoading.value = false;
}
};
/** 读取健康检查,失败时只写本地错误文案 */
const getHealth = async () => {
try {
const res = await AnswerService.getHealth();
health.value = res.data;
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
"健康检查失败"
);
}
};
/** 读取运行统计 */
const getStats = async () => {
statsLoading.value = true;
try {
const res = await AnswerService.getStats();
stats.value = res.data;
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
"统计信息读取失败"
);
} finally {
statsLoading.value = false;
}
};
/** 读取最近问答记录;服务端按 page/size 返回当前页数据 */
const getRecords = async (page = recordsPage.value) => {
recordsLoading.value = true;
try {
const res = await AnswerService.getRecords({
page,
size: recordsPageSize.value
});
records.value = res.data.records;
recordsPage.value = res.data.page;
recordsPageSize.value = res.data.size;
recordCount.value = res.data.total;
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
"问答记录读取失败"
);
} finally {
recordsLoading.value = false;
}
};
/** Dashboard 首次加载时并行拉取运行状态、统计和最近记录 */
const loadDashboard = async () => {
dashboardErrorMessage.value = "";
await Promise.allSettled([getHealth(), getStats(), getRecords()]);
};
/** 清空缓存和问答记录后刷新统计与表格 */
const clearCache = async () => {
cacheClearing.value = true;
dashboardErrorMessage.value = "";
try {
const res = await AnswerService.clearCache();
if (!res.data.success) {
dashboardErrorMessage.value = res.data.message;
}
await Promise.allSettled([getStats(), getRecords(1)]);
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
"缓存清理失败"
);
} finally {
cacheClearing.value = false;
}
};
return {
question,
questionType,
options,
searchLoading,
answerResult,
searchErrorMessage,
health,
stats,
records,
dashboardErrorMessage,
statsLoading,
recordsLoading,
cacheClearing,
recordsPage,
recordsPageSize,
selectedRecord,
pagedRecords,
recordCount,
uptimeText,
searchAnswer,
getHealth,
getStats,
getRecords,
loadDashboard,
clearCache,
selectRecord,
setRecordsPage,
setRecordsPageSize
};
});